@teamix-evo/ui 0.1.1
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/LICENSE +21 -0
- package/README.md +336 -0
- package/_data.json +12 -0
- package/manifest.json +1688 -0
- package/package.json +90 -0
- package/src/components/accordion/accordion.meta.md +87 -0
- package/src/components/accordion/accordion.stories.tsx +67 -0
- package/src/components/accordion/accordion.tsx +58 -0
- package/src/components/affix/affix.meta.md +80 -0
- package/src/components/affix/affix.stories.tsx +57 -0
- package/src/components/affix/affix.tsx +97 -0
- package/src/components/alert/alert.meta.md +101 -0
- package/src/components/alert/alert.stories.tsx +93 -0
- package/src/components/alert/alert.tsx +132 -0
- package/src/components/alert-dialog/alert-dialog.meta.md +107 -0
- package/src/components/alert-dialog/alert-dialog.stories.tsx +81 -0
- package/src/components/alert-dialog/alert-dialog.tsx +136 -0
- package/src/components/anchor/anchor.meta.md +87 -0
- package/src/components/anchor/anchor.stories.tsx +74 -0
- package/src/components/anchor/anchor.tsx +130 -0
- package/src/components/app/app.meta.md +86 -0
- package/src/components/app/app.stories.tsx +62 -0
- package/src/components/app/app.tsx +58 -0
- package/src/components/aspect-ratio/aspect-ratio.meta.md +81 -0
- package/src/components/aspect-ratio/aspect-ratio.stories.tsx +59 -0
- package/src/components/aspect-ratio/aspect-ratio.tsx +22 -0
- package/src/components/auto-complete/auto-complete.meta.md +102 -0
- package/src/components/auto-complete/auto-complete.stories.tsx +93 -0
- package/src/components/auto-complete/auto-complete.tsx +205 -0
- package/src/components/avatar/avatar.meta.md +94 -0
- package/src/components/avatar/avatar.stories.tsx +80 -0
- package/src/components/avatar/avatar.tsx +126 -0
- package/src/components/badge/badge.meta.md +119 -0
- package/src/components/badge/badge.stories.tsx +153 -0
- package/src/components/badge/badge.tsx +210 -0
- package/src/components/breadcrumb/breadcrumb.meta.md +107 -0
- package/src/components/breadcrumb/breadcrumb.stories.tsx +84 -0
- package/src/components/breadcrumb/breadcrumb.tsx +122 -0
- package/src/components/button/button.meta.md +98 -0
- package/src/components/button/button.stories.tsx +235 -0
- package/src/components/button/button.tsx +160 -0
- package/src/components/button-group/button-group.meta.md +92 -0
- package/src/components/button-group/button-group.stories.tsx +90 -0
- package/src/components/button-group/button-group.tsx +75 -0
- package/src/components/calendar/calendar.meta.md +118 -0
- package/src/components/calendar/calendar.stories.tsx +68 -0
- package/src/components/calendar/calendar.tsx +107 -0
- package/src/components/card/card.meta.md +117 -0
- package/src/components/card/card.stories.tsx +112 -0
- package/src/components/card/card.tsx +222 -0
- package/src/components/carousel/carousel.meta.md +117 -0
- package/src/components/carousel/carousel.stories.tsx +84 -0
- package/src/components/carousel/carousel.tsx +224 -0
- package/src/components/cascader/cascader.meta.md +110 -0
- package/src/components/cascader/cascader.stories.tsx +108 -0
- package/src/components/cascader/cascader.tsx +198 -0
- package/src/components/checkbox/checkbox.meta.md +99 -0
- package/src/components/checkbox/checkbox.stories.tsx +130 -0
- package/src/components/checkbox/checkbox.tsx +125 -0
- package/src/components/collapsible/collapsible.meta.md +80 -0
- package/src/components/collapsible/collapsible.stories.tsx +35 -0
- package/src/components/collapsible/collapsible.tsx +18 -0
- package/src/components/color-picker/color-picker.meta.md +84 -0
- package/src/components/color-picker/color-picker.stories.tsx +80 -0
- package/src/components/color-picker/color-picker.tsx +160 -0
- package/src/components/combobox/combobox.meta.md +93 -0
- package/src/components/combobox/combobox.stories.tsx +55 -0
- package/src/components/combobox/combobox.tsx +130 -0
- package/src/components/command/command.meta.md +104 -0
- package/src/components/command/command.stories.tsx +59 -0
- package/src/components/command/command.tsx +147 -0
- package/src/components/context-menu/context-menu.meta.md +90 -0
- package/src/components/context-menu/context-menu.stories.tsx +46 -0
- package/src/components/context-menu/context-menu.tsx +191 -0
- package/src/components/data-table/data-table.meta.md +149 -0
- package/src/components/data-table/data-table.stories.tsx +125 -0
- package/src/components/data-table/data-table.tsx +185 -0
- package/src/components/date-picker/date-picker.meta.md +106 -0
- package/src/components/date-picker/date-picker.stories.tsx +58 -0
- package/src/components/date-picker/date-picker.tsx +156 -0
- package/src/components/descriptions/descriptions.meta.md +78 -0
- package/src/components/descriptions/descriptions.stories.tsx +60 -0
- package/src/components/descriptions/descriptions.tsx +129 -0
- package/src/components/dialog/dialog.meta.md +105 -0
- package/src/components/dialog/dialog.stories.tsx +93 -0
- package/src/components/dialog/dialog.tsx +128 -0
- package/src/components/drawer/drawer.meta.md +96 -0
- package/src/components/drawer/drawer.stories.tsx +54 -0
- package/src/components/drawer/drawer.tsx +114 -0
- package/src/components/dropdown-menu/dropdown-menu.meta.md +103 -0
- package/src/components/dropdown-menu/dropdown-menu.stories.tsx +112 -0
- package/src/components/dropdown-menu/dropdown-menu.tsx +195 -0
- package/src/components/empty/empty.meta.md +81 -0
- package/src/components/empty/empty.stories.tsx +46 -0
- package/src/components/empty/empty.tsx +47 -0
- package/src/components/field/field.meta.md +116 -0
- package/src/components/field/field.stories.tsx +117 -0
- package/src/components/field/field.tsx +164 -0
- package/src/components/flex/flex.meta.md +94 -0
- package/src/components/flex/flex.stories.tsx +112 -0
- package/src/components/flex/flex.tsx +122 -0
- package/src/components/float-button/float-button.meta.md +87 -0
- package/src/components/float-button/float-button.stories.tsx +78 -0
- package/src/components/float-button/float-button.tsx +143 -0
- package/src/components/form/form.meta.md +131 -0
- package/src/components/form/form.stories.tsx +122 -0
- package/src/components/form/form.tsx +194 -0
- package/src/components/grid/grid.meta.md +87 -0
- package/src/components/grid/grid.stories.tsx +99 -0
- package/src/components/grid/grid.tsx +130 -0
- package/src/components/hover-card/hover-card.meta.md +92 -0
- package/src/components/hover-card/hover-card.stories.tsx +68 -0
- package/src/components/hover-card/hover-card.tsx +29 -0
- package/src/components/image/image.meta.md +94 -0
- package/src/components/image/image.stories.tsx +55 -0
- package/src/components/image/image.tsx +138 -0
- package/src/components/input/input.meta.md +109 -0
- package/src/components/input/input.stories.tsx +117 -0
- package/src/components/input/input.tsx +213 -0
- package/src/components/input-group/input-group.meta.md +92 -0
- package/src/components/input-group/input-group.stories.tsx +88 -0
- package/src/components/input-group/input-group.tsx +107 -0
- package/src/components/input-number/input-number.meta.md +91 -0
- package/src/components/input-number/input-number.stories.tsx +87 -0
- package/src/components/input-number/input-number.tsx +210 -0
- package/src/components/input-otp/input-otp.meta.md +105 -0
- package/src/components/input-otp/input-otp.stories.tsx +65 -0
- package/src/components/input-otp/input-otp.tsx +97 -0
- package/src/components/item/item.meta.md +116 -0
- package/src/components/item/item.stories.tsx +113 -0
- package/src/components/item/item.tsx +171 -0
- package/src/components/kbd/kbd.meta.md +85 -0
- package/src/components/kbd/kbd.stories.tsx +70 -0
- package/src/components/kbd/kbd.tsx +81 -0
- package/src/components/label/label.meta.md +91 -0
- package/src/components/label/label.stories.tsx +87 -0
- package/src/components/label/label.tsx +66 -0
- package/src/components/masonry/masonry.meta.md +85 -0
- package/src/components/masonry/masonry.stories.tsx +66 -0
- package/src/components/masonry/masonry.tsx +59 -0
- package/src/components/mentions/mentions.meta.md +89 -0
- package/src/components/mentions/mentions.stories.tsx +75 -0
- package/src/components/mentions/mentions.tsx +237 -0
- package/src/components/menubar/menubar.meta.md +100 -0
- package/src/components/menubar/menubar.stories.tsx +81 -0
- package/src/components/menubar/menubar.tsx +232 -0
- package/src/components/native-select/native-select.meta.md +88 -0
- package/src/components/native-select/native-select.stories.tsx +80 -0
- package/src/components/native-select/native-select.tsx +54 -0
- package/src/components/navigation-menu/navigation-menu.meta.md +108 -0
- package/src/components/navigation-menu/navigation-menu.stories.tsx +112 -0
- package/src/components/navigation-menu/navigation-menu.tsx +125 -0
- package/src/components/notification/notification.meta.md +91 -0
- package/src/components/notification/notification.stories.tsx +96 -0
- package/src/components/notification/notification.tsx +84 -0
- package/src/components/pagination/pagination.meta.md +127 -0
- package/src/components/pagination/pagination.stories.tsx +62 -0
- package/src/components/pagination/pagination.tsx +285 -0
- package/src/components/popconfirm/popconfirm.meta.md +109 -0
- package/src/components/popconfirm/popconfirm.stories.tsx +76 -0
- package/src/components/popconfirm/popconfirm.tsx +134 -0
- package/src/components/popover/popover.meta.md +97 -0
- package/src/components/popover/popover.stories.tsx +82 -0
- package/src/components/popover/popover.tsx +55 -0
- package/src/components/progress/progress.meta.md +86 -0
- package/src/components/progress/progress.stories.tsx +75 -0
- package/src/components/progress/progress.tsx +195 -0
- package/src/components/radio-group/radio-group.meta.md +103 -0
- package/src/components/radio-group/radio-group.stories.tsx +77 -0
- package/src/components/radio-group/radio-group.tsx +78 -0
- package/src/components/rate/rate.meta.md +87 -0
- package/src/components/rate/rate.stories.tsx +81 -0
- package/src/components/rate/rate.tsx +153 -0
- package/src/components/resizable/resizable.meta.md +92 -0
- package/src/components/resizable/resizable.stories.tsx +104 -0
- package/src/components/resizable/resizable.tsx +56 -0
- package/src/components/result/result.meta.md +90 -0
- package/src/components/result/result.stories.tsx +71 -0
- package/src/components/result/result.tsx +91 -0
- package/src/components/scroll-area/scroll-area.meta.md +84 -0
- package/src/components/scroll-area/scroll-area.stories.tsx +41 -0
- package/src/components/scroll-area/scroll-area.tsx +51 -0
- package/src/components/segmented/segmented.meta.md +103 -0
- package/src/components/segmented/segmented.stories.tsx +101 -0
- package/src/components/segmented/segmented.tsx +138 -0
- package/src/components/select/select.meta.md +110 -0
- package/src/components/select/select.stories.tsx +100 -0
- package/src/components/select/select.tsx +188 -0
- package/src/components/separator/separator.meta.md +74 -0
- package/src/components/separator/separator.stories.tsx +71 -0
- package/src/components/separator/separator.tsx +104 -0
- package/src/components/sheet/sheet.meta.md +97 -0
- package/src/components/sheet/sheet.stories.tsx +82 -0
- package/src/components/sheet/sheet.tsx +139 -0
- package/src/components/sidebar/sidebar.meta.md +131 -0
- package/src/components/sidebar/sidebar.stories.tsx +82 -0
- package/src/components/sidebar/sidebar.tsx +351 -0
- package/src/components/skeleton/skeleton.meta.md +95 -0
- package/src/components/skeleton/skeleton.stories.tsx +79 -0
- package/src/components/skeleton/skeleton.tsx +144 -0
- package/src/components/slider/slider.meta.md +94 -0
- package/src/components/slider/slider.stories.tsx +69 -0
- package/src/components/slider/slider.tsx +86 -0
- package/src/components/sonner/sonner.meta.md +96 -0
- package/src/components/sonner/sonner.stories.tsx +91 -0
- package/src/components/sonner/sonner.tsx +40 -0
- package/src/components/space/space.meta.md +94 -0
- package/src/components/space/space.stories.tsx +94 -0
- package/src/components/space/space.tsx +106 -0
- package/src/components/spinner/spinner.meta.md +76 -0
- package/src/components/spinner/spinner.stories.tsx +71 -0
- package/src/components/spinner/spinner.tsx +64 -0
- package/src/components/statistic/statistic.meta.md +99 -0
- package/src/components/statistic/statistic.stories.tsx +71 -0
- package/src/components/statistic/statistic.tsx +197 -0
- package/src/components/steps/steps.meta.md +102 -0
- package/src/components/steps/steps.stories.tsx +75 -0
- package/src/components/steps/steps.tsx +170 -0
- package/src/components/switch/switch.meta.md +92 -0
- package/src/components/switch/switch.stories.tsx +75 -0
- package/src/components/switch/switch.tsx +101 -0
- package/src/components/table/table.meta.md +95 -0
- package/src/components/table/table.stories.tsx +75 -0
- package/src/components/table/table.tsx +122 -0
- package/src/components/tabs/tabs.meta.md +98 -0
- package/src/components/tabs/tabs.stories.tsx +70 -0
- package/src/components/tabs/tabs.tsx +119 -0
- package/src/components/tag/tag.meta.md +94 -0
- package/src/components/tag/tag.stories.tsx +77 -0
- package/src/components/tag/tag.tsx +185 -0
- package/src/components/textarea/textarea.meta.md +83 -0
- package/src/components/textarea/textarea.stories.tsx +63 -0
- package/src/components/textarea/textarea.tsx +113 -0
- package/src/components/time-picker/time-picker.meta.md +83 -0
- package/src/components/time-picker/time-picker.stories.tsx +59 -0
- package/src/components/time-picker/time-picker.tsx +94 -0
- package/src/components/timeline/timeline.meta.md +102 -0
- package/src/components/timeline/timeline.stories.tsx +104 -0
- package/src/components/timeline/timeline.tsx +147 -0
- package/src/components/toggle/toggle.meta.md +88 -0
- package/src/components/toggle/toggle.stories.tsx +66 -0
- package/src/components/toggle/toggle.tsx +53 -0
- package/src/components/toggle-group/toggle-group.meta.md +90 -0
- package/src/components/toggle-group/toggle-group.stories.tsx +83 -0
- package/src/components/toggle-group/toggle-group.tsx +78 -0
- package/src/components/tooltip/tooltip.meta.md +99 -0
- package/src/components/tooltip/tooltip.stories.tsx +71 -0
- package/src/components/tooltip/tooltip.tsx +93 -0
- package/src/components/tour/tour.meta.md +116 -0
- package/src/components/tour/tour.stories.tsx +66 -0
- package/src/components/tour/tour.tsx +242 -0
- package/src/components/transfer/transfer.meta.md +90 -0
- package/src/components/transfer/transfer.stories.tsx +68 -0
- package/src/components/transfer/transfer.tsx +251 -0
- package/src/components/tree/tree.meta.md +111 -0
- package/src/components/tree/tree.stories.tsx +109 -0
- package/src/components/tree/tree.tsx +367 -0
- package/src/components/tree-select/tree-select.meta.md +100 -0
- package/src/components/tree-select/tree-select.stories.tsx +80 -0
- package/src/components/tree-select/tree-select.tsx +171 -0
- package/src/components/typography/typography.meta.md +102 -0
- package/src/components/typography/typography.stories.tsx +115 -0
- package/src/components/typography/typography.tsx +245 -0
- package/src/components/upload/upload.meta.md +111 -0
- package/src/components/upload/upload.stories.tsx +75 -0
- package/src/components/upload/upload.tsx +265 -0
- package/src/components/watermark/watermark.meta.md +95 -0
- package/src/components/watermark/watermark.stories.tsx +78 -0
- package/src/components/watermark/watermark.tsx +165 -0
- package/src/utils/cn.ts +6 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import {
|
|
3
|
+
Drawer,
|
|
4
|
+
DrawerTrigger,
|
|
5
|
+
DrawerContent,
|
|
6
|
+
DrawerHeader,
|
|
7
|
+
DrawerTitle,
|
|
8
|
+
DrawerDescription,
|
|
9
|
+
DrawerFooter,
|
|
10
|
+
DrawerClose,
|
|
11
|
+
} from './drawer';
|
|
12
|
+
import { Button } from '@/components/button/button';
|
|
13
|
+
|
|
14
|
+
const meta: Meta<typeof DrawerContent> = {
|
|
15
|
+
title: '反馈与浮层 · Feedback/Drawer',
|
|
16
|
+
component: DrawerContent,
|
|
17
|
+
tags: ['autodocs'],
|
|
18
|
+
parameters: {
|
|
19
|
+
docs: {
|
|
20
|
+
description: {
|
|
21
|
+
component:
|
|
22
|
+
'抽屉(移动优先) — 从屏幕边缘滑入的浮层容器,常用于移动端表单 / 详情 / 导航等中等复杂度的二级界面。基于 [Vaul](https://vaul.emilkowal.ski/) 实现,提供原生手势 + 拖拽关闭 + 顶部把手;桌面端可视为 `Sheet` 的移动友好版本(能力对齐 antd Drawer)。视觉走 OpenTrek semantic tokens,所有样式来自 `@teamix-evo/design`,无 mock。',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default meta;
|
|
29
|
+
type Story = StoryObj<typeof DrawerContent>;
|
|
30
|
+
|
|
31
|
+
export const Playground: Story = {
|
|
32
|
+
render: () => (
|
|
33
|
+
<Drawer>
|
|
34
|
+
<DrawerTrigger asChild>
|
|
35
|
+
<Button variant="outline">打开 Drawer</Button>
|
|
36
|
+
</DrawerTrigger>
|
|
37
|
+
<DrawerContent>
|
|
38
|
+
<DrawerHeader>
|
|
39
|
+
<DrawerTitle>发起讨论</DrawerTitle>
|
|
40
|
+
<DrawerDescription>讨论将通知到该项目所有成员。</DrawerDescription>
|
|
41
|
+
</DrawerHeader>
|
|
42
|
+
<div className="px-4 pb-4 text-sm text-muted-foreground">
|
|
43
|
+
表单字段放在这里... 试试向下拖拽抽屉关闭。
|
|
44
|
+
</div>
|
|
45
|
+
<DrawerFooter>
|
|
46
|
+
<Button>提交</Button>
|
|
47
|
+
<DrawerClose asChild>
|
|
48
|
+
<Button variant="outline">取消</Button>
|
|
49
|
+
</DrawerClose>
|
|
50
|
+
</DrawerFooter>
|
|
51
|
+
</DrawerContent>
|
|
52
|
+
</Drawer>
|
|
53
|
+
),
|
|
54
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Drawer as DrawerPrimitive } from 'vaul';
|
|
3
|
+
|
|
4
|
+
import { cn } from '@/utils/cn';
|
|
5
|
+
|
|
6
|
+
const Drawer = ({
|
|
7
|
+
shouldScaleBackground = true,
|
|
8
|
+
...props
|
|
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
|
+
|
|
103
|
+
export {
|
|
104
|
+
Drawer,
|
|
105
|
+
DrawerPortal,
|
|
106
|
+
DrawerOverlay,
|
|
107
|
+
DrawerTrigger,
|
|
108
|
+
DrawerClose,
|
|
109
|
+
DrawerContent,
|
|
110
|
+
DrawerHeader,
|
|
111
|
+
DrawerFooter,
|
|
112
|
+
DrawerTitle,
|
|
113
|
+
DrawerDescription,
|
|
114
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: dropdown-menu
|
|
3
|
+
name: DropdownMenu
|
|
4
|
+
type: component
|
|
5
|
+
category: navigation
|
|
6
|
+
since: 0.1.0
|
|
7
|
+
package: "@teamix-evo/ui"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# DropdownMenu
|
|
11
|
+
|
|
12
|
+
下拉菜单 — Radix DropdownMenu 完整实现:Item / CheckboxItem / RadioItem / Label / Separator / Shortcut / Sub(子菜单)。
|
|
13
|
+
对应 antd `Dropdown`,但本组件是**完整 menu primitives**(antd Dropdown 主要走 children 自由排版,本组件提供语义化每一种 item)。
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
|
|
17
|
+
- 行内动作菜单(列表项的"更多")
|
|
18
|
+
- 用户头像下拉(账户 / 设置 / 退出)
|
|
19
|
+
- 工具栏溢出菜单
|
|
20
|
+
- 多选 / 单选下拉(用 CheckboxItem / RadioItem)
|
|
21
|
+
|
|
22
|
+
## When NOT to use
|
|
23
|
+
|
|
24
|
+
- 页面顶部主导航 → `NavigationMenu`
|
|
25
|
+
- 工具栏多个一级菜单 → `Menubar`
|
|
26
|
+
- 右键菜单 → `ContextMenu`
|
|
27
|
+
- 选择类表单 → `Select` / `Combobox`
|
|
28
|
+
|
|
29
|
+
## Props
|
|
30
|
+
|
|
31
|
+
> 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成。下表是 `DropdownMenuContent` 的 props;`DropdownMenu`(Root)透传 Radix `open / defaultOpen / onOpenChange / modal`。
|
|
32
|
+
|
|
33
|
+
<!-- auto:props:begin -->
|
|
34
|
+
_(组件无 `<Name>Props` interface — props 详见 [`dropdown-menu.tsx`](./dropdown-menu.tsx))_
|
|
35
|
+
<!-- auto:props:end -->
|
|
36
|
+
|
|
37
|
+
## 依赖
|
|
38
|
+
|
|
39
|
+
> 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成,数据源是 [`manifest.json`](../../../manifest.json)。**手工编辑 marker 之间的内容会在下次生成时被覆盖**。
|
|
40
|
+
|
|
41
|
+
<!-- auto:deps:begin -->
|
|
42
|
+
### 同库依赖
|
|
43
|
+
|
|
44
|
+
> `teamix-evo ui add dropdown-menu` 时,以下 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 @radix-ui/react-dropdown-menu@^2.1.0 lucide-react@^0.460.0
|
|
56
|
+
```
|
|
57
|
+
<!-- auto:deps:end -->
|
|
58
|
+
|
|
59
|
+
> 完整子组件:`DropdownMenu` / `DropdownMenuTrigger` / `DropdownMenuContent` / `DropdownMenuItem`(支持 `inset`)/ `DropdownMenuCheckboxItem` / `DropdownMenuRadioItem` / `DropdownMenuLabel` / `DropdownMenuSeparator` / `DropdownMenuShortcut` / `DropdownMenuGroup` / `DropdownMenuRadioGroup` / `DropdownMenuSub`(嵌套)/ `DropdownMenuSubTrigger` / `DropdownMenuSubContent`。
|
|
60
|
+
|
|
61
|
+
## AI 生成纪律
|
|
62
|
+
|
|
63
|
+
- **Trigger 用 `asChild`**:wrap 已有 Button,而非新增 button 层
|
|
64
|
+
- **每个 Item 必有 `onClick` 或 `onSelect`**:无操作的 Item 是反模式 — 移到 Description 区
|
|
65
|
+
- **危险动作单独配色**:`<DropdownMenuItem className="text-destructive focus:text-destructive">删除</DropdownMenuItem>`,**不要**塞 destructive Button 进 Item
|
|
66
|
+
- **Shortcut 用 `<DropdownMenuShortcut>`**:右对齐快捷键文案,**不要**手写 `<span className="ml-auto">`
|
|
67
|
+
- **inset 对齐有 icon 的 item**:同一菜单内有 icon item 也有无 icon item 时,无 icon 的加 `inset` 让文字对齐
|
|
68
|
+
|
|
69
|
+
## Examples
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import {
|
|
73
|
+
DropdownMenu, DropdownMenuTrigger, DropdownMenuContent,
|
|
74
|
+
DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem,
|
|
75
|
+
DropdownMenuRadioGroup, DropdownMenuLabel, DropdownMenuSeparator,
|
|
76
|
+
DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubTrigger,
|
|
77
|
+
DropdownMenuSubContent, DropdownMenuPortal,
|
|
78
|
+
} from '@/components/ui/dropdown-menu';
|
|
79
|
+
import { Button } from '@/components/ui/button';
|
|
80
|
+
import { User, Settings, LogOut } from 'lucide-react';
|
|
81
|
+
|
|
82
|
+
<DropdownMenu>
|
|
83
|
+
<DropdownMenuTrigger asChild>
|
|
84
|
+
<Button variant="outline">菜单</Button>
|
|
85
|
+
</DropdownMenuTrigger>
|
|
86
|
+
<DropdownMenuContent className="w-56">
|
|
87
|
+
<DropdownMenuLabel>我的账户</DropdownMenuLabel>
|
|
88
|
+
<DropdownMenuSeparator />
|
|
89
|
+
<DropdownMenuItem>
|
|
90
|
+
<User /> 个人资料
|
|
91
|
+
<DropdownMenuShortcut>⌘P</DropdownMenuShortcut>
|
|
92
|
+
</DropdownMenuItem>
|
|
93
|
+
<DropdownMenuItem>
|
|
94
|
+
<Settings /> 设置
|
|
95
|
+
<DropdownMenuShortcut>⌘,</DropdownMenuShortcut>
|
|
96
|
+
</DropdownMenuItem>
|
|
97
|
+
<DropdownMenuSeparator />
|
|
98
|
+
<DropdownMenuItem className="text-destructive focus:text-destructive">
|
|
99
|
+
<LogOut /> 退出
|
|
100
|
+
</DropdownMenuItem>
|
|
101
|
+
</DropdownMenuContent>
|
|
102
|
+
</DropdownMenu>
|
|
103
|
+
```
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { LogOut, Settings, User, Cloud, Github } from 'lucide-react';
|
|
4
|
+
import {
|
|
5
|
+
DropdownMenu,
|
|
6
|
+
DropdownMenuTrigger,
|
|
7
|
+
DropdownMenuContent,
|
|
8
|
+
DropdownMenuItem,
|
|
9
|
+
DropdownMenuCheckboxItem,
|
|
10
|
+
DropdownMenuRadioGroup,
|
|
11
|
+
DropdownMenuRadioItem,
|
|
12
|
+
DropdownMenuLabel,
|
|
13
|
+
DropdownMenuSeparator,
|
|
14
|
+
DropdownMenuShortcut,
|
|
15
|
+
} from './dropdown-menu';
|
|
16
|
+
import { Button } from '@/components/button/button';
|
|
17
|
+
|
|
18
|
+
const meta: Meta<typeof DropdownMenuContent> = {
|
|
19
|
+
title: '导航 · Navigation/DropdownMenu',
|
|
20
|
+
component: DropdownMenuContent,
|
|
21
|
+
tags: ['autodocs'],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
type Story = StoryObj<typeof DropdownMenuContent>;
|
|
26
|
+
|
|
27
|
+
export const Default: Story = {
|
|
28
|
+
render: () => (
|
|
29
|
+
<DropdownMenu>
|
|
30
|
+
<DropdownMenuTrigger asChild>
|
|
31
|
+
<Button variant="outline">打开菜单</Button>
|
|
32
|
+
</DropdownMenuTrigger>
|
|
33
|
+
<DropdownMenuContent className="w-56">
|
|
34
|
+
<DropdownMenuLabel>我的账户</DropdownMenuLabel>
|
|
35
|
+
<DropdownMenuSeparator />
|
|
36
|
+
<DropdownMenuItem>
|
|
37
|
+
<User /> 个人资料
|
|
38
|
+
<DropdownMenuShortcut>⌘P</DropdownMenuShortcut>
|
|
39
|
+
</DropdownMenuItem>
|
|
40
|
+
<DropdownMenuItem>
|
|
41
|
+
<Settings /> 设置
|
|
42
|
+
<DropdownMenuShortcut>⌘,</DropdownMenuShortcut>
|
|
43
|
+
</DropdownMenuItem>
|
|
44
|
+
<DropdownMenuItem>
|
|
45
|
+
<Cloud /> API
|
|
46
|
+
</DropdownMenuItem>
|
|
47
|
+
<DropdownMenuItem>
|
|
48
|
+
<Github /> GitHub
|
|
49
|
+
</DropdownMenuItem>
|
|
50
|
+
<DropdownMenuSeparator />
|
|
51
|
+
<DropdownMenuItem className="text-destructive focus:text-destructive">
|
|
52
|
+
<LogOut /> 退出
|
|
53
|
+
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
|
|
54
|
+
</DropdownMenuItem>
|
|
55
|
+
</DropdownMenuContent>
|
|
56
|
+
</DropdownMenu>
|
|
57
|
+
),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const CheckboxItems: Story = {
|
|
61
|
+
parameters: { controls: { disable: true } },
|
|
62
|
+
render: () => {
|
|
63
|
+
const [bookmarks, setBookmarks] = React.useState(true);
|
|
64
|
+
const [fullURLs, setFullURLs] = React.useState(false);
|
|
65
|
+
return (
|
|
66
|
+
<DropdownMenu>
|
|
67
|
+
<DropdownMenuTrigger asChild>
|
|
68
|
+
<Button variant="outline">视图设置</Button>
|
|
69
|
+
</DropdownMenuTrigger>
|
|
70
|
+
<DropdownMenuContent className="w-56">
|
|
71
|
+
<DropdownMenuLabel>外观</DropdownMenuLabel>
|
|
72
|
+
<DropdownMenuSeparator />
|
|
73
|
+
<DropdownMenuCheckboxItem
|
|
74
|
+
checked={bookmarks}
|
|
75
|
+
onCheckedChange={setBookmarks}
|
|
76
|
+
>
|
|
77
|
+
显示书签栏
|
|
78
|
+
</DropdownMenuCheckboxItem>
|
|
79
|
+
<DropdownMenuCheckboxItem
|
|
80
|
+
checked={fullURLs}
|
|
81
|
+
onCheckedChange={setFullURLs}
|
|
82
|
+
>
|
|
83
|
+
显示完整 URL
|
|
84
|
+
</DropdownMenuCheckboxItem>
|
|
85
|
+
</DropdownMenuContent>
|
|
86
|
+
</DropdownMenu>
|
|
87
|
+
);
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const RadioItems: Story = {
|
|
92
|
+
parameters: { controls: { disable: true } },
|
|
93
|
+
render: () => {
|
|
94
|
+
const [pos, setPos] = React.useState('bottom');
|
|
95
|
+
return (
|
|
96
|
+
<DropdownMenu>
|
|
97
|
+
<DropdownMenuTrigger asChild>
|
|
98
|
+
<Button variant="outline">面板位置</Button>
|
|
99
|
+
</DropdownMenuTrigger>
|
|
100
|
+
<DropdownMenuContent className="w-56">
|
|
101
|
+
<DropdownMenuLabel>面板位置</DropdownMenuLabel>
|
|
102
|
+
<DropdownMenuSeparator />
|
|
103
|
+
<DropdownMenuRadioGroup value={pos} onValueChange={setPos}>
|
|
104
|
+
<DropdownMenuRadioItem value="top">顶部</DropdownMenuRadioItem>
|
|
105
|
+
<DropdownMenuRadioItem value="bottom">底部</DropdownMenuRadioItem>
|
|
106
|
+
<DropdownMenuRadioItem value="right">右侧</DropdownMenuRadioItem>
|
|
107
|
+
</DropdownMenuRadioGroup>
|
|
108
|
+
</DropdownMenuContent>
|
|
109
|
+
</DropdownMenu>
|
|
110
|
+
);
|
|
111
|
+
},
|
|
112
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
3
|
+
import { Check, ChevronRight, Circle } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
import { cn } from '@/utils/cn';
|
|
6
|
+
|
|
7
|
+
const DropdownMenu = DropdownMenuPrimitive.Root;
|
|
8
|
+
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
|
9
|
+
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
|
10
|
+
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
|
|
11
|
+
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
|
|
12
|
+
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
|
13
|
+
|
|
14
|
+
const DropdownMenuSubTrigger = React.forwardRef<
|
|
15
|
+
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
|
16
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
17
|
+
inset?: boolean;
|
|
18
|
+
}
|
|
19
|
+
>(({ className, inset, children, ...props }, ref) => (
|
|
20
|
+
<DropdownMenuPrimitive.SubTrigger
|
|
21
|
+
ref={ref}
|
|
22
|
+
className={cn(
|
|
23
|
+
'flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
|
24
|
+
inset && 'pl-8',
|
|
25
|
+
className,
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
<ChevronRight className="ml-auto" />
|
|
31
|
+
</DropdownMenuPrimitive.SubTrigger>
|
|
32
|
+
));
|
|
33
|
+
DropdownMenuSubTrigger.displayName =
|
|
34
|
+
DropdownMenuPrimitive.SubTrigger.displayName;
|
|
35
|
+
|
|
36
|
+
const DropdownMenuSubContent = React.forwardRef<
|
|
37
|
+
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
|
38
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
|
39
|
+
>(({ className, ...props }, ref) => (
|
|
40
|
+
<DropdownMenuPrimitive.SubContent
|
|
41
|
+
ref={ref}
|
|
42
|
+
className={cn(
|
|
43
|
+
'z-50 min-w-[8rem] overflow-hidden rounded-md 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
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
));
|
|
49
|
+
DropdownMenuSubContent.displayName =
|
|
50
|
+
DropdownMenuPrimitive.SubContent.displayName;
|
|
51
|
+
|
|
52
|
+
const DropdownMenuContent = React.forwardRef<
|
|
53
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
|
54
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
|
55
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
56
|
+
<DropdownMenuPrimitive.Portal>
|
|
57
|
+
<DropdownMenuPrimitive.Content
|
|
58
|
+
ref={ref}
|
|
59
|
+
sideOffset={sideOffset}
|
|
60
|
+
className={cn(
|
|
61
|
+
'z-50 min-w-[8rem] overflow-hidden rounded-md 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
|
+
className,
|
|
63
|
+
)}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
</DropdownMenuPrimitive.Portal>
|
|
67
|
+
));
|
|
68
|
+
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
|
69
|
+
|
|
70
|
+
const DropdownMenuItem = React.forwardRef<
|
|
71
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
|
72
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
|
73
|
+
inset?: boolean;
|
|
74
|
+
}
|
|
75
|
+
>(({ className, inset, ...props }, ref) => (
|
|
76
|
+
<DropdownMenuPrimitive.Item
|
|
77
|
+
ref={ref}
|
|
78
|
+
className={cn(
|
|
79
|
+
'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm 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
|
+
inset && 'pl-8',
|
|
81
|
+
className,
|
|
82
|
+
)}
|
|
83
|
+
{...props}
|
|
84
|
+
/>
|
|
85
|
+
));
|
|
86
|
+
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
|
87
|
+
|
|
88
|
+
const DropdownMenuCheckboxItem = React.forwardRef<
|
|
89
|
+
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
|
90
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
|
91
|
+
>(({ className, children, checked, ...props }, ref) => (
|
|
92
|
+
<DropdownMenuPrimitive.CheckboxItem
|
|
93
|
+
ref={ref}
|
|
94
|
+
className={cn(
|
|
95
|
+
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
96
|
+
className,
|
|
97
|
+
)}
|
|
98
|
+
checked={checked}
|
|
99
|
+
{...props}
|
|
100
|
+
>
|
|
101
|
+
<span className="absolute left-2 flex size-3.5 items-center justify-center">
|
|
102
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
103
|
+
<Check className="size-4" />
|
|
104
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
105
|
+
</span>
|
|
106
|
+
{children}
|
|
107
|
+
</DropdownMenuPrimitive.CheckboxItem>
|
|
108
|
+
));
|
|
109
|
+
DropdownMenuCheckboxItem.displayName =
|
|
110
|
+
DropdownMenuPrimitive.CheckboxItem.displayName;
|
|
111
|
+
|
|
112
|
+
const DropdownMenuRadioItem = React.forwardRef<
|
|
113
|
+
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
|
114
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
|
115
|
+
>(({ className, children, ...props }, ref) => (
|
|
116
|
+
<DropdownMenuPrimitive.RadioItem
|
|
117
|
+
ref={ref}
|
|
118
|
+
className={cn(
|
|
119
|
+
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
120
|
+
className,
|
|
121
|
+
)}
|
|
122
|
+
{...props}
|
|
123
|
+
>
|
|
124
|
+
<span className="absolute left-2 flex size-3.5 items-center justify-center">
|
|
125
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
126
|
+
<Circle className="size-2 fill-current" />
|
|
127
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
128
|
+
</span>
|
|
129
|
+
{children}
|
|
130
|
+
</DropdownMenuPrimitive.RadioItem>
|
|
131
|
+
));
|
|
132
|
+
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
|
|
133
|
+
|
|
134
|
+
const DropdownMenuLabel = React.forwardRef<
|
|
135
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
|
136
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
|
137
|
+
inset?: boolean;
|
|
138
|
+
}
|
|
139
|
+
>(({ className, inset, ...props }, ref) => (
|
|
140
|
+
<DropdownMenuPrimitive.Label
|
|
141
|
+
ref={ref}
|
|
142
|
+
className={cn(
|
|
143
|
+
'px-2 py-1.5 text-sm font-semibold',
|
|
144
|
+
inset && 'pl-8',
|
|
145
|
+
className,
|
|
146
|
+
)}
|
|
147
|
+
{...props}
|
|
148
|
+
/>
|
|
149
|
+
));
|
|
150
|
+
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
|
|
151
|
+
|
|
152
|
+
const DropdownMenuSeparator = React.forwardRef<
|
|
153
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
|
154
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
|
155
|
+
>(({ className, ...props }, ref) => (
|
|
156
|
+
<DropdownMenuPrimitive.Separator
|
|
157
|
+
ref={ref}
|
|
158
|
+
className={cn('-mx-1 my-1 h-px bg-muted', className)}
|
|
159
|
+
{...props}
|
|
160
|
+
/>
|
|
161
|
+
));
|
|
162
|
+
DropdownMenuSeparator.displayName =
|
|
163
|
+
DropdownMenuPrimitive.Separator.displayName;
|
|
164
|
+
|
|
165
|
+
const DropdownMenuShortcut = ({
|
|
166
|
+
className,
|
|
167
|
+
...props
|
|
168
|
+
}: React.HTMLAttributes<HTMLSpanElement>) => (
|
|
169
|
+
<span
|
|
170
|
+
className={cn(
|
|
171
|
+
'ml-auto text-xs tracking-widest text-muted-foreground',
|
|
172
|
+
className,
|
|
173
|
+
)}
|
|
174
|
+
{...props}
|
|
175
|
+
/>
|
|
176
|
+
);
|
|
177
|
+
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';
|
|
178
|
+
|
|
179
|
+
export {
|
|
180
|
+
DropdownMenu,
|
|
181
|
+
DropdownMenuTrigger,
|
|
182
|
+
DropdownMenuContent,
|
|
183
|
+
DropdownMenuItem,
|
|
184
|
+
DropdownMenuCheckboxItem,
|
|
185
|
+
DropdownMenuRadioItem,
|
|
186
|
+
DropdownMenuLabel,
|
|
187
|
+
DropdownMenuSeparator,
|
|
188
|
+
DropdownMenuShortcut,
|
|
189
|
+
DropdownMenuGroup,
|
|
190
|
+
DropdownMenuPortal,
|
|
191
|
+
DropdownMenuSub,
|
|
192
|
+
DropdownMenuSubContent,
|
|
193
|
+
DropdownMenuSubTrigger,
|
|
194
|
+
DropdownMenuRadioGroup,
|
|
195
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: empty
|
|
3
|
+
name: Empty
|
|
4
|
+
type: component
|
|
5
|
+
category: feedback
|
|
6
|
+
since: 0.1.0
|
|
7
|
+
package: "@teamix-evo/ui"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Empty
|
|
11
|
+
|
|
12
|
+
空状态 — antd 独有补足。**列表 / 表格 / 搜索结果为空**时的占位提示,提供引导动作鼓励用户创建首条数据。
|
|
13
|
+
|
|
14
|
+
## When to use
|
|
15
|
+
|
|
16
|
+
- 列表 / 表格无数据
|
|
17
|
+
- 搜索结果为空
|
|
18
|
+
- 收件箱 / 通知 / 任务首次访问无内容
|
|
19
|
+
|
|
20
|
+
## When NOT to use
|
|
21
|
+
|
|
22
|
+
- 加载中 → `Skeleton`
|
|
23
|
+
- 错误 / 失败 → `Result` 或 `Alert`
|
|
24
|
+
- 表单字段空 → 直接 placeholder
|
|
25
|
+
|
|
26
|
+
<!-- auto:props:begin -->
|
|
27
|
+
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
28
|
+
| --- | --- | --- | --- | --- |
|
|
29
|
+
| `image` | `React.ReactNode` | – | – | 自定义图标(覆盖默认 Inbox)。 |
|
|
30
|
+
| `description` | `React.ReactNode` | `"暂无数据"` | – | 描述文本。 |
|
|
31
|
+
| `extra` | `React.ReactNode` | – | – | 操作区(放 Button 引导用户创建首条数据)。 |
|
|
32
|
+
| `size` | `'sm' \| 'default'` | `"default"` | – | 尺寸 — `sm` 用于卡片内 / 表格空态;`default` 用于整区。 |
|
|
33
|
+
<!-- auto:props:end -->
|
|
34
|
+
|
|
35
|
+
<!-- auto:deps:begin -->
|
|
36
|
+
### 同库依赖
|
|
37
|
+
|
|
38
|
+
> `teamix-evo ui add empty` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
39
|
+
|
|
40
|
+
| Entry | 类型 | 描述 |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
43
|
+
|
|
44
|
+
### npm 依赖
|
|
45
|
+
|
|
46
|
+
> 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pnpm add lucide-react@^0.460.0
|
|
50
|
+
```
|
|
51
|
+
<!-- auto:deps:end -->
|
|
52
|
+
|
|
53
|
+
## AI 生成纪律
|
|
54
|
+
|
|
55
|
+
- **`description` 引导而非陈述**:"暂无数据" 是最朴素的;有创建动作时改成"还没有项目,点击下方新建"
|
|
56
|
+
- **`extra` 必有时配主操作**:Empty 不只是装饰,要给"接下来做什么"
|
|
57
|
+
- **`size="sm"` 在卡片 / 表格内**:不要让 Empty 撑爆容器
|
|
58
|
+
- **不嵌套 Empty**:嵌套占位是反模式
|
|
59
|
+
|
|
60
|
+
## Examples
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { Empty } from '@/components/ui/empty';
|
|
64
|
+
import { Button } from '@/components/ui/button';
|
|
65
|
+
import { Plus } from 'lucide-react';
|
|
66
|
+
|
|
67
|
+
// 基础
|
|
68
|
+
<Empty />
|
|
69
|
+
|
|
70
|
+
// 带描述 + 操作
|
|
71
|
+
<Empty
|
|
72
|
+
description="还没有项目"
|
|
73
|
+
extra={<Button icon={<Plus />}>创建项目</Button>}
|
|
74
|
+
/>
|
|
75
|
+
|
|
76
|
+
// 表格内空态
|
|
77
|
+
<Empty size="sm" description="无匹配的搜索结果" />
|
|
78
|
+
|
|
79
|
+
// 自定义图标
|
|
80
|
+
<Empty image={<Mailbox className="size-16" />} description="收件箱空空" />
|
|
81
|
+
```
|