@@ -11,6 +11,7 @@ import React from 'react';
1111import PropTypes from 'prop-types' ;
1212import styled from 'styled-components' ;
1313import * as CommonTypes from '../../helpers/commonTypes' ;
14+ import useEventCallback from '../../helpers/useEventCallback' ;
1415
1516const 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
7791const 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 >
0 commit comments