@marktiderman/genesis-ui 2.11.0 → 2.12.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/SKILL.md +57 -0
- package/dist/AppShell-DpQdcLE-.d.ts +347 -0
- package/dist/{chunk-5GOQ72WB.js → chunk-3TWCZYZY.js} +3 -3
- package/dist/{chunk-5GOQ72WB.js.map → chunk-3TWCZYZY.js.map} +1 -1
- package/dist/{chunk-WGMEVX3Y.js → chunk-KC43CNMA.js} +2 -2
- package/dist/{chunk-WGMEVX3Y.js.map → chunk-KC43CNMA.js.map} +1 -1
- package/dist/{chunk-UYI3GLFJ.js → chunk-SZMXNC5Q.js} +722 -159
- package/dist/chunk-SZMXNC5Q.js.map +1 -0
- package/dist/{chunk-OITWCIG2.js → chunk-XGG5RUDE.js} +3 -3
- package/dist/{chunk-OITWCIG2.js.map → chunk-XGG5RUDE.js.map} +1 -1
- package/dist/components/data/index.d.ts +5 -5
- package/dist/components/data/index.js +6 -5
- package/dist/components/data/index.js.map +1 -1
- package/dist/components/layout/index.d.ts +2 -2
- package/dist/components/layout/index.js +3 -2
- package/dist/components/rich-text/rich-text-editor.js +1 -1
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.js +3 -3
- package/dist/hooks/use-data-filters.d.ts +1 -1
- package/dist/hooks/use-data-filters.js +2 -2
- package/dist/hooks/use-resource-url-state.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/{navigation-Bha0R3Fu.d.ts → navigation-By1DrzaM.d.ts} +24 -0
- package/dist/router/react-router.d.ts +1 -1
- package/dist/router/tanstack-router.d.ts +1 -1
- package/dist/router/tanstack-router.js +1 -1
- package/dist/{use-resource-table-Dx1OZRne.d.ts → use-resource-table-DrTkD5O1.d.ts} +1 -1
- package/package.json +3 -2
- package/dist/AppShell-BVr4V0UK.d.ts +0 -174
- package/dist/chunk-UYI3GLFJ.js.map +0 -1
|
@@ -4,14 +4,16 @@ import { Collapsible, CollapsibleContent } from './chunk-B2GIBUQD.js';
|
|
|
4
4
|
import { Badge } from './chunk-NPNADZHM.js';
|
|
5
5
|
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from './chunk-QJ6P2DW6.js';
|
|
6
6
|
import { Switch } from './chunk-27ELYTGE.js';
|
|
7
|
+
import { Tooltip } from './chunk-G5YKBPBF.js';
|
|
7
8
|
import { Popover, PopoverTrigger, PopoverContent } from './chunk-22XB3Q6H.js';
|
|
8
9
|
import { Button } from './chunk-NWSCYLPT.js';
|
|
9
10
|
import { Label } from './chunk-F47U5KNW.js';
|
|
10
11
|
import { cn } from './chunk-NZ5WBA4R.js';
|
|
11
|
-
import { resolveNavigation, DefaultLink
|
|
12
|
-
import { useId, useState, useEffect, useMemo, useCallback } from 'react';
|
|
13
|
-
import { ListFilter, MoreHorizontal, SlidersHorizontal, Menu, ArrowLeft, X, PanelLeft, PanelLeftClose,
|
|
12
|
+
import { isPathActive, resolveNavigation, DefaultLink } from './chunk-KC43CNMA.js';
|
|
13
|
+
import { createContext, useId, useState, useEffect, useMemo, useCallback, useContext, useRef, useLayoutEffect, useSyncExternalStore } from 'react';
|
|
14
|
+
import { ListFilter, MoreHorizontal, SlidersHorizontal, Menu, ArrowLeft, X, Sun, Moon, PanelLeft, PanelLeftClose, ChevronRight, ChevronDown } from 'lucide-react';
|
|
14
15
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
16
|
+
import { createPortal } from 'react-dom';
|
|
15
17
|
|
|
16
18
|
// src/option-storage.ts
|
|
17
19
|
function readStoredOptions(key) {
|
|
@@ -391,6 +393,206 @@ function PageHeader({
|
|
|
391
393
|
] }) })
|
|
392
394
|
] });
|
|
393
395
|
}
|
|
396
|
+
var useIsoLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
397
|
+
function itemIsActive(item, activePath, isActive) {
|
|
398
|
+
if (item.end) {
|
|
399
|
+
return isPathActive(item.to, activePath, { end: true });
|
|
400
|
+
}
|
|
401
|
+
if (isActive(item.to, activePath)) return true;
|
|
402
|
+
return Boolean(
|
|
403
|
+
item.children?.some((child) => itemIsActive(child, activePath, isActive))
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
function mostSpecificActive(items, activePath, isActive) {
|
|
407
|
+
let best;
|
|
408
|
+
for (const item of items) {
|
|
409
|
+
if (!itemIsActive(item, activePath, isActive)) continue;
|
|
410
|
+
if (!best || item.to.length > best.to.length) best = item;
|
|
411
|
+
}
|
|
412
|
+
return best;
|
|
413
|
+
}
|
|
414
|
+
function BadgeChip({ badge, active }) {
|
|
415
|
+
return /* @__PURE__ */ jsx(
|
|
416
|
+
"span",
|
|
417
|
+
{
|
|
418
|
+
className: cn(
|
|
419
|
+
"inline-flex h-5 min-w-5 shrink-0 items-center justify-center rounded-full px-1.5 text-xs font-medium tabular-nums",
|
|
420
|
+
active ? "bg-primary/10 text-primary" : "bg-muted text-muted-foreground"
|
|
421
|
+
),
|
|
422
|
+
children: badge
|
|
423
|
+
}
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
function SubNavTabs({
|
|
427
|
+
items,
|
|
428
|
+
activePath,
|
|
429
|
+
linkComponent: LinkComp,
|
|
430
|
+
isActive = isPathActive,
|
|
431
|
+
end,
|
|
432
|
+
label = "Page sections",
|
|
433
|
+
className
|
|
434
|
+
}) {
|
|
435
|
+
const current = mostSpecificActive(items, activePath, isActive);
|
|
436
|
+
return /* @__PURE__ */ jsxs(
|
|
437
|
+
"div",
|
|
438
|
+
{
|
|
439
|
+
className: cn("flex min-w-0 items-end gap-4 border-b", className),
|
|
440
|
+
"data-testid": "app-shell-sub-nav",
|
|
441
|
+
children: [
|
|
442
|
+
/* @__PURE__ */ jsx("nav", { "aria-label": label, className: "-mb-px min-w-0 flex-1 overflow-x-auto", children: /* @__PURE__ */ jsx("ul", { className: "flex items-end gap-6", children: items.map((item) => {
|
|
443
|
+
const active = item === current;
|
|
444
|
+
const Icon = item.icon;
|
|
445
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
446
|
+
Icon ? /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0", "aria-hidden": true }) : null,
|
|
447
|
+
/* @__PURE__ */ jsx("span", { className: "whitespace-nowrap", children: item.label }),
|
|
448
|
+
item.badge != null ? /* @__PURE__ */ jsx(BadgeChip, { badge: item.badge, active }) : null
|
|
449
|
+
] });
|
|
450
|
+
const cls = cn(
|
|
451
|
+
"flex items-center gap-1.5 border-b-2 pb-2.5 pt-2 text-sm transition-colors motion-reduce:transition-none",
|
|
452
|
+
active ? "border-primary font-semibold text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
|
|
453
|
+
);
|
|
454
|
+
return /* @__PURE__ */ jsx("li", { className: "shrink-0", children: item.disabled ? /* @__PURE__ */ jsx("span", { className: cn(cls, "opacity-40"), role: "link", "aria-disabled": "true", children: body }) : /* @__PURE__ */ jsx(
|
|
455
|
+
LinkComp,
|
|
456
|
+
{
|
|
457
|
+
href: item.to,
|
|
458
|
+
"aria-current": active ? "page" : void 0,
|
|
459
|
+
className: cn(
|
|
460
|
+
cls,
|
|
461
|
+
"rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
462
|
+
),
|
|
463
|
+
testID: `app-shell-sub-nav-${item.label}`,
|
|
464
|
+
children: body
|
|
465
|
+
}
|
|
466
|
+
) }, item.to + item.label);
|
|
467
|
+
}) }) }),
|
|
468
|
+
end != null ? /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center pb-2", children: end }) : null
|
|
469
|
+
]
|
|
470
|
+
}
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
var SubNavContext = createContext(null);
|
|
474
|
+
function innermost(claims) {
|
|
475
|
+
let best;
|
|
476
|
+
for (const c of claims) if (!best || c.order > best.order) best = c;
|
|
477
|
+
return best?.id;
|
|
478
|
+
}
|
|
479
|
+
function useSubNavChannel() {
|
|
480
|
+
const [entries, setEntries] = useState([]);
|
|
481
|
+
const [endSlot, setEndSlot] = useState(null);
|
|
482
|
+
const [headerSlot, setHeaderSlot] = useState(null);
|
|
483
|
+
const [claims, setClaims] = useState({ header: [], end: [] });
|
|
484
|
+
const register = useCallback((id, order, value) => {
|
|
485
|
+
setEntries(
|
|
486
|
+
(prev) => prev.some((e) => e.id === id) ? prev.map((e) => e.id === id ? { ...value, id, order } : e) : [...prev, { ...value, id, order }]
|
|
487
|
+
);
|
|
488
|
+
}, []);
|
|
489
|
+
const unregister = useCallback((id) => {
|
|
490
|
+
setEntries((prev) => prev.filter((e) => e.id !== id));
|
|
491
|
+
}, []);
|
|
492
|
+
const claimSlot = useCallback((slot, id, order) => {
|
|
493
|
+
setClaims(
|
|
494
|
+
(prev) => prev[slot].some((c) => c.id === id) ? prev : { ...prev, [slot]: [...prev[slot], { id, order }] }
|
|
495
|
+
);
|
|
496
|
+
}, []);
|
|
497
|
+
const releaseSlot = useCallback((slot, id) => {
|
|
498
|
+
setClaims(
|
|
499
|
+
(prev) => prev[slot].some((c) => c.id === id) ? { ...prev, [slot]: prev[slot].filter((c) => c.id !== id) } : prev
|
|
500
|
+
);
|
|
501
|
+
}, []);
|
|
502
|
+
const headerOwner = innermost(claims.header);
|
|
503
|
+
const endOwner = innermost(claims.end);
|
|
504
|
+
const channel = useMemo(
|
|
505
|
+
() => ({
|
|
506
|
+
register,
|
|
507
|
+
unregister,
|
|
508
|
+
claimSlot,
|
|
509
|
+
releaseSlot,
|
|
510
|
+
slots: {
|
|
511
|
+
header: { el: headerSlot, owner: headerOwner },
|
|
512
|
+
end: { el: endSlot, owner: endOwner }
|
|
513
|
+
}
|
|
514
|
+
}),
|
|
515
|
+
[register, unregister, claimSlot, releaseSlot, headerSlot, headerOwner, endSlot, endOwner]
|
|
516
|
+
);
|
|
517
|
+
const byDepth = [...entries].sort((a, b) => b.order - a.order);
|
|
518
|
+
const items = byDepth.find((e) => e.items)?.items;
|
|
519
|
+
const mode = byDepth.find((e) => e.mode)?.mode;
|
|
520
|
+
return {
|
|
521
|
+
page: items || mode ? { items, mode } : void 0,
|
|
522
|
+
channel,
|
|
523
|
+
setEndSlot,
|
|
524
|
+
setHeaderSlot,
|
|
525
|
+
hasHeader: headerOwner !== void 0,
|
|
526
|
+
hasEnd: endOwner !== void 0
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
var nextOrder = 0;
|
|
530
|
+
var iconIds = /* @__PURE__ */ new WeakMap();
|
|
531
|
+
var nextIconId = 1;
|
|
532
|
+
function iconKey(icon) {
|
|
533
|
+
if (!icon || typeof icon !== "object" && typeof icon !== "function") return 0;
|
|
534
|
+
let id = iconIds.get(icon);
|
|
535
|
+
if (id == null) {
|
|
536
|
+
id = nextIconId++;
|
|
537
|
+
iconIds.set(icon, id);
|
|
538
|
+
}
|
|
539
|
+
return id;
|
|
540
|
+
}
|
|
541
|
+
function itemsKey(items) {
|
|
542
|
+
return JSON.stringify(
|
|
543
|
+
items.map((i) => [
|
|
544
|
+
i.to,
|
|
545
|
+
i.label,
|
|
546
|
+
i.badge ?? null,
|
|
547
|
+
Boolean(i.disabled),
|
|
548
|
+
Boolean(i.end),
|
|
549
|
+
iconKey(i.icon),
|
|
550
|
+
i.children ? itemsKey(i.children) : null
|
|
551
|
+
])
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
function useAppShellSubNav(items, options) {
|
|
555
|
+
const channel = useContext(SubNavContext);
|
|
556
|
+
const id = useId();
|
|
557
|
+
const [order] = useState(() => ++nextOrder);
|
|
558
|
+
const latest = useRef(items);
|
|
559
|
+
latest.current = items;
|
|
560
|
+
const key = items ? itemsKey(items) : "";
|
|
561
|
+
const mode = options?.mode;
|
|
562
|
+
const register = channel?.register;
|
|
563
|
+
const unregister = channel?.unregister;
|
|
564
|
+
useIsoLayoutEffect(() => {
|
|
565
|
+
if (!register || !unregister) return;
|
|
566
|
+
const value = latest.current ?? void 0;
|
|
567
|
+
if (value || mode) register(id, order, { items: value, mode });
|
|
568
|
+
else unregister(id);
|
|
569
|
+
}, [register, unregister, id, order, key, mode]);
|
|
570
|
+
useIsoLayoutEffect(() => {
|
|
571
|
+
if (!unregister) return;
|
|
572
|
+
return () => unregister(id);
|
|
573
|
+
}, [unregister, id]);
|
|
574
|
+
}
|
|
575
|
+
function useSlotPortal(slot, children) {
|
|
576
|
+
const channel = useContext(SubNavContext);
|
|
577
|
+
const id = useId();
|
|
578
|
+
const [order] = useState(() => ++nextOrder);
|
|
579
|
+
const claimSlot = channel?.claimSlot;
|
|
580
|
+
const releaseSlot = channel?.releaseSlot;
|
|
581
|
+
useIsoLayoutEffect(() => {
|
|
582
|
+
if (!claimSlot || !releaseSlot) return;
|
|
583
|
+
claimSlot(slot, id, order);
|
|
584
|
+
return () => releaseSlot(slot, id);
|
|
585
|
+
}, [claimSlot, releaseSlot, slot, id, order]);
|
|
586
|
+
const target = channel?.slots[slot];
|
|
587
|
+
if (!target?.el || target.owner !== id) return null;
|
|
588
|
+
return createPortal(children, target.el);
|
|
589
|
+
}
|
|
590
|
+
function AppShellSubNavEnd({ children }) {
|
|
591
|
+
return useSlotPortal("end", children);
|
|
592
|
+
}
|
|
593
|
+
function AppShellPageHeader({ children }) {
|
|
594
|
+
return useSlotPortal("header", children);
|
|
595
|
+
}
|
|
394
596
|
var OPTION_DEFAULTS2 = {
|
|
395
597
|
showLogo: true,
|
|
396
598
|
showAppName: true,
|
|
@@ -401,11 +603,27 @@ var OPTION_DEFAULTS2 = {
|
|
|
401
603
|
showSecondaryNav: true,
|
|
402
604
|
showMobileBottomNav: true,
|
|
403
605
|
defaultCollapsed: false,
|
|
606
|
+
collapseShortcut: false,
|
|
607
|
+
collapsedRailWidth: 56,
|
|
608
|
+
subNav: "section",
|
|
404
609
|
configurable: void 0,
|
|
405
610
|
density: "comfortable",
|
|
406
611
|
railWidth: "narrow",
|
|
407
612
|
scrollRoot: "container"
|
|
408
613
|
};
|
|
614
|
+
var collapsedListeners = /* @__PURE__ */ new Set();
|
|
615
|
+
function subscribeCollapsed(listener) {
|
|
616
|
+
collapsedListeners.add(listener);
|
|
617
|
+
if (typeof window !== "undefined") {
|
|
618
|
+
window.addEventListener("storage", listener);
|
|
619
|
+
}
|
|
620
|
+
return () => {
|
|
621
|
+
collapsedListeners.delete(listener);
|
|
622
|
+
if (typeof window !== "undefined") {
|
|
623
|
+
window.removeEventListener("storage", listener);
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
}
|
|
409
627
|
function readStoredCollapsed(key) {
|
|
410
628
|
if (typeof window === "undefined") return void 0;
|
|
411
629
|
try {
|
|
@@ -420,10 +638,39 @@ function readStoredCollapsed(key) {
|
|
|
420
638
|
function writeStoredCollapsed(key, collapsed) {
|
|
421
639
|
if (typeof window === "undefined") return;
|
|
422
640
|
try {
|
|
423
|
-
localStorage.
|
|
641
|
+
if (collapsed === void 0) localStorage.removeItem(`${key}:collapsed`);
|
|
642
|
+
else localStorage.setItem(`${key}:collapsed`, collapsed ? "1" : "0");
|
|
424
643
|
} catch {
|
|
425
644
|
}
|
|
645
|
+
collapsedListeners.forEach((listener) => listener());
|
|
426
646
|
}
|
|
647
|
+
function useStoredCollapsed(key) {
|
|
648
|
+
return useSyncExternalStore(
|
|
649
|
+
subscribeCollapsed,
|
|
650
|
+
() => key ? readStoredCollapsed(key) : void 0,
|
|
651
|
+
() => void 0
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
function isTypingTarget(el) {
|
|
655
|
+
if (!el || typeof el.tagName !== "string") return false;
|
|
656
|
+
const node = el;
|
|
657
|
+
const tag = node.tagName;
|
|
658
|
+
return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || node.isContentEditable === true || node.closest?.('[contenteditable=""], [contenteditable="true"]') != null;
|
|
659
|
+
}
|
|
660
|
+
var useIsoLayoutEffect2 = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
661
|
+
function ownsKeys(el) {
|
|
662
|
+
const node = el;
|
|
663
|
+
return Boolean(
|
|
664
|
+
node?.closest?.(
|
|
665
|
+
'[role="listbox"], [role="combobox"], [role="menu"], [role="menubar"], [role="menuitem"], [role="option"], [role="tree"], [role="grid"]'
|
|
666
|
+
)
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
function dialogOpen() {
|
|
670
|
+
if (typeof document === "undefined") return false;
|
|
671
|
+
return document.querySelector('[role="dialog"][data-state="open"], [role="alertdialog"][data-state="open"], [aria-modal="true"]') != null;
|
|
672
|
+
}
|
|
673
|
+
var DESKTOP_QUERY = "(min-width: 768px)";
|
|
427
674
|
var PARAM = /^\$(.+)$/;
|
|
428
675
|
function fillParams(to, params) {
|
|
429
676
|
const cut = to.search(/[?#]/);
|
|
@@ -484,15 +731,26 @@ function ThemeToggle({ testId }) {
|
|
|
484
731
|
}
|
|
485
732
|
);
|
|
486
733
|
}
|
|
487
|
-
function
|
|
488
|
-
if (item.
|
|
489
|
-
|
|
734
|
+
function NavIcon({ item, className }) {
|
|
735
|
+
if (item.icon) {
|
|
736
|
+
const Icon = item.icon;
|
|
737
|
+
return /* @__PURE__ */ jsx(Icon, { className });
|
|
490
738
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
739
|
+
return /* @__PURE__ */ jsx(
|
|
740
|
+
"span",
|
|
741
|
+
{
|
|
742
|
+
className: cn(
|
|
743
|
+
className,
|
|
744
|
+
"inline-flex items-center justify-center rounded text-[11px] font-semibold uppercase leading-none"
|
|
745
|
+
),
|
|
746
|
+
"aria-hidden": true,
|
|
747
|
+
children: item.label.trim().charAt(0)
|
|
748
|
+
}
|
|
494
749
|
);
|
|
495
750
|
}
|
|
751
|
+
function RailTooltip({ label, children }) {
|
|
752
|
+
return /* @__PURE__ */ jsx(Tooltip, { content: label, side: "right", className: "flex w-full", children: /* @__PURE__ */ jsx("span", { className: "flex w-full", children }) });
|
|
753
|
+
}
|
|
496
754
|
function NavLinkRow({
|
|
497
755
|
item,
|
|
498
756
|
collapsed,
|
|
@@ -501,51 +759,145 @@ function NavLinkRow({
|
|
|
501
759
|
linkComponent: LinkComp,
|
|
502
760
|
activePath,
|
|
503
761
|
isActive,
|
|
504
|
-
notCurrent = false
|
|
762
|
+
notCurrent = false,
|
|
763
|
+
active: activeOverride
|
|
505
764
|
}) {
|
|
506
|
-
const active = itemIsActive(item, activePath, isActive);
|
|
765
|
+
const active = activeOverride ?? itemIsActive(item, activePath, isActive);
|
|
507
766
|
const pad = density === "compact" ? "px-2.5 py-1.5" : "px-3 py-2";
|
|
508
767
|
if (item.disabled) {
|
|
509
|
-
|
|
768
|
+
if (!collapsed) {
|
|
769
|
+
return /* @__PURE__ */ jsxs(
|
|
770
|
+
"span",
|
|
771
|
+
{
|
|
772
|
+
title: item.label,
|
|
773
|
+
className: cn(
|
|
774
|
+
"flex items-center gap-3 rounded-lg text-sm font-medium opacity-40",
|
|
775
|
+
pad
|
|
776
|
+
),
|
|
777
|
+
"aria-disabled": true,
|
|
778
|
+
children: [
|
|
779
|
+
/* @__PURE__ */ jsx(NavIcon, { item, className: "h-4 w-4 shrink-0" }),
|
|
780
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: item.label })
|
|
781
|
+
]
|
|
782
|
+
}
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
return /* @__PURE__ */ jsx(RailTooltip, { label: item.label, children: /* @__PURE__ */ jsx(
|
|
510
786
|
"span",
|
|
511
787
|
{
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
),
|
|
518
|
-
"aria-disabled": true,
|
|
519
|
-
children: [
|
|
520
|
-
/* @__PURE__ */ jsx(item.icon, { className: "h-4 w-4 shrink-0" }),
|
|
521
|
-
!collapsed ? /* @__PURE__ */ jsx("span", { className: "truncate", children: item.label }) : null
|
|
522
|
-
]
|
|
788
|
+
role: "link",
|
|
789
|
+
"aria-disabled": "true",
|
|
790
|
+
"aria-label": item.label,
|
|
791
|
+
className: "flex h-10 w-full items-center justify-center rounded-lg text-sm font-medium opacity-40",
|
|
792
|
+
children: /* @__PURE__ */ jsx(NavIcon, { item, className: "h-4 w-4 shrink-0" })
|
|
523
793
|
}
|
|
524
|
-
);
|
|
794
|
+
) });
|
|
525
795
|
}
|
|
526
|
-
|
|
796
|
+
const link = /* @__PURE__ */ jsxs(
|
|
527
797
|
LinkComp,
|
|
528
798
|
{
|
|
529
799
|
href: item.to,
|
|
530
800
|
onClick: onNavClick,
|
|
531
|
-
|
|
532
|
-
"aria-label": collapsed ? item.label : void 0,
|
|
801
|
+
"aria-label": collapsed ? item.badge != null ? `${item.label} (${item.badge})` : item.label : void 0,
|
|
533
802
|
"aria-current": active && !notCurrent ? "page" : void 0,
|
|
534
803
|
className: cn(
|
|
535
804
|
"flex items-center gap-3 rounded-lg text-sm font-medium transition-colors",
|
|
536
805
|
pad,
|
|
537
|
-
collapsed && "justify-center px-
|
|
806
|
+
collapsed && "relative h-10 w-full justify-center px-0 py-0 motion-reduce:transition-none",
|
|
538
807
|
active ? "bg-sidebar-accent text-sidebar-primary" : "text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
|
539
808
|
),
|
|
540
809
|
children: [
|
|
541
|
-
/* @__PURE__ */ jsx(
|
|
810
|
+
/* @__PURE__ */ jsx(NavIcon, { item, className: "h-4 w-4 shrink-0" }),
|
|
542
811
|
!collapsed ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
543
812
|
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: item.label }),
|
|
544
813
|
item.badge != null ? /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "h-5 shrink-0 px-1.5 text-[10px]", children: item.badge }) : null
|
|
545
|
-
] }) : null
|
|
814
|
+
] }) : item.badge != null ? (
|
|
815
|
+
// The rail has no room for the count: a dot shows there is one, and
|
|
816
|
+
// the accessible name carries the number.
|
|
817
|
+
/* @__PURE__ */ jsx(
|
|
818
|
+
"span",
|
|
819
|
+
{
|
|
820
|
+
className: "absolute right-1.5 top-1.5 h-2 w-2 rounded-full bg-sidebar-primary",
|
|
821
|
+
"aria-hidden": true
|
|
822
|
+
}
|
|
823
|
+
)
|
|
824
|
+
) : null
|
|
546
825
|
]
|
|
547
826
|
}
|
|
548
827
|
);
|
|
828
|
+
return collapsed ? /* @__PURE__ */ jsx(RailTooltip, { label: item.label, children: link }) : link;
|
|
829
|
+
}
|
|
830
|
+
function RailGroup({
|
|
831
|
+
item,
|
|
832
|
+
density,
|
|
833
|
+
linkComponent,
|
|
834
|
+
activePath,
|
|
835
|
+
isActive,
|
|
836
|
+
notCurrent
|
|
837
|
+
}) {
|
|
838
|
+
const [open, setOpen] = useState(false);
|
|
839
|
+
const children = item.children ?? [];
|
|
840
|
+
const active = itemIsActive(item, activePath, isActive);
|
|
841
|
+
const entries = item.to ? [{ ...item, children: void 0, end: true }, ...children] : children;
|
|
842
|
+
const current = mostSpecificActive(entries, activePath, isActive);
|
|
843
|
+
useEffect(() => {
|
|
844
|
+
setOpen(false);
|
|
845
|
+
}, [activePath]);
|
|
846
|
+
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
|
|
847
|
+
/* @__PURE__ */ jsx(RailTooltip, { label: item.label, children: /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
848
|
+
"button",
|
|
849
|
+
{
|
|
850
|
+
type: "button",
|
|
851
|
+
"aria-label": item.label,
|
|
852
|
+
disabled: item.disabled,
|
|
853
|
+
"data-testid": `nav-rail-group-${item.label}`,
|
|
854
|
+
className: cn(
|
|
855
|
+
"relative flex h-10 w-full items-center justify-center rounded-lg transition-colors motion-reduce:transition-none",
|
|
856
|
+
active ? "bg-sidebar-accent text-sidebar-primary" : "text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
857
|
+
item.disabled && "pointer-events-none opacity-40"
|
|
858
|
+
),
|
|
859
|
+
children: [
|
|
860
|
+
/* @__PURE__ */ jsx(NavIcon, { item, className: "h-4 w-4 shrink-0" }),
|
|
861
|
+
/* @__PURE__ */ jsx(
|
|
862
|
+
ChevronRight,
|
|
863
|
+
{
|
|
864
|
+
className: "absolute right-0.5 h-3 w-3 opacity-50",
|
|
865
|
+
"aria-hidden": true
|
|
866
|
+
}
|
|
867
|
+
)
|
|
868
|
+
]
|
|
869
|
+
}
|
|
870
|
+
) }) }),
|
|
871
|
+
/* @__PURE__ */ jsxs(
|
|
872
|
+
PopoverContent,
|
|
873
|
+
{
|
|
874
|
+
side: "right",
|
|
875
|
+
align: "start",
|
|
876
|
+
sideOffset: 8,
|
|
877
|
+
className: "w-56 p-1.5 motion-reduce:animate-none",
|
|
878
|
+
"aria-label": item.label,
|
|
879
|
+
"data-testid": `nav-rail-popover-${item.label}`,
|
|
880
|
+
children: [
|
|
881
|
+
/* @__PURE__ */ jsx("p", { className: "truncate px-2.5 pb-1 pt-1.5 text-xs font-medium uppercase tracking-wider text-muted-foreground", children: item.label }),
|
|
882
|
+
/* @__PURE__ */ jsx("nav", { "aria-label": item.label, className: "space-y-0.5", children: entries.map((entry) => /* @__PURE__ */ jsx(
|
|
883
|
+
NavLinkRow,
|
|
884
|
+
{
|
|
885
|
+
item: entry,
|
|
886
|
+
collapsed: false,
|
|
887
|
+
density,
|
|
888
|
+
onNavClick: () => setOpen(false),
|
|
889
|
+
linkComponent,
|
|
890
|
+
activePath,
|
|
891
|
+
isActive,
|
|
892
|
+
notCurrent,
|
|
893
|
+
active: entry === current
|
|
894
|
+
},
|
|
895
|
+
entry.to + entry.label
|
|
896
|
+
)) })
|
|
897
|
+
]
|
|
898
|
+
}
|
|
899
|
+
)
|
|
900
|
+
] });
|
|
549
901
|
}
|
|
550
902
|
function NavGroup({
|
|
551
903
|
item,
|
|
@@ -581,14 +933,11 @@ function NavGroup({
|
|
|
581
933
|
);
|
|
582
934
|
}
|
|
583
935
|
if (collapsed) {
|
|
584
|
-
const target = item.to ? { ...item, children: void 0 } : { ...children[0], children: void 0 };
|
|
585
936
|
return /* @__PURE__ */ jsx(
|
|
586
|
-
|
|
937
|
+
RailGroup,
|
|
587
938
|
{
|
|
588
|
-
item
|
|
589
|
-
collapsed: true,
|
|
939
|
+
item,
|
|
590
940
|
density,
|
|
591
|
-
onNavClick,
|
|
592
941
|
linkComponent,
|
|
593
942
|
activePath,
|
|
594
943
|
isActive,
|
|
@@ -618,7 +967,7 @@ function NavGroup({
|
|
|
618
967
|
"aria-current": selfExact && !notCurrent ? "page" : void 0,
|
|
619
968
|
className: parentClass,
|
|
620
969
|
children: [
|
|
621
|
-
/* @__PURE__ */ jsx(
|
|
970
|
+
/* @__PURE__ */ jsx(NavIcon, { item, className: "h-4 w-4 shrink-0" }),
|
|
622
971
|
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: item.label })
|
|
623
972
|
]
|
|
624
973
|
}
|
|
@@ -632,7 +981,7 @@ function NavGroup({
|
|
|
632
981
|
"aria-disabled": item.disabled || void 0,
|
|
633
982
|
disabled: item.disabled,
|
|
634
983
|
children: [
|
|
635
|
-
/* @__PURE__ */ jsx(
|
|
984
|
+
/* @__PURE__ */ jsx(NavIcon, { item, className: "h-4 w-4 shrink-0" }),
|
|
636
985
|
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: item.label })
|
|
637
986
|
]
|
|
638
987
|
}
|
|
@@ -692,12 +1041,68 @@ function SidebarNav({
|
|
|
692
1041
|
onNavClick,
|
|
693
1042
|
linkComponent,
|
|
694
1043
|
activePath,
|
|
695
|
-
isActive
|
|
1044
|
+
isActive,
|
|
1045
|
+
subActive = false,
|
|
1046
|
+
nest = false
|
|
696
1047
|
}) {
|
|
697
1048
|
const contextualLabelId = useId();
|
|
698
|
-
const contextualActive = Boolean(
|
|
1049
|
+
const contextualActive = subActive || Boolean(
|
|
699
1050
|
contextualNavItems?.some((item) => itemIsActive(item, activePath, isActive))
|
|
700
1051
|
);
|
|
1052
|
+
const contextual = contextualNavItems ?? [];
|
|
1053
|
+
const host = nest && contextual.length > 0 ? mostSpecificActive(navItems, activePath, isActive) : void 0;
|
|
1054
|
+
const contextualCurrent = mostSpecificActive(contextual, activePath, isActive);
|
|
1055
|
+
const contextualGroup = (nested) => /* @__PURE__ */ jsxs(
|
|
1056
|
+
"div",
|
|
1057
|
+
{
|
|
1058
|
+
className: cn(
|
|
1059
|
+
nested ? "ml-3 space-y-0.5 border-l border-sidebar-border pl-2" : "mt-4 space-y-1 border-t border-sidebar-border pt-4",
|
|
1060
|
+
!nested && collapsed && "mt-2 pt-2"
|
|
1061
|
+
),
|
|
1062
|
+
"data-testid": "app-shell-contextual-nav",
|
|
1063
|
+
role: "group",
|
|
1064
|
+
"aria-labelledby": !collapsed && !nested && contextualNavLabel ? contextualLabelId : void 0,
|
|
1065
|
+
"aria-label": collapsed || nested ? contextualNavLabel : void 0,
|
|
1066
|
+
children: [
|
|
1067
|
+
!collapsed && !nested && contextualNavLabel ? /* @__PURE__ */ jsx(
|
|
1068
|
+
"p",
|
|
1069
|
+
{
|
|
1070
|
+
id: contextualLabelId,
|
|
1071
|
+
className: "mb-2 truncate px-3 text-xs font-medium uppercase tracking-wider text-sidebar-foreground/40",
|
|
1072
|
+
children: contextualNavLabel
|
|
1073
|
+
}
|
|
1074
|
+
) : null,
|
|
1075
|
+
contextual.map(
|
|
1076
|
+
(item) => item.children?.length ? /* @__PURE__ */ jsx(
|
|
1077
|
+
NavGroup,
|
|
1078
|
+
{
|
|
1079
|
+
item,
|
|
1080
|
+
collapsed,
|
|
1081
|
+
density,
|
|
1082
|
+
onNavClick,
|
|
1083
|
+
linkComponent,
|
|
1084
|
+
activePath,
|
|
1085
|
+
isActive
|
|
1086
|
+
},
|
|
1087
|
+
item.to + item.label
|
|
1088
|
+
) : /* @__PURE__ */ jsx(
|
|
1089
|
+
NavLinkRow,
|
|
1090
|
+
{
|
|
1091
|
+
item,
|
|
1092
|
+
collapsed,
|
|
1093
|
+
density,
|
|
1094
|
+
onNavClick,
|
|
1095
|
+
linkComponent,
|
|
1096
|
+
activePath,
|
|
1097
|
+
isActive,
|
|
1098
|
+
active: nest ? item === contextualCurrent : void 0
|
|
1099
|
+
},
|
|
1100
|
+
item.to + item.label
|
|
1101
|
+
)
|
|
1102
|
+
)
|
|
1103
|
+
]
|
|
1104
|
+
}
|
|
1105
|
+
);
|
|
701
1106
|
return /* @__PURE__ */ jsxs(
|
|
702
1107
|
"nav",
|
|
703
1108
|
{
|
|
@@ -707,56 +1112,42 @@ function SidebarNav({
|
|
|
707
1112
|
),
|
|
708
1113
|
"aria-label": "Main",
|
|
709
1114
|
children: [
|
|
710
|
-
navItems.map((item) =>
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
contextualNavItems && contextualNavItems.length > 0 ? /* @__PURE__ */ jsxs(
|
|
725
|
-
"div",
|
|
726
|
-
{
|
|
727
|
-
className: cn(
|
|
728
|
-
"mt-4 space-y-1 border-t border-sidebar-border pt-4",
|
|
729
|
-
collapsed && "mt-2 pt-2"
|
|
730
|
-
),
|
|
731
|
-
"data-testid": "app-shell-contextual-nav",
|
|
732
|
-
role: "group",
|
|
733
|
-
"aria-labelledby": !collapsed && contextualNavLabel ? contextualLabelId : void 0,
|
|
734
|
-
"aria-label": collapsed ? contextualNavLabel : void 0,
|
|
735
|
-
children: [
|
|
736
|
-
!collapsed && contextualNavLabel ? /* @__PURE__ */ jsx(
|
|
737
|
-
"p",
|
|
738
|
-
{
|
|
739
|
-
id: contextualLabelId,
|
|
740
|
-
className: "mb-2 truncate px-3 text-xs font-medium uppercase tracking-wider text-sidebar-foreground/40",
|
|
741
|
-
children: contextualNavLabel
|
|
742
|
-
}
|
|
743
|
-
) : null,
|
|
744
|
-
contextualNavItems.map((item) => /* @__PURE__ */ jsx(
|
|
745
|
-
NavGroup,
|
|
746
|
-
{
|
|
747
|
-
item,
|
|
748
|
-
collapsed,
|
|
749
|
-
density,
|
|
750
|
-
onNavClick,
|
|
751
|
-
linkComponent,
|
|
752
|
-
activePath,
|
|
753
|
-
isActive
|
|
754
|
-
},
|
|
755
|
-
item.to + item.label
|
|
756
|
-
))
|
|
757
|
-
]
|
|
1115
|
+
navItems.map((item) => {
|
|
1116
|
+
if (item === host && collapsed) {
|
|
1117
|
+
return /* @__PURE__ */ jsx(
|
|
1118
|
+
RailGroup,
|
|
1119
|
+
{
|
|
1120
|
+
item: { ...item, children: [...item.children ?? [], ...contextual] },
|
|
1121
|
+
density,
|
|
1122
|
+
linkComponent,
|
|
1123
|
+
activePath,
|
|
1124
|
+
isActive,
|
|
1125
|
+
notCurrent: false
|
|
1126
|
+
},
|
|
1127
|
+
item.to + item.label
|
|
1128
|
+
);
|
|
758
1129
|
}
|
|
759
|
-
|
|
1130
|
+
const row = /* @__PURE__ */ jsx(
|
|
1131
|
+
NavGroup,
|
|
1132
|
+
{
|
|
1133
|
+
item,
|
|
1134
|
+
collapsed,
|
|
1135
|
+
density,
|
|
1136
|
+
onNavClick,
|
|
1137
|
+
linkComponent,
|
|
1138
|
+
activePath,
|
|
1139
|
+
isActive,
|
|
1140
|
+
notCurrent: contextualActive
|
|
1141
|
+
},
|
|
1142
|
+
item.to + item.label
|
|
1143
|
+
);
|
|
1144
|
+
if (item !== host) return row;
|
|
1145
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
|
|
1146
|
+
row,
|
|
1147
|
+
contextualGroup(true)
|
|
1148
|
+
] }, item.to + item.label);
|
|
1149
|
+
}),
|
|
1150
|
+
contextual.length > 0 && !host ? contextualGroup(false) : null,
|
|
760
1151
|
showSecondary && secondaryNavItems && secondaryNavItems.length > 0 ? /* @__PURE__ */ jsxs(
|
|
761
1152
|
"div",
|
|
762
1153
|
{
|
|
@@ -803,7 +1194,18 @@ function SidebarBrand({
|
|
|
803
1194
|
collapsed && "justify-center"
|
|
804
1195
|
),
|
|
805
1196
|
children: [
|
|
806
|
-
showLogo ? logo ? /* @__PURE__ */ jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden", children: logo }) : appLogo
|
|
1197
|
+
showLogo ? logo ? /* @__PURE__ */ jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden", children: logo }) : appLogo || collapsed ? (
|
|
1198
|
+
// Collapsed with no logo at all: the app name's first letter keeps
|
|
1199
|
+
// a mark at the top of the rail.
|
|
1200
|
+
/* @__PURE__ */ jsx(
|
|
1201
|
+
"div",
|
|
1202
|
+
{
|
|
1203
|
+
className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-primary",
|
|
1204
|
+
"aria-hidden": !appLogo || void 0,
|
|
1205
|
+
children: /* @__PURE__ */ jsx("span", { className: "text-sm font-bold text-primary-foreground", children: appLogo || appName.trim().charAt(0).toUpperCase() })
|
|
1206
|
+
}
|
|
1207
|
+
)
|
|
1208
|
+
) : null : null,
|
|
807
1209
|
showAppName && !collapsed ? /* @__PURE__ */ jsx("span", { className: "truncate font-semibold text-sidebar-foreground", children: appName }) : null
|
|
808
1210
|
]
|
|
809
1211
|
}
|
|
@@ -829,6 +1231,9 @@ function AppShell({
|
|
|
829
1231
|
optionsStorageKey,
|
|
830
1232
|
collapsed: collapsedProp,
|
|
831
1233
|
onCollapsedChange,
|
|
1234
|
+
defaultCollapsed: defaultCollapsedProp,
|
|
1235
|
+
subNav: subNavProp,
|
|
1236
|
+
subNavLabel,
|
|
832
1237
|
linkComponent = DefaultLink,
|
|
833
1238
|
activePath,
|
|
834
1239
|
isActive = isPathActive
|
|
@@ -841,10 +1246,25 @@ function AppShell({
|
|
|
841
1246
|
const globalItems = fillItems(navigation.items, nav.params);
|
|
842
1247
|
const contextualItems = atRoot ? [] : fillItems(nav.items, nav.params);
|
|
843
1248
|
const parent = nav.parent && nav.trail.length > 2 && !nav.parent.path.split("/").some((p) => PARAM.test(p)) ? { href: nav.parent.path, label: nav.parent.label } : void 0;
|
|
844
|
-
|
|
1249
|
+
const navDefaultCollapsed = [...nav.trail].reverse().find((node) => node.defaultCollapsed !== void 0)?.defaultCollapsed;
|
|
1250
|
+
const navSubNav = [...nav.trail].reverse().find((node) => node.subNav !== void 0)?.subNav;
|
|
1251
|
+
const navParentPath = nav.parent ? fillParams(nav.parent.path, nav.params) : void 0;
|
|
1252
|
+
const backItem = nav.parent && navParentPath ? { href: navParentPath, label: nav.parent.label } : void 0;
|
|
1253
|
+
return {
|
|
1254
|
+
current: nav.current,
|
|
1255
|
+
globalItems,
|
|
1256
|
+
contextualItems,
|
|
1257
|
+
parent,
|
|
1258
|
+
navDefaultCollapsed,
|
|
1259
|
+
navSubNav,
|
|
1260
|
+
backItem
|
|
1261
|
+
};
|
|
845
1262
|
}, [navigation, activePath]);
|
|
846
1263
|
const navItems = navItemsProp ?? derived?.globalItems ?? [];
|
|
847
|
-
const
|
|
1264
|
+
const subNavChannel = useSubNavChannel();
|
|
1265
|
+
const pageSubNav = subNavChannel.page;
|
|
1266
|
+
const hasPageHeader = subNavChannel.hasHeader;
|
|
1267
|
+
const contextualNavItems = pageSubNav?.items ?? derived?.contextualItems ?? [];
|
|
848
1268
|
const parentNavigation = parentNavigationProp === void 0 ? derived?.parent : parentNavigationProp ?? void 0;
|
|
849
1269
|
const [userOverrides, setUserOverrides] = useState(
|
|
850
1270
|
{}
|
|
@@ -908,32 +1328,104 @@ function AppShell({
|
|
|
908
1328
|
},
|
|
909
1329
|
[lockedOptions]
|
|
910
1330
|
);
|
|
911
|
-
const
|
|
912
|
-
|
|
1331
|
+
const storedCollapsed = useStoredCollapsed(optionsStorageKey);
|
|
1332
|
+
const [sessionCollapsed, setSessionCollapsed] = useState(
|
|
1333
|
+
void 0
|
|
913
1334
|
);
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
const stored = readStoredCollapsed(optionsStorageKey);
|
|
917
|
-
if (stored !== void 0) setUncontrolledCollapsed(stored);
|
|
918
|
-
}, [optionsStorageKey, collapsedProp]);
|
|
1335
|
+
const userCollapsed = optionsStorageKey ? storedCollapsed : sessionCollapsed;
|
|
1336
|
+
const pageDefaultCollapsed = defaultCollapsedProp ?? derived?.navDefaultCollapsed ?? optionsProp?.defaultCollapsed ?? OPTION_DEFAULTS2.defaultCollapsed;
|
|
919
1337
|
const isCollapseControlled = collapsedProp !== void 0;
|
|
920
|
-
const collapsed = isCollapseControlled ? collapsedProp :
|
|
921
|
-
const setCollapsed = (
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1338
|
+
const collapsed = isCollapseControlled ? collapsedProp : userCollapsed ?? pageDefaultCollapsed;
|
|
1339
|
+
const setCollapsed = useCallback(
|
|
1340
|
+
(next) => {
|
|
1341
|
+
if (!isCollapseControlled) {
|
|
1342
|
+
if (optionsStorageKey) writeStoredCollapsed(optionsStorageKey, next);
|
|
1343
|
+
else setSessionCollapsed(next);
|
|
1344
|
+
}
|
|
1345
|
+
onCollapsedChange?.(next);
|
|
1346
|
+
},
|
|
1347
|
+
[isCollapseControlled, optionsStorageKey, onCollapsedChange]
|
|
1348
|
+
);
|
|
1349
|
+
const sidebarRef = useRef(null);
|
|
1350
|
+
const pendingFocus = useRef(null);
|
|
1351
|
+
const toggleCollapsed = useCallback(
|
|
1352
|
+
(fromToggle) => {
|
|
1353
|
+
const aside = sidebarRef.current;
|
|
1354
|
+
const focused = typeof document === "undefined" ? null : document.activeElement;
|
|
1355
|
+
if (fromToggle) {
|
|
1356
|
+
pendingFocus.current = { toggle: true };
|
|
1357
|
+
} else if (aside && focused instanceof HTMLElement && aside.contains(focused)) {
|
|
1358
|
+
const href = focused.getAttribute("href");
|
|
1359
|
+
pendingFocus.current = href ? { href } : { toggle: true };
|
|
1360
|
+
}
|
|
1361
|
+
setCollapsed(!collapsed);
|
|
1362
|
+
},
|
|
1363
|
+
[collapsed, setCollapsed]
|
|
1364
|
+
);
|
|
1365
|
+
const renderedCollapsed = useRef(collapsed);
|
|
1366
|
+
if (renderedCollapsed.current !== collapsed && !pendingFocus.current) {
|
|
1367
|
+
const aside = sidebarRef.current;
|
|
1368
|
+
const focused = typeof document === "undefined" ? null : document.activeElement;
|
|
1369
|
+
if (aside && focused instanceof HTMLElement && aside.contains(focused)) {
|
|
1370
|
+
const href = focused.getAttribute("href");
|
|
1371
|
+
pendingFocus.current = href ? { href } : { toggle: true };
|
|
925
1372
|
}
|
|
926
|
-
|
|
927
|
-
|
|
1373
|
+
}
|
|
1374
|
+
useIsoLayoutEffect2(() => {
|
|
1375
|
+
const changed = renderedCollapsed.current !== collapsed;
|
|
1376
|
+
renderedCollapsed.current = collapsed;
|
|
1377
|
+
const want = pendingFocus.current;
|
|
1378
|
+
pendingFocus.current = null;
|
|
1379
|
+
const aside = sidebarRef.current;
|
|
1380
|
+
if (!changed || !want || !aside) return;
|
|
1381
|
+
const toggleEl = aside.querySelector('[data-testid="app-shell-collapse"]');
|
|
1382
|
+
let target = null;
|
|
1383
|
+
if ("href" in want) {
|
|
1384
|
+
target = Array.from(aside.querySelectorAll("a[href]")).find(
|
|
1385
|
+
(a) => a.getAttribute("href") === want.href
|
|
1386
|
+
) ?? null;
|
|
1387
|
+
}
|
|
1388
|
+
(target ?? toggleEl)?.focus();
|
|
1389
|
+
});
|
|
1390
|
+
const showCollapseCtrl = resolved.showCollapse !== false;
|
|
1391
|
+
const collapseShortcut = optionsProp?.collapseShortcut ?? OPTION_DEFAULTS2.collapseShortcut;
|
|
1392
|
+
useEffect(() => {
|
|
1393
|
+
if (!collapseShortcut || !showCollapseCtrl) return;
|
|
1394
|
+
if (typeof window === "undefined") return;
|
|
1395
|
+
const onKeyDown = (e) => {
|
|
1396
|
+
if (e.key !== collapseShortcut || e.repeat || e.defaultPrevented) return;
|
|
1397
|
+
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
1398
|
+
if (isTypingTarget(e.target)) return;
|
|
1399
|
+
if (ownsKeys(e.target)) return;
|
|
1400
|
+
if (dialogOpen()) return;
|
|
1401
|
+
if (window.matchMedia && !window.matchMedia(DESKTOP_QUERY).matches) return;
|
|
1402
|
+
e.preventDefault();
|
|
1403
|
+
toggleCollapsed(false);
|
|
1404
|
+
};
|
|
1405
|
+
window.addEventListener("keydown", onKeyDown);
|
|
1406
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
1407
|
+
}, [collapseShortcut, showCollapseCtrl, toggleCollapsed]);
|
|
928
1408
|
const ParentLink = linkComponent;
|
|
1409
|
+
const LinkComp = linkComponent;
|
|
929
1410
|
const density = resolved.density ?? "comfortable";
|
|
930
1411
|
const scrollWindow = resolved.scrollRoot === "window";
|
|
931
1412
|
const railExpanded = resolved.railWidth === "wide" ? "md:w-64" : "md:w-52";
|
|
932
|
-
const
|
|
1413
|
+
const railCollapsed = (optionsProp?.collapsedRailWidth ?? OPTION_DEFAULTS2.collapsedRailWidth) === 64 ? "md:w-16" : "md:w-14";
|
|
1414
|
+
const railClass = collapsed ? railCollapsed : railExpanded;
|
|
1415
|
+
const subNavMode = pageSubNav?.mode ?? subNavProp ?? derived?.navSubNav ?? resolved.subNav ?? OPTION_DEFAULTS2.subNav;
|
|
1416
|
+
const subActive = contextualNavItems.some((i) => itemIsActive(i, activePath, isActive));
|
|
1417
|
+
const subNavOn = subNavMode === "tabs" || subNavMode === "sidebar";
|
|
933
1418
|
const leaves = (items) => items.filter((i) => !i.children || i.children.length === 0);
|
|
934
|
-
const contextualLeaves = leaves(contextualNavItems);
|
|
935
|
-
const
|
|
936
|
-
const
|
|
1419
|
+
const contextualLeaves = subNavMode === "off" ? [] : leaves(contextualNavItems);
|
|
1420
|
+
const subBottom = subNavOn && contextualLeaves.length > 0;
|
|
1421
|
+
const legacyBottom = !subNavOn && !mobileNavItems && contextualLeaves.length > 0;
|
|
1422
|
+
const backTarget = subBottom ? parentNavigation ?? derived?.backItem ?? (() => {
|
|
1423
|
+
const hostItem = mostSpecificActive(navItems, activePath, isActive);
|
|
1424
|
+
return hostItem ? { href: hostItem.to, label: hostItem.label } : void 0;
|
|
1425
|
+
})() : void 0;
|
|
1426
|
+
const bottomItems = (subBottom ? contextualLeaves : mobileNavItems ?? (legacyBottom ? contextualLeaves : leaves(navItems))).filter((i) => !i.disabled);
|
|
1427
|
+
const bottomNotCurrent = !mobileNavItems && !subBottom && !legacyBottom && subActive;
|
|
1428
|
+
const bottomCurrent = subBottom ? mostSpecificActive(bottomItems, activePath, isActive) : void 0;
|
|
937
1429
|
const toggles = [
|
|
938
1430
|
{ key: "showLogo", label: "Logo", available: hasLogo },
|
|
939
1431
|
{ key: "showAppName", label: "App name", available: true },
|
|
@@ -957,11 +1449,11 @@ function AppShell({
|
|
|
957
1449
|
}
|
|
958
1450
|
].filter((t) => t.available && !lockedOptions?.includes(t.key));
|
|
959
1451
|
const showTheme = resolved.showThemeToggle !== false;
|
|
960
|
-
const showCollapseCtrl = resolved.showCollapse !== false;
|
|
961
1452
|
const showSecondary = hasSecondary && resolved.showSecondaryNav !== false;
|
|
962
1453
|
const showFooterSlot = hasFooter && resolved.showFooter !== false;
|
|
963
1454
|
const showUser = hasUser && resolved.showUserSlot !== false;
|
|
964
1455
|
const showMobileBottom = bottomItems.length > 0 && resolved.showMobileBottomNav !== false;
|
|
1456
|
+
const showTabs = subNavMode === "tabs" && contextualNavItems.length > 0;
|
|
965
1457
|
const optionsControl = resolved.configurable && toggles.length > 0 ? /* @__PURE__ */ jsxs(Popover, { children: [
|
|
966
1458
|
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
967
1459
|
Button,
|
|
@@ -1010,14 +1502,17 @@ function AppShell({
|
|
|
1010
1502
|
t.key
|
|
1011
1503
|
);
|
|
1012
1504
|
}) }),
|
|
1013
|
-
optionsStorageKey && Object.keys(userOverrides).length > 0 ? /* @__PURE__ */ jsx(
|
|
1505
|
+
optionsStorageKey && (Object.keys(userOverrides).length > 0 || storedCollapsed !== void 0) ? /* @__PURE__ */ jsx(
|
|
1014
1506
|
Button,
|
|
1015
1507
|
{
|
|
1016
1508
|
type: "button",
|
|
1017
1509
|
variant: "ghost",
|
|
1018
1510
|
size: "sm",
|
|
1019
1511
|
className: "h-8 w-full text-xs",
|
|
1020
|
-
onClick: () =>
|
|
1512
|
+
onClick: () => {
|
|
1513
|
+
setUserOverrides({});
|
|
1514
|
+
writeStoredCollapsed(optionsStorageKey, void 0);
|
|
1515
|
+
},
|
|
1021
1516
|
"data-testid": "app-shell-options-reset",
|
|
1022
1517
|
"aria-label": "Reset sidebar options",
|
|
1023
1518
|
testID: "app-shell-options-reset",
|
|
@@ -1026,6 +1521,23 @@ function AppShell({
|
|
|
1026
1521
|
) : null
|
|
1027
1522
|
] })
|
|
1028
1523
|
] }) : null;
|
|
1524
|
+
const collapseLabel = collapsed ? "Expand sidebar" : "Collapse sidebar";
|
|
1525
|
+
const collapseToggle = showCollapseCtrl ? /* @__PURE__ */ jsx(
|
|
1526
|
+
Button,
|
|
1527
|
+
{
|
|
1528
|
+
variant: "ghost",
|
|
1529
|
+
size: "icon",
|
|
1530
|
+
className: "h-8 w-8",
|
|
1531
|
+
onClick: () => toggleCollapsed(true),
|
|
1532
|
+
title: collapseShortcut ? `${collapseLabel} (${collapseShortcut})` : collapseLabel,
|
|
1533
|
+
"aria-label": collapseLabel,
|
|
1534
|
+
"aria-pressed": collapsed,
|
|
1535
|
+
"aria-keyshortcuts": collapseShortcut || void 0,
|
|
1536
|
+
"data-testid": "app-shell-collapse",
|
|
1537
|
+
testID: "app-shell-collapse",
|
|
1538
|
+
children: collapsed ? /* @__PURE__ */ jsx(PanelLeft, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(PanelLeftClose, { className: "h-4 w-4" })
|
|
1539
|
+
}
|
|
1540
|
+
) : null;
|
|
1029
1541
|
const sidebarChrome = (opts) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1030
1542
|
/* @__PURE__ */ jsxs(
|
|
1031
1543
|
"div",
|
|
@@ -1057,24 +1569,14 @@ function AppShell({
|
|
|
1057
1569
|
testID: "app-shell-mobile-close",
|
|
1058
1570
|
children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" })
|
|
1059
1571
|
}
|
|
1060
|
-
) :
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
title: collapsed ? "Expand sidebar" : "Collapse sidebar",
|
|
1069
|
-
"aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
|
|
1070
|
-
"aria-pressed": collapsed,
|
|
1071
|
-
"data-testid": "app-shell-collapse",
|
|
1072
|
-
testID: "app-shell-collapse",
|
|
1073
|
-
children: collapsed ? /* @__PURE__ */ jsx(PanelLeft, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(PanelLeftClose, { className: "h-4 w-4" })
|
|
1074
|
-
}
|
|
1075
|
-
) : null,
|
|
1076
|
-
!collapsed ? optionsControl : null
|
|
1077
|
-
] })
|
|
1572
|
+
) : (
|
|
1573
|
+
// Expanded: collapse sits beside the logo. Collapsed: the rail's
|
|
1574
|
+
// header is the logo mark alone; expand moves to the rail's foot.
|
|
1575
|
+
!collapsed ? /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
|
|
1576
|
+
collapseToggle,
|
|
1577
|
+
optionsControl
|
|
1578
|
+
] }) : null
|
|
1579
|
+
)
|
|
1078
1580
|
]
|
|
1079
1581
|
}
|
|
1080
1582
|
),
|
|
@@ -1083,8 +1585,10 @@ function AppShell({
|
|
|
1083
1585
|
SidebarNav,
|
|
1084
1586
|
{
|
|
1085
1587
|
navItems,
|
|
1086
|
-
contextualNavItems,
|
|
1588
|
+
contextualNavItems: (opts.mobile ? subNavMode !== "off" : subNavMode === "sidebar" || subNavMode === "section") ? contextualNavItems : void 0,
|
|
1589
|
+
nest: subNavMode === "sidebar" || opts.mobile && subNavOn,
|
|
1087
1590
|
contextualNavLabel: derived?.current.label,
|
|
1591
|
+
subActive,
|
|
1088
1592
|
secondaryNavItems,
|
|
1089
1593
|
secondaryNavLabel,
|
|
1090
1594
|
showSecondary,
|
|
@@ -1096,7 +1600,7 @@ function AppShell({
|
|
|
1096
1600
|
isActive
|
|
1097
1601
|
}
|
|
1098
1602
|
),
|
|
1099
|
-
(showFooterSlot || showUser || showTheme || collapsed && optionsControl) && !opts.mobile ? /* @__PURE__ */ jsxs("div", { className: "mt-auto shrink-0 space-y-2 border-t border-sidebar-border p-2", children: [
|
|
1603
|
+
(showFooterSlot || showUser || showTheme || collapsed && (optionsControl || collapseToggle)) && !opts.mobile ? /* @__PURE__ */ jsxs("div", { className: "mt-auto shrink-0 space-y-2 border-t border-sidebar-border p-2", children: [
|
|
1100
1604
|
showFooterSlot && !collapsed ? footer : null,
|
|
1101
1605
|
showUser ? /* @__PURE__ */ jsx(
|
|
1102
1606
|
"div",
|
|
@@ -1116,7 +1620,8 @@ function AppShell({
|
|
|
1116
1620
|
),
|
|
1117
1621
|
children: [
|
|
1118
1622
|
collapsed ? optionsControl : null,
|
|
1119
|
-
showTheme ? /* @__PURE__ */ jsx(ThemeToggle, { testId: "app-shell-theme-toggle-desktop" }) : null
|
|
1623
|
+
showTheme ? /* @__PURE__ */ jsx(ThemeToggle, { testId: "app-shell-theme-toggle-desktop" }) : null,
|
|
1624
|
+
collapsed ? collapseToggle : null
|
|
1120
1625
|
]
|
|
1121
1626
|
}
|
|
1122
1627
|
)
|
|
@@ -1139,10 +1644,11 @@ function AppShell({
|
|
|
1139
1644
|
"aside",
|
|
1140
1645
|
{
|
|
1141
1646
|
className: cn(
|
|
1142
|
-
"hidden flex-col border-r border-sidebar-border bg-sidebar transition-[width] duration-200 md:flex",
|
|
1647
|
+
"hidden flex-col border-r border-sidebar-border bg-sidebar transition-[width] duration-200 motion-reduce:transition-none md:flex",
|
|
1143
1648
|
scrollWindow && "sticky top-0 h-screen",
|
|
1144
1649
|
railClass
|
|
1145
1650
|
),
|
|
1651
|
+
ref: sidebarRef,
|
|
1146
1652
|
"data-collapsed": collapsed || void 0,
|
|
1147
1653
|
"data-testid": "app-shell-sidebar",
|
|
1148
1654
|
children: sidebarChrome({})
|
|
@@ -1241,46 +1747,103 @@ function AppShell({
|
|
|
1241
1747
|
)
|
|
1242
1748
|
}
|
|
1243
1749
|
) : null,
|
|
1750
|
+
hasPageHeader ? /* @__PURE__ */ jsx(
|
|
1751
|
+
"div",
|
|
1752
|
+
{
|
|
1753
|
+
ref: subNavChannel.setHeaderSlot,
|
|
1754
|
+
className: cn(
|
|
1755
|
+
"min-w-0 px-4 sm:px-6 lg:px-8",
|
|
1756
|
+
parentNavigation ? "pt-2 sm:pt-3 lg:pt-4" : "pt-4 sm:pt-6 lg:pt-8"
|
|
1757
|
+
),
|
|
1758
|
+
"data-testid": "app-shell-page-header"
|
|
1759
|
+
}
|
|
1760
|
+
) : null,
|
|
1761
|
+
showTabs ? /* @__PURE__ */ jsx(
|
|
1762
|
+
"div",
|
|
1763
|
+
{
|
|
1764
|
+
className: cn(
|
|
1765
|
+
"px-4 sm:px-6 lg:px-8",
|
|
1766
|
+
// Phones use the bottom bar, unless the app turned it off.
|
|
1767
|
+
showMobileBottom && "hidden md:block",
|
|
1768
|
+
(!parentNavigation || hasPageHeader) && "pt-2 sm:pt-3 lg:pt-4"
|
|
1769
|
+
),
|
|
1770
|
+
children: /* @__PURE__ */ jsx(
|
|
1771
|
+
SubNavTabs,
|
|
1772
|
+
{
|
|
1773
|
+
items: contextualNavItems,
|
|
1774
|
+
activePath,
|
|
1775
|
+
isActive,
|
|
1776
|
+
linkComponent,
|
|
1777
|
+
label: subNavLabel ?? derived?.current.label ?? "Page sections",
|
|
1778
|
+
end: subNavChannel.hasEnd ? /* @__PURE__ */ jsx("div", { ref: subNavChannel.setEndSlot, className: "flex items-center" }) : void 0
|
|
1779
|
+
}
|
|
1780
|
+
)
|
|
1781
|
+
}
|
|
1782
|
+
) : null,
|
|
1244
1783
|
/* @__PURE__ */ jsx(
|
|
1245
1784
|
"div",
|
|
1246
1785
|
{
|
|
1247
1786
|
className: cn(
|
|
1248
1787
|
"min-w-0 p-4 sm:p-6 lg:p-8",
|
|
1249
|
-
parentNavigation && "pt-2 sm:pt-3 lg:pt-4"
|
|
1788
|
+
(parentNavigation || hasPageHeader) && !showTabs && "pt-2 sm:pt-3 lg:pt-4"
|
|
1250
1789
|
),
|
|
1251
|
-
children
|
|
1790
|
+
children: /* @__PURE__ */ jsx(SubNavContext.Provider, { value: subNavChannel.channel, children })
|
|
1252
1791
|
}
|
|
1253
1792
|
)
|
|
1254
1793
|
]
|
|
1255
1794
|
}
|
|
1256
1795
|
),
|
|
1257
|
-
showMobileBottom ? /* @__PURE__ */
|
|
1796
|
+
showMobileBottom ? /* @__PURE__ */ jsxs(
|
|
1258
1797
|
"nav",
|
|
1259
1798
|
{
|
|
1260
1799
|
className: "fixed bottom-0 left-0 right-0 z-30 flex border-t bg-background md:hidden",
|
|
1261
1800
|
"aria-label": "Primary",
|
|
1262
|
-
children:
|
|
1263
|
-
|
|
1264
|
-
const active = itemIsActive(item, activePath, isActive);
|
|
1265
|
-
return /* @__PURE__ */ jsxs(
|
|
1801
|
+
children: [
|
|
1802
|
+
backTarget ? /* @__PURE__ */ jsxs(
|
|
1266
1803
|
LinkComp,
|
|
1267
1804
|
{
|
|
1268
|
-
href:
|
|
1269
|
-
|
|
1270
|
-
"
|
|
1271
|
-
|
|
1272
|
-
className: cn(
|
|
1273
|
-
"flex flex-1 flex-col items-center gap-1 py-2 text-xs font-medium transition-colors",
|
|
1274
|
-
active ? "text-primary" : "text-muted-foreground"
|
|
1275
|
-
),
|
|
1805
|
+
href: backTarget.href,
|
|
1806
|
+
"aria-label": `Back to ${backTarget.label}`,
|
|
1807
|
+
className: "flex flex-1 flex-col items-center gap-1 py-2 text-xs font-medium text-muted-foreground transition-colors",
|
|
1808
|
+
testID: "app-shell-mobile-back",
|
|
1276
1809
|
children: [
|
|
1277
|
-
/* @__PURE__ */ jsx(
|
|
1278
|
-
/* @__PURE__ */ jsx("span", { className: "max-w-full truncate px-0.5", children:
|
|
1810
|
+
/* @__PURE__ */ jsx(ArrowLeft, { className: "h-5 w-5", "aria-hidden": true }),
|
|
1811
|
+
/* @__PURE__ */ jsx("span", { className: "max-w-full truncate px-0.5", children: backTarget.label })
|
|
1279
1812
|
]
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
)
|
|
1283
|
-
|
|
1813
|
+
}
|
|
1814
|
+
) : null,
|
|
1815
|
+
bottomItems.map((item) => {
|
|
1816
|
+
const active = bottomCurrent ? item === bottomCurrent : itemIsActive(item, activePath, isActive);
|
|
1817
|
+
return /* @__PURE__ */ jsxs(
|
|
1818
|
+
LinkComp,
|
|
1819
|
+
{
|
|
1820
|
+
href: item.to,
|
|
1821
|
+
title: item.label,
|
|
1822
|
+
"aria-label": subNavOn && item.badge != null ? `${item.label} (${item.badge})` : item.label,
|
|
1823
|
+
"aria-current": active && !bottomNotCurrent ? "page" : void 0,
|
|
1824
|
+
className: cn(
|
|
1825
|
+
"flex flex-1 flex-col items-center gap-1 py-2 text-xs font-medium transition-colors",
|
|
1826
|
+
active ? "text-primary" : "text-muted-foreground"
|
|
1827
|
+
),
|
|
1828
|
+
children: [
|
|
1829
|
+
subNavOn && item.badge != null ? /* @__PURE__ */ jsxs("span", { className: "relative", children: [
|
|
1830
|
+
/* @__PURE__ */ jsx(NavIcon, { item, className: "h-5 w-5" }),
|
|
1831
|
+
/* @__PURE__ */ jsx(
|
|
1832
|
+
"span",
|
|
1833
|
+
{
|
|
1834
|
+
className: "absolute -top-1.5 left-full -ml-1 inline-flex h-4 min-w-4 items-center justify-center whitespace-nowrap rounded-full bg-primary px-1 text-[10px] font-semibold leading-none text-primary-foreground ring-2 ring-background",
|
|
1835
|
+
"aria-hidden": true,
|
|
1836
|
+
children: item.badge
|
|
1837
|
+
}
|
|
1838
|
+
)
|
|
1839
|
+
] }) : /* @__PURE__ */ jsx(NavIcon, { item, className: "h-5 w-5" }),
|
|
1840
|
+
/* @__PURE__ */ jsx("span", { className: "max-w-full truncate px-0.5", children: item.label })
|
|
1841
|
+
]
|
|
1842
|
+
},
|
|
1843
|
+
item.to + item.label
|
|
1844
|
+
);
|
|
1845
|
+
})
|
|
1846
|
+
]
|
|
1284
1847
|
}
|
|
1285
1848
|
) : null
|
|
1286
1849
|
]
|
|
@@ -1291,6 +1854,6 @@ function AppShell({
|
|
|
1291
1854
|
);
|
|
1292
1855
|
}
|
|
1293
1856
|
|
|
1294
|
-
export { AppShell, PageHeader };
|
|
1295
|
-
//# sourceMappingURL=chunk-
|
|
1296
|
-
//# sourceMappingURL=chunk-
|
|
1857
|
+
export { AppShell, AppShellPageHeader, AppShellSubNavEnd, PageHeader, SubNavTabs, useAppShellSubNav };
|
|
1858
|
+
//# sourceMappingURL=chunk-SZMXNC5Q.js.map
|
|
1859
|
+
//# sourceMappingURL=chunk-SZMXNC5Q.js.map
|