Skip to content

Commit

Permalink
fix(color-picker): Add support for a11y labels on color swatches (#2894)
Browse files Browse the repository at this point in the history
Fixes: #701 

Set `role`, `aria-label`, and `aria-selected` to provide better accessibility when user is interacting with color swatches

[category:Accessibility]

Co-authored-by: Kiwook Kwon <[email protected]>
  • Loading branch information
wooksauce and Kiwook Kwon authored Aug 29, 2024
1 parent 631d1ec commit 8e4c674
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 27 deletions.
14 changes: 10 additions & 4 deletions modules/preview-react/color-picker/lib/ColorPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {FormField} from '@workday/canvas-kit-preview-react/form-field';
import styled from '@emotion/styled';

import {ResetButton} from './parts/ColorReset';
import {SwatchBook} from './parts/SwatchBook';
import {SwatchBook, SwatchBookColorObject} from './parts/SwatchBook';

export interface ColorPickerProps extends React.HTMLAttributes<HTMLDivElement> {
/**
Expand All @@ -21,7 +21,7 @@ export interface ColorPickerProps extends React.HTMLAttributes<HTMLDivElement> {
/**
* The array of colors to be rendered in the swatchbook.
*/
colorSet?: string[];
colorSet?: string[] | SwatchBookColorObject[];
/**
* If true, render an input for entering a custom hex color.
* @default false
Expand Down Expand Up @@ -149,13 +149,19 @@ const HexColorInput = styled(ColorInput)({
width: '168px',
});

const isCustomColor = (colors: string[], hexCode?: string) => {
const isCustomColor = (colors: (string | SwatchBookColorObject)[], hexCode?: string) => {
if (!hexCode) {
return false;
}

const lowercaseHex = hexCode.toLowerCase();
return !colors.includes(lowercaseHex);
return !colors.some((color: string | SwatchBookColorObject) => {
if (typeof color === 'string') {
return color.toLowerCase() === lowercaseHex;
} else {
return color.value.toLowerCase() === lowercaseHex;
}
});
};

export const ColorPicker = ({
Expand Down
56 changes: 34 additions & 22 deletions modules/preview-react/color-picker/lib/parts/SwatchBook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import {borderRadius, colors, space} from '@workday/canvas-kit-react/tokens';
import {focusRing, mouseFocusBehavior} from '@workday/canvas-kit-react/common';
import {ColorSwatch} from '@workday/canvas-kit-react/color-picker';

export interface SwatchBookColorObject {
value: string;
label: string;
}

export interface SwatchBookProps {
colors: string[];
colors: (string | SwatchBookColorObject)[];
value?: string;
onSelect: (color: string) => void;
}
Expand Down Expand Up @@ -58,26 +63,33 @@ const Container = styled('div')({
margin: `0px -${space.xxs} -${space.xxs} 0px`,
});

export const SwatchBook = ({colors, value, onSelect}: SwatchBookProps) => (
<Container>
{colors.map((color, index) => {
const isSelected = value ? color.toLowerCase() === value.toLowerCase() : false;
export const SwatchBook = ({colors, value, onSelect}: SwatchBookProps) => {
return (
<Container>
{colors.map((color: string | SwatchBookColorObject, index: number) => {
const hexCode = typeof color === 'object' ? color.value : color;
const label = typeof color === 'object' ? color.label : color;
const isSelected = value ? hexCode.toLowerCase() === value.toLowerCase() : false;

const handleClick = () => onSelect(color);
const handleKeyDown = (event: React.KeyboardEvent) =>
(event.key === 'Enter' || event.key === ' ') && onSelect(color);
const handleClick = () => onSelect(hexCode);
const handleKeyDown = (event: React.KeyboardEvent) =>
(event.key === 'Enter' || event.key === ' ') && onSelect(hexCode);

return (
<SwatchContainer
key={index + '-' + color}
onClick={handleClick}
onKeyDown={handleKeyDown}
tabIndex={0}
isSelected={isSelected}
>
<ColorSwatch color={color} showCheck={isSelected} />
</SwatchContainer>
);
})}
</Container>
);
return (
<SwatchContainer
key={index + '-' + color}
onClick={handleClick}
onKeyDown={handleKeyDown}
tabIndex={0}
isSelected={isSelected}
role="button"
aria-label={label}
aria-selected={isSelected}
>
<ColorSwatch color={hexCode} showCheck={isSelected} />
</SwatchContainer>
);
})}
</Container>
);
};
41 changes: 40 additions & 1 deletion modules/preview-react/color-picker/spec/ColorPicker.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import {fireEvent, render} from '@testing-library/react';
import {fireEvent, render, screen} from '@testing-library/react';
import {colors} from '@workday/canvas-kit-react/tokens';
import {ColorPicker, ColorPickerProps} from '@workday/canvas-kit-preview-react/color-picker';

Expand Down Expand Up @@ -49,6 +49,18 @@ describe('Color Picker', () => {

expect(getByRole('textbox')).not.toBeNull();
});

it('should work with color objects', () => {
const {getByRole} = renderColorPicker({
colorSet: [
{label: 'Cinnamon', value: colors.cinnamon200},
{label: 'Blueberry', value: colors.blueberry400},
],
showCustomHexInput: true,
});

expect(getByRole('textbox')).not.toBeNull();
});
});

describe('reset button', () => {
Expand Down Expand Up @@ -87,4 +99,31 @@ describe('Color Picker', () => {
});
});
});
describe('accessibility', () => {
it('should have correct aria attributes', () => {
renderColorPicker({value: colors.blueberry400});
const swatchCinnamon = screen.getByRole('button', {name: /#fcc9c5/});
const swatchBlueberry = screen.getByRole('button', {name: /#0875e1/});

expect(swatchCinnamon).toHaveAttribute('aria-selected', 'false');
expect(swatchBlueberry).toHaveAttribute('aria-selected', 'true');

expect(swatchCinnamon.getAttribute('aria-label')).toBe('#fcc9c5');
expect(swatchBlueberry.getAttribute('aria-label')).toBe('#0875e1');
});
it('should use color labels when provided', () => {
renderColorPicker({
colorSet: [
{label: 'Cinnamon', value: colors.cinnamon200},
{label: 'Blueberry', value: colors.blueberry400},
],
value: colors.cinnamon200,
});
const swatchCinnamon = screen.getByRole('button', {name: /Cinnamon/});
const swatchBlueberry = screen.getByRole('button', {name: /Blueberry/});

expect(swatchCinnamon.getAttribute('aria-label')).toBe('Cinnamon');
expect(swatchBlueberry.getAttribute('aria-label')).toBe('Blueberry');
});
});
});

0 comments on commit 8e4c674

Please sign in to comment.