@gm-pc/react 1.27.5-beta.1 → 1.28.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/component/loading/types.ts +1 -1
- package/src/component/more_select/base.tsx +369 -75
- package/src/component/more_select/more_select.tsx +11 -1
- package/src/component/more_select/stories.tsx +92 -0
- package/src/component/more_select/style.less +4 -0
- package/src/component/more_select/types.ts +35 -5
- package/src/component/switch/style.less +4 -0
- package/src/component/switch/switch.tsx +7 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gm-pc/react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.28.0-alpha.3",
|
|
4
4
|
"description": "观麦前端基础组件库",
|
|
5
5
|
"author": "liyatang <liyatang@qq.com>",
|
|
6
6
|
"homepage": "https://github.com/gmfe/gm-pc#readme",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@gm-common/hooks": "^2.10.0",
|
|
26
26
|
"@gm-common/tool": "^2.10.0",
|
|
27
|
-
"@gm-pc/locales": "^1.
|
|
27
|
+
"@gm-pc/locales": "^1.28.0-alpha.3",
|
|
28
28
|
"big.js": "^6.0.1",
|
|
29
29
|
"classnames": "^2.2.5",
|
|
30
30
|
"lodash": "^4.17.19",
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"react-router-dom": "^5.2.0",
|
|
49
49
|
"react-window": "^1.8.5"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "3ab0bb66ffd6b71a10df6c02debccc3b0a05ebba"
|
|
52
52
|
}
|
|
@@ -20,12 +20,17 @@ import { getLocale } from '@gm-pc/locales'
|
|
|
20
20
|
import { ListBase } from '../list'
|
|
21
21
|
import { findDOMNode } from 'react-dom'
|
|
22
22
|
import { ConfigConsumer, ConfigProvider, ConfigProviderProps } from '../config_provider'
|
|
23
|
-
|
|
23
|
+
import { Checkbox } from '../checkbox'
|
|
24
|
+
import { Switch } from '../switch'
|
|
24
25
|
interface MoreSelectBaseState {
|
|
26
|
+
canClear?: boolean
|
|
25
27
|
searchValue: string
|
|
26
28
|
loading: boolean
|
|
27
29
|
/* keyboard 默认第一个位置 */
|
|
28
30
|
willActiveIndex: number | null
|
|
31
|
+
isCheckedAll: boolean
|
|
32
|
+
isFilterDelete: boolean
|
|
33
|
+
displayCount: number
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
// @todo keydown item disabled
|
|
@@ -41,6 +46,9 @@ class MoreSelectBase<V extends string | number = string> extends Component<
|
|
|
41
46
|
searchValue: '',
|
|
42
47
|
loading: false,
|
|
43
48
|
willActiveIndex: this.props.isKeyboard ? 0 : null,
|
|
49
|
+
isCheckedAll: false,
|
|
50
|
+
isFilterDelete: true,
|
|
51
|
+
displayCount: 0,
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
private _isUnmounted = false
|
|
@@ -48,6 +56,7 @@ class MoreSelectBase<V extends string | number = string> extends Component<
|
|
|
48
56
|
private _selectionRef = createRef<HTMLDivElement>()
|
|
49
57
|
private _popoverRef = createRef<Popover>()
|
|
50
58
|
private _inputRef = createRef<HTMLInputElement>()
|
|
59
|
+
private _resizeObserver: ResizeObserver | null = null
|
|
51
60
|
|
|
52
61
|
private _filterData: MoreSelectGroupDataItem<V>[] | undefined
|
|
53
62
|
|
|
@@ -62,8 +71,42 @@ class MoreSelectBase<V extends string | number = string> extends Component<
|
|
|
62
71
|
}
|
|
63
72
|
}
|
|
64
73
|
|
|
74
|
+
componentDidMount() {
|
|
75
|
+
const { maxTagCount, tagItemWidth = 80, omittedTagWidth = 45 } = this.props
|
|
76
|
+
if (maxTagCount === 'responsive' && this._selectionRef.current) {
|
|
77
|
+
// HACK: 首次计算
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
if (this._selectionRef.current) {
|
|
80
|
+
const { width } = this._selectionRef.current.getBoundingClientRect()
|
|
81
|
+
const availableWidth = width - omittedTagWidth
|
|
82
|
+
const newDisplayCount = Math.floor(availableWidth / tagItemWidth)
|
|
83
|
+
if (this.state.displayCount !== newDisplayCount) {
|
|
84
|
+
this.setState({ displayCount: newDisplayCount > 0 ? newDisplayCount : 0 })
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}, 0)
|
|
88
|
+
|
|
89
|
+
this._resizeObserver = new ResizeObserver((entries) => {
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
const { width } = entry.contentRect
|
|
92
|
+
// Estimate item width, let's say 80px.
|
|
93
|
+
const availableWidth = width - omittedTagWidth
|
|
94
|
+
const newDisplayCount = Math.floor(availableWidth / tagItemWidth)
|
|
95
|
+
|
|
96
|
+
if (this.state.displayCount !== newDisplayCount) {
|
|
97
|
+
this.setState({ displayCount: newDisplayCount })
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
this._resizeObserver.observe(this._selectionRef.current)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
65
105
|
componentWillUnmount() {
|
|
66
|
-
this._isUnmounted =
|
|
106
|
+
this._isUnmounted = true
|
|
107
|
+
if (this._resizeObserver) {
|
|
108
|
+
this._resizeObserver.disconnect()
|
|
109
|
+
}
|
|
67
110
|
}
|
|
68
111
|
|
|
69
112
|
public apiDoFocus = (): void => {
|
|
@@ -130,8 +173,14 @@ class MoreSelectBase<V extends string | number = string> extends Component<
|
|
|
130
173
|
event: ChangeEvent<HTMLInputElement>,
|
|
131
174
|
isInitSearch?: boolean
|
|
132
175
|
): void => {
|
|
176
|
+
const { onSearch } = this.props
|
|
133
177
|
const searchValue = event.target.value
|
|
134
178
|
this.setState({ searchValue })
|
|
179
|
+
if (onSearch && !this._isUnmounted) {
|
|
180
|
+
this.setState({
|
|
181
|
+
loading: true,
|
|
182
|
+
})
|
|
183
|
+
}
|
|
135
184
|
this._debounceDoSearch(searchValue)
|
|
136
185
|
setTimeout(() => {
|
|
137
186
|
// eslint-disable-next-line no-unused-expressions
|
|
@@ -145,14 +194,12 @@ class MoreSelectBase<V extends string | number = string> extends Component<
|
|
|
145
194
|
private _doSearch = (query: string): void => {
|
|
146
195
|
const { onSearch, data = [] } = this.props
|
|
147
196
|
if (!this._isUnmounted && onSearch) {
|
|
148
|
-
const result = onSearch(query, data)
|
|
149
|
-
if (!result) {
|
|
150
|
-
return
|
|
151
|
-
}
|
|
152
197
|
this.setState({ loading: true })
|
|
153
|
-
|
|
198
|
+
const result = onSearch(query, data)
|
|
154
199
|
Promise.resolve(result).finally(() => {
|
|
155
|
-
|
|
200
|
+
setTimeout(() => {
|
|
201
|
+
this.setState({ loading: false })
|
|
202
|
+
}, 50)
|
|
156
203
|
})
|
|
157
204
|
}
|
|
158
205
|
}
|
|
@@ -166,7 +213,13 @@ class MoreSelectBase<V extends string | number = string> extends Component<
|
|
|
166
213
|
onSelect(willSelected)
|
|
167
214
|
}
|
|
168
215
|
|
|
169
|
-
private
|
|
216
|
+
private _handleClearAll = (event: MouseEvent): void => {
|
|
217
|
+
event.stopPropagation()
|
|
218
|
+
const { onSelect = _.noop } = this.props
|
|
219
|
+
onSelect([])
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private _handlePopupKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {
|
|
170
223
|
const { onKeyDown } = this.props
|
|
171
224
|
let willActiveIndex = this.state.willActiveIndex as number
|
|
172
225
|
if (!onKeyDown) {
|
|
@@ -201,9 +254,9 @@ class MoreSelectBase<V extends string | number = string> extends Component<
|
|
|
201
254
|
}
|
|
202
255
|
|
|
203
256
|
private _getFilterData = () => {
|
|
204
|
-
const { data = [], renderListFilter, renderListFilterType } = this.props
|
|
257
|
+
const { data = [], renderListFilter, renderListFilterType, onSearch } = this.props
|
|
205
258
|
const { searchValue } = this.state
|
|
206
|
-
let filterData: MoreSelectGroupDataItem<V>[]
|
|
259
|
+
let filterData: MoreSelectGroupDataItem<V>[] = []
|
|
207
260
|
if (renderListFilter) {
|
|
208
261
|
filterData = renderListFilter(data, searchValue)
|
|
209
262
|
} else if (renderListFilterType === 'pinyin') {
|
|
@@ -232,19 +285,223 @@ class MoreSelectBase<V extends string | number = string> extends Component<
|
|
|
232
285
|
)
|
|
233
286
|
}
|
|
234
287
|
|
|
235
|
-
|
|
288
|
+
renderBottom = () => {
|
|
236
289
|
const {
|
|
237
290
|
selected = [],
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
291
|
+
isShowDeletedSwitch = true,
|
|
292
|
+
isShowCheckedAll = true,
|
|
293
|
+
} = this.props
|
|
294
|
+
const { isCheckedAll, isFilterDelete } = this.state
|
|
295
|
+
const flatFilterData = this._getFlatFilterData()
|
|
296
|
+
|
|
297
|
+
// 根据过滤状态决定是否过滤已删除商品
|
|
298
|
+
const availableData = isFilterDelete
|
|
299
|
+
? flatFilterData.filter((item) => !item.deleted)
|
|
300
|
+
: flatFilterData
|
|
301
|
+
|
|
302
|
+
// 检查是否所有可用数据都被选中
|
|
303
|
+
const allSelected =
|
|
304
|
+
availableData.length > 0 &&
|
|
305
|
+
availableData.every((item) =>
|
|
306
|
+
selected.some((selectedItem) => selectedItem.value === item.value)
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
return (
|
|
310
|
+
<Flex
|
|
311
|
+
justifyBetween
|
|
312
|
+
className='tw-p-[8px] gm-more-select-default-bottom'
|
|
313
|
+
alignCenter
|
|
314
|
+
>
|
|
315
|
+
{isShowCheckedAll && (
|
|
316
|
+
<Checkbox
|
|
317
|
+
checked={allSelected}
|
|
318
|
+
onChange={(e) => {
|
|
319
|
+
const isChecked = e.target.checked
|
|
320
|
+
this.setState({
|
|
321
|
+
isCheckedAll: isChecked,
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
if (isChecked) {
|
|
325
|
+
// 全选当前过滤后的可用数据
|
|
326
|
+
const valuesToSelect = availableData.map((item) => item.value)
|
|
327
|
+
const prevSelectedValue = selected.map((item) => item.value)
|
|
328
|
+
// console.log(valuesToSelect, selected)
|
|
329
|
+
const newSelected = Array.from(
|
|
330
|
+
new Set([...prevSelectedValue, ...valuesToSelect])
|
|
331
|
+
)
|
|
332
|
+
this._handleSelect(newSelected)
|
|
333
|
+
} else {
|
|
334
|
+
// 取消全选 - 只反勾选availableData的数据
|
|
335
|
+
const { selected = [] } = this.props
|
|
336
|
+
const availableValues = availableData.map((item) => item.value)
|
|
337
|
+
const newSelected = selected.filter(
|
|
338
|
+
(item) => !availableValues.includes(item.value)
|
|
339
|
+
)
|
|
340
|
+
this._handleSelect(newSelected.map((item) => item.value))
|
|
341
|
+
}
|
|
342
|
+
}}
|
|
343
|
+
>
|
|
344
|
+
全选({availableData.length})
|
|
345
|
+
</Checkbox>
|
|
346
|
+
)}
|
|
347
|
+
{isShowDeletedSwitch && (
|
|
348
|
+
<Flex alignCenter>
|
|
349
|
+
<Flex row>
|
|
350
|
+
<Switch
|
|
351
|
+
size='small'
|
|
352
|
+
style={{ width: 32 }}
|
|
353
|
+
checked={isFilterDelete}
|
|
354
|
+
onChange={(open) => {
|
|
355
|
+
this.setState({
|
|
356
|
+
isFilterDelete: open,
|
|
357
|
+
})
|
|
358
|
+
// if (isCheckedAll) {
|
|
359
|
+
// const newAvailableData = open
|
|
360
|
+
// ? flatFilterData.filter((item) => !item.deleted)
|
|
361
|
+
// : flatFilterData
|
|
362
|
+
|
|
363
|
+
// const valuesToSelect = newAvailableData.map((item) => item.value)
|
|
364
|
+
// this._handleSelect(valuesToSelect)
|
|
365
|
+
// }
|
|
366
|
+
}}
|
|
367
|
+
/>
|
|
368
|
+
</Flex>
|
|
369
|
+
<span className='gm-margin-left-5'>过滤已删除数据</span>
|
|
370
|
+
</Flex>
|
|
371
|
+
)}
|
|
372
|
+
</Flex>
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
renderContent = () => {
|
|
377
|
+
const { loading, willActiveIndex, isFilterDelete } = this.state
|
|
378
|
+
|
|
379
|
+
if (loading) {
|
|
380
|
+
return (
|
|
381
|
+
<Flex alignCenter justifyCenter className='gm-bg gm-padding-5'>
|
|
382
|
+
<Loading size='20px' />
|
|
383
|
+
</Flex>
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
let filterData = this._getFilterData()
|
|
388
|
+
|
|
389
|
+
// 如果开启了过滤已删除商品功能,需要过滤掉已删除的商品
|
|
390
|
+
if (isFilterDelete) {
|
|
391
|
+
filterData = filterData
|
|
392
|
+
.map((group) => ({
|
|
393
|
+
...group,
|
|
394
|
+
children: group.children.filter((item) => !item.deleted),
|
|
395
|
+
}))
|
|
396
|
+
.filter((group) => group.children.length > 0)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (!loading && filterData.length === 0) {
|
|
400
|
+
return this._renderEmpty()
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (!loading && filterData.length > 0) {
|
|
404
|
+
const {
|
|
405
|
+
selected = [],
|
|
406
|
+
multiple,
|
|
407
|
+
isGroupList,
|
|
408
|
+
renderListItem,
|
|
409
|
+
listHeight,
|
|
410
|
+
} = this.props
|
|
411
|
+
|
|
412
|
+
const selectedValues = new Set(selected?.map((v) => v.value))
|
|
413
|
+
|
|
414
|
+
// 分离已勾选和未勾选的数据
|
|
415
|
+
const availableGroups: MoreSelectGroupDataItem<V>[] = []
|
|
416
|
+
// 已选中区域直接使用 selected 构建,不受筛选影响
|
|
417
|
+
const selectedGroups: MoreSelectGroupDataItem<V>[] = []
|
|
418
|
+
|
|
419
|
+
if (multiple) {
|
|
420
|
+
filterData.forEach((group) => {
|
|
421
|
+
const availableChildren = group.children.filter(
|
|
422
|
+
(item) => !selectedValues.has(item.value)
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
if (availableChildren.length > 0) {
|
|
426
|
+
availableGroups.push({
|
|
427
|
+
...group,
|
|
428
|
+
children: availableChildren,
|
|
429
|
+
})
|
|
430
|
+
}
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
// if (selected.length > 0) {
|
|
434
|
+
// selectedGroups.push({
|
|
435
|
+
// label: '',
|
|
436
|
+
// children: selected,
|
|
437
|
+
// })
|
|
438
|
+
// }
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return (
|
|
442
|
+
<div style={{ height: listHeight, overflow: 'auto' }}>
|
|
443
|
+
{/* {selected.length > 0 && multiple && (
|
|
444
|
+
<>
|
|
445
|
+
<div className='gm-more-select-section-title gm-padding-5 gm-text-desc gm-text-12'>
|
|
446
|
+
已选中
|
|
447
|
+
</div>
|
|
448
|
+
<ListBase
|
|
449
|
+
selected={selected.map((v) => v.value)}
|
|
450
|
+
data={selectedGroups}
|
|
451
|
+
multiple={multiple}
|
|
452
|
+
isGroupList={false}
|
|
453
|
+
className='gm-border-0'
|
|
454
|
+
renderItem={renderListItem}
|
|
455
|
+
onSelect={this._handleSelect}
|
|
456
|
+
isScrollTo={false}
|
|
457
|
+
/>
|
|
458
|
+
</>
|
|
459
|
+
)} */}
|
|
460
|
+
<>
|
|
461
|
+
{multiple && (
|
|
462
|
+
<div className='gm-more-select-section-title gm-padding-5 gm-text-desc gm-text-12'>
|
|
463
|
+
未选中
|
|
464
|
+
</div>
|
|
465
|
+
)}
|
|
466
|
+
|
|
467
|
+
<ListBase
|
|
468
|
+
selected={selected?.map((v) => v.value)}
|
|
469
|
+
data={multiple ? availableGroups : filterData}
|
|
470
|
+
multiple={multiple}
|
|
471
|
+
isGroupList={isGroupList}
|
|
472
|
+
className='gm-border-0'
|
|
473
|
+
renderItem={renderListItem}
|
|
474
|
+
onSelect={this._handleSelect}
|
|
475
|
+
isScrollTo
|
|
476
|
+
willActiveIndex={willActiveIndex!}
|
|
477
|
+
/>
|
|
478
|
+
</>
|
|
479
|
+
</div>
|
|
480
|
+
)
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
private _renderList = (config: ConfigProviderProps): ReactNode => {
|
|
485
|
+
const {
|
|
241
486
|
searchPlaceholder,
|
|
242
487
|
listHeight,
|
|
243
488
|
popupClassName,
|
|
244
489
|
renderCustomizedBottom,
|
|
490
|
+
isRenderDefaultBottom = false,
|
|
245
491
|
} = this.props
|
|
246
|
-
const { loading, searchValue, willActiveIndex } = this.state
|
|
247
|
-
|
|
492
|
+
const { loading, searchValue, willActiveIndex, isFilterDelete } = this.state
|
|
493
|
+
let filterData = this._getFilterData()
|
|
494
|
+
|
|
495
|
+
// 如果开启了过滤已删除商品功能,需要过滤掉已删除的商品
|
|
496
|
+
if (isFilterDelete) {
|
|
497
|
+
filterData = filterData
|
|
498
|
+
.map((group) => ({
|
|
499
|
+
...group,
|
|
500
|
+
children: group.children.filter((item) => !item.deleted),
|
|
501
|
+
}))
|
|
502
|
+
.filter((group) => group.children.length > 0)
|
|
503
|
+
}
|
|
504
|
+
|
|
248
505
|
return (
|
|
249
506
|
<ConfigProvider {...config}>
|
|
250
507
|
<div
|
|
@@ -260,32 +517,14 @@ class MoreSelectBase<V extends string | number = string> extends Component<
|
|
|
260
517
|
placeholder={searchPlaceholder}
|
|
261
518
|
/>
|
|
262
519
|
</div>
|
|
263
|
-
<div style={{ height: listHeight }}>
|
|
264
|
-
{loading && (
|
|
265
|
-
<Flex alignCenter justifyCenter className='gm-bg gm-padding-5'>
|
|
266
|
-
<Loading size='20px' />
|
|
267
|
-
</Flex>
|
|
268
|
-
)}
|
|
269
|
-
{!loading && !filterData.length && this._renderEmpty()}
|
|
270
|
-
{!loading && !!filterData.length && (
|
|
271
|
-
<ListBase
|
|
272
|
-
selected={selected.map((v) => v.value)}
|
|
273
|
-
data={filterData}
|
|
274
|
-
multiple={multiple}
|
|
275
|
-
isGroupList={isGroupList}
|
|
276
|
-
className='gm-border-0'
|
|
277
|
-
renderItem={renderListItem}
|
|
278
|
-
onSelect={this._handleSelect}
|
|
279
|
-
isScrollTo
|
|
280
|
-
willActiveIndex={willActiveIndex!}
|
|
281
|
-
style={{ height: listHeight }}
|
|
282
|
-
/>
|
|
283
|
-
)}
|
|
284
|
-
</div>
|
|
520
|
+
<div style={{ height: listHeight }}>{this.renderContent()}</div>
|
|
285
521
|
{!loading &&
|
|
286
522
|
!!filterData.length &&
|
|
287
|
-
renderCustomizedBottom
|
|
288
|
-
|
|
523
|
+
(renderCustomizedBottom
|
|
524
|
+
? renderCustomizedBottom(this._popoverRef, this.renderBottom)
|
|
525
|
+
: isRenderDefaultBottom
|
|
526
|
+
? this.renderBottom()
|
|
527
|
+
: null)}
|
|
289
528
|
</div>
|
|
290
529
|
</ConfigProvider>
|
|
291
530
|
)
|
|
@@ -325,7 +564,94 @@ class MoreSelectBase<V extends string | number = string> extends Component<
|
|
|
325
564
|
style,
|
|
326
565
|
popoverType,
|
|
327
566
|
children,
|
|
567
|
+
maxTagCount,
|
|
568
|
+
maxTagPlaceholder,
|
|
328
569
|
} = this.props
|
|
570
|
+
|
|
571
|
+
// 处理 maxTagCount 逻辑
|
|
572
|
+
const renderSelectedItems = () => {
|
|
573
|
+
if (!multiple || !maxTagCount || selected.length === 0) {
|
|
574
|
+
return selected.map((item) => (
|
|
575
|
+
<Flex key={item.value as any} className='gm-more-select-selected-item'>
|
|
576
|
+
<Popover
|
|
577
|
+
disabled={!this.props.isKeyboard}
|
|
578
|
+
type='hover'
|
|
579
|
+
popup={<div className='gm-padding-10'>{item.text}</div>}
|
|
580
|
+
>
|
|
581
|
+
<Flex flex column>
|
|
582
|
+
{renderSelected!(item)}
|
|
583
|
+
</Flex>
|
|
584
|
+
</Popover>
|
|
585
|
+
{multiple ? (
|
|
586
|
+
<SVGRemove
|
|
587
|
+
className='gm-cursor gm-more-select-clear-btn'
|
|
588
|
+
onClick={disabled ? _.noop : this._handleClear.bind(this, item)}
|
|
589
|
+
/>
|
|
590
|
+
) : (
|
|
591
|
+
!disabledClose && ( // 是否不限时清除按钮,仅单选可用
|
|
592
|
+
<SVGCloseCircle
|
|
593
|
+
onClick={disabled ? _.noop : this._handleClear.bind(this, item)}
|
|
594
|
+
className='gm-cursor gm-more-select-clear-btn'
|
|
595
|
+
/>
|
|
596
|
+
)
|
|
597
|
+
)}
|
|
598
|
+
</Flex>
|
|
599
|
+
))
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// 处理 maxTagCount 逻辑
|
|
603
|
+
const isResponsive = maxTagCount === 'responsive'
|
|
604
|
+
let displayCount: number
|
|
605
|
+
|
|
606
|
+
if (isResponsive) {
|
|
607
|
+
displayCount = this.state.displayCount
|
|
608
|
+
} else {
|
|
609
|
+
displayCount = maxTagCount as number
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const itemsToShow = selected.slice(0, displayCount)
|
|
613
|
+
const omittedItems = selected.slice(displayCount)
|
|
614
|
+
const omittedCount = selected.length - displayCount
|
|
615
|
+
|
|
616
|
+
return (
|
|
617
|
+
<>
|
|
618
|
+
{itemsToShow.map((item) => (
|
|
619
|
+
<Flex key={item.value as any} className='gm-more-select-selected-item'>
|
|
620
|
+
<Popover
|
|
621
|
+
disabled={!this.props.isKeyboard}
|
|
622
|
+
type='hover'
|
|
623
|
+
popup={<div className='gm-padding-10'>{item.text}</div>}
|
|
624
|
+
>
|
|
625
|
+
<Flex flex column>
|
|
626
|
+
{renderSelected!(item)}
|
|
627
|
+
</Flex>
|
|
628
|
+
</Popover>
|
|
629
|
+
<SVGRemove
|
|
630
|
+
className='gm-cursor gm-more-select-clear-btn'
|
|
631
|
+
onClick={disabled ? _.noop : this._handleClear.bind(this, item)}
|
|
632
|
+
/>
|
|
633
|
+
</Flex>
|
|
634
|
+
))}
|
|
635
|
+
{omittedCount > 0 && (
|
|
636
|
+
<Flex
|
|
637
|
+
key='omitted'
|
|
638
|
+
className='gm-more-select-selected-item gm-more-select-omitted-item'
|
|
639
|
+
>
|
|
640
|
+
{maxTagPlaceholder ? (
|
|
641
|
+
maxTagPlaceholder(omittedItems, omittedCount)
|
|
642
|
+
) : (
|
|
643
|
+
<span className='gm-more-select-omitted-count'>+{omittedCount}...</span>
|
|
644
|
+
)}
|
|
645
|
+
</Flex>
|
|
646
|
+
)}
|
|
647
|
+
<SVGRemove
|
|
648
|
+
className='gm-cursor gm-more-select-clear-btn'
|
|
649
|
+
onClick={disabled ? _.noop : this._handleClearAll}
|
|
650
|
+
/>
|
|
651
|
+
</>
|
|
652
|
+
)
|
|
653
|
+
}
|
|
654
|
+
|
|
329
655
|
return (
|
|
330
656
|
<ConfigConsumer>
|
|
331
657
|
{(config) => (
|
|
@@ -340,7 +666,7 @@ class MoreSelectBase<V extends string | number = string> extends Component<
|
|
|
340
666
|
},
|
|
341
667
|
className
|
|
342
668
|
)}
|
|
343
|
-
style={style}
|
|
669
|
+
style={style as any}
|
|
344
670
|
>
|
|
345
671
|
<Popover
|
|
346
672
|
ref={this._popoverRef}
|
|
@@ -358,39 +684,7 @@ class MoreSelectBase<V extends string | number = string> extends Component<
|
|
|
358
684
|
className='gm-more-select-selected'
|
|
359
685
|
>
|
|
360
686
|
{selected.length !== 0 ? (
|
|
361
|
-
|
|
362
|
-
<Flex
|
|
363
|
-
key={item.value as any}
|
|
364
|
-
className='gm-more-select-selected-item'
|
|
365
|
-
>
|
|
366
|
-
<Popover
|
|
367
|
-
disabled={!this.props.isKeyboard}
|
|
368
|
-
type='hover'
|
|
369
|
-
popup={<div className='gm-padding-10'>{item.text}</div>}
|
|
370
|
-
>
|
|
371
|
-
<Flex flex column>
|
|
372
|
-
{renderSelected!(item)}
|
|
373
|
-
</Flex>
|
|
374
|
-
</Popover>
|
|
375
|
-
{multiple ? (
|
|
376
|
-
<SVGRemove
|
|
377
|
-
className='gm-cursor gm-more-select-clear-btn'
|
|
378
|
-
onClick={
|
|
379
|
-
disabled ? _.noop : this._handleClear.bind(this, item)
|
|
380
|
-
}
|
|
381
|
-
/>
|
|
382
|
-
) : (
|
|
383
|
-
!disabledClose && ( // 是否不限时清除按钮,仅单选可用
|
|
384
|
-
<SVGCloseCircle
|
|
385
|
-
onClick={
|
|
386
|
-
disabled ? _.noop : this._handleClear.bind(this, item)
|
|
387
|
-
}
|
|
388
|
-
className='gm-cursor gm-more-select-clear-btn'
|
|
389
|
-
/>
|
|
390
|
-
)
|
|
391
|
-
)}
|
|
392
|
-
</Flex>
|
|
393
|
-
))
|
|
687
|
+
renderSelectedItems()
|
|
394
688
|
) : (
|
|
395
689
|
// 加多个 避免对齐问题,有文本才有对齐
|
|
396
690
|
<div className='gm-text-placeholder'>{placeholder} </div>
|
|
@@ -8,10 +8,16 @@ class MoreSelect<V = any> extends Component<MoreSelectProps<V>> {
|
|
|
8
8
|
renderSelected: (item: MoreSelectDataItem<any>) => item.text,
|
|
9
9
|
renderListItem: (item: MoreSelectDataItem<any>) => item.text,
|
|
10
10
|
delay: 500,
|
|
11
|
-
listHeight: '
|
|
11
|
+
listHeight: '280px',
|
|
12
12
|
renderListFilterType: 'default',
|
|
13
13
|
popoverType: 'focus',
|
|
14
14
|
onKeyDown: _.noop,
|
|
15
|
+
/** 是否展示全选以及过滤已删除商品 */
|
|
16
|
+
isRenderDefaultBottom: false,
|
|
17
|
+
/** 是否展示已删除商品 */
|
|
18
|
+
isShowDeletedSwitch: true,
|
|
19
|
+
/** 是否展示全选 */
|
|
20
|
+
isShowCheckedAll: true,
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
private _moreSelectBaseRef = createRef<MoreSelectBase>()
|
|
@@ -85,6 +91,8 @@ class MoreSelect<V = any> extends Component<MoreSelectProps<V>> {
|
|
|
85
91
|
onSearch,
|
|
86
92
|
onClick,
|
|
87
93
|
renderListFilter,
|
|
94
|
+
maxTagCount,
|
|
95
|
+
maxTagPlaceholder,
|
|
88
96
|
...rest
|
|
89
97
|
} = this.props
|
|
90
98
|
let tempSelect = selected as MoreSelectDataItem<V>[]
|
|
@@ -129,6 +137,8 @@ class MoreSelect<V = any> extends Component<MoreSelectProps<V>> {
|
|
|
129
137
|
isGroupList={isGroupList}
|
|
130
138
|
onSearch={onSearch && this._handleSearch}
|
|
131
139
|
renderListFilter={renderListFilter && this._renderListFilter}
|
|
140
|
+
maxTagCount={maxTagCount}
|
|
141
|
+
maxTagPlaceholder={maxTagPlaceholder}
|
|
132
142
|
/>
|
|
133
143
|
)
|
|
134
144
|
}
|
|
@@ -318,6 +318,98 @@ export const ComMoreSelectWithIsGroupListMultiple = () => (
|
|
|
318
318
|
/>
|
|
319
319
|
)
|
|
320
320
|
|
|
321
|
+
export const ComMoreSelectWithMaxTagCount = () => {
|
|
322
|
+
// 创建一个包含多个选项的数据集
|
|
323
|
+
const manyOptions = [
|
|
324
|
+
{ value: 1, text: '选项1' },
|
|
325
|
+
{ value: 2, text: '选项2' },
|
|
326
|
+
{ value: 3, text: '选项3' },
|
|
327
|
+
{ value: 4, text: '选项4' },
|
|
328
|
+
{ value: 5, text: '选项5' },
|
|
329
|
+
{ value: 6, text: '选项6' },
|
|
330
|
+
{ value: 7, text: '选项7' },
|
|
331
|
+
{ value: 8, text: '选项8' },
|
|
332
|
+
]
|
|
333
|
+
|
|
334
|
+
// 预选多个选项
|
|
335
|
+
const preSelected = manyOptions.slice(0, 6)
|
|
336
|
+
|
|
337
|
+
return (
|
|
338
|
+
<div style={{ width: '300px' }}>
|
|
339
|
+
<h3>maxTagCount 示例</h3>
|
|
340
|
+
|
|
341
|
+
<div style={{ marginBottom: '20px' }}>
|
|
342
|
+
<h4>不使用 maxTagCount(显示所有选项)</h4>
|
|
343
|
+
<MoreSelect<number>
|
|
344
|
+
multiple
|
|
345
|
+
data={manyOptions}
|
|
346
|
+
selected={preSelected}
|
|
347
|
+
onSelect={(selected) => {
|
|
348
|
+
console.log('不限制显示数量:', selected)
|
|
349
|
+
}}
|
|
350
|
+
/>
|
|
351
|
+
</div>
|
|
352
|
+
|
|
353
|
+
<div style={{ marginBottom: '20px' }}>
|
|
354
|
+
<h4>maxTagCount={2}(最多显示2个选项)</h4>
|
|
355
|
+
<MoreSelect<number>
|
|
356
|
+
multiple
|
|
357
|
+
maxTagCount={2}
|
|
358
|
+
data={manyOptions}
|
|
359
|
+
selected={preSelected}
|
|
360
|
+
onSelect={(selected) => {
|
|
361
|
+
console.log('最多显示2个:', selected)
|
|
362
|
+
}}
|
|
363
|
+
/>
|
|
364
|
+
</div>
|
|
365
|
+
|
|
366
|
+
<div style={{ marginBottom: '20px' }}>
|
|
367
|
+
<h4>maxTagCount={3}(最多显示3个选项)</h4>
|
|
368
|
+
<MoreSelect<number>
|
|
369
|
+
multiple
|
|
370
|
+
maxTagCount={3}
|
|
371
|
+
data={manyOptions}
|
|
372
|
+
selected={preSelected}
|
|
373
|
+
onSelect={(selected) => {
|
|
374
|
+
console.log('最多显示3个:', selected)
|
|
375
|
+
}}
|
|
376
|
+
/>
|
|
377
|
+
</div>
|
|
378
|
+
|
|
379
|
+
<div style={{ marginBottom: '20px' }}>
|
|
380
|
+
<h4>maxTagCount=responsive(响应式模式)</h4>
|
|
381
|
+
<MoreSelect<number>
|
|
382
|
+
multiple
|
|
383
|
+
maxTagCount='responsive'
|
|
384
|
+
data={manyOptions}
|
|
385
|
+
selected={preSelected}
|
|
386
|
+
onSelect={(selected) => {
|
|
387
|
+
console.log('响应式模式:', selected)
|
|
388
|
+
}}
|
|
389
|
+
/>
|
|
390
|
+
</div>
|
|
391
|
+
|
|
392
|
+
<div style={{ marginBottom: '20px' }}>
|
|
393
|
+
<h4>自定义 maxTagPlaceholder</h4>
|
|
394
|
+
<MoreSelect<number>
|
|
395
|
+
multiple
|
|
396
|
+
maxTagCount={2}
|
|
397
|
+
maxTagPlaceholder={(omittedValues, omittedCount) => (
|
|
398
|
+
<span style={{ color: '#1890ff', fontWeight: 'bold' }}>
|
|
399
|
+
还有{omittedCount}项未显示
|
|
400
|
+
</span>
|
|
401
|
+
)}
|
|
402
|
+
data={manyOptions}
|
|
403
|
+
selected={preSelected}
|
|
404
|
+
onSelect={(selected) => {
|
|
405
|
+
console.log('自定义占位符:', selected)
|
|
406
|
+
}}
|
|
407
|
+
/>
|
|
408
|
+
</div>
|
|
409
|
+
</div>
|
|
410
|
+
)
|
|
411
|
+
}
|
|
412
|
+
|
|
321
413
|
export default {
|
|
322
414
|
title: '表单/MoreSelect',
|
|
323
415
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { Popover } from '@gm-pc/react'
|
|
2
1
|
import { CSSProperties, ReactNode, KeyboardEvent } from 'react'
|
|
2
|
+
import { Popover } from '../popover'
|
|
3
3
|
|
|
4
4
|
/** 普通的数据格式 */
|
|
5
5
|
interface MoreSelectDataItem<V extends string | number = string> {
|
|
6
6
|
value: V
|
|
7
7
|
text: string
|
|
8
8
|
disabled?: boolean
|
|
9
|
+
/** 是否已删除 */
|
|
10
|
+
deleted?: boolean
|
|
9
11
|
[key: string]: any
|
|
10
12
|
}
|
|
11
13
|
|
|
@@ -36,10 +38,13 @@ interface MoreSelectCommonProps<V extends string | number = string> {
|
|
|
36
38
|
renderListItem?(value: MoreSelectDataItem<V>, index: number): ReactNode
|
|
37
39
|
|
|
38
40
|
/** 自定义popup底部渲染 */
|
|
39
|
-
renderCustomizedBottom?(
|
|
41
|
+
renderCustomizedBottom?(
|
|
42
|
+
ref: React.RefObject<Popover>,
|
|
43
|
+
defaultBottom: () => ReactNode
|
|
44
|
+
): ReactNode
|
|
40
45
|
|
|
41
46
|
/**
|
|
42
|
-
*
|
|
47
|
+
* 自定义"空状态"渲染
|
|
43
48
|
*
|
|
44
49
|
* 若函数返回 undefined 则使用默认的空状态
|
|
45
50
|
*/
|
|
@@ -60,6 +65,25 @@ interface MoreSelectCommonProps<V extends string | number = string> {
|
|
|
60
65
|
/** 目前为了 keyboard */
|
|
61
66
|
isKeyboard?: boolean
|
|
62
67
|
onKeyDown?(event: KeyboardEvent): void
|
|
68
|
+
|
|
69
|
+
/** 最多显示的选中项数量,超出部分会折叠 */
|
|
70
|
+
maxTagCount?: number | 'responsive'
|
|
71
|
+
/** 自定义超出 maxTagCount 时显示的内容 */
|
|
72
|
+
maxTagPlaceholder?: (
|
|
73
|
+
omittedValues: MoreSelectDataItem<V>[],
|
|
74
|
+
omittedCount: number
|
|
75
|
+
) => ReactNode
|
|
76
|
+
|
|
77
|
+
/** 是否展示全选以及过滤已删除商品 */
|
|
78
|
+
isRenderDefaultBottom?: boolean
|
|
79
|
+
/** 是否展示已删除商品 */
|
|
80
|
+
isShowDeletedSwitch?: boolean
|
|
81
|
+
/** 是否展示全选 */
|
|
82
|
+
isShowCheckedAll?: boolean
|
|
83
|
+
/** 当设置 maxTagCount 的时候的宽度, 根据这个去计算显示内容 */
|
|
84
|
+
tagItemWidth?: number
|
|
85
|
+
/** +N 显示的宽度 */
|
|
86
|
+
omittedTagWidth?: number
|
|
63
87
|
}
|
|
64
88
|
|
|
65
89
|
interface MoreSelectBaseProps<V extends string | number = string>
|
|
@@ -69,7 +93,7 @@ interface MoreSelectBaseProps<V extends string | number = string>
|
|
|
69
93
|
onSelect(selected: MoreSelectDataItem<V>[]): void
|
|
70
94
|
|
|
71
95
|
/** 搜索回调 */
|
|
72
|
-
onSearch?(searchWord: string, data: MoreSelectGroupDataItem<V>[]): Promise<
|
|
96
|
+
onSearch?(searchWord: string, data: MoreSelectGroupDataItem<V>[]): Promise<any> | any
|
|
73
97
|
/** 点击回调 */
|
|
74
98
|
onClick?(selected: MoreSelectSelected<V>[]): void
|
|
75
99
|
|
|
@@ -80,6 +104,12 @@ interface MoreSelectBaseProps<V extends string | number = string>
|
|
|
80
104
|
): MoreSelectGroupDataItem<V>[]
|
|
81
105
|
/** 是否在active的时候搜索,订单业务相关,searchValue放在localstorage */
|
|
82
106
|
searchOnActive?: boolean
|
|
107
|
+
/** 是否展示全选以及过滤已删除商品 */
|
|
108
|
+
isRenderDefaultBottom?: boolean
|
|
109
|
+
/** 是否展示已删除商品 */
|
|
110
|
+
isShowDeletedSwitch?: boolean
|
|
111
|
+
/** 是否展示全选 */
|
|
112
|
+
isShowCheckedAll?: boolean
|
|
83
113
|
}
|
|
84
114
|
|
|
85
115
|
type MoreSelectData<V extends string | number = string> =
|
|
@@ -98,7 +128,7 @@ interface MoreSelectProps<V extends string | number = string>
|
|
|
98
128
|
onSelect?(selected?: MoreSelectSelected<V>): void
|
|
99
129
|
onChange?(value: V | V[]): void
|
|
100
130
|
/** 搜索回调 */
|
|
101
|
-
onSearch?(searchWord: string, data: MoreSelectData<V>): Promise<void> |
|
|
131
|
+
onSearch?(searchWord: string, data: MoreSelectData<V>): Promise<void | any> | any
|
|
102
132
|
/** 点击回调 */
|
|
103
133
|
onClick?(selected: MoreSelectSelected<V>[]): void
|
|
104
134
|
|
|
@@ -11,6 +11,7 @@ interface SwitchProps {
|
|
|
11
11
|
onChange?(checked: boolean): void
|
|
12
12
|
className?: string
|
|
13
13
|
style?: CSSProperties
|
|
14
|
+
size?: 'small' | 'middle'
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
interface SwitchState {
|
|
@@ -26,10 +27,11 @@ class Switch extends Component<SwitchProps, SwitchState> {
|
|
|
26
27
|
on: '',
|
|
27
28
|
off: '',
|
|
28
29
|
onChange: _.noop,
|
|
30
|
+
size: 'middle',
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
readonly state: SwitchState = {
|
|
32
|
-
checked: this.props.checked,
|
|
34
|
+
checked: !!this.props.checked,
|
|
33
35
|
labelWidth: null,
|
|
34
36
|
isReady: false,
|
|
35
37
|
}
|
|
@@ -53,7 +55,7 @@ class Switch extends Component<SwitchProps, SwitchState> {
|
|
|
53
55
|
UNSAFE_componentWillReceiveProps(nextProps: Readonly<SwitchProps>) {
|
|
54
56
|
if ('checked' in nextProps) {
|
|
55
57
|
this.setState({
|
|
56
|
-
checked: nextProps.checked,
|
|
58
|
+
checked: !!nextProps.checked,
|
|
57
59
|
})
|
|
58
60
|
}
|
|
59
61
|
}
|
|
@@ -82,6 +84,7 @@ class Switch extends Component<SwitchProps, SwitchState> {
|
|
|
82
84
|
disabled,
|
|
83
85
|
on,
|
|
84
86
|
off,
|
|
87
|
+
size,
|
|
85
88
|
...rest
|
|
86
89
|
} = this.props
|
|
87
90
|
|
|
@@ -101,8 +104,9 @@ class Switch extends Component<SwitchProps, SwitchState> {
|
|
|
101
104
|
ref={this._inputOffRef}
|
|
102
105
|
className={classNames('gm-switch gm-switch-' + type, className, {
|
|
103
106
|
disabled,
|
|
107
|
+
[`gm-switch-${size}`]: size,
|
|
104
108
|
})}
|
|
105
|
-
style={style}
|
|
109
|
+
style={style as any}
|
|
106
110
|
data-attr={this.state.labelWidth}
|
|
107
111
|
disabled={disabled}
|
|
108
112
|
type='checkbox'
|