@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
|
@@ -1,114 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
|
|
10
|
-
<DrawerPrimitive.Root
|
|
11
|
-
shouldScaleBackground={shouldScaleBackground}
|
|
12
|
-
{...props}
|
|
13
|
-
/>
|
|
14
|
-
);
|
|
15
|
-
Drawer.displayName = 'Drawer';
|
|
16
|
-
|
|
17
|
-
const DrawerTrigger = DrawerPrimitive.Trigger;
|
|
18
|
-
const DrawerPortal = DrawerPrimitive.Portal;
|
|
19
|
-
const DrawerClose = DrawerPrimitive.Close;
|
|
20
|
-
|
|
21
|
-
const DrawerOverlay = React.forwardRef<
|
|
22
|
-
React.ElementRef<typeof DrawerPrimitive.Overlay>,
|
|
23
|
-
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
|
|
24
|
-
>(({ className, ...props }, ref) => (
|
|
25
|
-
<DrawerPrimitive.Overlay
|
|
26
|
-
ref={ref}
|
|
27
|
-
className={cn('fixed inset-0 z-50 bg-black/80', className)}
|
|
28
|
-
{...props}
|
|
29
|
-
/>
|
|
30
|
-
));
|
|
31
|
-
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
|
|
32
|
-
|
|
33
|
-
export interface DrawerContentProps
|
|
34
|
-
extends React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> {}
|
|
35
|
-
|
|
36
|
-
const DrawerContent = React.forwardRef<
|
|
37
|
-
React.ElementRef<typeof DrawerPrimitive.Content>,
|
|
38
|
-
DrawerContentProps
|
|
39
|
-
>(({ className, children, ...props }, ref) => (
|
|
40
|
-
<DrawerPortal>
|
|
41
|
-
<DrawerOverlay />
|
|
42
|
-
<DrawerPrimitive.Content
|
|
43
|
-
ref={ref}
|
|
44
|
-
className={cn(
|
|
45
|
-
'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background',
|
|
46
|
-
className,
|
|
47
|
-
)}
|
|
48
|
-
{...props}
|
|
49
|
-
>
|
|
50
|
-
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
|
51
|
-
{children}
|
|
52
|
-
</DrawerPrimitive.Content>
|
|
53
|
-
</DrawerPortal>
|
|
54
|
-
));
|
|
55
|
-
DrawerContent.displayName = 'DrawerContent';
|
|
56
|
-
|
|
57
|
-
const DrawerHeader = ({
|
|
58
|
-
className,
|
|
59
|
-
...props
|
|
60
|
-
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
61
|
-
<div
|
|
62
|
-
className={cn('grid gap-1.5 p-4 text-center sm:text-left', className)}
|
|
63
|
-
{...props}
|
|
64
|
-
/>
|
|
65
|
-
);
|
|
66
|
-
DrawerHeader.displayName = 'DrawerHeader';
|
|
67
|
-
|
|
68
|
-
const DrawerFooter = ({
|
|
69
|
-
className,
|
|
70
|
-
...props
|
|
71
|
-
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
72
|
-
<div className={cn('mt-auto flex flex-col gap-2 p-4', className)} {...props} />
|
|
73
|
-
);
|
|
74
|
-
DrawerFooter.displayName = 'DrawerFooter';
|
|
75
|
-
|
|
76
|
-
const DrawerTitle = React.forwardRef<
|
|
77
|
-
React.ElementRef<typeof DrawerPrimitive.Title>,
|
|
78
|
-
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
|
|
79
|
-
>(({ className, ...props }, ref) => (
|
|
80
|
-
<DrawerPrimitive.Title
|
|
81
|
-
ref={ref}
|
|
82
|
-
className={cn(
|
|
83
|
-
'text-lg font-semibold leading-none tracking-tight',
|
|
84
|
-
className,
|
|
85
|
-
)}
|
|
86
|
-
{...props}
|
|
87
|
-
/>
|
|
88
|
-
));
|
|
89
|
-
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
|
|
90
|
-
|
|
91
|
-
const DrawerDescription = React.forwardRef<
|
|
92
|
-
React.ElementRef<typeof DrawerPrimitive.Description>,
|
|
93
|
-
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
|
|
94
|
-
>(({ className, ...props }, ref) => (
|
|
95
|
-
<DrawerPrimitive.Description
|
|
96
|
-
ref={ref}
|
|
97
|
-
className={cn('text-sm text-muted-foreground', className)}
|
|
98
|
-
{...props}
|
|
99
|
-
/>
|
|
100
|
-
));
|
|
101
|
-
DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
|
|
102
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Drawer — Sheet 的别名导出。
|
|
3
|
+
*
|
|
4
|
+
* 历史上 Drawer 基于 vaul(底部可拖拽抽屉),现在统一为 Sheet(Radix Dialog 底座)。
|
|
5
|
+
* 本文件通过 re-export 保持向后兼容:业务侧已有的 `import { Drawer, DrawerContent } from '...'` 不会断裂。
|
|
6
|
+
*
|
|
7
|
+
* @see sheet.tsx — 实际实现
|
|
8
|
+
*/
|
|
103
9
|
export {
|
|
104
|
-
Drawer,
|
|
105
|
-
DrawerPortal,
|
|
106
|
-
DrawerOverlay,
|
|
107
|
-
DrawerTrigger,
|
|
108
|
-
DrawerClose,
|
|
109
|
-
DrawerContent,
|
|
110
|
-
DrawerHeader,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
10
|
+
Sheet as Drawer,
|
|
11
|
+
SheetPortal as DrawerPortal,
|
|
12
|
+
SheetOverlay as DrawerOverlay,
|
|
13
|
+
SheetTrigger as DrawerTrigger,
|
|
14
|
+
SheetClose as DrawerClose,
|
|
15
|
+
SheetContent as DrawerContent,
|
|
16
|
+
SheetHeader as DrawerHeader,
|
|
17
|
+
SheetBody as DrawerBody,
|
|
18
|
+
SheetFooter as DrawerFooter,
|
|
19
|
+
SheetTitle as DrawerTitle,
|
|
20
|
+
SheetDescription as DrawerDescription,
|
|
21
|
+
} from '@/components/sheet/sheet';
|
|
22
|
+
|
|
23
|
+
export type { SheetContentProps as DrawerContentProps, SheetSize as DrawerSize } from '@/components/sheet/sheet';
|
|
@@ -4,10 +4,11 @@ name: DropdownMenu
|
|
|
4
4
|
type: component
|
|
5
5
|
category: navigation
|
|
6
6
|
since: 0.1.0
|
|
7
|
-
package:
|
|
7
|
+
package: '@teamix-evo/ui'
|
|
8
|
+
displayName: 下拉菜单
|
|
8
9
|
---
|
|
9
10
|
|
|
10
|
-
# DropdownMenu
|
|
11
|
+
# DropdownMenu 下拉菜单
|
|
11
12
|
|
|
12
13
|
下拉菜单 — Radix DropdownMenu 完整实现:Item / CheckboxItem / RadioItem / Label / Separator / Shortcut / Sub(子菜单)。
|
|
13
14
|
对应 antd `Dropdown`,但本组件是**完整 menu primitives**(antd Dropdown 主要走 children 自由排版,本组件提供语义化每一种 item)。
|
|
@@ -56,8 +57,6 @@ pnpm add @radix-ui/react-dropdown-menu@^2.1.0 lucide-react@^0.460.0
|
|
|
56
57
|
```
|
|
57
58
|
<!-- auto:deps:end -->
|
|
58
59
|
|
|
59
|
-
> 完整子组件:`DropdownMenu` / `DropdownMenuTrigger` / `DropdownMenuContent` / `DropdownMenuItem`(支持 `inset`)/ `DropdownMenuCheckboxItem` / `DropdownMenuRadioItem` / `DropdownMenuLabel` / `DropdownMenuSeparator` / `DropdownMenuShortcut` / `DropdownMenuGroup` / `DropdownMenuRadioGroup` / `DropdownMenuSub`(嵌套)/ `DropdownMenuSubTrigger` / `DropdownMenuSubContent`。
|
|
60
|
-
|
|
61
60
|
## AI 生成纪律
|
|
62
61
|
|
|
63
62
|
- **Trigger 用 `asChild`**:wrap 已有 Button,而非新增 button 层
|
|
@@ -70,11 +69,20 @@ pnpm add @radix-ui/react-dropdown-menu@^2.1.0 lucide-react@^0.460.0
|
|
|
70
69
|
|
|
71
70
|
```tsx
|
|
72
71
|
import {
|
|
73
|
-
DropdownMenu,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
DropdownMenu,
|
|
73
|
+
DropdownMenuTrigger,
|
|
74
|
+
DropdownMenuContent,
|
|
75
|
+
DropdownMenuItem,
|
|
76
|
+
DropdownMenuCheckboxItem,
|
|
77
|
+
DropdownMenuRadioItem,
|
|
78
|
+
DropdownMenuRadioGroup,
|
|
79
|
+
DropdownMenuLabel,
|
|
80
|
+
DropdownMenuSeparator,
|
|
81
|
+
DropdownMenuShortcut,
|
|
82
|
+
DropdownMenuSub,
|
|
83
|
+
DropdownMenuSubTrigger,
|
|
84
|
+
DropdownMenuSubContent,
|
|
85
|
+
DropdownMenuPortal,
|
|
78
86
|
} from '@/components/ui/dropdown-menu';
|
|
79
87
|
import { Button } from '@/components/ui/button';
|
|
80
88
|
import { User, Settings, LogOut } from 'lucide-react';
|
|
@@ -99,5 +107,65 @@ import { User, Settings, LogOut } from 'lucide-react';
|
|
|
99
107
|
<LogOut /> 退出
|
|
100
108
|
</DropdownMenuItem>
|
|
101
109
|
</DropdownMenuContent>
|
|
102
|
-
</DropdownMenu
|
|
110
|
+
</DropdownMenu>;
|
|
103
111
|
```
|
|
112
|
+
|
|
113
|
+
## MenuButton 形态 — 旧库 API → 新组合映射
|
|
114
|
+
|
|
115
|
+
新库 **不提供独立的 `MenuButton` 组件**。这是主动决策 — 旧库 cloud-design `MenuButton` / antd `Dropdown` 本质上就是 `DropdownMenu + Button trigger` 的组合,因此面向业务只暴露组合写法可以:
|
|
116
|
+
|
|
117
|
+
- 与 `SplitButton` 形态(见 [`button.meta.md`](../button/button.meta.md))共享同一套 trigger 协议
|
|
118
|
+
- 遵循 P1 "三层对齐"的原子/合成分层,不引入 wrapper 级套娃
|
|
119
|
+
- 业务侧保留 trigger Button 类型全控(variant / size / icon / iconRight / loading 任意组合)
|
|
120
|
+
|
|
121
|
+
### Props 映射表
|
|
122
|
+
|
|
123
|
+
| 旧库 `MenuButton` API | 新库等价写法 | 备注 |
|
|
124
|
+
| --- | --- | --- |
|
|
125
|
+
| `label="X"` | `<DropdownMenuTrigger asChild><Button iconRight={<ChevronDown />}>X</Button></DropdownMenuTrigger>` | trigger Button 是 unstyled Slot,业务自由组合 variant/size/icon/iconRight |
|
|
126
|
+
| `shape` / `type` / `size` / `component` / `ghost` 等 Button 属性透传 | 直接给 trigger Button 传同名 props | 新库 Button 见 [`button.meta.md`](../button/button.meta.md) |
|
|
127
|
+
| `autoWidth`(默认 true,弹层宽度=按钮宽度) | `<DropdownMenuContent style={{ minWidth: 'var(--radix-dropdown-menu-trigger-width)' }}>` | 读 Radix 暴露的 CSS 变量;默认遵 shadcn `min-w-menu`(8rem)。主动选 `style` 而非 Tailwind arbitrary value(`min-w-[var(--...)]`),避免 `no-arbitrary-tw-value` lint 告警 |
|
|
128
|
+
| `popupTriggerType="click"`(默认) | 默认即点击触发,无需配置 | — |
|
|
129
|
+
| `popupTriggerType="hover"` | **不支持** — Radix 故意不暴露 hover(WCAG 可达性) | 替代:用 `HoverCard`(纯展示)或 `NavigationMenu`(导航) |
|
|
130
|
+
| `popupContainer` | `<DropdownMenuPortal container={...}>` | 见 Radix 原生 Portal API |
|
|
131
|
+
| `visible` / `defaultVisible` | `<DropdownMenu open={...}>` / `defaultOpen={...}` | — |
|
|
132
|
+
| `onVisibleChange` | `<DropdownMenu onOpenChange={...}>` | — |
|
|
133
|
+
| `popupStyle` / `popupClassName` | `<DropdownMenuContent style={...} className={...}>` | 直接传给 Content |
|
|
134
|
+
| `popupProps` | `<DropdownMenuContent {...props}>` | 任意原生 prop 透传 |
|
|
135
|
+
| `followTrigger`(滚动跟随) | **不支持** — Radix Portal 默认即 follow | — |
|
|
136
|
+
| `defaultSelectedKeys` / `selectedKeys` + `selectMode="single"` | `<DropdownMenuRadioGroup defaultValue={...}>` / `value={...}` + `<DropdownMenuRadioItem value="...">` | 受控/非受控两套 |
|
|
137
|
+
| `selectMode="multiple"` + `selectedKeys` | 多个独立 `<DropdownMenuCheckboxItem checked={...} onCheckedChange={...}>` | 业务侧自管 state(单选 vs 多选语义不同) |
|
|
138
|
+
| `onItemClick` | 给每个 `<DropdownMenuItem onSelect={...}>` 单独绑 | shadcn/Radix 范式 — 每项独立处理器更类型安全 |
|
|
139
|
+
| `onSelect` | `<DropdownMenuRadioGroup onValueChange={...}>` | 单选场景 |
|
|
140
|
+
| `menuProps` | `<DropdownMenuContent {...}>` | 与 popupProps 等价 |
|
|
141
|
+
| `MenuButton.Item` | `DropdownMenuItem` | — |
|
|
142
|
+
| `MenuButton.Group` | `DropdownMenuGroup` + `DropdownMenuLabel` | — |
|
|
143
|
+
| `MenuButton.Divider` | `DropdownMenuSeparator` | — |
|
|
144
|
+
|
|
145
|
+
### 不修复清单(主动不补)
|
|
146
|
+
|
|
147
|
+
| 旧 API | 决策原因 |
|
|
148
|
+
| --- | --- |
|
|
149
|
+
| `popupTriggerType="hover"` | Radix 故意不暴露(WCAG 可达性 — hover-only 触发对键盘/触屏不友好);替代用 `HoverCard` |
|
|
150
|
+
| `delay`(hover 延时) | 无 hover 模式,故无需延时 |
|
|
151
|
+
| `beforeOpen` / `afterClose` 等生命周期 | 已统一为 `onOpenChange(open)`,语义更窄/无歧义 |
|
|
152
|
+
| `animation` 自定义参数 | 已固化为内置 CSS 动画(`animate-in/out fade+zoom+slide` 四向);业务覆盖 → `<DropdownMenuContent className="...">` |
|
|
153
|
+
| 默认 ChevronDown 图标 | trigger Button 由业务组合,不在 Trigger 内塞默认 icon — 与 shadcn / Radix 的 unstyled trigger 语义一致 |
|
|
154
|
+
| 独立 `MenuButton` 导出 | P1 三层对齐;`<DropdownMenu> + <Button>` 即原子合成,无需 wrapper |
|
|
155
|
+
|
|
156
|
+
### 4 段标准形态 ↔ 现有 Story
|
|
157
|
+
|
|
158
|
+
| 旧库 demo | 新库 Story | 文件 |
|
|
159
|
+
| --- | --- | --- |
|
|
160
|
+
| 基本(`label` + Button 属性透传) | `Default` | [`dropdown-menu.stories.tsx`](./dropdown-menu.stories.tsx) |
|
|
161
|
+
| 复杂菜单(`Group` / `Item` / `Divider`) | `WithSubMenu`(包含 Group + Sub + Separator) | 同上 |
|
|
162
|
+
| 多选菜单(`selectMode="multiple"`) | `CheckboxItems` | 同上 |
|
|
163
|
+
| 单选菜单(`selectMode="single"`) | `RadioItems` | 同上 |
|
|
164
|
+
| 尺寸(`size` 三档) | trigger Button 自传 `size` — 见 [`button.stories.tsx`](../button/button.stories.tsx) `GroupSplitButtonSizes` | — |
|
|
165
|
+
|
|
166
|
+
### MenuButton 专项 AI 生成纪律(在通用纪律之上叠加)
|
|
167
|
+
|
|
168
|
+
- **trigger Button 用 `iconRight={<ChevronDown />}`**(而非 `icon=`/`<ChevronDown />` 平铺到 children) — 让 "label + ▼" 顺序与旧库 MenuButton 一致,且自动适应 RTL
|
|
169
|
+
- **ghost / 暗色容器内不要单独发明 prop** — 直接 `variant="ghost"`(浅色容器)或在暗色作用域内 `variant="ghost"` 让其继承 `--accent`(本仓库走 OpenTrek tokens 主题作用域,不需要 prop 级 light/dark 二态)
|
|
170
|
+
- **autoWidth 不要写成 `min-w-[var(--radix-dropdown-menu-trigger-width)]`** — 会触发 `teamix-evo/no-arbitrary-tw-value` lint 告警;统一用 `style={{ minWidth: 'var(--radix-dropdown-menu-trigger-width)' }}` inline style
|
|
171
|
+
- **不要手控 `open` state 实现 `onItemClick` 全局回调** — 每个 Item 用 `onSelect`,Radix 默认会自动关闭弹层
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
-
import { LogOut, Settings, User, Cloud, Github } from 'lucide-react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
|
+
import { ChevronDown, LogOut, Settings, User, Cloud, Github } from 'lucide-react';
|
|
4
4
|
import {
|
|
5
5
|
DropdownMenu,
|
|
6
6
|
DropdownMenuTrigger,
|
|
@@ -12,6 +12,10 @@ import {
|
|
|
12
12
|
DropdownMenuLabel,
|
|
13
13
|
DropdownMenuSeparator,
|
|
14
14
|
DropdownMenuShortcut,
|
|
15
|
+
DropdownMenuSub,
|
|
16
|
+
DropdownMenuSubTrigger,
|
|
17
|
+
DropdownMenuSubContent,
|
|
18
|
+
DropdownMenuGroup,
|
|
15
19
|
} from './dropdown-menu';
|
|
16
20
|
import { Button } from '@/components/button/button';
|
|
17
21
|
|
|
@@ -19,6 +23,14 @@ const meta: Meta<typeof DropdownMenuContent> = {
|
|
|
19
23
|
title: '导航 · Navigation/DropdownMenu',
|
|
20
24
|
component: DropdownMenuContent,
|
|
21
25
|
tags: ['autodocs'],
|
|
26
|
+
parameters: {
|
|
27
|
+
docs: {
|
|
28
|
+
description: {
|
|
29
|
+
component:
|
|
30
|
+
'DropdownMenu 下拉菜单 —— 按钮 / 头像 / 溢出按钮触发一组能被选择、勾选或切换的动作。Radix `DropdownMenu` 内核 + shadcn 原子,**对齐 antd `Dropdown`** 为动作菜单提供完整语义化原语,对齐 shadcn 的 `DropdownMenuItem` / `DropdownMenuCheckboxItem` / `DropdownMenuRadioItem` / `DropdownMenuSub` / `DropdownMenuShortcut`,并补足 `disabled` / `inset` / 嵌套子菜单。视觉走 OpenTrek semantic tokens,所有样式来自 `@teamix-evo/tokens`,无 mock。',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
22
34
|
};
|
|
23
35
|
|
|
24
36
|
export default meta;
|
|
@@ -110,3 +122,77 @@ export const RadioItems: Story = {
|
|
|
110
122
|
);
|
|
111
123
|
},
|
|
112
124
|
};
|
|
125
|
+
|
|
126
|
+
export const WithSubMenu: Story = {
|
|
127
|
+
parameters: { controls: { disable: true } },
|
|
128
|
+
render: () => (
|
|
129
|
+
<DropdownMenu>
|
|
130
|
+
<DropdownMenuTrigger asChild>
|
|
131
|
+
<Button variant="outline">更多动作</Button>
|
|
132
|
+
</DropdownMenuTrigger>
|
|
133
|
+
<DropdownMenuContent className="w-56">
|
|
134
|
+
<DropdownMenuLabel>项目</DropdownMenuLabel>
|
|
135
|
+
<DropdownMenuSeparator />
|
|
136
|
+
<DropdownMenuGroup>
|
|
137
|
+
<DropdownMenuItem>新建</DropdownMenuItem>
|
|
138
|
+
<DropdownMenuItem>复制</DropdownMenuItem>
|
|
139
|
+
<DropdownMenuItem>重命名…</DropdownMenuItem>
|
|
140
|
+
</DropdownMenuGroup>
|
|
141
|
+
<DropdownMenuSeparator />
|
|
142
|
+
<DropdownMenuSub>
|
|
143
|
+
<DropdownMenuSubTrigger>分享到</DropdownMenuSubTrigger>
|
|
144
|
+
<DropdownMenuSubContent>
|
|
145
|
+
<DropdownMenuItem>邮件</DropdownMenuItem>
|
|
146
|
+
<DropdownMenuItem>消息</DropdownMenuItem>
|
|
147
|
+
<DropdownMenuItem>复制链接</DropdownMenuItem>
|
|
148
|
+
</DropdownMenuSubContent>
|
|
149
|
+
</DropdownMenuSub>
|
|
150
|
+
<DropdownMenuSub>
|
|
151
|
+
<DropdownMenuSubTrigger>导出为</DropdownMenuSubTrigger>
|
|
152
|
+
<DropdownMenuSubContent>
|
|
153
|
+
<DropdownMenuItem>PDF</DropdownMenuItem>
|
|
154
|
+
<DropdownMenuItem>Markdown</DropdownMenuItem>
|
|
155
|
+
<DropdownMenuItem disabled>HTML(即将支持)</DropdownMenuItem>
|
|
156
|
+
</DropdownMenuSubContent>
|
|
157
|
+
</DropdownMenuSub>
|
|
158
|
+
</DropdownMenuContent>
|
|
159
|
+
</DropdownMenu>
|
|
160
|
+
),
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const DisabledItems: Story = {
|
|
164
|
+
parameters: { controls: { disable: true } },
|
|
165
|
+
render: () => (
|
|
166
|
+
<DropdownMenu>
|
|
167
|
+
<DropdownMenuTrigger asChild>
|
|
168
|
+
<Button variant="outline">查看禁用项</Button>
|
|
169
|
+
</DropdownMenuTrigger>
|
|
170
|
+
<DropdownMenuContent className="w-56">
|
|
171
|
+
<DropdownMenuItem>可点击</DropdownMenuItem>
|
|
172
|
+
<DropdownMenuItem disabled>不可点击(disabled)</DropdownMenuItem>
|
|
173
|
+
<DropdownMenuSeparator />
|
|
174
|
+
<DropdownMenuItem>另一个可点击项</DropdownMenuItem>
|
|
175
|
+
</DropdownMenuContent>
|
|
176
|
+
</DropdownMenu>
|
|
177
|
+
),
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export const SplitButtonTrigger: Story = {
|
|
181
|
+
parameters: { controls: { disable: true } },
|
|
182
|
+
render: () => (
|
|
183
|
+
<div className="inline-flex isolate [&>*:not(:first-child)]:rounded-l-none [&>*:not(:last-child)]:rounded-r-none [&>*:not(:first-child)]:-ml-px hover:[&>*]:z-10 focus-visible:[&>*]:z-10">
|
|
184
|
+
<Button>保存</Button>
|
|
185
|
+
<DropdownMenu>
|
|
186
|
+
<DropdownMenuTrigger asChild>
|
|
187
|
+
<Button size="icon" icon={<ChevronDown />} aria-label="更多保存选项" />
|
|
188
|
+
</DropdownMenuTrigger>
|
|
189
|
+
<DropdownMenuContent align="end">
|
|
190
|
+
<DropdownMenuItem>保存为草稿</DropdownMenuItem>
|
|
191
|
+
<DropdownMenuItem>保存并发布</DropdownMenuItem>
|
|
192
|
+
<DropdownMenuSeparator />
|
|
193
|
+
<DropdownMenuItem>另存为…</DropdownMenuItem>
|
|
194
|
+
</DropdownMenuContent>
|
|
195
|
+
</DropdownMenu>
|
|
196
|
+
</div>
|
|
197
|
+
),
|
|
198
|
+
};
|
|
@@ -20,7 +20,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
|
|
|
20
20
|
<DropdownMenuPrimitive.SubTrigger
|
|
21
21
|
ref={ref}
|
|
22
22
|
className={cn(
|
|
23
|
-
'flex cursor-
|
|
23
|
+
'flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-xs outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
|
24
24
|
inset && 'pl-8',
|
|
25
25
|
className,
|
|
26
26
|
)}
|
|
@@ -40,7 +40,7 @@ const DropdownMenuSubContent = React.forwardRef<
|
|
|
40
40
|
<DropdownMenuPrimitive.SubContent
|
|
41
41
|
ref={ref}
|
|
42
42
|
className={cn(
|
|
43
|
-
'z-50 min-w-
|
|
43
|
+
'z-50 min-w-menu overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
|
44
44
|
className,
|
|
45
45
|
)}
|
|
46
46
|
{...props}
|
|
@@ -49,16 +49,31 @@ const DropdownMenuSubContent = React.forwardRef<
|
|
|
49
49
|
DropdownMenuSubContent.displayName =
|
|
50
50
|
DropdownMenuPrimitive.SubContent.displayName;
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* `onCloseAutoFocus` 默认 `preventDefault` — 阻止 Radix 关闭时把焦点程序化
|
|
54
|
+
* restore 回 trigger。
|
|
55
|
+
*
|
|
56
|
+
* 原因:Chromium/Safari 把"程序化 restore 的 focus"判定为非鼠标焦点 → 触发
|
|
57
|
+
* `:focus-visible` → trigger(尤其是 SplitButton 右侧 ChevronDown 这类只有
|
|
58
|
+
* 下拉箭头的 Button)在关闭后残留蓝色 ring,与用户"鼠标点完已经失焦了"的
|
|
59
|
+
* 视觉预期冲突。对齐 antd `Dropdown.Button` / cloud-design `SplitButton` 的
|
|
60
|
+
* 鼠标体感:关闭后无残留 ring。
|
|
61
|
+
*
|
|
62
|
+
* 取舍:键盘 Esc 关闭后焦点也不会 restore 回 trigger,而是落到 body(下一次
|
|
63
|
+
* Tab 从文档头开始)。这是为视觉体感做的可达性轻微降级 — 业务侧需要保留
|
|
64
|
+
* restore 行为时,显式传 `onCloseAutoFocus` 即可完全覆盖默认。
|
|
65
|
+
*/
|
|
52
66
|
const DropdownMenuContent = React.forwardRef<
|
|
53
67
|
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
|
54
68
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
|
55
|
-
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
69
|
+
>(({ className, sideOffset = 4, onCloseAutoFocus, ...props }, ref) => (
|
|
56
70
|
<DropdownMenuPrimitive.Portal>
|
|
57
71
|
<DropdownMenuPrimitive.Content
|
|
58
72
|
ref={ref}
|
|
59
73
|
sideOffset={sideOffset}
|
|
74
|
+
onCloseAutoFocus={onCloseAutoFocus ?? ((event) => event.preventDefault())}
|
|
60
75
|
className={cn(
|
|
61
|
-
'z-50 min-w-
|
|
76
|
+
'z-50 min-w-menu overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
62
77
|
className,
|
|
63
78
|
)}
|
|
64
79
|
{...props}
|
|
@@ -76,7 +91,7 @@ const DropdownMenuItem = React.forwardRef<
|
|
|
76
91
|
<DropdownMenuPrimitive.Item
|
|
77
92
|
ref={ref}
|
|
78
93
|
className={cn(
|
|
79
|
-
'relative flex cursor-
|
|
94
|
+
'relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-xs outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
|
80
95
|
inset && 'pl-8',
|
|
81
96
|
className,
|
|
82
97
|
)}
|
|
@@ -92,7 +107,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
|
|
|
92
107
|
<DropdownMenuPrimitive.CheckboxItem
|
|
93
108
|
ref={ref}
|
|
94
109
|
className={cn(
|
|
95
|
-
'relative flex cursor-
|
|
110
|
+
'relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-xs outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
96
111
|
className,
|
|
97
112
|
)}
|
|
98
113
|
checked={checked}
|
|
@@ -116,7 +131,7 @@ const DropdownMenuRadioItem = React.forwardRef<
|
|
|
116
131
|
<DropdownMenuPrimitive.RadioItem
|
|
117
132
|
ref={ref}
|
|
118
133
|
className={cn(
|
|
119
|
-
'relative flex cursor-
|
|
134
|
+
'relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-xs outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
120
135
|
className,
|
|
121
136
|
)}
|
|
122
137
|
{...props}
|
|
@@ -140,7 +155,7 @@ const DropdownMenuLabel = React.forwardRef<
|
|
|
140
155
|
<DropdownMenuPrimitive.Label
|
|
141
156
|
ref={ref}
|
|
142
157
|
className={cn(
|
|
143
|
-
'px-2 py-1.5 text-
|
|
158
|
+
'px-2 py-1.5 text-xs font-semibold',
|
|
144
159
|
inset && 'pl-8',
|
|
145
160
|
className,
|
|
146
161
|
)}
|
|
@@ -159,8 +174,7 @@ const DropdownMenuSeparator = React.forwardRef<
|
|
|
159
174
|
{...props}
|
|
160
175
|
/>
|
|
161
176
|
));
|
|
162
|
-
DropdownMenuSeparator.displayName =
|
|
163
|
-
DropdownMenuPrimitive.Separator.displayName;
|
|
177
|
+
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
|
|
164
178
|
|
|
165
179
|
const DropdownMenuShortcut = ({
|
|
166
180
|
className,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: ellipsis
|
|
3
|
+
name: Ellipsis
|
|
4
|
+
displayName: 截断文本
|
|
5
|
+
type: component
|
|
6
|
+
category: data-display
|
|
7
|
+
since: 0.2.0
|
|
8
|
+
package: '@teamix-evo/ui'
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Ellipsis 截断文本
|
|
12
|
+
|
|
13
|
+
单行 / 多行文本截断 + 渐变遮罩 + 溢出时自动 hover tooltip。对齐《Teamix UI 表单设计规范》§八 "内容显示不完全 — 截断 + 渐变遮罩 + hover tooltip 三件套"。
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
|
|
17
|
+
- 表格单元格、Field 详情值、Tag、面包屑等横向宽度受限场景
|
|
18
|
+
- Label / Description 长文案需要在固定宽度下完整可读(hover 看全)
|
|
19
|
+
- 任何"窄列展示长字符串"的位置(对齐规范第八节示例)
|
|
20
|
+
|
|
21
|
+
## When NOT to use
|
|
22
|
+
|
|
23
|
+
- 容器永远不会溢出 → 直接 `<span>` / `<p>`,不引入额外组件
|
|
24
|
+
- 需要"逐字符显示且可换行" → 用普通段落
|
|
25
|
+
- 富文本(含 button / link 等交互) → Tooltip 与子级事件可能冲突,自行组合 Tooltip + truncate
|
|
26
|
+
|
|
27
|
+
## Props
|
|
28
|
+
|
|
29
|
+
<!-- auto:props:begin -->
|
|
30
|
+
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
31
|
+
| --- | --- | --- | --- | --- |
|
|
32
|
+
| `tooltip` | `React.ReactNode` | – | – | 超长时显示的 tooltip 文本 — 不传时自动用 children 的纯文本(优先)或 `title` 属性。 显示规则:仅当实际渲染时检测到溢出(scrollWidth > clientWidth 或 scrollHeight > clientHeight)才挂载 Tooltip。 |
|
|
33
|
+
| `lines` | `number` | `1` | – | 多行截断行数。传入数字 >1 时走 `-webkit-line-clamp` 多行模式;否则单行 truncate。 |
|
|
34
|
+
| `fade` | `boolean` | `true` | – | 是否在文本右侧叠加渐变遮罩(对齐《Teamix UI 表单设计规范》§八 "截断 + 渐变遮罩 + hover tooltip 三件套")。 默认开启;在容器无溢出时 mask 自动隐藏(scrollWidth ≤ clientWidth)。 |
|
|
35
|
+
| `tooltipSide` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'top'` | – | Tooltip 弹出方向。 |
|
|
36
|
+
<!-- auto:props:end -->
|
|
37
|
+
|
|
38
|
+
## 依赖
|
|
39
|
+
|
|
40
|
+
<!-- auto:deps:begin -->
|
|
41
|
+
### 同库依赖
|
|
42
|
+
|
|
43
|
+
> `teamix-evo ui add ellipsis` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
44
|
+
|
|
45
|
+
| Entry | 类型 | 描述 |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
48
|
+
| `tooltip` | component | 文字提示气泡 — Radix Tooltip + antd arrow/placement(side) 并集 + cloud-design Balloon 双变体(default 深底 / light 白底) + 一行 wrapper API |
|
|
49
|
+
|
|
50
|
+
### npm 依赖
|
|
51
|
+
|
|
52
|
+
_无 — 本组件不依赖任何 npm 包。_
|
|
53
|
+
<!-- auto:deps:end -->
|
|
54
|
+
|
|
55
|
+
## AI 生成纪律
|
|
56
|
+
|
|
57
|
+
- **不要手写 `truncate` + `<Tooltip>`**:直接用 `<Ellipsis>`,组件内置 ResizeObserver 检测溢出,仅溢出时挂 Tooltip(避免短文本也弹)
|
|
58
|
+
- **多行截断走 `lines` prop**:`<Ellipsis lines={2}>`,不要手写 `-webkit-line-clamp` className
|
|
59
|
+
- **`fade` 默认开启**:渐变遮罩仅在溢出时显示;关掉只走纯 ellipsis 可设 `fade={false}`
|
|
60
|
+
- **tooltip 内容兜底**:不传 `tooltip` 时自动用 children 作为 tooltip 文本(适合纯文本场景);若 children 含 JSX,显式传 `tooltip="..."`
|
|
61
|
+
|
|
62
|
+
## Examples
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
import { Ellipsis } from '@/components/ui/ellipsis';
|
|
66
|
+
|
|
67
|
+
// 单行 + 渐变遮罩 + 自动 tooltip
|
|
68
|
+
<div className="w-32">
|
|
69
|
+
<Ellipsis>这一段超长文本会自动截断、右侧带渐变遮罩,hover 弹 tooltip 看全文</Ellipsis>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
// 多行
|
|
73
|
+
<div className="w-48">
|
|
74
|
+
<Ellipsis lines={2}>段落多行截断的示例文本,超过两行后剩余内容会被裁剪。</Ellipsis>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
// 显式 tooltip 内容(child 含 JSX 时常用)
|
|
78
|
+
<Ellipsis tooltip="完整路径:zhao.yu > 公共部门 > 财务部">
|
|
79
|
+
zhao.yu > 公共部门 > 财务部
|
|
80
|
+
</Ellipsis>
|
|
81
|
+
|
|
82
|
+
// 详情表格场景
|
|
83
|
+
<Field orientation="horizontal" inputWidth="m">
|
|
84
|
+
<FieldLabel className="min-w-24 shrink-0 text-muted-foreground">备注</FieldLabel>
|
|
85
|
+
<Ellipsis className="flex-1 text-xs">{remark}</Ellipsis>
|
|
86
|
+
</Field>
|
|
87
|
+
```
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { Ellipsis } from './ellipsis';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Data Display / Ellipsis',
|
|
7
|
+
component: Ellipsis,
|
|
8
|
+
parameters: { layout: 'centered' },
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
} satisfies Meta<typeof Ellipsis>;
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
type Story = StoryObj<typeof meta>;
|
|
14
|
+
|
|
15
|
+
/** 单行截断 + 渐变遮罩 + 自动 tooltip(对齐规范 §八)。 */
|
|
16
|
+
export const Default: Story = {
|
|
17
|
+
parameters: { controls: { disable: true } },
|
|
18
|
+
render: () => (
|
|
19
|
+
<div className="w-48 rounded-md border border-border bg-card p-3">
|
|
20
|
+
<Ellipsis>
|
|
21
|
+
这是一段超长文本,容器宽度有限自动截断并右侧加渐变遮罩,hover 弹 tooltip 看全文
|
|
22
|
+
</Ellipsis>
|
|
23
|
+
</div>
|
|
24
|
+
),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** 不会溢出时,不挂 tooltip 也不显示渐变。 */
|
|
28
|
+
export const NoOverflow: Story = {
|
|
29
|
+
parameters: { controls: { disable: true } },
|
|
30
|
+
render: () => (
|
|
31
|
+
<div className="w-64 rounded-md border border-border bg-card p-3">
|
|
32
|
+
<Ellipsis>短文本不溢出</Ellipsis>
|
|
33
|
+
</div>
|
|
34
|
+
),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** 多行截断(`lines={2}`)。 */
|
|
38
|
+
export const MultiLine: Story = {
|
|
39
|
+
parameters: { controls: { disable: true } },
|
|
40
|
+
render: () => (
|
|
41
|
+
<div className="w-72 rounded-md border border-border bg-card p-3">
|
|
42
|
+
<Ellipsis lines={2}>
|
|
43
|
+
段落多行截断的示例文本。当容器宽度受限且文本超过指定行数时,剩余内容会被裁剪,
|
|
44
|
+
鼠标 hover 弹出完整内容。这是规范 §八 "内容显示不完全" 的标准做法之一。
|
|
45
|
+
</Ellipsis>
|
|
46
|
+
</div>
|
|
47
|
+
),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** 关闭渐变遮罩,仅保留 ellipsis。 */
|
|
51
|
+
export const NoFade: Story = {
|
|
52
|
+
parameters: { controls: { disable: true } },
|
|
53
|
+
render: () => (
|
|
54
|
+
<div className="w-48 rounded-md border border-border bg-card p-3">
|
|
55
|
+
<Ellipsis fade={false}>
|
|
56
|
+
这是一段超长文本,关闭渐变遮罩,仅保留省略号
|
|
57
|
+
</Ellipsis>
|
|
58
|
+
</div>
|
|
59
|
+
),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** 显式 tooltip 内容(JSX children 场景)。 */
|
|
63
|
+
export const CustomTooltip: Story = {
|
|
64
|
+
parameters: { controls: { disable: true } },
|
|
65
|
+
render: () => (
|
|
66
|
+
<div className="w-48 rounded-md border border-border bg-card p-3">
|
|
67
|
+
<Ellipsis tooltip="完整路径:zhao.yu > 公共部门 > 财务部">
|
|
68
|
+
zhao.yu > 公共部门 > 财务部
|
|
69
|
+
</Ellipsis>
|
|
70
|
+
</div>
|
|
71
|
+
),
|
|
72
|
+
};
|