@olympusoss/canvas 2.20.2 → 3.1.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 +69 -35
- package/package.json +45 -177
- package/src/cn.ts +3 -0
- package/src/index.ts +12 -603
- package/src/theme.ts +62 -0
- package/src/tokens.ts +11 -0
- package/styles/base.css +17 -0
- package/styles/canvas.css +77 -52
- package/styles/components/alert.css +66 -0
- package/styles/components/app-shell.css +46 -0
- package/styles/components/avatar.css +22 -0
- package/styles/components/badge.css +83 -0
- package/styles/components/breadcrumb.css +35 -0
- package/styles/components/button-group.css +23 -0
- package/styles/components/button.css +107 -0
- package/styles/components/calendar.css +73 -0
- package/styles/components/card.css +58 -0
- package/styles/components/checkbox.css +55 -0
- package/styles/components/code-block.css +18 -0
- package/styles/components/combobox.css +75 -0
- package/styles/components/command.css +94 -0
- package/styles/components/data-table.css +142 -0
- package/styles/components/dialog.css +72 -0
- package/styles/components/dropdown.css +54 -0
- package/styles/components/empty-state.css +17 -0
- package/styles/components/field.css +27 -0
- package/styles/components/filter-panel.css +58 -0
- package/styles/components/form.css +27 -0
- package/styles/components/icon.css +8 -0
- package/styles/components/input-group.css +45 -0
- package/styles/components/input.css +56 -0
- package/styles/components/kbd.css +15 -0
- package/styles/components/page-header.css +52 -0
- package/styles/components/pagination.css +48 -0
- package/styles/components/popover.css +14 -0
- package/styles/components/radio.css +28 -0
- package/styles/components/row-menu.css +69 -0
- package/styles/components/section-card.css +49 -0
- package/styles/components/select.css +57 -0
- package/styles/components/separator.css +32 -0
- package/styles/components/sheet.css +70 -0
- package/styles/components/sidebar.css +146 -0
- package/styles/components/skeleton.css +32 -0
- package/styles/components/spinner.css +26 -0
- package/styles/components/stat-card.css +71 -0
- package/styles/components/stepper.css +63 -0
- package/styles/components/switch.css +45 -0
- package/styles/components/tabs.css +40 -0
- package/styles/components/textarea.css +31 -0
- package/styles/components/toast.css +95 -0
- package/styles/components/tooltip.css +53 -0
- package/styles/components/topbar.css +24 -0
- package/styles/components/typography.css +105 -0
- package/styles/patterns/backdrops.css +35 -0
- package/styles/patterns/density.css +66 -0
- package/styles/patterns/focus.css +22 -0
- package/styles/patterns/glass.css +85 -0
- package/styles/patterns/high-contrast.css +70 -0
- package/styles/patterns/reduced-motion.css +12 -0
- package/styles/patterns/scrollbar.css +10 -0
- package/styles/reset.css +89 -0
- package/styles/tokens/colors.css +106 -0
- package/styles/tokens/motion.css +33 -0
- package/styles/tokens/radius.css +10 -0
- package/styles/tokens/shadows.css +35 -0
- package/styles/tokens/spacing.css +19 -0
- package/styles/tokens/typography.css +6 -0
- package/styles/tokens/z-index.css +12 -0
- package/styles/utilities/display.css +66 -0
- package/styles/utilities/flexbox.css +240 -0
- package/styles/utilities/gap.css +288 -0
- package/styles/utilities/grid.css +138 -0
- package/styles/utilities/position.css +78 -0
- package/styles/utilities/sizing.css +138 -0
- package/tsconfig.json +20 -21
- package/src/components/atoms/README.md +0 -11
- package/src/components/atoms/aspect-ratio.tsx +0 -32
- package/src/components/atoms/avatar.tsx +0 -98
- package/src/components/atoms/badge.tsx +0 -44
- package/src/components/atoms/brand-mark.tsx +0 -74
- package/src/components/atoms/button.tsx +0 -105
- package/src/components/atoms/checkbox.tsx +0 -63
- package/src/components/atoms/flex-box.tsx +0 -105
- package/src/components/atoms/icon.tsx +0 -34
- package/src/components/atoms/input.tsx +0 -92
- package/src/components/atoms/label.tsx +0 -41
- package/src/components/atoms/logo.tsx +0 -89
- package/src/components/atoms/progress.tsx +0 -55
- package/src/components/atoms/radio-group.tsx +0 -122
- package/src/components/atoms/scroll-area.tsx +0 -106
- package/src/components/atoms/section.tsx +0 -48
- package/src/components/atoms/separator.tsx +0 -45
- package/src/components/atoms/skeleton.tsx +0 -17
- package/src/components/atoms/slider.tsx +0 -93
- package/src/components/atoms/spinner.tsx +0 -47
- package/src/components/atoms/switch.tsx +0 -60
- package/src/components/atoms/textarea.tsx +0 -78
- package/src/components/atoms/toggle.tsx +0 -80
- package/src/components/charts/activity-heatmap.tsx +0 -186
- package/src/components/charts/axes.tsx +0 -21
- package/src/components/charts/chart-container.tsx +0 -254
- package/src/components/charts/chart-legend.tsx +0 -67
- package/src/components/charts/chart-tooltip.tsx +0 -161
- package/src/components/charts/chart-types.tsx +0 -49
- package/src/components/charts/containers.tsx +0 -11
- package/src/components/charts/data.tsx +0 -16
- package/src/components/charts/details.tsx +0 -25
- package/src/components/charts/dot-pulse.tsx +0 -61
- package/src/components/charts/gauge.tsx +0 -106
- package/src/components/charts/grids.tsx +0 -8
- package/src/components/charts/index.ts +0 -62
- package/src/components/charts/labeled-bar-list.tsx +0 -85
- package/src/components/charts/metric-breakdown.tsx +0 -316
- package/src/components/charts/references.tsx +0 -8
- package/src/components/charts/service-health-list.tsx +0 -85
- package/src/components/charts/sparkline-area.tsx +0 -80
- package/src/components/charts/sparkline.tsx +0 -52
- package/src/components/charts/stacked-bar.tsx +0 -104
- package/src/components/charts/text.tsx +0 -10
- package/src/components/charts/world-heat-map-inner.tsx +0 -317
- package/src/components/charts/world-heat-map.tsx +0 -184
- package/src/components/molecules/README.md +0 -12
- package/src/components/molecules/action-bar.tsx +0 -73
- package/src/components/molecules/activity-item.tsx +0 -74
- package/src/components/molecules/alert.tsx +0 -86
- package/src/components/molecules/animated-background.tsx +0 -92
- package/src/components/molecules/auth-shell.tsx +0 -95
- package/src/components/molecules/brand-lockup.tsx +0 -48
- package/src/components/molecules/breadcrumb.tsx +0 -157
- package/src/components/molecules/button-group.tsx +0 -104
- package/src/components/molecules/calendar.tsx +0 -217
- package/src/components/molecules/card.tsx +0 -102
- package/src/components/molecules/client-brand.tsx +0 -95
- package/src/components/molecules/code-block.tsx +0 -86
- package/src/components/molecules/countdown-button.tsx +0 -92
- package/src/components/molecules/empty-state.tsx +0 -56
- package/src/components/molecules/error-state.tsx +0 -42
- package/src/components/molecules/field-display.tsx +0 -35
- package/src/components/molecules/input-otp.tsx +0 -74
- package/src/components/molecules/launcher-card.tsx +0 -152
- package/src/components/molecules/loading-state.tsx +0 -36
- package/src/components/molecules/notification-item.tsx +0 -67
- package/src/components/molecules/notification-list.tsx +0 -45
- package/src/components/molecules/number-badge.tsx +0 -53
- package/src/components/molecules/or-separator.tsx +0 -38
- package/src/components/molecules/page-header.tsx +0 -88
- package/src/components/molecules/page-tabs.tsx +0 -94
- package/src/components/molecules/pagination.tsx +0 -150
- package/src/components/molecules/password-input.tsx +0 -83
- package/src/components/molecules/password-strength-meter.tsx +0 -104
- package/src/components/molecules/phone-input.tsx +0 -200
- package/src/components/molecules/search-bar.tsx +0 -64
- package/src/components/molecules/secret-field.tsx +0 -158
- package/src/components/molecules/section-card.tsx +0 -91
- package/src/components/molecules/social-buttons.tsx +0 -165
- package/src/components/molecules/stat-card.tsx +0 -100
- package/src/components/molecules/status-badge.tsx +0 -42
- package/src/components/molecules/stepper.tsx +0 -96
- package/src/components/molecules/table.tsx +0 -157
- package/src/components/molecules/terminal.tsx +0 -74
- package/src/components/molecules/toggle-group.tsx +0 -145
- package/src/components/molecules/tooltip.tsx +0 -155
- package/src/components/molecules/user-avatar-chip.tsx +0 -71
- package/src/components/organisms/README.md +0 -14
- package/src/components/organisms/accordion.tsx +0 -154
- package/src/components/organisms/alert-dialog.tsx +0 -277
- package/src/components/organisms/carousel.tsx +0 -244
- package/src/components/organisms/collapsible.tsx +0 -69
- package/src/components/organisms/command.tsx +0 -144
- package/src/components/organisms/context-menu.tsx +0 -339
- package/src/components/organisms/dashboard-grid.tsx +0 -369
- package/src/components/organisms/data-table.tsx +0 -330
- package/src/components/organisms/dialog.tsx +0 -312
- package/src/components/organisms/drawer.tsx +0 -123
- package/src/components/organisms/dropdown-menu.tsx +0 -440
- package/src/components/organisms/editors/code-editor.tsx +0 -144
- package/src/components/organisms/editors/index.ts +0 -4
- package/src/components/organisms/editors/markdown-editor.tsx +0 -153
- package/src/components/organisms/editors/markdown-renderer.ts +0 -27
- package/src/components/organisms/editors/prose-canvas-classes.ts +0 -45
- package/src/components/organisms/editors/rich-text-editor.tsx +0 -126
- package/src/components/organisms/editors/toolbar/md-toolbar.tsx +0 -129
- package/src/components/organisms/editors/toolbar/rte-toolbar.tsx +0 -211
- package/src/components/organisms/editors/toolbar/toolbar-shell.tsx +0 -45
- package/src/components/organisms/editors/use-codemirror-theme.ts +0 -61
- package/src/components/organisms/error-boundary.tsx +0 -61
- package/src/components/organisms/form.tsx +0 -174
- package/src/components/organisms/hover-card.tsx +0 -115
- package/src/components/organisms/menubar.tsx +0 -498
- package/src/components/organisms/navbar.tsx +0 -104
- package/src/components/organisms/navigation-menu.tsx +0 -235
- package/src/components/organisms/popover.tsx +0 -149
- package/src/components/organisms/resizable.tsx +0 -58
- package/src/components/organisms/schema-form.tsx +0 -232
- package/src/components/organisms/select.tsx +0 -309
- package/src/components/organisms/sheet.tsx +0 -265
- package/src/components/organisms/sidebar.tsx +0 -1040
- package/src/components/organisms/sonner.tsx +0 -96
- package/src/components/organisms/tabs.tsx +0 -133
- package/src/components/organisms/theme-provider.tsx +0 -101
- package/src/hooks/use-mobile.tsx +0 -19
- package/src/lib/portal-container.tsx +0 -35
- package/src/lib/utils.ts +0 -6
- package/src/native.ts +0 -23
- package/src/tokens/colors.ts +0 -91
- package/src/tokens/index.ts +0 -3
- package/src/tokens/spacing.ts +0 -55
- package/src/tokens/typography.ts +0 -27
- package/styles/dashboard-grid.css +0 -47
- package/styles/fonts/Roboto-VariableFont_wdth_wght.ttf +0 -0
- package/styles/glass.css +0 -175
- package/styles/leaflet.css +0 -13
- package/styles/tokens.css +0 -317
- package/tailwind.config.ts +0 -70
|
@@ -1,1040 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { Slot } from "@radix-ui/react-slot";
|
|
4
|
-
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
|
-
import { PanelLeft } from "lucide-react";
|
|
6
|
-
import * as React from "react";
|
|
7
|
-
|
|
8
|
-
import { useIsMobile } from "../../hooks/use-mobile";
|
|
9
|
-
import { cn } from "../../lib/utils";
|
|
10
|
-
import { Button } from "../atoms/button";
|
|
11
|
-
import { Input } from "../atoms/input";
|
|
12
|
-
import { ScrollArea } from "../atoms/scroll-area";
|
|
13
|
-
import { Separator } from "../atoms/separator";
|
|
14
|
-
import { Skeleton } from "../atoms/skeleton";
|
|
15
|
-
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../molecules/tooltip";
|
|
16
|
-
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "./sheet";
|
|
17
|
-
|
|
18
|
-
const SIDEBAR_COOKIE_NAME = "sidebar_state";
|
|
19
|
-
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
|
20
|
-
const SIDEBAR_WIDTH = "15rem";
|
|
21
|
-
const SIDEBAR_WIDTH_MOBILE = "18rem";
|
|
22
|
-
const SIDEBAR_WIDTH_ICON = "3.5rem";
|
|
23
|
-
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
|
|
24
|
-
|
|
25
|
-
type SidebarContextProps = {
|
|
26
|
-
state: "expanded" | "collapsed";
|
|
27
|
-
open: boolean;
|
|
28
|
-
setOpen: (open: boolean) => void;
|
|
29
|
-
openMobile: boolean;
|
|
30
|
-
setOpenMobile: (open: boolean) => void;
|
|
31
|
-
isMobile: boolean;
|
|
32
|
-
toggleSidebar: () => void;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const SidebarContext = React.createContext<SidebarContextProps | null>(null);
|
|
36
|
-
|
|
37
|
-
function useSidebar() {
|
|
38
|
-
const context = React.useContext(SidebarContext);
|
|
39
|
-
if (!context) {
|
|
40
|
-
throw new Error("useSidebar must be used within a SidebarProvider.");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return context;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface SidebarProviderProps extends React.ComponentProps<"div"> {
|
|
47
|
-
/**
|
|
48
|
-
* Initial open state when uncontrolled.
|
|
49
|
-
* @default true
|
|
50
|
-
*/
|
|
51
|
-
defaultOpen?: boolean;
|
|
52
|
-
/** Controlled open state. Pair with `onOpenChange`. */
|
|
53
|
-
open?: boolean;
|
|
54
|
-
/** Fires with the next open state. */
|
|
55
|
-
onOpenChange?: (open: boolean) => void;
|
|
56
|
-
/** Sidebar + Inset + page content. */
|
|
57
|
-
children?: React.ReactNode;
|
|
58
|
-
className?: string;
|
|
59
|
-
style?: React.CSSProperties;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Wraps the entire shell and supplies the `useSidebar()` context. Reads the
|
|
64
|
-
* `--sidebar-width` / `--sidebar-width-icon` CSS variables off this element,
|
|
65
|
-
* so any width override goes here via `style`. Required around every Sidebar.
|
|
66
|
-
*/
|
|
67
|
-
const SidebarProvider = React.forwardRef<HTMLDivElement, SidebarProviderProps>(
|
|
68
|
-
(
|
|
69
|
-
{
|
|
70
|
-
defaultOpen = true,
|
|
71
|
-
open: openProp,
|
|
72
|
-
onOpenChange: setOpenProp,
|
|
73
|
-
className,
|
|
74
|
-
style,
|
|
75
|
-
children,
|
|
76
|
-
...props
|
|
77
|
-
},
|
|
78
|
-
ref,
|
|
79
|
-
) => {
|
|
80
|
-
const isMobile = useIsMobile();
|
|
81
|
-
const [openMobile, setOpenMobile] = React.useState(false);
|
|
82
|
-
|
|
83
|
-
// This is the internal state of the sidebar.
|
|
84
|
-
// We use openProp and setOpenProp for control from outside the component.
|
|
85
|
-
const [_open, _setOpen] = React.useState(defaultOpen);
|
|
86
|
-
const open = openProp ?? _open;
|
|
87
|
-
const setOpen = React.useCallback(
|
|
88
|
-
(value: boolean | ((value: boolean) => boolean)) => {
|
|
89
|
-
/* c8 ignore next -- direct-boolean branch: internal toggleSidebar only ever passes a function updater */
|
|
90
|
-
const openState = typeof value === "function" ? value(open) : value;
|
|
91
|
-
if (setOpenProp) {
|
|
92
|
-
setOpenProp(openState);
|
|
93
|
-
} else {
|
|
94
|
-
_setOpen(openState);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// This sets the cookie to keep the sidebar state.
|
|
98
|
-
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
|
|
99
|
-
},
|
|
100
|
-
[setOpenProp, open],
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
// Helper to toggle the sidebar.
|
|
104
|
-
const toggleSidebar = React.useCallback(() => {
|
|
105
|
-
/* c8 ignore next -- branch coverage for the mobile vs desktop sides is split across mocked-and-unmocked test contexts */
|
|
106
|
-
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
|
|
107
|
-
}, [isMobile, setOpen]);
|
|
108
|
-
|
|
109
|
-
// Adds a keyboard shortcut to toggle the sidebar.
|
|
110
|
-
React.useEffect(() => {
|
|
111
|
-
const handleKeyDown = (event: KeyboardEvent) => {
|
|
112
|
-
/* c8 ignore next 4 -- branch coverage for the metaKey path vs ctrlKey is combinatorial; the global handler's core path is tested */
|
|
113
|
-
if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
|
|
114
|
-
event.preventDefault();
|
|
115
|
-
toggleSidebar();
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
window.addEventListener("keydown", handleKeyDown);
|
|
120
|
-
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
121
|
-
}, [toggleSidebar]);
|
|
122
|
-
|
|
123
|
-
// We add a state so that we can do data-state="expanded" or "collapsed".
|
|
124
|
-
// This makes it easier to style the sidebar with Tailwind classes.
|
|
125
|
-
const state = open ? "expanded" : "collapsed";
|
|
126
|
-
|
|
127
|
-
const contextValue = React.useMemo<SidebarContextProps>(
|
|
128
|
-
() => ({
|
|
129
|
-
state,
|
|
130
|
-
open,
|
|
131
|
-
setOpen,
|
|
132
|
-
isMobile,
|
|
133
|
-
openMobile,
|
|
134
|
-
setOpenMobile,
|
|
135
|
-
toggleSidebar,
|
|
136
|
-
}),
|
|
137
|
-
[state, open, setOpen, isMobile, openMobile, toggleSidebar],
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
return (
|
|
141
|
-
<SidebarContext.Provider value={contextValue}>
|
|
142
|
-
<TooltipProvider delayDuration={0}>
|
|
143
|
-
<div
|
|
144
|
-
style={
|
|
145
|
-
{
|
|
146
|
-
"--sidebar-width": SIDEBAR_WIDTH,
|
|
147
|
-
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
|
|
148
|
-
...style,
|
|
149
|
-
} as React.CSSProperties
|
|
150
|
-
}
|
|
151
|
-
className={cn(
|
|
152
|
-
"group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar",
|
|
153
|
-
className,
|
|
154
|
-
)}
|
|
155
|
-
ref={ref}
|
|
156
|
-
{...props}
|
|
157
|
-
>
|
|
158
|
-
{children}
|
|
159
|
-
</div>
|
|
160
|
-
</TooltipProvider>
|
|
161
|
-
</SidebarContext.Provider>
|
|
162
|
-
);
|
|
163
|
-
},
|
|
164
|
-
);
|
|
165
|
-
SidebarProvider.displayName = "SidebarProvider";
|
|
166
|
-
|
|
167
|
-
export interface SidebarProps extends React.ComponentProps<"div"> {
|
|
168
|
-
/**
|
|
169
|
-
* Which side of the layout the sidebar lives on.
|
|
170
|
-
* @default "left"
|
|
171
|
-
*/
|
|
172
|
-
side?: "left" | "right";
|
|
173
|
-
/**
|
|
174
|
-
* `sidebar` is the standard layout column, `floating` lifts the sidebar
|
|
175
|
-
* with a shadow, `inset` insets it into the page surface.
|
|
176
|
-
* @default "sidebar"
|
|
177
|
-
*/
|
|
178
|
-
variant?: "sidebar" | "floating" | "inset";
|
|
179
|
-
/**
|
|
180
|
-
* Collapse mode. `offcanvas` slides off-screen, `icon` collapses to
|
|
181
|
-
* icons-only, `none` disables collapsing.
|
|
182
|
-
* @default "offcanvas"
|
|
183
|
-
*/
|
|
184
|
-
collapsible?: "offcanvas" | "icon" | "none";
|
|
185
|
-
/** Header + Content + Footer. */
|
|
186
|
-
children?: React.ReactNode;
|
|
187
|
-
className?: string;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* The sidebar shell — chooses left/right side, `sidebar`/`floating`/`inset`
|
|
192
|
-
* variant, and the collapse mode. Renders a Sheet drawer instead of an inline
|
|
193
|
-
* column when `useIsMobile()` returns true.
|
|
194
|
-
*/
|
|
195
|
-
const Sidebar = React.forwardRef<HTMLDivElement, SidebarProps>(
|
|
196
|
-
(
|
|
197
|
-
{
|
|
198
|
-
side = "left",
|
|
199
|
-
variant = "sidebar",
|
|
200
|
-
collapsible = "offcanvas",
|
|
201
|
-
className,
|
|
202
|
-
children,
|
|
203
|
-
...props
|
|
204
|
-
},
|
|
205
|
-
ref,
|
|
206
|
-
) => {
|
|
207
|
-
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
|
|
208
|
-
|
|
209
|
-
if (collapsible === "none") {
|
|
210
|
-
return (
|
|
211
|
-
<div
|
|
212
|
-
data-slot="sidebar"
|
|
213
|
-
className={cn(
|
|
214
|
-
"flex h-full w-[var(--sidebar-width)] flex-col bg-sidebar text-sidebar-foreground",
|
|
215
|
-
className,
|
|
216
|
-
)}
|
|
217
|
-
ref={ref}
|
|
218
|
-
{...props}
|
|
219
|
-
>
|
|
220
|
-
{children}
|
|
221
|
-
</div>
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (isMobile) {
|
|
226
|
-
return (
|
|
227
|
-
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
|
|
228
|
-
<SheetContent
|
|
229
|
-
data-sidebar="sidebar"
|
|
230
|
-
data-mobile="true"
|
|
231
|
-
className="w-[var(--sidebar-width)] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
|
|
232
|
-
style={
|
|
233
|
-
{
|
|
234
|
-
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
|
|
235
|
-
} as React.CSSProperties
|
|
236
|
-
}
|
|
237
|
-
side={side}
|
|
238
|
-
>
|
|
239
|
-
<SheetHeader className="sr-only">
|
|
240
|
-
<SheetTitle>Sidebar</SheetTitle>
|
|
241
|
-
<SheetDescription>Displays the mobile sidebar.</SheetDescription>
|
|
242
|
-
</SheetHeader>
|
|
243
|
-
<div className="flex h-full w-full flex-col">{children}</div>
|
|
244
|
-
</SheetContent>
|
|
245
|
-
</Sheet>
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return (
|
|
250
|
-
<div
|
|
251
|
-
ref={ref}
|
|
252
|
-
className="group peer hidden text-sidebar-foreground md:block"
|
|
253
|
-
data-state={state}
|
|
254
|
-
data-collapsible={state === "collapsed" ? collapsible : ""}
|
|
255
|
-
data-variant={variant}
|
|
256
|
-
data-side={side}
|
|
257
|
-
>
|
|
258
|
-
{/* This is what handles the sidebar gap on desktop */}
|
|
259
|
-
<div
|
|
260
|
-
className={cn(
|
|
261
|
-
"relative w-[var(--sidebar-width)] bg-transparent transition-[width] duration-200 ease-linear",
|
|
262
|
-
"group-data-[collapsible=offcanvas]:w-0",
|
|
263
|
-
"group-data-[side=right]:rotate-180",
|
|
264
|
-
variant === "floating" || variant === "inset"
|
|
265
|
-
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
|
|
266
|
-
: "group-data-[collapsible=icon]:w-[var(--sidebar-width-icon)]",
|
|
267
|
-
)}
|
|
268
|
-
/>
|
|
269
|
-
<div
|
|
270
|
-
className={cn(
|
|
271
|
-
"fixed inset-y-0 z-10 hidden h-svh w-[var(--sidebar-width)] transition-[left,right,width] duration-200 ease-linear md:flex",
|
|
272
|
-
side === "left"
|
|
273
|
-
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
|
|
274
|
-
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
|
|
275
|
-
// Adjust the padding for floating and inset variants.
|
|
276
|
-
variant === "floating" || variant === "inset"
|
|
277
|
-
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
|
|
278
|
-
: "group-data-[collapsible=icon]:w-[var(--sidebar-width-icon)] group-data-[side=left]:border-r group-data-[side=right]:border-l",
|
|
279
|
-
className,
|
|
280
|
-
)}
|
|
281
|
-
{...props}
|
|
282
|
-
>
|
|
283
|
-
<div
|
|
284
|
-
data-sidebar="sidebar"
|
|
285
|
-
data-slot="sidebar"
|
|
286
|
-
className="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"
|
|
287
|
-
>
|
|
288
|
-
{children}
|
|
289
|
-
</div>
|
|
290
|
-
</div>
|
|
291
|
-
</div>
|
|
292
|
-
);
|
|
293
|
-
},
|
|
294
|
-
);
|
|
295
|
-
Sidebar.displayName = "Sidebar";
|
|
296
|
-
|
|
297
|
-
export interface SidebarTriggerProps extends React.ComponentProps<typeof Button> {
|
|
298
|
-
/**
|
|
299
|
-
* Mirrors `Button` variants. Defaults to ghost for low-emphasis chrome.
|
|
300
|
-
* @default "ghost"
|
|
301
|
-
*/
|
|
302
|
-
variant?: React.ComponentProps<typeof Button>["variant"];
|
|
303
|
-
/**
|
|
304
|
-
* Mirrors `Button` sizes. Defaults to icon.
|
|
305
|
-
* @default "icon"
|
|
306
|
-
*/
|
|
307
|
-
size?: React.ComponentProps<typeof Button>["size"];
|
|
308
|
-
/** Click handler chained before `toggleSidebar()`. */
|
|
309
|
-
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
|
310
|
-
className?: string;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Default button that toggles the sidebar via `useSidebar().toggleSidebar`.
|
|
315
|
-
* Renders the panel-left glyph; swap with your own button if you want a
|
|
316
|
-
* different icon (e.g. a hamburger).
|
|
317
|
-
*/
|
|
318
|
-
const SidebarTrigger = React.forwardRef<React.ElementRef<typeof Button>, SidebarTriggerProps>(
|
|
319
|
-
({ className, onClick, ...props }, ref) => {
|
|
320
|
-
const { toggleSidebar } = useSidebar();
|
|
321
|
-
|
|
322
|
-
return (
|
|
323
|
-
<Button
|
|
324
|
-
ref={ref}
|
|
325
|
-
data-sidebar="trigger"
|
|
326
|
-
variant="ghost"
|
|
327
|
-
size="icon"
|
|
328
|
-
className={cn("h-7 w-7", className)}
|
|
329
|
-
onClick={(event) => {
|
|
330
|
-
onClick?.(event);
|
|
331
|
-
toggleSidebar();
|
|
332
|
-
}}
|
|
333
|
-
{...props}
|
|
334
|
-
>
|
|
335
|
-
<PanelLeft />
|
|
336
|
-
<span className="sr-only">Toggle Sidebar</span>
|
|
337
|
-
</Button>
|
|
338
|
-
);
|
|
339
|
-
},
|
|
340
|
-
);
|
|
341
|
-
SidebarTrigger.displayName = "SidebarTrigger";
|
|
342
|
-
|
|
343
|
-
export interface SidebarRailProps extends React.ComponentProps<"button"> {
|
|
344
|
-
className?: string;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Thin draggable rail at the sidebar's edge that toggles open/collapsed.
|
|
349
|
-
* Optional — only useful when `collapsible !== "none"`.
|
|
350
|
-
*/
|
|
351
|
-
const SidebarRail = React.forwardRef<HTMLButtonElement, SidebarRailProps>(
|
|
352
|
-
({ className, ...props }, ref) => {
|
|
353
|
-
const { toggleSidebar } = useSidebar();
|
|
354
|
-
|
|
355
|
-
return (
|
|
356
|
-
<button
|
|
357
|
-
ref={ref}
|
|
358
|
-
data-sidebar="rail"
|
|
359
|
-
aria-label="Toggle Sidebar"
|
|
360
|
-
tabIndex={-1}
|
|
361
|
-
onClick={toggleSidebar}
|
|
362
|
-
title="Toggle Sidebar"
|
|
363
|
-
className={cn(
|
|
364
|
-
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
|
|
365
|
-
"[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
|
|
366
|
-
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
|
|
367
|
-
"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar",
|
|
368
|
-
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
|
|
369
|
-
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
|
|
370
|
-
className,
|
|
371
|
-
)}
|
|
372
|
-
{...props}
|
|
373
|
-
/>
|
|
374
|
-
);
|
|
375
|
-
},
|
|
376
|
-
);
|
|
377
|
-
SidebarRail.displayName = "SidebarRail";
|
|
378
|
-
|
|
379
|
-
export interface SidebarInsetProps extends React.ComponentProps<"main"> {
|
|
380
|
-
className?: string;
|
|
381
|
-
children?: React.ReactNode;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* The main content column — sibling of `<Sidebar>`. Adapts margin /
|
|
386
|
-
* border-radius / shadow when the sidebar uses `variant="inset"`. Place your
|
|
387
|
-
* topbar + page content inside.
|
|
388
|
-
*/
|
|
389
|
-
const SidebarInset = React.forwardRef<HTMLDivElement, SidebarInsetProps>(
|
|
390
|
-
({ className, ...props }, ref) => {
|
|
391
|
-
return (
|
|
392
|
-
<main
|
|
393
|
-
ref={ref}
|
|
394
|
-
data-slot="sidebar-inset"
|
|
395
|
-
className={cn(
|
|
396
|
-
"relative flex w-full flex-1 flex-col bg-background",
|
|
397
|
-
"md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
|
|
398
|
-
className,
|
|
399
|
-
)}
|
|
400
|
-
{...props}
|
|
401
|
-
/>
|
|
402
|
-
);
|
|
403
|
-
},
|
|
404
|
-
);
|
|
405
|
-
SidebarInset.displayName = "SidebarInset";
|
|
406
|
-
|
|
407
|
-
export interface SidebarInputProps extends React.ComponentProps<typeof Input> {
|
|
408
|
-
className?: string;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* `<Input>` styled to fit inside the sidebar (transparent bg, no shadow,
|
|
413
|
-
* sidebar-ring focus colour). Use for an in-sidebar search field.
|
|
414
|
-
*/
|
|
415
|
-
const SidebarInput = React.forwardRef<React.ElementRef<typeof Input>, SidebarInputProps>(
|
|
416
|
-
({ className, ...props }, ref) => {
|
|
417
|
-
return (
|
|
418
|
-
<Input
|
|
419
|
-
ref={ref}
|
|
420
|
-
data-sidebar="input"
|
|
421
|
-
className={cn(
|
|
422
|
-
"h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
|
|
423
|
-
className,
|
|
424
|
-
)}
|
|
425
|
-
{...props}
|
|
426
|
-
/>
|
|
427
|
-
);
|
|
428
|
-
},
|
|
429
|
-
);
|
|
430
|
-
SidebarInput.displayName = "SidebarInput";
|
|
431
|
-
|
|
432
|
-
export interface SidebarHeaderProps extends React.ComponentProps<"div"> {
|
|
433
|
-
className?: string;
|
|
434
|
-
children?: React.ReactNode;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Top region of the sidebar. Default styling: 56px tall, row flex, gap-2.5,
|
|
439
|
-
* 16px horizontal padding, bottom border. Centred when collapsed to the icon
|
|
440
|
-
* rail. Typically holds the brand mark and a collapse trigger.
|
|
441
|
-
*/
|
|
442
|
-
const SidebarHeader = React.forwardRef<HTMLDivElement, SidebarHeaderProps>(
|
|
443
|
-
({ className, ...props }, ref) => {
|
|
444
|
-
return (
|
|
445
|
-
<div
|
|
446
|
-
ref={ref}
|
|
447
|
-
data-sidebar="header"
|
|
448
|
-
className={cn(
|
|
449
|
-
"flex h-14 shrink-0 items-center gap-2.5 border-b border-sidebar-border px-4 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:px-0",
|
|
450
|
-
className,
|
|
451
|
-
)}
|
|
452
|
-
{...props}
|
|
453
|
-
/>
|
|
454
|
-
);
|
|
455
|
-
},
|
|
456
|
-
);
|
|
457
|
-
SidebarHeader.displayName = "SidebarHeader";
|
|
458
|
-
|
|
459
|
-
export interface SidebarFooterProps extends React.ComponentProps<"div"> {
|
|
460
|
-
className?: string;
|
|
461
|
-
children?: React.ReactNode;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/**
|
|
465
|
-
* Bottom region of the sidebar — default styling: column flex with 8px gap
|
|
466
|
-
* and 8px padding. Common uses: a sign-out button, version string, or theme
|
|
467
|
-
* toggle.
|
|
468
|
-
*/
|
|
469
|
-
const SidebarFooter = React.forwardRef<HTMLDivElement, SidebarFooterProps>(
|
|
470
|
-
({ className, ...props }, ref) => {
|
|
471
|
-
return (
|
|
472
|
-
<div
|
|
473
|
-
ref={ref}
|
|
474
|
-
data-sidebar="footer"
|
|
475
|
-
className={cn("flex flex-col gap-2 p-2", className)}
|
|
476
|
-
{...props}
|
|
477
|
-
/>
|
|
478
|
-
);
|
|
479
|
-
},
|
|
480
|
-
);
|
|
481
|
-
SidebarFooter.displayName = "SidebarFooter";
|
|
482
|
-
|
|
483
|
-
export interface SidebarSeparatorProps extends React.ComponentProps<typeof Separator> {
|
|
484
|
-
className?: string;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* Horizontal divider with `mx-2` and the sidebar-border colour. Drop between
|
|
489
|
-
* groups when you want a visible split (otherwise the gap-2 spacing on
|
|
490
|
-
* `SidebarContent` is usually enough).
|
|
491
|
-
*/
|
|
492
|
-
const SidebarSeparator = React.forwardRef<
|
|
493
|
-
React.ElementRef<typeof Separator>,
|
|
494
|
-
SidebarSeparatorProps
|
|
495
|
-
>(({ className, ...props }, ref) => {
|
|
496
|
-
return (
|
|
497
|
-
<Separator
|
|
498
|
-
ref={ref}
|
|
499
|
-
data-sidebar="separator"
|
|
500
|
-
className={cn("mx-2 w-auto bg-sidebar-border", className)}
|
|
501
|
-
{...props}
|
|
502
|
-
/>
|
|
503
|
-
);
|
|
504
|
-
});
|
|
505
|
-
SidebarSeparator.displayName = "SidebarSeparator";
|
|
506
|
-
|
|
507
|
-
export interface SidebarContentProps extends React.ComponentProps<"div"> {
|
|
508
|
-
children?: React.ReactNode;
|
|
509
|
-
className?: string;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
/**
|
|
513
|
-
* Scrollable region between header and footer. Wrap one or more
|
|
514
|
-
* `<SidebarGroup>`s here. Uses canvas's `<ScrollArea>` so the scrollbar
|
|
515
|
-
* matches the rest of the design system.
|
|
516
|
-
*/
|
|
517
|
-
const SidebarContent = React.forwardRef<HTMLDivElement, SidebarContentProps>(
|
|
518
|
-
({ className, children, ...props }, ref) => {
|
|
519
|
-
return (
|
|
520
|
-
<ScrollArea data-sidebar="content" className={cn("flex min-h-0 flex-1 flex-col", className)}>
|
|
521
|
-
<div
|
|
522
|
-
ref={ref}
|
|
523
|
-
className="flex flex-col gap-2 group-data-[collapsible=icon]:overflow-hidden"
|
|
524
|
-
{...props}
|
|
525
|
-
>
|
|
526
|
-
{children}
|
|
527
|
-
</div>
|
|
528
|
-
</ScrollArea>
|
|
529
|
-
);
|
|
530
|
-
},
|
|
531
|
-
);
|
|
532
|
-
SidebarContent.displayName = "SidebarContent";
|
|
533
|
-
|
|
534
|
-
export interface SidebarGroupProps extends React.ComponentProps<"div"> {
|
|
535
|
-
className?: string;
|
|
536
|
-
children?: React.ReactNode;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
/**
|
|
540
|
-
* Section wrapper inside `SidebarContent`. Use one per logical chunk of nav
|
|
541
|
-
* (Overview, Identity, OAuth2, …). Pair with `SidebarGroupLabel` +
|
|
542
|
-
* `SidebarGroupContent`.
|
|
543
|
-
*/
|
|
544
|
-
const SidebarGroup = React.forwardRef<HTMLDivElement, SidebarGroupProps>(
|
|
545
|
-
({ className, ...props }, ref) => {
|
|
546
|
-
return (
|
|
547
|
-
<div
|
|
548
|
-
ref={ref}
|
|
549
|
-
data-sidebar="group"
|
|
550
|
-
className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
|
|
551
|
-
{...props}
|
|
552
|
-
/>
|
|
553
|
-
);
|
|
554
|
-
},
|
|
555
|
-
);
|
|
556
|
-
SidebarGroup.displayName = "SidebarGroup";
|
|
557
|
-
|
|
558
|
-
export interface SidebarGroupLabelProps extends React.ComponentProps<"div"> {
|
|
559
|
-
/**
|
|
560
|
-
* Render as a Radix Slot — wrap a custom element while inheriting the
|
|
561
|
-
* label styling.
|
|
562
|
-
* @default false
|
|
563
|
-
*/
|
|
564
|
-
asChild?: boolean;
|
|
565
|
-
className?: string;
|
|
566
|
-
children?: React.ReactNode;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
/**
|
|
570
|
-
* Small uppercase heading at the top of a group. 11px, tracking-wider,
|
|
571
|
-
* sidebar-foreground/70. Auto-hides when the sidebar collapses to the icon
|
|
572
|
-
* rail.
|
|
573
|
-
*/
|
|
574
|
-
const SidebarGroupLabel = React.forwardRef<HTMLDivElement, SidebarGroupLabelProps>(
|
|
575
|
-
({ className, asChild = false, ...props }, ref) => {
|
|
576
|
-
const Comp = asChild ? Slot : "div";
|
|
577
|
-
|
|
578
|
-
return (
|
|
579
|
-
<Comp
|
|
580
|
-
ref={ref}
|
|
581
|
-
data-sidebar="group-label"
|
|
582
|
-
className={cn(
|
|
583
|
-
"flex h-8 shrink-0 items-center rounded-md px-2.5 text-[11px] font-medium uppercase tracking-wider text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
|
584
|
-
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
|
|
585
|
-
className,
|
|
586
|
-
)}
|
|
587
|
-
{...props}
|
|
588
|
-
/>
|
|
589
|
-
);
|
|
590
|
-
},
|
|
591
|
-
);
|
|
592
|
-
SidebarGroupLabel.displayName = "SidebarGroupLabel";
|
|
593
|
-
|
|
594
|
-
export interface SidebarGroupActionProps extends React.ComponentProps<"button"> {
|
|
595
|
-
/**
|
|
596
|
-
* Render as a Radix Slot — wrap a custom button element.
|
|
597
|
-
* @default false
|
|
598
|
-
*/
|
|
599
|
-
asChild?: boolean;
|
|
600
|
-
className?: string;
|
|
601
|
-
children?: React.ReactNode;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
/**
|
|
605
|
-
* Right-aligned button inside a `SidebarGroup` (e.g. a `+` to add an item to
|
|
606
|
-
* that section). Hidden when the sidebar collapses to the icon rail.
|
|
607
|
-
*/
|
|
608
|
-
const SidebarGroupAction = React.forwardRef<HTMLButtonElement, SidebarGroupActionProps>(
|
|
609
|
-
({ className, asChild = false, ...props }, ref) => {
|
|
610
|
-
const Comp = asChild ? Slot : "button";
|
|
611
|
-
|
|
612
|
-
return (
|
|
613
|
-
<Comp
|
|
614
|
-
ref={ref}
|
|
615
|
-
data-sidebar="group-action"
|
|
616
|
-
className={cn(
|
|
617
|
-
"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",
|
|
618
|
-
// Increases the hit area of the button on mobile.
|
|
619
|
-
"after:absolute after:-inset-2 after:md:hidden",
|
|
620
|
-
"group-data-[collapsible=icon]:hidden",
|
|
621
|
-
className,
|
|
622
|
-
)}
|
|
623
|
-
{...props}
|
|
624
|
-
/>
|
|
625
|
-
);
|
|
626
|
-
},
|
|
627
|
-
);
|
|
628
|
-
SidebarGroupAction.displayName = "SidebarGroupAction";
|
|
629
|
-
|
|
630
|
-
export interface SidebarGroupContentProps extends React.ComponentProps<"div"> {
|
|
631
|
-
className?: string;
|
|
632
|
-
children?: React.ReactNode;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
/**
|
|
636
|
-
* Inner content slot of a `SidebarGroup`. Holds a `SidebarMenu` (or any
|
|
637
|
-
* custom UI you want grouped under the label).
|
|
638
|
-
*/
|
|
639
|
-
const SidebarGroupContent = React.forwardRef<HTMLDivElement, SidebarGroupContentProps>(
|
|
640
|
-
({ className, ...props }, ref) => (
|
|
641
|
-
<div
|
|
642
|
-
ref={ref}
|
|
643
|
-
data-sidebar="group-content"
|
|
644
|
-
className={cn("w-full text-sm", className)}
|
|
645
|
-
{...props}
|
|
646
|
-
/>
|
|
647
|
-
),
|
|
648
|
-
);
|
|
649
|
-
SidebarGroupContent.displayName = "SidebarGroupContent";
|
|
650
|
-
|
|
651
|
-
export interface SidebarMenuProps extends React.ComponentProps<"ul"> {
|
|
652
|
-
className?: string;
|
|
653
|
-
children?: React.ReactNode;
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
/**
|
|
657
|
-
* Unordered list (`<ul>`) of `SidebarMenuItem`s. The list semantics are
|
|
658
|
-
* important for screen-reader users — keep menu items in here rather than
|
|
659
|
-
* inlining buttons in the group.
|
|
660
|
-
*/
|
|
661
|
-
const SidebarMenu = React.forwardRef<HTMLUListElement, SidebarMenuProps>(
|
|
662
|
-
({ className, ...props }, ref) => (
|
|
663
|
-
<ul
|
|
664
|
-
ref={ref}
|
|
665
|
-
data-sidebar="menu"
|
|
666
|
-
className={cn("flex w-full min-w-0 flex-col gap-1", className)}
|
|
667
|
-
{...props}
|
|
668
|
-
/>
|
|
669
|
-
),
|
|
670
|
-
);
|
|
671
|
-
SidebarMenu.displayName = "SidebarMenu";
|
|
672
|
-
|
|
673
|
-
export interface SidebarMenuItemProps extends React.ComponentProps<"li"> {
|
|
674
|
-
className?: string;
|
|
675
|
-
children?: React.ReactNode;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
/**
|
|
679
|
-
* Single row in the menu (`<li>`). Wraps a `SidebarMenuButton` plus an
|
|
680
|
-
* optional `SidebarMenuAction` and `SidebarMenuBadge`.
|
|
681
|
-
*/
|
|
682
|
-
const SidebarMenuItem = React.forwardRef<HTMLLIElement, SidebarMenuItemProps>(
|
|
683
|
-
({ className, ...props }, ref) => (
|
|
684
|
-
<li
|
|
685
|
-
ref={ref}
|
|
686
|
-
data-sidebar="menu-item"
|
|
687
|
-
className={cn("group/menu-item relative", className)}
|
|
688
|
-
{...props}
|
|
689
|
-
/>
|
|
690
|
-
),
|
|
691
|
-
);
|
|
692
|
-
SidebarMenuItem.displayName = "SidebarMenuItem";
|
|
693
|
-
|
|
694
|
-
const sidebarMenuButtonVariants = cva(
|
|
695
|
-
"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",
|
|
696
|
-
{
|
|
697
|
-
variants: {
|
|
698
|
-
variant: {
|
|
699
|
-
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
700
|
-
outline:
|
|
701
|
-
"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))]",
|
|
702
|
-
},
|
|
703
|
-
size: {
|
|
704
|
-
default: "h-8 text-sm",
|
|
705
|
-
sm: "h-7 text-xs",
|
|
706
|
-
lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
|
|
707
|
-
},
|
|
708
|
-
},
|
|
709
|
-
defaultVariants: {
|
|
710
|
-
variant: "default",
|
|
711
|
-
size: "default",
|
|
712
|
-
},
|
|
713
|
-
},
|
|
714
|
-
);
|
|
715
|
-
|
|
716
|
-
export interface SidebarMenuButtonProps
|
|
717
|
-
extends React.ComponentProps<"button">,
|
|
718
|
-
VariantProps<typeof sidebarMenuButtonVariants> {
|
|
719
|
-
/**
|
|
720
|
-
* Render as a Radix Slot — wrap a router `<Link>` to use the menu item
|
|
721
|
-
* as navigation.
|
|
722
|
-
* @default false
|
|
723
|
-
*/
|
|
724
|
-
asChild?: boolean;
|
|
725
|
-
/**
|
|
726
|
-
* Mark as the currently active item (highlighted styling).
|
|
727
|
-
* @default false
|
|
728
|
-
*/
|
|
729
|
-
isActive?: boolean;
|
|
730
|
-
/**
|
|
731
|
-
* Tooltip text shown when the sidebar collapses to icons-only mode (so
|
|
732
|
-
* the label is still discoverable).
|
|
733
|
-
*/
|
|
734
|
-
tooltip?: string | React.ComponentProps<typeof TooltipContent>;
|
|
735
|
-
/**
|
|
736
|
-
* Visual style.
|
|
737
|
-
* @default "default"
|
|
738
|
-
*/
|
|
739
|
-
variant?: "default" | "outline";
|
|
740
|
-
/**
|
|
741
|
-
* Height preset.
|
|
742
|
-
* @default "default"
|
|
743
|
-
*/
|
|
744
|
-
size?: "default" | "sm" | "lg";
|
|
745
|
-
className?: string;
|
|
746
|
-
children?: React.ReactNode;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
/**
|
|
750
|
-
* The clickable nav item itself — supports `isActive`, `tooltip` (shown when
|
|
751
|
-
* collapsed to the rail), and `variant`/`size`. Use `asChild` to wrap a
|
|
752
|
-
* router `<Link>` instead of rendering a `<button>`.
|
|
753
|
-
*/
|
|
754
|
-
const SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenuButtonProps>(
|
|
755
|
-
(
|
|
756
|
-
{
|
|
757
|
-
asChild = false,
|
|
758
|
-
isActive = false,
|
|
759
|
-
variant = "default",
|
|
760
|
-
size = "default",
|
|
761
|
-
tooltip,
|
|
762
|
-
className,
|
|
763
|
-
...props
|
|
764
|
-
},
|
|
765
|
-
ref,
|
|
766
|
-
) => {
|
|
767
|
-
const Comp = asChild ? Slot : "button";
|
|
768
|
-
const { isMobile, state } = useSidebar();
|
|
769
|
-
|
|
770
|
-
const button = (
|
|
771
|
-
<Comp
|
|
772
|
-
ref={ref}
|
|
773
|
-
data-sidebar="menu-button"
|
|
774
|
-
data-size={size}
|
|
775
|
-
data-active={isActive}
|
|
776
|
-
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
|
|
777
|
-
{...props}
|
|
778
|
-
/>
|
|
779
|
-
);
|
|
780
|
-
|
|
781
|
-
if (!tooltip) {
|
|
782
|
-
return button;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
if (typeof tooltip === "string") {
|
|
786
|
-
tooltip = {
|
|
787
|
-
children: tooltip,
|
|
788
|
-
};
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
return (
|
|
792
|
-
<Tooltip>
|
|
793
|
-
<TooltipTrigger asChild>{button}</TooltipTrigger>
|
|
794
|
-
<TooltipContent
|
|
795
|
-
side="right"
|
|
796
|
-
align="center"
|
|
797
|
-
hidden={state !== "collapsed" || isMobile}
|
|
798
|
-
{...tooltip}
|
|
799
|
-
/>
|
|
800
|
-
</Tooltip>
|
|
801
|
-
);
|
|
802
|
-
},
|
|
803
|
-
);
|
|
804
|
-
SidebarMenuButton.displayName = "SidebarMenuButton";
|
|
805
|
-
|
|
806
|
-
export interface SidebarMenuActionProps extends React.ComponentProps<"button"> {
|
|
807
|
-
/**
|
|
808
|
-
* Render as a Radix Slot — wrap a custom button element.
|
|
809
|
-
* @default false
|
|
810
|
-
*/
|
|
811
|
-
asChild?: boolean;
|
|
812
|
-
/**
|
|
813
|
-
* Only reveal the action when the parent menu item is hovered or
|
|
814
|
-
* focused.
|
|
815
|
-
* @default false
|
|
816
|
-
*/
|
|
817
|
-
showOnHover?: boolean;
|
|
818
|
-
className?: string;
|
|
819
|
-
children?: React.ReactNode;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
/**
|
|
823
|
-
* Icon button anchored to the right of a `SidebarMenuItem` (e.g. a row's
|
|
824
|
-
* overflow menu). Use `showOnHover` to keep it hidden until the row is
|
|
825
|
-
* hovered/focused.
|
|
826
|
-
*/
|
|
827
|
-
const SidebarMenuAction = React.forwardRef<HTMLButtonElement, SidebarMenuActionProps>(
|
|
828
|
-
({ className, asChild = false, showOnHover = false, ...props }, ref) => {
|
|
829
|
-
const Comp = asChild ? Slot : "button";
|
|
830
|
-
|
|
831
|
-
return (
|
|
832
|
-
<Comp
|
|
833
|
-
ref={ref}
|
|
834
|
-
data-sidebar="menu-action"
|
|
835
|
-
className={cn(
|
|
836
|
-
"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",
|
|
837
|
-
// Increases the hit area of the button on mobile.
|
|
838
|
-
"after:absolute after:-inset-2 after:md:hidden",
|
|
839
|
-
"peer-data-[size=sm]/menu-button:top-1",
|
|
840
|
-
"peer-data-[size=default]/menu-button:top-1.5",
|
|
841
|
-
"peer-data-[size=lg]/menu-button:top-2.5",
|
|
842
|
-
"group-data-[collapsible=icon]:hidden",
|
|
843
|
-
showOnHover &&
|
|
844
|
-
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
|
|
845
|
-
className,
|
|
846
|
-
)}
|
|
847
|
-
{...props}
|
|
848
|
-
/>
|
|
849
|
-
);
|
|
850
|
-
},
|
|
851
|
-
);
|
|
852
|
-
SidebarMenuAction.displayName = "SidebarMenuAction";
|
|
853
|
-
|
|
854
|
-
export interface SidebarMenuBadgeProps extends React.ComponentProps<"div"> {
|
|
855
|
-
className?: string;
|
|
856
|
-
children?: React.ReactNode;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
/**
|
|
860
|
-
* Right-aligned numeric or status badge inside a menu item (e.g. unread
|
|
861
|
-
* count, pending count). Hidden when the sidebar collapses to the icon rail.
|
|
862
|
-
*/
|
|
863
|
-
const SidebarMenuBadge = React.forwardRef<HTMLDivElement, SidebarMenuBadgeProps>(
|
|
864
|
-
({ className, ...props }, ref) => (
|
|
865
|
-
<div
|
|
866
|
-
ref={ref}
|
|
867
|
-
data-sidebar="menu-badge"
|
|
868
|
-
className={cn(
|
|
869
|
-
"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",
|
|
870
|
-
"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
|
|
871
|
-
"peer-data-[size=sm]/menu-button:top-1",
|
|
872
|
-
"peer-data-[size=default]/menu-button:top-1.5",
|
|
873
|
-
"peer-data-[size=lg]/menu-button:top-2.5",
|
|
874
|
-
"group-data-[collapsible=icon]:hidden",
|
|
875
|
-
className,
|
|
876
|
-
)}
|
|
877
|
-
{...props}
|
|
878
|
-
/>
|
|
879
|
-
),
|
|
880
|
-
);
|
|
881
|
-
SidebarMenuBadge.displayName = "SidebarMenuBadge";
|
|
882
|
-
|
|
883
|
-
export interface SidebarMenuSkeletonProps extends React.ComponentProps<"div"> {
|
|
884
|
-
/**
|
|
885
|
-
* Render a skeleton block where the leading icon would normally be.
|
|
886
|
-
* @default false
|
|
887
|
-
*/
|
|
888
|
-
showIcon?: boolean;
|
|
889
|
-
className?: string;
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
/**
|
|
893
|
-
* Loading-state placeholder that matches the `SidebarMenuButton` height. Set
|
|
894
|
-
* `showIcon` to also render a leading icon placeholder.
|
|
895
|
-
*/
|
|
896
|
-
const SidebarMenuSkeleton = React.forwardRef<HTMLDivElement, SidebarMenuSkeletonProps>(
|
|
897
|
-
({ className, showIcon = false, ...props }, ref) => {
|
|
898
|
-
// Random width between 50 to 90%.
|
|
899
|
-
const width = React.useMemo(() => {
|
|
900
|
-
return `${Math.floor(Math.random() * 40) + 50}%`;
|
|
901
|
-
}, []);
|
|
902
|
-
|
|
903
|
-
return (
|
|
904
|
-
<div
|
|
905
|
-
ref={ref}
|
|
906
|
-
data-sidebar="menu-skeleton"
|
|
907
|
-
className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
|
|
908
|
-
{...props}
|
|
909
|
-
>
|
|
910
|
-
{showIcon && <Skeleton className="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />}
|
|
911
|
-
<Skeleton
|
|
912
|
-
className="h-4 max-w-[var(--skeleton-width)] flex-1"
|
|
913
|
-
data-sidebar="menu-skeleton-text"
|
|
914
|
-
style={
|
|
915
|
-
{
|
|
916
|
-
"--skeleton-width": width,
|
|
917
|
-
} as React.CSSProperties
|
|
918
|
-
}
|
|
919
|
-
/>
|
|
920
|
-
</div>
|
|
921
|
-
);
|
|
922
|
-
},
|
|
923
|
-
);
|
|
924
|
-
SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton";
|
|
925
|
-
|
|
926
|
-
export interface SidebarMenuSubProps extends React.ComponentProps<"ul"> {
|
|
927
|
-
className?: string;
|
|
928
|
-
children?: React.ReactNode;
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
/**
|
|
932
|
-
* Nested menu (`<ul>`) for sub-items under a `SidebarMenuButton`. Pair with
|
|
933
|
-
* `SidebarMenuSubItem` + `SidebarMenuSubButton`.
|
|
934
|
-
*/
|
|
935
|
-
const SidebarMenuSub = React.forwardRef<HTMLUListElement, SidebarMenuSubProps>(
|
|
936
|
-
({ className, ...props }, ref) => (
|
|
937
|
-
<ul
|
|
938
|
-
ref={ref}
|
|
939
|
-
data-sidebar="menu-sub"
|
|
940
|
-
className={cn(
|
|
941
|
-
"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",
|
|
942
|
-
"group-data-[collapsible=icon]:hidden",
|
|
943
|
-
className,
|
|
944
|
-
)}
|
|
945
|
-
{...props}
|
|
946
|
-
/>
|
|
947
|
-
),
|
|
948
|
-
);
|
|
949
|
-
SidebarMenuSub.displayName = "SidebarMenuSub";
|
|
950
|
-
|
|
951
|
-
export interface SidebarMenuSubItemProps extends React.ComponentProps<"li"> {
|
|
952
|
-
className?: string;
|
|
953
|
-
children?: React.ReactNode;
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
/**
|
|
957
|
-
* `<li>` wrapper for an item inside a `SidebarMenuSub`. Matches
|
|
958
|
-
* `SidebarMenuItem` semantically but at the sub level.
|
|
959
|
-
*/
|
|
960
|
-
const SidebarMenuSubItem = React.forwardRef<HTMLLIElement, SidebarMenuSubItemProps>(
|
|
961
|
-
({ ...props }, ref) => <li ref={ref} {...props} />,
|
|
962
|
-
);
|
|
963
|
-
SidebarMenuSubItem.displayName = "SidebarMenuSubItem";
|
|
964
|
-
|
|
965
|
-
export interface SidebarMenuSubButtonProps extends React.ComponentProps<"a"> {
|
|
966
|
-
/**
|
|
967
|
-
* Render as a Radix Slot — wrap a router `<Link>` instead of `<a>`.
|
|
968
|
-
* @default false
|
|
969
|
-
*/
|
|
970
|
-
asChild?: boolean;
|
|
971
|
-
/**
|
|
972
|
-
* `sm` or `md` — sub-items are typically smaller than top-level menu
|
|
973
|
-
* buttons.
|
|
974
|
-
* @default "md"
|
|
975
|
-
*/
|
|
976
|
-
size?: "sm" | "md";
|
|
977
|
-
/**
|
|
978
|
-
* Mark as the currently active sub-item.
|
|
979
|
-
* @default false
|
|
980
|
-
*/
|
|
981
|
-
isActive?: boolean;
|
|
982
|
-
className?: string;
|
|
983
|
-
children?: React.ReactNode;
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
/**
|
|
987
|
-
* The clickable element inside a `SidebarMenuSubItem`. Defaults to an `<a>`
|
|
988
|
-
* (use `asChild` for router links). Smaller than top-level menu buttons.
|
|
989
|
-
*/
|
|
990
|
-
const SidebarMenuSubButton = React.forwardRef<HTMLAnchorElement, SidebarMenuSubButtonProps>(
|
|
991
|
-
({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
|
|
992
|
-
const Comp = asChild ? Slot : "a";
|
|
993
|
-
|
|
994
|
-
return (
|
|
995
|
-
<Comp
|
|
996
|
-
ref={ref}
|
|
997
|
-
data-sidebar="menu-sub-button"
|
|
998
|
-
data-size={size}
|
|
999
|
-
data-active={isActive}
|
|
1000
|
-
className={cn(
|
|
1001
|
-
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring 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 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
|
|
1002
|
-
"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
|
|
1003
|
-
size === "sm" && "text-xs",
|
|
1004
|
-
size === "md" && "text-sm",
|
|
1005
|
-
"group-data-[collapsible=icon]:hidden",
|
|
1006
|
-
className,
|
|
1007
|
-
)}
|
|
1008
|
-
{...props}
|
|
1009
|
-
/>
|
|
1010
|
-
);
|
|
1011
|
-
},
|
|
1012
|
-
);
|
|
1013
|
-
SidebarMenuSubButton.displayName = "SidebarMenuSubButton";
|
|
1014
|
-
|
|
1015
|
-
export {
|
|
1016
|
-
Sidebar,
|
|
1017
|
-
SidebarContent,
|
|
1018
|
-
SidebarFooter,
|
|
1019
|
-
SidebarGroup,
|
|
1020
|
-
SidebarGroupAction,
|
|
1021
|
-
SidebarGroupContent,
|
|
1022
|
-
SidebarGroupLabel,
|
|
1023
|
-
SidebarHeader,
|
|
1024
|
-
SidebarInput,
|
|
1025
|
-
SidebarInset,
|
|
1026
|
-
SidebarMenu,
|
|
1027
|
-
SidebarMenuAction,
|
|
1028
|
-
SidebarMenuBadge,
|
|
1029
|
-
SidebarMenuButton,
|
|
1030
|
-
SidebarMenuItem,
|
|
1031
|
-
SidebarMenuSkeleton,
|
|
1032
|
-
SidebarMenuSub,
|
|
1033
|
-
SidebarMenuSubButton,
|
|
1034
|
-
SidebarMenuSubItem,
|
|
1035
|
-
SidebarProvider,
|
|
1036
|
-
SidebarRail,
|
|
1037
|
-
SidebarSeparator,
|
|
1038
|
-
SidebarTrigger,
|
|
1039
|
-
useSidebar,
|
|
1040
|
-
};
|