@mihcm/ui 0.14.1 → 0.15.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/dist/CheckboxGrid.native.d.ts.map +1 -1
- package/dist/CheckboxGrid.native.js +2 -1
- package/dist/CheckboxGrid.native.js.map +1 -1
- package/dist/Combobox.native.d.ts.map +1 -1
- package/dist/Combobox.native.js +2 -1
- package/dist/Combobox.native.js.map +1 -1
- package/dist/DataTable/column-filter.d.ts +8 -0
- package/dist/DataTable/column-filter.d.ts.map +1 -0
- package/dist/DataTable/column-filter.js +67 -0
- package/dist/DataTable/column-filter.js.map +1 -0
- package/dist/DataTable/column-header.d.ts +16 -0
- package/dist/DataTable/column-header.d.ts.map +1 -0
- package/dist/DataTable/column-header.js +11 -0
- package/dist/DataTable/column-header.js.map +1 -0
- package/dist/DataTable/column-visibility.d.ts +7 -0
- package/dist/DataTable/column-visibility.d.ts.map +1 -0
- package/dist/DataTable/column-visibility.js +35 -0
- package/dist/DataTable/column-visibility.js.map +1 -0
- package/dist/DataTable/index.d.ts +5 -0
- package/dist/DataTable/index.d.ts.map +1 -0
- package/dist/DataTable/index.js +5 -0
- package/dist/DataTable/index.js.map +1 -0
- package/dist/DataTable/pinning.d.ts +13 -0
- package/dist/DataTable/pinning.d.ts.map +1 -0
- package/dist/DataTable/pinning.js +29 -0
- package/dist/DataTable/pinning.js.map +1 -0
- package/dist/DataTable.d.ts +3 -7
- package/dist/DataTable.d.ts.map +1 -1
- package/dist/DataTable.js +7 -126
- package/dist/DataTable.js.map +1 -1
- package/dist/Dialog.native.d.ts +3 -1
- package/dist/Dialog.native.d.ts.map +1 -1
- package/dist/Dialog.native.js +2 -2
- package/dist/Dialog.native.js.map +1 -1
- package/dist/Form/building-blocks.d.ts +26 -0
- package/dist/Form/building-blocks.d.ts.map +1 -0
- package/dist/Form/building-blocks.js +29 -0
- package/dist/Form/building-blocks.js.map +1 -0
- package/dist/Form/fields-choice.d.ts +72 -0
- package/dist/Form/fields-choice.d.ts.map +1 -0
- package/dist/Form/fields-choice.js +69 -0
- package/dist/Form/fields-choice.js.map +1 -0
- package/dist/Form/fields-complex.d.ts +28 -0
- package/dist/Form/fields-complex.d.ts.map +1 -0
- package/dist/Form/fields-complex.js +38 -0
- package/dist/Form/fields-complex.js.map +1 -0
- package/dist/Form/fields-date.d.ts +46 -0
- package/dist/Form/fields-date.d.ts.map +1 -0
- package/dist/Form/fields-date.js +41 -0
- package/dist/Form/fields-date.js.map +1 -0
- package/dist/Form/fields-text.d.ts +47 -0
- package/dist/Form/fields-text.d.ts.map +1 -0
- package/dist/Form/fields-text.js +46 -0
- package/dist/Form/fields-text.js.map +1 -0
- package/dist/Form/fields-toggle.d.ts +24 -0
- package/dist/Form/fields-toggle.d.ts.map +1 -0
- package/dist/Form/fields-toggle.js +32 -0
- package/dist/Form/fields-toggle.js.map +1 -0
- package/dist/Form/helpers.d.ts +66 -0
- package/dist/Form/helpers.d.ts.map +1 -0
- package/dist/Form/helpers.js +44 -0
- package/dist/Form/helpers.js.map +1 -0
- package/dist/Form/types.d.ts +25 -0
- package/dist/Form/types.d.ts.map +1 -0
- package/dist/Form/types.js +8 -0
- package/dist/Form/types.js.map +1 -0
- package/dist/Form.d.ts +24 -298
- package/dist/Form.d.ts.map +1 -1
- package/dist/Form.js +30 -246
- package/dist/Form.js.map +1 -1
- package/dist/IconSidebar.d.ts +6 -46
- package/dist/IconSidebar.d.ts.map +1 -1
- package/dist/IconSidebar.js +6 -116
- package/dist/IconSidebar.js.map +1 -1
- package/dist/MainSidebar/back-button.d.ts +14 -0
- package/dist/MainSidebar/back-button.d.ts.map +1 -0
- package/dist/MainSidebar/back-button.js +14 -0
- package/dist/MainSidebar/back-button.js.map +1 -0
- package/dist/MainSidebar/breadcrumb.d.ts +10 -0
- package/dist/MainSidebar/breadcrumb.d.ts.map +1 -0
- package/dist/MainSidebar/breadcrumb.js +24 -0
- package/dist/MainSidebar/breadcrumb.js.map +1 -0
- package/dist/MainSidebar/columns.d.ts +3 -0
- package/dist/MainSidebar/columns.d.ts.map +1 -0
- package/dist/MainSidebar/columns.js +198 -0
- package/dist/MainSidebar/columns.js.map +1 -0
- package/dist/MainSidebar/command.d.ts +3 -0
- package/dist/MainSidebar/command.d.ts.map +1 -0
- package/dist/MainSidebar/command.js +193 -0
- package/dist/MainSidebar/command.js.map +1 -0
- package/dist/MainSidebar/drilldown.d.ts +3 -0
- package/dist/MainSidebar/drilldown.d.ts.map +1 -0
- package/dist/MainSidebar/drilldown.js +154 -0
- package/dist/MainSidebar/drilldown.js.map +1 -0
- package/dist/MainSidebar/expanded.d.ts +7 -0
- package/dist/MainSidebar/expanded.d.ts.map +1 -0
- package/dist/MainSidebar/expanded.js +102 -0
- package/dist/MainSidebar/expanded.js.map +1 -0
- package/dist/MainSidebar/floating.d.ts +3 -0
- package/dist/MainSidebar/floating.d.ts.map +1 -0
- package/dist/MainSidebar/floating.js +116 -0
- package/dist/MainSidebar/floating.js.map +1 -0
- package/dist/MainSidebar/helpers.d.ts +50 -0
- package/dist/MainSidebar/helpers.d.ts.map +1 -0
- package/dist/MainSidebar/helpers.js +150 -0
- package/dist/MainSidebar/helpers.js.map +1 -0
- package/dist/MainSidebar/hover.d.ts +3 -0
- package/dist/MainSidebar/hover.d.ts.map +1 -0
- package/dist/MainSidebar/hover.js +177 -0
- package/dist/MainSidebar/hover.js.map +1 -0
- package/dist/MainSidebar/index.d.ts +6 -0
- package/dist/MainSidebar/index.d.ts.map +1 -0
- package/dist/MainSidebar/index.js +108 -0
- package/dist/MainSidebar/index.js.map +1 -0
- package/dist/MainSidebar/mobile.d.ts +29 -0
- package/dist/MainSidebar/mobile.d.ts.map +1 -0
- package/dist/MainSidebar/mobile.js +38 -0
- package/dist/MainSidebar/mobile.js.map +1 -0
- package/dist/MainSidebar/motion.d.ts +23 -0
- package/dist/MainSidebar/motion.d.ts.map +1 -0
- package/dist/MainSidebar/motion.js +40 -0
- package/dist/MainSidebar/motion.js.map +1 -0
- package/dist/MainSidebar/rail.d.ts +24 -0
- package/dist/MainSidebar/rail.d.ts.map +1 -0
- package/dist/MainSidebar/rail.js +29 -0
- package/dist/MainSidebar/rail.js.map +1 -0
- package/dist/MainSidebar/search.d.ts +19 -0
- package/dist/MainSidebar/search.d.ts.map +1 -0
- package/dist/MainSidebar/search.js +33 -0
- package/dist/MainSidebar/search.js.map +1 -0
- package/dist/MainSidebar/types.d.ts +161 -0
- package/dist/MainSidebar/types.d.ts.map +1 -0
- package/dist/MainSidebar/types.js +2 -0
- package/dist/MainSidebar/types.js.map +1 -0
- package/dist/MainSidebar.d.ts +6 -1
- package/dist/MainSidebar.d.ts.map +1 -1
- package/dist/MainSidebar.js +6 -1
- package/dist/MainSidebar.js.map +1 -1
- package/dist/NavigationMenu.js +1 -1
- package/dist/NavigationMenu.js.map +1 -1
- package/dist/RichTextEditor/theme.d.ts +44 -0
- package/dist/RichTextEditor/theme.d.ts.map +1 -0
- package/dist/RichTextEditor/theme.js +41 -0
- package/dist/RichTextEditor/theme.js.map +1 -0
- package/dist/RichTextEditor/toolbar-icons.d.ts +21 -0
- package/dist/RichTextEditor/toolbar-icons.d.ts.map +1 -0
- package/dist/RichTextEditor/toolbar-icons.js +21 -0
- package/dist/RichTextEditor/toolbar-icons.js.map +1 -0
- package/dist/RichTextEditor/toolbar.d.ts +5 -0
- package/dist/RichTextEditor/toolbar.d.ts.map +1 -0
- package/dist/RichTextEditor/toolbar.js +116 -0
- package/dist/RichTextEditor/toolbar.js.map +1 -0
- package/dist/RichTextEditor.d.ts +16 -9
- package/dist/RichTextEditor.d.ts.map +1 -1
- package/dist/RichTextEditor.js +18 -164
- package/dist/RichTextEditor.js.map +1 -1
- package/dist/Select/content.d.ts +9 -0
- package/dist/Select/content.d.ts.map +1 -0
- package/dist/Select/content.js +80 -0
- package/dist/Select/content.js.map +1 -0
- package/dist/Select/context.d.ts +27 -0
- package/dist/Select/context.d.ts.map +1 -0
- package/dist/Select/context.js +35 -0
- package/dist/Select/context.js.map +1 -0
- package/dist/Select/item.d.ts +13 -0
- package/dist/Select/item.d.ts.map +1 -0
- package/dist/Select/item.js +39 -0
- package/dist/Select/item.js.map +1 -0
- package/dist/Select/parts.d.ts +14 -0
- package/dist/Select/parts.d.ts.map +1 -0
- package/dist/Select/parts.js +17 -0
- package/dist/Select/parts.js.map +1 -0
- package/dist/Select/react-select.d.ts +25 -0
- package/dist/Select/react-select.d.ts.map +1 -0
- package/dist/Select/react-select.js +66 -0
- package/dist/Select/react-select.js.map +1 -0
- package/dist/Select/root.d.ts +15 -0
- package/dist/Select/root.d.ts.map +1 -0
- package/dist/Select/root.js +41 -0
- package/dist/Select/root.js.map +1 -0
- package/dist/Select/trigger.d.ts +15 -0
- package/dist/Select/trigger.d.ts.map +1 -0
- package/dist/Select/trigger.js +61 -0
- package/dist/Select/trigger.js.map +1 -0
- package/dist/Select.d.ts +14 -62
- package/dist/Select.d.ts.map +1 -1
- package/dist/Select.js +14 -293
- package/dist/Select.js.map +1 -1
- package/dist/Sidebar/context.d.ts +28 -0
- package/dist/Sidebar/context.d.ts.map +1 -0
- package/dist/Sidebar/context.js +37 -0
- package/dist/Sidebar/context.js.map +1 -0
- package/dist/Sidebar/group.d.ts +13 -0
- package/dist/Sidebar/group.d.ts.map +1 -0
- package/dist/Sidebar/group.js +20 -0
- package/dist/Sidebar/group.js.map +1 -0
- package/dist/Sidebar/icons.d.ts +7 -0
- package/dist/Sidebar/icons.d.ts.map +1 -0
- package/dist/Sidebar/icons.js +12 -0
- package/dist/Sidebar/icons.js.map +1 -0
- package/dist/Sidebar/layout.d.ts +9 -0
- package/dist/Sidebar/layout.d.ts.map +1 -0
- package/dist/Sidebar/layout.js +21 -0
- package/dist/Sidebar/layout.js.map +1 -0
- package/dist/Sidebar/menu.d.ts +29 -0
- package/dist/Sidebar/menu.d.ts.map +1 -0
- package/dist/Sidebar/menu.js +55 -0
- package/dist/Sidebar/menu.js.map +1 -0
- package/dist/Sidebar/provider.d.ts +33 -0
- package/dist/Sidebar/provider.d.ts.map +1 -0
- package/dist/Sidebar/provider.js +110 -0
- package/dist/Sidebar/provider.js.map +1 -0
- package/dist/Sidebar/sidebar.d.ts +17 -0
- package/dist/Sidebar/sidebar.d.ts.map +1 -0
- package/dist/Sidebar/sidebar.js +51 -0
- package/dist/Sidebar/sidebar.js.map +1 -0
- package/dist/Sidebar/submenu.d.ts +13 -0
- package/dist/Sidebar/submenu.d.ts.map +1 -0
- package/dist/Sidebar/submenu.js +17 -0
- package/dist/Sidebar/submenu.js.map +1 -0
- package/dist/Sidebar/trigger.d.ts +9 -0
- package/dist/Sidebar/trigger.d.ts.map +1 -0
- package/dist/Sidebar/trigger.js +33 -0
- package/dist/Sidebar/trigger.js.map +1 -0
- package/dist/Sidebar.d.ts +14 -104
- package/dist/Sidebar.d.ts.map +1 -1
- package/dist/Sidebar.js +14 -300
- package/dist/Sidebar.js.map +1 -1
- package/dist/StatCard.d.ts +67 -9
- package/dist/StatCard.d.ts.map +1 -1
- package/dist/StatCard.js +111 -9
- package/dist/StatCard.js.map +1 -1
- package/dist/TransferList.native.d.ts.map +1 -1
- package/dist/TransferList.native.js +2 -1
- package/dist/TransferList.native.js.map +1 -1
- package/package.json +2 -2
- package/src/CheckboxGrid.native.tsx +2 -1
- package/src/Combobox.native.tsx +2 -1
- package/src/DataTable/column-filter.tsx +134 -0
- package/src/DataTable/column-header.tsx +67 -0
- package/src/DataTable/column-visibility.tsx +87 -0
- package/src/DataTable/index.ts +4 -0
- package/src/DataTable/pinning.ts +40 -0
- package/src/DataTable.tsx +14 -297
- package/src/Dialog.native.tsx +4 -2
- package/src/Form/building-blocks.tsx +97 -0
- package/src/Form/fields-choice.tsx +312 -0
- package/src/Form/fields-complex.tsx +195 -0
- package/src/Form/fields-date.tsx +195 -0
- package/src/Form/fields-text.tsx +218 -0
- package/src/Form/fields-toggle.tsx +123 -0
- package/src/Form/helpers.tsx +189 -0
- package/src/Form/types.ts +26 -0
- package/src/Form.tsx +91 -1308
- package/src/IconSidebar.tsx +20 -442
- package/src/MainSidebar/back-button.tsx +58 -0
- package/src/MainSidebar/breadcrumb.tsx +53 -0
- package/src/MainSidebar/columns.tsx +350 -0
- package/src/MainSidebar/command.tsx +404 -0
- package/src/MainSidebar/drilldown.tsx +373 -0
- package/src/MainSidebar/expanded.tsx +414 -0
- package/src/MainSidebar/floating.tsx +268 -0
- package/src/MainSidebar/helpers.ts +166 -0
- package/src/MainSidebar/hover.tsx +334 -0
- package/src/MainSidebar/index.tsx +191 -0
- package/src/MainSidebar/mobile.tsx +117 -0
- package/src/MainSidebar/motion.ts +64 -0
- package/src/MainSidebar/rail.tsx +137 -0
- package/src/MainSidebar/search.tsx +99 -0
- package/src/MainSidebar/types.ts +208 -0
- package/src/MainSidebar.tsx +15 -4
- package/src/NavigationMenu.tsx +1 -1
- package/src/RichTextEditor/theme.ts +43 -0
- package/src/RichTextEditor/toolbar-icons.tsx +40 -0
- package/src/RichTextEditor/toolbar.tsx +271 -0
- package/src/RichTextEditor.tsx +23 -371
- package/src/Select/content.tsx +111 -0
- package/src/Select/context.tsx +66 -0
- package/src/Select/item.tsx +97 -0
- package/src/Select/parts.tsx +43 -0
- package/src/Select/react-select.tsx +216 -0
- package/src/Select/root.tsx +75 -0
- package/src/Select/trigger.tsx +122 -0
- package/src/Select.tsx +34 -692
- package/src/Sidebar/context.tsx +72 -0
- package/src/Sidebar/group.tsx +69 -0
- package/src/Sidebar/icons.tsx +42 -0
- package/src/Sidebar/layout.tsx +64 -0
- package/src/Sidebar/menu.tsx +171 -0
- package/src/Sidebar/provider.tsx +224 -0
- package/src/Sidebar/sidebar.tsx +178 -0
- package/src/Sidebar/submenu.tsx +58 -0
- package/src/Sidebar/trigger.tsx +104 -0
- package/src/Sidebar.tsx +44 -927
- package/src/StatCard.tsx +365 -20
- package/src/TransferList.native.tsx +2 -1
- package/dist/TiptapEditor.d.ts +0 -24
- package/dist/TiptapEditor.d.ts.map +0 -1
- package/dist/TiptapEditor.js +0 -84
- package/dist/TiptapEditor.js.map +0 -1
package/src/Sidebar.tsx
CHANGED
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Sidebar (web-only — React DOM).
|
|
5
5
|
*
|
|
6
|
-
* A composable, collapsible sidebar with mobile sheet support,
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Composes: Button, Sheet, Separator, Skeleton, Tooltip, Input.
|
|
6
|
+
* A composable, collapsible sidebar with mobile sheet support, keyboard
|
|
7
|
+
* shortcut (Ctrl/Cmd+B), and cookie persistence. Composes Button, Sheet,
|
|
8
|
+
* Separator, Skeleton, Tooltip, Input.
|
|
10
9
|
*
|
|
11
10
|
* <SidebarProvider>
|
|
12
11
|
* <Sidebar>
|
|
@@ -28,929 +27,47 @@
|
|
|
28
27
|
* <SidebarInset>…main content…</SidebarInset>
|
|
29
28
|
* </SidebarProvider>
|
|
30
29
|
*
|
|
30
|
+
* Public barrel — implementation is split across `./Sidebar/*` so each
|
|
31
|
+
* file stays under the 400-line rule (CLAUDE.md §6).
|
|
32
|
+
*
|
|
31
33
|
* Wiki: docs/components/Sidebar.md
|
|
32
34
|
*/
|
|
33
|
-
import {
|
|
34
|
-
createContext,
|
|
35
|
-
forwardRef,
|
|
36
|
-
useCallback,
|
|
37
|
-
useContext,
|
|
38
|
-
useEffect,
|
|
39
|
-
useMemo,
|
|
40
|
-
useState,
|
|
41
|
-
type AnchorHTMLAttributes,
|
|
42
|
-
type ButtonHTMLAttributes,
|
|
43
|
-
type ComponentProps,
|
|
44
|
-
type HTMLAttributes,
|
|
45
|
-
} from 'react';
|
|
46
|
-
import { cva, type VariantProps } from 'class-variance-authority';
|
|
47
|
-
import { cn } from './internal/cn.js';
|
|
48
|
-
import { Button } from './Button.js';
|
|
49
|
-
import { Sheet, SheetContent } from './Sheet.js';
|
|
50
|
-
import { Separator } from './Separator.js';
|
|
51
|
-
import { Skeleton } from './Skeleton.js';
|
|
52
|
-
import { Tooltip } from './Tooltip.js';
|
|
53
|
-
import { Input } from './Input.js';
|
|
54
|
-
|
|
55
|
-
type SidebarSide = 'left' | 'right';
|
|
56
|
-
type SidebarVariant = 'sidebar' | 'floating' | 'inset';
|
|
57
|
-
type SidebarCollapsible = 'offcanvas' | 'icon' | 'none';
|
|
58
|
-
type SidebarPosition = 'contained' | 'fixed';
|
|
59
|
-
|
|
60
|
-
/* ── Inline icon (avoids a lucide-react dep on the ui package) ───── */
|
|
61
|
-
|
|
62
|
-
function PanelLeftIcon() {
|
|
63
|
-
return (
|
|
64
|
-
<svg
|
|
65
|
-
aria-hidden="true"
|
|
66
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
67
|
-
viewBox="0 0 24 24"
|
|
68
|
-
fill="none"
|
|
69
|
-
stroke="currentColor"
|
|
70
|
-
strokeWidth={2}
|
|
71
|
-
strokeLinecap="round"
|
|
72
|
-
strokeLinejoin="round"
|
|
73
|
-
className="h-4 w-4"
|
|
74
|
-
>
|
|
75
|
-
<rect width="18" height="18" x="3" y="3" rx="2" />
|
|
76
|
-
<path d="M9 3v18" />
|
|
77
|
-
</svg>
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function PanelRightIcon() {
|
|
82
|
-
return (
|
|
83
|
-
<svg
|
|
84
|
-
aria-hidden="true"
|
|
85
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
86
|
-
viewBox="0 0 24 24"
|
|
87
|
-
fill="none"
|
|
88
|
-
stroke="currentColor"
|
|
89
|
-
strokeWidth={2}
|
|
90
|
-
strokeLinecap="round"
|
|
91
|
-
strokeLinejoin="round"
|
|
92
|
-
className="h-4 w-4"
|
|
93
|
-
>
|
|
94
|
-
<rect width="18" height="18" x="3" y="3" rx="2" />
|
|
95
|
-
<path d="M15 3v18" />
|
|
96
|
-
</svg>
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/* ── Constants ─────────────────────────────────────────────────────── */
|
|
101
|
-
|
|
102
|
-
const SIDEBAR_COOKIE_NAME = 'sidebar_state';
|
|
103
|
-
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; // 7 days
|
|
104
|
-
const SIDEBAR_WIDTH = '16rem';
|
|
105
|
-
const SIDEBAR_WIDTH_ICON = '3rem';
|
|
106
|
-
const SIDEBAR_WIDTH_MOBILE = '18rem';
|
|
107
|
-
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
|
|
108
|
-
const SIDEBAR_MOBILE_BREAKPOINT = 768;
|
|
109
|
-
|
|
110
|
-
/* ── useIsMobile (inline) ──────────────────────────────────────────── */
|
|
111
|
-
|
|
112
|
-
function useIsMobile(breakpoint: number) {
|
|
113
|
-
const [isMobile, setIsMobile] = useState(false);
|
|
114
|
-
|
|
115
|
-
useEffect(() => {
|
|
116
|
-
if (typeof window.matchMedia !== 'function') {
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const mql = window.matchMedia(`(max-width: ${breakpoint}px)`);
|
|
121
|
-
const onChange = () => setIsMobile(mql.matches);
|
|
122
|
-
onChange();
|
|
123
|
-
mql.addEventListener('change', onChange);
|
|
124
|
-
return () => mql.removeEventListener('change', onChange);
|
|
125
|
-
}, [breakpoint]);
|
|
126
|
-
|
|
127
|
-
return isMobile;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/* ── Context ───────────────────────────────────────────────────────── */
|
|
131
|
-
|
|
132
|
-
interface SidebarContextValue {
|
|
133
|
-
state: 'expanded' | 'collapsed';
|
|
134
|
-
open: boolean;
|
|
135
|
-
setOpen: (open: boolean | ((prev: boolean) => boolean)) => void;
|
|
136
|
-
openMobile: boolean;
|
|
137
|
-
setOpenMobile: (open: boolean | ((prev: boolean) => boolean)) => void;
|
|
138
|
-
isMobile: boolean;
|
|
139
|
-
side: SidebarSide;
|
|
140
|
-
variant: SidebarVariant;
|
|
141
|
-
collapsible: SidebarCollapsible;
|
|
142
|
-
position: SidebarPosition;
|
|
143
|
-
toggleSidebar: () => void;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const SidebarContext = createContext<SidebarContextValue | null>(null);
|
|
147
|
-
|
|
148
|
-
export function useSidebar() {
|
|
149
|
-
const ctx = useContext(SidebarContext);
|
|
150
|
-
if (!ctx) {
|
|
151
|
-
throw new Error('useSidebar must be used within a <SidebarProvider>.');
|
|
152
|
-
}
|
|
153
|
-
return ctx;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/* ── SidebarProvider ───────────────────────────────────────────────── */
|
|
157
|
-
|
|
158
|
-
export interface SidebarProviderProps extends HTMLAttributes<HTMLDivElement> {
|
|
159
|
-
defaultOpen?: boolean;
|
|
160
|
-
open?: boolean;
|
|
161
|
-
onOpenChange?: (open: boolean) => void;
|
|
162
|
-
/** Initial mobile Sheet open state. Useful for examples and forced mobile previews. */
|
|
163
|
-
defaultOpenMobile?: boolean;
|
|
164
|
-
/** Controlled mobile Sheet open state. */
|
|
165
|
-
openMobile?: boolean;
|
|
166
|
-
/** Called when mobile Sheet open state changes. */
|
|
167
|
-
onOpenMobileChange?: (open: boolean) => void;
|
|
168
|
-
side?: SidebarSide;
|
|
169
|
-
variant?: SidebarVariant;
|
|
170
|
-
collapsible?: SidebarCollapsible;
|
|
171
|
-
position?: SidebarPosition;
|
|
172
|
-
/** Force mobile behavior for previews/tests. When omitted, matchMedia decides. */
|
|
173
|
-
mobile?: boolean;
|
|
174
|
-
/** Breakpoint in px where the sidebar switches to mobile Sheet mode. */
|
|
175
|
-
mobileBreakpoint?: number;
|
|
176
|
-
/** Expanded desktop width. */
|
|
177
|
-
width?: string;
|
|
178
|
-
/** Collapsed icon-rail width. */
|
|
179
|
-
iconWidth?: string;
|
|
180
|
-
/** Mobile Sheet width. */
|
|
181
|
-
mobileWidth?: string;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export const SidebarProvider = forwardRef<HTMLDivElement, SidebarProviderProps>(
|
|
185
|
-
function SidebarProvider(
|
|
186
|
-
{
|
|
187
|
-
defaultOpen = true,
|
|
188
|
-
open: openProp,
|
|
189
|
-
onOpenChange: setOpenProp,
|
|
190
|
-
defaultOpenMobile = false,
|
|
191
|
-
openMobile: openMobileProp,
|
|
192
|
-
onOpenMobileChange: setOpenMobileProp,
|
|
193
|
-
side = 'left',
|
|
194
|
-
variant = 'sidebar',
|
|
195
|
-
collapsible = 'offcanvas',
|
|
196
|
-
position = 'contained',
|
|
197
|
-
mobile,
|
|
198
|
-
mobileBreakpoint = SIDEBAR_MOBILE_BREAKPOINT,
|
|
199
|
-
width = SIDEBAR_WIDTH,
|
|
200
|
-
iconWidth = SIDEBAR_WIDTH_ICON,
|
|
201
|
-
mobileWidth = SIDEBAR_WIDTH_MOBILE,
|
|
202
|
-
className,
|
|
203
|
-
style,
|
|
204
|
-
children,
|
|
205
|
-
...props
|
|
206
|
-
},
|
|
207
|
-
ref,
|
|
208
|
-
) {
|
|
209
|
-
const detectedMobile = useIsMobile(mobileBreakpoint);
|
|
210
|
-
const isMobile = mobile ?? detectedMobile;
|
|
211
|
-
const [_openMobile, _setOpenMobile] = useState(defaultOpenMobile);
|
|
212
|
-
|
|
213
|
-
// Start from props so server and initial client render match.
|
|
214
|
-
const [_open, _setOpen] = useState(defaultOpen);
|
|
215
|
-
|
|
216
|
-
const open = openProp ?? _open;
|
|
217
|
-
const openMobile = openMobileProp ?? _openMobile;
|
|
218
|
-
|
|
219
|
-
useEffect(() => {
|
|
220
|
-
if (openProp !== undefined) return;
|
|
221
|
-
|
|
222
|
-
const cookie = document.cookie
|
|
223
|
-
.split('; ')
|
|
224
|
-
.find((row) => row.startsWith(`${SIDEBAR_COOKIE_NAME}=`));
|
|
225
|
-
if (!cookie) return;
|
|
226
|
-
|
|
227
|
-
const nextOpen = cookie.split('=')[1] === 'true';
|
|
228
|
-
let cancelled = false;
|
|
229
|
-
queueMicrotask(() => {
|
|
230
|
-
if (!cancelled) {
|
|
231
|
-
_setOpen(nextOpen);
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
return () => {
|
|
236
|
-
cancelled = true;
|
|
237
|
-
};
|
|
238
|
-
}, [openProp]);
|
|
239
|
-
|
|
240
|
-
const setOpen = useCallback(
|
|
241
|
-
(value: boolean | ((prev: boolean) => boolean)) => {
|
|
242
|
-
const next = typeof value === 'function' ? value(open) : value;
|
|
243
|
-
if (setOpenProp) {
|
|
244
|
-
setOpenProp(next);
|
|
245
|
-
} else {
|
|
246
|
-
_setOpen(next);
|
|
247
|
-
}
|
|
248
|
-
if (typeof document !== 'undefined') {
|
|
249
|
-
document.cookie = `${SIDEBAR_COOKIE_NAME}=${next}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
|
|
250
|
-
}
|
|
251
|
-
},
|
|
252
|
-
[setOpenProp, open],
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
const setOpenMobile = useCallback(
|
|
256
|
-
(value: boolean | ((prev: boolean) => boolean)) => {
|
|
257
|
-
const next = typeof value === 'function' ? value(openMobile) : value;
|
|
258
|
-
if (setOpenMobileProp) {
|
|
259
|
-
setOpenMobileProp(next);
|
|
260
|
-
} else {
|
|
261
|
-
_setOpenMobile(next);
|
|
262
|
-
}
|
|
263
|
-
},
|
|
264
|
-
[openMobile, setOpenMobileProp],
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
const toggleSidebar = useCallback(() => {
|
|
268
|
-
if (collapsible === 'none') return;
|
|
269
|
-
|
|
270
|
-
if (isMobile) {
|
|
271
|
-
setOpenMobile((prev) => !prev);
|
|
272
|
-
} else {
|
|
273
|
-
setOpen((prev) => !prev);
|
|
274
|
-
}
|
|
275
|
-
}, [collapsible, isMobile, setOpen]);
|
|
276
|
-
|
|
277
|
-
// Keyboard shortcut: Ctrl+B / Cmd+B
|
|
278
|
-
useEffect(() => {
|
|
279
|
-
const handleKeyDown = (e: KeyboardEvent) => {
|
|
280
|
-
if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) {
|
|
281
|
-
e.preventDefault();
|
|
282
|
-
toggleSidebar();
|
|
283
|
-
}
|
|
284
|
-
};
|
|
285
|
-
window.addEventListener('keydown', handleKeyDown);
|
|
286
|
-
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
287
|
-
}, [toggleSidebar]);
|
|
288
|
-
|
|
289
|
-
const state = open ? 'expanded' : 'collapsed';
|
|
290
|
-
|
|
291
|
-
const value = useMemo<SidebarContextValue>(
|
|
292
|
-
() => ({
|
|
293
|
-
state,
|
|
294
|
-
open,
|
|
295
|
-
setOpen,
|
|
296
|
-
isMobile,
|
|
297
|
-
openMobile,
|
|
298
|
-
setOpenMobile,
|
|
299
|
-
side,
|
|
300
|
-
variant,
|
|
301
|
-
collapsible,
|
|
302
|
-
position,
|
|
303
|
-
toggleSidebar,
|
|
304
|
-
}),
|
|
305
|
-
[
|
|
306
|
-
state,
|
|
307
|
-
open,
|
|
308
|
-
setOpen,
|
|
309
|
-
isMobile,
|
|
310
|
-
openMobile,
|
|
311
|
-
setOpenMobile,
|
|
312
|
-
side,
|
|
313
|
-
variant,
|
|
314
|
-
collapsible,
|
|
315
|
-
position,
|
|
316
|
-
toggleSidebar,
|
|
317
|
-
],
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
return (
|
|
321
|
-
<SidebarContext.Provider value={value}>
|
|
322
|
-
<div
|
|
323
|
-
ref={ref}
|
|
324
|
-
style={
|
|
325
|
-
{
|
|
326
|
-
'--sidebar-width': width,
|
|
327
|
-
'--sidebar-width-icon': iconWidth,
|
|
328
|
-
'--sidebar-width-mobile': mobileWidth,
|
|
329
|
-
...style,
|
|
330
|
-
} as React.CSSProperties
|
|
331
|
-
}
|
|
332
|
-
className={cn(
|
|
333
|
-
'group/sidebar-wrapper flex min-h-svh w-full',
|
|
334
|
-
side === 'right' && 'flex-row-reverse',
|
|
335
|
-
variant === 'inset' && 'bg-sidebar',
|
|
336
|
-
className,
|
|
337
|
-
)}
|
|
338
|
-
data-sidebar="provider"
|
|
339
|
-
data-state={state}
|
|
340
|
-
data-collapsible={state === 'collapsed' ? collapsible : ''}
|
|
341
|
-
data-variant={variant}
|
|
342
|
-
data-side={side}
|
|
343
|
-
data-position={position}
|
|
344
|
-
{...props}
|
|
345
|
-
>
|
|
346
|
-
{children}
|
|
347
|
-
</div>
|
|
348
|
-
</SidebarContext.Provider>
|
|
349
|
-
);
|
|
350
|
-
},
|
|
351
|
-
);
|
|
352
|
-
|
|
353
|
-
/* ── Sidebar ───────────────────────────────────────────────────────── */
|
|
354
|
-
|
|
355
|
-
export interface SidebarProps extends HTMLAttributes<HTMLElement> {
|
|
356
|
-
side?: SidebarSide;
|
|
357
|
-
variant?: SidebarVariant;
|
|
358
|
-
collapsible?: SidebarCollapsible;
|
|
359
|
-
position?: SidebarPosition;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
export const Sidebar = forwardRef<HTMLElement, SidebarProps>(function Sidebar(
|
|
363
|
-
{ side, variant, collapsible, position, className, children, ...props },
|
|
364
|
-
ref,
|
|
365
|
-
) {
|
|
366
|
-
const context = useSidebar();
|
|
367
|
-
const { isMobile, state, openMobile, setOpenMobile } = context;
|
|
368
|
-
const currentSide = side ?? context.side;
|
|
369
|
-
const currentVariant = variant ?? context.variant;
|
|
370
|
-
const currentCollapsible = collapsible ?? context.collapsible;
|
|
371
|
-
const currentPosition = position ?? context.position;
|
|
372
|
-
const isCollapsed = state === 'collapsed';
|
|
373
|
-
const isFloating = currentVariant === 'floating' || currentVariant === 'inset';
|
|
374
|
-
const isOffcanvasCollapsed = isCollapsed && currentCollapsible === 'offcanvas';
|
|
375
|
-
const isIconCollapsed = isCollapsed && currentCollapsible === 'icon';
|
|
376
|
-
const sidebarEdgeBorder = currentSide === 'right'
|
|
377
|
-
? 'border-l border-sidebar-border'
|
|
378
|
-
: 'border-r border-sidebar-border';
|
|
379
|
-
|
|
380
|
-
if (currentCollapsible === 'none') {
|
|
381
|
-
return (
|
|
382
|
-
<nav
|
|
383
|
-
ref={ref}
|
|
384
|
-
aria-label={props['aria-label'] ?? 'Sidebar'}
|
|
385
|
-
data-sidebar="sidebar"
|
|
386
|
-
data-state={state}
|
|
387
|
-
data-collapsible=""
|
|
388
|
-
data-variant={currentVariant}
|
|
389
|
-
data-side={currentSide}
|
|
390
|
-
data-position={currentPosition}
|
|
391
|
-
className={cn(
|
|
392
|
-
'flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground',
|
|
393
|
-
isFloating ? 'rounded-lg border border-sidebar-border shadow' : sidebarEdgeBorder,
|
|
394
|
-
className,
|
|
395
|
-
)}
|
|
396
|
-
{...props}
|
|
397
|
-
>
|
|
398
|
-
{children}
|
|
399
|
-
</nav>
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if (isMobile) {
|
|
404
|
-
return (
|
|
405
|
-
<Sheet open={openMobile} onOpenChange={setOpenMobile}>
|
|
406
|
-
<SheetContent
|
|
407
|
-
data-sidebar="sidebar"
|
|
408
|
-
data-mobile="true"
|
|
409
|
-
className={cn(
|
|
410
|
-
'w-(--sidebar-width-mobile) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden',
|
|
411
|
-
className,
|
|
412
|
-
)}
|
|
413
|
-
side={currentSide}
|
|
414
|
-
{...props}
|
|
415
|
-
>
|
|
416
|
-
<nav aria-label={props['aria-label'] ?? 'Sidebar'} className="flex h-full w-full flex-col">
|
|
417
|
-
{children}
|
|
418
|
-
</nav>
|
|
419
|
-
</SheetContent>
|
|
420
|
-
</Sheet>
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (currentPosition === 'contained') {
|
|
425
|
-
return (
|
|
426
|
-
<div
|
|
427
|
-
data-sidebar="sidebar-container"
|
|
428
|
-
className={cn(
|
|
429
|
-
'group peer relative hidden h-full shrink-0 overflow-hidden text-sidebar-foreground transition-[width] duration-200 ease-linear md:flex',
|
|
430
|
-
isOffcanvasCollapsed
|
|
431
|
-
? 'w-0'
|
|
432
|
-
: isIconCollapsed
|
|
433
|
-
? isFloating
|
|
434
|
-
? 'w-[calc(var(--sidebar-width-icon)+theme(spacing.4))]'
|
|
435
|
-
: 'w-(--sidebar-width-icon)'
|
|
436
|
-
: 'w-(--sidebar-width)',
|
|
437
|
-
isFloating && !isOffcanvasCollapsed && 'p-2',
|
|
438
|
-
className,
|
|
439
|
-
)}
|
|
440
|
-
data-state={state}
|
|
441
|
-
data-collapsible={isCollapsed ? currentCollapsible : ''}
|
|
442
|
-
data-variant={currentVariant}
|
|
443
|
-
data-side={currentSide}
|
|
444
|
-
data-position={currentPosition}
|
|
445
|
-
>
|
|
446
|
-
<nav
|
|
447
|
-
ref={ref}
|
|
448
|
-
aria-label={props['aria-label'] ?? 'Sidebar'}
|
|
449
|
-
data-sidebar="sidebar"
|
|
450
|
-
className={cn(
|
|
451
|
-
'flex h-full w-full min-w-0 flex-col bg-sidebar',
|
|
452
|
-
isFloating ? 'rounded-lg border border-sidebar-border shadow' : sidebarEdgeBorder,
|
|
453
|
-
)}
|
|
454
|
-
{...props}
|
|
455
|
-
>
|
|
456
|
-
{children}
|
|
457
|
-
</nav>
|
|
458
|
-
</div>
|
|
459
|
-
);
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
return (
|
|
463
|
-
<div
|
|
464
|
-
data-sidebar="sidebar-container"
|
|
465
|
-
className="group peer hidden md:block text-sidebar-foreground"
|
|
466
|
-
data-state={state}
|
|
467
|
-
data-collapsible={isCollapsed ? currentCollapsible : ''}
|
|
468
|
-
data-variant={currentVariant}
|
|
469
|
-
data-side={currentSide}
|
|
470
|
-
data-position={currentPosition}
|
|
471
|
-
>
|
|
472
|
-
{/* Gap element — occupies space so the main content shifts */}
|
|
473
|
-
<div
|
|
474
|
-
className={cn(
|
|
475
|
-
'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',
|
|
476
|
-
'group-data-[collapsible=offcanvas]:w-0',
|
|
477
|
-
'group-data-[side=right]:rotate-180',
|
|
478
|
-
isFloating
|
|
479
|
-
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+theme(spacing.4))]'
|
|
480
|
-
: 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)',
|
|
481
|
-
)}
|
|
482
|
-
/>
|
|
483
|
-
<div
|
|
484
|
-
className={cn(
|
|
485
|
-
'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex',
|
|
486
|
-
currentSide === 'left'
|
|
487
|
-
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
|
|
488
|
-
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
|
|
489
|
-
isFloating
|
|
490
|
-
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+theme(spacing.4)+2px)]'
|
|
491
|
-
: cn(
|
|
492
|
-
'group-data-[collapsible=icon]:w-(--sidebar-width-icon)',
|
|
493
|
-
'border-sidebar-border',
|
|
494
|
-
currentSide === 'right' ? 'group-data-[variant=sidebar]:border-l' : 'group-data-[variant=sidebar]:border-r',
|
|
495
|
-
),
|
|
496
|
-
className,
|
|
497
|
-
)}
|
|
498
|
-
{...props}
|
|
499
|
-
>
|
|
500
|
-
<nav
|
|
501
|
-
ref={ref}
|
|
502
|
-
aria-label={props['aria-label'] ?? 'Sidebar'}
|
|
503
|
-
data-sidebar="sidebar"
|
|
504
|
-
className={cn(
|
|
505
|
-
'flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow',
|
|
506
|
-
)}
|
|
507
|
-
>
|
|
508
|
-
{children}
|
|
509
|
-
</nav>
|
|
510
|
-
</div>
|
|
511
|
-
</div>
|
|
512
|
-
);
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
/* ── SidebarTrigger ────────────────────────────────────────────────── */
|
|
516
|
-
|
|
517
|
-
export const SidebarTrigger = forwardRef<
|
|
518
|
-
HTMLButtonElement,
|
|
519
|
-
ComponentProps<typeof Button>
|
|
520
|
-
>(function SidebarTrigger({ className, disabled, onClick, ...props }, ref) {
|
|
521
|
-
const { isMobile, open, openMobile, collapsible, side, toggleSidebar } = useSidebar();
|
|
522
|
-
const expanded = collapsible === 'none' ? true : isMobile ? openMobile : open;
|
|
523
|
-
const isDisabled = disabled || collapsible === 'none';
|
|
524
|
-
|
|
525
|
-
return (
|
|
526
|
-
<Button
|
|
527
|
-
ref={ref}
|
|
528
|
-
data-sidebar="trigger"
|
|
529
|
-
aria-expanded={expanded}
|
|
530
|
-
aria-disabled={isDisabled || undefined}
|
|
531
|
-
disabled={isDisabled}
|
|
532
|
-
variant="ghost"
|
|
533
|
-
size="icon"
|
|
534
|
-
className={cn('size-7', className)}
|
|
535
|
-
onClick={(e) => {
|
|
536
|
-
onClick?.(e);
|
|
537
|
-
if (collapsible === 'none') return;
|
|
538
|
-
toggleSidebar();
|
|
539
|
-
}}
|
|
540
|
-
{...props}
|
|
541
|
-
>
|
|
542
|
-
{side === 'right' ? <PanelRightIcon /> : <PanelLeftIcon />}
|
|
543
|
-
<span className="sr-only">Toggle Sidebar</span>
|
|
544
|
-
</Button>
|
|
545
|
-
);
|
|
546
|
-
});
|
|
547
|
-
|
|
548
|
-
/* ── SidebarRail ───────────────────────────────────────────────────── */
|
|
549
|
-
|
|
550
|
-
export const SidebarRail = forwardRef<HTMLButtonElement, HTMLAttributes<HTMLButtonElement>>(
|
|
551
|
-
function SidebarRail({ className, ...props }, ref) {
|
|
552
|
-
const { toggleSidebar } = useSidebar();
|
|
553
|
-
|
|
554
|
-
return (
|
|
555
|
-
<button
|
|
556
|
-
ref={ref}
|
|
557
|
-
type="button"
|
|
558
|
-
data-sidebar="rail"
|
|
559
|
-
aria-label="Toggle Sidebar"
|
|
560
|
-
tabIndex={-1}
|
|
561
|
-
onClick={toggleSidebar}
|
|
562
|
-
title="Toggle Sidebar"
|
|
563
|
-
className={cn(
|
|
564
|
-
'absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] group-data-[side=left]:-right-4 group-data-[side=left]:-translate-x-1/2 group-data-[side=right]:-left-4 group-data-[side=right]:translate-x-1/2 sm:flex',
|
|
565
|
-
'hover:after:bg-sidebar-border',
|
|
566
|
-
'[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize',
|
|
567
|
-
'[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
|
|
568
|
-
'group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar',
|
|
569
|
-
'[[data-side=left][data-collapsible=offcanvas]_&]:-right-2 [[data-side=left][data-collapsible=offcanvas]_&]:translate-x-0',
|
|
570
|
-
'[[data-side=right][data-collapsible=offcanvas]_&]:-left-2 [[data-side=right][data-collapsible=offcanvas]_&]:translate-x-0',
|
|
571
|
-
className,
|
|
572
|
-
)}
|
|
573
|
-
{...props}
|
|
574
|
-
/>
|
|
575
|
-
);
|
|
576
|
-
},
|
|
577
|
-
);
|
|
578
|
-
|
|
579
|
-
/* ── SidebarInset ──────────────────────────────────────────────────── */
|
|
580
|
-
|
|
581
|
-
export const SidebarInset = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
582
|
-
function SidebarInset({ className, ...props }, ref) {
|
|
583
|
-
return (
|
|
584
|
-
<main
|
|
585
|
-
ref={ref}
|
|
586
|
-
className={cn(
|
|
587
|
-
'relative flex min-h-svh flex-1 flex-col bg-background',
|
|
588
|
-
'peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[side=left]:peer-data-[variant=inset]:ml-0 md:peer-data-[side=right]:peer-data-[variant=inset]:mr-0 md:peer-data-[side=left]:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[side=right]:peer-data-[state=collapsed]:peer-data-[variant=inset]:mr-2 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow',
|
|
589
|
-
className,
|
|
590
|
-
)}
|
|
591
|
-
{...props}
|
|
592
|
-
/>
|
|
593
|
-
);
|
|
594
|
-
},
|
|
595
|
-
);
|
|
596
|
-
|
|
597
|
-
/* ── SidebarInput ──────────────────────────────────────────────────── */
|
|
598
|
-
|
|
599
|
-
export const SidebarInput = forwardRef<
|
|
600
|
-
HTMLInputElement,
|
|
601
|
-
ComponentProps<typeof Input>
|
|
602
|
-
>(function SidebarInput({ className, ...props }, ref) {
|
|
603
|
-
return (
|
|
604
|
-
<Input
|
|
605
|
-
ref={ref}
|
|
606
|
-
data-sidebar="input"
|
|
607
|
-
className={cn(
|
|
608
|
-
'h-8 w-full bg-background shadow-none focus-within:ring-2 focus-within:ring-sidebar-ring',
|
|
609
|
-
className,
|
|
610
|
-
)}
|
|
611
|
-
{...props}
|
|
612
|
-
/>
|
|
613
|
-
);
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
/* ── Layout sections ───────────────────────────────────────────────── */
|
|
617
|
-
|
|
618
|
-
export const SidebarHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
619
|
-
function SidebarHeader({ className, ...props }, ref) {
|
|
620
|
-
return (
|
|
621
|
-
<div
|
|
622
|
-
ref={ref}
|
|
623
|
-
data-sidebar="header"
|
|
624
|
-
className={cn('flex flex-col gap-2 p-2', className)}
|
|
625
|
-
{...props}
|
|
626
|
-
/>
|
|
627
|
-
);
|
|
628
|
-
},
|
|
629
|
-
);
|
|
630
|
-
|
|
631
|
-
export const SidebarFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
632
|
-
function SidebarFooter({ className, ...props }, ref) {
|
|
633
|
-
return (
|
|
634
|
-
<div
|
|
635
|
-
ref={ref}
|
|
636
|
-
data-sidebar="footer"
|
|
637
|
-
className={cn('flex flex-col gap-2 p-2', className)}
|
|
638
|
-
{...props}
|
|
639
|
-
/>
|
|
640
|
-
);
|
|
641
|
-
},
|
|
642
|
-
);
|
|
643
|
-
|
|
644
|
-
export const SidebarContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
645
|
-
function SidebarContent({ className, ...props }, ref) {
|
|
646
|
-
return (
|
|
647
|
-
<div
|
|
648
|
-
ref={ref}
|
|
649
|
-
data-sidebar="content"
|
|
650
|
-
className={cn(
|
|
651
|
-
'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
|
|
652
|
-
className,
|
|
653
|
-
)}
|
|
654
|
-
{...props}
|
|
655
|
-
/>
|
|
656
|
-
);
|
|
657
|
-
},
|
|
658
|
-
);
|
|
659
|
-
|
|
660
|
-
/* ── SidebarSeparator ──────────────────────────────────────────────── */
|
|
661
|
-
|
|
662
|
-
export const SidebarSeparator = forwardRef<
|
|
663
|
-
HTMLDivElement,
|
|
664
|
-
ComponentProps<typeof Separator>
|
|
665
|
-
>(function SidebarSeparator({ className, ...props }, ref) {
|
|
666
|
-
return (
|
|
667
|
-
<Separator
|
|
668
|
-
ref={ref}
|
|
669
|
-
data-sidebar="separator"
|
|
670
|
-
className={cn('mx-2 w-auto bg-sidebar-border', className)}
|
|
671
|
-
{...props}
|
|
672
|
-
/>
|
|
673
|
-
);
|
|
674
|
-
});
|
|
675
|
-
|
|
676
|
-
/* ── Group ─────────────────────────────────────────────────────────── */
|
|
677
|
-
|
|
678
|
-
export const SidebarGroup = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
679
|
-
function SidebarGroup({ className, ...props }, ref) {
|
|
680
|
-
return (
|
|
681
|
-
<div
|
|
682
|
-
ref={ref}
|
|
683
|
-
data-sidebar="group"
|
|
684
|
-
className={cn('relative flex w-full min-w-0 flex-col p-2', className)}
|
|
685
|
-
{...props}
|
|
686
|
-
/>
|
|
687
|
-
);
|
|
688
|
-
},
|
|
689
|
-
);
|
|
690
|
-
|
|
691
|
-
export const SidebarGroupLabel = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement> & { asChild?: boolean }>(
|
|
692
|
-
function SidebarGroupLabel({ className, asChild: _asChild, ...props }, ref) {
|
|
693
|
-
return (
|
|
694
|
-
<div
|
|
695
|
-
ref={ref}
|
|
696
|
-
data-sidebar="group-label"
|
|
697
|
-
className={cn(
|
|
698
|
-
'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2',
|
|
699
|
-
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
|
|
700
|
-
className,
|
|
701
|
-
)}
|
|
702
|
-
{...props}
|
|
703
|
-
/>
|
|
704
|
-
);
|
|
705
|
-
},
|
|
706
|
-
);
|
|
707
|
-
|
|
708
|
-
export const SidebarGroupAction = forwardRef<HTMLButtonElement, ButtonHTMLAttributes<HTMLButtonElement> & { asChild?: boolean }>(
|
|
709
|
-
function SidebarGroupAction({ className, asChild: _asChild, ...props }, ref) {
|
|
710
|
-
return (
|
|
711
|
-
<button
|
|
712
|
-
ref={ref}
|
|
713
|
-
type="button"
|
|
714
|
-
data-sidebar="group-action"
|
|
715
|
-
className={cn(
|
|
716
|
-
'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
|
717
|
-
'after:absolute after:-inset-2 after:md:hidden',
|
|
718
|
-
'group-data-[collapsible=icon]:hidden',
|
|
719
|
-
className,
|
|
720
|
-
)}
|
|
721
|
-
{...props}
|
|
722
|
-
/>
|
|
723
|
-
);
|
|
724
|
-
},
|
|
725
|
-
);
|
|
726
|
-
|
|
727
|
-
export const SidebarGroupContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
728
|
-
function SidebarGroupContent({ className, ...props }, ref) {
|
|
729
|
-
return (
|
|
730
|
-
<div
|
|
731
|
-
ref={ref}
|
|
732
|
-
data-sidebar="group-content"
|
|
733
|
-
className={cn('w-full text-sm', className)}
|
|
734
|
-
{...props}
|
|
735
|
-
/>
|
|
736
|
-
);
|
|
737
|
-
},
|
|
738
|
-
);
|
|
739
|
-
|
|
740
|
-
/* ── Menu ──────────────────────────────────────────────────────────── */
|
|
741
|
-
|
|
742
|
-
export const SidebarMenu = forwardRef<HTMLUListElement, HTMLAttributes<HTMLUListElement>>(
|
|
743
|
-
function SidebarMenu({ className, ...props }, ref) {
|
|
744
|
-
return (
|
|
745
|
-
<ul
|
|
746
|
-
ref={ref}
|
|
747
|
-
data-sidebar="menu"
|
|
748
|
-
className={cn('flex w-full min-w-0 flex-col gap-1', className)}
|
|
749
|
-
{...props}
|
|
750
|
-
/>
|
|
751
|
-
);
|
|
752
|
-
},
|
|
753
|
-
);
|
|
754
|
-
|
|
755
|
-
export const SidebarMenuItem = forwardRef<HTMLLIElement, HTMLAttributes<HTMLLIElement>>(
|
|
756
|
-
function SidebarMenuItem({ className, ...props }, ref) {
|
|
757
|
-
return (
|
|
758
|
-
<li
|
|
759
|
-
ref={ref}
|
|
760
|
-
data-sidebar="menu-item"
|
|
761
|
-
className={cn('group/menu-item relative', className)}
|
|
762
|
-
{...props}
|
|
763
|
-
/>
|
|
764
|
-
);
|
|
765
|
-
},
|
|
766
|
-
);
|
|
767
|
-
|
|
768
|
-
/* ── SidebarMenuButton ─────────────────────────────────────────────── */
|
|
769
|
-
|
|
770
|
-
export const sidebarMenuButtonVariants = cva(
|
|
771
|
-
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground 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 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
|
|
772
|
-
{
|
|
773
|
-
variants: {
|
|
774
|
-
variant: {
|
|
775
|
-
default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
|
|
776
|
-
outline:
|
|
777
|
-
'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
|
|
778
|
-
},
|
|
779
|
-
size: {
|
|
780
|
-
default: 'h-8 text-sm',
|
|
781
|
-
sm: 'h-7 text-xs',
|
|
782
|
-
lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',
|
|
783
|
-
},
|
|
784
|
-
},
|
|
785
|
-
defaultVariants: {
|
|
786
|
-
variant: 'default',
|
|
787
|
-
size: 'default',
|
|
788
|
-
},
|
|
789
|
-
},
|
|
790
|
-
);
|
|
791
|
-
|
|
792
|
-
export interface SidebarMenuButtonProps
|
|
793
|
-
extends ButtonHTMLAttributes<HTMLButtonElement>,
|
|
794
|
-
VariantProps<typeof sidebarMenuButtonVariants> {
|
|
795
|
-
asChild?: boolean;
|
|
796
|
-
isActive?: boolean;
|
|
797
|
-
tooltip?: string | ComponentProps<typeof Tooltip>;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
export const SidebarMenuButton = forwardRef<HTMLButtonElement, SidebarMenuButtonProps>(
|
|
801
|
-
function SidebarMenuButton(
|
|
802
|
-
{ asChild: _asChild, isActive = false, variant, size, tooltip, className, ...props },
|
|
803
|
-
ref,
|
|
804
|
-
) {
|
|
805
|
-
useSidebar();
|
|
806
|
-
|
|
807
|
-
const button = (
|
|
808
|
-
<button
|
|
809
|
-
ref={ref}
|
|
810
|
-
type="button"
|
|
811
|
-
data-sidebar="menu-button"
|
|
812
|
-
data-size={size}
|
|
813
|
-
data-active={isActive}
|
|
814
|
-
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
|
|
815
|
-
{...props}
|
|
816
|
-
/>
|
|
817
|
-
);
|
|
818
|
-
|
|
819
|
-
if (!tooltip) return button;
|
|
820
|
-
|
|
821
|
-
const tooltipProps: ComponentProps<typeof Tooltip> =
|
|
822
|
-
typeof tooltip === 'string' ? { content: tooltip } : tooltip;
|
|
823
|
-
|
|
824
|
-
return (
|
|
825
|
-
<Tooltip {...tooltipProps} side="right">
|
|
826
|
-
{button}
|
|
827
|
-
</Tooltip>
|
|
828
|
-
);
|
|
829
|
-
},
|
|
830
|
-
);
|
|
831
|
-
|
|
832
|
-
/* ── SidebarMenuAction ─────────────────────────────────────────────── */
|
|
833
|
-
|
|
834
|
-
export const SidebarMenuAction = forwardRef<
|
|
835
|
-
HTMLButtonElement,
|
|
836
|
-
ButtonHTMLAttributes<HTMLButtonElement> & { asChild?: boolean; showOnHover?: boolean }
|
|
837
|
-
>(function SidebarMenuAction({ className, asChild: _asChild, showOnHover = false, ...props }, ref) {
|
|
838
|
-
return (
|
|
839
|
-
<button
|
|
840
|
-
ref={ref}
|
|
841
|
-
type="button"
|
|
842
|
-
data-sidebar="menu-action"
|
|
843
|
-
className={cn(
|
|
844
|
-
'absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0',
|
|
845
|
-
'after:absolute after:-inset-2 after:md:hidden',
|
|
846
|
-
'group-data-[collapsible=icon]:hidden',
|
|
847
|
-
showOnHover &&
|
|
848
|
-
'peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0',
|
|
849
|
-
className,
|
|
850
|
-
)}
|
|
851
|
-
{...props}
|
|
852
|
-
/>
|
|
853
|
-
);
|
|
854
|
-
});
|
|
855
|
-
|
|
856
|
-
/* ── SidebarMenuBadge ──────────────────────────────────────────────── */
|
|
857
|
-
|
|
858
|
-
export const SidebarMenuBadge = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
859
|
-
function SidebarMenuBadge({ className, ...props }, ref) {
|
|
860
|
-
return (
|
|
861
|
-
<div
|
|
862
|
-
ref={ref}
|
|
863
|
-
data-sidebar="menu-badge"
|
|
864
|
-
className={cn(
|
|
865
|
-
'pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground',
|
|
866
|
-
'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',
|
|
867
|
-
'group-data-[collapsible=icon]:hidden',
|
|
868
|
-
className,
|
|
869
|
-
)}
|
|
870
|
-
{...props}
|
|
871
|
-
/>
|
|
872
|
-
);
|
|
873
|
-
},
|
|
874
|
-
);
|
|
875
|
-
|
|
876
|
-
/* ── SidebarMenuSkeleton ───────────────────────────────────────────── */
|
|
877
|
-
|
|
878
|
-
export interface SidebarMenuSkeletonProps extends HTMLAttributes<HTMLDivElement> {
|
|
879
|
-
showIcon?: boolean;
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
export const SidebarMenuSkeleton = forwardRef<HTMLDivElement, SidebarMenuSkeletonProps>(
|
|
883
|
-
function SidebarMenuSkeleton({ className, showIcon = false, ...props }, ref) {
|
|
884
|
-
// Fixed skeleton width — visual variety comes from multiple instances
|
|
885
|
-
const width = '70%';
|
|
886
|
-
|
|
887
|
-
return (
|
|
888
|
-
<div
|
|
889
|
-
ref={ref}
|
|
890
|
-
data-sidebar="menu-skeleton"
|
|
891
|
-
className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}
|
|
892
|
-
{...props}
|
|
893
|
-
>
|
|
894
|
-
{showIcon && <Skeleton className="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />}
|
|
895
|
-
<Skeleton
|
|
896
|
-
className="h-4 max-w-(--skeleton-width) flex-1"
|
|
897
|
-
data-sidebar="menu-skeleton-text"
|
|
898
|
-
style={{ '--skeleton-width': width } as React.CSSProperties}
|
|
899
|
-
/>
|
|
900
|
-
</div>
|
|
901
|
-
);
|
|
902
|
-
},
|
|
903
|
-
);
|
|
904
|
-
|
|
905
|
-
/* ── Sub-menu ──────────────────────────────────────────────────────── */
|
|
906
|
-
|
|
907
|
-
export const SidebarMenuSub = forwardRef<HTMLUListElement, HTMLAttributes<HTMLUListElement>>(
|
|
908
|
-
function SidebarMenuSub({ className, ...props }, ref) {
|
|
909
|
-
return (
|
|
910
|
-
<ul
|
|
911
|
-
ref={ref}
|
|
912
|
-
data-sidebar="menu-sub"
|
|
913
|
-
className={cn(
|
|
914
|
-
'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5',
|
|
915
|
-
'group-data-[collapsible=icon]:hidden',
|
|
916
|
-
className,
|
|
917
|
-
)}
|
|
918
|
-
{...props}
|
|
919
|
-
/>
|
|
920
|
-
);
|
|
921
|
-
},
|
|
922
|
-
);
|
|
923
|
-
|
|
924
|
-
export const SidebarMenuSubItem = forwardRef<HTMLLIElement, HTMLAttributes<HTMLLIElement>>(
|
|
925
|
-
function SidebarMenuSubItem({ ...props }, ref) {
|
|
926
|
-
return <li ref={ref} {...props} />;
|
|
927
|
-
},
|
|
928
|
-
);
|
|
929
|
-
|
|
930
|
-
export interface SidebarMenuSubButtonProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
931
|
-
asChild?: boolean;
|
|
932
|
-
size?: 'sm' | 'md';
|
|
933
|
-
isActive?: boolean;
|
|
934
|
-
}
|
|
935
35
|
|
|
936
|
-
export
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
36
|
+
export { useSidebar } from './Sidebar/context.js';
|
|
37
|
+
export { SidebarProvider, type SidebarProviderProps } from './Sidebar/provider.js';
|
|
38
|
+
export { Sidebar, type SidebarProps } from './Sidebar/sidebar.js';
|
|
39
|
+
export {
|
|
40
|
+
SidebarInput,
|
|
41
|
+
SidebarInset,
|
|
42
|
+
SidebarRail,
|
|
43
|
+
SidebarTrigger,
|
|
44
|
+
} from './Sidebar/trigger.js';
|
|
45
|
+
export {
|
|
46
|
+
SidebarContent,
|
|
47
|
+
SidebarFooter,
|
|
48
|
+
SidebarHeader,
|
|
49
|
+
SidebarSeparator,
|
|
50
|
+
} from './Sidebar/layout.js';
|
|
51
|
+
export {
|
|
52
|
+
SidebarGroup,
|
|
53
|
+
SidebarGroupAction,
|
|
54
|
+
SidebarGroupContent,
|
|
55
|
+
SidebarGroupLabel,
|
|
56
|
+
} from './Sidebar/group.js';
|
|
57
|
+
export {
|
|
58
|
+
SidebarMenu,
|
|
59
|
+
SidebarMenuAction,
|
|
60
|
+
SidebarMenuBadge,
|
|
61
|
+
SidebarMenuButton,
|
|
62
|
+
SidebarMenuItem,
|
|
63
|
+
SidebarMenuSkeleton,
|
|
64
|
+
sidebarMenuButtonVariants,
|
|
65
|
+
type SidebarMenuButtonProps,
|
|
66
|
+
type SidebarMenuSkeletonProps,
|
|
67
|
+
} from './Sidebar/menu.js';
|
|
68
|
+
export {
|
|
69
|
+
SidebarMenuSub,
|
|
70
|
+
SidebarMenuSubButton,
|
|
71
|
+
SidebarMenuSubItem,
|
|
72
|
+
type SidebarMenuSubButtonProps,
|
|
73
|
+
} from './Sidebar/submenu.js';
|