@gm-pc/react 1.27.5-beta.1 → 1.28.0-alpha.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gm-pc/react",
3
- "version": "1.27.5-beta.1",
3
+ "version": "1.28.0-alpha.0",
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.5-beta.1",
27
+ "@gm-pc/locales": "^1.28.0-alpha.0",
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": "ba7a645cc22f0b5f7271160bf327909c1c057388"
51
+ "gitHead": "5349cb6157a22e000f906b3e19fa944ecd48d8d8"
52
52
  }
@@ -2,7 +2,7 @@ import { HTMLAttributes, ReactNode } from 'react'
2
2
 
3
3
  interface LoadingProps {
4
4
  size?: string
5
- type: any
5
+ type?: any
6
6
  }
7
7
 
8
8
  interface LoadingChunkProps extends HTMLAttributes<HTMLDivElement> {
@@ -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 = false
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
- this.setState({ loading: false })
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 _handlePopupKeyDown = (event: KeyboardEvent): void => {
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
- private _renderList = (config: ConfigProviderProps): ReactNode => {
288
+ renderBottom = () => {
236
289
  const {
237
290
  selected = [],
238
- multiple,
239
- isGroupList,
240
- renderListItem,
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
- const filterData = this._getFilterData()
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
- renderCustomizedBottom(this._popoverRef)}
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
- selected.map((item) => (
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
  // 加多个 &nbsp; 避免对齐问题,有文本才有对齐
396
690
  <div className='gm-text-placeholder'>{placeholder}&nbsp; </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: '180px',
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
  }
@@ -70,3 +70,7 @@
70
70
  }
71
71
  }
72
72
  }
73
+
74
+ .gm-more-select-default-bottom {
75
+ border-top: 1px solid var(--gm-color-border);
76
+ }
@@ -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?(ref: React.RefObject<Popover>): ReactNode
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<void> | void
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> | void
131
+ onSearch?(searchWord: string, data: MoreSelectData<V>): Promise<void | any> | any
102
132
  /** 点击回调 */
103
133
  onClick?(selected: MoreSelectSelected<V>[]): void
104
134
 
@@ -6,6 +6,10 @@
6
6
  border-radius: calc(var(--gm-switch-size-height) / 2);
7
7
  background-color: var(--gm-color-bg-switch);
8
8
 
9
+ &.gm-switch-small {
10
+ --gm-switch-size-height: 16px;
11
+ }
12
+
9
13
  &::before {
10
14
  position: absolute;
11
15
  content: '';
@@ -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'