Skip to content

Commit 3f29f01

Browse files
authored
feat: accessibility #46 (#47)
1 parent 0627ac2 commit 3f29f01

File tree

7 files changed

+297
-57
lines changed

7 files changed

+297
-57
lines changed

src/components/ColorBox/AlphaSlider.jsx

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,14 @@ export default styled(({ color, ...other }) => <Slider {...other} />)`
3737
border-radius: 4px;
3838
}
3939
& .MuiSlider-thumb {
40-
width: 8px;
41-
border: 1px solid #9e9e9e;
42-
height: 20px;
43-
opacity: 0.8;
44-
margin-top: -2px;
45-
margin-left: -4px;
46-
border-radius: 4px;
47-
background-color: #fff;
48-
&:focus,
49-
&:hover,
50-
&$active {
51-
box-shadow: inherit;
40+
width: 16px;
41+
height: 16px;
42+
margin-top: 0px;
43+
margin-left: -8px;
44+
background-color: #f0f0f0;
45+
box-shadow: rgba(0, 0, 0, 0.37) 0px 1px 4px 0px;
46+
&:focus {
47+
box-shadow: 0px 0px 0px 8px rgba(63, 81, 181, 0.16);
5248
}
5349
}
5450
`;

src/components/ColorBox/HSVGradient.jsx

Lines changed: 91 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import React from 'react';
1111
import PropTypes from 'prop-types';
1212
import styled from 'styled-components';
1313
import * as CommonTypes from '../../helpers/commonTypes';
14+
import useEventCallback from '../../helpers/useEventCallback';
1415

