@teamix-evo/ui 0.1.1 → 0.3.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/README.md +184 -184
- package/manifest.json +680 -492
- package/package.json +20 -10
- package/src/components/accordion/accordion.meta.md +5 -4
- package/src/components/accordion/accordion.stories.tsx +14 -9
- package/src/components/accordion/accordion.tsx +104 -8
- package/src/components/affix/affix.meta.md +20 -2
- package/src/components/affix/affix.stories.tsx +102 -25
- package/src/components/affix/affix.tsx +79 -9
- package/src/components/alert/alert.meta.md +44 -13
- package/src/components/alert/alert.stories.tsx +66 -21
- package/src/components/alert/alert.tsx +81 -34
- package/src/components/alert-dialog/alert-dialog.meta.md +61 -16
- package/src/components/alert-dialog/alert-dialog.stories.tsx +145 -3
- package/src/components/alert-dialog/alert-dialog.tsx +60 -13
- package/src/components/anchor/anchor.meta.md +8 -3
- package/src/components/anchor/anchor.stories.tsx +3 -3
- package/src/components/anchor/anchor.tsx +2 -2
- package/src/components/app/app.meta.md +9 -4
- package/src/components/app/app.stories.tsx +9 -7
- package/src/components/aspect-ratio/aspect-ratio.meta.md +4 -3
- package/src/components/aspect-ratio/aspect-ratio.stories.tsx +3 -3
- package/src/components/auto-complete/auto-complete.meta.md +14 -6
- package/src/components/auto-complete/auto-complete.stories.tsx +47 -4
- package/src/components/auto-complete/auto-complete.tsx +119 -71
- package/src/components/avatar/avatar.meta.md +6 -7
- package/src/components/avatar/avatar.stories.tsx +21 -3
- package/src/components/avatar/avatar.tsx +24 -23
- package/src/components/badge/badge.meta.md +10 -9
- package/src/components/badge/badge.stories.tsx +2 -2
- package/src/components/badge/badge.tsx +9 -15
- package/src/components/breadcrumb/breadcrumb.meta.md +27 -7
- package/src/components/breadcrumb/breadcrumb.stories.tsx +127 -4
- package/src/components/breadcrumb/breadcrumb.tsx +22 -8
- package/src/components/button/button.meta.md +258 -21
- package/src/components/button/button.stories.tsx +549 -41
- package/src/components/button/button.tsx +335 -33
- package/src/components/button/demo/as-child.tsx +24 -0
- package/src/components/button/demo/basic.tsx +8 -0
- package/src/components/button/demo/block.tsx +16 -0
- package/src/components/button/demo/loading.tsx +19 -0
- package/src/components/button/demo/shapes.tsx +18 -0
- package/src/components/button/demo/sizes.tsx +19 -0
- package/src/components/button/demo/variants.tsx +19 -0
- package/src/components/button/demo/with-icon.tsx +20 -0
- package/src/components/calendar/calendar.meta.md +13 -3
- package/src/components/calendar/calendar.stories.tsx +6 -6
- package/src/components/calendar/calendar.tsx +73 -8
- package/src/components/card/card.meta.md +27 -5
- package/src/components/card/card.stories.tsx +42 -3
- package/src/components/card/card.tsx +146 -63
- package/src/components/carousel/carousel.meta.md +4 -3
- package/src/components/carousel/carousel.stories.tsx +11 -6
- package/src/components/cascader/cascader.meta.md +47 -17
- package/src/components/cascader/cascader.stories.tsx +22 -10
- package/src/components/cascader/cascader.tsx +428 -85
- package/src/components/checkbox/checkbox.meta.md +75 -7
- package/src/components/checkbox/checkbox.stories.tsx +161 -3
- package/src/components/checkbox/checkbox.tsx +77 -9
- package/src/components/collapsible/collapsible.meta.md +14 -6
- package/src/components/collapsible/collapsible.stories.tsx +10 -2
- package/src/components/collapsible/collapsible.tsx +93 -6
- package/src/components/color-picker/color-picker.meta.md +12 -7
- package/src/components/color-picker/color-picker.stories.tsx +86 -7
- package/src/components/color-picker/color-picker.tsx +20 -9
- package/src/components/command/command.meta.md +29 -13
- package/src/components/command/command.stories.tsx +4 -4
- package/src/components/command/command.tsx +19 -8
- package/src/components/context-menu/context-menu.meta.md +11 -8
- package/src/components/context-menu/context-menu.stories.tsx +11 -3
- package/src/components/context-menu/context-menu.tsx +21 -8
- package/src/components/data-table/data-table.meta.md +6 -5
- package/src/components/data-table/data-table.stories.tsx +13 -6
- package/src/components/data-table/data-table.tsx +2 -2
- package/src/components/date-picker/date-picker.meta.md +88 -19
- package/src/components/date-picker/date-picker.stories.tsx +55 -5
- package/src/components/date-picker/date-picker.tsx +1489 -91
- package/src/components/descriptions/descriptions.meta.md +10 -5
- package/src/components/descriptions/descriptions.stories.tsx +3 -3
- package/src/components/descriptions/descriptions.tsx +22 -14
- package/src/components/dialog/dialog.meta.md +76 -13
- package/src/components/dialog/dialog.stories.tsx +182 -20
- package/src/components/dialog/dialog.tsx +67 -15
- package/src/components/dialog/imperative.tsx +252 -0
- package/src/components/drawer/drawer.meta.md +33 -34
- package/src/components/drawer/drawer.stories.tsx +29 -12
- package/src/components/drawer/drawer.tsx +22 -113
- package/src/components/dropdown-menu/dropdown-menu.meta.md +78 -10
- package/src/components/dropdown-menu/dropdown-menu.stories.tsx +88 -2
- package/src/components/dropdown-menu/dropdown-menu.tsx +24 -10
- package/src/components/ellipsis/ellipsis.meta.md +87 -0
- package/src/components/ellipsis/ellipsis.stories.tsx +72 -0
- package/src/components/ellipsis/ellipsis.tsx +153 -0
- package/src/components/empty/empty.meta.md +9 -4
- package/src/components/empty/empty.stories.tsx +4 -4
- package/src/components/empty/empty.tsx +10 -3
- package/src/components/field/field.meta.md +47 -9
- package/src/components/field/field.stories.tsx +385 -5
- package/src/components/field/field.tsx +263 -35
- package/src/components/filter-bar/filter-bar.meta.md +92 -0
- package/src/components/filter-bar/filter-bar.stories.tsx +1083 -0
- package/src/components/filter-bar/filter-bar.tsx +568 -0
- package/src/components/flex/flex.meta.md +54 -6
- package/src/components/flex/flex.stories.tsx +107 -20
- package/src/components/flex/flex.tsx +27 -4
- package/src/components/float-button/float-button.meta.md +8 -3
- package/src/components/float-button/float-button.stories.tsx +9 -7
- package/src/components/float-button/float-button.tsx +1 -1
- package/src/components/form/form.meta.md +39 -17
- package/src/components/form/form.stories.tsx +350 -3
- package/src/components/form/form.tsx +101 -35
- package/src/components/grid/grid.meta.md +7 -2
- package/src/components/grid/grid.stories.tsx +6 -4
- package/src/components/hover-card/hover-card.meta.md +20 -9
- package/src/components/hover-card/hover-card.stories.tsx +34 -5
- package/src/components/hover-card/hover-card.tsx +51 -13
- package/src/components/icon/DEVELOPMENT.md +809 -0
- package/src/components/icon/icon.meta.md +170 -0
- package/src/components/icon/icon.stories.tsx +344 -0
- package/src/components/icon/icon.tsx +248 -0
- package/src/components/image/image.meta.md +9 -4
- package/src/components/image/image.stories.tsx +3 -3
- package/src/components/image/image.tsx +6 -4
- package/src/components/input/demo/basic.tsx +12 -0
- package/src/components/input/demo/clearable.tsx +21 -0
- package/src/components/input/demo/show-count.tsx +18 -0
- package/src/components/input/demo/sizes.tsx +15 -0
- package/src/components/input/input.meta.md +39 -33
- package/src/components/input/input.stories.tsx +62 -35
- package/src/components/input/input.tsx +97 -98
- package/src/components/input-group/input-group.meta.md +54 -22
- package/src/components/input-group/input-group.stories.tsx +49 -16
- package/src/components/input-group/input-group.tsx +44 -8
- package/src/components/input-number/input-number.meta.md +64 -7
- package/src/components/input-number/input-number.stories.tsx +46 -8
- package/src/components/input-number/input-number.tsx +99 -26
- package/src/components/input-otp/input-otp.meta.md +4 -3
- package/src/components/input-otp/input-otp.stories.tsx +3 -3
- package/src/components/input-otp/input-otp.tsx +1 -1
- package/src/components/item/item.meta.md +8 -3
- package/src/components/item/item.stories.tsx +8 -5
- package/src/components/item/item.tsx +7 -6
- package/src/components/kbd/kbd.meta.md +13 -4
- package/src/components/kbd/kbd.stories.tsx +4 -4
- package/src/components/kbd/kbd.tsx +10 -5
- package/src/components/label/label.meta.md +18 -10
- package/src/components/label/label.stories.tsx +64 -6
- package/src/components/label/label.tsx +91 -19
- package/src/components/masonry/masonry.meta.md +8 -3
- package/src/components/masonry/masonry.stories.tsx +7 -5
- package/src/components/masonry/masonry.tsx +1 -0
- package/src/components/mentions/mentions.meta.md +36 -6
- package/src/components/mentions/mentions.stories.tsx +120 -6
- package/src/components/mentions/mentions.tsx +11 -5
- package/src/components/menubar/menubar.meta.md +30 -12
- package/src/components/menubar/menubar.stories.tsx +62 -2
- package/src/components/menubar/menubar.tsx +9 -9
- package/src/components/native-select/native-select.meta.md +8 -3
- package/src/components/native-select/native-select.stories.tsx +8 -5
- package/src/components/native-select/native-select.tsx +1 -1
- package/src/components/navigation-menu/navigation-menu.meta.md +19 -9
- package/src/components/navigation-menu/navigation-menu.stories.tsx +112 -9
- package/src/components/navigation-menu/navigation-menu.tsx +8 -4
- package/src/components/notification/notification.meta.md +52 -10
- package/src/components/notification/notification.stories.tsx +11 -9
- package/src/components/notification/notification.tsx +36 -21
- package/src/components/page-header/DEVELOPMENT.md +842 -0
- package/src/components/page-header/page-header.meta.md +208 -0
- package/src/components/page-header/page-header.stories.tsx +421 -0
- package/src/components/page-header/page-header.tsx +281 -0
- package/src/components/pagination/pagination.meta.md +140 -37
- package/src/components/pagination/pagination.stories.tsx +232 -10
- package/src/components/pagination/pagination.tsx +355 -63
- package/src/components/popconfirm/popconfirm.meta.md +9 -4
- package/src/components/popconfirm/popconfirm.stories.tsx +3 -4
- package/src/components/popconfirm/popconfirm.tsx +2 -2
- package/src/components/popover/popover.meta.md +62 -5
- package/src/components/popover/popover.stories.tsx +83 -7
- package/src/components/popover/popover.tsx +77 -28
- package/src/components/progress/progress.meta.md +38 -6
- package/src/components/progress/progress.stories.tsx +3 -3
- package/src/components/progress/progress.tsx +24 -16
- package/src/components/radio-group/radio-group.meta.md +79 -7
- package/src/components/radio-group/radio-group.stories.tsx +39 -3
- package/src/components/radio-group/radio-group.tsx +149 -18
- package/src/components/rate/rate.meta.md +35 -4
- package/src/components/rate/rate.stories.tsx +13 -5
- package/src/components/rate/rate.tsx +37 -10
- package/src/components/resizable/resizable.meta.md +7 -4
- package/src/components/resizable/resizable.stories.tsx +6 -6
- package/src/components/resizable/resizable.tsx +1 -1
- package/src/components/result/result.meta.md +7 -2
- package/src/components/result/result.stories.tsx +4 -8
- package/src/components/result/result.tsx +24 -15
- package/src/components/scroll-area/scroll-area.meta.md +4 -3
- package/src/components/scroll-area/scroll-area.stories.tsx +12 -4
- package/src/components/scroll-area/scroll-area.tsx +3 -3
- package/src/components/segmented/segmented.meta.md +7 -4
- package/src/components/segmented/segmented.stories.tsx +37 -8
- package/src/components/segmented/segmented.tsx +15 -7
- package/src/components/select/select.meta.md +197 -52
- package/src/components/select/select.stories.tsx +238 -63
- package/src/components/select/select.tsx +718 -171
- package/src/components/separator/separator.meta.md +4 -3
- package/src/components/separator/separator.stories.tsx +3 -3
- package/src/components/separator/separator.tsx +3 -7
- package/src/components/sheet/sheet.meta.md +32 -16
- package/src/components/sheet/sheet.stories.tsx +116 -10
- package/src/components/sheet/sheet.tsx +116 -29
- package/src/components/sidebar/sidebar.meta.md +37 -18
- package/src/components/sidebar/sidebar.stories.tsx +701 -29
- package/src/components/sidebar/sidebar.tsx +615 -142
- package/src/components/skeleton/skeleton.meta.md +4 -5
- package/src/components/skeleton/skeleton.stories.tsx +4 -4
- package/src/components/skeleton/skeleton.tsx +7 -7
- package/src/components/slider/slider.meta.md +57 -5
- package/src/components/slider/slider.stories.tsx +58 -6
- package/src/components/slider/slider.tsx +154 -13
- package/src/components/sonner/sonner.meta.md +58 -7
- package/src/components/sonner/sonner.stories.tsx +78 -5
- package/src/components/sonner/sonner.tsx +137 -8
- package/src/components/spinner/spinner.meta.md +62 -13
- package/src/components/spinner/spinner.stories.tsx +66 -14
- package/src/components/spinner/spinner.tsx +111 -9
- package/src/components/statistic/statistic.meta.md +7 -2
- package/src/components/statistic/statistic.stories.tsx +3 -7
- package/src/components/statistic/statistic.tsx +5 -6
- package/src/components/steps/steps.meta.md +18 -4
- package/src/components/steps/steps.stories.tsx +43 -3
- package/src/components/steps/steps.tsx +15 -12
- package/src/components/switch/switch.meta.md +51 -5
- package/src/components/switch/switch.stories.tsx +6 -6
- package/src/components/switch/switch.tsx +109 -41
- package/src/components/table/table.meta.md +17 -6
- package/src/components/table/table.stories.tsx +10 -5
- package/src/components/table/table.tsx +4 -4
- package/src/components/tabs/tabs.meta.md +38 -25
- package/src/components/tabs/tabs.stories.tsx +111 -25
- package/src/components/tabs/tabs.tsx +125 -54
- package/src/components/tag/tag.meta.md +105 -40
- package/src/components/tag/tag.stories.tsx +189 -16
- package/src/components/tag/tag.tsx +222 -21
- package/src/components/textarea/textarea.meta.md +35 -19
- package/src/components/textarea/textarea.stories.tsx +32 -6
- package/src/components/textarea/textarea.tsx +33 -9
- package/src/components/time-picker/time-picker.meta.md +124 -32
- package/src/components/time-picker/time-picker.stories.tsx +85 -15
- package/src/components/time-picker/time-picker.tsx +913 -61
- package/src/components/timeline/timeline.meta.md +14 -6
- package/src/components/timeline/timeline.stories.tsx +37 -7
- package/src/components/timeline/timeline.tsx +35 -14
- package/src/components/toggle/toggle.meta.md +5 -4
- package/src/components/toggle/toggle.stories.tsx +4 -4
- package/src/components/toggle/toggle.tsx +4 -3
- package/src/components/toggle-group/toggle-group.meta.md +5 -4
- package/src/components/toggle-group/toggle-group.stories.tsx +3 -3
- package/src/components/toggle-group/toggle-group.tsx +2 -2
- package/src/components/tooltip/tooltip.meta.md +55 -5
- package/src/components/tooltip/tooltip.stories.tsx +42 -5
- package/src/components/tooltip/tooltip.tsx +81 -21
- package/src/components/tour/tour.meta.md +9 -4
- package/src/components/tour/tour.stories.tsx +3 -3
- package/src/components/tour/tour.tsx +4 -4
- package/src/components/transfer/transfer.meta.md +11 -6
- package/src/components/transfer/transfer.stories.tsx +4 -8
- package/src/components/transfer/transfer.tsx +28 -21
- package/src/components/tree/tree.meta.md +63 -5
- package/src/components/tree/tree.stories.tsx +31 -12
- package/src/components/tree/tree.tsx +9 -8
- package/src/components/tree-select/tree-select.meta.md +59 -8
- package/src/components/tree-select/tree-select.stories.tsx +3 -3
- package/src/components/tree-select/tree-select.tsx +42 -7
- package/src/components/typography/typography.meta.md +61 -14
- package/src/components/typography/typography.stories.tsx +12 -11
- package/src/components/typography/typography.tsx +43 -28
- package/src/components/upload/upload.meta.md +49 -4
- package/src/components/upload/upload.stories.tsx +72 -12
- package/src/components/upload/upload.tsx +170 -37
- package/src/components/watermark/watermark.meta.md +7 -2
- package/src/components/watermark/watermark.stories.tsx +101 -9
- package/src/components/watermark/watermark.tsx +1 -0
- package/src/hooks/use-breakpoint.ts +117 -0
- package/src/hooks/use-debounce-callback.ts +52 -0
- package/src/hooks/use-mobile.ts +23 -0
- package/src/stories/theme-tokens.stories.tsx +747 -0
- package/src/utils/trigger-input.ts +53 -0
- package/src/components/button-group/button-group.meta.md +0 -92
- package/src/components/button-group/button-group.stories.tsx +0 -90
- package/src/components/button-group/button-group.tsx +0 -75
- package/src/components/combobox/combobox.meta.md +0 -93
- package/src/components/combobox/combobox.stories.tsx +0 -55
- package/src/components/combobox/combobox.tsx +0 -130
- package/src/components/space/space.meta.md +0 -94
- package/src/components/space/space.stories.tsx +0 -94
- package/src/components/space/space.tsx +0 -106
|
@@ -2,43 +2,71 @@
|
|
|
2
2
|
id: select
|
|
3
3
|
name: Select
|
|
4
4
|
type: component
|
|
5
|
-
category:
|
|
5
|
+
category: data-entry
|
|
6
6
|
since: 0.1.0
|
|
7
|
-
package:
|
|
7
|
+
package: '@teamix-evo/ui'
|
|
8
|
+
displayName: 下拉选择
|
|
8
9
|
---
|
|
9
10
|
|
|
10
|
-
# Select
|
|
11
|
+
# Select 下拉选择
|
|
11
12
|
|
|
12
|
-
下拉选择 —
|
|
13
|
-
|
|
13
|
+
下拉选择 — **单组件覆盖 cd `Select` 全部模式**:单选 / 多选 / 搜索 / 标签创建(creatable)/ 远程异步(onSearch)/ 虚拟滚动(virtual)/ 弹层容器(container)。基于 **Popover + cmdk Command + @tanstack/react-virtual**;触发器 `size` 三档与 Button / Input 同档(24 / 32 / 36)。原独立的 `Combobox` 已合并入此组件(参考 ButtonGroup → Button、Textarea → Input 范式)。
|
|
14
|
+
|
|
15
|
+
> **W3 重构**(2026-06):原 `Select`(Radix Select 单选基础版) + `Combobox`(独立多选/搜索) 合并为一个数据驱动的 `Select`,完整对齐 cd hybridcloud `Select` 全部能力。原 JSX-style API(`SelectTrigger` / `SelectContent` / `SelectItem`)已下线,业务侧需迁移到数据驱动 API(`options` / `groups` / `value`)。详见 §[迁移指南](#迁移指南--从旧版-jsx-api--新数据驱动-api)。
|
|
14
16
|
|
|
15
17
|
## When to use
|
|
16
18
|
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
19
|
+
- 单选(`<Select options={...} />`)— 单值场景
|
|
20
|
+
- 多选(`<Select multiple options={...} />`)— Tag 形式回显已选项
|
|
21
|
+
- 可搜索过滤(`searchable`)— 弹层顶部搜索框,本地 filter
|
|
22
|
+
- 远程异步(`onSearch`)— 输入触发拉接口刷新 options(自动关闭本地 filter)
|
|
23
|
+
- 自由创建标签(`creatable` + `multiple`)— 等价 cd `mode="tag"`,经典 Tag 输入器
|
|
24
|
+
- 大数据集(`virtual`)— ≥ 100 项时启用虚拟滚动,首次渲染显著优化
|
|
25
|
+
- 内层容器场景(`container`)— Dialog / Drawer / 自定义 overlay 内,Portal 挂到指定节点
|
|
20
26
|
|
|
21
27
|
## When NOT to use
|
|
22
28
|
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
29
|
+
- 选项 ≤ 4 → `RadioGroup`(更直观,无需展开)
|
|
30
|
+
- 自由文本输入 → `Input`
|
|
31
|
+
- 树形选择 → `TreeSelect` / `Cascader`
|
|
32
|
+
- 多列级联选择 → `Cascader`
|
|
33
|
+
- 命令面板 / 全局搜索 → `Command`(直接用底层 cmdk,不要走 Select)
|
|
27
34
|
|
|
28
35
|
## Props
|
|
29
36
|
|
|
30
|
-
> 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta`
|
|
37
|
+
> 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成。
|
|
31
38
|
|
|
32
39
|
<!-- auto:props:begin -->
|
|
33
40
|
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
34
41
|
| --- | --- | --- | --- | --- |
|
|
35
|
-
| `
|
|
42
|
+
| `options` | `SelectOption[]` | – | – | 扁平选项列表(与 `groups` 二选一)— 等价 antd `<Select options>` 数据驱动模式。 |
|
|
43
|
+
| `groups` | `SelectOptionGroup[]` | – | – | 分组选项列表 — 等价 antd `<Select.OptGroup>`,数据驱动版本。 |
|
|
44
|
+
| `multiple` | `boolean` | `false` | – | 多选模式(antd `mode="multiple"` 并集)。开启后 `value` / `defaultValue` 为 `string[]`,选中项以 Tag 形式回显。 |
|
|
45
|
+
| `searchable` | `boolean` | `false` | – | 启用搜索(antd `showSearch` 并集) — 弹层顶部出现搜索输入框。 |
|
|
46
|
+
| `creatable` | `boolean` | `false` | – | 允许创建新项(antd `mode="tag"` 并集) — 输入未匹配现有选项时,顶部显示 "Create <query>" 项,Enter 或点击即创建并选中。配合 `multiple` 是经典 Tag 模式。 创建时通过 `onCreate(value)` 通知消费方(由消费方决定是否把新项追加到 `options`)。 |
|
|
47
|
+
| `onCreate` | `(value: string) => void` | – | – | 创建新项时的回调(creatable 模式) — 消费方据此追加 options。 |
|
|
48
|
+
| `onSearch` | `(query: string) => void` | – | – | 异步搜索(antd `onSearch` 并集)— 输入变化时触发,消费方据 `query` 拉接口刷新 `options`。 触发后默认**关闭本地 filter**(因为远端已经过滤过);如需保留本地 filter,设 `filter={true}` 显式启用。 |
|
|
49
|
+
| `filter` | `boolean \| ((query: string, option: SelectOption) => boolean)` | `true` | – | 是否启用本地 filter:`true`(默认) / `false` / 自定义函数。 配合 `onSearch` 时通常设 `false` — 服务端已过滤,本地不再筛。 |
|
|
50
|
+
| `value` | `SelectValue` | – | – | 受控 value — 单选 `string`、多选 `string[]`。 |
|
|
51
|
+
| `defaultValue` | `SelectValue` | – | – | uncontrolled 初值。 |
|
|
52
|
+
| `onChange` | `((value: string) => void) \| ((value: string[]) => void)` | – | – | value 变化回调。 |
|
|
53
|
+
| `maxTagCount` | `number` | – | – | 多选模式下展示的最大 Tag 数,超出折叠为 `+N`(antd `maxTagCount` 并集)。 |
|
|
54
|
+
| `placeholder` | `string` | `"请选择..."` | – | 触发器占位文本。 |
|
|
55
|
+
| `searchPlaceholder` | `string` | `"搜索..."` | – | 搜索框占位文本(searchable / creatable 时生效)。 |
|
|
56
|
+
| `emptyText` | `string` | `"无匹配项"` | – | 无匹配项时的提示文本。 |
|
|
57
|
+
| `hideEmptyOptionGroup` | `boolean` | `true` | – | 搜索后无匹配项的分组是否隐藏(antd `hideEmptyOptionGroup` 并集)。 |
|
|
58
|
+
| `clearable` | `boolean` | `false` | – | 显示清除按钮(antd `allowClear` 并集) — 触发器有值时右侧 ✕,点击清空。 |
|
|
59
|
+
| `size` | `'sm' \| 'md' \| 'default' \| 'lg'` | `"md"` | – | 触发器尺寸 — 与 Button / Input 同档:`sm`(24) / `md`(32) / `lg`(36)。 |
|
|
60
|
+
| `error` | `boolean` | `false` | – | 错误态(自动设置 `aria-invalid="true"`,渲染 destructive 边框)。 |
|
|
61
|
+
| `disabled` | `boolean` | – | – | 禁用整个组件。 |
|
|
62
|
+
| `className` | `string` | – | – | 触发器额外类名(可覆盖宽度 / 边距等)。 |
|
|
63
|
+
| `contentClassName` | `string` | – | – | 弹层 className。 |
|
|
64
|
+
| `container` | `HTMLElement \| null` | – | – | 弹层挂载容器(antd `popupContainer` 并集) — 透传给 Popover Portal。 默认挂在 `document.body`;在内层滚动容器 / Dialog 内时常需指定。 |
|
|
65
|
+
| `virtual` | `boolean \| { threshold?: number; itemHeight?: number }` | `false (传 `true` 等价 `{ threshold: 100, itemHeight: 36 }`)` | – | 虚拟滚动(antd `virtual`) — 选项数 ≥ `threshold` 时启用 react-virtual, 大数据集下显著优化首次渲染。注意:启用后本地 filter 仍生效但分组渲染受限 (groups + virtual 同时启用时按 flatten 渲染),业务侧大数据集建议直接走 options。 |
|
|
36
66
|
<!-- auto:props:end -->
|
|
37
67
|
|
|
38
68
|
## 依赖
|
|
39
69
|
|
|
40
|
-
> 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成,数据源是 [`manifest.json`](../../../manifest.json)。**手工编辑 marker 之间的内容会在下次生成时被覆盖**。
|
|
41
|
-
|
|
42
70
|
<!-- auto:deps:begin -->
|
|
43
71
|
### 同库依赖
|
|
44
72
|
|
|
@@ -47,64 +75,181 @@ package: "@teamix-evo/ui"
|
|
|
47
75
|
| Entry | 类型 | 描述 |
|
|
48
76
|
| --- | --- | --- |
|
|
49
77
|
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
78
|
+
| `command` | component | 命令面板 / 下拉底座 — cmdk(Linear / Raycast 风格)提供过滤 / 键盘导航 / a11y;同时为 Combobox / Select / AutoComplete 提供同源下拉内核 |
|
|
79
|
+
| `popover` | component | 可交互浮层 — Radix Popover + antd arrow 并集,使用 showArrow 控制尖角(与 Tooltip / HoverCard 命名统一) |
|
|
50
80
|
|
|
51
81
|
### npm 依赖
|
|
52
82
|
|
|
53
83
|
> 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
|
|
54
84
|
|
|
55
85
|
```bash
|
|
56
|
-
pnpm add @
|
|
86
|
+
pnpm add @tanstack/react-virtual@^3.0.0 lucide-react@^0.460.0
|
|
57
87
|
```
|
|
58
88
|
<!-- auto:deps:end -->
|
|
59
89
|
|
|
60
|
-
> 完整子组件:`Select / SelectGroup / SelectValue / SelectTrigger / SelectContent / SelectLabel / SelectItem / SelectSeparator / SelectScrollUpButton / SelectScrollDownButton`。
|
|
61
|
-
|
|
62
90
|
## AI 生成纪律
|
|
63
91
|
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
92
|
+
- **数据驱动 API**:`options: SelectOption[]` 或 `groups: SelectOptionGroup[]` 二选一,**不要**回到 JSX 子组件嵌套(原 `<SelectItem>` 已下线)
|
|
93
|
+
- **multiple 模式 value 是 `string[]`** — 单选是 `string`;TypeScript 上联合类型,业务侧传值时形态要与 `multiple` 匹配
|
|
94
|
+
- **creatable 必配 onCreate**:消费方在 `onCreate(value)` 回调中决定是否把新项追加到 `options`(否则只是临时选中,关闭后丢失)
|
|
95
|
+
- **onSearch 异步搜索 + filter=false 配合用**:`onSearch` 默认会关闭本地 filter(避免"客户端 filter + 服务端 filter 双过滤"导致结果空集);如果想保留本地 filter,显式 `filter={true}`
|
|
96
|
+
- **virtual 启用门槛**:`options.length >= threshold`(默认 100)才启用;小数据集走 cmdk 路径(更轻量、自带 keyboard nav)
|
|
97
|
+
- **container 用法**:仅在 Dialog / Drawer / 自定义 overlay 内才需要;默认挂 `document.body` 即可。Dialog 内必须传 container 否则 Popover 会被 Dialog overlay 遮挡
|
|
98
|
+
- **size 三档**:`sm`(24) / `md`(32) / `lg`(36),与 Button / Input 同档;`"default"` 仅向后兼容,新代码用 `"md"`
|
|
99
|
+
- **error 走 aria-invalid**:`error=true` 自动设 `aria-invalid="true"` + destructive 边框;**不要**手写 `border-destructive`
|
|
100
|
+
- **clearable 与 maxTagCount 组合用**:多选 + 大量已选项时,`maxTagCount` 折叠展示,`clearable` 一键清空,UX 更友好
|
|
69
101
|
|
|
70
102
|
## Examples
|
|
71
103
|
|
|
72
104
|
```tsx
|
|
73
|
-
import {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
105
|
+
import { Select } from '@/components/ui/select';
|
|
106
|
+
|
|
107
|
+
// 单选
|
|
108
|
+
<Select
|
|
109
|
+
options={[
|
|
110
|
+
{ value: 'bj', label: '北京' },
|
|
111
|
+
{ value: 'sh', label: '上海' },
|
|
112
|
+
]}
|
|
113
|
+
placeholder="选择城市"
|
|
114
|
+
/>
|
|
115
|
+
|
|
116
|
+
// 单选 + 受控
|
|
117
|
+
const [v, setV] = React.useState('bj');
|
|
118
|
+
<Select
|
|
119
|
+
options={cityOptions}
|
|
120
|
+
value={v}
|
|
121
|
+
onChange={(next) => setV(next as string)}
|
|
122
|
+
/>
|
|
123
|
+
|
|
124
|
+
// 分组
|
|
125
|
+
<Select
|
|
126
|
+
groups={[
|
|
127
|
+
{ label: '亚洲', options: [{ value: 'cn', label: '北京' }] },
|
|
128
|
+
{ label: '欧洲', options: [{ value: 'uk', label: '伦敦' }] },
|
|
129
|
+
]}
|
|
130
|
+
/>
|
|
131
|
+
|
|
132
|
+
// 可搜索
|
|
133
|
+
<Select searchable options={cityOptions} />
|
|
134
|
+
|
|
135
|
+
// 多选 + 折叠 +N
|
|
136
|
+
<Select multiple maxTagCount={3} options={cityOptions} defaultValue={['bj','sh','hz','sz']} />
|
|
137
|
+
|
|
138
|
+
// Creatable / Tag 模式(对齐 cd `mode="tag"`)
|
|
139
|
+
const [opts, setOpts] = React.useState([...]);
|
|
140
|
+
<Select
|
|
141
|
+
multiple
|
|
142
|
+
creatable
|
|
143
|
+
searchable
|
|
144
|
+
options={opts}
|
|
145
|
+
onCreate={(val) => setOpts(prev => [...prev, { value: val, label: val }])}
|
|
146
|
+
/>
|
|
147
|
+
|
|
148
|
+
// 远程异步搜索
|
|
149
|
+
const [opts, setOpts] = React.useState([]);
|
|
150
|
+
<Select
|
|
151
|
+
searchable
|
|
152
|
+
options={opts}
|
|
153
|
+
onSearch={async (q) => {
|
|
154
|
+
const list = await api.search(q);
|
|
155
|
+
setOpts(list);
|
|
156
|
+
}}
|
|
157
|
+
/>
|
|
158
|
+
|
|
159
|
+
// 虚拟滚动(5000 项)
|
|
160
|
+
<Select searchable virtual options={hugeList} />
|
|
161
|
+
|
|
162
|
+
// Dialog 内场景:挂到 Dialog 容器
|
|
163
|
+
const dialogRef = React.useRef<HTMLDivElement>(null);
|
|
164
|
+
<DialogContent ref={dialogRef}>
|
|
165
|
+
<Select options={...} container={dialogRef.current} />
|
|
166
|
+
</DialogContent>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Select 形态 — 旧库 API → 新库映射
|
|
170
|
+
|
|
171
|
+
> cd hybridcloud `Select` 单组件多模式;新库 W3 重构后也是单组件多模式 — 模式拆为 prop 而非组件。
|
|
172
|
+
|
|
173
|
+
### 命名映射
|
|
174
|
+
|
|
175
|
+
| cloud-design / antd 旧库 | `@teamix-evo/ui` Select | 备注 |
|
|
176
|
+
| --- | --- | --- |
|
|
177
|
+
| `<Select mode="single">` | `<Select>` | 默认单选 |
|
|
178
|
+
| `<Select mode="multiple">` | `<Select multiple>` | Tag 回显 |
|
|
179
|
+
| `<Select mode="tag">` | `<Select multiple creatable>` | 拆为两个 prop |
|
|
180
|
+
| `<Select.AutoComplete>` | `<Select searchable creatable>` | 合并到 Select |
|
|
181
|
+
| `dataSource` / `children OptGroup` | `options={...}` / `groups={...}` | 数据驱动 |
|
|
182
|
+
| `showSearch` | `searchable` | ✅ |
|
|
183
|
+
| `showSearch + onSearch` | `searchable` + `onSearch` | ✅ async 远程 |
|
|
184
|
+
| `useVirtual` | `virtual`(默认 false,设 true 启用) | ✅ 阈值 100 项 |
|
|
185
|
+
| `popupContainer` | `container` | ✅ 透传 Popover Portal |
|
|
186
|
+
| `popupClassName` / `popupStyle` | `contentClassName` | API 精简 |
|
|
187
|
+
| `maxTagCount` | `maxTagCount` | ✅ 同名 |
|
|
188
|
+
| `hasClear` | `clearable` | ✅ |
|
|
189
|
+
| `value` / `defaultValue` / `onChange` | 同名;value 类型由 `multiple` 决定 | `string` 或 `string[]` |
|
|
190
|
+
| `visible` / `onVisibleChange` | `open` / `onOpenChange` | (Radix Popover 透传) |
|
|
191
|
+
| `hasArrow` | 不修复 — 走 outline 视觉 | — |
|
|
192
|
+
| `fillProps` / `displayValue` | 不修复 — `SelectOption.label` 直接接 ReactNode | — |
|
|
193
|
+
| `filterTreeNode` | `filter` prop(自定义函数) | 单选 / 多选都可 |
|
|
194
|
+
| `hasBorder={false}` | `className="border-0"` 自定义 | — |
|
|
195
|
+
|
|
196
|
+
### Breaking Changes(从原 v0.1 Select 迁移时需改写)
|
|
197
|
+
|
|
198
|
+
> 注意:这是**新旧 @teamix-evo/ui Select** 之间的 breaking,不是 cd → @teamix-evo/ui 的迁移。cd → @teamix-evo/ui 见上方命名映射表。
|
|
199
|
+
|
|
200
|
+
1. **JSX 子组件 API 已下线** — 原 `<SelectTrigger>` / `<SelectContent>` / `<SelectItem>` / `<SelectValue>` / `<SelectGroup>` / `<SelectLabel>` / `<SelectSeparator>` 全部移除,改用数据驱动 API
|
|
201
|
+
2. **value 类型** — 单选保持 `string`,多选必须是 `string[]`(原 v0.1 仅支持单选)
|
|
202
|
+
3. **onChange 签名** — 原 Radix `onValueChange` 改为 `onChange`,支持 `(string) => void`(单选)或 `(string[]) => void`(多选)
|
|
203
|
+
4. **`searchable` 默认 false** — 原 Combobox 默认 true;现合并到 Select 后默认关闭(对齐 cd `showSearch` 默认 false)
|
|
204
|
+
5. **Combobox 已下线** — 原从 `@/components/ui/combobox` 导入 `Combobox` 的代码,改为从 `@/components/ui/select` 导入 `Select` 即可(props 大致兼容,部分命名优化)
|
|
205
|
+
|
|
206
|
+
### 迁移指南 — 从旧版 JSX API → 新数据驱动 API
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
// ❌ Before(旧 v0.1 Select,JSX 子组件)
|
|
210
|
+
<Select onValueChange={setV}>
|
|
211
|
+
<SelectTrigger>
|
|
81
212
|
<SelectValue placeholder="选择城市" />
|
|
82
213
|
</SelectTrigger>
|
|
83
214
|
<SelectContent>
|
|
84
215
|
<SelectItem value="bj">北京</SelectItem>
|
|
85
216
|
<SelectItem value="sh">上海</SelectItem>
|
|
86
|
-
<SelectItem value="hz">杭州</SelectItem>
|
|
87
217
|
</SelectContent>
|
|
88
218
|
</Select>
|
|
89
219
|
|
|
90
|
-
//
|
|
91
|
-
<Select
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
<SelectGroup>
|
|
101
|
-
<SelectLabel>欧洲</SelectLabel>
|
|
102
|
-
<SelectItem value="uk">伦敦时间 (UTC+0)</SelectItem>
|
|
103
|
-
</SelectGroup>
|
|
104
|
-
</SelectContent>
|
|
105
|
-
</Select>
|
|
220
|
+
// ✅ After(W3+,数据驱动)
|
|
221
|
+
<Select
|
|
222
|
+
onChange={(next) => setV(next as string)}
|
|
223
|
+
placeholder="选择城市"
|
|
224
|
+
options={[
|
|
225
|
+
{ value: 'bj', label: '北京' },
|
|
226
|
+
{ value: 'sh', label: '上海' },
|
|
227
|
+
]}
|
|
228
|
+
/>
|
|
229
|
+
```
|
|
106
230
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
231
|
+
```tsx
|
|
232
|
+
// ❌ Before(旧 Combobox)
|
|
233
|
+
import { Combobox } from '@/components/ui/combobox';
|
|
234
|
+
<Combobox multiple searchable options={...} />
|
|
235
|
+
|
|
236
|
+
// ✅ After(W3+,Combobox 合一进 Select)
|
|
237
|
+
import { Select } from '@/components/ui/select';
|
|
238
|
+
<Select multiple searchable options={...} />
|
|
110
239
|
```
|
|
240
|
+
|
|
241
|
+
### 不修复 / 后续工序清单
|
|
242
|
+
|
|
243
|
+
- **`hasArrow` / `hasBorder={false}`**:走 outline trigger 视觉一致;需要无边框时自定义 `className="border-0 shadow-none"`
|
|
244
|
+
- **`fillProps` / `displayValue`**:`SelectOption.label` 已支持 ReactNode,自定义渲染直接传节点
|
|
245
|
+
- **`itemRender` / `valueRender`**:同上,通过 label 节点渲染
|
|
246
|
+
- **`onMouseEnter` / `onMouseLeave` 选项级**:不修复 — 业务低频,需要时用 `label: <span onMouseEnter={...}>` 自管
|
|
247
|
+
- **`isPreview` / `renderPreview`**:不修复 — 走 `disabled` + 自定义渲染替代
|
|
248
|
+
|
|
249
|
+
### Select 专项 AI 生成纪律
|
|
250
|
+
|
|
251
|
+
- 数据驱动 — `options` 或 `groups` 二选一;`groups[].options[]` 不可空数组
|
|
252
|
+
- `keywords` 用于扩展搜索匹配 — 当 `label` 是富节点(如带图标)无法直接 includes 匹配时,通过 `keywords: ['搜索关键字','别名']` 让 cmdk 命中
|
|
253
|
+
- creatable 时业务侧 `onCreate` 必须更新 `options`,否则关闭弹层后新创建项丢失(只在已选 value 中,但下次打开时无对应 label)
|
|
254
|
+
- 远程搜索时 `emptyText` 建议条件渲染:加载中 → "加载中...",未输入 → "请输入关键字",无结果 → "无匹配项"
|
|
255
|
+
- virtual 启用后 `groups` 渲染受限(强制 flatten),业务大数据集建议直接走 `options` 扁平列表
|
|
@@ -1,32 +1,64 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
|
+
import { Select } from './select';
|
|
4
|
+
|
|
5
|
+
const cityOptions = [
|
|
6
|
+
{ value: 'bj', label: '北京' },
|
|
7
|
+
{ value: 'sh', label: '上海' },
|
|
8
|
+
{ value: 'hz', label: '杭州' },
|
|
9
|
+
{ value: 'sz', label: '深圳' },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const tzGroups = [
|
|
13
|
+
{
|
|
14
|
+
label: '亚洲',
|
|
15
|
+
options: [
|
|
16
|
+
{ value: 'cn', label: '北京时间 (UTC+8)' },
|
|
17
|
+
{ value: 'jp', label: '东京时间 (UTC+9)' },
|
|
18
|
+
{ value: 'sg', label: '新加坡时间 (UTC+8)' },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
label: '欧洲',
|
|
23
|
+
options: [
|
|
24
|
+
{ value: 'uk', label: '伦敦时间 (UTC+0)' },
|
|
25
|
+
{ value: 'de', label: '柏林时间 (UTC+1)' },
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const meta: Meta<typeof Select> = {
|
|
31
|
+
title: '数据录入 · Data Entry/Select',
|
|
32
|
+
component: Select,
|
|
16
33
|
tags: ['autodocs'],
|
|
17
34
|
parameters: {
|
|
18
35
|
docs: {
|
|
19
36
|
description: {
|
|
20
37
|
component:
|
|
21
|
-
'下拉选择 —
|
|
38
|
+
'下拉选择 — 单组件覆盖 cd `Select` 全部模式:**单选 / 多选 / 搜索 / 标签创建(creatable)/ 远程异步 / 虚拟滚动**。基于 Popover + cmdk Command;触发器 `size` 三档与 Button / Input 同档(24/32/36)。原 `Combobox` 已合并入此组件。',
|
|
22
39
|
},
|
|
23
40
|
},
|
|
24
41
|
},
|
|
25
42
|
argTypes: {
|
|
26
|
-
size: { control: 'inline-radio', options: ['sm', '
|
|
43
|
+
size: { control: 'inline-radio', options: ['sm', 'md', 'lg'] },
|
|
44
|
+
multiple: { control: 'boolean' },
|
|
45
|
+
searchable: { control: 'boolean' },
|
|
46
|
+
creatable: { control: 'boolean' },
|
|
47
|
+
clearable: { control: 'boolean' },
|
|
27
48
|
disabled: { control: 'boolean' },
|
|
49
|
+
error: { control: 'boolean' },
|
|
50
|
+
placeholder: { control: 'text' },
|
|
51
|
+
},
|
|
52
|
+
args: {
|
|
53
|
+
size: 'md',
|
|
54
|
+
multiple: false,
|
|
55
|
+
searchable: false,
|
|
56
|
+
creatable: false,
|
|
57
|
+
clearable: false,
|
|
58
|
+
disabled: false,
|
|
59
|
+
error: false,
|
|
60
|
+
placeholder: '请选择...',
|
|
28
61
|
},
|
|
29
|
-
args: { size: 'default' },
|
|
30
62
|
decorators: [
|
|
31
63
|
(Story) => (
|
|
32
64
|
<div className="w-64">
|
|
@@ -37,64 +69,207 @@ const meta: Meta<typeof SelectTrigger> = {
|
|
|
37
69
|
};
|
|
38
70
|
|
|
39
71
|
export default meta;
|
|
40
|
-
type Story = StoryObj<typeof
|
|
72
|
+
type Story = StoryObj<typeof Select>;
|
|
41
73
|
|
|
42
74
|
export const Playground: Story = {
|
|
43
|
-
render: (args) =>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
75
|
+
render: (args) => <Select {...args} options={cityOptions} />,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const Sizes: Story = {
|
|
79
|
+
parameters: { controls: { disable: true } },
|
|
80
|
+
render: () => (
|
|
81
|
+
<div className="flex flex-col gap-3">
|
|
82
|
+
{(['sm', 'md', 'lg'] as const).map((s) => (
|
|
83
|
+
<Select
|
|
84
|
+
key={s}
|
|
85
|
+
size={s}
|
|
86
|
+
options={cityOptions}
|
|
87
|
+
placeholder={`size = ${s} (h-${s === 'sm' ? '6' : s === 'md' ? '8' : '9'})`}
|
|
88
|
+
/>
|
|
89
|
+
))}
|
|
90
|
+
</div>
|
|
55
91
|
),
|
|
56
92
|
};
|
|
57
93
|
|
|
58
94
|
export const Grouped: Story = {
|
|
95
|
+
parameters: { controls: { disable: true } },
|
|
96
|
+
render: () => <Select groups={tzGroups} placeholder="选择时区" />,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const Searchable: Story = {
|
|
59
100
|
parameters: { controls: { disable: true } },
|
|
60
101
|
render: () => (
|
|
61
|
-
<Select
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
<SelectLabel>亚洲</SelectLabel>
|
|
68
|
-
<SelectItem value="cn">北京时间 (UTC+8)</SelectItem>
|
|
69
|
-
<SelectItem value="jp">东京时间 (UTC+9)</SelectItem>
|
|
70
|
-
<SelectItem value="sg">新加坡时间 (UTC+8)</SelectItem>
|
|
71
|
-
</SelectGroup>
|
|
72
|
-
<SelectSeparator />
|
|
73
|
-
<SelectGroup>
|
|
74
|
-
<SelectLabel>欧洲</SelectLabel>
|
|
75
|
-
<SelectItem value="uk">伦敦时间 (UTC+0)</SelectItem>
|
|
76
|
-
<SelectItem value="de">柏林时间 (UTC+1)</SelectItem>
|
|
77
|
-
</SelectGroup>
|
|
78
|
-
</SelectContent>
|
|
79
|
-
</Select>
|
|
102
|
+
<Select
|
|
103
|
+
searchable
|
|
104
|
+
options={cityOptions}
|
|
105
|
+
placeholder="可搜索的城市"
|
|
106
|
+
searchPlaceholder="输入城市名..."
|
|
107
|
+
/>
|
|
80
108
|
),
|
|
81
109
|
};
|
|
82
110
|
|
|
83
|
-
export const
|
|
111
|
+
export const Multiple: Story = {
|
|
84
112
|
parameters: { controls: { disable: true } },
|
|
85
113
|
render: () => (
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
114
|
+
<Select
|
|
115
|
+
multiple
|
|
116
|
+
options={cityOptions}
|
|
117
|
+
placeholder="选择多个城市"
|
|
118
|
+
defaultValue={['bj', 'hz']}
|
|
119
|
+
/>
|
|
120
|
+
),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const MultipleWithMaxTagCount: Story = {
|
|
124
|
+
parameters: { controls: { disable: true } },
|
|
125
|
+
render: () => (
|
|
126
|
+
<Select
|
|
127
|
+
multiple
|
|
128
|
+
maxTagCount={2}
|
|
129
|
+
options={cityOptions}
|
|
130
|
+
placeholder="最多展示 2 个 Tag,其余折叠 +N"
|
|
131
|
+
defaultValue={['bj', 'sh', 'hz', 'sz']}
|
|
132
|
+
/>
|
|
133
|
+
),
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Creatable / Tag 模式 — 等价 cd `Select mode="tag"`。
|
|
138
|
+
* 输入未匹配 options 的内容时,顶部显示"创建 'xxx'"项,Enter 即创建并选中。
|
|
139
|
+
* 配合 `multiple` 是经典 Tag 输入器(自由标签创建 + 已选标签 chip 展示)。
|
|
140
|
+
*/
|
|
141
|
+
export const CreatableTags: Story = {
|
|
142
|
+
parameters: { controls: { disable: true } },
|
|
143
|
+
render: () => {
|
|
144
|
+
const [opts, setOpts] = React.useState([
|
|
145
|
+
{ value: 'react', label: 'React' },
|
|
146
|
+
{ value: 'vue', label: 'Vue' },
|
|
147
|
+
{ value: 'angular', label: 'Angular' },
|
|
148
|
+
]);
|
|
149
|
+
return (
|
|
150
|
+
<Select
|
|
151
|
+
multiple
|
|
152
|
+
creatable
|
|
153
|
+
searchable
|
|
154
|
+
options={opts}
|
|
155
|
+
placeholder="选择 / 创建技术栈"
|
|
156
|
+
searchPlaceholder="输入并按 Enter 创建..."
|
|
157
|
+
onCreate={(val) => setOpts((prev) => [...prev, { value: val, label: val }])}
|
|
158
|
+
/>
|
|
159
|
+
);
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 远程异步搜索 — 等价 cd `Select showSearch onSearch`。
|
|
165
|
+
* `onSearch` 在输入变化时触发(本 demo 模拟接口延迟),消费方据 `query` 拉接口
|
|
166
|
+
* 刷新 `options`;同时本地 filter 默认关闭(已由服务端过滤)。
|
|
167
|
+
*/
|
|
168
|
+
export const AsyncRemoteSearch: Story = {
|
|
169
|
+
parameters: { controls: { disable: true } },
|
|
170
|
+
render: () => {
|
|
171
|
+
const [opts, setOpts] = React.useState<{ value: string; label: string }[]>(
|
|
172
|
+
[],
|
|
173
|
+
);
|
|
174
|
+
const [loading, setLoading] = React.useState(false);
|
|
175
|
+
|
|
176
|
+
const handleSearch = React.useCallback(async (q: string) => {
|
|
177
|
+
if (!q.trim()) {
|
|
178
|
+
setOpts([]);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
setLoading(true);
|
|
182
|
+
// 模拟接口请求
|
|
183
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
184
|
+
setOpts([
|
|
185
|
+
{ value: `${q}-1`, label: `${q} 远程结果 1` },
|
|
186
|
+
{ value: `${q}-2`, label: `${q} 远程结果 2` },
|
|
187
|
+
{ value: `${q}-3`, label: `${q} 远程结果 3` },
|
|
188
|
+
]);
|
|
189
|
+
setLoading(false);
|
|
190
|
+
}, []);
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<Select
|
|
194
|
+
searchable
|
|
195
|
+
options={opts}
|
|
196
|
+
onSearch={handleSearch}
|
|
197
|
+
placeholder="远程搜索"
|
|
198
|
+
searchPlaceholder="输入查询关键字..."
|
|
199
|
+
emptyText={loading ? '加载中...' : '输入关键字开始搜索'}
|
|
200
|
+
/>
|
|
201
|
+
);
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 虚拟滚动 — 选项数 ≥ `threshold`(默认 100)时启用 `@tanstack/react-virtual`,
|
|
207
|
+
* 大数据集首次渲染显著提速。本 demo 渲染 5000 项。
|
|
208
|
+
*/
|
|
209
|
+
export const Virtual: Story = {
|
|
210
|
+
parameters: { controls: { disable: true } },
|
|
211
|
+
render: () => {
|
|
212
|
+
const opts = React.useMemo(
|
|
213
|
+
() =>
|
|
214
|
+
Array.from({ length: 5000 }, (_, i) => ({
|
|
215
|
+
value: `item-${i}`,
|
|
216
|
+
label: `选项 #${i.toString().padStart(4, '0')}`,
|
|
217
|
+
})),
|
|
218
|
+
[],
|
|
219
|
+
);
|
|
220
|
+
return (
|
|
221
|
+
<Select
|
|
222
|
+
searchable
|
|
223
|
+
virtual
|
|
224
|
+
options={opts}
|
|
225
|
+
placeholder="5000 项 — 虚拟滚动"
|
|
226
|
+
searchPlaceholder="搜索 5000 个选项..."
|
|
227
|
+
/>
|
|
228
|
+
);
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export const Clearable: Story = {
|
|
233
|
+
parameters: { controls: { disable: true } },
|
|
234
|
+
render: () => (
|
|
235
|
+
<Select
|
|
236
|
+
clearable
|
|
237
|
+
options={cityOptions}
|
|
238
|
+
defaultValue="bj"
|
|
239
|
+
placeholder="可清除"
|
|
240
|
+
/>
|
|
241
|
+
),
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export const Disabled: Story = {
|
|
245
|
+
parameters: { controls: { disable: true } },
|
|
246
|
+
render: () => <Select disabled options={cityOptions} placeholder="已禁用" />,
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export const Error: Story = {
|
|
250
|
+
parameters: { controls: { disable: true } },
|
|
251
|
+
render: () => (
|
|
252
|
+
<div className="flex flex-col gap-1">
|
|
253
|
+
<Select error options={cityOptions} placeholder="请选择(必填)" />
|
|
254
|
+
<span className="text-xs text-destructive">该字段为必填项</span>
|
|
98
255
|
</div>
|
|
99
256
|
),
|
|
100
257
|
};
|
|
258
|
+
|
|
259
|
+
export const Controlled: Story = {
|
|
260
|
+
parameters: { controls: { disable: true } },
|
|
261
|
+
render: () => {
|
|
262
|
+
const [v, setV] = React.useState<string>('');
|
|
263
|
+
return (
|
|
264
|
+
<div className="flex flex-col gap-2">
|
|
265
|
+
<Select
|
|
266
|
+
options={cityOptions}
|
|
267
|
+
value={v}
|
|
268
|
+
onChange={(next: string | string[]) => setV(next as string)}
|
|
269
|
+
placeholder="受控单选"
|
|
270
|
+
/>
|
|
271
|
+
<span className="text-xs text-muted-foreground">当前值: {v || '(未选)'}</span>
|
|
272
|
+
</div>
|
|
273
|
+
);
|
|
274
|
+
},
|
|
275
|
+
};
|