4
4
useResizeObserver ,
5
5
} from '@rocket.chat/fuselage-hooks' ;
6
6
import React , { useState , useRef , useEffect , memo , forwardRef } from 'react' ;
7
+ import { isForwardRef } from 'react-is' ;
7
8
8
9
import AnimatedVisibility from '../AnimatedVisibility' ;
9
10
import { Box } from '../Box' ;
@@ -13,7 +14,8 @@ import { Icon } from '../Icon';
13
14
import Margins from '../Margins' ;
14
15
import { Options , CheckOption , useCursor } from '../Options' ;
15
16
import Position from '../Position' ;
16
- import { Focus , Addon } from '../Select/Select' ;
17
+ import SelectAddon from '../Select/SelectAddon' ;
18
+ import MultiSelectAnchor from './MultiSelectAnchor' ;
17
19
18
20
const SelectedOptions = memo ( ( props ) => < Chip { ...props } /> ) ;
19
21
@@ -23,152 +25,153 @@ const prevent = (e) => {
23
25
e . nativeEvent . stopImmediatePropagation ( ) ;
24
26
} ;
25
27
26
- export const MultiSelect = forwardRef (
27
- (
28
- {
29
- value ,
30
- filter ,
31
- options = [ ] ,
32
- error ,
33
- disabled ,
34
- anchor : Anchor = Focus ,
35
- onChange = ( ) => { } ,
36
- getLabel = ( [ , label ] = [ ] ) => label ,
37
- getValue = ( [ value ] ) => value ,
38
- placeholder ,
39
- renderOptions : _Options = Options ,
40
- customEmpty ,
41
- ...props
42
- } ,
43
- ref
44
- ) => {
45
- const [ internalValue , setInternalValue ] = useState ( value || [ ] ) ;
28
+ export const MultiSelect = forwardRef ( function MultiSelect (
29
+ {
30
+ value ,
31
+ filter ,
32
+ options = [ ] ,
33
+ error ,
34
+ disabled ,
35
+ anchor : Anchor = MultiSelectAnchor ,
36
+ onChange = ( ) => { } ,
37
+ getLabel = ( [ , label ] = [ ] ) => label ,
38
+ getValue = ( [ value ] ) => value ,
39
+ placeholder ,
40
+ renderOptions : _Options = Options ,
41
+ customEmpty ,
42
+ onSelect ,
43
+ ...props
44
+ } ,
45
+ ref
46
+ ) {
47
+ const [ internalValue , setInternalValue ] = useState ( value || [ ] ) ;
46
48
47
- const currentValue = value !== undefined ? value : internalValue ;
48
- const option = options . find ( ( option ) => getValue ( option ) === currentValue ) ;
49
- const index = options . indexOf ( option ) ;
49
+ const option = options . find ( ( option ) => getValue ( option ) === internalValue ) ;
50
+ const index = options . indexOf ( option ) ;
50
51
51
- const internalChanged = ( [ value ] ) => {
52
- if ( currentValue . includes ( value ) ) {
53
- const newValue = currentValue . filter ( ( item ) => item !== value ) ;
54
- setInternalValue ( newValue ) ;
55
- return onChange ( newValue ) ;
56
- }
57
- const newValue = [ ...currentValue , value ] ;
52
+ const internalChanged = ( [ value ] ) => {
53
+ if ( internalValue . includes ( value ) ) {
54
+ const newValue = internalValue . filter ( ( item ) => item !== value ) ;
58
55
setInternalValue ( newValue ) ;
59
56
return onChange ( newValue ) ;
60
- } ;
57
+ }
58
+ const newValue = [ ...internalValue , value ] ;
59
+ setInternalValue ( newValue ) ;
60
+ onSelect ( ) ;
61
+ return onChange ( newValue ) ;
62
+ } ;
61
63
62
- const mapOptions = ( [ value , label ] ) => {
63
- if ( currentValue . includes ( value ) ) {
64
- return [ value , label , true ] ;
65
- }
66
- return [ value , label ] ;
67
- } ;
68
- const applyFilter = ( [ , option ] ) =>
69
- ! filter || ~ option . toLowerCase ( ) . indexOf ( filter . toLowerCase ( ) ) ;
70
- const filteredOptions = options . filter ( applyFilter ) . map ( mapOptions ) ;
71
- const [ cursor , handleKeyDown , handleKeyUp , reset , [ visible , hide , show ] ] =
72
- useCursor ( index , filteredOptions , internalChanged ) ;
64
+ const mapOptions = ( [ value , label ] ) => {
65
+ if ( internalValue . includes ( value ) ) {
66
+ return [ value , label , true ] ;
67
+ }
68
+ return [ value , label ] ;
69
+ } ;
70
+ const applyFilter = ( [ , option ] ) =>
71
+ ! filter || ~ option . toLowerCase ( ) . indexOf ( filter . toLowerCase ( ) ) ;
72
+ const filteredOptions = options . filter ( applyFilter ) . map ( mapOptions ) ;
73
+ const [ cursor , handleKeyDown , handleKeyUp , reset , [ visible , hide , show ] ] =
74
+ useCursor ( index , filteredOptions , internalChanged ) ;
73
75
74
- useEffect ( reset , [ filter ] ) ;
76
+ useEffect ( reset , [ filter ] ) ;
75
77
76
- const innerRef = useRef ( ) ;
77
- const anchorRef = useMergedRefs ( ref , innerRef ) ;
78
+ const innerRef = useRef ( ) ;
79
+ const anchorRef = useMergedRefs ( ref , innerRef ) ;
78
80
79
- const { ref : containerRef , borderBoxSize } = useResizeObserver ( ) ;
81
+ const { ref : containerRef , borderBoxSize } = useResizeObserver ( ) ;
80
82
81
- return (
82
- < Box
83
- is = 'div'
84
- rcx-select
85
- className = { [ error && 'invalid' , disabled && 'disabled' ] }
86
- ref = { containerRef }
87
- onClick = { useMutableCallback ( ( ) =>
88
- visible === AnimatedVisibility . VISIBLE
89
- ? hide ( )
90
- : innerRef . current . focus ( ) & show ( )
91
- ) }
92
- disabled = { disabled }
93
- { ...props }
94
- >
95
- < Flex . Item grow = { 1 } >
96
- < Margins inline = 'x4' >
97
- < Flex . Container >
98
- < Box is = 'div' >
99
- < Box
100
- is = 'div'
101
- display = 'flex'
102
- alignItems = 'center'
103
- flexWrap = 'wrap'
104
- margin = '-x8'
105
- role = 'listbox'
106
- >
107
- < Margins all = 'x4' >
108
- < Anchor
109
- disabled = { disabled }
110
- ref = { anchorRef }
111
- aria-haspopup = 'listbox'
112
- onClick = { show }
113
- onBlur = { hide }
114
- onKeyUp = { handleKeyUp }
115
- onKeyDown = { handleKeyDown }
116
- order = { 1 }
117
- rcx-input-box--undecorated
118
- children = { ! value ? option || placeholder : null }
83
+ const handleClick = useMutableCallback ( ( e ) => {
84
+ if ( e . target . tagName !== 'I' ) {
85
+ innerRef . current ?. focus ( ) ;
86
+ }
87
+ return visible === AnimatedVisibility . VISIBLE ? hide ( ) : show ( ) ;
88
+ } ) ;
89
+
90
+ const renderAnchor = isForwardRef ( Anchor )
91
+ ? ( params ) => < Anchor { ...params } />
92
+ : Anchor ;
93
+
94
+ return (
95
+ < Box
96
+ is = 'div'
97
+ rcx-select
98
+ className = { [ error && 'invalid' , disabled && 'disabled' ] }
99
+ ref = { containerRef }
100
+ onClick = { handleClick }
101
+ disabled = { disabled }
102
+ { ...props }
103
+ >
104
+ < Flex . Item grow = { 1 } >
105
+ < Margins inline = 'x4' >
106
+ < Flex . Container >
107
+ < Box is = 'div' >
108
+ < Box
109
+ is = 'div'
110
+ display = 'flex'
111
+ alignItems = 'center'
112
+ flexWrap = 'wrap'
113
+ margin = '-x8'
114
+ role = 'listbox'
115
+ >
116
+ < Margins all = 'x4' >
117
+ { renderAnchor ( {
118
+ ref : anchorRef ,
119
+ children : ! value ? option || placeholder : null ,
120
+ disabled,
121
+ onClick : show ,
122
+ onBlur : hide ,
123
+ onKeyDown : handleKeyDown ,
124
+ onKeyUp : handleKeyUp ,
125
+ } ) }
126
+ { internalValue . map ( ( value ) => (
127
+ < SelectedOptions
128
+ tabIndex = { - 1 }
129
+ role = 'option'
130
+ key = { value }
131
+ onMouseDown = { ( e ) =>
132
+ prevent ( e ) & internalChanged ( [ value ] ) && false
133
+ }
134
+ children = { getLabel (
135
+ options . find ( ( [ val ] ) => val === value )
136
+ ) }
119
137
/>
120
- { currentValue . map ( ( value ) => (
121
- < SelectedOptions
122
- tabIndex = { - 1 }
123
- role = 'option'
124
- key = { value }
125
- onMouseDown = { ( e ) =>
126
- prevent ( e ) & internalChanged ( [ value ] ) && false
127
- }
128
- children = { getLabel (
129
- options . find ( ( [ val ] ) => val === value )
130
- ) }
131
- />
132
- ) ) }
133
- </ Margins >
134
- </ Box >
138
+ ) ) }
139
+ </ Margins >
135
140
</ Box >
136
- </ Flex . Container >
137
- </ Margins >
138
- </ Flex . Item >
139
- < Flex . Item grow = { 0 } shrink = { 0 } >
140
- < Margins inline = 'x4' >
141
- < Addon
142
- children = {
143
- < Icon
144
- name = {
145
- visible === AnimatedVisibility . VISIBLE
146
- ? 'cross'
147
- : 'chevron-down'
148
- }
149
- size = 'x20'
150
- />
141
+ </ Box >
142
+ </ Flex . Container >
143
+ </ Margins >
144
+ </ Flex . Item >
145
+ < Flex . Item grow = { 0 } shrink = { 0 } >
146
+ < Margins inline = 'x4' >
147
+ < SelectAddon >
148
+ < Icon
149
+ name = {
150
+ visible === AnimatedVisibility . VISIBLE
151
+ ? 'cross'
152
+ : 'chevron-down'
151
153
}
154
+ size = 'x20'
152
155
/>
153
- </ Margins >
154
- </ Flex . Item >
155
- < AnimatedVisibility visibility = { visible } >
156
- < Position anchor = { containerRef } >
157
- < _Options
158
- width = { borderBoxSize . inlineSize }
159
- onMouseDown = { prevent }
160
- multiple
161
- filter = { filter }
162
- renderItem = { CheckOption }
163
- role = 'listbox'
164
- options = { filteredOptions }
165
- onSelect = { internalChanged }
166
- cursor = { cursor }
167
- customEmpty = { customEmpty }
168
- />
169
- </ Position >
170
- </ AnimatedVisibility >
171
- </ Box >
172
- ) ;
173
- }
174
- ) ;
156
+ </ SelectAddon >
157
+ </ Margins >
158
+ </ Flex . Item >
159
+ < AnimatedVisibility visibility = { visible } >
160
+ < Position anchor = { containerRef } >
161
+ < _Options
162
+ width = { borderBoxSize . inlineSize }
163
+ onMouseDown = { prevent }
164
+ multiple
165
+ filter = { filter }
166
+ renderItem = { CheckOption }
167
+ role = 'listbox'
168
+ options = { filteredOptions }
169
+ onSelect = { internalChanged }
170
+ cursor = { cursor }
171
+ customEmpty = { customEmpty }
172
+ / >
173
+ </ Position >
174
+ </ AnimatedVisibility >
175
+ </ Box >
176
+ ) ;
177
+ } ) ;
0 commit comments