@teamix-evo/ui 0.2.0 → 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 +15 -9
- package/src/components/accordion/accordion.meta.md +5 -9
- package/src/components/accordion/accordion.stories.tsx +3 -3
- package/src/components/accordion/accordion.tsx +104 -8
- package/src/components/affix/affix.meta.md +21 -12
- package/src/components/affix/affix.stories.tsx +101 -26
- package/src/components/affix/affix.tsx +79 -9
- package/src/components/alert/alert.meta.md +52 -26
- 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 +48 -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 +10 -14
- package/src/components/anchor/anchor.stories.tsx +3 -3
- package/src/components/anchor/anchor.tsx +2 -2
- package/src/components/app/app.meta.md +10 -14
- package/src/components/app/app.stories.tsx +6 -6
- package/src/components/aspect-ratio/aspect-ratio.meta.md +4 -8
- package/src/components/aspect-ratio/aspect-ratio.stories.tsx +3 -3
- package/src/components/auto-complete/auto-complete.meta.md +19 -20
- package/src/components/auto-complete/auto-complete.stories.tsx +44 -3
- package/src/components/auto-complete/auto-complete.tsx +119 -71
- package/src/components/avatar/avatar.meta.md +9 -22
- package/src/components/avatar/avatar.stories.tsx +21 -3
- package/src/components/avatar/avatar.tsx +24 -23
- package/src/components/badge/badge.meta.md +14 -18
- package/src/components/badge/badge.stories.tsx +2 -2
- package/src/components/badge/badge.tsx +2 -2
- package/src/components/breadcrumb/breadcrumb.meta.md +29 -20
- package/src/components/breadcrumb/breadcrumb.stories.tsx +120 -5
- package/src/components/breadcrumb/breadcrumb.tsx +22 -8
- package/src/components/button/button.meta.md +261 -29
- package/src/components/button/button.stories.tsx +549 -41
- package/src/components/button/button.tsx +335 -33
- package/src/components/calendar/calendar.meta.md +19 -14
- package/src/components/calendar/calendar.stories.tsx +5 -5
- package/src/components/calendar/calendar.tsx +73 -8
- package/src/components/card/card.meta.md +31 -34
- package/src/components/card/card.stories.tsx +34 -3
- package/src/components/card/card.tsx +146 -63
- package/src/components/carousel/carousel.meta.md +10 -14
- package/src/components/carousel/carousel.stories.tsx +1 -1
- package/src/components/cascader/cascader.meta.md +43 -22
- package/src/components/cascader/cascader.stories.tsx +13 -2
- package/src/components/cascader/cascader.tsx +427 -84
- package/src/components/checkbox/checkbox.meta.md +74 -24
- package/src/components/checkbox/checkbox.stories.tsx +160 -2
- package/src/components/checkbox/checkbox.tsx +77 -9
- package/src/components/collapsible/collapsible.meta.md +7 -6
- package/src/components/collapsible/collapsible.stories.tsx +2 -2
- package/src/components/collapsible/collapsible.tsx +93 -6
- package/src/components/color-picker/color-picker.meta.md +16 -20
- package/src/components/color-picker/color-picker.stories.tsx +86 -7
- package/src/components/color-picker/color-picker.tsx +19 -9
- package/src/components/command/command.meta.md +7 -11
- package/src/components/command/command.stories.tsx +4 -4
- package/src/components/command/command.tsx +18 -7
- package/src/components/context-menu/context-menu.meta.md +5 -25
- package/src/components/context-menu/context-menu.stories.tsx +4 -4
- package/src/components/context-menu/context-menu.tsx +21 -8
- package/src/components/data-table/data-table.meta.md +14 -18
- package/src/components/data-table/data-table.stories.tsx +1 -1
- package/src/components/data-table/data-table.tsx +2 -2
- package/src/components/date-picker/date-picker.meta.md +90 -41
- 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 +12 -16
- package/src/components/descriptions/descriptions.stories.tsx +2 -2
- package/src/components/descriptions/descriptions.tsx +22 -14
- package/src/components/dialog/dialog.meta.md +67 -17
- 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 +27 -39
- package/src/components/drawer/drawer.stories.tsx +29 -12
- package/src/components/drawer/drawer.tsx +22 -114
- package/src/components/dropdown-menu/dropdown-menu.meta.md +64 -24
- package/src/components/dropdown-menu/dropdown-menu.stories.tsx +81 -3
- 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 +10 -14
- package/src/components/empty/empty.stories.tsx +3 -3
- package/src/components/empty/empty.tsx +10 -3
- package/src/components/field/field.meta.md +46 -25
- package/src/components/field/field.stories.tsx +380 -3
- 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 +59 -20
- package/src/components/flex/flex.stories.tsx +65 -10
- package/src/components/flex/flex.tsx +27 -4
- package/src/components/float-button/float-button.meta.md +10 -29
- package/src/components/float-button/float-button.stories.tsx +6 -6
- package/src/components/form/form.meta.md +31 -52
- package/src/components/form/form.stories.tsx +350 -3
- package/src/components/form/form.tsx +101 -35
- package/src/components/grid/grid.meta.md +4 -24
- package/src/components/grid/grid.stories.tsx +2 -2
- package/src/components/hover-card/hover-card.meta.md +9 -10
- package/src/components/hover-card/hover-card.stories.tsx +29 -4
- 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 +14 -18
- package/src/components/image/image.stories.tsx +3 -3
- package/src/components/image/image.tsx +2 -0
- package/src/components/input/demo/sizes.tsx +2 -2
- package/src/components/input/input.meta.md +44 -43
- package/src/components/input/input.stories.tsx +62 -35
- package/src/components/input/input.tsx +96 -101
- package/src/components/input-group/input-group.meta.md +53 -39
- package/src/components/input-group/input-group.stories.tsx +49 -16
- package/src/components/input-group/input-group.tsx +43 -8
- package/src/components/input-number/input-number.meta.md +68 -20
- package/src/components/input-number/input-number.stories.tsx +33 -6
- package/src/components/input-number/input-number.tsx +79 -20
- package/src/components/input-otp/input-otp.meta.md +8 -20
- 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 -26
- package/src/components/item/item.stories.tsx +3 -3
- package/src/components/item/item.tsx +7 -6
- package/src/components/kbd/kbd.meta.md +7 -19
- package/src/components/kbd/kbd.stories.tsx +4 -4
- package/src/components/kbd/kbd.tsx +8 -4
- package/src/components/label/label.meta.md +21 -18
- 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 -12
- package/src/components/masonry/masonry.stories.tsx +4 -4
- package/src/components/mentions/mentions.meta.md +42 -21
- package/src/components/mentions/mentions.stories.tsx +120 -6
- package/src/components/mentions/mentions.tsx +10 -5
- package/src/components/menubar/menubar.meta.md +4 -8
- package/src/components/menubar/menubar.stories.tsx +55 -3
- package/src/components/menubar/menubar.tsx +9 -9
- package/src/components/native-select/native-select.meta.md +7 -11
- package/src/components/native-select/native-select.stories.tsx +4 -4
- package/src/components/native-select/native-select.tsx +1 -1
- package/src/components/navigation-menu/navigation-menu.meta.md +4 -8
- package/src/components/navigation-menu/navigation-menu.stories.tsx +106 -3
- package/src/components/navigation-menu/navigation-menu.tsx +6 -3
- package/src/components/notification/notification.meta.md +41 -8
- package/src/components/notification/notification.stories.tsx +9 -9
- package/src/components/notification/notification.tsx +34 -19
- 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 +122 -50
- package/src/components/pagination/pagination.stories.tsx +227 -11
- package/src/components/pagination/pagination.tsx +355 -63
- package/src/components/popconfirm/popconfirm.meta.md +19 -23
- package/src/components/popconfirm/popconfirm.stories.tsx +2 -2
- package/src/components/popconfirm/popconfirm.tsx +1 -1
- package/src/components/popover/popover.meta.md +64 -12
- package/src/components/popover/popover.stories.tsx +83 -7
- package/src/components/popover/popover.tsx +77 -28
- package/src/components/progress/progress.meta.md +43 -26
- package/src/components/progress/progress.stories.tsx +2 -2
- package/src/components/progress/progress.tsx +19 -11
- package/src/components/radio-group/radio-group.meta.md +78 -11
- package/src/components/radio-group/radio-group.stories.tsx +38 -2
- package/src/components/radio-group/radio-group.tsx +149 -18
- package/src/components/rate/rate.meta.md +41 -19
- package/src/components/rate/rate.stories.tsx +2 -2
- package/src/components/rate/rate.tsx +37 -10
- package/src/components/resizable/resizable.meta.md +4 -12
- package/src/components/resizable/resizable.stories.tsx +5 -5
- package/src/components/resizable/resizable.tsx +1 -1
- package/src/components/result/result.meta.md +10 -14
- package/src/components/result/result.stories.tsx +2 -2
- package/src/components/result/result.tsx +21 -12
- package/src/components/scroll-area/scroll-area.meta.md +4 -8
- package/src/components/scroll-area/scroll-area.stories.tsx +5 -5
- package/src/components/segmented/segmented.meta.md +15 -17
- package/src/components/segmented/segmented.stories.tsx +3 -3
- package/src/components/segmented/segmented.tsx +15 -7
- package/src/components/select/select.meta.md +199 -67
- package/src/components/select/select.stories.tsx +238 -63
- package/src/components/select/select.tsx +718 -171
- package/src/components/separator/separator.meta.md +10 -14
- package/src/components/separator/separator.stories.tsx +2 -2
- package/src/components/separator/separator.tsx +3 -7
- package/src/components/sheet/sheet.meta.md +26 -21
- package/src/components/sheet/sheet.stories.tsx +116 -10
- package/src/components/sheet/sheet.tsx +116 -29
- package/src/components/sidebar/sidebar.meta.md +28 -38
- package/src/components/sidebar/sidebar.stories.tsx +696 -29
- package/src/components/sidebar/sidebar.tsx +615 -142
- package/src/components/skeleton/skeleton.meta.md +7 -31
- package/src/components/skeleton/skeleton.stories.tsx +3 -3
- package/src/components/skeleton/skeleton.tsx +7 -7
- package/src/components/slider/slider.meta.md +60 -13
- package/src/components/slider/slider.stories.tsx +58 -6
- package/src/components/slider/slider.tsx +154 -13
- package/src/components/sonner/sonner.meta.md +54 -8
- package/src/components/sonner/sonner.stories.tsx +79 -11
- package/src/components/sonner/sonner.tsx +137 -8
- package/src/components/spinner/spinner.meta.md +57 -21
- package/src/components/spinner/spinner.stories.tsx +66 -14
- package/src/components/spinner/spinner.tsx +111 -9
- package/src/components/statistic/statistic.meta.md +14 -30
- package/src/components/statistic/statistic.stories.tsx +1 -1
- package/src/components/statistic/statistic.tsx +4 -5
- package/src/components/steps/steps.meta.md +20 -15
- package/src/components/steps/steps.stories.tsx +37 -2
- package/src/components/steps/steps.tsx +15 -12
- package/src/components/switch/switch.meta.md +56 -15
- package/src/components/switch/switch.stories.tsx +5 -5
- package/src/components/switch/switch.tsx +59 -13
- package/src/components/table/table.meta.md +3 -7
- package/src/components/table/table.stories.tsx +1 -1
- package/src/components/table/table.tsx +4 -4
- package/src/components/tabs/tabs.meta.md +40 -32
- package/src/components/tabs/tabs.stories.tsx +104 -26
- package/src/components/tabs/tabs.tsx +125 -54
- package/src/components/tag/tag.meta.md +104 -68
- package/src/components/tag/tag.stories.tsx +183 -15
- package/src/components/tag/tag.tsx +222 -21
- package/src/components/textarea/textarea.meta.md +42 -31
- package/src/components/textarea/textarea.stories.tsx +32 -6
- package/src/components/textarea/textarea.tsx +32 -8
- package/src/components/time-picker/time-picker.meta.md +119 -50
- package/src/components/time-picker/time-picker.stories.tsx +65 -33
- package/src/components/time-picker/time-picker.tsx +889 -101
- package/src/components/timeline/timeline.meta.md +16 -17
- package/src/components/timeline/timeline.stories.tsx +24 -4
- package/src/components/timeline/timeline.tsx +32 -12
- package/src/components/toggle/toggle.meta.md +8 -12
- 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 +10 -14
- 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 +63 -18
- package/src/components/tooltip/tooltip.stories.tsx +42 -5
- package/src/components/tooltip/tooltip.tsx +81 -21
- package/src/components/tour/tour.meta.md +16 -20
- package/src/components/tour/tour.stories.tsx +3 -3
- package/src/components/tour/tour.tsx +3 -3
- package/src/components/transfer/transfer.meta.md +18 -22
- package/src/components/transfer/transfer.stories.tsx +2 -2
- package/src/components/transfer/transfer.tsx +28 -21
- package/src/components/tree/tree.meta.md +67 -22
- package/src/components/tree/tree.stories.tsx +1 -1
- package/src/components/tree/tree.tsx +9 -8
- package/src/components/tree-select/tree-select.meta.md +59 -23
- package/src/components/tree-select/tree-select.stories.tsx +2 -2
- package/src/components/tree-select/tree-select.tsx +42 -7
- package/src/components/typography/typography.meta.md +61 -39
- package/src/components/typography/typography.stories.tsx +14 -9
- package/src/components/typography/typography.tsx +38 -25
- package/src/components/upload/upload.meta.md +61 -25
- package/src/components/upload/upload.stories.tsx +69 -3
- package/src/components/upload/upload.tsx +170 -37
- package/src/components/watermark/watermark.meta.md +15 -19
- package/src/components/watermark/watermark.stories.tsx +98 -8
- 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 -101
- package/src/components/button-group/button-group.stories.tsx +0 -93
- package/src/components/button-group/button-group.tsx +0 -75
- package/src/components/combobox/combobox.meta.md +0 -102
- package/src/components/combobox/combobox.stories.tsx +0 -55
- package/src/components/combobox/combobox.tsx +0 -130
- package/src/components/input/demo/addon.tsx +0 -15
- package/src/components/input/demo/with-prefix-suffix.tsx +0 -19
- package/src/components/space/space.meta.md +0 -103
- package/src/components/space/space.stories.tsx +0 -108
- 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,101 +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
|
-
displayName: 按钮组
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# ButtonGroup 按钮组
|
|
12
|
-
|
|
13
|
-
按钮组 — shadcn 2025-10 新增,**等价 antd `Space.Compact`**(v6 替代旧 `Button.Group`)。把多个按钮 / 输入框"粘"在一起共享边线,用于 Split Button、Toolbar、Input + Button 拼装等场景。配 `ButtonGroupText` 渲染只读 addon 文本。
|
|
14
|
-
|
|
15
|
-
## When to use
|
|
16
|
-
|
|
17
|
-
- Split Button(主操作 + 右侧下拉箭头)
|
|
18
|
-
- Toolbar(同语义按钮集合,如 "上一页 / 下一页 / 跳转")
|
|
19
|
-
- Input + 按钮拼装(搜索框、单位选择)
|
|
20
|
-
- 多档切换显示(配 `Toggle` 也可,但 ToggleGroup 更专精)
|
|
21
|
-
|
|
22
|
-
## When NOT to use
|
|
23
|
-
|
|
24
|
-
- 互斥单选切换 → `ToggleGroup`(语义更强)
|
|
25
|
-
- 不相干的按钮 → 普通 `Button` + flex gap
|
|
26
|
-
|
|
27
|
-
## Props
|
|
28
|
-
|
|
29
|
-
<!-- auto:props:begin -->
|
|
30
|
-
|
|
31
|
-
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
32
|
-
| ------------- | ---------------------------- | -------------- | ---- | ----------------------------------------------------------------------------------------- |
|
|
33
|
-
| `orientation` | `'horizontal' \| 'vertical'` | `"horizontal"` | – | 排列方向。 |
|
|
34
|
-
| `attached` | `boolean` | `true` | – | 是否把按钮"粘"在一起去除边距(antd `Space.Compact` 等价行为)— `false` 时按钮间留 8px gap。 |
|
|
35
|
-
|
|
36
|
-
<!-- auto:props:end -->
|
|
37
|
-
|
|
38
|
-
## 依赖
|
|
39
|
-
|
|
40
|
-
<!-- auto:deps:begin -->
|
|
41
|
-
|
|
42
|
-
### 同库依赖
|
|
43
|
-
|
|
44
|
-
> `teamix-evo ui add button-group` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
45
|
-
|
|
46
|
-
| Entry | 类型 | 描述 |
|
|
47
|
-
| ----- | ---- | -------------------------------------------------- |
|
|
48
|
-
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
49
|
-
|
|
50
|
-
### npm 依赖
|
|
51
|
-
|
|
52
|
-
> 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
pnpm add class-variance-authority@^0.7.0
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
<!-- auto:deps:end -->
|
|
59
|
-
|
|
60
|
-
## AI 生成纪律
|
|
61
|
-
|
|
62
|
-
- **`attached=true`(默认)子项之间边线共享**:第一项 / 最后一项保留外侧圆角,中间项去除圆角;**不要**在内部再设 `rounded`
|
|
63
|
-
- **混用 Input + Button**:把 Input 直接放进 ButtonGroup,它的 `rounded-md` 也会被组件圈圆角(无需特殊处理)
|
|
64
|
-
- **`ButtonGroupText`** 用于只读 addon(`https://` 前缀、`/月` 后缀);**不要**把它当按钮点击
|
|
65
|
-
- **vertical 模式** 适合工具栏侧边小集合,horizontal 是默认推荐
|
|
66
|
-
- **不要**在 ButtonGroup 内放 spinner / icon-only 与文字按钮混排时,高度需要靠 button `size` 对齐 — 子项尺寸应一致
|
|
67
|
-
|
|
68
|
-
## Examples
|
|
69
|
-
|
|
70
|
-
```tsx
|
|
71
|
-
import { ButtonGroup, ButtonGroupText } from '@/components/ui/button-group';
|
|
72
|
-
import { Button } from '@/components/ui/button';
|
|
73
|
-
import { Input } from '@/components/ui/input';
|
|
74
|
-
import { ChevronDown } from 'lucide-react';
|
|
75
|
-
|
|
76
|
-
// Split button
|
|
77
|
-
<ButtonGroup>
|
|
78
|
-
<Button>保存</Button>
|
|
79
|
-
<Button variant="default" size="icon" icon={<ChevronDown />} aria-label="更多" />
|
|
80
|
-
</ButtonGroup>
|
|
81
|
-
|
|
82
|
-
// Toolbar
|
|
83
|
-
<ButtonGroup>
|
|
84
|
-
<Button variant="outline">上一页</Button>
|
|
85
|
-
<Button variant="outline">1 / 10</Button>
|
|
86
|
-
<Button variant="outline">下一页</Button>
|
|
87
|
-
</ButtonGroup>
|
|
88
|
-
|
|
89
|
-
// 带 addon 文本
|
|
90
|
-
<ButtonGroup>
|
|
91
|
-
<ButtonGroupText>https://</ButtonGroupText>
|
|
92
|
-
<Input defaultValue="example.com" className="rounded-l-none" />
|
|
93
|
-
<Button>访问</Button>
|
|
94
|
-
</ButtonGroup>
|
|
95
|
-
|
|
96
|
-
// 不粘连(留间距)
|
|
97
|
-
<ButtonGroup attached={false}>
|
|
98
|
-
<Button variant="outline">取消</Button>
|
|
99
|
-
<Button>确定</Button>
|
|
100
|
-
</ButtonGroup>
|
|
101
|
-
```
|
|
@@ -1,93 +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`)。',
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
argTypes: {
|
|
20
|
-
orientation: {
|
|
21
|
-
control: 'inline-radio',
|
|
22
|
-
options: ['horizontal', 'vertical'],
|
|
23
|
-
},
|
|
24
|
-
attached: { control: 'boolean' },
|
|
25
|
-
},
|
|
26
|
-
args: { orientation: 'horizontal', attached: true },
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export default meta;
|
|
30
|
-
type Story = StoryObj<typeof ButtonGroup>;
|
|
31
|
-
|
|
32
|
-
export const Playground: Story = {
|
|
33
|
-
render: (args) => (
|
|
34
|
-
<ButtonGroup {...args}>
|
|
35
|
-
<Button variant="outline">左</Button>
|
|
36
|
-
<Button variant="outline">中</Button>
|
|
37
|
-
<Button variant="outline">右</Button>
|
|
38
|
-
</ButtonGroup>
|
|
39
|
-
),
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export const SplitButton: Story = {
|
|
43
|
-
parameters: { controls: { disable: true } },
|
|
44
|
-
render: () => (
|
|
45
|
-
<ButtonGroup>
|
|
46
|
-
<Button>保存</Button>
|
|
47
|
-
<Button size="icon" icon={<ChevronDown />} aria-label="更多保存选项" />
|
|
48
|
-
</ButtonGroup>
|
|
49
|
-
),
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export const Pager: Story = {
|
|
53
|
-
parameters: { controls: { disable: true } },
|
|
54
|
-
render: () => (
|
|
55
|
-
<ButtonGroup>
|
|
56
|
-
<Button variant="outline">上一页</Button>
|
|
57
|
-
<Button variant="outline">1 / 10</Button>
|
|
58
|
-
<Button variant="outline">下一页</Button>
|
|
59
|
-
</ButtonGroup>
|
|
60
|
-
),
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
export const InputAddon: Story = {
|
|
64
|
-
parameters: { controls: { disable: true } },
|
|
65
|
-
render: () => (
|
|
66
|
-
<ButtonGroup>
|
|
67
|
-
<ButtonGroupText>https://</ButtonGroupText>
|
|
68
|
-
<Input defaultValue="example.com" className="rounded-l-none" />
|
|
69
|
-
<Button>访问</Button>
|
|
70
|
-
</ButtonGroup>
|
|
71
|
-
),
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
export const Vertical: Story = {
|
|
75
|
-
parameters: { controls: { disable: true } },
|
|
76
|
-
render: () => (
|
|
77
|
-
<ButtonGroup orientation="vertical">
|
|
78
|
-
<Button variant="outline">编辑</Button>
|
|
79
|
-
<Button variant="outline">复制</Button>
|
|
80
|
-
<Button variant="outline">删除</Button>
|
|
81
|
-
</ButtonGroup>
|
|
82
|
-
),
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
export const NotAttached: Story = {
|
|
86
|
-
parameters: { controls: { disable: true } },
|
|
87
|
-
render: () => (
|
|
88
|
-
<ButtonGroup attached={false}>
|
|
89
|
-
<Button variant="outline">取消</Button>
|
|
90
|
-
<Button>确定</Button>
|
|
91
|
-
</ButtonGroup>
|
|
92
|
-
),
|
|
93
|
-
};
|
|
@@ -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,102 +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
|
-
displayName: 组合框
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# Combobox 组合框
|
|
12
|
-
|
|
13
|
-
可搜索单选下拉 — `Command + Popover` 复合,等价 antd `AutoComplete` / `Select showSearch` 的核心场景。
|
|
14
|
-
**单选**(刻意不做多选,保持语义简单);多选请直接用 `Command` + 自管 selected 数组。
|
|
15
|
-
|
|
16
|
-
## When to use
|
|
17
|
-
|
|
18
|
-
- 选项较多需要搜索筛选(国家 / 时区 / 用户列表 / 标签)
|
|
19
|
-
- 选项动态加载(异步 — 在父组件准备数据后传给 `options`)
|
|
20
|
-
|
|
21
|
-
## When NOT to use
|
|
22
|
-
|
|
23
|
-
- 选项 ≤ 7 个 → `Select`(无需搜索)
|
|
24
|
-
- 多选 → `Command` 自定义,或 `CheckboxGroup`
|
|
25
|
-
- 自由文本输入 → `Input` + 自定义 suggestion(超出本组件范围)
|
|
26
|
-
|
|
27
|
-
## Props
|
|
28
|
-
|
|
29
|
-
<!-- auto:props:begin -->
|
|
30
|
-
|
|
31
|
-
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
32
|
-
| ------------------- | --------------------------- | ------------- | ---- | -------------------------------------------- |
|
|
33
|
-
| `options` | `ComboboxOption[]` | – | ✓ | 选项列表(antd `AutoComplete.options` 并集)。 |
|
|
34
|
-
| `value` | `string` | – | – | 受控 value(单选)。 |
|
|
35
|
-
| `onChange` | `(value: string) => void` | – | – | value 变化回调。 |
|
|
36
|
-
| `placeholder` | `string` | `"请选择..."` | – | 触发器占位文本(未选)。 |
|
|
37
|
-
| `searchPlaceholder` | `string` | `"搜索..."` | – | 搜索框占位文本。 |
|
|
38
|
-
| `emptyText` | `string` | `"无匹配项"` | – | 无匹配时的提示文本。 |
|
|
39
|
-
| `className` | `string` | `"w-[200px]"` | – | 触发器宽度。 |
|
|
40
|
-
| `disabled` | `boolean` | – | – | 是否禁用。 |
|
|
41
|
-
| `size` | `'sm' \| 'default' \| 'lg'` | `"default"` | – | 触发器尺寸。 |
|
|
42
|
-
|
|
43
|
-
<!-- auto:props:end -->
|
|
44
|
-
|
|
45
|
-
## 依赖
|
|
46
|
-
|
|
47
|
-
<!-- auto:deps:begin -->
|
|
48
|
-
|
|
49
|
-
### 同库依赖
|
|
50
|
-
|
|
51
|
-
> `teamix-evo ui add combobox` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
52
|
-
|
|
53
|
-
| Entry | 类型 | 描述 |
|
|
54
|
-
| --------- | --------- | --------------------------------------------------------------------------------------- |
|
|
55
|
-
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
56
|
-
| `button` | component | 通用按钮 — shadcn 实现 + antd 功能扩展(loading / icon / shape / block / dashed variant) |
|
|
57
|
-
| `command` | component | 命令面板 — cmdk(Linear / Raycast 风格),全局搜索 / 命令执行;Combobox 的底座 |
|
|
58
|
-
| `popover` | component | 可交互浮层 — Radix Popover + antd arrow 并集 |
|
|
59
|
-
|
|
60
|
-
### npm 依赖
|
|
61
|
-
|
|
62
|
-
> 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
pnpm add lucide-react@^0.460.0
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
<!-- auto:deps:end -->
|
|
69
|
-
|
|
70
|
-
> 选项类型 `ComboboxOption`:`{ value: string; label: ReactNode; disabled?: boolean }`。
|
|
71
|
-
|
|
72
|
-
## AI 生成纪律
|
|
73
|
-
|
|
74
|
-
- **`value` 稳定**:用业务 ID,不要用 index
|
|
75
|
-
- **`label` 可富节点**:支持图标 + 文本,不止 string
|
|
76
|
-
- **第二次点击当前 value 会清空**:`onChange('')` 触发,业务侧据此实现"取消选择"
|
|
77
|
-
- **大量选项(>1000) → 自定义虚拟滚动**:本组件未集成 virtualizer
|
|
78
|
-
- **异步加载选项**:父组件 fetch 后传 `options`,加 loading 占位符在 options 列表外
|
|
79
|
-
|
|
80
|
-
## Examples
|
|
81
|
-
|
|
82
|
-
```tsx
|
|
83
|
-
import { Combobox, type ComboboxOption } from '@/components/ui/combobox';
|
|
84
|
-
import * as React from 'react';
|
|
85
|
-
|
|
86
|
-
const frameworks: ComboboxOption[] = [
|
|
87
|
-
{ value: 'next', label: 'Next.js' },
|
|
88
|
-
{ value: 'sveltekit', label: 'SvelteKit' },
|
|
89
|
-
{ value: 'nuxt', label: 'Nuxt' },
|
|
90
|
-
{ value: 'remix', label: 'Remix' },
|
|
91
|
-
{ value: 'astro', label: 'Astro' },
|
|
92
|
-
];
|
|
93
|
-
|
|
94
|
-
const [v, setV] = React.useState('');
|
|
95
|
-
<Combobox
|
|
96
|
-
options={frameworks}
|
|
97
|
-
value={v}
|
|
98
|
-
onChange={setV}
|
|
99
|
-
placeholder="选择框架"
|
|
100
|
-
searchPlaceholder="搜索框架..."
|
|
101
|
-
/>;
|
|
102
|
-
```
|
|
@@ -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 的单选场景。',
|
|
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-panel-sm" */
|
|
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-panel-sm 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-panel-sm 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,15 +0,0 @@
|
|
|
1
|
-
import { Input } from '@/components/ui/input';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 前后置标签:通过 `addonBefore` / `addonAfter` 形成 input-group。
|
|
5
|
-
* 常用于域名拼接、协议前缀、单位后缀。
|
|
6
|
-
*/
|
|
7
|
-
export default function Demo() {
|
|
8
|
-
return (
|
|
9
|
-
<div className="flex w-96 flex-col gap-3">
|
|
10
|
-
<Input addonBefore="https://" placeholder="example.com" />
|
|
11
|
-
<Input addonAfter=".teamix.com" placeholder="alice" />
|
|
12
|
-
<Input addonBefore="https://" addonAfter=".com" placeholder="my-site" />
|
|
13
|
-
</div>
|
|
14
|
-
);
|
|
15
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Search, Mail } from 'lucide-react';
|
|
2
|
-
import { Input } from '@/components/ui/input';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* 前置 / 后置图标:通过 `prefix` / `suffix` 传任意 ReactNode(图标或文本)。
|
|
6
|
-
* 视觉对齐由组件内部处理,不要再额外用 padding 兼容图标。
|
|
7
|
-
*/
|
|
8
|
-
export default function Demo() {
|
|
9
|
-
return (
|
|
10
|
-
<div className="flex w-80 flex-col gap-3">
|
|
11
|
-
<Input prefix={<Search />} placeholder="搜索..." />
|
|
12
|
-
<Input
|
|
13
|
-
prefix={<Mail />}
|
|
14
|
-
suffix={<span className="text-xs">@teamix.com</span>}
|
|
15
|
-
placeholder="alice"
|
|
16
|
-
/>
|
|
17
|
-
</div>
|
|
18
|
-
);
|
|
19
|
-
}
|