@stll/ui 0.19.0 → 0.21.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.
@@ -0,0 +1,37 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/components/context-menu.d.ts
3
+ type ContextMenuAction = {
4
+ label: string;
5
+ icon?: ReactNode;
6
+ onClick?: () => void;
7
+ variant?: "default" | "destructive";
8
+ disabled?: boolean;
9
+ /** Set on a toggle-style action: it renders as a checkbox item whose
10
+ * state assistive technology can read. `onClick` flips it. */
11
+ checked?: boolean;
12
+ submenu?: readonly ContextMenuAction[];
13
+ /** Draw a divider above this item — e.g. to set a trailing "New …" action
14
+ * apart from the list of existing choices above it. */
15
+ separatorBefore?: boolean;
16
+ /** Keep the menu open after the click, for a toggle the user may flip
17
+ * several of in a row. */
18
+ closeOnClick?: boolean;
19
+ };
20
+ type ContextMenuProps = {
21
+ actions: readonly ContextMenuAction[];
22
+ children: ReactNode;
23
+ };
24
+ /**
25
+ * Wrap arbitrary content with a right-click context menu. Built on
26
+ * base-ui's `ContextMenu`, which is purpose-built for right-click /
27
+ * long-press triggers — it tracks the cursor anchor itself and uses
28
+ * dismissal semantics tuned for context menus, so the popup doesn't
29
+ * close on incidental pointer movement the way a hover/click `Menu`
30
+ * does.
31
+ *
32
+ * Renders only the children when `actions` is empty so callers can
33
+ * pass conditionally.
34
+ */
35
+ declare const ContextMenu: ({ actions, children }: ContextMenuProps) => ReactNode;
36
+ //#endregion
37
+ export { ContextMenu, ContextMenuAction, ContextMenuProps };
@@ -0,0 +1,48 @@
1
+ "use client";
2
+ import { cn } from "../lib/utils.js";
3
+ import { DropdownMenuCheckboxItem as MenuCheckboxItem, DropdownMenuContent as MenuPopup, DropdownMenuItem as MenuItem, DropdownMenuSeparator as MenuSeparator, DropdownMenuSub as MenuSub, DropdownMenuSubContent as MenuSubPopup, DropdownMenuSubTrigger as MenuSubTrigger } from "./menu.js";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { ContextMenu as ContextMenu$1 } from "@base-ui/react/context-menu";
6
+ //#region src/components/context-menu.tsx
7
+ /**
8
+ * Wrap arbitrary content with a right-click context menu. Built on
9
+ * base-ui's `ContextMenu`, which is purpose-built for right-click /
10
+ * long-press triggers — it tracks the cursor anchor itself and uses
11
+ * dismissal semantics tuned for context menus, so the popup doesn't
12
+ * close on incidental pointer movement the way a hover/click `Menu`
13
+ * does.
14
+ *
15
+ * Renders only the children when `actions` is empty so callers can
16
+ * pass conditionally.
17
+ */
18
+ const ContextMenu = ({ actions, children }) => {
19
+ if (actions.length === 0) return children;
20
+ return /* @__PURE__ */ jsxs(ContextMenu$1.Root, { children: [/* @__PURE__ */ jsx(ContextMenu$1.Trigger, {
21
+ "data-slot": "context-menu-trigger",
22
+ render: /* @__PURE__ */ jsx("div", { className: "contents" }),
23
+ children
24
+ }), /* @__PURE__ */ jsx(MenuPopup, {
25
+ "data-slot": "context-menu-popup",
26
+ children: actions.map((action) => /* @__PURE__ */ jsx(ContextMenuActionItem, { action }, action.label))
27
+ })] });
28
+ };
29
+ const ContextMenuActionItem = ({ action }) => {
30
+ const separator = action.separatorBefore ? /* @__PURE__ */ jsx(MenuSeparator, {}) : null;
31
+ if (action.submenu) return /* @__PURE__ */ jsxs(Fragment, { children: [separator, /* @__PURE__ */ jsxs(MenuSub, { children: [/* @__PURE__ */ jsxs(MenuSubTrigger, { children: [action.icon, action.label] }), /* @__PURE__ */ jsx(MenuSubPopup, { children: action.submenu.map((sub) => /* @__PURE__ */ jsx(ContextMenuActionItem, { action: sub }, sub.label)) })] })] });
32
+ if (action.checked !== void 0) return /* @__PURE__ */ jsxs(Fragment, { children: [separator, /* @__PURE__ */ jsxs(MenuCheckboxItem, {
33
+ checked: action.checked,
34
+ closeOnClick: action.closeOnClick ?? true,
35
+ disabled: action.disabled === true,
36
+ onCheckedChange: () => action.onClick?.(),
37
+ children: [action.icon, action.label]
38
+ })] });
39
+ return /* @__PURE__ */ jsxs(Fragment, { children: [separator, /* @__PURE__ */ jsxs(MenuItem, {
40
+ className: cn(action.variant === "destructive" && "text-destructive"),
41
+ closeOnClick: action.closeOnClick ?? true,
42
+ disabled: action.disabled === true,
43
+ onClick: action.onClick,
44
+ children: [action.icon, action.label]
45
+ })] });
46
+ };
47
+ //#endregion
48
+ export { ContextMenu };
@@ -5,7 +5,7 @@ import { VariantProps } from "class-variance-authority";
5
5
  //#region src/components/input-group.d.ts
