@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,37 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar — shadcn 完整版 25 primitives.
|
|
3
|
+
*
|
|
4
|
+
* **arbitrary value 豁免说明**:本组件少量使用 Tailwind arbitrary value
|
|
5
|
+
* (`w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]` 等),原因:
|
|
6
|
+
*
|
|
7
|
+
* 1. Sidebar 折叠态需要在 `group-data-[collapsible=icon]` 选择器下响应父级 data
|
|
8
|
+
* 属性 — 这只能通过 Tailwind selector 实现,无法 inline `style` 替代;
|
|
9
|
+
* 2. floating/inset variant 的 gap/container 宽度是 `--sidebar-width-icon + p-2`
|
|
10
|
+
* 的合成值,Tailwind 标尺无法直接表达;
|
|
11
|
+
* 3. MenuButton outline variant 用 `box-shadow: 0 0 0 1px ...` 模拟 1px 边框
|
|
12
|
+
* 避免占用尺寸,这是 shadcn outline 风格的核心实现技术。
|
|
13
|
+
*
|
|
14
|
+
* 这些 arbitrary value 是 Sidebar 复合组件的结构性必要,与 ADR 0008 视觉规则
|
|
15
|
+
* 的本意(避免任意像素值/任意色值)相符 — 此处的值都是 token 引用,不是
|
|
16
|
+
* 硬编码像素或色值。本文件单独豁免 `teamix-evo/no-arbitrary-tw-value`。
|
|
17
|
+
*/
|
|
18
|
+
/* eslint-disable teamix-evo/no-arbitrary-tw-value */
|
|
1
19
|
import * as React from 'react';
|
|
2
20
|
import { Slot } from '@radix-ui/react-slot';
|
|
3
21
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
4
22
|
import { PanelLeft } from 'lucide-react';
|
|
5
23
|
|
|
6
24
|
import { cn } from '@/utils/cn';
|
|
25
|
+
import { useIsMobile } from '@/hooks/use-mobile';
|
|
7
26
|
import { Button } from '@/components/button/button';
|
|
27
|
+
import { Input } from '@/components/input/input';
|
|
8
28
|
import { Separator } from '@/components/separator/separator';
|
|
29
|
+
import {
|
|
30
|
+
Sheet,
|
|
31
|
+
SheetContent,
|
|
32
|
+
SheetDescription,
|
|
33
|
+
SheetHeader,
|
|
34
|
+
SheetTitle,
|
|
35
|
+
} from '@/components/sheet/sheet';
|
|
36
|
+
import { Skeleton } from '@/components/skeleton/skeleton';
|
|
37
|
+
import {
|
|
38
|
+
TooltipContent,
|
|
39
|
+
TooltipProvider,
|
|
40
|
+
TooltipRoot,
|
|
41
|
+
TooltipTrigger,
|
|
42
|
+
} from '@/components/tooltip/tooltip';
|
|
9
43
|
|
|
10
|
-
|
|
44
|
+
const SIDEBAR_COOKIE_NAME = 'sidebar_state';
|
|
45
|
+
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
|
46
|
+
const SIDEBAR_WIDTH = '16rem';
|
|
47
|
+
const SIDEBAR_WIDTH_MOBILE = '18rem';
|
|
48
|
+
const SIDEBAR_WIDTH_ICON = '3rem';
|
|
49
|
+
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
|
|
11
50
|
|
|
12
51
|
interface SidebarContextValue {
|
|
52
|
+
state: 'expanded' | 'collapsed';
|
|
13
53
|
open: boolean;
|
|
14
54
|
setOpen: (open: boolean) => void;
|
|
15
|
-
|
|
55
|
+
openMobile: boolean;
|
|
56
|
+
setOpenMobile: (open: boolean) => void;
|
|
57
|
+
isMobile: boolean;
|
|
58
|
+
toggleSidebar: () => void;
|
|
16
59
|
}
|
|
17
60
|
|
|
18
61
|
const SidebarContext = React.createContext<SidebarContextValue | null>(null);
|
|
19
62
|
|
|
20
|
-
export function useSidebar() {
|
|
63
|
+
export function useSidebar(): SidebarContextValue {
|
|
21
64
|
const ctx = React.useContext(SidebarContext);
|
|
22
65
|
if (!ctx) {
|
|
23
|
-
throw new Error('useSidebar must be used within
|
|
66
|
+
throw new Error('useSidebar must be used within a SidebarProvider.');
|
|
24
67
|
}
|
|
25
68
|
return ctx;
|
|
26
69
|
}
|
|
27
70
|
|
|
71
|
+
// ─── Provider ──────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
28
73
|
export interface SidebarProviderProps
|
|
29
74
|
extends React.HTMLAttributes<HTMLDivElement> {
|
|
30
|
-
/** 受控 open 状态。 */
|
|
31
|
-
open?: boolean;
|
|
32
|
-
/** uncontrolled 初始状态。 @default true */
|
|
33
75
|
defaultOpen?: boolean;
|
|
34
|
-
|
|
76
|
+
open?: boolean;
|
|
35
77
|
onOpenChange?: (open: boolean) => void;
|
|
36
78
|
}
|
|
37
79
|
|
|
@@ -40,129 +82,308 @@ const SidebarProvider = React.forwardRef<HTMLDivElement, SidebarProviderProps>(
|
|
|
40
82
|
{
|
|
41
83
|
defaultOpen = true,
|
|
42
84
|
open: openProp,
|
|
43
|
-
onOpenChange,
|
|
85
|
+
onOpenChange: setOpenProp,
|
|
44
86
|
className,
|
|
87
|
+
style,
|
|
45
88
|
children,
|
|
46
89
|
...props
|
|
47
90
|
},
|
|
48
91
|
ref,
|
|
49
92
|
) => {
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const open = isControlled ? (openProp as boolean) : internal;
|
|
93
|
+
const isMobile = useIsMobile();
|
|
94
|
+
const [openMobile, setOpenMobile] = React.useState(false);
|
|
53
95
|
|
|
96
|
+
const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
|
|
97
|
+
const open = openProp ?? internalOpen;
|
|
54
98
|
const setOpen = React.useCallback(
|
|
55
|
-
(
|
|
56
|
-
|
|
57
|
-
|
|
99
|
+
(value: boolean | ((value: boolean) => boolean)) => {
|
|
100
|
+
const openState = typeof value === 'function' ? value(open) : value;
|
|
101
|
+
if (setOpenProp) {
|
|
102
|
+
setOpenProp(openState);
|
|
103
|
+
} else {
|
|
104
|
+
setInternalOpen(openState);
|
|
105
|
+
}
|
|
106
|
+
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
|
|
58
107
|
},
|
|
59
|
-
[
|
|
108
|
+
[setOpenProp, open],
|
|
60
109
|
);
|
|
61
|
-
const toggle = React.useCallback(() => setOpen(!open), [open, setOpen]);
|
|
62
110
|
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
111
|
+
const toggleSidebar = React.useCallback(() => {
|
|
112
|
+
return isMobile
|
|
113
|
+
? setOpenMobile((current) => !current)
|
|
114
|
+
: setOpen((current) => !current);
|
|
115
|
+
}, [isMobile, setOpen, setOpenMobile]);
|
|
116
|
+
|
|
117
|
+
React.useEffect(() => {
|
|
118
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
119
|
+
if (
|
|
120
|
+
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
|
|
121
|
+
(event.metaKey || event.ctrlKey)
|
|
122
|
+
) {
|
|
123
|
+
event.preventDefault();
|
|
124
|
+
toggleSidebar();
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
128
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
129
|
+
}, [toggleSidebar]);
|
|
130
|
+
|
|
131
|
+
const state: 'expanded' | 'collapsed' = open ? 'expanded' : 'collapsed';
|
|
132
|
+
|
|
133
|
+
const contextValue = React.useMemo<SidebarContextValue>(
|
|
134
|
+
() => ({
|
|
135
|
+
state,
|
|
136
|
+
open,
|
|
137
|
+
setOpen,
|
|
138
|
+
isMobile,
|
|
139
|
+
openMobile,
|
|
140
|
+
setOpenMobile,
|
|
141
|
+
toggleSidebar,
|
|
142
|
+
}),
|
|
143
|
+
[
|
|
144
|
+
state,
|
|
145
|
+
open,
|
|
146
|
+
setOpen,
|
|
147
|
+
isMobile,
|
|
148
|
+
openMobile,
|
|
149
|
+
setOpenMobile,
|
|
150
|
+
toggleSidebar,
|
|
151
|
+
],
|
|
66
152
|
);
|
|
67
153
|
|
|
68
154
|
return (
|
|
69
|
-
<SidebarContext.Provider value={
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
155
|
+
<SidebarContext.Provider value={contextValue}>
|
|
156
|
+
<TooltipProvider delayDuration={0}>
|
|
157
|
+
<div
|
|
158
|
+
ref={ref}
|
|
159
|
+
data-slot="sidebar-wrapper"
|
|
160
|
+
style={
|
|
161
|
+
{
|
|
162
|
+
'--sidebar-width': SIDEBAR_WIDTH,
|
|
163
|
+
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
|
|
164
|
+
...style,
|
|
165
|
+
} as React.CSSProperties
|
|
166
|
+
}
|
|
167
|
+
className={cn(
|
|
168
|
+
'group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full',
|
|
169
|
+
className,
|
|
170
|
+
)}
|
|
171
|
+
{...props}
|
|
172
|
+
>
|
|
173
|
+
{children}
|
|
174
|
+
</div>
|
|
175
|
+
</TooltipProvider>
|
|
81
176
|
</SidebarContext.Provider>
|
|
82
177
|
);
|
|
83
178
|
},
|
|
84
179
|
);
|
|
85
180
|
SidebarProvider.displayName = 'SidebarProvider';
|
|
86
181
|
|
|
87
|
-
// ─── Main Sidebar container
|
|
182
|
+
// ─── Main Sidebar container ────────────────────────────────────────────────
|
|
88
183
|
|
|
89
|
-
|
|
90
|
-
'flex h-svh shrink-0 flex-col border-r bg-background transition-[width] duration-200 ease-linear',
|
|
91
|
-
{
|
|
92
|
-
variants: {
|
|
93
|
-
side: {
|
|
94
|
-
left: 'border-r',
|
|
95
|
-
right: 'border-l border-r-0 order-last',
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
defaultVariants: { side: 'left' },
|
|
99
|
-
},
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
export interface SidebarProps
|
|
103
|
-
extends React.HTMLAttributes<HTMLElement>,
|
|
104
|
-
VariantProps<typeof sidebarVariants> {
|
|
105
|
-
/**
|
|
106
|
-
* 侧边方向。
|
|
107
|
-
* @default "left"
|
|
108
|
-
*/
|
|
184
|
+
export interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
109
185
|
side?: 'left' | 'right';
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
* @default "icon"
|
|
113
|
-
*/
|
|
114
|
-
collapsible?: 'icon' | 'offcanvas' | 'none';
|
|
115
|
-
/** 展开宽度。 @default "16rem" */
|
|
116
|
-
width?: string;
|
|
117
|
-
/** 折叠后宽度(`collapsible="icon"` 时生效)。 @default "3rem" */
|
|
118
|
-
collapsedWidth?: string;
|
|
186
|
+
variant?: 'sidebar' | 'floating' | 'inset';
|
|
187
|
+
collapsible?: 'offcanvas' | 'icon' | 'none';
|
|
119
188
|
}
|
|
120
189
|
|
|
121
|
-
const Sidebar = React.forwardRef<
|
|
190
|
+
const Sidebar = React.forwardRef<HTMLDivElement, SidebarProps>(
|
|
122
191
|
(
|
|
123
192
|
{
|
|
124
193
|
side = 'left',
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
collapsedWidth = '3rem',
|
|
194
|
+
variant = 'sidebar',
|
|
195
|
+
collapsible = 'offcanvas',
|
|
128
196
|
className,
|
|
129
|
-
style,
|
|
130
197
|
children,
|
|
131
198
|
...props
|
|
132
199
|
},
|
|
133
200
|
ref,
|
|
134
201
|
) => {
|
|
135
|
-
const {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
202
|
+
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
|
|
203
|
+
|
|
204
|
+
if (collapsible === 'none') {
|
|
205
|
+
return (
|
|
206
|
+
<div
|
|
207
|
+
ref={ref}
|
|
208
|
+
data-slot="sidebar"
|
|
209
|
+
className={cn(
|
|
210
|
+
'bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col',
|
|
211
|
+
className,
|
|
212
|
+
)}
|
|
213
|
+
{...props}
|
|
214
|
+
>
|
|
215
|
+
{children}
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (isMobile) {
|
|
221
|
+
return (
|
|
222
|
+
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
|
|
223
|
+
<SheetContent
|
|
224
|
+
data-sidebar="sidebar"
|
|
225
|
+
data-slot="sidebar"
|
|
226
|
+
data-mobile="true"
|
|
227
|
+
className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
|
|
228
|
+
style={
|
|
229
|
+
{
|
|
230
|
+
'--sidebar-width': SIDEBAR_WIDTH_MOBILE,
|
|
231
|
+
} as React.CSSProperties
|
|
232
|
+
}
|
|
233
|
+
side={side}
|
|
234
|
+
>
|
|
235
|
+
<SheetHeader className="sr-only">
|
|
236
|
+
<SheetTitle>Sidebar</SheetTitle>
|
|
237
|
+
<SheetDescription>Displays the mobile sidebar.</SheetDescription>
|
|
238
|
+
</SheetHeader>
|
|
239
|
+
<div className="flex h-full w-full flex-col">{children}</div>
|
|
240
|
+
</SheetContent>
|
|
241
|
+
</Sheet>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
144
245
|
return (
|
|
145
|
-
<
|
|
246
|
+
<div
|
|
146
247
|
ref={ref}
|
|
147
|
-
|
|
148
|
-
data-
|
|
248
|
+
className="group peer text-sidebar-foreground hidden md:block"
|
|
249
|
+
data-state={state}
|
|
250
|
+
data-collapsible={state === 'collapsed' ? collapsible : ''}
|
|
251
|
+
data-variant={variant}
|
|
149
252
|
data-side={side}
|
|
150
|
-
|
|
151
|
-
className={cn(
|
|
152
|
-
sidebarVariants({ side }),
|
|
153
|
-
collapsible === 'offcanvas' && !open && 'overflow-hidden',
|
|
154
|
-
className,
|
|
155
|
-
)}
|
|
156
|
-
{...props}
|
|
253
|
+
data-slot="sidebar"
|
|
157
254
|
>
|
|
158
|
-
{
|
|
159
|
-
|
|
255
|
+
{/* sidebar gap on desktop */}
|
|
256
|
+
<div
|
|
257
|
+
data-slot="sidebar-gap"
|
|
258
|
+
className={cn(
|
|
259
|
+
'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',
|
|
260
|
+
'group-data-[collapsible=offcanvas]:w-0',
|
|
261
|
+
'group-data-[side=right]:rotate-180',
|
|
262
|
+
variant === 'floating' || variant === 'inset'
|
|
263
|
+
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'
|
|
264
|
+
: 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)',
|
|
265
|
+
)}
|
|
266
|
+
/>
|
|
267
|
+
<div
|
|
268
|
+
data-slot="sidebar-container"
|
|
269
|
+
data-side={side}
|
|
270
|
+
className={cn(
|
|
271
|
+
'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex',
|
|
272
|
+
'data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]',
|
|
273
|
+
'data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
|
|
274
|
+
variant === 'floating' || variant === 'inset'
|
|
275
|
+
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
|
|
276
|
+
: 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l border-sidebar-border',
|
|
277
|
+
className,
|
|
278
|
+
)}
|
|
279
|
+
{...props}
|
|
280
|
+
>
|
|
281
|
+
<div
|
|
282
|
+
data-sidebar="sidebar"
|
|
283
|
+
data-slot="sidebar-inner"
|
|
284
|
+
className="bg-sidebar group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:ring-sidebar-border flex size-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
|
|
285
|
+
>
|
|
286
|
+
{children}
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
160
290
|
);
|
|
161
291
|
},
|
|
162
292
|
);
|
|
163
293
|
Sidebar.displayName = 'Sidebar';
|
|
164
294
|
|
|
165
|
-
// ───
|
|
295
|
+
// ─── Trigger / Rail / Inset / Input ────────────────────────────────────────
|
|
296
|
+
|
|
297
|
+
const SidebarTrigger = React.forwardRef<
|
|
298
|
+
HTMLButtonElement,
|
|
299
|
+
React.ComponentProps<typeof Button>
|
|
300
|
+
>(({ className, onClick, ...props }, ref) => {
|
|
301
|
+
const { toggleSidebar } = useSidebar();
|
|
302
|
+
return (
|
|
303
|
+
<Button
|
|
304
|
+
ref={ref}
|
|
305
|
+
data-sidebar="trigger"
|
|
306
|
+
data-slot="sidebar-trigger"
|
|
307
|
+
variant="ghost"
|
|
308
|
+
size="icon"
|
|
309
|
+
className={cn('size-7', className)}
|
|
310
|
+
onClick={(event) => {
|
|
311
|
+
onClick?.(event);
|
|
312
|
+
toggleSidebar();
|
|
313
|
+
}}
|
|
314
|
+
aria-label="Toggle Sidebar"
|
|
315
|
+
{...props}
|
|
316
|
+
>
|
|
317
|
+
<PanelLeft />
|
|
318
|
+
<span className="sr-only">Toggle Sidebar</span>
|
|
319
|
+
</Button>
|
|
320
|
+
);
|
|
321
|
+
});
|
|
322
|
+
SidebarTrigger.displayName = 'SidebarTrigger';
|
|
323
|
+
|
|
324
|
+
const SidebarRail = React.forwardRef<
|
|
325
|
+
HTMLButtonElement,
|
|
326
|
+
React.ButtonHTMLAttributes<HTMLButtonElement>
|
|
327
|
+
>(({ className, ...props }, ref) => {
|
|
328
|
+
const { toggleSidebar } = useSidebar();
|
|
329
|
+
return (
|
|
330
|
+
<button
|
|
331
|
+
ref={ref}
|
|
332
|
+
type="button"
|
|
333
|
+
data-sidebar="rail"
|
|
334
|
+
data-slot="sidebar-rail"
|
|
335
|
+
aria-label="Toggle Sidebar"
|
|
336
|
+
tabIndex={-1}
|
|
337
|
+
onClick={toggleSidebar}
|
|
338
|
+
title="Toggle Sidebar"
|
|
339
|
+
className={cn(
|
|
340
|
+
'hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex',
|
|
341
|
+
'after:absolute after:inset-y-0 after:start-1/2 after:w-0.5',
|
|
342
|
+
'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',
|
|
343
|
+
'[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
|
|
344
|
+
'group-data-[collapsible=offcanvas]:hover:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full',
|
|
345
|
+
'[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
|
|
346
|
+
'[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
|
|
347
|
+
className,
|
|
348
|
+
)}
|
|
349
|
+
{...props}
|
|
350
|
+
/>
|
|
351
|
+
);
|
|
352
|
+
});
|
|
353
|
+
SidebarRail.displayName = 'SidebarRail';
|
|
354
|
+
|
|
355
|
+
const SidebarInset = React.forwardRef<
|
|
356
|
+
HTMLElement,
|
|
357
|
+
React.HTMLAttributes<HTMLElement>
|
|
358
|
+
>(({ className, ...props }, ref) => (
|
|
359
|
+
<main
|
|
360
|
+
ref={ref}
|
|
361
|
+
data-slot="sidebar-inset"
|
|
362
|
+
className={cn(
|
|
363
|
+
'bg-background relative flex w-full flex-1 flex-col',
|
|
364
|
+
'md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2',
|
|
365
|
+
className,
|
|
366
|
+
)}
|
|
367
|
+
{...(props as React.HTMLAttributes<HTMLElement>)}
|
|
368
|
+
/>
|
|
369
|
+
));
|
|
370
|
+
SidebarInset.displayName = 'SidebarInset';
|
|
371
|
+
|
|
372
|
+
const SidebarInput = React.forwardRef<
|
|
373
|
+
HTMLInputElement,
|
|
374
|
+
React.ComponentProps<typeof Input>
|
|
375
|
+
>(({ className, ...props }, ref) => (
|
|
376
|
+
<Input
|
|
377
|
+
ref={ref}
|
|
378
|
+
data-slot="sidebar-input"
|
|
379
|
+
data-sidebar="input"
|
|
380
|
+
className={cn('bg-background h-8 w-full shadow-none', className)}
|
|
381
|
+
{...props}
|
|
382
|
+
/>
|
|
383
|
+
));
|
|
384
|
+
SidebarInput.displayName = 'SidebarInput';
|
|
385
|
+
|
|
386
|
+
// ─── Header / Footer / Separator / Content ──────────────────────────────────
|
|
166
387
|
|
|
167
388
|
const SidebarHeader = React.forwardRef<
|
|
168
389
|
HTMLDivElement,
|
|
@@ -170,6 +391,8 @@ const SidebarHeader = React.forwardRef<
|
|
|
170
391
|
>(({ className, ...props }, ref) => (
|
|
171
392
|
<div
|
|
172
393
|
ref={ref}
|
|
394
|
+
data-slot="sidebar-header"
|
|
395
|
+
data-sidebar="header"
|
|
173
396
|
className={cn('flex flex-col gap-2 p-2', className)}
|
|
174
397
|
{...props}
|
|
175
398
|
/>
|
|
@@ -182,7 +405,9 @@ const SidebarFooter = React.forwardRef<
|
|
|
182
405
|
>(({ className, ...props }, ref) => (
|
|
183
406
|
<div
|
|
184
407
|
ref={ref}
|
|
185
|
-
|
|
408
|
+
data-slot="sidebar-footer"
|
|
409
|
+
data-sidebar="footer"
|
|
410
|
+
className={cn('flex flex-col gap-2 p-2', className)}
|
|
186
411
|
{...props}
|
|
187
412
|
/>
|
|
188
413
|
));
|
|
@@ -194,7 +419,9 @@ const SidebarSeparator = React.forwardRef<
|
|
|
194
419
|
>(({ className, ...props }, ref) => (
|
|
195
420
|
<Separator
|
|
196
421
|
ref={ref}
|
|
197
|
-
|
|
422
|
+
data-slot="sidebar-separator"
|
|
423
|
+
data-sidebar="separator"
|
|
424
|
+
className={cn('bg-sidebar-border mx-2 w-auto', className)}
|
|
198
425
|
{...props}
|
|
199
426
|
/>
|
|
200
427
|
));
|
|
@@ -206,8 +433,10 @@ const SidebarContent = React.forwardRef<
|
|
|
206
433
|
>(({ className, ...props }, ref) => (
|
|
207
434
|
<div
|
|
208
435
|
ref={ref}
|
|
436
|
+
data-slot="sidebar-content"
|
|
437
|
+
data-sidebar="content"
|
|
209
438
|
className={cn(
|
|
210
|
-
'flex min-h-0 flex-1 flex-col gap-
|
|
439
|
+
'no-scrollbar flex min-h-0 flex-1 flex-col gap-0 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
|
|
211
440
|
className,
|
|
212
441
|
)}
|
|
213
442
|
{...props}
|
|
@@ -215,29 +444,39 @@ const SidebarContent = React.forwardRef<
|
|
|
215
444
|
));
|
|
216
445
|
SidebarContent.displayName = 'SidebarContent';
|
|
217
446
|
|
|
447
|
+
// ─── Group / GroupLabel / GroupAction / GroupContent ────────────────────────
|
|
448
|
+
|
|
218
449
|
const SidebarGroup = React.forwardRef<
|
|
219
450
|
HTMLDivElement,
|
|
220
451
|
React.HTMLAttributes<HTMLDivElement>
|
|
221
452
|
>(({ className, ...props }, ref) => (
|
|
222
453
|
<div
|
|
223
454
|
ref={ref}
|
|
224
|
-
|
|
455
|
+
data-slot="sidebar-group"
|
|
456
|
+
data-sidebar="group"
|
|
457
|
+
className={cn('relative flex w-full min-w-0 flex-col p-2', className)}
|
|
225
458
|
{...props}
|
|
226
459
|
/>
|
|
227
460
|
));
|
|
228
461
|
SidebarGroup.displayName = 'SidebarGroup';
|
|
229
462
|
|
|
463
|
+
export interface SidebarGroupLabelProps
|
|
464
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
465
|
+
asChild?: boolean;
|
|
466
|
+
}
|
|
467
|
+
|
|
230
468
|
const SidebarGroupLabel = React.forwardRef<
|
|
231
469
|
HTMLDivElement,
|
|
232
|
-
|
|
233
|
-
>(({ className, ...props }, ref) => {
|
|
234
|
-
const
|
|
470
|
+
SidebarGroupLabelProps
|
|
471
|
+
>(({ className, asChild = false, ...props }, ref) => {
|
|
472
|
+
const Comp = asChild ? Slot : 'div';
|
|
235
473
|
return (
|
|
236
|
-
<
|
|
474
|
+
<Comp
|
|
237
475
|
ref={ref}
|
|
476
|
+
data-slot="sidebar-group-label"
|
|
477
|
+
data-sidebar="group-label"
|
|
238
478
|
className={cn(
|
|
239
|
-
'flex h-8 shrink-0 items-center px-2 text-xs font-medium
|
|
240
|
-
!open && 'opacity-0',
|
|
479
|
+
'text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium transition-[margin,opacity] duration-200 ease-linear outline-hidden group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
|
241
480
|
className,
|
|
242
481
|
)}
|
|
243
482
|
{...props}
|
|
@@ -246,13 +485,57 @@ const SidebarGroupLabel = React.forwardRef<
|
|
|
246
485
|
});
|
|
247
486
|
SidebarGroupLabel.displayName = 'SidebarGroupLabel';
|
|
248
487
|
|
|
488
|
+
export interface SidebarGroupActionProps
|
|
489
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
490
|
+
asChild?: boolean;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const SidebarGroupAction = React.forwardRef<
|
|
494
|
+
HTMLButtonElement,
|
|
495
|
+
SidebarGroupActionProps
|
|
496
|
+
>(({ className, asChild = false, ...props }, ref) => {
|
|
497
|
+
const Comp = asChild ? Slot : 'button';
|
|
498
|
+
return (
|
|
499
|
+
<Comp
|
|
500
|
+
ref={ref}
|
|
501
|
+
data-slot="sidebar-group-action"
|
|
502
|
+
data-sidebar="group-action"
|
|
503
|
+
className={cn(
|
|
504
|
+
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 transition-transform outline-hidden focus-visible:ring-2 group-data-[collapsible=icon]:hidden [&>svg]:size-4 [&>svg]:shrink-0',
|
|
505
|
+
'after:absolute after:-inset-2 md:after:hidden',
|
|
506
|
+
className,
|
|
507
|
+
)}
|
|
508
|
+
{...props}
|
|
509
|
+
/>
|
|
510
|
+
);
|
|
511
|
+
});
|
|
512
|
+
SidebarGroupAction.displayName = 'SidebarGroupAction';
|
|
513
|
+
|
|
514
|
+
const SidebarGroupContent = React.forwardRef<
|
|
515
|
+
HTMLDivElement,
|
|
516
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
517
|
+
>(({ className, ...props }, ref) => (
|
|
518
|
+
<div
|
|
519
|
+
ref={ref}
|
|
520
|
+
data-slot="sidebar-group-content"
|
|
521
|
+
data-sidebar="group-content"
|
|
522
|
+
className={cn('w-full text-xs', className)}
|
|
523
|
+
{...props}
|
|
524
|
+
/>
|
|
525
|
+
));
|
|
526
|
+
SidebarGroupContent.displayName = 'SidebarGroupContent';
|
|
527
|
+
|
|
528
|
+
// ─── Menu / MenuItem / MenuButton ──────────────────────────────────────────
|
|
529
|
+
|
|
249
530
|
const SidebarMenu = React.forwardRef<
|
|
250
531
|
HTMLUListElement,
|
|
251
532
|
React.HTMLAttributes<HTMLUListElement>
|
|
252
533
|
>(({ className, ...props }, ref) => (
|
|
253
534
|
<ul
|
|
254
535
|
ref={ref}
|
|
255
|
-
|
|
536
|
+
data-slot="sidebar-menu"
|
|
537
|
+
data-sidebar="menu"
|
|
538
|
+
className={cn('flex w-full min-w-0 flex-col gap-0', className)}
|
|
256
539
|
{...props}
|
|
257
540
|
/>
|
|
258
541
|
));
|
|
@@ -262,90 +545,280 @@ const SidebarMenuItem = React.forwardRef<
|
|
|
262
545
|
HTMLLIElement,
|
|
263
546
|
React.HTMLAttributes<HTMLLIElement>
|
|
264
547
|
>(({ className, ...props }, ref) => (
|
|
265
|
-
<li
|
|
548
|
+
<li
|
|
549
|
+
ref={ref}
|
|
550
|
+
data-slot="sidebar-menu-item"
|
|
551
|
+
data-sidebar="menu-item"
|
|
552
|
+
className={cn('group/menu-item relative', className)}
|
|
553
|
+
{...props}
|
|
554
|
+
/>
|
|
266
555
|
));
|
|
267
556
|
SidebarMenuItem.displayName = 'SidebarMenuItem';
|
|
268
557
|
|
|
558
|
+
const sidebarMenuButtonVariants = cva(
|
|
559
|
+
'peer/menu-button group/menu-button ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-xs outline-hidden transition-[width,height,padding] focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&_svg]:size-4 [&_svg]:shrink-0',
|
|
560
|
+
{
|
|
561
|
+
variants: {
|
|
562
|
+
variant: {
|
|
563
|
+
default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
|
|
564
|
+
outline:
|
|
565
|
+
'bg-background hover:bg-sidebar-accent hover:text-sidebar-accent-foreground shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
|
|
566
|
+
},
|
|
567
|
+
size: {
|
|
568
|
+
default: 'h-8 text-xs',
|
|
569
|
+
sm: 'h-7 text-xs',
|
|
570
|
+
lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
defaultVariants: {
|
|
574
|
+
variant: 'default',
|
|
575
|
+
size: 'default',
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
);
|
|
579
|
+
|
|
269
580
|
export interface SidebarMenuButtonProps
|
|
270
|
-
extends React.ButtonHTMLAttributes<HTMLButtonElement
|
|
271
|
-
|
|
272
|
-
isActive?: boolean;
|
|
273
|
-
/** 用 Slot 渲染为子元素(配合 router Link)。 */
|
|
581
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
582
|
+
VariantProps<typeof sidebarMenuButtonVariants> {
|
|
274
583
|
asChild?: boolean;
|
|
584
|
+
isActive?: boolean;
|
|
585
|
+
tooltip?: string | React.ComponentProps<typeof TooltipContent>;
|
|
275
586
|
}
|
|
276
587
|
|
|
277
588
|
const SidebarMenuButton = React.forwardRef<
|
|
278
589
|
HTMLButtonElement,
|
|
279
590
|
SidebarMenuButtonProps
|
|
280
|
-
>(
|
|
591
|
+
>(
|
|
592
|
+
(
|
|
593
|
+
{
|
|
594
|
+
asChild = false,
|
|
595
|
+
isActive = false,
|
|
596
|
+
variant = 'default',
|
|
597
|
+
size = 'default',
|
|
598
|
+
tooltip,
|
|
599
|
+
className,
|
|
600
|
+
...props
|
|
601
|
+
},
|
|
602
|
+
ref,
|
|
603
|
+
) => {
|
|
604
|
+
const Comp = asChild ? Slot : 'button';
|
|
605
|
+
const { isMobile, state } = useSidebar();
|
|
606
|
+
|
|
607
|
+
const button = (
|
|
608
|
+
<Comp
|
|
609
|
+
ref={ref}
|
|
610
|
+
data-slot="sidebar-menu-button"
|
|
611
|
+
data-sidebar="menu-button"
|
|
612
|
+
data-size={size}
|
|
613
|
+
data-active={isActive ? 'true' : undefined}
|
|
614
|
+
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
|
|
615
|
+
{...props}
|
|
616
|
+
/>
|
|
617
|
+
);
|
|
618
|
+
|
|
619
|
+
if (!tooltip) {
|
|
620
|
+
return button;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const tooltipProps =
|
|
624
|
+
typeof tooltip === 'string' ? { children: tooltip } : tooltip;
|
|
625
|
+
|
|
626
|
+
return (
|
|
627
|
+
<TooltipRoot>
|
|
628
|
+
<TooltipTrigger asChild>{button}</TooltipTrigger>
|
|
629
|
+
<TooltipContent
|
|
630
|
+
side="right"
|
|
631
|
+
align="center"
|
|
632
|
+
hidden={state !== 'collapsed' || isMobile}
|
|
633
|
+
{...tooltipProps}
|
|
634
|
+
/>
|
|
635
|
+
</TooltipRoot>
|
|
636
|
+
);
|
|
637
|
+
},
|
|
638
|
+
);
|
|
639
|
+
SidebarMenuButton.displayName = 'SidebarMenuButton';
|
|
640
|
+
|
|
641
|
+
// ─── MenuAction / MenuBadge / MenuSkeleton ─────────────────────────────────
|
|
642
|
+
|
|
643
|
+
export interface SidebarMenuActionProps
|
|
644
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
645
|
+
asChild?: boolean;
|
|
646
|
+
showOnHover?: boolean;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const SidebarMenuAction = React.forwardRef<
|
|
650
|
+
HTMLButtonElement,
|
|
651
|
+
SidebarMenuActionProps
|
|
652
|
+
>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
|
|
281
653
|
const Comp = asChild ? Slot : 'button';
|
|
282
654
|
return (
|
|
283
655
|
<Comp
|
|
284
656
|
ref={ref}
|
|
285
|
-
data-
|
|
657
|
+
data-slot="sidebar-menu-action"
|
|
658
|
+
data-sidebar="menu-action"
|
|
286
659
|
className={cn(
|
|
287
|
-
'
|
|
660
|
+
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 transition-transform outline-hidden focus-visible:ring-2 group-data-[collapsible=icon]:hidden [&>svg]:size-4 [&>svg]:shrink-0',
|
|
661
|
+
'after:absolute after:-inset-2 md:after:hidden',
|
|
662
|
+
'peer-data-[size=sm]/menu-button:top-1 peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5',
|
|
663
|
+
showOnHover &&
|
|
664
|
+
'peer-data-active/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 aria-expanded:opacity-100 md:opacity-0',
|
|
288
665
|
className,
|
|
289
666
|
)}
|
|
290
667
|
{...props}
|
|
291
668
|
/>
|
|
292
669
|
);
|
|
293
670
|
});
|
|
294
|
-
|
|
671
|
+
SidebarMenuAction.displayName = 'SidebarMenuAction';
|
|
295
672
|
|
|
296
|
-
|
|
673
|
+
const SidebarMenuBadge = React.forwardRef<
|
|
674
|
+
HTMLDivElement,
|
|
675
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
676
|
+
>(({ className, ...props }, ref) => (
|
|
677
|
+
<div
|
|
678
|
+
ref={ref}
|
|
679
|
+
data-slot="sidebar-menu-badge"
|
|
680
|
+
data-sidebar="menu-badge"
|
|
681
|
+
className={cn(
|
|
682
|
+
'text-sidebar-foreground peer-hover/menu-button:text-sidebar-accent-foreground peer-data-active/menu-button:text-sidebar-accent-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none group-data-[collapsible=icon]:hidden',
|
|
683
|
+
'peer-data-[size=sm]/menu-button:top-1 peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5',
|
|
684
|
+
className,
|
|
685
|
+
)}
|
|
686
|
+
{...props}
|
|
687
|
+
/>
|
|
688
|
+
));
|
|
689
|
+
SidebarMenuBadge.displayName = 'SidebarMenuBadge';
|
|
297
690
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
691
|
+
export interface SidebarMenuSkeletonProps
|
|
692
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
693
|
+
showIcon?: boolean;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const SidebarMenuSkeleton = React.forwardRef<
|
|
697
|
+
HTMLDivElement,
|
|
698
|
+
SidebarMenuSkeletonProps
|
|
699
|
+
>(({ className, showIcon = false, ...props }, ref) => {
|
|
700
|
+
// Stable but varied widths so a stack of skeletons doesn't look uniform.
|
|
701
|
+
const [width] = React.useState(
|
|
702
|
+
() => `${Math.floor(Math.random() * 40) + 50}%`,
|
|
703
|
+
);
|
|
303
704
|
return (
|
|
304
|
-
<
|
|
705
|
+
<div
|
|
305
706
|
ref={ref}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
className={cn('
|
|
309
|
-
onClick={(e) => {
|
|
310
|
-
onClick?.(e);
|
|
311
|
-
toggle();
|
|
312
|
-
}}
|
|
313
|
-
aria-label="Toggle Sidebar"
|
|
707
|
+
data-slot="sidebar-menu-skeleton"
|
|
708
|
+
data-sidebar="menu-skeleton"
|
|
709
|
+
className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}
|
|
314
710
|
{...props}
|
|
315
711
|
>
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
712
|
+
{showIcon && (
|
|
713
|
+
<Skeleton
|
|
714
|
+
className="size-4 rounded-md"
|
|
715
|
+
data-sidebar="menu-skeleton-icon"
|
|
716
|
+
/>
|
|
717
|
+
)}
|
|
718
|
+
<Skeleton
|
|
719
|
+
className="h-4 max-w-(--skeleton-width) flex-1"
|
|
720
|
+
data-sidebar="menu-skeleton-text"
|
|
721
|
+
style={
|
|
722
|
+
{
|
|
723
|
+
'--skeleton-width': width,
|
|
724
|
+
} as React.CSSProperties
|
|
725
|
+
}
|
|
726
|
+
/>
|
|
727
|
+
</div>
|
|
319
728
|
);
|
|
320
729
|
});
|
|
321
|
-
|
|
730
|
+
SidebarMenuSkeleton.displayName = 'SidebarMenuSkeleton';
|
|
322
731
|
|
|
323
|
-
// ───
|
|
732
|
+
// ─── MenuSub / MenuSubItem / MenuSubButton ─────────────────────────────────
|
|
324
733
|
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
React.HTMLAttributes<
|
|
734
|
+
const SidebarMenuSub = React.forwardRef<
|
|
735
|
+
HTMLUListElement,
|
|
736
|
+
React.HTMLAttributes<HTMLUListElement>
|
|
328
737
|
>(({ className, ...props }, ref) => (
|
|
329
|
-
<
|
|
738
|
+
<ul
|
|
330
739
|
ref={ref}
|
|
331
|
-
|
|
332
|
-
|
|
740
|
+
data-slot="sidebar-menu-sub"
|
|
741
|
+
data-sidebar="menu-sub"
|
|
742
|
+
className={cn(
|
|
743
|
+
'border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-l-border px-2.5 py-0.5 group-data-[collapsible=icon]:hidden',
|
|
744
|
+
className,
|
|
745
|
+
)}
|
|
746
|
+
{...props}
|
|
333
747
|
/>
|
|
334
748
|
));
|
|
335
|
-
|
|
749
|
+
SidebarMenuSub.displayName = 'SidebarMenuSub';
|
|
750
|
+
|
|
751
|
+
const SidebarMenuSubItem = React.forwardRef<
|
|
752
|
+
HTMLLIElement,
|
|
753
|
+
React.HTMLAttributes<HTMLLIElement>
|
|
754
|
+
>(({ className, ...props }, ref) => (
|
|
755
|
+
<li
|
|
756
|
+
ref={ref}
|
|
757
|
+
data-slot="sidebar-menu-sub-item"
|
|
758
|
+
data-sidebar="menu-sub-item"
|
|
759
|
+
className={cn('group/menu-sub-item relative', className)}
|
|
760
|
+
{...props}
|
|
761
|
+
/>
|
|
762
|
+
));
|
|
763
|
+
SidebarMenuSubItem.displayName = 'SidebarMenuSubItem';
|
|
764
|
+
|
|
765
|
+
export interface SidebarMenuSubButtonProps
|
|
766
|
+
extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
767
|
+
asChild?: boolean;
|
|
768
|
+
size?: 'sm' | 'md';
|
|
769
|
+
isActive?: boolean;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
const SidebarMenuSubButton = React.forwardRef<
|
|
773
|
+
HTMLAnchorElement,
|
|
774
|
+
SidebarMenuSubButtonProps
|
|
775
|
+
>(
|
|
776
|
+
(
|
|
777
|
+
{ className, asChild = false, size = 'md', isActive = false, ...props },
|
|
778
|
+
ref,
|
|
779
|
+
) => {
|
|
780
|
+
const Comp = asChild ? Slot : 'a';
|
|
781
|
+
return (
|
|
782
|
+
<Comp
|
|
783
|
+
ref={ref}
|
|
784
|
+
data-slot="sidebar-menu-sub-button"
|
|
785
|
+
data-sidebar="menu-sub-button"
|
|
786
|
+
data-size={size}
|
|
787
|
+
data-active={isActive ? 'true' : undefined}
|
|
788
|
+
className={cn(
|
|
789
|
+
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 group-data-[collapsible=icon]:hidden aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0',
|
|
790
|
+
'data-[size=sm]:text-xs data-[size=md]:text-xs',
|
|
791
|
+
className,
|
|
792
|
+
)}
|
|
793
|
+
{...props}
|
|
794
|
+
/>
|
|
795
|
+
);
|
|
796
|
+
},
|
|
797
|
+
);
|
|
798
|
+
SidebarMenuSubButton.displayName = 'SidebarMenuSubButton';
|
|
336
799
|
|
|
337
800
|
export {
|
|
338
|
-
SidebarProvider,
|
|
339
801
|
Sidebar,
|
|
340
|
-
SidebarHeader,
|
|
341
|
-
SidebarFooter,
|
|
342
|
-
SidebarSeparator,
|
|
343
802
|
SidebarContent,
|
|
803
|
+
SidebarFooter,
|
|
344
804
|
SidebarGroup,
|
|
805
|
+
SidebarGroupAction,
|
|
806
|
+
SidebarGroupContent,
|
|
345
807
|
SidebarGroupLabel,
|
|
808
|
+
SidebarHeader,
|
|
809
|
+
SidebarInput,
|
|
810
|
+
SidebarInset,
|
|
346
811
|
SidebarMenu,
|
|
347
|
-
|
|
812
|
+
SidebarMenuAction,
|
|
813
|
+
SidebarMenuBadge,
|
|
348
814
|
SidebarMenuButton,
|
|
815
|
+
SidebarMenuItem,
|
|
816
|
+
SidebarMenuSkeleton,
|
|
817
|
+
SidebarMenuSub,
|
|
818
|
+
SidebarMenuSubButton,
|
|
819
|
+
SidebarMenuSubItem,
|
|
820
|
+
SidebarProvider,
|
|
821
|
+
SidebarRail,
|
|
822
|
+
SidebarSeparator,
|
|
349
823
|
SidebarTrigger,
|
|
350
|
-
SidebarInset,
|
|
351
824
|
};
|