diff options
Diffstat (limited to 'packages/forms/src/select/index.tsx')
-rw-r--r-- | packages/forms/src/select/index.tsx | 146 |
1 files changed, 81 insertions, 65 deletions
diff --git a/packages/forms/src/select/index.tsx b/packages/forms/src/select/index.tsx index 0e5ded176..d7479f63e 100644 --- a/packages/forms/src/select/index.tsx +++ b/packages/forms/src/select/index.tsx | |||
@@ -1,13 +1,16 @@ | |||
1 | import { mdiArrowRightDropCircleOutline, mdiCloseCircle, mdiMagnify } from '@mdi/js'; | 1 | import { |
2 | mdiArrowRightDropCircleOutline, | ||
3 | mdiCloseCircle, | ||
4 | mdiMagnify, | ||
5 | } from '@mdi/js'; | ||
2 | import Icon from '@mdi/react'; | 6 | import Icon from '@mdi/react'; |
3 | import { Theme } from '@meetfranz/theme'; | ||
4 | import classnames from 'classnames'; | 7 | import classnames from 'classnames'; |
5 | import React, { Component, createRef } from 'react'; | 8 | import React, { Component, createRef } from 'react'; |
6 | import injectStyle from 'react-jss'; | 9 | import injectStyle from 'react-jss'; |
7 | 10 | ||
8 | import { IFormField, IWithStyle } from '../typings/generic'; | 11 | import { IFormField, IWithStyle } from '../typings/generic'; |
12 | import { Theme } from '../../../theme'; | ||
9 | 13 | ||
10 | import { NONAME } from 'dns'; | ||
11 | import { Error } from '../error'; | 14 | import { Error } from '../error'; |
12 | import { Label } from '../label'; | 15 | import { Label } from '../label'; |
13 | import { Wrapper } from '../wrapper'; | 16 | import { Wrapper } from '../wrapper'; |
@@ -43,6 +46,14 @@ interface IState { | |||
43 | options: IOptions; | 46 | options: IOptions; |
44 | } | 47 | } |
45 | 48 | ||
49 | let popupTransition: string = 'none'; | ||
50 | let toggleTransition: string = 'none'; | ||
51 | |||
52 | if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) { | ||
53 | popupTransition = 'all 0.3s'; | ||
54 | toggleTransition = 'transform 0.3s'; | ||
55 | } | ||
56 | |||
46 | const styles = (theme: Theme) => ({ | 57 | const styles = (theme: Theme) => ({ |
47 | select: { | 58 | select: { |
48 | background: theme.selectBackground, | 59 | background: theme.selectBackground, |
@@ -59,7 +70,7 @@ const styles = (theme: Theme) => ({ | |||
59 | label: { | 70 | label: { |
60 | '& > div': { | 71 | '& > div': { |
61 | marginTop: 5, | 72 | marginTop: 5, |
62 | } | 73 | }, |
63 | }, | 74 | }, |
64 | popup: { | 75 | popup: { |
65 | opacity: 0, | 76 | opacity: 0, |
@@ -67,7 +78,7 @@ const styles = (theme: Theme) => ({ | |||
67 | overflowX: 'scroll', | 78 | overflowX: 'scroll', |
68 | border: theme.selectBorder, | 79 | border: theme.selectBorder, |
69 | borderTop: 0, | 80 | borderTop: 0, |
70 | transition: 'all 0.3s', | 81 | transition: popupTransition, |
71 | }, | 82 | }, |
72 | open: { | 83 | open: { |
73 | opacity: 1, | 84 | opacity: 1, |
@@ -95,7 +106,7 @@ const styles = (theme: Theme) => ({ | |||
95 | toggle: { | 106 | toggle: { |
96 | marginLeft: 'auto', | 107 | marginLeft: 'auto', |
97 | fill: theme.selectToggleColor, | 108 | fill: theme.selectToggleColor, |
98 | transition: 'transform 0.3s', | 109 | transition: toggleTransition, |
99 | }, | 110 | }, |
100 | toggleOpened: { | 111 | toggleOpened: { |
101 | transform: 'rotateZ(90deg)', | 112 | transform: 'rotateZ(90deg)', |
@@ -154,9 +165,13 @@ class SelectComponent extends Component<IProps> { | |||
154 | }; | 165 | }; |
155 | 166 | ||
156 | private componentRef = createRef<HTMLDivElement>(); | 167 | private componentRef = createRef<HTMLDivElement>(); |
168 | |||
157 | private inputRef = createRef<HTMLInputElement>(); | 169 | private inputRef = createRef<HTMLInputElement>(); |
170 | |||
158 | private searchInputRef = createRef<HTMLInputElement>(); | 171 | private searchInputRef = createRef<HTMLInputElement>(); |
172 | |||
159 | private scrollContainerRef = createRef<HTMLDivElement>(); | 173 | private scrollContainerRef = createRef<HTMLDivElement>(); |
174 | |||
160 | private activeOptionRef = createRef<HTMLDivElement>(); | 175 | private activeOptionRef = createRef<HTMLDivElement>(); |
161 | 176 | ||
162 | private keyListener: any; | 177 | private keyListener: any; |
@@ -169,10 +184,8 @@ class SelectComponent extends Component<IProps> { | |||
169 | } | 184 | } |
170 | } | 185 | } |
171 | 186 | ||
172 | componentDidUpdate(prevProps: IProps, prevState: IState) { | 187 | componentDidUpdate() { |
173 | const { | 188 | const { open } = this.state; |
174 | open, | ||
175 | } = this.state; | ||
176 | 189 | ||
177 | if (this.searchInputRef && this.searchInputRef.current) { | 190 | if (this.searchInputRef && this.searchInputRef.current) { |
178 | if (open) { | 191 | if (open) { |
@@ -183,12 +196,12 @@ class SelectComponent extends Component<IProps> { | |||
183 | 196 | ||
184 | componentDidMount() { | 197 | componentDidMount() { |
185 | if (this.inputRef && this.inputRef.current) { | 198 | if (this.inputRef && this.inputRef.current) { |
186 | const { | 199 | const { data } = this.props; |
187 | data, | ||
188 | } = this.props; | ||
189 | 200 | ||
190 | if (data) { | 201 | if (data) { |
191 | Object.keys(data).map(key => this.inputRef.current!.dataset[key] = data[key]); | 202 | Object.keys(data).map( |
203 | key => (this.inputRef.current!.dataset[key] = data[key]), | ||
204 | ); | ||
192 | } | 205 | } |
193 | } | 206 | } |
194 | 207 | ||
@@ -196,12 +209,13 @@ class SelectComponent extends Component<IProps> { | |||
196 | } | 209 | } |
197 | 210 | ||
198 | componentWillMount() { | 211 | componentWillMount() { |
199 | const { | 212 | const { value } = this.props; |
200 | value, | ||
201 | } = this.props; | ||
202 | 213 | ||
203 | if (this.componentRef && this.componentRef.current) { | 214 | if (this.componentRef && this.componentRef.current) { |
204 | this.componentRef.current.removeEventListener('keydown', this.keyListener); | 215 | this.componentRef.current.removeEventListener( |
216 | 'keydown', | ||
217 | this.keyListener, | ||
218 | ); | ||
205 | } | 219 | } |
206 | 220 | ||
207 | if (value) { | 221 | if (value) { |
@@ -217,13 +231,18 @@ class SelectComponent extends Component<IProps> { | |||
217 | window.removeEventListener('keydown', this.arrowKeysHandler.bind(this)); | 231 | window.removeEventListener('keydown', this.arrowKeysHandler.bind(this)); |
218 | } | 232 | } |
219 | 233 | ||
220 | setFilter(needle: string = '') { | 234 | setFilter(needle = '') { |
221 | const { options } = this.props; | 235 | const { options } = this.props; |
222 | 236 | ||
223 | let filteredOptions = {}; | 237 | let filteredOptions = {}; |
224 | if (needle) { | 238 | if (needle) { |
225 | Object.keys(options).map((key) => { | 239 | Object.keys(options).map(key => { |
226 | if (key.toLocaleLowerCase().startsWith(needle.toLocaleLowerCase()) || options[key].toLocaleLowerCase().startsWith(needle.toLocaleLowerCase())) { | 240 | if ( |
241 | key.toLocaleLowerCase().startsWith(needle.toLocaleLowerCase()) || | ||
242 | options[key] | ||
243 | .toLocaleLowerCase() | ||
244 | .startsWith(needle.toLocaleLowerCase()) | ||
245 | ) { | ||
227 | Object.assign(filteredOptions, { | 246 | Object.assign(filteredOptions, { |
228 | [`${key}`]: options[key], | 247 | [`${key}`]: options[key], |
229 | }); | 248 | }); |
@@ -241,7 +260,7 @@ class SelectComponent extends Component<IProps> { | |||
241 | } | 260 | } |
242 | 261 | ||
243 | select(key: string) { | 262 | select(key: string) { |
244 | this.setState((state: IState) => ({ | 263 | this.setState(() => ({ |
245 | value: key, | 264 | value: key, |
246 | open: false, | 265 | open: false, |
247 | })); | 266 | })); |
@@ -254,11 +273,7 @@ class SelectComponent extends Component<IProps> { | |||
254 | } | 273 | } |
255 | 274 | ||
256 | arrowKeysHandler(e: KeyboardEvent) { | 275 | arrowKeysHandler(e: KeyboardEvent) { |
257 | const { | 276 | const { selected, open, options } = this.state; |
258 | selected, | ||
259 | open, | ||
260 | options, | ||
261 | } = this.state; | ||
262 | 277 | ||
263 | if (!open) return; | 278 | if (!open) return; |
264 | 279 | ||
@@ -271,7 +286,10 @@ class SelectComponent extends Component<IProps> { | |||
271 | this.setState((state: IState) => ({ | 286 | this.setState((state: IState) => ({ |
272 | selected: state.selected - 1, | 287 | selected: state.selected - 1, |
273 | })); | 288 | })); |
274 | } else if (e.keyCode === 40 && selected < Object.keys(options!).length - 1) { | 289 | } else if ( |
290 | e.keyCode === 40 && | ||
291 | selected < Object.keys(options!).length - 1 | ||
292 | ) { | ||
275 | this.setState((state: IState) => ({ | 293 | this.setState((state: IState) => ({ |
276 | selected: state.selected + 1, | 294 | selected: state.selected + 1, |
277 | })); | 295 | })); |
@@ -279,7 +297,12 @@ class SelectComponent extends Component<IProps> { | |||
279 | this.select(Object.keys(options!)[selected]); | 297 | this.select(Object.keys(options!)[selected]); |
280 | } | 298 | } |
281 | 299 | ||
282 | if (this.activeOptionRef && this.activeOptionRef.current && this.scrollContainerRef && this.scrollContainerRef.current) { | 300 | if ( |
301 | this.activeOptionRef && | ||
302 | this.activeOptionRef.current && | ||
303 | this.scrollContainerRef && | ||
304 | this.scrollContainerRef.current | ||
305 | ) { | ||
283 | const containerTopOffset = this.scrollContainerRef.current.offsetTop; | 306 | const containerTopOffset = this.scrollContainerRef.current.offsetTop; |
284 | const optionTopOffset = this.activeOptionRef.current.offsetTop; | 307 | const optionTopOffset = this.activeOptionRef.current.offsetTop; |
285 | 308 | ||
@@ -289,10 +312,15 @@ class SelectComponent extends Component<IProps> { | |||
289 | } | 312 | } |
290 | } | 313 | } |
291 | 314 | ||
292 | switch (e.keyCode){ | 315 | switch (e.keyCode) { |
293 | case 37: case 39: case 38: case 40: // Arrow keys | 316 | case 37: |
294 | case 32: break; // Space | 317 | case 39: |
295 | default: break; // do not block other keys | 318 | case 38: |
319 | case 40: // Arrow keys | ||
320 | case 32: | ||
321 | break; // Space | ||
322 | default: | ||
323 | break; // do not block other keys | ||
296 | } | 324 | } |
297 | } | 325 | } |
298 | 326 | ||
@@ -314,13 +342,7 @@ class SelectComponent extends Component<IProps> { | |||
314 | required, | 342 | required, |
315 | } = this.props; | 343 | } = this.props; |
316 | 344 | ||
317 | const { | 345 | const { open, needle, value, selected, options } = this.state; |
318 | open, | ||
319 | needle, | ||
320 | value, | ||
321 | selected, | ||
322 | options, | ||
323 | } = this.state; | ||
324 | 346 | ||
325 | let selection = ''; | 347 | let selection = ''; |
326 | if (!value && defaultValue && options![defaultValue]) { | 348 | if (!value && defaultValue && options![defaultValue]) { |
@@ -332,10 +354,7 @@ class SelectComponent extends Component<IProps> { | |||
332 | } | 354 | } |
333 | 355 | ||
334 | return ( | 356 | return ( |
335 | <Wrapper | 357 | <Wrapper className={className} identifier="franz-select"> |
336 | className={className} | ||
337 | identifier="franz-select" | ||
338 | > | ||
339 | <Label | 358 | <Label |
340 | title={label} | 359 | title={label} |
341 | showLabel={showLabel} | 360 | showLabel={showLabel} |
@@ -352,14 +371,19 @@ class SelectComponent extends Component<IProps> { | |||
352 | > | 371 | > |
353 | <button | 372 | <button |
354 | type="button" | 373 | type="button" |
355 | className={classnames({ | 374 | className={classnames({ |
356 | [`${inputClassName}`]: inputClassName, | 375 | [`${inputClassName}`]: inputClassName, |
357 | [`${classes.select}`]: true, | 376 | [`${classes.select}`]: true, |
358 | [`${classes.hasError}`]: error, | 377 | [`${classes.hasError}`]: error, |
359 | })} | 378 | })} |
360 | onClick= {!disabled ? () => this.setState((state: IState) => ({ | 379 | onClick={ |
361 | open: !state.open, | 380 | !disabled |
362 | })) : () => {}} | 381 | ? () => |
382 | this.setState((state: IState) => ({ | ||
383 | open: !state.open, | ||
384 | })) | ||
385 | : () => {} | ||
386 | } | ||
363 | > | 387 | > |
364 | {selection} | 388 | {selection} |
365 | <Icon | 389 | <Icon |
@@ -373,10 +397,7 @@ class SelectComponent extends Component<IProps> { | |||
373 | </button> | 397 | </button> |
374 | {showSearch && open && ( | 398 | {showSearch && open && ( |
375 | <div className={classes.searchContainer}> | 399 | <div className={classes.searchContainer}> |
376 | <Icon | 400 | <Icon path={mdiMagnify} size={0.8} /> |
377 | path={mdiMagnify} | ||
378 | size={0.8} | ||
379 | /> | ||
380 | <input | 401 | <input |
381 | type="text" | 402 | type="text" |
382 | value={needle} | 403 | value={needle} |
@@ -391,10 +412,7 @@ class SelectComponent extends Component<IProps> { | |||
391 | className={classes.clearNeedle} | 412 | className={classes.clearNeedle} |
392 | onClick={() => this.setFilter()} | 413 | onClick={() => this.setFilter()} |
393 | > | 414 | > |
394 | <Icon | 415 | <Icon path={mdiCloseCircle} size={0.7} /> |
395 | path={mdiCloseCircle} | ||
396 | size={0.7} | ||
397 | /> | ||
398 | </button> | 416 | </button> |
399 | )} | 417 | )} |
400 | </div> | 418 | </div> |
@@ -406,7 +424,7 @@ class SelectComponent extends Component<IProps> { | |||
406 | })} | 424 | })} |
407 | ref={this.scrollContainerRef} | 425 | ref={this.scrollContainerRef} |
408 | > | 426 | > |
409 | {Object.keys(options!).map(((key, i) => ( | 427 | {Object.keys(options!).map((key, i) => ( |
410 | <div | 428 | <div |
411 | key={key} | 429 | key={key} |
412 | onClick={() => this.select(key)} | 430 | onClick={() => this.select(key)} |
@@ -420,7 +438,7 @@ class SelectComponent extends Component<IProps> { | |||
420 | > | 438 | > |
421 | {options![key]} | 439 | {options![key]} |
422 | </div> | 440 | </div> |
423 | )))} | 441 | ))} |
424 | </div> | 442 | </div> |
425 | </div> | 443 | </div> |
426 | <input | 444 | <input |
@@ -434,9 +452,7 @@ class SelectComponent extends Component<IProps> { | |||
434 | ref={this.inputRef} | 452 | ref={this.inputRef} |
435 | /> | 453 | /> |
436 | </Label> | 454 | </Label> |
437 | {error && ( | 455 | {error && <Error message={error} />} |
438 | <Error message={error} /> | ||
439 | )} | ||
440 | </Wrapper> | 456 | </Wrapper> |
441 | ); | 457 | ); |
442 | } | 458 | } |