6
6
  declare const InputGroup: ({ className, ...props }: React$1.ComponentProps<"div">) => React$1.JSX.Element;
7
7
  declare const inputGroupAddonVariants: (props?: ({
8
- align?: "inline-end" | "inline-start" | "block-end" | "block-start" | null | undefined;
8
+ align?: "inline-start" | "block-end" | "block-start" | "inline-end" | null | undefined;
9
9
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
10
10
  declare const InputGroupAddon: ({ className, align, ...props }: React$1.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) => React$1.JSX.Element;
11
11
  declare const InputGroupText: ({ className, ...props }: React$1.ComponentProps<"span">) => React$1.JSX.Element;
@@ -0,0 +1,109 @@
1
+ import { Button } from "./button.js";
2
+ import { Input } from "./input.js";
3
+ import { Separator } from "./separator.js";
4
+ import { TooltipContent as TooltipPopup } from "./tooltip.js";
5
+ import { VariantProps } from "class-variance-authority";
6
+ //#region src/components/sidebar.d.ts
7
+ /**
8
+ * Inline size the sidebar takes out of the layout row when expanded, in CSS
9
+ * pixels. Kept equal across the two rail-shaped primitives in this package:
10
+ * `SIDE_RAIL_WIDTH` (inspector) and `APPLICATION_RAIL_WIDTH` are both `w-12`
11
+ * (48px), the same as {@link SIDEBAR_WIDTH_ICON_PX} below, so a collapsed
12
+ * sidebar lines up with either rail.
13
+ */
14
+ declare const SIDEBAR_WIDTH_PX = 256;
15
+ /** Inline size of the collapsed ("icon") rail, in CSS pixels. See above. */
16
+ declare const SIDEBAR_WIDTH_ICON_PX = 48;
17
+ type SidebarContextProps = {
18
+ state: "expanded" | "collapsed";
19
+ open: boolean;
20
+ setOpen: (open: boolean) => void;
21
+ openMobile: boolean;
22
+ setOpenMobile: (open: boolean) => void;
23
+ isMobile: boolean;
24
+ toggleSidebar: () => void;
25
+ };
26
+ declare function useSidebar(): SidebarContextProps;
27
+ /**
28
+ * Inline size the sidebar takes out of the layout row, in CSS pixels. Panes
29
+ * docked to the opposite edge need this to know how much room is actually
30
+ * left for the content column. Mobile reports 0: there the sidebar is an
31
+ * overlay sheet and occupies no layout width.
32
+ */
33
+ declare function useSidebarInlineSize(): 0 | 48 | 256;
34
+ declare const SidebarProvider: ({ defaultOpen, forceCollapsed, open: openProp, onOpenChange: setOpenProp, className, style, children, ...props }: React.ComponentProps<"div"> & {
35
+ defaultOpen?: boolean;
36
+ forceCollapsed?: boolean;
37
+ open?: boolean;
38
+ onOpenChange?: (open: boolean) => void;
39
+ }) => import("react").JSX.Element;
40
+ declare const Sidebar: ({ side, variant, collapsible, mobileTitle, mobileDescription, className, children, ...props }: React.ComponentProps<"div"> & {
41
+ /**
42
+ * Which edge the sidebar docks to. Despite the physical name, this is
43
+ * applied as a *logical* position: `"left"` docks at the CSS inline-start
44
+ * edge and `"right"` docks at the inline-end edge (see the `start-0` /
45
+ * `end-0` classes below, keyed off `data-side`). Under `dir="rtl"` a
46
+ * `side="left"` sidebar therefore renders on the visual right, mirroring
47
+ * the reading direction rather than a fixed screen edge. `SidebarRail`
48
+ * derives its content-facing offset from `data-side` using the same
49
+ * left→end / right→start mapping so it keeps tracking the boundary
50
+ * between the sidebar and the content pane in both directions.
51
+ */
52
+ side?: "left" | "right";
53
+ variant?: "sidebar" | "floating" | "inset";
54
+ collapsible?: "offcanvas" | "icon" | "none";
55
+ /** Screen-reader title for the mobile sheet. Override to localize. */
56
+ mobileTitle?: string;
57
+ /** Screen-reader description for the mobile sheet. Override to localize. */
58
+ mobileDescription?: string;
59
+ }) => import("react").JSX.Element;
60
+ declare const SidebarTrigger: ({ className, onClick, label, "aria-label": ariaLabel, ...props }: React.ComponentProps<typeof Button> & {
61
+ /** Accessible name for the trigger. Override to localize. */
62
+ label?: string;
63
+ }) => import("react").JSX.Element;
64
+ declare const SidebarRail: ({ className, label, "aria-label": ariaLabel, ...props }: React.ComponentProps<"button"> & {
65
+ /** Accessible name for the rail. Override to localize. */
66
+ label?: string;
67
+ }) => import("react").JSX.Element;
68
+ declare const SidebarInset: ({ className, ...props }: React.ComponentProps<"main">) => import("react").JSX.Element;
69
+ declare const SidebarInput: ({ className, ...props }: React.ComponentProps<typeof Input>) => import("react").JSX.Element;
70
+ declare const SidebarHeader: ({ className, ...props }: React.ComponentProps<"div">) => import("react").JSX.Element;
71
+ declare const SidebarFooter: ({ className, ...props }: React.ComponentProps<"div">) => import("react").JSX.Element;
72
+ declare const SidebarSeparator: ({ className, ...props }: React.ComponentProps<typeof Separator>) => import("react").JSX.Element;
73
+ declare const SidebarContent: ({ className, ...props }: React.ComponentProps<"div">) => import("react").JSX.Element;
74
+ declare const SidebarGroup: ({ className, ...props }: React.ComponentProps<"div">) => import("react").JSX.Element;
75
+ declare const SidebarGroupLabel: ({ className, asChild, ...props }: React.ComponentProps<"div"> & {
76
+ asChild?: boolean;
77
+ }) => import("react").JSX.Element;
78
+ declare const SidebarGroupAction: ({ className, asChild, ...props }: React.ComponentProps<"button"> & {
79
+ asChild?: boolean;
80
+ }) => import("react").JSX.Element;
81
+ declare const SidebarGroupContent: ({ className, ...props }: React.ComponentProps<"div">) => import("react").JSX.Element;
82
+ declare const SidebarMenu: ({ className, ...props }: React.ComponentProps<"ul">) => import("react").JSX.Element;
83
+ declare const SidebarMenuItem: ({ className, ...props }: React.ComponentProps<"li">) => import("react").JSX.Element;
84
+ declare const sidebarMenuButtonVariants: (props?: ({
85
+ variant?: "default" | "outline" | null | undefined;
86
+ size?: "default" | "lg" | "sm" | "rail" | null | undefined;
87
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
88
+ declare const SidebarMenuButton: ({ asChild, isActive, variant, size, tooltip, className, ...props }: React.ComponentProps<"button"> & {
89
+ asChild?: boolean;
90
+ isActive?: boolean;
91
+ tooltip?: string | React.ComponentProps<typeof TooltipPopup>;
92
+ } & VariantProps<typeof sidebarMenuButtonVariants>) => import("react").JSX.Element;
93
+ declare const SidebarMenuAction: ({ className, asChild, showOnHover, ...props }: React.ComponentProps<"button"> & {
94
+ asChild?: boolean;
95
+ showOnHover?: boolean;
96
+ }) => import("react").JSX.Element;
97
+ declare const SidebarMenuBadge: ({ className, ...props }: React.ComponentProps<"div">) => import("react").JSX.Element;
98
+ declare const SidebarMenuSkeleton: ({ className, showIcon, ...props }: React.ComponentProps<"div"> & {
99
+ showIcon?: boolean;
100
+ }) => import("react").JSX.Element;
101
+ declare const SidebarMenuSub: ({ className, ...props }: React.ComponentProps<"ul">) => import("react").JSX.Element;
102
+ declare const SidebarMenuSubItem: ({ className, ...props }: React.ComponentProps<"li">) => import("react").JSX.Element;
103
+ declare const SidebarMenuSubButton: ({ asChild, size, isActive, className, ...props }: React.ComponentProps<"a"> & {
104
+ asChild?: boolean;
105
+ size?: "sm" | "md";
106
+ isActive?: boolean;
107
+ }) => import("react").JSX.Element;
108
+ //#endregion
109
+ export { SIDEBAR_WIDTH_ICON_PX, SIDEBAR_WIDTH_PX, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, useSidebar, useSidebarInlineSize };
@@ -0,0 +1,361 @@
1
+ "use client";
2
+ import { cn } from "../lib/utils.js";
3
+ import { Tooltip, TooltipContent as TooltipPopup, TooltipTrigger } from "./tooltip.js";
4
+ import { useIsMobile } from "../hooks/use-mobile.js";
5
+ import { Button } from "./button.js";
6
+ import { Sheet, SheetContent as SheetPopup, SheetDescription, SheetHeader, SheetTitle } from "./sheet.js";
7
+ import { Input } from "./input.js";
8
+ import { Separator } from "./separator.js";
9
+ import { Slot } from "../lib/slot.js";
10
+ import { deriveSidebarState, isSidebarMenuButtonTooltipVisible, nextOpenMobile, nextRequestedOpen, resolveSidebarOpen } from "./sidebar.logic.js";
11
+ import { Skeleton } from "./skeleton.js";
12
+ import { PanelLeftIcon } from "lucide-react";
13
+ import { jsx, jsxs } from "react/jsx-runtime";
14
+ import { createContext, use, useId, useState } from "react";
15
+ import { cva } from "class-variance-authority";
16
+ import { panic } from "better-result";
17
+ //#region src/components/sidebar.tsx
18
+ /**
19
+ * Inline size the sidebar takes out of the layout row when expanded, in CSS
20
+ * pixels. Kept equal across the two rail-shaped primitives in this package:
21
+ * `SIDE_RAIL_WIDTH` (inspector) and `APPLICATION_RAIL_WIDTH` are both `w-12`
22
+ * (48px), the same as {@link SIDEBAR_WIDTH_ICON_PX} below, so a collapsed
23
+ * sidebar lines up with either rail.
24
+ */
25
+ const SIDEBAR_WIDTH_PX = 256;
26
+ /** Inline size of the collapsed ("icon") rail, in CSS pixels. See above. */
27
+ const SIDEBAR_WIDTH_ICON_PX = 48;
28
+ const REM_PX = 16;
29
+ const SIDEBAR_WIDTH = `${256 / REM_PX}rem`;
30
+ const SIDEBAR_WIDTH_MOBILE = "18rem";
31
+ const SIDEBAR_WIDTH_ICON = `${48 / REM_PX}rem`;
32
+ const DEFAULT_SIDEBAR_OPEN = true;
33
+ const DEFAULT_TOGGLE_LABEL = "Toggle Sidebar";
34
+ const DEFAULT_MOBILE_TITLE = "Sidebar";
35
+ const DEFAULT_MOBILE_DESCRIPTION = "Displays the mobile sidebar.";
36
+ const skeletonWidthFromId = (id) => {
37
+ let hash = 0;
38
+ for (const character of id) hash = (hash * 31 + (character.codePointAt(0) ?? 0)) % 40;
39
+ return `${hash + 50}%`;
40
+ };
41
+ const SidebarContext = createContext(null);
42
+ function useSidebar() {
43
+ const context = use(SidebarContext);
44
+ if (!context) panic("useSidebar must be used within a SidebarProvider.");
45
+ return context;
46
+ }
47
+ /**
48
+ * Inline size the sidebar takes out of the layout row, in CSS pixels. Panes
49
+ * docked to the opposite edge need this to know how much room is actually
50
+ * left for the content column. Mobile reports 0: there the sidebar is an
51
+ * overlay sheet and occupies no layout width.
52
+ */
53
+ function useSidebarInlineSize() {
54
+ const { isMobile, state } = useSidebar();
55
+ if (isMobile) return 0;
56
+ return state === "expanded" ? 256 : 48;
57
+ }
58
+ const SidebarProvider = ({ defaultOpen, forceCollapsed = false, open: openProp, onOpenChange: setOpenProp, className, style, children, ...props }) => {
59
+ const isMobile = useIsMobile();
60
+ const [openMobile, setOpenMobile] = useState(false);
61
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen ?? DEFAULT_SIDEBAR_OPEN);
62
+ const requestedOpen = openProp ?? uncontrolledOpen;
63
+ const open = resolveSidebarOpen({
64
+ forceCollapsed,
65
+ requestedOpen
66
+ });
67
+ const setOpen = (value) => {
68
+ const openState = typeof value === "function" ? value(requestedOpen) : value;
69
+ if (setOpenProp) setOpenProp(openState);
70
+ else setUncontrolledOpen(openState);
71
+ };
72
+ const toggleSidebar = () => isMobile ? setOpenMobile(nextOpenMobile) : setOpen(nextRequestedOpen);
73
+ const contextValue = {
74
+ state: deriveSidebarState(open),
75
+ open,
76
+ setOpen,
77
+ isMobile,
78
+ openMobile,
79
+ setOpenMobile,
80
+ toggleSidebar
81
+ };
82
+ const wrapperStyle = {
83
+ "--sidebar-width": SIDEBAR_WIDTH,
84
+ "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
85
+ ...style
86
+ };
87
+ return /* @__PURE__ */ jsx(SidebarContext, {
88
+ value: contextValue,
89
+ children: /* @__PURE__ */ jsx("div", {
90
+ className: cn("group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full", className),
91
+ "data-slot": "sidebar-wrapper",
92
+ style: wrapperStyle,
93
+ ...props,
94
+ children
95
+ })
96
+ });
97
+ };
98
+ const Sidebar = ({ side = "left", variant = "sidebar", collapsible = "offcanvas", mobileTitle = DEFAULT_MOBILE_TITLE, mobileDescription = DEFAULT_MOBILE_DESCRIPTION, className, children, ...props }) => {
99
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
100
+ if (collapsible === "none") return /* @__PURE__ */ jsx("div", {
101
+ className: cn("bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col", className),
102
+ "data-slot": "sidebar",
103
+ ...props,
104
+ children
105
+ });
106
+ if (isMobile) {
107
+ const mobileStyle = { "--sidebar-width": SIDEBAR_WIDTH_MOBILE };
108
+ return /* @__PURE__ */ jsx(Sheet, {
109
+ onOpenChange: setOpenMobile,
110
+ open: openMobile,
111
+ ...props,
112
+ children: /* @__PURE__ */ jsxs(SheetPopup, {
113
+ className: "bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden",
114
+ "data-mobile": "true",
115
+ "data-sidebar": "sidebar",
116
+ "data-slot": "sidebar",
117
+ side: side === "left" ? "inline-start" : "inline-end",
118
+ style: mobileStyle,
119
+ children: [/* @__PURE__ */ jsxs(SheetHeader, {
120
+ className: "sr-only",
121
+ children: [/* @__PURE__ */ jsx(SheetTitle, { children: mobileTitle }), /* @__PURE__ */ jsx(SheetDescription, { children: mobileDescription })]
122
+ }), /* @__PURE__ */ jsx("div", {
123
+ className: "flex h-full w-full flex-col",
124
+ children
125
+ })]
126
+ })
127
+ });
128
+ }
129
+ return /* @__PURE__ */ jsxs("div", {
130
+ className: "group peer text-sidebar-foreground hidden md:block",
131
+ "data-collapsible": state === "collapsed" ? collapsible : "",
132
+ "data-side": side,
133
+ "data-slot": "sidebar",
134
+ "data-state": state,
135
+ "data-variant": variant,
136
+ children: [/* @__PURE__ */ jsx("div", {
137
+ className: cn("relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear", "group-data-[collapsible=offcanvas]:w-0", "group-data-[side=right]:rotate-180", variant === "floating" || variant === "inset" ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)"),
138
+ "data-slot": "sidebar-gap"
139
+ }), /* @__PURE__ */ jsx("div", {
140
+ className: cn("fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[inset-inline-start,inset-inline-end,width] duration-200 ease-linear md:flex", side === "left" ? "start-0 group-data-[collapsible=offcanvas]:start-[calc(var(--sidebar-width)*-1)]" : "end-0 group-data-[collapsible=offcanvas]:end-[calc(var(--sidebar-width)*-1)]", variant === "floating" || variant === "inset" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-e group-data-[side=right]:border-s", className),
141
+ "data-slot": "sidebar-container",
142
+ ...props,
143
+ children: /* @__PURE__ */ jsx("div", {
144
+ className: "bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm",
145
+ "data-sidebar": "sidebar",
146
+ "data-slot": "sidebar-inner",
147
+ children
148
+ })
149
+ })]
150
+ });
151
+ };
152
+ const SidebarTrigger = ({ className, onClick, label = DEFAULT_TOGGLE_LABEL, "aria-label": ariaLabel, ...props }) => {
153
+ const { toggleSidebar } = useSidebar();
154
+ return /* @__PURE__ */ jsxs(Button, {
155
+ "aria-label": ariaLabel ?? label,
156
+ className: cn("size-7", className),
157
+ "data-sidebar": "trigger",
158
+ "data-slot": "sidebar-trigger",
159
+ onClick: (event) => {
160
+ onClick?.(event);
161
+ toggleSidebar();
162
+ },
163
+ size: "icon",
164
+ variant: "ghost",
165
+ ...props,
166
+ children: [/* @__PURE__ */ jsx(PanelLeftIcon, {}), /* @__PURE__ */ jsx("span", {
167
+ className: "sr-only",
168
+ children: label
169
+ })]
170
+ });
171
+ };
172
+ const SidebarRail = ({ className, label = DEFAULT_TOGGLE_LABEL, "aria-label": ariaLabel, ...props }) => {
173
+ const { toggleSidebar } = useSidebar();
174
+ return /* @__PURE__ */ jsx("button", {
175
+ "aria-label": ariaLabel ?? label,
176
+ type: "button",
177
+ className: cn("hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 transition-[transform,background-color] ease-linear group-data-[side=left]:-end-4 group-data-[side=right]:start-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] sm:flex", "ltr:-translate-x-1/2 rtl:translate-x-1/2", "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize", "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize", "hover:group-data-[collapsible=offcanvas]:bg-sidebar", "group-data-[collapsible=offcanvas]:ltr:translate-x-0 group-data-[collapsible=offcanvas]:rtl:translate-x-0", "group-data-[collapsible=offcanvas]:group-data-[side=left]:after:end-full group-data-[collapsible=offcanvas]:group-data-[side=right]:after:start-full", "[[data-side=left][data-collapsible=offcanvas]_&]:-end-2", "[[data-side=right][data-collapsible=offcanvas]_&]:-start-2", className),
178
+ "data-sidebar": "rail",
179
+ "data-slot": "sidebar-rail",
180
+ onClick: toggleSidebar,
181
+ tabIndex: -1,
182
+ ...props
183
+ });
184
+ };
185
+ const SidebarInset = ({ className, ...props }) => /* @__PURE__ */ jsx("main", {
186
+ className: cn("bg-background relative flex w-full flex-1 flex-col overflow-hidden", "md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ms-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ms-2", className),
187
+ "data-slot": "sidebar-inset",
188
+ ...props
189
+ });
190
+ const SidebarInput = ({ className, ...props }) => /* @__PURE__ */ jsx(Input, {
191
+ className: cn("bg-background h-8 w-full shadow-none", className),
192
+ "data-sidebar": "input",
193
+ "data-slot": "sidebar-input",
194
+ ...props
195
+ });
196
+ const SidebarHeader = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
197
+ className: cn("flex flex-col gap-2 p-2", className),
198
+ "data-sidebar": "header",
199
+ "data-slot": "sidebar-header",
200
+ ...props
201
+ });
202
+ const SidebarFooter = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
203
+ className: cn("flex flex-col gap-2 p-2", className),
204
+ "data-sidebar": "footer",
205
+ "data-slot": "sidebar-footer",
206
+ ...props
207
+ });
208
+ const SidebarSeparator = ({ className, ...props }) => /* @__PURE__ */ jsx(Separator, {
209
+ className: cn("bg-sidebar-border w-auto", className),
210
+ "data-sidebar": "separator",
211
+ "data-slot": "sidebar-separator",
212
+ ...props
213
+ });
214
+ const SidebarContent = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
215
+ className: cn("flex min-h-0 flex-1 flex-col gap-2 overflow-x-hidden overflow-y-auto group-data-[collapsible=icon]:[scrollbar-width:none] group-data-[collapsible=icon]:[&::-webkit-scrollbar]:hidden", className),
216
+ "data-sidebar": "content",
217
+ "data-slot": "sidebar-content",
218
+ ...props
219
+ });
220
+ const SidebarGroup = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
221
+ className: cn("relative flex w-full min-w-0 flex-col p-2", className),
222
+ "data-sidebar": "group",
223
+ "data-slot": "sidebar-group",
224
+ ...props
225
+ });
226
+ const SidebarGroupLabel = ({ className, asChild = false, ...props }) => {
227
+ return /* @__PURE__ */ jsx(asChild ? Slot : "div", {
228
+ className: cn("text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", "group-data-[collapsible=icon]:hidden", className),
229
+ "data-sidebar": "group-label",
230
+ "data-slot": "sidebar-group-label",
231
+ ...props
232
+ });
233
+ };
234
+ const SidebarGroupAction = ({ className, asChild = false, ...props }) => {
235
+ return /* @__PURE__ */ jsx(asChild ? Slot : "button", {
236
+ className: cn("text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute end-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", "after:absolute after:-inset-2 md:after:hidden", "group-data-[collapsible=icon]:hidden", className),
237
+ "data-sidebar": "group-action",
238
+ "data-slot": "sidebar-group-action",
239
+ ...props
240
+ });
241
+ };
242
+ const SidebarGroupContent = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
243
+ className: cn("w-full text-sm", className),
244
+ "data-sidebar": "group-content",
245
+ "data-slot": "sidebar-group-content",
246
+ ...props
247
+ });
248
+ const SidebarMenu = ({ className, ...props }) => /* @__PURE__ */ jsx("ul", {
249
+ className: cn("flex w-full min-w-0 flex-col gap-1", className),
250
+ "data-sidebar": "menu",
251
+ "data-slot": "sidebar-menu",
252
+ ...props
253
+ });
254
+ const SidebarMenuItem = ({ className, ...props }) => /* @__PURE__ */ jsx("li", {
255
+ className: cn("group/menu-item relative", className),
256
+ "data-sidebar": "menu-item",
257
+ "data-slot": "sidebar-menu-item",
258
+ ...props
259
+ });
260
+ const sidebarMenuButtonVariants = cva("peer/menu-button ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-[active=true]:bg-accent data-[active=true]:text-sidebar-accent-foreground data-[active=true]:hover:bg-sidebar-accent data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-start text-sm outline-hidden transition-[width,height,padding,border-radius] group-has-data-[sidebar=menu-action]/menu-item:pe-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:font-medium [&>span:last-child]:truncate [&>span:last-child]:transition-opacity [&>span:last-child]:duration-200 group-data-[collapsible=icon]:[&>span:last-child]:opacity-0 [&>svg]:size-4 [&>svg]:shrink-0", {
261
+ variants: {
262
+ variant: {
263
+ default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
264
+ outline: "bg-background hover:bg-sidebar-accent hover:text-sidebar-accent-foreground shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]"
265
+ },
266
+ size: {
267
+ default: "h-8 text-sm",
268
+ sm: "h-7 text-xs",
269
+ lg: "h-12 text-sm",
270
+ /** A 44px target in both states, for a sidebar that stands in for
271
+ * the application rail on touch and hybrid devices. */
272
+ rail: "h-11 text-sm group-data-[collapsible=icon]:size-11! group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:p-0! group-data-[collapsible=icon]:[&>span:last-child]:sr-only"
273
+ }
274
+ },
275
+ defaultVariants: {
276
+ variant: "default",
277
+ size: "default"
278
+ }
279
+ });
280
+ const SidebarMenuButton = ({ asChild = false, isActive = false, variant = "default", size = "default", tooltip, className, ...props }) => {
281
+ const Comp = asChild ? Slot : "button";
282
+ const { isMobile, state } = useSidebar();
283
+ const button = /* @__PURE__ */ jsx(Comp, {
284
+ className: cn(sidebarMenuButtonVariants({
285
+ variant,
286
+ size
287
+ }), className),
288
+ "data-active": isActive,
289
+ "data-sidebar": "menu-button",
290
+ "data-size": size,
291
+ "data-slot": "sidebar-menu-button",
292
+ ...props
293
+ });
294
+ if (tooltip === void 0) return button;
295
+ const tooltipProps = typeof tooltip === "string" ? { children: tooltip } : tooltip;
296
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, { render: button }), /* @__PURE__ */ jsx(TooltipPopup, {
297
+ align: "center",
298
+ hidden: !isSidebarMenuButtonTooltipVisible({
299
+ isMobile,
300
+ state
301
+ }),
302
+ side: "right",
303
+ ...tooltipProps
304
+ })] });
305
+ };
306
+ const SidebarMenuAction = ({ className, asChild = false, showOnHover = false, ...props }) => {
307
+ return /* @__PURE__ */ jsx(asChild ? Slot : "button", {
308
+ className: cn("text-sidebar-foreground ring-sidebar-ring peer-hover/menu-button:text-sidebar-accent-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute end-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", "after:absolute after:-inset-2 md:after:hidden", "peer-data-[size=sm]/menu-button:top-1", "peer-data-[size=default]/menu-button:top-1.5", "peer-data-[size=lg]/menu-button:top-2.5", "group-data-[collapsible=icon]:hidden", showOnHover && "peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0", className),
309
+ "data-sidebar": "menu-action",
310
+ "data-slot": "sidebar-menu-action",
311
+ ...props
312
+ });
313
+ };
314
+ const SidebarMenuBadge = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
315
+ className: cn("text-sidebar-foreground pointer-events-none absolute end-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none", "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground", "peer-data-[size=sm]/menu-button:top-1", "peer-data-[size=default]/menu-button:top-1.5", "peer-data-[size=lg]/menu-button:top-2.5", "transition-opacity duration-200 group-data-[collapsible=icon]:opacity-0", className),
316
+ "data-sidebar": "menu-badge",
317
+ "data-slot": "sidebar-menu-badge",
318
+ ...props
319
+ });
320
+ const SidebarMenuSkeleton = ({ className, showIcon = false, ...props }) => {
321
+ const skeletonId = useId();
322
+ const textStyle = { "--skeleton-width": skeletonWidthFromId(skeletonId) };
323
+ return /* @__PURE__ */ jsxs("div", {
324
+ className: cn("flex h-8 items-center gap-2 rounded-md px-2", className),
325
+ "data-sidebar": "menu-skeleton",
326
+ "data-slot": "sidebar-menu-skeleton",
327
+ ...props,
328
+ children: [showIcon && /* @__PURE__ */ jsx(Skeleton, {
329
+ className: "size-4 rounded-md",
330
+ "data-sidebar": "menu-skeleton-icon"
331
+ }), /* @__PURE__ */ jsx(Skeleton, {
332
+ className: "h-4 max-w-(--skeleton-width) flex-1",
333
+ "data-sidebar": "menu-skeleton-text",
334
+ style: textStyle
335
+ })]
336
+ });
337
+ };
338
+ const SidebarMenuSub = ({ className, ...props }) => /* @__PURE__ */ jsx("ul", {
339
+ className: cn("border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-s px-2.5 py-0.5", "group-data-[collapsible=icon]:hidden", className),
340
+ "data-sidebar": "menu-sub",
341
+ "data-slot": "sidebar-menu-sub",
342
+ ...props
343
+ });
344
+ const SidebarMenuSubItem = ({ className, ...props }) => /* @__PURE__ */ jsx("li", {
345
+ className: cn("group/menu-sub-item relative", className),
346
+ "data-sidebar": "menu-sub-item",
347
+ "data-slot": "sidebar-menu-sub-item",
348
+ ...props
349
+ });
350
+ const SidebarMenuSubButton = ({ asChild = false, size = "md", isActive = false, className, ...props }) => {
351
+ return /* @__PURE__ */ jsx(asChild ? Slot : "a", {
352
+ className: cn("text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground", size === "sm" && "text-xs", size === "md" && "text-sm", "group-data-[collapsible=icon]:hidden", className),
353
+ "data-active": isActive,
354
+ "data-sidebar": "menu-sub-button",
355
+ "data-size": size,
356
+ "data-slot": "sidebar-menu-sub-button",
357
+ ...props
358
+ });
359
+ };
360
+ //#endregion
361
+ export { SIDEBAR_WIDTH_ICON_PX, SIDEBAR_WIDTH_PX, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, useSidebar, useSidebarInlineSize };
@@ -0,0 +1,34 @@
1
+ //#region src/components/sidebar.logic.d.ts
2
+ type SidebarState = "expanded" | "collapsed";
3
+ /**
4
+ * A `SidebarMenuButton`'s tooltip repeats the label already visible in the
5
+ * expanded rail, and mobile never collapses to icons, so it only earns its
6
+ * keep on desktop while the sidebar is icon-collapsed.
7
+ */
8
+ declare const isSidebarMenuButtonTooltipVisible: ({ state, isMobile }: {
9
+ state: SidebarState;
10
+ isMobile: boolean;
11
+ }) => boolean;
12
+ /**
13
+ * The sidebar's displayed open state: the requested state (controlled prop,
14
+ * or the uncontrolled fallback), masked by `forceCollapsed`. A host can force
15
+ * the sidebar collapsed (e.g. a narrow viewport) without losing track of
16
+ * what the user actually asked for.
17
+ */
18
+ declare const resolveSidebarOpen: ({ requestedOpen, forceCollapsed }: {
19
+ requestedOpen: boolean;
20
+ forceCollapsed: boolean;
21
+ }) => boolean;
22
+ /** The `data-state` driven off the sidebar's displayed open state. */
23
+ declare const deriveSidebarState: (open: boolean) => SidebarState;
24
+ /**
25
+ * `toggleSidebar`'s desktop transition: it flips the *requested* open state,
26
+ * not the *displayed* one. `forceCollapsed` can mask a `true` requested state
27
+ * down to a collapsed display; toggling while forced-collapsed still records
28
+ * the flip so it takes effect once `forceCollapsed` lifts.
29
+ */
30
+ declare const nextRequestedOpen: (requestedOpen: boolean) => boolean;
31
+ /** `toggleSidebar`'s mobile transition: it flips the sheet's open state. */
32
+ declare const nextOpenMobile: (openMobile: boolean) => boolean;
33
+ //#endregion
34
+ export { SidebarState, deriveSidebarState, isSidebarMenuButtonTooltipVisible, nextOpenMobile, nextRequestedOpen, resolveSidebarOpen };
@@ -0,0 +1,27 @@
1
+ //#region src/components/sidebar.logic.ts
2
+ /**
3
+ * A `SidebarMenuButton`'s tooltip repeats the label already visible in the
4
+ * expanded rail, and mobile never collapses to icons, so it only earns its
5
+ * keep on desktop while the sidebar is icon-collapsed.
6
+ */
7
+ const isSidebarMenuButtonTooltipVisible = ({ state, isMobile }) => state === "collapsed" && !isMobile;
8
+ /**
9
+ * The sidebar's displayed open state: the requested state (controlled prop,
10
+ * or the uncontrolled fallback), masked by `forceCollapsed`. A host can force
11
+ * the sidebar collapsed (e.g. a narrow viewport) without losing track of
12
+ * what the user actually asked for.
13
+ */
14
+ const resolveSidebarOpen = ({ requestedOpen, forceCollapsed }) => requestedOpen && !forceCollapsed;
15
+ /** The `data-state` driven off the sidebar's displayed open state. */
16
+ const deriveSidebarState = (open) => open ? "expanded" : "collapsed";
17
+ /**
18
+ * `toggleSidebar`'s desktop transition: it flips the *requested* open state,
19
+ * not the *displayed* one. `forceCollapsed` can mask a `true` requested state
20
+ * down to a collapsed display; toggling while forced-collapsed still records
21
+ * the flip so it takes effect once `forceCollapsed` lifts.
22
+ */
23
+ const nextRequestedOpen = (requestedOpen) => !requestedOpen;
24
+ /** `toggleSidebar`'s mobile transition: it flips the sheet's open state. */
25
+ const nextOpenMobile = (openMobile) => !openMobile;
26
+ //#endregion
27
+ export { deriveSidebarState, isSidebarMenuButtonTooltipVisible, nextOpenMobile, nextRequestedOpen, resolveSidebarOpen };