1516
const getRGB = _h => {
1617
let rgb;
@@ -61,43 +62,62 @@ const StyledRoot = styled.div`
6162
position: absolute;
6263
top: 0px;
6364
left: 0px;
65+
border: 1px solid #f0f0f0;
66+
box-shadow: rgba(0, 0, 0, 0.37) 0px 1px 4px 0px;
67+
transition: box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
68+
border-radius: 4px;
6469
cursor: ${props => !props.pressed && 'pointer'};
6570
zindex: 100;
71+
transform: translate(-4px, -4px);
72+
}
73+
& .muicc-hsvgradient-cursor:hover {
74+
box-shadow: 0px 0px 0px 8px rgba(63, 81, 181, 0.16);
75+
}
76+
& .muicc-hsvgradient-cursor:focus {
77+
outline: none !important;
78+
box-shadow: 0px 0px 0px 8px rgba(63, 81, 181, 0.16);
79+
}
80+
& .muicc-hsvgradient-cursor:focus > div {
81+
// TODO
6682
}
6783
& .muicc-hsvgradient-cursor-c {
6884
width: 8px;
6985
height: 8px;
7086
border-radius: 4px;
71-
box-shadow: rgb(255, 255, 255) 0px 0px 0px 1.5px, rgba(0, 0, 0, 0.3) 0px 0px 1px 1px inset,
72-
rgba(0, 0, 0, 0.4) 0px 0px 1px 2px;
73-
transform: translate(-4px, -4px);
87+
box-shadow: white 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
7488
}
7589
`;
7690

7791
const HSVGradient = ({ className, color, onChange, ...props }) => {
7892
const latestColor = React.useRef(color);
93+
const [focus, onFocus] = React.useState(false);
94+
const [pressed, setPressed] = React.useState(false);
7995
React.useEffect(() => {
8096
latestColor.current = color;
8197
});
8298
const box = React.useRef();
8399
const cursor = React.useRef();
100+
let cursorPos = { x: 0, y: 0 };
84101
const rgb = getRGB(color.hsv[0]);
85102
const cssRgb = `rgb(${rgb[0]},${rgb[1]},${rgb[2]})`;
86-
const [pressed, setPressed] = React.useState(false);
87103

88-
const setPosition = pos => {
104+
const setPosition = (pos, f) => {
105+
cursorPos = pos;
89106
cursor.current.style.top = `${pos.y}px`;
90107
cursor.current.style.left = `${pos.x}px`;
108+
if (f) {
109+
cursor.current.focus();
110+
}
91111
};
92112

93113
const initPosition = ref => {
94114
if (ref) {
95115
const { hsv } = color;
96-
const pos = {
116+
cursorPos = {
97117
x: Math.round((hsv[1] / 100) * (ref.clientWidth - 1)),
98118
y: Math.round((1 - hsv[2] / 100) * (ref.clientHeight - 1)),
99119
};
100-
setPosition(pos);
120+
setPosition(cursorPos);
101121
}
102122
};
103123

@@ -106,10 +126,9 @@ const HSVGradient = ({ className, color, onChange, ...props }) => {
106126
box.current.style.background = `${cssRgb} none repeat scroll 0% 0%`;
107127
}
108128

109-
const convertMousePosition = (event, ref) => {
110-
const { pageX, pageY } = event;
129+
const convertMousePosition = ({ x, y }, ref) => {
111130
const bounds = ref.getBoundingClientRect();
112-
const pos = { x: pageX - bounds.left, y: pageY - bounds.top };
131+
const pos = { x: x - bounds.left, y: y - bounds.top };
113132
if (pos.x < 0) {
114133
pos.x = 0;
115134
}
@@ -122,7 +141,7 @@ const HSVGradient = ({ className, color, onChange, ...props }) => {
122141
if (pos.y >= ref.clientHeight) {
123142
pos.y = ref.clientHeight - 1;
124143
}
125-
setPosition(pos);
144+
setPosition(pos, true);
126145
const s = (pos.x / (ref.clientWidth - 1)) * 100;
127146
const v = (1 - pos.y / (ref.clientHeight - 1)) * 100;
128147
const c = latestColor.current;
@@ -132,39 +151,95 @@ const HSVGradient = ({ className, color, onChange, ...props }) => {
132151
React.useEffect(() => {
133152
const ref = box.current;
134153
initPosition(ref);
135-
const handleDown = () => {
154+
const handleDown = event => {
155+
onFocus(true);
136156
setPressed(true);
157+
event.preventDefault();
137158
};
138159
const handleUp = event => {
139-
convertMousePosition(event, ref);
160+
convertMousePosition({ x: event.pageX, y: event.pageY }, ref);
140161
setPressed(false);
162+
event.preventDefault();
141163
};
142164
const handleMove = event => {
143165
if (pressed || event.buttons) {
144-
convertMousePosition(event, ref);
166+
convertMousePosition({ x: event.pageX, y: event.pageY }, ref);
167+
event.preventDefault();
145168
}
146169
};
170+
const handleTouch = event => {
171+
const xy = { x: event.touches[0].pageX, y: event.touches[0].pageY };
172+
convertMousePosition(xy, ref);
173+
event.preventDefault();
174+
};
175+
147176
ref.addEventListener('mousedown', handleDown);
148177
ref.addEventListener('mouseup', handleUp);
149178
ref.addEventListener('mousemove', handleMove);
179+
ref.addEventListener('touchdown', handleDown);
180+
ref.addEventListener('touchup', handleUp);
181+
ref.addEventListener('touchmove', handleTouch);
150182
return () => {
151183
ref.removeEventListener('mousedown', handleDown);
152184
ref.removeEventListener('mouseup', handleUp);
153185
ref.removeEventListener('mousemove', handleMove);
186+
ref.removeEventListener('touchdown', handleDown);
187+
ref.removeEventListener('touchup', handleUp);
188+
ref.removeEventListener('touchmove', handleTouch);
154189
};
155190
}, []);
156-
191+
const handleKey = useEventCallback(event => {
192+
if (!focus) return;
193+
let { x, y } = cursorPos;
194+
switch (event.key) {
195+
case 'ArrowRight':
196+
x += 1;
197+
break;
198+
case 'ArrowLeft':
199+
x -= 1;
200+
break;
201+
case 'ArrowDown':
202+
y += 1;
203+
break;
204+
case 'ArrowUp':
205+
y -= 1;
206+
break;
207+
case 'Tab':
208+
onFocus(false);
209+
return;
210+
default:
211+
return;
212+
}
213+
event.preventDefault();
214+
const bounds = box.current.getBoundingClientRect();
215+
convertMousePosition({ x: x + bounds.left, y: y + bounds.top }, box.current);
216+
});
217+
const handleFocus = useEventCallback(event => {
218+
onFocus(true);
219+
event.preventDefault();
220+
});
221+
const handleBlur = useEventCallback(event => {
222+
onFocus(false);
223+
event.preventDefault();
224+
});
157225
return (
158-
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
159226
<div className={className}>
160227
<StyledRoot {...props} ref={box} cssRgb={cssRgb} data-testid="hsvgradient-color">
161228
<div className="muicc-hsvgradient-s">
162229
<div className="muicc-hsvgradient-v">
163230
<div
164231
ref={cursor}
232+
tabIndex="0"
233+
role="slider"
234+
aria-valuemax={100}
235+
aria-valuemin={0}
236+
aria-valuenow={color.hsv[1]}
165237
pressed={`${pressed}`}
166238
data-testid="hsvgradient-cursor"
167239
className="muicc-hsvgradient-cursor"
240+
onKeyDown={handleKey}
241+
onFocus={handleFocus}
242+
onBlur={handleBlur}
168243
>
169244
<div className="muicc-hsvgradient-cursor-c" />
170245
</div>

src/components/ColorBox/HueSlider.jsx

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import Slider from '@material-ui/core/Slider';
1010

1111
export default styled(Slider)`
1212
width: 100%;
13-
height: 24px;
13+
height: 16px;
1414
padding: 0;
1515
& .MuiSlider-rail {
16-
height: 24px;
16+
height: 16px;
1717
opacity: 1;
1818
background: rgba(0, 0, 0, 0)
1919
linear-gradient(
@@ -30,24 +30,20 @@ export default styled(Slider)`
3030
border-radius: 0;
3131
}
3232
& .MuiSlider-track {
33-
height: 24px;
33+
height: 16px;
3434
opacity: 0;
3535
border-radius: 4px;
3636
background-color: transparent;
3737
}
3838
& .MuiSlider-thumb {
39-
width: 8px;
40-
border: 1px solid #9e9e9e;
41-
height: 28px;
42-
opacity: 0.8;
43-
margin-top: -2px;
44-
margin-left: -4px;
45-
border-radius: 4px;
46-
background-color: #fff;
47-
&:focus,
48-
&:hover,
49-
&$active {
50-
box-shadow: inherit;
39+
width: 16px;
40+
height: 16px;
41+
margin-top: 0px;
42+
margin-left: -8px;
43+
background-color: #f0f0f0;
44+
box-shadow: rgba(0, 0, 0, 0.37) 0px 1px 4px 0px;
45+
&:focus {
46+
box-shadow: 0px 0px 0px 8px rgba(63, 81, 181, 0.16);
5147
}
5248
}
5349
`;

src/components/ColorBox/index.jsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,20 @@ const StyledBox = styled.div`
4242
width: ${props => `${props.boxWidth}px`};
4343
padding: 0px;
4444
& .muicc-colorbox-hsvgradient {
45-
width: ${props => `calc(${props.boxWidth}px - 12px)`};
46-
height: calc(128px - 12px);
47-
margin: 6px;
45+
width: ${props => `calc(${props.boxWidth}px - 16px)`};
46+
height: calc(128px - 16px);
47+
margin: 8px;
4848
}
4949
& .muicc-colorbox-sliders {
5050
width: ${props => `${props.boxWidth}px`};
51-
padding: 0 6px;
51+
padding: 8px 8px 4px 8px;
5252
}
5353
& .muicc-colorbox-inputs {
5454
display: flex;
5555
flex-direction: row;
5656
flex-wrap: wrap;
57-
padding: 6px;
57+
padding: 8px 4px 8px 8px;
58+
justify-content: space-between;
5859
}
5960
& .muicc-colorbox-input {
6061
marginright: 14px;
@@ -67,18 +68,19 @@ const StyledBox = styled.div`
6768
background-size: 8px 8px;
6869
background-position: 0 0, 4px 0, 4px -4px, 0px 4px;
6970
background-color: white;
70-
margin-right: 24px;
71+
border-radius: 4px;
7172
}
7273
& .muicc-colorbox-color {
7374
width: 48px;
7475
height: 48px;
7576
background-color: ${props => props.backgroundColor};
77+
border-radius: 4px;
7678
}
7779
& .muicc-colorbox-controls {
7880
display: flex;
7981
flex-direction: row-reverse;
8082
flex-wrap: wrap;
81-
padding: 6px;
83+
padding: 8px;
8284
}
8385
`;
8486

@@ -173,7 +175,7 @@ const ColorBox = ({ value, palette, inputFormats, deferred, onChange: _onChange,
173175
{palette && (
174176
<>
175177
<Divider />
176-
<ColorPalette palette={palette} onSelect={handlePaletteSelection} />
178+
<ColorPalette size={26.65} palette={palette} onSelect={handlePaletteSelection} />
177179
</>
178180
)}
179181
{deferred && (

src/components/ColorPalette.jsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@ const StyledRoot = styled.div`
1717
display: flex;
1818
flex-direction: row;
1919
flex-wrap: wrap;
20-
padding: 6px;
20+
padding: 8px 0 0 8px;
2121
& .muicc-palette-button {
22-
margin-right: 4px;
23-
margin-bottom: 4px;
22+
margin: 0 8px 8px 0;
2423
padding: 0;
2524
}
2625
`;
2726

28-
const ColorPalette = ({ borderWidth, palette, onSelect }) => {
27+
const ColorPalette = ({ size, borderWidth, palette, onSelect }) => {
2928
const handleSelectColor = name => {
3029
if (onSelect) onSelect(name, palette[name]);
3130
};
@@ -34,7 +33,7 @@ const ColorPalette = ({ borderWidth, palette, onSelect }) => {
3433
<StyledRoot>
3534
{Object.keys(palette).map(name => (
3635
<ColorButton
37-
size={24}
36+
size={size}
3837
key={`${name}`}
3938
color={palette[name]}
4039
className="muicc-palette-button"
@@ -49,13 +48,15 @@ const ColorPalette = ({ borderWidth, palette, onSelect }) => {
4948

5049
ColorPalette.propTypes = {
5150
borderWidth: PropTypes.number,
51+
size: PropTypes.number,
5252
palette: CommonTypes.palette.isRequired,
5353
forwardRef: PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
5454
onSelect: PropTypes.func,
5555
};
5656

5757
ColorPalette.defaultProps = {
5858
borderWidth: 0,
59+
size: 24,
5960
forwardRef: undefined,
6061
onSelect: undefined,
6162
};

src/helpers/useEventCallback.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/* istanbul ignore file */
2+
import * as React from 'react';
3+
4+
const useEnhancedEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
5+
6+
/**
7+
* https://github.com/facebook/react/issues/14099#issuecomment-440013892
8+
*
9+
* @param {function} fn
10+
*/
11+
export default function useEventCallback(fn) {
12+
const ref = React.useRef(fn);
13+
useEnhancedEffect(() => {
14+
ref.current = fn;
15+
});
16+
return React.useCallback((...args) => (0, ref.current)(...args), []);
17+
}

0 commit comments

Comments
 (0)