@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
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { parse as parseDate, isValid as isValidDate } from 'date-fns';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/utils/cn';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Trigger wrapper —— input 可输入模式下,wrapper 承担 border + focus-within 视觉态,
|
|
7
|
+
* 内部 input 元素去掉自己的边框 / ring,共享 wrapper 的视觉态。
|
|
8
|
+
* hover / focus 走"灰色 border 加深"(antd 风格),不走蓝色 ring,避免与 popover 弹层视觉抢戏。
|
|
9
|
+
*
|
|
10
|
+
* 被 DatePicker / DateRangePicker / TimePicker / TimeRangePicker 共用,
|
|
11
|
+
* 确保 4 个 picker 的 trigger 视觉完全一致。
|
|
12
|
+
*/
|
|
13
|
+
export const triggerWrapperClass = cn(
|
|
14
|
+
'flex items-center gap-2 rounded-lg border border-input bg-background px-3 shadow-sm transition-colors',
|
|
15
|
+
'hover:border-foreground/30 focus-within:border-foreground/60 focus-within:outline-none',
|
|
16
|
+
'[&:has(input:disabled)]:cursor-not-allowed [&:has(input:disabled)]:opacity-50',
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
/** Input 元素 className —— 透明、无边框、占满 wrapper 剩余空间。 */
|
|
20
|
+
export const inputElementClass =
|
|
21
|
+
'min-w-0 grow bg-transparent text-foreground outline-none tabular-nums placeholder:text-muted-foreground disabled:cursor-not-allowed';
|
|
22
|
+
|
|
23
|
+
/** Trigger 高度尺寸(对齐 Input / Button 三档)。 */
|
|
24
|
+
export const triggerSizeClass = {
|
|
25
|
+
sm: 'h-7 text-xs',
|
|
26
|
+
default: 'h-8 text-sm',
|
|
27
|
+
md: 'h-8 text-sm',
|
|
28
|
+
lg: 'h-9 text-base',
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
export type TriggerSize = keyof typeof triggerSizeClass;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 按指定 format 解析日期文本。
|
|
35
|
+
* - 成功:返回 Date 实例
|
|
36
|
+
* - 失败(非法、空、Invalid Date):返回 undefined
|
|
37
|
+
*/
|
|
38
|
+
export const tryParseDate = (
|
|
39
|
+
text: string,
|
|
40
|
+
format: string,
|
|
41
|
+
): Date | undefined => {
|
|
42
|
+
const trimmed = text.trim();
|
|
43
|
+
if (!trimmed) return undefined;
|
|
44
|
+
const parsed = parseDate(trimmed, format, new Date());
|
|
45
|
+
return isValidDate(parsed) ? parsed : undefined;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** 比较两个 Date 是否相等(都为 undefined 也算相等)。 */
|
|
49
|
+
export const datesEqual = (a: Date | undefined, b: Date | undefined) => {
|
|
50
|
+
if (a == null && b == null) return true;
|
|
51
|
+
if (a == null || b == null) return false;
|
|
52
|
+
return a.getTime() === b.getTime();
|
|
53
|
+
};
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: button-group
|
|
3
|
-
name: ButtonGroup
|
|
4
|
-
type: component
|
|
5
|
-
category: form
|
|
6
|
-
since: 0.1.0
|
|
7
|
-
package: "@teamix-evo/ui"
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# ButtonGroup
|
|
11
|
-
|
|
12
|
-
按钮组 — shadcn 2025-10 新增,**等价 antd `Space.Compact`**(v6 替代旧 `Button.Group`)。把多个按钮 / 输入框"粘"在一起共享边线,用于 Split Button、Toolbar、Input + Button 拼装等场景。配 `ButtonGroupText` 渲染只读 addon 文本。
|
|
13
|
-
|
|
14
|
-
## When to use
|
|
15
|
-
|
|
16
|
-
- Split Button(主操作 + 右侧下拉箭头)
|
|
17
|
-
- Toolbar(同语义按钮集合,如 "上一页 / 下一页 / 跳转")
|
|
18
|
-
- Input + 按钮拼装(搜索框、单位选择)
|
|
19
|
-
- 多档切换显示(配 `Toggle` 也可,但 ToggleGroup 更专精)
|
|
20
|
-
|
|
21
|
-
## When NOT to use
|
|
22
|
-
|
|
23
|
-
- 互斥单选切换 → `ToggleGroup`(语义更强)
|
|
24
|
-
- 不相干的按钮 → 普通 `Button` + flex gap
|
|
25
|
-
|
|
26
|
-
<!-- auto:props:begin -->
|
|
27
|
-
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
28
|
-
| --- | --- | --- | --- | --- |
|
|
29
|
-
| `orientation` | `'horizontal' \| 'vertical'` | `"horizontal"` | – | 排列方向。 |
|
|
30
|
-
| `attached` | `boolean` | `true` | – | 是否把按钮"粘"在一起去除边距(antd `Space.Compact` 等价行为)— `false` 时按钮间留 8px gap。 |
|
|
31
|
-
<!-- auto:props:end -->
|
|
32
|
-
|
|
33
|
-
<!-- auto:deps:begin -->
|
|
34
|
-
### 同库依赖
|
|
35
|
-
|
|
36
|
-
> `teamix-evo ui add button-group` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
37
|
-
|
|
38
|
-
| Entry | 类型 | 描述 |
|
|
39
|
-
| --- | --- | --- |
|
|
40
|
-
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
41
|
-
|
|
42
|
-
### npm 依赖
|
|
43
|
-
|
|
44
|
-
> 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
pnpm add class-variance-authority@^0.7.0
|
|
48
|
-
```
|
|
49
|
-
<!-- auto:deps:end -->
|
|
50
|
-
|
|
51
|
-
## AI 生成纪律
|
|
52
|
-
|
|
53
|
-
- **`attached=true`(默认)子项之间边线共享**:第一项 / 最后一项保留外侧圆角,中间项去除圆角;**不要**在内部再设 `rounded`
|
|
54
|
-
- **混用 Input + Button**:把 Input 直接放进 ButtonGroup,它的 `rounded-md` 也会被组件圈圆角(无需特殊处理)
|
|
55
|
-
- **`ButtonGroupText`** 用于只读 addon(`https://` 前缀、`/月` 后缀);**不要**把它当按钮点击
|
|
56
|
-
- **vertical 模式** 适合工具栏侧边小集合,horizontal 是默认推荐
|
|
57
|
-
- **不要**在 ButtonGroup 内放 spinner / icon-only 与文字按钮混排时,高度需要靠 button `size` 对齐 — 子项尺寸应一致
|
|
58
|
-
|
|
59
|
-
## Examples
|
|
60
|
-
|
|
61
|
-
```tsx
|
|
62
|
-
import { ButtonGroup, ButtonGroupText } from '@/components/ui/button-group';
|
|
63
|
-
import { Button } from '@/components/ui/button';
|
|
64
|
-
import { Input } from '@/components/ui/input';
|
|
65
|
-
import { ChevronDown } from 'lucide-react';
|
|
66
|
-
|
|
67
|
-
// Split button
|
|
68
|
-
<ButtonGroup>
|
|
69
|
-
<Button>保存</Button>
|
|
70
|
-
<Button variant="default" size="icon" icon={<ChevronDown />} aria-label="更多" />
|
|
71
|
-
</ButtonGroup>
|
|
72
|
-
|
|
73
|
-
// Toolbar
|
|
74
|
-
<ButtonGroup>
|
|
75
|
-
<Button variant="outline">上一页</Button>
|
|
76
|
-
<Button variant="outline">1 / 10</Button>
|
|
77
|
-
<Button variant="outline">下一页</Button>
|
|
78
|
-
</ButtonGroup>
|
|
79
|
-
|
|
80
|
-
// 带 addon 文本
|
|
81
|
-
<ButtonGroup>
|
|
82
|
-
<ButtonGroupText>https://</ButtonGroupText>
|
|
83
|
-
<Input defaultValue="example.com" className="rounded-l-none" />
|
|
84
|
-
<Button>访问</Button>
|
|
85
|
-
</ButtonGroup>
|
|
86
|
-
|
|
87
|
-
// 不粘连(留间距)
|
|
88
|
-
<ButtonGroup attached={false}>
|
|
89
|
-
<Button variant="outline">取消</Button>
|
|
90
|
-
<Button>确定</Button>
|
|
91
|
-
</ButtonGroup>
|
|
92
|
-
```
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
-
import { ChevronDown } from 'lucide-react';
|
|
3
|
-
import { ButtonGroup, ButtonGroupText } from './button-group';
|
|
4
|
-
import { Button } from '@/components/button/button';
|
|
5
|
-
import { Input } from '@/components/input/input';
|
|
6
|
-
|
|
7
|
-
const meta: Meta<typeof ButtonGroup> = {
|
|
8
|
-
title: '表单与输入 · Form/ButtonGroup',
|
|
9
|
-
component: ButtonGroup,
|
|
10
|
-
tags: ['autodocs'],
|
|
11
|
-
parameters: {
|
|
12
|
-
docs: {
|
|
13
|
-
description: {
|
|
14
|
-
component:
|
|
15
|
-
'按钮组 — 把多个按钮 / 输入框粘在一起共享边线(Split Button、Toolbar、Input + Button 拼装)。配 ButtonGroupText 渲染只读 addon。shadcn 2025-10 新增,等价 antd `Space.Compact`(v6 替代旧 `Button.Group`)。视觉走 OpenTrek tokens,所有样式来自 `@teamix-evo/design`,无 mock。',
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
argTypes: {
|
|
20
|
-
orientation: { control: 'inline-radio', options: ['horizontal', 'vertical'] },
|
|
21
|
-
attached: { control: 'boolean' },
|
|
22
|
-
},
|
|
23
|
-
args: { orientation: 'horizontal', attached: true },
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export default meta;
|
|
27
|
-
type Story = StoryObj<typeof ButtonGroup>;
|
|
28
|
-
|
|
29
|
-
export const Playground: Story = {
|
|
30
|
-
render: (args) => (
|
|
31
|
-
<ButtonGroup {...args}>
|
|
32
|
-
<Button variant="outline">左</Button>
|
|
33
|
-
<Button variant="outline">中</Button>
|
|
34
|
-
<Button variant="outline">右</Button>
|
|
35
|
-
</ButtonGroup>
|
|
36
|
-
),
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export const SplitButton: Story = {
|
|
40
|
-
parameters: { controls: { disable: true } },
|
|
41
|
-
render: () => (
|
|
42
|
-
<ButtonGroup>
|
|
43
|
-
<Button>保存</Button>
|
|
44
|
-
<Button size="icon" icon={<ChevronDown />} aria-label="更多保存选项" />
|
|
45
|
-
</ButtonGroup>
|
|
46
|
-
),
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export const Pager: Story = {
|
|
50
|
-
parameters: { controls: { disable: true } },
|
|
51
|
-
render: () => (
|
|
52
|
-
<ButtonGroup>
|
|
53
|
-
<Button variant="outline">上一页</Button>
|
|
54
|
-
<Button variant="outline">1 / 10</Button>
|
|
55
|
-
<Button variant="outline">下一页</Button>
|
|
56
|
-
</ButtonGroup>
|
|
57
|
-
),
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export const InputAddon: Story = {
|
|
61
|
-
parameters: { controls: { disable: true } },
|
|
62
|
-
render: () => (
|
|
63
|
-
<ButtonGroup>
|
|
64
|
-
<ButtonGroupText>https://</ButtonGroupText>
|
|
65
|
-
<Input defaultValue="example.com" className="rounded-l-none" />
|
|
66
|
-
<Button>访问</Button>
|
|
67
|
-
</ButtonGroup>
|
|
68
|
-
),
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
export const Vertical: Story = {
|
|
72
|
-
parameters: { controls: { disable: true } },
|
|
73
|
-
render: () => (
|
|
74
|
-
<ButtonGroup orientation="vertical">
|
|
75
|
-
<Button variant="outline">编辑</Button>
|
|
76
|
-
<Button variant="outline">复制</Button>
|
|
77
|
-
<Button variant="outline">删除</Button>
|
|
78
|
-
</ButtonGroup>
|
|
79
|
-
),
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
export const NotAttached: Story = {
|
|
83
|
-
parameters: { controls: { disable: true } },
|
|
84
|
-
render: () => (
|
|
85
|
-
<ButtonGroup attached={false}>
|
|
86
|
-
<Button variant="outline">取消</Button>
|
|
87
|
-
<Button>确定</Button>
|
|
88
|
-
</ButtonGroup>
|
|
89
|
-
),
|
|
90
|
-
};
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
-
|
|
4
|
-
import { cn } from '@/utils/cn';
|
|
5
|
-
|
|
6
|
-
const buttonGroupVariants = cva('inline-flex isolate', {
|
|
7
|
-
variants: {
|
|
8
|
-
orientation: {
|
|
9
|
-
horizontal:
|
|
10
|
-
'flex-row [&>*:not(:first-child)]:rounded-l-none [&>*:not(:last-child)]:rounded-r-none [&>*:not(:first-child)]:-ml-px hover:[&>*]:z-10 focus-visible:[&>*]:z-10',
|
|
11
|
-
vertical:
|
|
12
|
-
'flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:last-child)]:rounded-b-none [&>*:not(:first-child)]:-mt-px hover:[&>*]:z-10 focus-visible:[&>*]:z-10',
|
|
13
|
-
},
|
|
14
|
-
attached: {
|
|
15
|
-
true: '',
|
|
16
|
-
false: 'gap-2',
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
defaultVariants: { orientation: 'horizontal', attached: true },
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
export interface ButtonGroupProps
|
|
23
|
-
extends React.HTMLAttributes<HTMLDivElement>,
|
|
24
|
-
VariantProps<typeof buttonGroupVariants> {
|
|
25
|
-
/**
|
|
26
|
-
* 排列方向。
|
|
27
|
-
* @default "horizontal"
|
|
28
|
-
*/
|
|
29
|
-
orientation?: 'horizontal' | 'vertical';
|
|
30
|
-
/**
|
|
31
|
-
* 是否把按钮"粘"在一起去除边距(antd `Space.Compact` 等价行为)— `false` 时按钮间留 8px gap。
|
|
32
|
-
* @default true
|
|
33
|
-
*/
|
|
34
|
-
attached?: boolean;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 按钮组容器 — shadcn 2025-10 新增。语义类似 antd `Space.Compact`(v6 替代旧 `Button.Group`):
|
|
39
|
-
* 把多个 `Button` / `Input` 包在一起共享边线,典型场景:
|
|
40
|
-
* - Split button(左主操作 + 右下拉箭头)
|
|
41
|
-
* - Toolbar(同语义按钮集合)
|
|
42
|
-
* - Input + 按钮拼装(`<Input/><Button>搜索</Button>`)
|
|
43
|
-
*
|
|
44
|
-
* 内部不限定子组件类型 — 任何带 `rounded-md` 的元素都会被组件圈圆角。
|
|
45
|
-
*/
|
|
46
|
-
const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(
|
|
47
|
-
({ orientation, attached, className, ...props }, ref) => (
|
|
48
|
-
<div
|
|
49
|
-
ref={ref}
|
|
50
|
-
role="group"
|
|
51
|
-
className={cn(buttonGroupVariants({ orientation, attached }), className)}
|
|
52
|
-
{...props}
|
|
53
|
-
/>
|
|
54
|
-
),
|
|
55
|
-
);
|
|
56
|
-
ButtonGroup.displayName = 'ButtonGroup';
|
|
57
|
-
|
|
58
|
-
// ─── ButtonGroupText(antd-style addon text)──────────────────────────────
|
|
59
|
-
|
|
60
|
-
const ButtonGroupText = React.forwardRef<
|
|
61
|
-
HTMLSpanElement,
|
|
62
|
-
React.HTMLAttributes<HTMLSpanElement>
|
|
63
|
-
>(({ className, ...props }, ref) => (
|
|
64
|
-
<span
|
|
65
|
-
ref={ref}
|
|
66
|
-
className={cn(
|
|
67
|
-
'inline-flex h-9 select-none items-center border border-input bg-muted px-3 text-sm text-muted-foreground',
|
|
68
|
-
className,
|
|
69
|
-
)}
|
|
70
|
-
{...props}
|
|
71
|
-
/>
|
|
72
|
-
));
|
|
73
|
-
ButtonGroupText.displayName = 'ButtonGroupText';
|
|
74
|
-
|
|
75
|
-
export { ButtonGroup, ButtonGroupText, buttonGroupVariants };
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: combobox
|
|
3
|
-
name: Combobox
|
|
4
|
-
type: component
|
|
5
|
-
category: form
|
|
6
|
-
since: 0.1.0
|
|
7
|
-
package: "@teamix-evo/ui"
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# Combobox
|
|
11
|
-
|
|
12
|
-
可搜索单选下拉 — `Command + Popover` 复合,等价 antd `AutoComplete` / `Select showSearch` 的核心场景。
|
|
13
|
-
**单选**(刻意不做多选,保持语义简单);多选请直接用 `Command` + 自管 selected 数组。
|
|
14
|
-
|
|
15
|
-
## When to use
|
|
16
|
-
|
|
17
|
-
- 选项较多需要搜索筛选(国家 / 时区 / 用户列表 / 标签)
|
|
18
|
-
- 选项动态加载(异步 — 在父组件准备数据后传给 `options`)
|
|
19
|
-
|
|
20
|
-
## When NOT to use
|
|
21
|
-
|
|
22
|
-
- 选项 ≤ 7 个 → `Select`(无需搜索)
|
|
23
|
-
- 多选 → `Command` 自定义,或 `CheckboxGroup`
|
|
24
|
-
- 自由文本输入 → `Input` + 自定义 suggestion(超出本组件范围)
|
|
25
|
-
|
|
26
|
-
<!-- auto:props:begin -->
|
|
27
|
-
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
28
|
-
| --- | --- | --- | --- | --- |
|
|
29
|
-
| `options` | `ComboboxOption[]` | – | ✓ | 选项列表(antd `AutoComplete.options` 并集)。 |
|
|
30
|
-
| `value` | `string` | – | – | 受控 value(单选)。 |
|
|
31
|
-
| `onChange` | `(value: string) => void` | – | – | value 变化回调。 |
|
|
32
|
-
| `placeholder` | `string` | `"请选择..."` | – | 触发器占位文本(未选)。 |
|
|
33
|
-
| `searchPlaceholder` | `string` | `"搜索..."` | – | 搜索框占位文本。 |
|
|
34
|
-
| `emptyText` | `string` | `"无匹配项"` | – | 无匹配时的提示文本。 |
|
|
35
|
-
| `className` | `string` | `"w-[200px]"` | – | 触发器宽度。 |
|
|
36
|
-
| `disabled` | `boolean` | – | – | 是否禁用。 |
|
|
37
|
-
| `size` | `'sm' \| 'default' \| 'lg'` | `"default"` | – | 触发器尺寸。 |
|
|
38
|
-
<!-- auto:props:end -->
|
|
39
|
-
|
|
40
|
-
<!-- auto:deps:begin -->
|
|
41
|
-
### 同库依赖
|
|
42
|
-
|
|
43
|
-
> `teamix-evo ui add combobox` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
44
|
-
|
|
45
|
-
| Entry | 类型 | 描述 |
|
|
46
|
-
| --- | --- | --- |
|
|
47
|
-
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
48
|
-
| `button` | component | 通用按钮 — shadcn 实现 + antd 功能扩展(loading / icon / shape / block / dashed variant) |
|
|
49
|
-
| `command` | component | 命令面板 — cmdk(Linear / Raycast 风格),全局搜索 / 命令执行;Combobox 的底座 |
|
|
50
|
-
| `popover` | component | 可交互浮层 — Radix Popover + antd arrow 并集 |
|
|
51
|
-
|
|
52
|
-
### npm 依赖
|
|
53
|
-
|
|
54
|
-
> 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
pnpm add lucide-react@^0.460.0
|
|
58
|
-
```
|
|
59
|
-
<!-- auto:deps:end -->
|
|
60
|
-
|
|
61
|
-
> 选项类型 `ComboboxOption`:`{ value: string; label: ReactNode; disabled?: boolean }`。
|
|
62
|
-
|
|
63
|
-
## AI 生成纪律
|
|
64
|
-
|
|
65
|
-
- **`value` 稳定**:用业务 ID,不要用 index
|
|
66
|
-
- **`label` 可富节点**:支持图标 + 文本,不止 string
|
|
67
|
-
- **第二次点击当前 value 会清空**:`onChange('')` 触发,业务侧据此实现"取消选择"
|
|
68
|
-
- **大量选项(>1000) → 自定义虚拟滚动**:本组件未集成 virtualizer
|
|
69
|
-
- **异步加载选项**:父组件 fetch 后传 `options`,加 loading 占位符在 options 列表外
|
|
70
|
-
|
|
71
|
-
## Examples
|
|
72
|
-
|
|
73
|
-
```tsx
|
|
74
|
-
import { Combobox, type ComboboxOption } from '@/components/ui/combobox';
|
|
75
|
-
import * as React from 'react';
|
|
76
|
-
|
|
77
|
-
const frameworks: ComboboxOption[] = [
|
|
78
|
-
{ value: 'next', label: 'Next.js' },
|
|
79
|
-
{ value: 'sveltekit', label: 'SvelteKit' },
|
|
80
|
-
{ value: 'nuxt', label: 'Nuxt' },
|
|
81
|
-
{ value: 'remix', label: 'Remix' },
|
|
82
|
-
{ value: 'astro', label: 'Astro' },
|
|
83
|
-
];
|
|
84
|
-
|
|
85
|
-
const [v, setV] = React.useState('');
|
|
86
|
-
<Combobox
|
|
87
|
-
options={frameworks}
|
|
88
|
-
value={v}
|
|
89
|
-
onChange={setV}
|
|
90
|
-
placeholder="选择框架"
|
|
91
|
-
searchPlaceholder="搜索框架..."
|
|
92
|
-
/>
|
|
93
|
-
```
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
-
import { Combobox, type ComboboxOption } from './combobox';
|
|
4
|
-
|
|
5
|
-
const meta: Meta<typeof Combobox> = {
|
|
6
|
-
title: '表单与输入 · Form/Combobox',
|
|
7
|
-
component: Combobox,
|
|
8
|
-
tags: ['autodocs'],
|
|
9
|
-
parameters: {
|
|
10
|
-
docs: {
|
|
11
|
-
description: {
|
|
12
|
-
component:
|
|
13
|
-
'可搜索单选下拉 — Command + Popover 复合,对标 antd AutoComplete / Select showSearch。键盘可达、模糊搜索、空态、再次选中清空,适合选项数 > 7 的单选场景。OpenTrek tokens 适配。',
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export default meta;
|
|
20
|
-
type Story = StoryObj<typeof Combobox>;
|
|
21
|
-
|
|
22
|
-
const frameworks: ComboboxOption[] = [
|
|
23
|
-
{ value: 'next', label: 'Next.js' },
|
|
24
|
-
{ value: 'sveltekit', label: 'SvelteKit' },
|
|
25
|
-
{ value: 'nuxt', label: 'Nuxt' },
|
|
26
|
-
{ value: 'remix', label: 'Remix' },
|
|
27
|
-
{ value: 'astro', label: 'Astro' },
|
|
28
|
-
{ value: 'qwik', label: 'Qwik', disabled: true },
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
export const Default: Story = {
|
|
32
|
-
render: () => {
|
|
33
|
-
const [v, setV] = React.useState('');
|
|
34
|
-
return (
|
|
35
|
-
<Combobox
|
|
36
|
-
options={frameworks}
|
|
37
|
-
value={v}
|
|
38
|
-
onChange={setV}
|
|
39
|
-
placeholder="选择框架"
|
|
40
|
-
searchPlaceholder="搜索框架..."
|
|
41
|
-
/>
|
|
42
|
-
);
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export const Sizes: Story = {
|
|
47
|
-
parameters: { controls: { disable: true } },
|
|
48
|
-
render: () => (
|
|
49
|
-
<div className="flex flex-col gap-3">
|
|
50
|
-
<Combobox options={frameworks} placeholder="sm" size="sm" />
|
|
51
|
-
<Combobox options={frameworks} placeholder="default" />
|
|
52
|
-
<Combobox options={frameworks} placeholder="lg" size="lg" />
|
|
53
|
-
</div>
|
|
54
|
-
),
|
|
55
|
-
};
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { Check, ChevronsUpDown } from 'lucide-react';
|
|
3
|
-
|
|
4
|
-
import { cn } from '@/utils/cn';
|
|
5
|
-
import { Button } from '@/components/button/button';
|
|
6
|
-
import {
|
|
7
|
-
Command,
|
|
8
|
-
CommandEmpty,
|
|
9
|
-
CommandGroup,
|
|
10
|
-
CommandInput,
|
|
11
|
-
CommandItem,
|
|
12
|
-
CommandList,
|
|
13
|
-
} from '@/components/command/command';
|
|
14
|
-
import {
|
|
15
|
-
Popover,
|
|
16
|
-
PopoverContent,
|
|
17
|
-
PopoverTrigger,
|
|
18
|
-
} from '@/components/popover/popover';
|
|
19
|
-
|
|
20
|
-
export interface ComboboxOption {
|
|
21
|
-
/** 选项值(受控 value 比对的依据,稳定 ID)。 */
|
|
22
|
-
value: string;
|
|
23
|
-
/** 显示文本。 */
|
|
24
|
-
label: React.ReactNode;
|
|
25
|
-
/** 禁用此选项。 */
|
|
26
|
-
disabled?: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface ComboboxProps {
|
|
30
|
-
/** 选项列表(antd `AutoComplete.options` 并集)。 */
|
|
31
|
-
options: ComboboxOption[];
|
|
32
|
-
/** 受控 value(单选)。 */
|
|
33
|
-
value?: string;
|
|
34
|
-
/** value 变化回调。 */
|
|
35
|
-
onChange?: (value: string) => void;
|
|
36
|
-
/** 触发器占位文本(未选)。 @default "请选择..." */
|
|
37
|
-
placeholder?: string;
|
|
38
|
-
/** 搜索框占位文本。 @default "搜索..." */
|
|
39
|
-
searchPlaceholder?: string;
|
|
40
|
-
/** 无匹配时的提示文本。 @default "无匹配项" */
|
|
41
|
-
emptyText?: string;
|
|
42
|
-
/** 触发器宽度。 @default "w-[200px]" */
|
|
43
|
-
className?: string;
|
|
44
|
-
/** 是否禁用。 */
|
|
45
|
-
disabled?: boolean;
|
|
46
|
-
/** 触发器尺寸。 @default "default" */
|
|
47
|
-
size?: 'sm' | 'default' | 'lg';
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* 可搜索单选下拉 — Command + Popover 复合,等价 antd `AutoComplete` /
|
|
52
|
-
* `Select showSearch` 的核心场景。
|
|
53
|
-
*
|
|
54
|
-
* 多选场景请直接用 Command + 自管 selected 数组(本组件刻意不做多选,保持
|
|
55
|
-
* 单选语义清晰)。
|
|
56
|
-
*/
|
|
57
|
-
const Combobox = React.forwardRef<HTMLButtonElement, ComboboxProps>(
|
|
58
|
-
(
|
|
59
|
-
{
|
|
60
|
-
options,
|
|
61
|
-
value,
|
|
62
|
-
onChange,
|
|
63
|
-
placeholder = '请选择...',
|
|
64
|
-
searchPlaceholder = '搜索...',
|
|
65
|
-
emptyText = '无匹配项',
|
|
66
|
-
className,
|
|
67
|
-
disabled,
|
|
68
|
-
size = 'default',
|
|
69
|
-
},
|
|
70
|
-
ref,
|
|
71
|
-
) => {
|
|
72
|
-
const [open, setOpen] = React.useState(false);
|
|
73
|
-
const selected = options.find((o) => o.value === value);
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<Popover open={open} onOpenChange={setOpen}>
|
|
77
|
-
<PopoverTrigger asChild>
|
|
78
|
-
<Button
|
|
79
|
-
ref={ref}
|
|
80
|
-
variant="outline"
|
|
81
|
-
size={size}
|
|
82
|
-
disabled={disabled}
|
|
83
|
-
role="combobox"
|
|
84
|
-
aria-expanded={open}
|
|
85
|
-
className={cn(
|
|
86
|
-
'w-[200px] justify-between font-normal',
|
|
87
|
-
!value && 'text-muted-foreground',
|
|
88
|
-
className,
|
|
89
|
-
)}
|
|
90
|
-
>
|
|
91
|
-
{selected ? selected.label : placeholder}
|
|
92
|
-
<ChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" />
|
|
93
|
-
</Button>
|
|
94
|
-
</PopoverTrigger>
|
|
95
|
-
<PopoverContent className="w-[200px] p-0">
|
|
96
|
-
<Command>
|
|
97
|
-
<CommandInput placeholder={searchPlaceholder} />
|
|
98
|
-
<CommandList>
|
|
99
|
-
<CommandEmpty>{emptyText}</CommandEmpty>
|
|
100
|
-
<CommandGroup>
|
|
101
|
-
{options.map((opt) => (
|
|
102
|
-
<CommandItem
|
|
103
|
-
key={opt.value}
|
|
104
|
-
value={opt.value}
|
|
105
|
-
disabled={opt.disabled}
|
|
106
|
-
onSelect={(v) => {
|
|
107
|
-
onChange?.(v === value ? '' : v);
|
|
108
|
-
setOpen(false);
|
|
109
|
-
}}
|
|
110
|
-
>
|
|
111
|
-
<Check
|
|
112
|
-
className={cn(
|
|
113
|
-
'mr-2 size-4',
|
|
114
|
-
opt.value === value ? 'opacity-100' : 'opacity-0',
|
|
115
|
-
)}
|
|
116
|
-
/>
|
|
117
|
-
{opt.label}
|
|
118
|
-
</CommandItem>
|
|
119
|
-
))}
|
|
120
|
-
</CommandGroup>
|
|
121
|
-
</CommandList>
|
|
122
|
-
</Command>
|
|
123
|
-
</PopoverContent>
|
|
124
|
-
</Popover>
|
|
125
|
-
);
|
|
126
|
-
},
|
|
127
|
-
);
|
|
128
|
-
Combobox.displayName = 'Combobox';
|
|
129
|
-
|
|
130
|
-
export { Combobox };
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: space
|
|
3
|
-
name: Space
|
|
4
|
-
type: component
|
|
5
|
-
category: layout
|
|
6
|
-
since: 0.1.0
|
|
7
|
-
package: "@teamix-evo/ui"
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# Space
|
|
11
|
-
|
|
12
|
-
间距容器 — antd 独有补足。**等价 antd `Space`**:把同语义并排子项统一间距、对齐与可选换行,可选 `split` 节点(常用 Separator)。与 `Flex` 区别:Space 偏向**小集合 inline 间距**(按钮组、tag 组、表单 label-value 对),Flex 偏向**完整布局容器**。
|
|
13
|
-
|
|
14
|
-
## When to use
|
|
15
|
-
|
|
16
|
-
- 多个按钮 / 标签 / 链接并排(避免直接写 `flex gap-2`)
|
|
17
|
-
- 表单某一行的"标签 + 值"对
|
|
18
|
-
- 列表行末的多个操作链接(配 `split=<Separator orientation="vertical" />`)
|
|
19
|
-
|
|
20
|
-
## When NOT to use
|
|
21
|
-
|
|
22
|
-
- 完整页面布局 → `Flex` / `Grid`
|
|
23
|
-
- 按钮粘连共享边线 → `ButtonGroup`
|
|
24
|
-
- 大段排版分段 → `Typography Prose`
|
|
25
|
-
|
|
26
|
-
<!-- auto:props:begin -->
|
|
27
|
-
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
28
|
-
| --- | --- | --- | --- | --- |
|
|
29
|
-
| `direction` | `'horizontal' \| 'vertical'` | `"horizontal"` | – | 排列方向(antd `direction` 并集)。 |
|
|
30
|
-
| `size` | `'sm' \| 'default' \| 'lg'` | `"default"` | – | 间距档位(antd `size` 并集) — 走 design 间距刻度,不接受 number。 |
|
|
31
|
-
| `wrap` | `boolean` | `false` | – | 是否自动换行(antd `wrap` 并集) — horizontal 模式生效。 |
|
|
32
|
-
| `align` | `'start' \| 'center' \| 'end' \| 'baseline' \| 'stretch'` | `"center"` | – | 主轴对齐方式(antd `align` 并集)。 |
|
|
33
|
-
| `justify` | `'start' \| 'center' \| 'end' \| 'between' \| 'around'` | – | – | 副轴对齐(antd `Space` 无,Space.Compact 也无 — 但中后台需求高,补)。 |
|
|
34
|
-
| `split` | `React.ReactNode` | – | – | 分隔节点(antd `split` 并集) — 在每两个子项之间插入(常用 `<Separator>`)。 |
|
|
35
|
-
<!-- auto:props:end -->
|
|
36
|
-
|
|
37
|
-
<!-- auto:deps:begin -->
|
|
38
|
-
### 同库依赖
|
|
39
|
-
|
|
40
|
-
> `teamix-evo ui add space` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
41
|
-
|
|
42
|
-
| Entry | 类型 | 描述 |
|
|
43
|
-
| --- | --- | --- |
|
|
44
|
-
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
45
|
-
|
|
46
|
-
### npm 依赖
|
|
47
|
-
|
|
48
|
-
> 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
pnpm add class-variance-authority@^0.7.0
|
|
52
|
-
```
|
|
53
|
-
<!-- auto:deps:end -->
|
|
54
|
-
|
|
55
|
-
## AI 生成纪律
|
|
56
|
-
|
|
57
|
-
- **`size` 是档位枚举**(sm/default/lg),**不接受 number** — 与 antd 偏离,因为我们走 design 间距刻度
|
|
58
|
-
- **`wrap` 仅 horizontal 生效** — vertical 自动换行无意义
|
|
59
|
-
- **`split` 应是无状态轻节点**(Separator / `|` / dot),不要传 Button 等复杂组件,语义混乱
|
|
60
|
-
- **`justify`** 是 antd 缺失但中后台高频补的能力 — 列表行末右对齐操作就用 `justify="end"`
|
|
61
|
-
- **不要嵌套 Space**:嵌套用 Flex 表达父子结构
|
|
62
|
-
|
|
63
|
-
## Examples
|
|
64
|
-
|
|
65
|
-
```tsx
|
|
66
|
-
import { Space } from '@/components/ui/space';
|
|
67
|
-
import { Button } from '@/components/ui/button';
|
|
68
|
-
import { Separator } from '@/components/ui/separator';
|
|
69
|
-
|
|
70
|
-
// 按钮组
|
|
71
|
-
<Space>
|
|
72
|
-
<Button variant="outline">取消</Button>
|
|
73
|
-
<Button>确定</Button>
|
|
74
|
-
</Space>
|
|
75
|
-
|
|
76
|
-
// 链接组 + split
|
|
77
|
-
<Space split={<Separator orientation="vertical" className="h-4" />} size="sm">
|
|
78
|
-
<a href="#">查看</a>
|
|
79
|
-
<a href="#">编辑</a>
|
|
80
|
-
<a href="#">删除</a>
|
|
81
|
-
</Space>
|
|
82
|
-
|
|
83
|
-
// 表单行
|
|
84
|
-
<Space justify="between">
|
|
85
|
-
<span className="text-sm text-muted-foreground">总计</span>
|
|
86
|
-
<span className="text-lg font-semibold">¥ 1,299</span>
|
|
87
|
-
</Space>
|
|
88
|
-
|
|
89
|
-
// 纵向
|
|
90
|
-
<Space direction="vertical" align="start">
|
|
91
|
-
<Title>标题</Title>
|
|
92
|
-
<Paragraph>描述</Paragraph>
|
|
93
|
-
</Space>
|
|
94
|
-
```
|