@tradalab/lyra 1.0.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { ComponentProps, ReactNode } from 'react';
3
- import { S as SidebarProvider, a as SidebarInset, B as Breadcrumb } from '../breadcrumb-CGzHbSCd.js';
3
+ import { S as SidebarProvider, a as SidebarInset, b as Sidebar, A as AccountMenuUser, c as AccountMenuLabels, B as Breadcrumb } from '../breadcrumb-CgEcSonR.js';
4
4
  import 'class-variance-authority/types';
5
5
  import 'class-variance-authority';
6
6
  import 'radix-ui';
@@ -39,6 +39,47 @@ declare function AppShellHeader({ className, children, ...rest }: ComponentProps
39
39
  /** Scroll body inside `<AppShellMain>`. Sibling of `<AppShellHeader>`. */
40
40
  declare function AppShellBody({ className, children, ...rest }: ComponentProps<"div">): React.JSX.Element;
41
41
 
42
+ /**
43
+ * Dual / stacked sidebar wrapper — an icon rail plus one or more content
44
+ * panels rendered side-by-side inside a single `<Sidebar>`. This is the
45
+ * shadcn "sidebar-07" pattern.
46
+ *
47
+ * The `*:data-[sidebar=sidebar]:flex-row` trick lays the inner
48
+ * `[data-sidebar=sidebar]` wrappers of the nested {@link SidebarDock} /
49
+ * {@link SidebarPanel} columns out horizontally. `variant`/`collapsible`
50
+ * default to `"sidebar"`/`"icon"` but can be overridden.
51
+ *
52
+ * @example
53
+ * import { AppShell, AppShellMain, StackedSidebar, SidebarDock, SidebarPanel } from "@tradalab/lyra/shell"
54
+ * import { SidebarHeader, SidebarContent, SidebarMenu, SidebarMenuItem, SidebarMenuButton } from "@tradalab/lyra/ui"
55
+ *
56
+ * <AppShell>
57
+ * <StackedSidebar>
58
+ * <SidebarDock>
59
+ * <SidebarHeader>{logo}</SidebarHeader>
60
+ * <SidebarContent>{iconButtons}</SidebarContent>
61
+ * </SidebarDock>
62
+ * {tab === "files" ? <SidebarPanel>{tree}</SidebarPanel> : null}
63
+ * </StackedSidebar>
64
+ * <AppShellMain>{children}</AppShellMain>
65
+ * </AppShell>
66
+ */
67
+ declare function StackedSidebar({ className, ...rest }: ComponentProps<typeof Sidebar>): React.JSX.Element;
68
+ /**
69
+ * Narrow icon-rail column inside a {@link StackedSidebar}. Fixed to the icon
70
+ * width, never collapses on its own (the parent `StackedSidebar` owns the
71
+ * icon-mode toggle).
72
+ *
73
+ * Named `SidebarDock` — not `SidebarRail` — because `SidebarRail` already
74
+ * exists in `@tradalab/lyra/ui` as shadcn's drag-to-resize edge handle.
75
+ */
76
+ declare function SidebarDock({ className, ...rest }: ComponentProps<typeof Sidebar>): React.JSX.Element;
77
+ /**
78
+ * Wider content column inside a {@link StackedSidebar} (tree, list, …).
79
+ * Fills remaining width; render conditionally to show/hide per app state.
80
+ */
81
+ declare function SidebarPanel({ className, ...rest }: ComponentProps<typeof Sidebar>): React.JSX.Element;
82
+
42
83
  interface NavGroupProps {
43
84
  label?: ReactNode;
44
85
  children: ReactNode;
@@ -72,6 +113,134 @@ interface NavItemProps {
72
113
  /** Sidebar nav row. Must be rendered inside a {@link NavGroup}. */
73
114
  declare function NavItem({ href, label, icon, tooltip, matchMode, }: NavItemProps): React.JSX.Element;
74
115
 
116
+ interface NavSectionProps {
117
+ /** Optional group heading. */
118
+ label?: ReactNode;
119
+ /** Nav rows — each rendered as a {@link NavItem}. */
120
+ items: NavItemProps[];
121
+ }
122
+ /**
123
+ * Config-driven convenience over {@link NavGroup} + {@link NavItem}: pass a
124
+ * data array instead of hand-mapping JSX. For dynamic or conditional rows,
125
+ * compose `NavGroup`/`NavItem` directly.
126
+ *
127
+ * @example
128
+ * const NAV = [
129
+ * { href: "/files", label: "Files", icon: <Folder /> },
130
+ * { href: "/trash", label: "Trash", icon: <Trash2 /> },
131
+ * ]
132
+ * <NavSection label="Workspace" items={NAV} />
133
+ */
134
+ declare function NavSection({ label, items }: NavSectionProps): React.JSX.Element;
135
+
136
+ interface NavUserThemeLabels {
137
+ theme: string;
138
+ light: string;
139
+ dark: string;
140
+ system: string;
141
+ }
142
+ /** Default English theme-submenu labels — spread + override for i18n. */
143
+ declare const defaultNavUserThemeLabels: NavUserThemeLabels;
144
+ interface NavUserProps {
145
+ /** Signed-in user. Renders nothing when `null`/`undefined`. */
146
+ user?: AccountMenuUser | null;
147
+ /** Account-action labels — reuses lyra's {@link AccountMenuLabels}. */
148
+ labels?: Partial<AccountMenuLabels>;
149
+ /** Switch-account handler. Item hidden if omitted. */
150
+ onSwitchAccount?: () => void;
151
+ /** Sign-out handler. Item hidden if omitted. */
152
+ onSignOut?: () => void;
153
+ /** Render the light/dark/system theme submenu. Default `true`. */
154
+ showTheme?: boolean;
155
+ /** Theme-submenu labels. */
156
+ themeLabels?: Partial<NavUserThemeLabels>;
157
+ /** Extra menu content inserted below the theme submenu. */
158
+ children?: ReactNode;
159
+ }
160
+ /**
161
+ * Sidebar-footer account menu — the standardized NavUser for app shells.
162
+ * Avatar trigger (size `lg`) + name/email opening a dropdown with an optional
163
+ * theme switcher and switch-account / sign-out actions. Auth flows stay in the
164
+ * app (callbacks); labels are props for i18n.
165
+ *
166
+ * Differs from {@link AccountMenu} in `@tradalab/lyra/ui`: that one is a
167
+ * generic header dropdown; this is styled for the collapsible sidebar footer
168
+ * and follows `useSidebar()` for mobile placement.
169
+ *
170
+ * Must render inside an `AppShell`/SidebarProvider, and below a
171
+ * `TooltipProvider` (the avatar button's collapsed-mode tooltip requires one —
172
+ * app root providers normally mount it). The theme submenu needs a
173
+ * `next-themes` provider mounted (it no-ops without one); pass
174
+ * `showTheme={false}` for apps with a fixed theme.
175
+ *
176
+ * @example
177
+ * import { NavUser } from "@tradalab/lyra/shell"
178
+ * import { SidebarFooter } from "@tradalab/lyra/ui"
179
+ *
180
+ * <SidebarFooter>
181
+ * <NavUser
182
+ * user={session.user}
183
+ * onSwitchAccount={() => signIn("zitadel", { redirectTo: "/" }, { prompt: "select_account" })}
184
+ * onSignOut={() => signOut({ redirectTo: "/" })}
185
+ * />
186
+ * </SidebarFooter>
187
+ */
188
+ declare function NavUser({ user, labels, onSwitchAccount, onSignOut, showTheme, themeLabels, children, }: NavUserProps): React.JSX.Element | null;
189
+
190
+ interface TabItem {
191
+ id: string;
192
+ title: string;
193
+ /** Secondary line under the title (e.g. connection/db, file path). */
194
+ subtitle?: ReactNode;
195
+ /** Leading icon element — the app maps its own tab type → icon. */
196
+ icon?: ReactNode;
197
+ pinned?: boolean;
198
+ }
199
+ interface TabBarLabels {
200
+ close: string;
201
+ pinTab: string;
202
+ unpinTab: string;
203
+ closeOthers: string;
204
+ closeAll: string;
205
+ }
206
+ /** Default English labels — spread + override for i18n. */
207
+ declare const defaultTabBarLabels: TabBarLabels;
208
+ interface TabBarProps {
209
+ tabs: TabItem[];
210
+ activeId: string | null;
211
+ onSelect: (id: string) => void;
212
+ onClose: (id: string) => void;
213
+ /** Pin row in the context menu is hidden when omitted. */
214
+ onTogglePin?: (id: string) => void;
215
+ /** "Close others" row is hidden when omitted. */
216
+ onCloseOthers?: (id: string) => void;
217
+ /** "Close all" row is hidden when omitted. */
218
+ onCloseAll?: () => void;
219
+ labels?: Partial<TabBarLabels>;
220
+ }
221
+ /**
222
+ * Horizontal tab strip for the content area of stacked-sidebar apps
223
+ * (redishub, loom). Presentational + store-agnostic: the app owns tab state
224
+ * and passes `tabs` + callbacks; `icon`/`subtitle` are per-tab so apps map
225
+ * their own tab types, and labels are props for i18n. Renders nothing when
226
+ * `tabs` is empty.
227
+ *
228
+ * Sizes rows from the `--tab-height` CSS var — set it in your globals.
229
+ *
230
+ * @example
231
+ * const s = useTabStore()
232
+ * <TabBar
233
+ * tabs={s.tabs.map(t => ({ id: t.id, title: t.title, pinned: t.pinned, icon: <Icon t={t.type}/>, subtitle: fmt(t) }))}
234
+ * activeId={s.activeTabId}
235
+ * onSelect={s.setActiveTabId}
236
+ * onClose={s.removeTab}
237
+ * onTogglePin={s.togglePin}
238
+ * onCloseOthers={s.closeOthers}
239
+ * onCloseAll={s.closeAll}
240
+ * />
241
+ */
242
+ declare function TabBar({ tabs, activeId, onSelect, onClose, onTogglePin, onCloseOthers, onCloseAll, labels, }: TabBarProps): React.JSX.Element | null;
243
+
75
244
  interface BreadcrumbsProps extends ComponentProps<typeof Breadcrumb> {
76
245
  /** Separator content. Falls back to shadcn's chevron when omitted. */
77
246
  separator?: ReactNode;
@@ -81,4 +250,4 @@ interface BreadcrumbsProps extends ComponentProps<typeof Breadcrumb> {
81
250
  /** Pathname-derived breadcrumbs. Renders nothing on the root route. */
82
251
  declare function Breadcrumbs({ separator, labels, ...rest }: BreadcrumbsProps): React.JSX.Element | null;
83
252
 
84
- export { AppShell, AppShellBody, AppShellHeader, AppShellMain, type AppShellProps, Breadcrumbs, type BreadcrumbsProps, type MatchMode, NavGroup, type NavGroupProps, type NavIcon, NavItem, type NavItemProps, type UseActiveRoute, useActiveRoute };
253
+ export { AppShell, AppShellBody, AppShellHeader, AppShellMain, type AppShellProps, Breadcrumbs, type BreadcrumbsProps, type MatchMode, NavGroup, type NavGroupProps, type NavIcon, NavItem, type NavItemProps, NavSection, type NavSectionProps, NavUser, type NavUserProps, type NavUserThemeLabels, SidebarDock, SidebarPanel, StackedSidebar, TabBar, type TabBarLabels, type TabBarProps, type TabItem, type UseActiveRoute, defaultNavUserThemeLabels, defaultTabBarLabels, useActiveRoute };
@@ -1,12 +1,15 @@
1
1
  "use client";
2
- import { SidebarInset, SidebarProvider, SidebarGroup, SidebarGroupLabel, SidebarGroupContent, SidebarMenu, SidebarMenuItem, SidebarMenuButton, Breadcrumb, BreadcrumbList, BreadcrumbSeparator, BreadcrumbItem, BreadcrumbPage, BreadcrumbLink } from '../chunk-E2QCV5QI.js';
2
+ import { SidebarInset, SidebarProvider, Sidebar, SidebarGroup, SidebarGroupLabel, SidebarGroupContent, SidebarMenu, SidebarMenuItem, SidebarMenuButton, useSidebar, defaultAccountMenuLabels, Avatar, AvatarImage, AvatarFallback, initialsOf, ScrollArea, ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ScrollBar, Breadcrumb, BreadcrumbList, BreadcrumbSeparator, BreadcrumbItem, BreadcrumbPage, BreadcrumbLink } from '../chunk-5UAFV75V.js';
3
3
  import '../chunk-QYA2HGKW.js';
4
- import '../chunk-KFQOYZ64.js';
4
+ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuItem } from '../chunk-VEWKPOMS.js';
5
+ import { Button } from '../chunk-KFQOYZ64.js';
5
6
  import { cn } from '../chunk-XH55BHUU.js';
6
- import { jsx, jsxs } from 'react/jsx-runtime';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
8
  import Link from 'next/link';
8
9
  import { usePathname } from 'next/navigation';
9
- import { Fragment } from 'react';
10
+ import { ChevronsUpDown, Palette, Sun, Moon, Monitor, UserRoundCog, LogOut, Pin, X, PinOff, ListX, Trash2, ChevronDown } from 'lucide-react';
11
+ import { useTheme } from 'next-themes';
12
+ import { useRef, useEffect, Fragment as Fragment$1 } from 'react';
10
13
 
11
14
  function AppShell({ children, ...rest }) {
12
15
  return /* @__PURE__ */ jsx(SidebarProvider, { ...rest, children });
@@ -36,6 +39,52 @@ function AppShellBody({
36
39
  }) {
37
40
  return /* @__PURE__ */ jsx("div", { className: cn("flex-1 overflow-auto", className), ...rest, children });
38
41
  }
42
+ function StackedSidebar({
43
+ className,
44
+ ...rest
45
+ }) {
46
+ return /* @__PURE__ */ jsx(
47
+ Sidebar,
48
+ {
49
+ variant: "sidebar",
50
+ collapsible: "icon",
51
+ ...rest,
52
+ className: cn(
53
+ "overflow-hidden *:data-[sidebar=sidebar]:flex-row",
54
+ className
55
+ )
56
+ }
57
+ );
58
+ }
59
+ function SidebarDock({
60
+ className,
61
+ ...rest
62
+ }) {
63
+ return /* @__PURE__ */ jsx(
64
+ Sidebar,
65
+ {
66
+ ...rest,
67
+ collapsible: "none",
68
+ className: cn(
69
+ "w-[calc(var(--sidebar-width-icon)+1px)]! overflow-hidden border-r",
70
+ className
71
+ )
72
+ }
73
+ );
74
+ }
75
+ function SidebarPanel({
76
+ className,
77
+ ...rest
78
+ }) {
79
+ return /* @__PURE__ */ jsx(
80
+ Sidebar,
81
+ {
82
+ ...rest,
83
+ collapsible: "none",
84
+ className: cn("flex-1 overflow-hidden", className)
85
+ }
86
+ );
87
+ }
39
88
  function NavGroup({ label, children }) {
40
89
  return /* @__PURE__ */ jsxs(SidebarGroup, { children: [
41
90
  label != null ? /* @__PURE__ */ jsx(SidebarGroupLabel, { children: label }) : null,
@@ -73,6 +122,310 @@ function NavItem({
73
122
  }
74
123
  ) });
75
124
  }
125
+ function NavSection({ label, items }) {
126
+ return /* @__PURE__ */ jsx(NavGroup, { label, children: items.map((item) => /* @__PURE__ */ jsx(NavItem, { ...item }, item.href)) });
127
+ }
128
+ var defaultNavUserThemeLabels = {
129
+ theme: "Theme",
130
+ light: "Light",
131
+ dark: "Dark",
132
+ system: "System"
133
+ };
134
+ var THEMES = [
135
+ { value: "light", key: "light", icon: Sun },
136
+ { value: "dark", key: "dark", icon: Moon },
137
+ { value: "system", key: "system", icon: Monitor }
138
+ ];
139
+ function NavUser({
140
+ user,
141
+ labels,
142
+ onSwitchAccount,
143
+ onSignOut,
144
+ showTheme = true,
145
+ themeLabels,
146
+ children
147
+ }) {
148
+ const { isMobile } = useSidebar();
149
+ const { theme, setTheme } = useTheme();
150
+ const l = { ...defaultAccountMenuLabels, ...labels };
151
+ const t = { ...defaultNavUserThemeLabels, ...themeLabels };
152
+ if (!user) return null;
153
+ const name = user.name ?? user.email ?? l.account;
154
+ const email = user.email ?? "";
155
+ return /* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
156
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
157
+ SidebarMenuButton,
158
+ {
159
+ size: "lg",
160
+ tooltip: name,
161
+ className: "data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground",
162
+ children: [
163
+ /* @__PURE__ */ jsxs(Avatar, { className: "size-8 rounded-lg", children: [
164
+ user.image ? /* @__PURE__ */ jsx(AvatarImage, { src: user.image, alt: name }) : null,
165
+ /* @__PURE__ */ jsx(AvatarFallback, { className: "rounded-lg", children: initialsOf(name) })
166
+ ] }),
167
+ /* @__PURE__ */ jsxs("div", { className: "grid flex-1 text-left text-sm leading-tight", children: [
168
+ /* @__PURE__ */ jsx("span", { className: "truncate font-medium", children: name }),
169
+ email ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground truncate text-xs", children: email }) : null
170
+ ] }),
171
+ /* @__PURE__ */ jsx(ChevronsUpDown, { className: "ml-auto size-4" })
172
+ ]
173
+ }
174
+ ) }),
175
+ /* @__PURE__ */ jsxs(
176
+ DropdownMenuContent,
177
+ {
178
+ className: "w-(--radix-dropdown-menu-trigger-width) min-w-56",
179
+ side: isMobile ? "bottom" : "right",
180
+ align: "end",
181
+ sideOffset: 4,
182
+ children: [
183
+ /* @__PURE__ */ jsxs(DropdownMenuLabel, { className: "flex flex-col gap-0.5", children: [
184
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium leading-tight", children: name }),
185
+ email ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground truncate text-xs font-normal leading-tight", children: email }) : null
186
+ ] }),
187
+ showTheme ? /* @__PURE__ */ jsxs(Fragment, { children: [
188
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
189
+ /* @__PURE__ */ jsxs(DropdownMenuSub, { children: [
190
+ /* @__PURE__ */ jsxs(DropdownMenuSubTrigger, { children: [
191
+ /* @__PURE__ */ jsx(Palette, { className: "mr-2 size-4" }),
192
+ t.theme
193
+ ] }),
194
+ /* @__PURE__ */ jsx(DropdownMenuSubContent, { children: /* @__PURE__ */ jsx(
195
+ DropdownMenuRadioGroup,
196
+ {
197
+ value: theme ?? "system",
198
+ onValueChange: setTheme,
199
+ children: THEMES.map(({ value, key, icon: Icon }) => /* @__PURE__ */ jsxs(DropdownMenuRadioItem, { value, children: [
200
+ /* @__PURE__ */ jsx(Icon, { className: "mr-2 size-4" }),
201
+ t[key]
202
+ ] }, value))
203
+ }
204
+ ) })
205
+ ] })
206
+ ] }) : null,
207
+ children ? /* @__PURE__ */ jsxs(Fragment, { children: [
208
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
209
+ children
210
+ ] }) : null,
211
+ onSwitchAccount || onSignOut ? /* @__PURE__ */ jsx(DropdownMenuSeparator, {}) : null,
212
+ onSwitchAccount ? /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: onSwitchAccount, children: [
213
+ /* @__PURE__ */ jsx(UserRoundCog, { className: "mr-2 size-4" }),
214
+ l.switchAccount
215
+ ] }) : null,
216
+ onSignOut ? /* @__PURE__ */ jsxs(DropdownMenuItem, { variant: "destructive", onSelect: onSignOut, children: [
217
+ /* @__PURE__ */ jsx(LogOut, { className: "mr-2 size-4" }),
218
+ l.signOut
219
+ ] }) : null
220
+ ]
221
+ }
222
+ )
223
+ ] }) }) });
224
+ }
225
+ var defaultTabBarLabels = {
226
+ close: "Close",
227
+ pinTab: "Pin tab",
228
+ unpinTab: "Unpin tab",
229
+ closeOthers: "Close others",
230
+ closeAll: "Close all"
231
+ };
232
+ function TabBar({
233
+ tabs,
234
+ activeId,
235
+ onSelect,
236
+ onClose,
237
+ onTogglePin,
238
+ onCloseOthers,
239
+ onCloseAll,
240
+ labels
241
+ }) {
242
+ const l = { ...defaultTabBarLabels, ...labels };
243
+ const activeTabRef = useRef(null);
244
+ useEffect(() => {
245
+ if (activeId && activeTabRef.current) {
246
+ activeTabRef.current.scrollIntoView({
247
+ behavior: "smooth",
248
+ block: "nearest",
249
+ inline: "nearest"
250
+ });
251
+ }
252
+ }, [activeId]);
253
+ const handleWheel = (e) => {
254
+ if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
255
+ const viewport = e.currentTarget.querySelector(
256
+ '[data-slot="scroll-area-viewport"]'
257
+ );
258
+ if (viewport) viewport.scrollLeft += e.deltaY * 0.5;
259
+ }
260
+ };
261
+ if (tabs.length === 0) return null;
262
+ const hasContextActions = Boolean(onTogglePin || onCloseOthers || onCloseAll);
263
+ return /* @__PURE__ */ jsxs(
264
+ "div",
265
+ {
266
+ className: "group/tabbar flex w-full shrink-0 items-center overflow-hidden border-b bg-muted/30",
267
+ style: { height: "var(--tab-height)" },
268
+ children: [
269
+ /* @__PURE__ */ jsxs(
270
+ ScrollArea,
271
+ {
272
+ type: "hover",
273
+ className: "min-w-0 flex-1 overflow-hidden [&_[data-orientation=vertical]]:hidden",
274
+ onWheel: handleWheel,
275
+ children: [
276
+ /* @__PURE__ */ jsx(
277
+ "div",
278
+ {
279
+ className: "flex w-max items-center gap-0",
280
+ style: { height: "var(--tab-height)" },
281
+ children: tabs.map((tab) => {
282
+ const isActive = activeId === tab.id;
283
+ return /* @__PURE__ */ jsxs(ContextMenu, { children: [
284
+ /* @__PURE__ */ jsx(ContextMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
285
+ "div",
286
+ {
287
+ ref: isActive ? activeTabRef : null,
288
+ onClick: () => onSelect(tab.id),
289
+ title: tab.title,
290
+ className: cn(
291
+ "group relative flex min-w-[140px] max-w-[240px] shrink-0 cursor-pointer select-none items-center gap-2 border-r px-3 transition-colors",
292
+ isActive ? "border-t-2 border-t-primary bg-background" : "border-t-2 border-t-transparent bg-transparent text-muted-foreground hover:bg-muted/50",
293
+ tab.pinned && "min-w-[100px] bg-primary/5"
294
+ ),
295
+ style: { height: "var(--tab-height)" },
296
+ children: [
297
+ tab.icon ? /* @__PURE__ */ jsxs("div", { className: "relative shrink-0", children: [
298
+ /* @__PURE__ */ jsx(
299
+ "span",
300
+ {
301
+ className: cn(
302
+ "flex [&_svg]:size-3.5",
303
+ isActive && "text-primary"
304
+ ),
305
+ children: tab.icon
306
+ }
307
+ ),
308
+ tab.pinned ? /* @__PURE__ */ jsx("div", { className: "absolute -right-1.5 -top-1.5 rounded-full bg-background p-0.5 shadow-sm", children: /* @__PURE__ */ jsx(Pin, { className: "size-2 fill-primary text-primary" }) }) : null
309
+ ] }) : null,
310
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col items-start justify-center overflow-hidden py-1", children: [
311
+ /* @__PURE__ */ jsx(
312
+ "span",
313
+ {
314
+ className: cn(
315
+ "w-full truncate text-xs leading-tight",
316
+ isActive && "font-medium text-foreground"
317
+ ),
318
+ children: tab.title
319
+ }
320
+ ),
321
+ tab.subtitle ? /* @__PURE__ */ jsx("span", { className: "mt-0.5 w-full truncate text-[10px] leading-none text-muted-foreground/70", children: tab.subtitle }) : null
322
+ ] }),
323
+ /* @__PURE__ */ jsx(
324
+ Button,
325
+ {
326
+ variant: "ghost",
327
+ size: "icon",
328
+ title: l.close,
329
+ className: cn(
330
+ "size-5 shrink-0 rounded-md opacity-0 transition-opacity hover:bg-muted-foreground/20 group-hover:opacity-100",
331
+ isActive && "opacity-100"
332
+ ),
333
+ onClick: (e) => {
334
+ e.stopPropagation();
335
+ onClose(tab.id);
336
+ },
337
+ children: /* @__PURE__ */ jsx(X, { className: "size-3" })
338
+ }
339
+ ),
340
+ isActive ? /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 z-10 h-[2px] bg-background" }) : null
341
+ ]
342
+ }
343
+ ) }),
344
+ hasContextActions ? /* @__PURE__ */ jsxs(ContextMenuContent, { className: "w-48", children: [
345
+ onTogglePin ? /* @__PURE__ */ jsxs(Fragment, { children: [
346
+ /* @__PURE__ */ jsx(ContextMenuItem, { onClick: () => onTogglePin(tab.id), children: tab.pinned ? /* @__PURE__ */ jsxs(Fragment, { children: [
347
+ /* @__PURE__ */ jsx(PinOff, { className: "mr-2 size-4" }),
348
+ /* @__PURE__ */ jsx("span", { children: l.unpinTab })
349
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
350
+ /* @__PURE__ */ jsx(Pin, { className: "mr-2 size-4" }),
351
+ /* @__PURE__ */ jsx("span", { children: l.pinTab })
352
+ ] }) }),
353
+ /* @__PURE__ */ jsx(ContextMenuSeparator, {})
354
+ ] }) : null,
355
+ /* @__PURE__ */ jsxs(ContextMenuItem, { onClick: () => onClose(tab.id), children: [
356
+ /* @__PURE__ */ jsx(X, { className: "mr-2 size-4" }),
357
+ /* @__PURE__ */ jsx("span", { children: l.close })
358
+ ] }),
359
+ onCloseOthers ? /* @__PURE__ */ jsxs(ContextMenuItem, { onClick: () => onCloseOthers(tab.id), children: [
360
+ /* @__PURE__ */ jsx(ListX, { className: "mr-2 size-4" }),
361
+ /* @__PURE__ */ jsx("span", { children: l.closeOthers })
362
+ ] }) : null,
363
+ onCloseAll ? /* @__PURE__ */ jsxs(ContextMenuItem, { onClick: () => onCloseAll(), children: [
364
+ /* @__PURE__ */ jsx(Trash2, { className: "mr-2 size-4" }),
365
+ /* @__PURE__ */ jsx("span", { children: l.closeAll })
366
+ ] }) : null
367
+ ] }) : null
368
+ ] }, tab.id);
369
+ })
370
+ }
371
+ ),
372
+ /* @__PURE__ */ jsx(ScrollBar, { orientation: "horizontal", className: "z-20 h-1.5 hover:h-2" })
373
+ ]
374
+ }
375
+ ),
376
+ /* @__PURE__ */ jsx(
377
+ "div",
378
+ {
379
+ className: "flex shrink-0 items-center border-l bg-muted/30 px-1",
380
+ style: { height: "var(--tab-height)" },
381
+ children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
382
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon", className: "size-8 shrink-0 rounded-sm", children: /* @__PURE__ */ jsx(ChevronDown, { className: "size-4" }) }) }),
383
+ /* @__PURE__ */ jsx(
384
+ DropdownMenuContent,
385
+ {
386
+ align: "end",
387
+ className: "max-h-[400px] w-[260px] overflow-y-auto",
388
+ children: tabs.map((tab) => /* @__PURE__ */ jsxs(
389
+ DropdownMenuItem,
390
+ {
391
+ onClick: () => onSelect(tab.id),
392
+ title: tab.title,
393
+ className: cn(
394
+ "cursor-pointer gap-2 py-2",
395
+ activeId === tab.id && "bg-accent"
396
+ ),
397
+ children: [
398
+ tab.icon ? /* @__PURE__ */ jsxs("div", { className: "relative shrink-0", children: [
399
+ /* @__PURE__ */ jsx("span", { className: "flex text-muted-foreground [&_svg]:size-4", children: tab.icon }),
400
+ tab.pinned ? /* @__PURE__ */ jsx(Pin, { className: "absolute -right-1 -top-1 size-2 fill-primary text-primary" }) : null
401
+ ] }) : null,
402
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-start overflow-hidden leading-tight", children: [
403
+ /* @__PURE__ */ jsx("span", { className: "w-full flex-1 truncate text-sm", children: tab.title }),
404
+ tab.subtitle ? /* @__PURE__ */ jsx("span", { className: "w-full truncate text-[10px] text-muted-foreground", children: tab.subtitle }) : null
405
+ ] }),
406
+ /* @__PURE__ */ jsx(
407
+ X,
408
+ {
409
+ className: "size-4 shrink-0 rounded-sm p-0.5 hover:bg-red-50 hover:text-red-500",
410
+ onClick: (e) => {
411
+ e.stopPropagation();
412
+ onClose(tab.id);
413
+ }
414
+ }
415
+ )
416
+ ]
417
+ },
418
+ tab.id
419
+ ))
420
+ }
421
+ )
422
+ ] })
423
+ }
424
+ )
425
+ ]
426
+ }
427
+ );
428
+ }
76
429
  function Breadcrumbs({ separator, labels, ...rest }) {
77
430
  const { pathname } = useActiveRoute();
78
431
  const segments = pathname.split("/").filter(Boolean);
@@ -81,13 +434,13 @@ function Breadcrumbs({ separator, labels, ...rest }) {
81
434
  return /* @__PURE__ */ jsx(Breadcrumb, { ...rest, children: /* @__PURE__ */ jsx(BreadcrumbList, { children: segments.map((seg, i) => {
82
435
  const href = "/" + segments.slice(0, i + 1).join("/");
83
436
  const label = labels?.[seg] ?? seg;
84
- return /* @__PURE__ */ jsxs(Fragment, { children: [
437
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
85
438
  i > 0 ? /* @__PURE__ */ jsx(BreadcrumbSeparator, { children: separator }) : null,
86
439
  /* @__PURE__ */ jsx(BreadcrumbItem, { children: i === last ? /* @__PURE__ */ jsx(BreadcrumbPage, { children: label }) : /* @__PURE__ */ jsx(BreadcrumbLink, { asChild: true, children: /* @__PURE__ */ jsx(Link, { href, children: label }) }) })
87
440
  ] }, href);
88
441
  }) }) });
89
442
  }
90
443
 
91
- export { AppShell, AppShellBody, AppShellHeader, AppShellMain, Breadcrumbs, NavGroup, NavItem, useActiveRoute };
444
+ export { AppShell, AppShellBody, AppShellHeader, AppShellMain, Breadcrumbs, NavGroup, NavItem, NavSection, NavUser, SidebarDock, SidebarPanel, StackedSidebar, TabBar, defaultNavUserThemeLabels, defaultTabBarLabels, useActiveRoute };
92
445
  //# sourceMappingURL=index.js.map
93
446
  //# sourceMappingURL=index.js.map