@bensdev/react-sidebar 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1323 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var React21 = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+ var reactDom = require('react-dom');
7
+
8
+ function _interopNamespace(e) {
9
+ if (e && e.__esModule) return e;
10
+ var n = Object.create(null);
11
+ if (e) {
12
+ Object.keys(e).forEach(function (k) {
13
+ if (k !== 'default') {
14
+ var d = Object.getOwnPropertyDescriptor(e, k);
15
+ Object.defineProperty(n, k, d.get ? d : {
16
+ enumerable: true,
17
+ get: function () { return e[k]; }
18
+ });
19
+ }
20
+ });
21
+ }
22
+ n.default = e;
23
+ return Object.freeze(n);
24
+ }
25
+
26
+ var React21__namespace = /*#__PURE__*/_interopNamespace(React21);
27
+
28
+ // src/Sidebar.tsx
29
+
30
+ // src/lib/cx.ts
31
+ function cx(...values) {
32
+ let out = "";
33
+ for (const v of values) {
34
+ if (!v && v !== 0) continue;
35
+ out += (out ? " " : "") + v;
36
+ }
37
+ return out;
38
+ }
39
+
40
+ // src/lib/themeToStyle.ts
41
+ var kebab = (s) => s.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
42
+ var NUMERIC_PX = /* @__PURE__ */ new Set([
43
+ "width",
44
+ "widthCollapsed",
45
+ "drawerWidth",
46
+ "headerHeight",
47
+ "itemHeight",
48
+ "iconSize",
49
+ "accentWidth",
50
+ "toggleSize",
51
+ "toggleOffsetY",
52
+ "logoSize",
53
+ "avatarSize"
54
+ ]);
55
+ function themeToStyle(theme, scheme) {
56
+ if (!theme) return {};
57
+ const { light, dark, ...base } = theme;
58
+ const merged = {
59
+ ...base,
60
+ ...scheme === "dark" ? dark : light
61
+ };
62
+ const out = {};
63
+ for (const [key, value] of Object.entries(merged)) {
64
+ if (value == null) continue;
65
+ out[`--bsb-${kebab(key)}`] = typeof value === "number" && NUMERIC_PX.has(key) ? `${value}px` : String(value);
66
+ }
67
+ return out;
68
+ }
69
+ function useMediaQuery(query) {
70
+ const subscribe = React21__namespace.useCallback(
71
+ (cb) => {
72
+ if (typeof window === "undefined" || !window.matchMedia) return () => {
73
+ };
74
+ const mql = window.matchMedia(query);
75
+ mql.addEventListener("change", cb);
76
+ return () => mql.removeEventListener("change", cb);
77
+ },
78
+ [query]
79
+ );
80
+ const getSnapshot = React21__namespace.useCallback(() => {
81
+ if (typeof window === "undefined" || !window.matchMedia) return false;
82
+ return window.matchMedia(query).matches;
83
+ }, [query]);
84
+ const getServerSnapshot = React21__namespace.useCallback(() => false, []);
85
+ return React21__namespace.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
86
+ }
87
+ function toBreakpointQuery(breakpoint) {
88
+ return typeof breakpoint === "number" ? `(max-width: ${breakpoint - 1}px)` : breakpoint;
89
+ }
90
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React21__namespace.useLayoutEffect : React21__namespace.useEffect;
91
+
92
+ // src/hooks/useColorScheme.ts
93
+ function useColorScheme(pref, ref) {
94
+ const [resolved, setResolved] = React21__namespace.useState(pref === "dark" ? "dark" : "light");
95
+ useIsomorphicLayoutEffect(() => {
96
+ if (pref !== "auto") {
97
+ setResolved(pref);
98
+ return;
99
+ }
100
+ const el = ref.current;
101
+ if (!el || typeof window === "undefined") return;
102
+ const read = () => {
103
+ const darkAncestor = el.closest('.dark, [data-theme="dark"], [data-color-scheme="dark"]');
104
+ const lightAncestor = el.closest('[data-theme="light"], [data-color-scheme="light"]');
105
+ if (darkAncestor && (!lightAncestor || darkAncestor.contains(lightAncestor))) {
106
+ setResolved("dark");
107
+ return;
108
+ }
109
+ if (lightAncestor) {
110
+ setResolved("light");
111
+ return;
112
+ }
113
+ setResolved(window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light");
114
+ };
115
+ read();
116
+ const mo = new MutationObserver(read);
117
+ mo.observe(document.documentElement, {
118
+ attributes: true,
119
+ attributeFilter: ["class", "data-theme", "data-color-scheme"],
120
+ subtree: true
121
+ });
122
+ const mql = window.matchMedia?.("(prefers-color-scheme: dark)");
123
+ mql?.addEventListener("change", read);
124
+ return () => {
125
+ mo.disconnect();
126
+ mql?.removeEventListener("change", read);
127
+ };
128
+ }, [pref, ref]);
129
+ return resolved;
130
+ }
131
+ function useControllableState({
132
+ value,
133
+ defaultValue,
134
+ onChange
135
+ }) {
136
+ const isControlled = value !== void 0;
137
+ const [internal, setInternal] = React21__namespace.useState(defaultValue);
138
+ const current = isControlled ? value : internal;
139
+ const setState = React21__namespace.useCallback(
140
+ (next) => {
141
+ const resolved = typeof next === "function" ? next(current) : next;
142
+ if (!isControlled) setInternal(resolved);
143
+ onChange?.(resolved);
144
+ },
145
+ [isControlled, current, onChange]
146
+ );
147
+ return [current, setState];
148
+ }
149
+
150
+ // src/lib/storage.ts
151
+ function safeLocalStorage() {
152
+ if (typeof window === "undefined") return null;
153
+ try {
154
+ const testKey = "__bsb_storage_test__";
155
+ window.localStorage.setItem(testKey, "1");
156
+ window.localStorage.removeItem(testKey);
157
+ } catch {
158
+ return null;
159
+ }
160
+ return {
161
+ getItem: (key) => {
162
+ try {
163
+ return window.localStorage.getItem(key);
164
+ } catch {
165
+ return null;
166
+ }
167
+ },
168
+ setItem: (key, value) => {
169
+ try {
170
+ window.localStorage.setItem(key, value);
171
+ } catch {
172
+ }
173
+ }
174
+ };
175
+ }
176
+ function isStorageAdapter(value) {
177
+ return typeof value === "object" && value !== null && typeof value.getItem === "function" && typeof value.setItem === "function";
178
+ }
179
+
180
+ // src/hooks/usePersistedCollapse.ts
181
+ var DEFAULT_KEY = "sidebar-collapsed";
182
+ function usePersistedCollapse(persist) {
183
+ const key = typeof persist === "string" ? persist : DEFAULT_KEY;
184
+ const store = React21__namespace.useMemo(() => {
185
+ if (!persist) return null;
186
+ if (isStorageAdapter(persist)) return persist;
187
+ return safeLocalStorage();
188
+ }, [persist]);
189
+ const subscribe = React21__namespace.useCallback(
190
+ (cb) => {
191
+ if (!store) return () => {
192
+ };
193
+ if (store.subscribe) return store.subscribe(cb);
194
+ if (typeof window === "undefined") return () => {
195
+ };
196
+ const handler = (e) => {
197
+ if (e.key === key) cb();
198
+ };
199
+ window.addEventListener("storage", handler);
200
+ return () => window.removeEventListener("storage", handler);
201
+ },
202
+ [store, key]
203
+ );
204
+ const getSnapshot = React21__namespace.useCallback(() => {
205
+ if (!store) return null;
206
+ const raw = store.getItem(key);
207
+ return raw === null ? null : raw === "true";
208
+ }, [store, key]);
209
+ const getServerSnapshot = React21__namespace.useCallback(() => null, []);
210
+ const persisted = React21__namespace.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
211
+ const setPersisted = React21__namespace.useCallback(
212
+ (value) => {
213
+ store?.setItem(key, String(value));
214
+ },
215
+ [store, key]
216
+ );
217
+ return [persist ? persisted : null, setPersisted];
218
+ }
219
+ function useCurrentPath(explicit) {
220
+ const subscribe = React21__namespace.useCallback((cb) => {
221
+ if (typeof window === "undefined") return () => {
222
+ };
223
+ window.addEventListener("popstate", cb);
224
+ window.addEventListener("hashchange", cb);
225
+ return () => {
226
+ window.removeEventListener("popstate", cb);
227
+ window.removeEventListener("hashchange", cb);
228
+ };
229
+ }, []);
230
+ const getSnapshot = React21__namespace.useCallback(() => {
231
+ if (typeof window === "undefined") return "/";
232
+ return window.location.pathname;
233
+ }, []);
234
+ const getServerSnapshot = React21__namespace.useCallback(() => "/", []);
235
+ const tracked = React21__namespace.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
236
+ return explicit ?? tracked;
237
+ }
238
+ var NavContext = React21__namespace.createContext(null);
239
+ function useNavContext() {
240
+ const ctx = React21__namespace.useContext(NavContext);
241
+ if (!ctx) {
242
+ throw new Error("@bensdev/react-sidebar: nav components must be rendered inside <Sidebar>.");
243
+ }
244
+ return ctx;
245
+ }
246
+ function isShapedBadge(value) {
247
+ return typeof value === "object" && value !== null && "content" in value;
248
+ }
249
+ function Badge({
250
+ value,
251
+ className
252
+ }) {
253
+ if (value == null) return null;
254
+ const shaped = isShapedBadge(value);
255
+ const content = shaped ? value.content : value;
256
+ const tone = shaped ? value.tone ?? "default" : "default";
257
+ return /* @__PURE__ */ jsxRuntime.jsx(
258
+ "span",
259
+ {
260
+ className: cx("bsb-badge", `bsb-badge--${tone}`, shaped ? value.className : void 0, className),
261
+ children: content
262
+ }
263
+ );
264
+ }
265
+ function isBrandConfig(value) {
266
+ return typeof value === "object" && value !== null && !React21__namespace.isValidElement(value) && ("logo" in value || "title" in value || "subtitle" in value || "badge" in value || "logoNode" in value);
267
+ }
268
+ function SidebarHeader({ brand, header, collapsed, classNames, renderCtx, toggle }) {
269
+ if (header !== void 0) {
270
+ const content = typeof header === "function" ? header(renderCtx) : header;
271
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx("bsb-header", classNames.header), children: [
272
+ content,
273
+ toggle
274
+ ] });
275
+ }
276
+ if (brand === void 0) return null;
277
+ const resolved = typeof brand === "function" ? brand(renderCtx) : brand;
278
+ if (!isBrandConfig(resolved)) {
279
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx("bsb-header", classNames.header), children: [
280
+ resolved,
281
+ toggle
282
+ ] });
283
+ }
284
+ const config = resolved;
285
+ const TitleTag = config.href ? "a" : config.onClick ? "button" : "div";
286
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx("bsb-header", collapsed && "bsb-header--collapsed", classNames.header), children: [
287
+ /* @__PURE__ */ jsxRuntime.jsxs(
288
+ TitleTag,
289
+ {
290
+ className: cx("bsb-brand", classNames.brand),
291
+ href: config.href,
292
+ type: TitleTag === "button" ? "button" : void 0,
293
+ onClick: config.onClick,
294
+ children: [
295
+ config.logoNode ?? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx("bsb-logo", classNames.logo), "aria-hidden": !!config.title, children: config.logo }),
296
+ !collapsed && (config.title || config.subtitle || config.badge != null) && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "bsb-brand__text", children: [
297
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "bsb-brand__title-row", children: [
298
+ config.title != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx("bsb-title", classNames.title), children: config.title }),
299
+ config.badge != null && /* @__PURE__ */ jsxRuntime.jsx(Badge, { value: config.badge, className: classNames.headerBadge })
300
+ ] }),
301
+ config.subtitle != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx("bsb-subtitle", classNames.subtitle), children: config.subtitle })
302
+ ] })
303
+ ]
304
+ }
305
+ ),
306
+ toggle
307
+ ] });
308
+ }
309
+
310
+ // src/lib/isActive.ts
311
+ function normalizePath(path) {
312
+ const stripped = (path || "/").split("?")[0].split("#")[0];
313
+ return stripped.length > 1 && stripped.endsWith("/") ? stripped.slice(0, -1) : stripped;
314
+ }
315
+ function defaultIsActive(href, currentPath, end = false) {
316
+ const target = normalizePath(href);
317
+ const current = normalizePath(currentPath);
318
+ if (end || target === "/") return target === current;
319
+ return current === target || current.startsWith(target + "/");
320
+ }
321
+ function resolveIsActive(item, currentPath, exact, isItemActive) {
322
+ const href = item.href;
323
+ const fallback = href ? defaultIsActive(href, currentPath, item.end ?? exact) : false;
324
+ if (typeof item.isActive === "function") {
325
+ return item.isActive({ currentPath, item, defaultIsActive: fallback });
326
+ }
327
+ if (typeof item.isActive === "boolean") return item.isActive;
328
+ if (item.type !== "group" && isItemActive) {
329
+ const custom = isItemActive(item, { currentPath, item, defaultIsActive: fallback });
330
+ if (custom !== void 0) return custom;
331
+ }
332
+ return fallback;
333
+ }
334
+ function groupHasActiveDescendant(item, currentPath, exact, isItemActive) {
335
+ if (item.href && resolveIsActive(item, currentPath, exact, isItemActive)) return true;
336
+ return item.items.some((child) => itemHasActiveDescendant(child, currentPath, exact, isItemActive));
337
+ }
338
+ function itemHasActiveDescendant(item, currentPath, exact, isItemActive) {
339
+ if (item.type === "group") return groupHasActiveDescendant(item, currentPath, exact, isItemActive);
340
+ if (item.type === "link" || item.type === void 0) {
341
+ return resolveIsActive(item, currentPath, exact, isItemActive);
342
+ }
343
+ return false;
344
+ }
345
+ function Tooltip({ id, children, className }) {
346
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { id, role: "tooltip", className: cx("bsb-tooltip", className), "aria-hidden": "true", children });
347
+ }
348
+ var uid = 0;
349
+ function useStableId(prefix) {
350
+ const ref = React21__namespace.useRef(null);
351
+ if (!ref.current) ref.current = `${prefix}-${++uid}`;
352
+ return ref.current;
353
+ }
354
+ function SidebarLinkRow({ item, depth, active: activeOverride }) {
355
+ const ctx = useNavContext();
356
+ const tooltipId = useStableId("bsb-tooltip");
357
+ const isLink = item.type !== "action";
358
+ const linkItem = isLink ? item : null;
359
+ const actionItem = !isLink ? item : null;
360
+ const isActive = activeOverride ?? (linkItem ? resolveIsActive(linkItem, ctx.currentPath, ctx.exact, ctx.isItemActive) : !!actionItem?.selected);
361
+ const showTooltip = ctx.collapsed && ctx.tooltips && item.tooltip !== false && !ctx.isMobile;
362
+ const tooltipContent = item.tooltip ?? item.label;
363
+ const handleClick = (e) => {
364
+ if (item.disabled) {
365
+ e.preventDefault();
366
+ return;
367
+ }
368
+ if (linkItem?.onClick) linkItem.onClick(e);
369
+ if (actionItem) actionItem.onSelect(e);
370
+ ctx.handleNavigate(item, e);
371
+ };
372
+ const className = cx(
373
+ "bsb-item",
374
+ ctx.classNames.item,
375
+ isActive && "bsb-item--active",
376
+ isActive && ctx.classNames.itemActive,
377
+ item.disabled && "bsb-item--disabled",
378
+ item.disabled && ctx.classNames.itemDisabled,
379
+ item.className
380
+ );
381
+ const style = depth > 0 ? { paddingInlineStart: `calc(var(--bsb-item-padding-x) + ${depth} * var(--bsb-indent-step))` } : void 0;
382
+ const inner = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
383
+ item.icon != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx("bsb-item__icon", ctx.classNames.itemIcon), "aria-hidden": "true", children: item.icon }),
384
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx("bsb-item__label", ctx.classNames.itemLabel), children: item.label }),
385
+ item.badge != null && /* @__PURE__ */ jsxRuntime.jsx(Badge, { value: item.badge, className: ctx.classNames.badge }),
386
+ showTooltip && /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { id: tooltipId, className: ctx.classNames.tooltip, children: tooltipContent })
387
+ ] });
388
+ const commonProps = {
389
+ className,
390
+ style,
391
+ onClick: handleClick,
392
+ "aria-disabled": item.disabled || void 0,
393
+ "aria-describedby": showTooltip ? tooltipId : void 0
394
+ };
395
+ let node;
396
+ if (actionItem) {
397
+ node = /* @__PURE__ */ jsxRuntime.jsx(
398
+ "button",
399
+ {
400
+ type: "button",
401
+ ...commonProps,
402
+ disabled: item.disabled,
403
+ "aria-pressed": actionItem.selected,
404
+ children: inner
405
+ }
406
+ );
407
+ } else if (linkItem && !item.disabled) {
408
+ const href = linkItem.href;
409
+ const rowProps = {
410
+ ...commonProps,
411
+ ...linkItem.linkProps,
412
+ target: linkItem.target,
413
+ rel: linkItem.rel,
414
+ "aria-current": isActive ? "page" : void 0
415
+ };
416
+ if (ctx.renderLink) {
417
+ node = ctx.renderLink({
418
+ item: linkItem,
419
+ href,
420
+ isActive,
421
+ className,
422
+ children: inner,
423
+ props: rowProps,
424
+ ctx: ctx.renderCtx
425
+ });
426
+ } else {
427
+ const Comp = ctx.linkComponent ?? "a";
428
+ const hrefKey = ctx.hrefProp || "href";
429
+ node = /* @__PURE__ */ jsxRuntime.jsx(Comp, { ...rowProps, ...{ [hrefKey]: href }, children: inner });
430
+ }
431
+ } else {
432
+ node = /* @__PURE__ */ jsxRuntime.jsx("span", { role: "link", tabIndex: -1, ...commonProps, children: inner });
433
+ }
434
+ return /* @__PURE__ */ jsxRuntime.jsx("li", { className: "bsb-list-item", children: node });
435
+ }
436
+ function baseProps(size, rest) {
437
+ return {
438
+ xmlns: "http://www.w3.org/2000/svg",
439
+ width: size ?? 18,
440
+ height: size ?? 18,
441
+ viewBox: "0 0 24 24",
442
+ fill: "none",
443
+ stroke: "currentColor",
444
+ strokeWidth: 2,
445
+ strokeLinecap: "round",
446
+ strokeLinejoin: "round",
447
+ "aria-hidden": true,
448
+ ...rest
449
+ };
450
+ }
451
+ function ChevronLeftIcon({ size, ...rest }) {
452
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { ...baseProps(size, rest), children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m15 18-6-6 6-6" }) });
453
+ }
454
+ function ChevronRightIcon({ size, ...rest }) {
455
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { ...baseProps(size, rest), children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m9 18 6-6-6-6" }) });
456
+ }
457
+ function ChevronDownIcon({ size, ...rest }) {
458
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { ...baseProps(size, rest), children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 9 6 6 6-6" }) });
459
+ }
460
+ function CloseIcon({ size, ...rest }) {
461
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { ...baseProps(size, rest), children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18M6 6l12 12" }) });
462
+ }
463
+ function MenuIcon({ size, ...rest }) {
464
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { ...baseProps(size, rest), children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 12h16M4 6h16M4 18h16" }) });
465
+ }
466
+ var uid2 = 0;
467
+ function useStableId2(prefix) {
468
+ const ref = React21__namespace.useRef(null);
469
+ if (!ref.current) ref.current = `${prefix}-${++uid2}`;
470
+ return ref.current;
471
+ }
472
+ function SidebarGroupRow({ item, depth }) {
473
+ const ctx = useNavContext();
474
+ const fallbackGroupId = useStableId2("bsb-group");
475
+ const groupId = item.id ?? fallbackGroupId;
476
+ const panelId = `${groupId}-panel`;
477
+ const tooltipId = `${groupId}-tooltip`;
478
+ const collapsible = item.collapsible ?? true;
479
+ const hasActiveChild = groupHasActiveDescendant(item, ctx.currentPath, ctx.exact, ctx.isItemActive);
480
+ const selfActive = item.href ? resolveIsActive(item, ctx.currentPath, ctx.exact) : false;
481
+ const controlledOpen = item.open;
482
+ const isOpen = controlledOpen ?? (!collapsible || ctx.openGroups.has(groupId) || ctx.autoOpenActiveGroup && hasActiveChild);
483
+ const toggle = () => {
484
+ if (item.onOpenChange) {
485
+ item.onOpenChange(!isOpen);
486
+ return;
487
+ }
488
+ ctx.toggleGroup(groupId);
489
+ };
490
+ const showTooltip = ctx.collapsed && ctx.tooltips && item.tooltip !== false && !ctx.isMobile;
491
+ const isRail = ctx.collapsed && !ctx.isMobile;
492
+ const behavior = ctx.collapsedGroupBehavior;
493
+ const triggerClassName = cx(
494
+ "bsb-item",
495
+ "bsb-group__trigger",
496
+ ctx.classNames.item,
497
+ ctx.classNames.groupTrigger,
498
+ (hasActiveChild || selfActive) && "bsb-item--active",
499
+ (hasActiveChild || selfActive) && ctx.classNames.itemActive,
500
+ item.className
501
+ );
502
+ const style = depth > 0 ? { paddingInlineStart: `calc(var(--bsb-item-padding-x) + ${depth} * var(--bsb-indent-step))` } : void 0;
503
+ const trigger = /* @__PURE__ */ jsxRuntime.jsxs(
504
+ "button",
505
+ {
506
+ type: "button",
507
+ className: triggerClassName,
508
+ style,
509
+ onClick: collapsible ? toggle : void 0,
510
+ "aria-expanded": collapsible ? isOpen : void 0,
511
+ "aria-controls": collapsible ? panelId : void 0,
512
+ "aria-describedby": showTooltip ? tooltipId : void 0,
513
+ children: [
514
+ item.icon != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx("bsb-item__icon", ctx.classNames.itemIcon), "aria-hidden": "true", children: item.icon }),
515
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx("bsb-item__label", ctx.classNames.itemLabel), children: item.label }),
516
+ item.badge != null && /* @__PURE__ */ jsxRuntime.jsx(Badge, { value: item.badge, className: ctx.classNames.badge }),
517
+ collapsible && !isRail && /* @__PURE__ */ jsxRuntime.jsx(
518
+ "span",
519
+ {
520
+ className: cx("bsb-group__chevron", isOpen && "bsb-group__chevron--open", ctx.classNames.groupChevron),
521
+ "aria-hidden": "true",
522
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronDownIcon, { size: 14 })
523
+ }
524
+ ),
525
+ showTooltip && behavior !== "flyout" && /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { id: tooltipId, className: ctx.classNames.tooltip, children: item.tooltip ?? item.label })
526
+ ]
527
+ }
528
+ );
529
+ if (isRail && behavior === "flyout") {
530
+ return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: cx("bsb-list-item", "bsb-group", "bsb-group--flyout"), children: [
531
+ trigger,
532
+ /* @__PURE__ */ jsxRuntime.jsxs(
533
+ "div",
534
+ {
535
+ className: cx("bsb-group__flyout", ctx.classNames.groupPanel),
536
+ role: "group",
537
+ "aria-label": typeof item.label === "string" ? item.label : void 0,
538
+ children: [
539
+ item.label != null && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bsb-group__flyout-label", children: item.label }),
540
+ /* @__PURE__ */ jsxRuntime.jsx(NavContext.Provider, { value: { ...ctx, collapsed: false, tooltips: false }, children: /* @__PURE__ */ jsxRuntime.jsx(SidebarNodeList, { items: item.items, depth: 0 }) })
541
+ ]
542
+ }
543
+ )
544
+ ] });
545
+ }
546
+ if (isRail && behavior === "ignore") {
547
+ return /* @__PURE__ */ jsxRuntime.jsx("li", { className: "bsb-list-item", children: trigger });
548
+ }
549
+ return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: cx("bsb-list-item", "bsb-group"), children: [
550
+ trigger,
551
+ (isOpen || !collapsible) && /* @__PURE__ */ jsxRuntime.jsx("ul", { id: panelId, className: cx("bsb-group__panel", ctx.classNames.groupPanel), children: /* @__PURE__ */ jsxRuntime.jsx(SidebarNodeList, { items: item.items, depth: depth + 1 }) })
552
+ ] });
553
+ }
554
+ function SidebarNode({ item, depth }) {
555
+ const ctx = useNavContext();
556
+ if ("hidden" in item && item.hidden) return null;
557
+ const defaultNode = (() => {
558
+ switch (item.type) {
559
+ case "divider":
560
+ return /* @__PURE__ */ jsxRuntime.jsx("li", { className: cx("bsb-list-item", "bsb-divider-item"), role: "separator", children: /* @__PURE__ */ jsxRuntime.jsx("hr", { className: cx("bsb-divider", ctx.classNames.divider, item.className) }) });
561
+ case "heading":
562
+ return /* @__PURE__ */ jsxRuntime.jsx("li", { className: cx("bsb-list-item", "bsb-heading-item"), children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: cx("bsb-heading", ctx.classNames.heading, item.className), children: item.label }) });
563
+ case "custom":
564
+ return /* @__PURE__ */ jsxRuntime.jsx("li", { className: "bsb-list-item bsb-custom-item", children: item.render(ctx.renderCtx) });
565
+ case "group":
566
+ return /* @__PURE__ */ jsxRuntime.jsx(SidebarGroupRow, { item, depth });
567
+ case "action":
568
+ case "link":
569
+ default:
570
+ return /* @__PURE__ */ jsxRuntime.jsx(SidebarLinkRow, { item, depth });
571
+ }
572
+ })();
573
+ if (ctx.renderItem) {
574
+ const custom = ctx.renderItem(item, ctx.renderCtx, defaultNode);
575
+ if (custom !== void 0) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: custom });
576
+ }
577
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: defaultNode });
578
+ }
579
+ function SidebarNodeList({ items, depth }) {
580
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(SidebarNode, { item, depth }, item.id ?? ("href" in item ? item.href : void 0) ?? `${item.type ?? "link"}-${i}`)) });
581
+ }
582
+ function SidebarNav({ items, ariaLabel }) {
583
+ const ctx = useNavContext();
584
+ return /* @__PURE__ */ jsxRuntime.jsx("nav", { className: cx("bsb-nav", ctx.classNames.nav), "aria-label": ariaLabel, children: /* @__PURE__ */ jsxRuntime.jsx("ul", { className: cx("bsb-list", ctx.classNames.list), children: /* @__PURE__ */ jsxRuntime.jsx(SidebarNodeList, { items, depth: 0 }) }) });
585
+ }
586
+
587
+ // src/lib/initials.ts
588
+ function initialsFromName(name, fallback = "") {
589
+ if (typeof name !== "string" || !name.trim()) return fallback;
590
+ return name.trim().split(/\s+/).map((part) => part[0]).join("").slice(0, 2).toUpperCase();
591
+ }
592
+ function SidebarFooter({
593
+ user,
594
+ footerAction,
595
+ footer,
596
+ collapsed,
597
+ collapsedFooter,
598
+ classNames,
599
+ renderCtx
600
+ }) {
601
+ if (footer !== void 0) {
602
+ const content = typeof footer === "function" ? footer(renderCtx) : footer;
603
+ if (!content) return null;
604
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cx("bsb-footer", classNames.footer), children: content });
605
+ }
606
+ if (!user && !footerAction) return null;
607
+ const actions = footerAction ? Array.isArray(footerAction) ? footerAction : [footerAction] : [];
608
+ const initials = user ? initialsFromName(user.name, user.fallbackInitials ?? "") || user.fallbackInitials || "" : "";
609
+ if (collapsed && collapsedFooter === "hidden") return null;
610
+ const avatar = user && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx("bsb-avatar", classNames.avatar), children: user.avatarUrl ? (
611
+ // eslint-disable-next-line @next/next/no-img-element
612
+ /* @__PURE__ */ jsxRuntime.jsx("img", { src: user.avatarUrl, alt: "", className: "bsb-avatar__img" })
613
+ ) : user.avatar ? user.avatar : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bsb-avatar__initials", children: initials }) });
614
+ if (collapsed && collapsedFooter === "avatar") {
615
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cx("bsb-footer", "bsb-footer--collapsed", classNames.footer), children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: cx("bsb-user", "bsb-user--centered", classNames.user), children: avatar }) });
616
+ }
617
+ if (collapsed && collapsedFooter === "stack") {
618
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx("bsb-footer", "bsb-footer--collapsed", "bsb-footer--stacked", classNames.footer), children: [
619
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cx("bsb-user", "bsb-user--centered", classNames.user), children: avatar }),
620
+ actions.map((action, i) => /* @__PURE__ */ jsxRuntime.jsx(
621
+ "button",
622
+ {
623
+ type: "button",
624
+ className: cx("bsb-footer-action", classNames.footerAction),
625
+ title: action.label,
626
+ "aria-label": action.label,
627
+ onClick: action.onClick,
628
+ children: action.icon
629
+ },
630
+ i
631
+ ))
632
+ ] });
633
+ }
634
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cx("bsb-footer", classNames.footer), children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx("bsb-user", classNames.user), children: [
635
+ avatar,
636
+ (user?.name != null || user?.email != null) && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "bsb-user__text", children: [
637
+ user?.name != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx("bsb-user__name", classNames.userName), children: user.name }),
638
+ user?.email != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx("bsb-user__meta", classNames.userMeta), children: user.email })
639
+ ] }),
640
+ actions.map((action, i) => /* @__PURE__ */ jsxRuntime.jsx(
641
+ "button",
642
+ {
643
+ type: "button",
644
+ className: cx("bsb-footer-action", classNames.footerAction),
645
+ title: action.label,
646
+ "aria-label": action.label,
647
+ onClick: action.onClick,
648
+ children: action.icon
649
+ },
650
+ i
651
+ ))
652
+ ] }) });
653
+ }
654
+ function CollapseToggle({
655
+ collapsed,
656
+ onToggle,
657
+ className,
658
+ position,
659
+ strategy,
660
+ labels,
661
+ icon
662
+ }) {
663
+ const label = collapsed ? labels.expand : labels.collapse;
664
+ const showRightChevron = position === "left" ? collapsed : !collapsed;
665
+ return /* @__PURE__ */ jsxRuntime.jsx(
666
+ "button",
667
+ {
668
+ type: "button",
669
+ onClick: onToggle,
670
+ title: label,
671
+ "aria-label": label,
672
+ "data-toggle-strategy": strategy,
673
+ className: cx("bsb-toggle", className),
674
+ children: icon ? icon(collapsed) : showRightChevron ? /* @__PURE__ */ jsxRuntime.jsx(ChevronRightIcon, { size: 13 }) : /* @__PURE__ */ jsxRuntime.jsx(ChevronLeftIcon, { size: 13 })
675
+ }
676
+ );
677
+ }
678
+ function SidebarShell({
679
+ items,
680
+ ariaLabel,
681
+ brand,
682
+ header,
683
+ footer,
684
+ user,
685
+ footerAction,
686
+ collapsed,
687
+ collapsedFooter,
688
+ showToggle,
689
+ onToggle,
690
+ togglePosition,
691
+ toggleStrategy,
692
+ toggleLabels,
693
+ classNames,
694
+ slots,
695
+ renderCtx,
696
+ extraHeader
697
+ }) {
698
+ const navTop = typeof slots.navTop === "function" ? slots.navTop(renderCtx) : slots.navTop;
699
+ const navBottom = typeof slots.navBottom === "function" ? slots.navBottom(renderCtx) : slots.navBottom;
700
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bsb-shell", children: [
701
+ /* @__PURE__ */ jsxRuntime.jsx(SidebarHeader, { brand, header, collapsed, classNames, renderCtx }),
702
+ extraHeader,
703
+ navTop,
704
+ /* @__PURE__ */ jsxRuntime.jsx(SidebarNav, { items, ariaLabel }),
705
+ navBottom,
706
+ /* @__PURE__ */ jsxRuntime.jsx(
707
+ SidebarFooter,
708
+ {
709
+ user,
710
+ footerAction,
711
+ footer,
712
+ collapsed,
713
+ collapsedFooter,
714
+ classNames,
715
+ renderCtx
716
+ }
717
+ ),
718
+ showToggle && /* @__PURE__ */ jsxRuntime.jsx(
719
+ CollapseToggle,
720
+ {
721
+ collapsed,
722
+ onToggle,
723
+ className: classNames.toggle,
724
+ position: togglePosition,
725
+ strategy: toggleStrategy,
726
+ labels: toggleLabels,
727
+ icon: slots.collapseIcon
728
+ }
729
+ )
730
+ ] });
731
+ }
732
+ function useMountTransition(open, durationMs) {
733
+ const [mounted, setMounted] = React21__namespace.useState(open);
734
+ const [state, setState] = React21__namespace.useState(open ? "open" : "closed");
735
+ React21__namespace.useEffect(() => {
736
+ if (open) {
737
+ setMounted(true);
738
+ const raf1 = requestAnimationFrame(() => {
739
+ requestAnimationFrame(() => setState("open"));
740
+ });
741
+ return () => cancelAnimationFrame(raf1);
742
+ }
743
+ setState("closed");
744
+ const timer = setTimeout(() => setMounted(false), durationMs + 60);
745
+ return () => clearTimeout(timer);
746
+ }, [open, durationMs]);
747
+ const onPanelTransitionEnd = React21__namespace.useCallback(
748
+ (e) => {
749
+ if (e.propertyName === "transform" && !open) setMounted(false);
750
+ },
751
+ [open]
752
+ );
753
+ return { mounted, state, onPanelTransitionEnd };
754
+ }
755
+ var FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
756
+ function useFocusTrap(containerRef, { active, onEscape, restoreFocus = true }) {
757
+ const previouslyFocused = React21__namespace.useRef(null);
758
+ React21__namespace.useEffect(() => {
759
+ if (!active || typeof document === "undefined") return;
760
+ previouslyFocused.current = document.activeElement;
761
+ const container = containerRef.current;
762
+ const focusFirst = () => {
763
+ if (!container) return;
764
+ const focusable = container.querySelectorAll(FOCUSABLE_SELECTOR);
765
+ (focusable[0] ?? container).focus();
766
+ };
767
+ const id = requestAnimationFrame(focusFirst);
768
+ const onKeyDown = (e) => {
769
+ if (e.key === "Escape") {
770
+ onEscape?.();
771
+ return;
772
+ }
773
+ if (e.key !== "Tab" || !container) return;
774
+ const focusable = Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR));
775
+ if (focusable.length === 0) {
776
+ e.preventDefault();
777
+ return;
778
+ }
779
+ const first = focusable[0];
780
+ const last = focusable[focusable.length - 1];
781
+ const activeEl = document.activeElement;
782
+ if (e.shiftKey && activeEl === first) {
783
+ e.preventDefault();
784
+ last.focus();
785
+ } else if (!e.shiftKey && activeEl === last) {
786
+ e.preventDefault();
787
+ first.focus();
788
+ }
789
+ };
790
+ document.addEventListener("keydown", onKeyDown);
791
+ return () => {
792
+ cancelAnimationFrame(id);
793
+ document.removeEventListener("keydown", onKeyDown);
794
+ if (restoreFocus) previouslyFocused.current?.focus?.();
795
+ };
796
+ }, [active, containerRef, onEscape, restoreFocus]);
797
+ }
798
+ var lockCount = 0;
799
+ var savedOverflow = "";
800
+ var savedPaddingRight = "";
801
+ function lock() {
802
+ if (typeof document === "undefined") return;
803
+ if (lockCount === 0) {
804
+ const body = document.body;
805
+ const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
806
+ savedOverflow = body.style.overflow;
807
+ savedPaddingRight = body.style.paddingRight;
808
+ body.style.overflow = "hidden";
809
+ if (scrollbarWidth > 0) {
810
+ const current = parseFloat(getComputedStyle(body).paddingRight) || 0;
811
+ body.style.paddingRight = `${current + scrollbarWidth}px`;
812
+ }
813
+ }
814
+ lockCount++;
815
+ }
816
+ function unlock() {
817
+ if (typeof document === "undefined") return;
818
+ lockCount = Math.max(0, lockCount - 1);
819
+ if (lockCount === 0) {
820
+ document.body.style.overflow = savedOverflow;
821
+ document.body.style.paddingRight = savedPaddingRight;
822
+ }
823
+ }
824
+ function useBodyScrollLock(active) {
825
+ React21__namespace.useEffect(() => {
826
+ if (!active) return;
827
+ lock();
828
+ return unlock;
829
+ }, [active]);
830
+ }
831
+ function Portal({ target, disabled, children }) {
832
+ const [mounted, setMounted] = React21__namespace.useState(false);
833
+ React21__namespace.useEffect(() => setMounted(true), []);
834
+ if (disabled) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
835
+ if (!mounted || typeof document === "undefined") return null;
836
+ const node = typeof target === "function" ? target() : target;
837
+ return reactDom.createPortal(children, node ?? document.body);
838
+ }
839
+ function MobileDrawer({
840
+ open,
841
+ onClose,
842
+ ariaLabel,
843
+ width,
844
+ position,
845
+ closeOnEscape,
846
+ closeOnBackdropClick,
847
+ trapFocus,
848
+ lockScroll,
849
+ restoreFocus,
850
+ portalTarget,
851
+ disablePortal,
852
+ closeLabel,
853
+ classNames,
854
+ durationMs = 400,
855
+ children
856
+ }) {
857
+ const { mounted, state, onPanelTransitionEnd } = useMountTransition(open, durationMs);
858
+ const panelRef = React21__namespace.useRef(null);
859
+ useFocusTrap(panelRef, {
860
+ active: trapFocus && open,
861
+ onEscape: closeOnEscape ? onClose : void 0,
862
+ restoreFocus
863
+ });
864
+ useBodyScrollLock(lockScroll && open);
865
+ if (!mounted) return null;
866
+ const widthValue = typeof width === "number" ? `${width}px` : width;
867
+ return /* @__PURE__ */ jsxRuntime.jsxs(Portal, { target: portalTarget, disabled: disablePortal, children: [
868
+ /* @__PURE__ */ jsxRuntime.jsx(
869
+ "div",
870
+ {
871
+ className: cx("bsb-backdrop", classNames.backdrop),
872
+ "data-state": state,
873
+ "aria-hidden": "true",
874
+ onClick: closeOnBackdropClick ? onClose : void 0
875
+ }
876
+ ),
877
+ /* @__PURE__ */ jsxRuntime.jsxs(
878
+ "div",
879
+ {
880
+ ref: panelRef,
881
+ className: cx("bsb-drawer", classNames.drawer),
882
+ "data-state": state,
883
+ "data-position": position,
884
+ style: { "--bsb-drawer-current-width": widthValue },
885
+ role: "dialog",
886
+ "aria-modal": "true",
887
+ "aria-label": ariaLabel,
888
+ tabIndex: -1,
889
+ onTransitionEnd: onPanelTransitionEnd,
890
+ children: [
891
+ /* @__PURE__ */ jsxRuntime.jsx(
892
+ "button",
893
+ {
894
+ type: "button",
895
+ className: cx("bsb-drawer__close", classNames.drawerClose),
896
+ onClick: onClose,
897
+ "aria-label": closeLabel,
898
+ title: closeLabel,
899
+ children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, { size: 18 })
900
+ }
901
+ ),
902
+ children
903
+ ]
904
+ }
905
+ )
906
+ ] });
907
+ }
908
+ function widthToCss(value) {
909
+ if (value == null) return void 0;
910
+ return typeof value === "number" ? `${value}px` : value;
911
+ }
912
+ var Sidebar = React21__namespace.forwardRef(function Sidebar2(props, ref) {
913
+ const {
914
+ items,
915
+ renderItem,
916
+ slots = {},
917
+ classNames = {},
918
+ currentPath: currentPathProp,
919
+ linkComponent,
920
+ hrefProp = "href",
921
+ renderLink,
922
+ isItemActive,
923
+ exact = false,
924
+ onItemClick,
925
+ closeOnNavigate = true,
926
+ collapsible = true,
927
+ collapsed: collapsedProp,
928
+ defaultCollapsed = false,
929
+ onCollapsedChange,
930
+ persistCollapse,
931
+ showCollapseToggle,
932
+ collapsedFooter = "avatar",
933
+ openGroups: openGroupsProp,
934
+ defaultOpenGroups = [],
935
+ onOpenGroupsChange,
936
+ accordion = false,
937
+ autoOpenActiveGroup = true,
938
+ collapsedGroupBehavior = "flyout",
939
+ mobileOpen: mobileOpenProp,
940
+ onMobileClose,
941
+ onMobileOpenChange,
942
+ breakpoint = 1024,
943
+ responsive = true,
944
+ drawerWidth = 280,
945
+ closeOnEscape = true,
946
+ closeOnBackdropClick = true,
947
+ trapFocus = true,
948
+ lockScroll = true,
949
+ restoreFocus = true,
950
+ portalTarget,
951
+ disablePortal,
952
+ brand,
953
+ user,
954
+ footerAction,
955
+ header,
956
+ footer,
957
+ theme,
958
+ colorScheme: colorSchemeProp = "auto",
959
+ width,
960
+ collapsedWidth,
961
+ position = "left",
962
+ bordered = true,
963
+ tooltips = true,
964
+ reduceMotion = "auto",
965
+ toggleStrategy = "absolute",
966
+ as: RootTag = "aside",
967
+ id,
968
+ className,
969
+ style,
970
+ "aria-label": ariaLabel = "Main navigation"
971
+ } = props;
972
+ const rootRef = React21__namespace.useRef(null);
973
+ const [internalCollapsed, setInternalCollapsed] = useControllableState({
974
+ value: collapsedProp,
975
+ defaultValue: defaultCollapsed,
976
+ onChange: onCollapsedChange
977
+ });
978
+ const isCollapseControlled = collapsedProp !== void 0;
979
+ const [persistedCollapsed, setPersistedCollapsed] = usePersistedCollapse(
980
+ isCollapseControlled ? void 0 : persistCollapse
981
+ );
982
+ const collapsed = !collapsible ? false : isCollapseControlled ? collapsedProp : persistedCollapsed ?? internalCollapsed;
983
+ const setCollapsed = React21__namespace.useCallback(
984
+ (next) => {
985
+ setInternalCollapsed(next);
986
+ if (persistCollapse) setPersistedCollapsed(next);
987
+ },
988
+ [setInternalCollapsed, setPersistedCollapsed, persistCollapse]
989
+ );
990
+ const toggleCollapsed = React21__namespace.useCallback(() => setCollapsed(!collapsed), [setCollapsed, collapsed]);
991
+ const mediaIsMobile = useMediaQuery(toBreakpointQuery(breakpoint));
992
+ const isMobile = responsive === false ? false : mediaIsMobile;
993
+ const [mobileOpen, setMobileOpenInternal] = useControllableState({
994
+ value: mobileOpenProp,
995
+ defaultValue: false,
996
+ onChange: (open) => {
997
+ onMobileOpenChange?.(open);
998
+ if (!open) onMobileClose?.();
999
+ }
1000
+ });
1001
+ const closeMobile = React21__namespace.useCallback(() => setMobileOpenInternal(false), [setMobileOpenInternal]);
1002
+ const openMobile = React21__namespace.useCallback(() => setMobileOpenInternal(true), [setMobileOpenInternal]);
1003
+ const [openGroupsArr, setOpenGroupsArr] = useControllableState({
1004
+ value: openGroupsProp,
1005
+ defaultValue: defaultOpenGroups,
1006
+ onChange: onOpenGroupsChange
1007
+ });
1008
+ const openGroups = React21__namespace.useMemo(() => new Set(openGroupsArr), [openGroupsArr]);
1009
+ const toggleGroup = React21__namespace.useCallback(
1010
+ (id2) => {
1011
+ setOpenGroupsArr((prev) => {
1012
+ const has = prev.includes(id2);
1013
+ if (accordion) return has ? [] : [id2];
1014
+ return has ? prev.filter((x) => x !== id2) : [...prev, id2];
1015
+ });
1016
+ },
1017
+ [setOpenGroupsArr, accordion]
1018
+ );
1019
+ const currentPath = useCurrentPath(currentPathProp);
1020
+ const handleNavigate = React21__namespace.useCallback(
1021
+ (item, e) => {
1022
+ onItemClick?.(item, e);
1023
+ const type = item.type ?? "link";
1024
+ if (isMobile && closeOnNavigate && type !== "group") closeMobile();
1025
+ },
1026
+ [onItemClick, isMobile, closeOnNavigate, closeMobile]
1027
+ );
1028
+ const colorScheme = useColorScheme(colorSchemeProp, rootRef);
1029
+ const themeStyle = themeToStyle(theme, colorScheme);
1030
+ const widthStyle = {};
1031
+ if (width != null) widthStyle["--bsb-width"] = widthToCss(width);
1032
+ if (collapsedWidth != null)
1033
+ widthStyle["--bsb-width-collapsed"] = widthToCss(collapsedWidth);
1034
+ if (drawerWidth != null)
1035
+ widthStyle["--bsb-drawer-width"] = widthToCss(drawerWidth);
1036
+ const mergedStyle = { ...themeStyle, ...widthStyle, ...style };
1037
+ React21__namespace.useImperativeHandle(
1038
+ ref,
1039
+ () => ({
1040
+ collapse: () => setCollapsed(true),
1041
+ expand: () => setCollapsed(false),
1042
+ toggleCollapsed,
1043
+ openMobile,
1044
+ closeMobile,
1045
+ get element() {
1046
+ return rootRef.current;
1047
+ }
1048
+ }),
1049
+ [setCollapsed, toggleCollapsed, openMobile, closeMobile]
1050
+ );
1051
+ const renderCtx = React21__namespace.useMemo(
1052
+ () => ({ collapsed, isMobile, colorScheme, currentPath, toggleCollapsed, closeMobile }),
1053
+ [collapsed, isMobile, colorScheme, currentPath, toggleCollapsed, closeMobile]
1054
+ );
1055
+ const navContextValue = React21__namespace.useMemo(
1056
+ () => ({
1057
+ currentPath,
1058
+ linkComponent,
1059
+ hrefProp,
1060
+ renderLink,
1061
+ isItemActive,
1062
+ exact,
1063
+ handleNavigate,
1064
+ collapsed,
1065
+ isMobile,
1066
+ colorScheme,
1067
+ openGroups,
1068
+ toggleGroup,
1069
+ accordion,
1070
+ autoOpenActiveGroup,
1071
+ collapsedGroupBehavior,
1072
+ tooltips,
1073
+ classNames,
1074
+ slots,
1075
+ renderItem,
1076
+ renderCtx
1077
+ }),
1078
+ [
1079
+ currentPath,
1080
+ linkComponent,
1081
+ hrefProp,
1082
+ renderLink,
1083
+ isItemActive,
1084
+ exact,
1085
+ handleNavigate,
1086
+ collapsed,
1087
+ isMobile,
1088
+ colorScheme,
1089
+ openGroups,
1090
+ toggleGroup,
1091
+ accordion,
1092
+ autoOpenActiveGroup,
1093
+ collapsedGroupBehavior,
1094
+ tooltips,
1095
+ classNames,
1096
+ slots,
1097
+ renderItem,
1098
+ renderCtx
1099
+ ]
1100
+ );
1101
+ const rootClassName = cx(
1102
+ "bsb-root",
1103
+ bordered && "bsb-root--bordered",
1104
+ classNames.root,
1105
+ className
1106
+ );
1107
+ const rootDataAttrs = {
1108
+ "data-collapsed": collapsed,
1109
+ "data-position": position,
1110
+ ...colorSchemeProp !== "auto" ? { "data-color-scheme": colorScheme } : {},
1111
+ ...reduceMotion === true ? { "data-reduce-motion": "true" } : {}
1112
+ };
1113
+ const shellProps = {
1114
+ items,
1115
+ ariaLabel,
1116
+ brand,
1117
+ header,
1118
+ footer,
1119
+ user,
1120
+ footerAction,
1121
+ collapsedFooter,
1122
+ classNames,
1123
+ slots,
1124
+ renderCtx
1125
+ };
1126
+ const desktopToggle = collapsible && (showCollapseToggle ?? true);
1127
+ return /* @__PURE__ */ jsxRuntime.jsxs(NavContext.Provider, { value: navContextValue, children: [
1128
+ !isMobile && /* @__PURE__ */ jsxRuntime.jsx(
1129
+ RootTag,
1130
+ {
1131
+ ref: rootRef,
1132
+ id,
1133
+ className: rootClassName,
1134
+ style: mergedStyle,
1135
+ ...rootDataAttrs,
1136
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1137
+ SidebarShell,
1138
+ {
1139
+ ...shellProps,
1140
+ collapsed,
1141
+ showToggle: desktopToggle,
1142
+ onToggle: toggleCollapsed,
1143
+ togglePosition: position,
1144
+ toggleStrategy,
1145
+ toggleLabels: { expand: "Expand sidebar", collapse: "Collapse sidebar" }
1146
+ }
1147
+ )
1148
+ }
1149
+ ),
1150
+ isMobile && /* @__PURE__ */ jsxRuntime.jsx(
1151
+ "div",
1152
+ {
1153
+ ref: rootRef,
1154
+ className: rootClassName,
1155
+ style: mergedStyle,
1156
+ ...rootDataAttrs,
1157
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1158
+ MobileDrawer,
1159
+ {
1160
+ open: mobileOpen,
1161
+ onClose: closeMobile,
1162
+ ariaLabel,
1163
+ width: drawerWidth,
1164
+ position,
1165
+ closeOnEscape,
1166
+ closeOnBackdropClick,
1167
+ trapFocus,
1168
+ lockScroll,
1169
+ restoreFocus,
1170
+ portalTarget,
1171
+ disablePortal,
1172
+ closeLabel: "Close navigation",
1173
+ classNames,
1174
+ children: /* @__PURE__ */ jsxRuntime.jsx(SidebarShell, { ...shellProps, collapsed: false, showToggle: false, onToggle: toggleCollapsed, togglePosition: position, toggleStrategy, toggleLabels: { expand: "Expand sidebar", collapse: "Collapse sidebar" } })
1175
+ }
1176
+ )
1177
+ }
1178
+ )
1179
+ ] });
1180
+ });
1181
+ Sidebar.displayName = "Sidebar";
1182
+
1183
+ // src/lib/themes.ts
1184
+ var slate = {};
1185
+ var greenMist = {
1186
+ fontDisplay: `'Plus Jakarta Sans', system-ui, sans-serif`,
1187
+ radiusItem: "16px",
1188
+ radiusLogo: "10px",
1189
+ radiusTooltip: "10px",
1190
+ radiusAction: "4px",
1191
+ ease: "cubic-bezier(0.16, 1, 0.3, 1)",
1192
+ shadowLogo: "0 1px 2px rgba(10,37,16,0.05)",
1193
+ shadowToggle: "0 4px 12px rgba(10,37,16,0.08)",
1194
+ shadowTooltip: "0 12px 32px rgba(10,37,16,0.12)",
1195
+ shadowDrawer: "0 24px 48px rgba(10,37,16,0.16)",
1196
+ badgeFontSize: "9px",
1197
+ light: {
1198
+ bg: "#0f3a18",
1199
+ border: "rgba(23,84,35,0.6)",
1200
+ divider: "rgba(23,84,35,0.4)",
1201
+ itemFg: "#b3ddb6",
1202
+ itemHoverBg: "rgba(23,84,35,0.7)",
1203
+ itemHoverFg: "#ffffff",
1204
+ itemActiveBg: "rgba(31,110,46,0.6)",
1205
+ itemActiveFg: "#ffffff",
1206
+ itemAccent: "#7cc485",
1207
+ headingFg: "#4faa5a",
1208
+ logoBg: "#4faa5a",
1209
+ logoFg: "#ffffff",
1210
+ titleFg: "#ffffff",
1211
+ subtitleFg: "#4faa5a",
1212
+ tooltipBg: "#0a2510",
1213
+ tooltipFg: "#d9eed9",
1214
+ tooltipBorder: "#175423",
1215
+ toggleBg: "#2d8a3e",
1216
+ toggleHoverBg: "#4faa5a",
1217
+ toggleFg: "#ffffff",
1218
+ toggleBorder: "#175423",
1219
+ avatarBg: "#2d8a3e",
1220
+ avatarFg: "#ffffff",
1221
+ avatarRing: "rgba(79,170,90,0.4)",
1222
+ userNameFg: "#d9eed9",
1223
+ userMetaFg: "#4faa5a",
1224
+ actionFg: "#4faa5a",
1225
+ actionHoverFg: "#ffffff",
1226
+ actionHoverBg: "rgba(23,84,35,0.5)",
1227
+ badgeBg: "rgba(251,191,36,0.2)",
1228
+ badgeFg: "#fcd34d",
1229
+ badgeBorder: "rgba(251,191,36,0.3)",
1230
+ scrollbarThumb: "#b3ddb6",
1231
+ scrollbarThumbHover: "#7cc485"
1232
+ },
1233
+ dark: {
1234
+ bg: "#18181b",
1235
+ border: "#2d2d2d",
1236
+ divider: "#2d2d2d",
1237
+ itemFg: "#8a8a8a",
1238
+ itemHoverBg: "rgba(255,255,255,0.1)",
1239
+ itemHoverFg: "#f5f5f5",
1240
+ itemActiveBg: "rgba(255,255,255,0.1)",
1241
+ itemActiveFg: "#e2e2e2",
1242
+ itemAccent: "#8a8a8a",
1243
+ headingFg: "#8a8a8a",
1244
+ logoBg: "#27272a",
1245
+ logoFg: "#e2e2e2",
1246
+ titleFg: "#e2e2e2",
1247
+ subtitleFg: "#8a8a8a",
1248
+ tooltipBg: "#0f0f0f",
1249
+ tooltipFg: "#c8c8c8",
1250
+ tooltipBorder: "#2d2d2d",
1251
+ toggleBg: "#27272a",
1252
+ toggleHoverBg: "#333333",
1253
+ toggleFg: "#e2e2e2",
1254
+ toggleBorder: "#3d3d3d",
1255
+ avatarBg: "#27272a",
1256
+ avatarFg: "#e2e2e2",
1257
+ avatarRing: "rgba(255,255,255,0.1)",
1258
+ userNameFg: "#e5e5e5",
1259
+ userMetaFg: "#8a8a8a",
1260
+ actionFg: "#8a8a8a",
1261
+ actionHoverFg: "#ffffff",
1262
+ actionHoverBg: "rgba(255,255,255,0.1)",
1263
+ scrollbarThumb: "#3a3a3a",
1264
+ scrollbarThumbHover: "#4d4d4d"
1265
+ }
1266
+ };
1267
+ var midnight = {
1268
+ light: {
1269
+ bg: "#18181b",
1270
+ border: "rgba(255,255,255,0.08)",
1271
+ divider: "rgba(255,255,255,0.06)",
1272
+ itemFg: "#a1a1aa",
1273
+ itemHoverBg: "rgba(255,255,255,0.08)",
1274
+ itemHoverFg: "#ffffff",
1275
+ itemActiveBg: "rgba(255,255,255,0.12)",
1276
+ itemActiveFg: "#ffffff",
1277
+ itemAccent: "#a78bfa",
1278
+ headingFg: "#71717a",
1279
+ logoBg: "#3f3f46",
1280
+ logoFg: "#ffffff",
1281
+ titleFg: "#ffffff",
1282
+ subtitleFg: "#a1a1aa",
1283
+ tooltipBg: "#09090b",
1284
+ tooltipFg: "#e4e4e7",
1285
+ tooltipBorder: "#3f3f46",
1286
+ toggleBg: "#3f3f46",
1287
+ toggleHoverBg: "#52525b",
1288
+ toggleFg: "#ffffff",
1289
+ toggleBorder: "#18181b",
1290
+ avatarBg: "#3f3f46",
1291
+ avatarFg: "#ffffff",
1292
+ avatarRing: "rgba(255,255,255,0.15)",
1293
+ userNameFg: "#e4e4e7",
1294
+ userMetaFg: "#a1a1aa",
1295
+ actionFg: "#a1a1aa",
1296
+ actionHoverFg: "#ffffff",
1297
+ actionHoverBg: "rgba(255,255,255,0.08)",
1298
+ scrollbarThumb: "rgba(255,255,255,0.15)",
1299
+ scrollbarThumbHover: "rgba(255,255,255,0.25)"
1300
+ }
1301
+ };
1302
+ var themes = { slate, greenMist, midnight };
1303
+ function defineTheme(theme) {
1304
+ return theme;
1305
+ }
1306
+
1307
+ exports.ChevronDownIcon = ChevronDownIcon;
1308
+ exports.ChevronLeftIcon = ChevronLeftIcon;
1309
+ exports.ChevronRightIcon = ChevronRightIcon;
1310
+ exports.CloseIcon = CloseIcon;
1311
+ exports.MenuIcon = MenuIcon;
1312
+ exports.Sidebar = Sidebar;
1313
+ exports.defaultIsActive = defaultIsActive;
1314
+ exports.defineTheme = defineTheme;
1315
+ exports.greenMist = greenMist;
1316
+ exports.midnight = midnight;
1317
+ exports.normalizePath = normalizePath;
1318
+ exports.resolveIsActive = resolveIsActive;
1319
+ exports.slate = slate;
1320
+ exports.themeToStyle = themeToStyle;
1321
+ exports.themes = themes;
1322
+ //# sourceMappingURL=index.cjs.map
1323
+ //# sourceMappingURL=index.cjs.map