@@ -8,20 +8,22 @@ import './style/index.less';
8
8
9
9
type ValueType = Array < string | number > ;
10
10
type OptionType = { value : string | number ; label : React . ReactNode ; children ?: Array < OptionType > } ;
11
+ type SearchOptionType = { label : string ; options : Array < OptionType > } ;
11
12
12
13
export interface CascaderProps extends IProps , DropdownProps {
13
14
option ?: Array < OptionType > ;
14
15
value ?: ValueType ;
15
16
onChange ?: ( isSeleted : boolean , value : ValueType , selectedOptions : Array < OptionType > ) => void ;
17
+ onSearch ?: boolean | ( ( searchText : string ) => void ) ;
16
18
allowClear ?: boolean ;
17
19
placeholder ?: string ;
18
- isOpen ?: boolean ;
19
20
}
20
21
21
22
function Cascader ( props : CascaderProps ) {
22
23
const {
23
24
value,
24
25
onChange,
26
+ onSearch,
25
27
26
28
allowClear,
27
29
placeholder,
@@ -36,16 +38,48 @@ function Cascader(props: CascaderProps) {
36
38
const [ innerIsOpen , setInnerIsOpen ] = useState ( false ) ;
37
39
const [ selectedValue , setSelectedValue ] = useState < Array < OptionType > > ( [ ] ) ;
38
40
const [ selectIconType , setSelectIconType ] = useState ( '' ) ;
41
+ const [ searchText , setSearchText ] = useState < string > ( '' ) ;
42
+ const [ searchOn , setSearchOn ] = useState ( false ) ;
43
+ const [ inputValue , setInputValue ] = useState ( '' ) ;
44
+ const [ searchOption , setSearchOption ] = useState < Array < SearchOptionType > > ( ) ;
39
45
40
46
useEffect ( ( ) => {
41
- const valueTemp : Array < OptionType > = [ ] ;
42
- let optChildren = option ;
43
- value ?. map ( ( item ) => {
44
- const findOpt = optChildren . find ( ( opt ) => opt . value === item ) ;
45
- optChildren = findOpt ?. children || [ ] ;
46
- valueTemp . push ( { label : item , value : item , ...findOpt } ) ;
47
+ if ( onSearch ) {
48
+ const tempOptions : Array < SearchOptionType > = [ ] ;
49
+ iteratorOption ( option , ( opt ) => {
50
+ const label = opt . map ( ( m ) => m . label ) . join ( ' / ' ) ;
51
+ tempOptions . push ( { label, options : opt } ) ;
52
+ } ) ;
53
+ setSearchOption ( tempOptions ) ;
54
+ }
55
+ } , [ onSearch ] ) ;
56
+
57
+ const iteratorOption = (
58
+ options : Array < OptionType > ,
59
+ cb : ( opt : Array < OptionType > ) => void ,
60
+ opts : Array < OptionType > = [ ] ,
61
+ ) => {
62
+ options . map ( ( opt ) => {
63
+ const optsTemp = [ ...opts , opt ] ;
64
+ if ( opt . children ) {
65
+ iteratorOption ( opt . children , cb , optsTemp ) ;
66
+ } else {
67
+ cb ?.( optsTemp ) ;
68
+ }
47
69
} ) ;
48
- setSelectedValue ( valueTemp ) ;
70
+ } ;
71
+
72
+ useEffect ( ( ) => {
73
+ if ( value ) {
74
+ const valueTemp : Array < OptionType > = [ ] ;
75
+ let optChildren = option ;
76
+ value ?. map ( ( item ) => {
77
+ const findOpt = optChildren . find ( ( opt ) => opt . value === item ) ;
78
+ optChildren = findOpt ?. children || [ ] ;
79
+ valueTemp . push ( { label : item , value : item , ...findOpt } ) ;
80
+ } ) ;
81
+ setSelectedValue ( valueTemp ) ;
82
+ }
49
83
} , [ value ] ) ;
50
84
51
85
function onVisibleChange ( isOpen : boolean ) {
@@ -62,6 +96,12 @@ function Cascader(props: CascaderProps) {
62
96
setSelectIconType ( selectIconType ) ;
63
97
}
64
98
99
+ const searchItemClick = ( options : Array < OptionType > ) => {
100
+ setSearchText ( '' ) ;
101
+ setInnerIsOpen ( false ) ;
102
+ handelChange ( false , options ) ;
103
+ } ;
104
+
65
105
const handleItemClick = ( optionsItem : OptionType , level : number ) => {
66
106
selectedValue . splice ( level , selectedValue . length - level , optionsItem ) ;
67
107
@@ -76,10 +116,21 @@ function Cascader(props: CascaderProps) {
76
116
77
117
const onClear = ( e : React . MouseEvent < any , MouseEvent > ) => {
78
118
e . stopPropagation ( ) ;
79
- console . log ( 123 ) ;
80
119
handelChange ( false , [ ] ) ;
81
120
} ;
82
121
122
+ const handelSearch = ( searchText : string ) => {
123
+ setSearchText ( searchText ) ;
124
+ } ;
125
+
126
+ const inputChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
127
+ if ( ! innerIsOpen ) {
128
+ setInnerIsOpen ( ! innerIsOpen ) ;
129
+ }
130
+ const value = e . target . value ;
131
+ onSearch && handelSearch ( value ) ;
132
+ } ;
133
+
83
134
const widths = ( style ?. width as number ) * 0.5 || undefined ;
84
135
85
136
const OptionIter = ( option : Array < OptionType > , level : number = 0 ) => {
@@ -116,9 +167,10 @@ function Cascader(props: CascaderProps) {
116
167
) as JSX . Element ;
117
168
} ;
118
169
119
- const inputValue = useMemo ( ( ) => {
120
- return selectedValue . map ( ( opt ) => opt . label ) . join ( ' / ' ) ;
121
- } , [ selectedValue . length ] ) ;
170
+ useEffect ( ( ) => {
171
+ const inputValue = selectedValue . map ( ( opt ) => opt . label ) . join ( ' / ' ) ;
172
+ setInputValue ( inputValue ) ;
173
+ } , [ selectedValue ] ) ;
122
174
123
175
return (
124
176
< Dropdown
@@ -130,28 +182,60 @@ function Cascader(props: CascaderProps) {
130
182
onVisibleChange = { onVisibleChange }
131
183
isOpen = { innerIsOpen }
132
184
menu = {
133
- < div style = { { display : 'flex' } } >
134
- { new Array ( selectedValue . length + 1 )
135
- . fill ( 0 )
136
- . map ( ( _ , index ) => {
137
- const options = index ? selectedValue [ index - 1 ] ?. children ! : option ;
138
- return OptionIter ( options , index ) ;
139
- } )
140
- . filter ( ( m ) => ! ! m ) }
141
- </ div >
185
+ ! searchText ? (
186
+ < div style = { { display : 'flex' } } >
187
+ { new Array ( selectedValue . length + 1 )
188
+ . fill ( 0 )
189
+ . map ( ( _ , index ) => {
190
+ const options = index ? selectedValue [ index - 1 ] ?. children ! : option ;
191
+ return OptionIter ( options , index ) ;
192
+ } )
193
+ . filter ( ( m ) => ! ! m ) }
194
+ </ div >
195
+ ) : (
196
+ < Menu
197
+ bordered
198
+ style = { {
199
+ minHeight : 25 ,
200
+ minWidth : style ?. width ,
201
+ overflowY : 'scroll' ,
202
+ width : style ?. width ,
203
+ } }
204
+ >
205
+ { ! searchOption || searchOption . length === 0 ? (
206
+ < div style = { { color : '#c7c7c7' , fontSize : 12 } } > { '没有数据' } </ div >
207
+ ) : (
208
+ searchOption
209
+ . filter ( ( opt ) => opt . label . includes ( searchText . trim ( ) ) )
210
+ . map ( ( opt , index ) => {
211
+ return (
212
+ < Menu . Item
213
+ key = { index }
214
+ text = { opt . label }
215
+ onClick = { ( ) => searchItemClick ( opt . options ) } //
216
+ />
217
+ ) ;
218
+ } )
219
+ ) }
220
+ </ Menu >
221
+ )
142
222
}
143
223
>
144
224
< span onMouseLeave = { ( ) => renderSelectIcon ( 'leave' ) } onMouseOver = { ( ) => renderSelectIcon ( 'enter' ) } >
145
225
< Input
146
- value = { inputValue }
147
- onChange = { ( ) => { } }
148
- placeholder = { placeholder }
226
+ value = { searchOn ? searchText : inputValue }
227
+ onChange = { inputChange }
228
+ placeholder = { searchOn ? inputValue : placeholder }
149
229
style = { { width : style ?. width } }
150
- readOnly
230
+ onFocus = { ( ) => onSearch && setSearchOn ( true ) }
231
+ onBlur = { ( ) => onSearch && setSearchOn ( false ) }
232
+ readOnly = { ! onSearch }
151
233
addonAfter = {
152
- selectIconType === 'close' && (
153
- < Icon type = { `${ selectIconType } ` } onClick = { onClear } className = { `${ prefixCls } -close` } />
154
- )
234
+ < span style = { { width : 'auto' } } >
235
+ { selectIconType === 'close' && (
236
+ < Icon type = { selectIconType } onClick = { onClear } className = { `${ prefixCls } -close` } />
237
+ ) }
238
+ </ span >
155
239
}
156
240
/>
157
241
</ span >
0 commit comments