@olympusoss/canvas 2.6.19

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