cerner / terra-core

Terra offers a set of configurable React components designed to help build scalable and modular application UIs. This UI library was created to solve real-world issues in projects we work on day to day.
http://terra-ui.com
Apache License 2.0
181 stars 167 forks source link

terra-image UX Audit #3079

Open ryanthemanuel opened 4 years ago

ryanthemanuel commented 4 years ago

terra-image

Known Aliases

Document any known aliases of the component, decide if another name makes more sense.

Design Standard Doc

Any documentation needed to be added to terra-ui describing the make up and usage of the component Any doc examples that need to be updated?

Guides

Any guides needed to be added to terra-ui describing the usage of this component when used with other components.

Accessibility Guides

Behaviours

Accessibility

Deprecation

Related Issues

Other Considerations

It could be possible in the future to provide some canned placeholders to be used in well defined situations.

supreethmr commented 2 years ago

Tech Design

Summary:

Approach 1

Add imageType prop to terra-image to differentiate between informative and decorative image.

Requirements and Accessibility:

As a consumer of Terra-Image, I would like it have API to support for meaningful images (ability to be read by screen-readers) when they add important context for users and support for decorative images (ignored and hidden to screen-readers), so that Image meets WCAG Success Criterion 1.1.1 (Non-text Content).

New React Props:

Terra-Image

Prop Type isRequired Default Description
imageType Enum optional 'informative' Specifies Type of image which is either informative OR decorative images

Implementation

    createImage(customProps, imageClasses) {
    const {
      src, alt, height, width, imageType
    } = this.props;

    if (imageType === ImageTypes.DECORATIVE) {
     // Decorative Image
      return (
        <img
-          {...customProps}
          src={src}
+          role="presentation"
+          alt=""
          height={height}
          width={width}
          onLoad={this.handleOnLoad}
          onError={this.handleOnError}
          className={imageClasses}
          ref={this.ImageRef}
        />
      );
    }

// Informative Image (default )
    return (
      <img
        {...customProps}
        src={src}
        alt={alt}
        height={height}
        width={width}
        onLoad={this.handleOnLoad}
        onError={this.handleOnError}
        className={imageClasses}
        ref={this.ImageRef}
      />
    );
  }

Usage Example

import Image from 'terra-image';

export default () => <Image src={placeholder150x150} title="example image" imageType="decorative" />;
import Image from 'terra-image';

export default () => <Image src={placeholder150x150} title="example image" imageType="informative" alt="example image"/>;

With this approach we would require to flex the image tag depending on it's type. For Decorative Image we need to

Flexing between image types might add up more functions in a single file and might became difficult to maintain when we provide support for more image types along with informative and decorative.

supreethmr commented 2 years ago

Tech Design

Summary:

Approach 2

Add new subcomponent Decorative Image to render Image API as a non-text content.

Requirements and Accessibility:

As a consumer of Terra-Image, I would like it have API to support for meaningful images (ability to be read by screen-readers) when they add important context for users and support for decorative images (ignored and hidden to screen-readers), so that Image meets WCAG Success Criterion 1.1.1 (Non-text Content).

React Props of New Sub Component:

Decorative-Image

Prop Type isRequired Default Description
src string required none The source string for the image which will be loaded and displayed.
variant Enum optional 'default' Sets the style of the image from the following values; default, rounded, circle, thumbnail.
isFluid bool optional false Sets the fluid behavior of the image, which is non-fluid by default.
placeholder element optional none A React element which will be displayed during loading and in case of src load failure.
height string optional none Sets the height of the image
width string optional none Sets the width of the image
onLoad function optional none Function to be executed when the image load is successful.
onError function optional none Function to be executed when the image load errors.
fit enum optional 'fill' Sets the object-fit style of the image from the following values; cover, contain, fill, scale-down, none.

Implementation

import Image from './Image';
import DecorativeImage from './variants/DecorativeImage';

export default Image;
export { DecorativeImage };
createImage(imageClasses) {
    const {
      src, height, width,
    } = this.props;

    return (
      <img
        src={src}
        role="presentation"
        alt=""
        height={height}
        width={width}
        onLoad={this.handleOnLoad}
        onError={this.handleOnError}
        className={imageClasses}
        ref={this.ImageRef}
      />
    );
  }

