1
- import React , { useMemo } from 'react' ;
1
+ import React , { useMemo , useState } from 'react' ;
2
2
import ReactSelect from 'react-select' ;
3
3
import cn from 'classnames' ;
4
4
5
5
import * as css from './Select.module.css' ;
6
6
7
- const toOption = ( value ) => ( { value, label : value } ) ;
8
-
9
7
export const Select = ( {
10
8
title,
11
9
options,
@@ -17,8 +15,12 @@ export const Select = ({
17
15
variant,
18
16
instanceId
19
17
} ) => {
18
+ const [ input , setInput ] = useState ( '' ) ;
20
19
const opts = useMemo ( ( ) => options . map ( toOption ) , [ options ] ) ;
21
- const handleOnChange = ( o , action ) => onChange ( o ? o . value : o ) ;
20
+ const filteredOpts = useMemo (
21
+ ( ) => ( input === '' ? opts : filterAndSortRank ( opts , input ) ) ,
22
+ [ opts , input ]
23
+ ) ;
22
24
23
25
return (
24
26
< section className = { cn ( css . root , className , { [ css [ variant ] ] : variant } ) } >
@@ -36,10 +38,12 @@ export const Select = ({
36
38
placeholder = { placeholder }
37
39
className = { css . select }
38
40
classNamePrefix = "rs"
39
- options = { opts }
40
- defaultValue = { selected ? toOption ( selected ) : selected }
41
- onChange = { handleOnChange }
41
+ options = { filteredOpts }
42
+ defaultValue = { selected ? toOption ( selected ) : '' }
43
+ onChange = { ( o ) => onChange ( o ? o . value : null ) }
42
44
instanceId = { instanceId }
45
+ onInputChange = { setInput }
46
+ filterOption = { ( ) => true }
43
47
/>
44
48
45
49
< div className = { css . itemSpacer } > </ div >
@@ -48,4 +52,49 @@ export const Select = ({
48
52
</ section >
49
53
) ;
50
54
} ;
55
+
51
56
export default Select ;
57
+
58
+ const toOption = ( value ) => ( { value, label : value } ) ;
59
+
60
+ // trim, lowercase and strip accents
61
+ const normalize = ( value ) =>
62
+ value
63
+ . trim ( )
64
+ . toLowerCase ( )
65
+ . normalize ( 'NFD' )
66
+ . replace ( / [ \u0300 - \u036f ] / g, '' ) ;
67
+
68
+ const rank = ( value , input ) => {
69
+ // exact match: highest priority
70
+ if ( value === input ) return 0 ;
71
+
72
+ // complete word match: higher priority based on word position
73
+ const words = value . split ( ' ' ) ;
74
+ for ( let i = 0 ; i < words . length ; i ++ ) {
75
+ if ( words [ i ] === input ) return i + 1 ;
76
+ }
77
+
78
+ // partial match: lower priority based on character position
79
+ const index = value . indexOf ( input ) ;
80
+ return index === - 1 ? Number . MAX_SAFE_INTEGER : 1000 + index ;
81
+ } ;
82
+
83
+ const filterAndSortRank = ( options , input ) => {
84
+ // It doesn't seem possible to only sort the filtered options in react-select, but we can re-implement the filtering to do so.
85
+ // https://github.com/JedWatson/react-select/discussions/4426
86
+
87
+ const normalizedInput = normalize ( input ) ;
88
+
89
+ return options
90
+ . filter ( ( o ) => normalize ( o . value ) . includes ( normalizedInput ) )
91
+ . sort ( ( optA , optB ) => {
92
+ const rankDelta =
93
+ rank ( normalize ( optA . value ) , normalizedInput ) -
94
+ rank ( normalize ( optB . value ) , normalizedInput ) ;
95
+
96
+ if ( rankDelta !== 0 ) return rankDelta ;
97
+
98
+ return optA . value . localeCompare ( optB . value ) ;
99
+ } ) ;
100
+ } ;
0 commit comments