1
- import { MenuDivider , MenuItem } from '@blueprintjs/core'
1
+ import { Button , Classes , MenuDivider , MenuItem } from '@blueprintjs/core'
2
2
3
3
import clsx from 'clsx'
4
4
import Fuse from 'fuse.js'
5
- import { FC , Fragment , useMemo } from 'react'
5
+ import { FC , useMemo } from 'react'
6
6
7
7
import { useLevels } from '../apis/level'
8
8
import { createCustomLevel , isHardMode } from '../models/level'
9
9
import { Level } from '../models/operation'
10
- import { Suggest } from './Suggest'
10
+ import { useDebouncedQuery } from '../utils/useDebouncedQuery'
11
+ import { Select } from './Select'
11
12
12
13
interface LevelSelectProps {
13
14
className ?: string
@@ -29,7 +30,6 @@ export const LevelSelect: FC<LevelSelectProps> = ({
29
30
. sort ( ( a , b ) => a . levelId . localeCompare ( b . levelId ) ) ,
30
31
[ data ] ,
31
32
)
32
-
33
33
const fuse = useMemo (
34
34
( ) =>
35
35
new Fuse ( levels , {
@@ -39,15 +39,24 @@ export const LevelSelect: FC<LevelSelectProps> = ({
39
39
[ levels ] ,
40
40
)
41
41
42
- // value 可以由用户输入,所以可以是任何值,只有用 stageId 才能匹配到唯一的关卡
43
- const selectedLevel = useMemo (
44
- ( ) => levels . find ( ( el ) => el . stageId === value ) ?? null ,
45
- [ levels , value ] ,
46
- )
42
+ const { query, debouncedQuery, updateQuery, onOptionMouseDown } =
43
+ useDebouncedQuery ( )
44
+
45
+ const selectedLevel = useMemo ( ( ) => {
46
+ const level = levels . find ( ( el ) => el . stageId === value )
47
+ if ( level ) {
48
+ return level
49
+ }
50
+ // 如果有 value 但匹配不到,就创建一个自定义关卡来显示
51
+ if ( value ) {
52
+ return createCustomLevel ( value )
53
+ }
54
+ return undefined
55
+ } , [ levels , value ] )
47
56
48
- const search = ( query : string ) => {
49
- // 如果 query 和当前关卡完全匹配(也就是唯一对应),就显示同类关卡
50
- if ( selectedLevel && selectedLevel . stageId === query ) {
57
+ const filteredLevels = useMemo ( ( ) => {
58
+ // 未输入 query 时显示同类关卡
59
+ if ( selectedLevel && ! debouncedQuery ) {
51
60
let similarLevels : Level [ ]
52
61
let headerName : string
53
62
@@ -83,55 +92,78 @@ export const LevelSelect: FC<LevelSelectProps> = ({
83
92
}
84
93
85
94
if ( similarLevels . length > 1 ) {
86
- const header = createCustomLevel ( headerName )
87
- header . stageId = 'header'
95
+ const header = createCustomLevel ( 'header' )
96
+ header . name = headerName
88
97
return [ header , ...similarLevels ]
89
98
}
90
99
}
91
100
92
- return query ? fuse . search ( query ) . map ( ( el ) => el . item ) : levels
93
- }
101
+ return debouncedQuery . trim ( )
102
+ ? fuse . search ( debouncedQuery ) . map ( ( el ) => el . item )
103
+ : levels
104
+ } , [ debouncedQuery , selectedLevel , levels , fuse ] )
94
105
95
106
return (
96
- < Suggest < Level >
97
- updateQueryOnSelect
107
+ < Select < Level >
98
108
items = { levels }
99
- itemListPredicate = { search }
109
+ itemListPredicate = { ( ) => filteredLevels }
110
+ query = { query }
111
+ onQueryChange = { ( query ) => updateQuery ( query , false ) }
100
112
onReset = { ( ) => onChange ( '' ) }
101
- className = { clsx ( className , selectedLevel && '[&_input]:italic' ) }
113
+ className = { clsx ( 'items-stretch' , className ) }
114
+ itemsEqual = { ( a , b ) => a . stageId === b . stageId }
115
+ itemDisabled = { ( item ) => item . stageId === 'header' } // 避免 header 被选中为 active
102
116
itemRenderer = { ( item , { handleClick, handleFocus, modifiers } ) =>
103
117
item . stageId === 'header' ? (
104
- < Fragment key = "header" >
105
- < div className = "ml-2 text-zinc-500 text-xs" > { item . name } </ div >
106
- < MenuDivider />
107
- </ Fragment >
118
+ < MenuDivider key = "header" title = { item . name } />
108
119
) : (
109
120
< MenuItem
121
+ roleStructure = "listoption"
110
122
key = { item . stageId }
123
+ className = { clsx ( modifiers . active && Classes . ACTIVE ) }
111
124
text = { `${ item . catThree } ${ item . name } ` }
112
125
onClick = { handleClick }
113
126
onFocus = { handleFocus }
114
- selected = { modifiers . active }
127
+ onMouseDown = { onOptionMouseDown }
128
+ selected = { item === selectedLevel }
115
129
disabled = { modifiers . disabled }
116
130
/>
117
131
)
118
132
}
119
133
selectedItem = { selectedLevel }
120
- onItemSelect = { ( level ) => onChange ( level . stageId ) }
121
- inputValueRenderer = { ( item ) => item . stageId }
122
- noResults = { < MenuItem disabled text = "没有可选的关卡" /> }
134
+ onItemSelect = { ( level ) => {
135
+ // 重置 query 以显示同类关卡
136
+ updateQuery ( '' , true )
137
+ onChange ( level . stageId )
138
+ } }
139
+ createNewItemFromQuery = { ( query ) => createCustomLevel ( query ) }
140
+ createNewItemRenderer = { ( query , active , handleClick ) => (
141
+ < MenuItem
142
+ key = "create-new-item"
143
+ roleStructure = "listoption"
144
+ text = { `使用自定义关卡名 "${ query } "` }
145
+ icon = "text-highlight"
146
+ onClick = { handleClick }
147
+ active = { active }
148
+ />
149
+ ) }
123
150
inputProps = { {
124
- placeholder : '关卡名、关卡类型、关卡编号' ,
125
- leftIcon : 'area-of-interest' ,
126
- large : true ,
127
- size : 64 ,
128
- onBlur : ( e ) => {
129
- // 失焦时直接把 query 提交上去,用于处理关卡未匹配的情况
130
- if ( value !== e . target . value ) {
131
- onChange ( e . target . value )
132
- }
133
- } ,
151
+ placeholder : '关卡名、类型、编号' ,
134
152
} }
135
- />
153
+ popoverProps = { {
154
+ minimal : true ,
155
+ } }
156
+ >
157
+ {
158
+ < Button
159
+ minimal
160
+ className = "!pl-3 !pr-2"
161
+ icon = "area-of-interest"
162
+ rightIcon = "chevron-down"
163
+ >
164
+ { selectedLevel ? selectedLevel . catThree : '关卡' }
165
+ </ Button >
166
+ }
167
+ </ Select >
136
168
)
137
169
}
0 commit comments