Usage Example

import { DecorativeImage } from 'terra-image';

export default () => <DecorativeImage src={placeholder150x150} title="example image" />;
import Image from 'terra-image'; OR import InformativeImage from 'terra-image'; 

export default () => <Image src={placeholder150x150} title="example image"  alt="example image" />;

With this Approach we can create new sub-components in future when new image types are created. This reduces the load of an existing API by moving the implementation of new image types to different file.

Existing Image would remain as default Export of the package and new code changes are passive to existing Image API's.

benbcai commented 2 years ago

I think in design #2 to create a new subcomponent for this is unnecessary because most of the core features remain the same with the exception of the image type. For this reason, I like design #1 better and it is easier to maintain.

What is the reason to prevent spreading the custom props for a decorative image?

benbcai commented 2 years ago

alt is currently a required prop. For a decorative image it is no longer needed and will be overwritten with an empty string. Do we still have to keep it as required? I think so because it is still needed for the informative image type. Maybe we can just update the prop description to call out that it is not required if the imageType is decorative and will be set to an empty string.

sdadn commented 2 years ago

I agree with @benbcai . While it is good to plan ahead to easily allow updates to the API, Design 2 feels overkill with the scope of this story and information we have right now. I opt for design 1 because it's simpler to consume. A consumer may want to update their UI and update an image from informative or decorative and they can do that by simply updating a prop in Design 1 instead of needing to change the component.

supreethmr commented 2 years ago

alt is currently a required prop. For a decorative image it is no longer needed and will be overwritten with an empty string. Do we still have to keep it as required? I think so because it is still needed for the informative image type. Maybe we can just update the prop description to call out that it is not required if the imageType is decorative and will be set to an empty string.

Keeping alt prop as required would make it inappropriate for decorative image as consumer cannot skip alt prop and passing alt prop with empty string will not make sense for consumer as it should be handled internally.

So either we should make alt prop as optional with doc update to highlight that value for alt prop is required to make image accessible to assistive technologies.

supreethmr commented 2 years ago

I think in design #2 to create a new subcomponent for this is unnecessary because most of the core features remain the same with the exception of the image type. For this reason, I like design #1 better and it is easier to maintain.

we can actually move the code to different file and refer it on both Image.jsx and DecorativeImage.jsx I think this would not require much effort on code review as we are not introducing lot of code changes with new subcomponent. To make this code changes passive the existing Image API will remain unchanged as a default API for informative Images.

What is the reason to prevent spreading the custom props for a decorative image?

Edit: for custom props there are few props that would be inappropriate to use like title, aria-label, aria-labelledby and aria-describedby . these attributes would make decorative image accessible to assistive technologies. So these props should be removed from image when the imageType is decorative

Aria role presentation throws axe error when any of the aria attributes are included in the element.

benbcai commented 2 years ago

for custom props there are few props that would be inappropriate to use like title, aria-label, aria-labelledby and aria-describedby . these attributes would make decorative image accessible to assistive technologies. So these props should be removed from image when the imageType is decorative

Could we selectively delete these unwanted props instead of not spreading all custom props?

For examples:

delete customProps.title;
delete customProps.aria-label;
neilpfeiffer commented 2 years ago
Tech Design Decision

Based on our conversations, the API that has the best the method to educate consumers as to their responsibility for accessibility, Approach 2 will be the most clear and have the most amount of enforceability.

While Approach 1 is more flexible, its puts more onus on the consumer to understand their accessibility responsibilities, and it has more opportunities to create erratic end behaviors if intended to be decorative but title, aria-label are provided, or meant not to be decorative and alt is not able to be strictly enforced.

Approach 2 also has ability to not create necessarily additional code overhead for maintenance as the two exposed components will largely be exposing API-only, and we can move all of the current component logic into a shared single private subcomponent that smartly will add or remove the props and relevant html attributes to create stable markup for both situations. It also allows for additional W3C image concepts types in the future that will have their own unique API demands.

To summarize the changes:

Image.jsx

DecorativeImage.jsx

/private/_image.jsx

This will also resolve https://github.com/cerner/terra-core/issues/3396