Skip to content

Commit a5a9d95

Browse files
Michael JordansnowystingerLFDanLu
authored
fix(#3245): useColorArea and ColorArea src updates found while writing docs (#3278)
* fix(#3245): fix useColorArea description * fix(#3245): fix variable name for colorAreaLabellingProps * fix(#3245): add comma to color area group aria-label for consistency * fix(#3245): ColorArea fix logic for adding aria-hidden and tabIndex to 2nd input * fix(#3245): ColorArea: simplify layout of stories using ColorWheel * fix(#3245): add descriptions for props Per #3268 (comment) * fix(#3245): add ColorArea description for docs * fix(#3245): Color component hooks should support WHCM Add `forcedColorAdjust: 'none'` to the ColorArea, Gradient and Track and Thumb background styles to better support Windows high contrast mode out of the box. * fix(#3345): useColorSlider add forcedColorAdjust: 'none' to thumb and track Co-authored-by: Robert Snow <[email protected]> Co-authored-by: Daniel Lu <[email protected]>
1 parent b24ac46 commit a5a9d95

File tree

9 files changed

+86
-33
lines changed

9 files changed

+86
-33
lines changed

packages/@react-aria/color/src/useColorArea.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ interface ColorAreaAriaProps extends AriaColorAreaProps {
4444
}
4545

4646
/**
47-
* Provides the behavior and accessibility implementation for a color wheel component.
48-
* Color wheels allow users to adjust the hue of an HSL or HSB color value on a circular track.
47+
* Provides the behavior and accessibility implementation for a color area component.
48+
* Color area allows users to adjust two channels of an RGB, HSL or HSB color value against a two-dimensional gradient background.
4949
*/
5050
export function useColorArea(props: ColorAreaAriaProps, state: ColorAreaState): ColorAreaAria {
5151
let {
@@ -351,10 +351,10 @@ export function useColorArea(props: ColorAreaAriaProps, state: ColorAreaState):
351351
'aria-label': ariaLabel ? formatMessage('colorInputLabel', {label: ariaLabel, channelLabel: colorPickerLabel}) : colorPickerLabel
352352
});
353353

354-
let colorAriaLabellingProps = useLabels(
354+
let colorAreaLabellingProps = useLabels(
355355
{
356356
...props,
357-
'aria-label': ariaLabel ? `${ariaLabel} ${colorPickerLabel}` : undefined
357+
'aria-label': ariaLabel ? `${ariaLabel}, ${colorPickerLabel}` : undefined
358358
},
359359
isMobile ? colorPickerLabel : undefined
360360
);
@@ -382,7 +382,7 @@ export function useColorArea(props: ColorAreaAriaProps, state: ColorAreaState):
382382

383383
return {
384384
colorAreaProps: {
385-
...colorAriaLabellingProps,
385+
...colorAreaLabellingProps,
386386
...colorAreaInteractions,
387387
...colorAreaStyleProps,
388388
role: 'group'
@@ -414,7 +414,7 @@ export function useColorArea(props: ColorAreaAriaProps, state: ColorAreaState):
414414
add aria-hidden="true" to the unfocused control when the value has not changed via the keyboard,
415415
but remove aria-hidden to reveal the input for each channel when the value has changed with the keyboard.
416416
*/
417-
'aria-hidden': (!isMobile && focusedInputRef.current === inputYRef.current && !valueChangedViaKeyboard.current ? 'true' : undefined),
417+
'aria-hidden': (isMobile || !focusedInputRef.current || focusedInputRef.current === inputXRef.current || valueChangedViaKeyboard.current ? undefined : 'true'),
418418
onChange: (e: ChangeEvent<HTMLInputElement>) => {
419419
state.setXValue(parseFloat(e.target.value));
420420
}
@@ -432,13 +432,13 @@ export function useColorArea(props: ColorAreaAriaProps, state: ColorAreaState):
432432
'aria-orientation': 'vertical',
433433
disabled: isDisabled,
434434
value: state.value.getChannelValue(yChannel),
435-
tabIndex: (isMobile || focusedInputRef.current === inputYRef.current ? undefined : -1),
435+
tabIndex: (isMobile || (focusedInputRef.current && focusedInputRef.current === inputYRef.current) ? undefined : -1),
436436
/*
437437
So that only a single "2d slider" control shows up when listing form elements for screen readers,
438438
add aria-hidden="true" to the unfocused input when the value has not changed via the keyboard,
439439
but remove aria-hidden to reveal the input for each channel when the value has changed with the keyboard.
440440
*/
441-
'aria-hidden': (isMobile || focusedInputRef.current === inputYRef.current || valueChangedViaKeyboard.current ? undefined : 'true'),
441+
'aria-hidden': (isMobile || (focusedInputRef.current && focusedInputRef.current === inputYRef.current) || valueChangedViaKeyboard.current ? undefined : 'true'),
442442
onChange: (e: ChangeEvent<HTMLInputElement>) => {
443443
state.setYValue(parseFloat(e.target.value));
444444
}

packages/@react-aria/color/src/useColorAreaGradient.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,17 +218,21 @@ export function useColorAreaGradient({direction, state, zChannel, xChannel, isDi
218218
x = 1 - x;
219219
}
220220

221+
let forcedColorAdjustNoneStyle = {forcedColorAdjust: 'none'};
222+
221223
return {
222224
colorAreaStyleProps: {
223225
style: {
224226
position: 'relative',
225227
touchAction: 'none',
228+
...forcedColorAdjustNoneStyle,
226229
...background.colorAreaStyles
227230
}
228231
},
229232
gradientStyleProps: {
230233
style: {
231234
touchAction: 'none',
235+
...forcedColorAdjustNoneStyle,
232236
...background.gradientStyles
233237
}
234238
},
@@ -238,7 +242,8 @@ export function useColorAreaGradient({direction, state, zChannel, xChannel, isDi
238242
left: `${x * 100}%`,
239243
top: `${y * 100}%`,
240244
transform: 'translate(0%, 0%)',
241-
touchAction: 'none'
245+
touchAction: 'none',
246+
...forcedColorAdjustNoneStyle
242247
}
243248
}
244249
};

packages/@react-aria/color/src/useColorSlider.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import {AriaColorSliderProps} from '@react-types/color';
1414
import {ColorSliderState} from '@react-stately/color';
15-
import {HTMLAttributes, RefObject} from 'react';
15+
import {HTMLAttributes, InputHTMLAttributes, RefObject} from 'react';
1616
import {mergeProps} from '@react-aria/utils';
1717
import {useLocale} from '@react-aria/i18n';
1818
import {useSlider, useSliderThumb} from '@react-aria/slider';
@@ -32,7 +32,7 @@ interface ColorSliderAria {
3232
/** Props for the thumb element. */
3333
thumbProps: HTMLAttributes<HTMLElement>,
3434
/** Props for the visually hidden range input element. */
35-
inputProps: HTMLAttributes<HTMLElement>,
35+
inputProps: InputHTMLAttributes<HTMLInputElement>,
3636
/** Props for the output element, displaying the value of the color slider. */
3737
outputProps: HTMLAttributes<HTMLElement>
3838
}
@@ -99,16 +99,25 @@ export function useColorSlider(props: ColorSliderAriaOptions, state: ColorSlider
9999
}
100100
};
101101

102+
let forcedColorAdjustNoneStyle = {forcedColorAdjust: 'none'};
103+
102104
return {
103105
trackProps: {
104106
...mergeProps(groupProps, trackProps),
105107
style: {
106108
...trackProps.style,
109+
...forcedColorAdjustNoneStyle,
107110
background: generateBackground()
108111
}
109112
},
110113
inputProps,
111-
thumbProps,
114+
thumbProps: {
115+
...thumbProps,
116+
style: {
117+
...thumbProps.style,
118+
...forcedColorAdjustNoneStyle
119+
}
120+
},
112121
labelProps,
113122
outputProps
114123
};

packages/@react-aria/color/src/useColorWheel.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,11 @@ export function useColorWheel(props: ColorWheelAriaProps, state: ColorWheelState
257257
});
258258

259259
let {minValue, maxValue, step} = state.value.getChannelRange('hue');
260+
261+
let forcedColorAdjustNoneStyle = {
262+
forcedColorAdjust: 'none'
263+
};
264+
260265
return {
261266
trackProps: {
262267
...trackInteractions,
@@ -283,7 +288,8 @@ export function useColorWheel(props: ColorWheelAriaProps, state: ColorWheelState
283288
hsl(360, 100%, 50%)
284289
)
285290
`,
286-
clipPath: `path(evenodd, "${circlePath(outerRadius, outerRadius, outerRadius)} ${circlePath(outerRadius, outerRadius, innerRadius)}")`
291+
clipPath: `path(evenodd, "${circlePath(outerRadius, outerRadius, outerRadius)} ${circlePath(outerRadius, outerRadius, innerRadius)}")`,
292+
...forcedColorAdjustNoneStyle
287293
}
288294
},
289295
thumbProps: {
@@ -293,7 +299,8 @@ export function useColorWheel(props: ColorWheelAriaProps, state: ColorWheelState
293299
left: '50%',
294300
top: '50%',
295301
transform: `translate(calc(${x}px - 50%), calc(${y}px - 50%))`,
296-
touchAction: 'none'
302+
touchAction: 'none',
303+
...forcedColorAdjustNoneStyle
297304
}
298305
},
299306
inputProps: mergeProps(

packages/@react-spectrum/color/src/ColorArea.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,8 @@ function ColorArea(props: SpectrumColorAreaProps, ref: FocusableRef<HTMLDivEleme
8282
);
8383
}
8484

85+
/**
86+
* ColorArea allows users to adjust two channels of an RGB, HSL or HSB color value against a two-dimensional gradient background.
87+
*/
8588
let _ColorArea = React.forwardRef(ColorArea) as (props: SpectrumColorAreaProps & {ref?: FocusableRef<HTMLDivElement>}) => ReactElement;
8689
export {_ColorArea as ColorArea};

packages/@react-spectrum/color/stories/ColorArea.stories.tsx

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,12 @@
1212

1313
import {action} from '@storybook/addon-actions';
1414
import {ColorArea, ColorField, ColorSlider, ColorWheel} from '../';
15-
import {Flex} from '@adobe/react-spectrum';
15+
import {Flex, Grid, View} from '@adobe/react-spectrum';
1616
import {Meta, Story} from '@storybook/react';
1717
import {parseColor} from '@react-stately/color';
1818
import React, {useState} from 'react';
1919
import {SpectrumColorAreaProps} from '@react-types/color';
2020

21-
2221
const meta: Meta<SpectrumColorAreaProps> = {
2322
title: 'ColorArea',
2423
component: ColorArea
@@ -50,32 +49,48 @@ function ColorAreaExample(props: SpectrumColorAreaProps) {
5049
return (
5150
<div role="group" aria-label={`${ariaLabel ? `${ariaLabel} ` : ''}${colorSpace.toUpperCase()} Color Picker`}>
5251
<Flex gap="size-500" alignItems="start">
53-
<Flex direction="column" gap={isHue ? 0 : 'size-50'} alignItems="center">
54-
<ColorArea
55-
size={isHue ? 'size-1200' : null}
56-
{...props}
57-
value={color}
58-
onChange={onChange}
59-
onChangeEnd={props.onChangeEnd} />
60-
{isHue ? (
61-
<ColorWheel
52+
{isHue ? (
53+
<Flex direction="column" gap={0} alignItems="center">
54+
<View
55+
position="relative"
56+
width="size-2400">
57+
<Grid
58+
position="absolute"
59+
justifyContent="center"
60+
alignContent="center"
61+
width="100%"
62+
height="100%">
63+
<ColorArea
64+
size={'size-1200'}
65+
{...props}
66+
value={color}
67+
onChange={onChange}
68+
onChangeEnd={props.onChangeEnd} />
69+
</Grid>
70+
<ColorWheel
71+
size={'size-2400'}
72+
value={color}
73+
onChange={onChange}
74+
onChangeEnd={props.onChangeEnd}
75+
isDisabled={isDisabled} />
76+
</View>
77+
</Flex>
78+
) : (
79+
<Flex direction="column" gap={'size-50'} alignItems="center">
80+
<ColorArea
81+
{...props}
6282
value={color}
6383
onChange={onChange}
6484
onChangeEnd={props.onChangeEnd}
65-
isDisabled={isDisabled}
66-
size={'size-2400'}
67-
UNSAFE_style={{
68-
marginTop: 'calc( -.75 * var(--spectrum-global-dimension-size-2400))'
69-
}} />
70-
) : (
85+
isDisabled={isDisabled} />
7186
<ColorSlider
7287
value={color}
7388
onChange={onChange}
7489
onChangeEnd={props.onChangeEnd}
7590
channel={zChannel}
7691
isDisabled={isDisabled} />
77-
)}
78-
</Flex>
92+
</Flex>
93+
)}
7994
<Flex direction="column" alignItems="center" gap="size-100" minWidth="size-1200">
8095
<div
8196
role="img"

packages/@react-spectrum/color/test/ColorArea.test.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ describe('ColorArea', () => {
109109
expect(ySlider).toHaveAttribute('aria-label', 'Color hue, Color picker');
110110
expect(xSlider).not.toHaveAttribute('aria-labelledby');
111111
expect(ySlider).not.toHaveAttribute('aria-labelledby');
112+
113+
let colorAreaGroup = xSlider.closest('[role="group"]');
114+
expect(colorAreaGroup).toHaveAttribute('aria-label', 'Color hue, Color picker');
115+
expect(colorAreaGroup).not.toHaveAttribute('aria-labelledby');
112116
});
113117

114118
it('should support a custom aria-labelledby', () => {
@@ -119,6 +123,10 @@ describe('ColorArea', () => {
119123
expect(ySlider).toHaveAttribute('aria-label', 'Color picker');
120124
expect(xSlider).toHaveAttribute('aria-labelledby', `label-id ${xSlider.id}`);
121125
expect(ySlider).toHaveAttribute('aria-labelledby', `label-id ${ySlider.id}`);
126+
127+
let colorAreaGroup = xSlider.closest('[role="group"]');
128+
expect(colorAreaGroup).toHaveAttribute('aria-labelledby', 'label-id');
129+
expect(colorAreaGroup).not.toHaveAttribute('aria-label');
122130
});
123131
});
124132
});

packages/@react-stately/color/src/useColorAreaState.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,13 @@ export interface ColorAreaState {
5353

5454
/** Returns the xChannel, yChannel and zChannel names based on the color value. */
5555
channels: {xChannel: ColorChannel, yChannel: ColorChannel, zChannel: ColorChannel},
56+
/** The step value of the xChannel, used when incrementing and decrementing. */
5657
xChannelStep: number,
58+
/** The step value of the yChannel, used when incrementing and decrementing. */
5759
yChannelStep: number,
60+
/** The page step value of the xChannel, used when incrementing and decrementing. */
5861
xChannelPageStep: number,
62+
/** The page step value of the yChannel, used when incrementing and decrementing. */
5963
yChannelPageStep: number,
6064

6165
/** Returns the color that should be displayed in the color area thumb instead of `value`. */

packages/@react-stately/color/src/useColorWheelState.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ export interface ColorWheelState {
4242
setDragging(value: boolean): void,
4343
/** Returns the color that should be displayed in the color wheel instead of `value`. */
4444
getDisplayColor(): Color,
45+
/** The step value of the hue channel, used when incrementing and decrementing. */
4546
step: number,
47+
/** The page step value of the hue channel, used when incrementing and decrementing. */
4648
pageStep: number
4749
}
4850

0 commit comments

Comments
 (0)