@inf-monkeys-tech/monkeys-design 2.0.0 → 2.0.1

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.
@@ -3144,6 +3144,30 @@ function MoreIcon2() {
3144
3144
  }
3145
3145
  );
3146
3146
  }
3147
+ function DisclosureIcon({ expanded }) {
3148
+ return /* @__PURE__ */ jsxRuntime.jsx(
3149
+ "svg",
3150
+ {
3151
+ "aria-hidden": "true",
3152
+ viewBox: "0 0 20 20",
3153
+ className: cn(
3154
+ "h-4 w-4 transition-transform motion-reduce:transition-none",
3155
+ expanded && "rotate-180"
3156
+ ),
3157
+ fill: "none",
3158
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3159
+ "path",
3160
+ {
3161
+ d: "m6 8 4 4 4-4",
3162
+ stroke: "currentColor",
3163
+ strokeWidth: "1.6",
3164
+ strokeLinecap: "round",
3165
+ strokeLinejoin: "round"
3166
+ }
3167
+ )
3168
+ }
3169
+ );
3170
+ }
3147
3171
  function assignRef(ref, node) {
3148
3172
  if (typeof ref === "function") {
3149
3173
  ref(node);
@@ -3299,6 +3323,94 @@ function resolveNavigationTreeClassNames(classNames) {
3299
3323
  )
3300
3324
  };
3301
3325
  }
3326
+ function entryClassName({
3327
+ item,
3328
+ active,
3329
+ collapsed,
3330
+ mobile,
3331
+ embedded = false,
3332
+ appearance,
3333
+ classNames,
3334
+ domClassName
3335
+ }) {
3336
+ return cn(
3337
+ "group/navigation-item relative flex w-full min-w-0 items-center gap-3 rounded-[var(--radius)] border border-transparent text-left text-sm outline-none transition-[background-color,border-color,color,box-shadow] focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none",
3338
+ appearance.itemHeight,
3339
+ appearance.itemPadding,
3340
+ embedded ? "min-h-0 self-stretch border-0 bg-transparent text-inherit hover:bg-transparent hover:text-inherit" : active ? activeVariantClassName(appearance.activeVariant) : "text-muted-foreground hover:border-border/70 hover:bg-muted/60 hover:text-foreground",
3341
+ collapsed && !mobile && "justify-center px-0",
3342
+ item.disabled && "pointer-events-none opacity-50",
3343
+ classNames?.item,
3344
+ active && classNames?.itemActive,
3345
+ mobile && classNames?.mobileItem,
3346
+ domClassName,
3347
+ item.className
3348
+ );
3349
+ }
3350
+ function EntryContent({
3351
+ item,
3352
+ collapsed,
3353
+ mobile,
3354
+ classNames,
3355
+ endIndicator
3356
+ }) {
3357
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3358
+ hasRenderableNode(item.icon) ? /* @__PURE__ */ jsxRuntime.jsx(
3359
+ "span",
3360
+ {
3361
+ className: cn(
3362
+ "flex size-5 shrink-0 items-center justify-center [&_svg]:size-full",
3363
+ classNames?.itemIcon
3364
+ ),
3365
+ "aria-hidden": "true",
3366
+ children: item.icon
3367
+ }
3368
+ ) : null,
3369
+ mobile || !collapsed ? /* @__PURE__ */ jsxRuntime.jsxs(
3370
+ "span",
3371
+ {
3372
+ className: cn(
3373
+ "flex min-w-0 flex-1 flex-col",
3374
+ classNames?.itemContent
3375
+ ),
3376
+ children: [
3377
+ /* @__PURE__ */ jsxRuntime.jsx(
3378
+ "span",
3379
+ {
3380
+ className: cn(
3381
+ "min-w-0 truncate font-semibold",
3382
+ classNames?.itemLabel
3383
+ ),
3384
+ children: item.label
3385
+ }
3386
+ ),
3387
+ hasRenderableNode(item.description) ? /* @__PURE__ */ jsxRuntime.jsx(
3388
+ "span",
3389
+ {
3390
+ className: cn(
3391
+ "mt-0.5 truncate text-xs font-normal",
3392
+ classNames?.itemDescription
3393
+ ),
3394
+ children: item.description
3395
+ }
3396
+ ) : null
3397
+ ]
3398
+ }
3399
+ ) : null,
3400
+ (mobile || !collapsed) && hasRenderableNode(item.meta) ? /* @__PURE__ */ jsxRuntime.jsx(
3401
+ "span",
3402
+ {
3403
+ className: cn(
3404
+ "shrink-0 text-xs text-muted-foreground",
3405
+ classNames?.itemMeta
3406
+ ),
3407
+ children: item.meta
3408
+ }
3409
+ ) : null,
3410
+ (mobile || !collapsed) && hasRenderableNode(item.endContent) ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("shrink-0", classNames?.itemEndContent), children: item.endContent }) : null,
3411
+ (mobile || !collapsed) && hasRenderableNode(endIndicator) ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-center justify-center", children: endIndicator }) : null
3412
+ ] });
3413
+ }
3302
3414
  function EntryButton({
3303
3415
  item,
3304
3416
  active,
@@ -3307,7 +3419,11 @@ function EntryButton({
3307
3419
  appearance,
3308
3420
  classNames,
3309
3421
  mobile = false,
3310
- onSelect
3422
+ embedded = false,
3423
+ onSelect,
3424
+ ariaControls,
3425
+ ariaExpanded,
3426
+ endIndicator
3311
3427
  }) {
3312
3428
  const ItemComponent = itemAs ?? (item.href ? "a" : "button");
3313
3429
  const title = getNodeTitle(item.label, item.title);
@@ -3324,90 +3440,90 @@ function EntryButton({
3324
3440
  ...domProps,
3325
3441
  ...item.to ? { to: item.to } : {},
3326
3442
  ...item.href ? { href: item.href } : {},
3327
- ...ItemComponent === "button" ? { type: "button" } : {},
3443
+ ...ItemComponent === "button" ? { type: "button", disabled: item.disabled } : {},
3328
3444
  title,
3329
3445
  "aria-current": active ? "page" : void 0,
3330
3446
  "aria-disabled": item.disabled || void 0,
3447
+ "aria-controls": ariaControls,
3448
+ "aria-expanded": ariaExpanded,
3331
3449
  tabIndex: item.disabled ? -1 : domProps.tabIndex,
3332
3450
  onClick: handleClick
3333
3451
  };
3334
- return /* @__PURE__ */ jsxRuntime.jsxs(
3452
+ return /* @__PURE__ */ jsxRuntime.jsx(
3335
3453
  ItemComponent,
3336
3454
  {
3337
3455
  ...componentProps,
3338
3456
  "data-navigation-sidebar-item-id": item.id,
3339
3457
  "data-active": active ? "true" : "false",
3340
- className: cn(
3341
- "group/navigation-item relative flex w-full min-w-0 items-center gap-3 rounded-[var(--radius)] border border-transparent text-left text-sm outline-none transition-[background-color,border-color,color,box-shadow] focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none",
3342
- appearance.itemHeight,
3343
- appearance.itemPadding,
3344
- active ? activeVariantClassName(appearance.activeVariant) : "text-muted-foreground hover:border-border/70 hover:bg-muted/60 hover:text-foreground",
3345
- collapsed && !mobile && "justify-center px-0",
3346
- item.disabled && "pointer-events-none opacity-50",
3347
- classNames?.item,
3348
- active && classNames?.itemActive,
3349
- mobile && classNames?.mobileItem,
3350
- domProps.className,
3351
- item.className
3352
- ),
3353
- children: [
3354
- hasRenderableNode(item.icon) ? /* @__PURE__ */ jsxRuntime.jsx(
3355
- "span",
3356
- {
3357
- className: cn(
3358
- "flex size-5 shrink-0 items-center justify-center [&_svg]:size-full",
3359
- classNames?.itemIcon
3360
- ),
3361
- "aria-hidden": "true",
3362
- children: item.icon
3363
- }
3364
- ) : null,
3365
- mobile || !collapsed ? /* @__PURE__ */ jsxRuntime.jsxs(
3366
- "span",
3367
- {
3368
- className: cn(
3369
- "flex min-w-0 flex-1 flex-col",
3370
- classNames?.itemContent
3371
- ),
3372
- children: [
3373
- /* @__PURE__ */ jsxRuntime.jsx(
3374
- "span",
3375
- {
3376
- className: cn(
3377
- "min-w-0 truncate font-semibold",
3378
- classNames?.itemLabel
3379
- ),
3380
- children: item.label
3381
- }
3382
- ),
3383
- hasRenderableNode(item.description) ? /* @__PURE__ */ jsxRuntime.jsx(
3384
- "span",
3385
- {
3386
- className: cn(
3387
- "mt-0.5 truncate text-xs font-normal",
3388
- classNames?.itemDescription
3389
- ),
3390
- children: item.description
3391
- }
3392
- ) : null
3393
- ]
3394
- }
3395
- ) : null,
3396
- (mobile || !collapsed) && hasRenderableNode(item.meta) ? /* @__PURE__ */ jsxRuntime.jsx(
3397
- "span",
3398
- {
3399
- className: cn(
3400
- "shrink-0 text-xs text-muted-foreground",
3401
- classNames?.itemMeta
3402
- ),
3403
- children: item.meta
3404
- }
3405
- ) : null,
3406
- (mobile || !collapsed) && hasRenderableNode(item.endContent) ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("shrink-0", classNames?.itemEndContent), children: item.endContent }) : null
3407
- ]
3458
+ "data-expanded": ariaExpanded === void 0 ? void 0 : String(ariaExpanded),
3459
+ className: entryClassName({
3460
+ item,
3461
+ active,
3462
+ collapsed,
3463
+ mobile,
3464
+ embedded,
3465
+ appearance,
3466
+ classNames,
3467
+ domClassName: domProps.className
3468
+ }),
3469
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3470
+ EntryContent,
3471
+ {
3472
+ item,
3473
+ collapsed,
3474
+ mobile,
3475
+ classNames,
3476
+ endIndicator
3477
+ }
3478
+ )
3479
+ }
3480
+ );
3481
+ }
3482
+ function EntryPresentation({
3483
+ item,
3484
+ appearance,
3485
+ classNames
3486
+ }) {
3487
+ const domProps = item.domProps ?? {};
3488
+ return /* @__PURE__ */ jsxRuntime.jsx(
3489
+ "div",
3490
+ {
3491
+ ...domProps,
3492
+ "data-navigation-sidebar-item-id": item.id,
3493
+ "aria-disabled": item.disabled || void 0,
3494
+ title: getNodeTitle(item.label, item.title),
3495
+ className: entryClassName({
3496
+ item,
3497
+ active: false,
3498
+ collapsed: false,
3499
+ mobile: true,
3500
+ embedded: true,
3501
+ appearance,
3502
+ classNames,
3503
+ domClassName: domProps.className
3504
+ }),
3505
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3506
+ EntryContent,
3507
+ {
3508
+ item,
3509
+ collapsed: false,
3510
+ mobile: true,
3511
+ classNames
3512
+ }
3513
+ )
3408
3514
  }
3409
3515
  );
3410
3516
  }
3517
+ function routeRowClassName({
3518
+ active,
3519
+ appearance
3520
+ }) {
3521
+ return cn(
3522
+ "flex w-full min-w-0 items-center gap-0.5 rounded-[var(--radius)] border border-transparent pr-1 text-sm transition-[background-color,border-color,color,box-shadow] motion-reduce:transition-none",
3523
+ appearance.itemHeight,
3524
+ active ? activeVariantClassName(appearance.activeVariant) : "text-muted-foreground hover:border-border/70 hover:bg-muted/60 hover:text-foreground"
3525
+ );
3526
+ }
3411
3527
  function filterCatalogItems(items, activeFilterId) {
3412
3528
  if (!activeFilterId) return items;
3413
3529
  return items.filter(
@@ -3539,7 +3655,9 @@ function ChildEntry({
3539
3655
  active,
3540
3656
  interactive,
3541
3657
  onSelect,
3542
- classNames
3658
+ appearance,
3659
+ classNames,
3660
+ embedded = false
3543
3661
  }) {
3544
3662
  const Component = interactive ? "button" : "div";
3545
3663
  const domProps = child.domProps ?? {};
@@ -3554,8 +3672,11 @@ function ChildEntry({
3554
3672
  title: getNodeTitle(child.label, child.title),
3555
3673
  disabled: interactive ? child.disabled : void 0,
3556
3674
  className: cn(
3557
- "flex min-h-9 w-full min-w-0 items-center gap-2 rounded-md border border-transparent px-2 text-left text-xs font-medium outline-none transition-[background-color,border-color,color] focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none",
3558
- active ? "border-primary/35 bg-primary/10 text-primary" : "text-muted-foreground hover:border-border hover:bg-muted/60 hover:text-foreground",
3675
+ "flex w-full min-w-0 flex-1 items-center gap-3 rounded-[var(--radius)] border border-transparent text-left text-sm font-semibold outline-none transition-[background-color,border-color,color,box-shadow] focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none",
3676
+ appearance.itemHeight,
3677
+ appearance.itemPadding,
3678
+ "pl-11",
3679
+ embedded ? "min-h-0 self-stretch border-0 bg-transparent text-inherit hover:bg-transparent hover:text-inherit" : active ? activeVariantClassName(appearance.activeVariant) : "text-muted-foreground hover:border-border/70 hover:bg-muted/60 hover:text-foreground",
3559
3680
  child.disabled && "pointer-events-none opacity-50",
3560
3681
  classNames?.childItem,
3561
3682
  active && classNames?.childItemActive,
@@ -3567,7 +3688,7 @@ function ChildEntry({
3567
3688
  hasRenderableNode(child.icon) ? /* @__PURE__ */ jsxRuntime.jsx(
3568
3689
  "span",
3569
3690
  {
3570
- className: "flex size-4 shrink-0 items-center justify-center",
3691
+ className: "flex size-5 shrink-0 items-center justify-center [&_svg]:size-full",
3571
3692
  "aria-hidden": "true",
3572
3693
  children: child.icon
3573
3694
  }
@@ -3577,29 +3698,167 @@ function ChildEntry({
3577
3698
  }
3578
3699
  );
3579
3700
  }
3701
+ function RouteActions({
3702
+ itemId,
3703
+ actions,
3704
+ kind,
3705
+ className,
3706
+ mobile = false
3707
+ }) {
3708
+ if (!hasRenderableNode(actions)) return null;
3709
+ return /* @__PURE__ */ jsxRuntime.jsx(
3710
+ "div",
3711
+ {
3712
+ "data-navigation-sidebar-item-actions": kind === "item" ? itemId : void 0,
3713
+ "data-navigation-sidebar-child-actions": kind === "child" ? itemId : void 0,
3714
+ "data-navigation-sidebar-actions-mobile": mobile ? "true" : void 0,
3715
+ className: cn(
3716
+ "ml-auto flex shrink-0 items-center gap-1 [&>button]:border-0 [&>button]:bg-transparent [&>button]:shadow-none [&>button:hover]:bg-muted",
3717
+ className
3718
+ ),
3719
+ children: actions
3720
+ }
3721
+ );
3722
+ }
3723
+ function isDisclosureRouteItem(item) {
3724
+ return item.interaction === "disclosure";
3725
+ }
3726
+ function validateRouteItems(routeItems) {
3727
+ const itemIds = /* @__PURE__ */ new Set();
3728
+ const childIds = /* @__PURE__ */ new Set();
3729
+ const collapsibleItemIds = /* @__PURE__ */ new Set();
3730
+ for (const item of routeItems) {
3731
+ const itemId = item.id;
3732
+ if (itemIds.has(itemId)) {
3733
+ throw new RangeError(
3734
+ `NavigationSidebar route item ids must be unique; received "${itemId}" more than once.`
3735
+ );
3736
+ }
3737
+ itemIds.add(itemId);
3738
+ const disclosure = isDisclosureRouteItem(item);
3739
+ if (disclosure) {
3740
+ if (!item.children.length) {
3741
+ throw new RangeError(
3742
+ `NavigationSidebar disclosure item "${item.id}" must include at least one child.`
3743
+ );
3744
+ }
3745
+ if (item.href !== void 0 || item.to !== void 0 || item.onClick !== void 0) {
3746
+ throw new TypeError(
3747
+ `NavigationSidebar disclosure item "${itemId}" cannot define href, to, or onClick.`
3748
+ );
3749
+ }
3750
+ collapsibleItemIds.add(item.id);
3751
+ } else if (item.childrenPresentation === "collapsible") {
3752
+ if (!item.children?.length) {
3753
+ throw new RangeError(
3754
+ `NavigationSidebar collapsible route item "${item.id}" must include at least one child.`
3755
+ );
3756
+ }
3757
+ collapsibleItemIds.add(item.id);
3758
+ }
3759
+ for (const child of item.children ?? []) {
3760
+ if (childIds.has(child.id)) {
3761
+ throw new RangeError(
3762
+ `NavigationSidebar route child ids must be unique; received "${child.id}" more than once.`
3763
+ );
3764
+ }
3765
+ childIds.add(child.id);
3766
+ }
3767
+ }
3768
+ return collapsibleItemIds;
3769
+ }
3770
+ function RouteChildrenToggle({
3771
+ item,
3772
+ expanded,
3773
+ controls,
3774
+ label,
3775
+ onToggle,
3776
+ classNames
3777
+ }) {
3778
+ const itemLabel = typeof item.label === "string" ? item.label : item.id;
3779
+ return /* @__PURE__ */ jsxRuntime.jsx(
3780
+ "button",
3781
+ {
3782
+ type: "button",
3783
+ disabled: item.disabled,
3784
+ "aria-expanded": expanded,
3785
+ "aria-controls": controls,
3786
+ "aria-label": `${label}: ${itemLabel}`,
3787
+ title: label,
3788
+ "data-navigation-sidebar-disclosure-trigger": item.id,
3789
+ className: cn(
3790
+ "inline-flex h-8 w-7 shrink-0 items-center justify-center rounded-md border-0 bg-transparent text-muted-foreground shadow-none outline-none hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
3791
+ classNames?.childMenuTrigger
3792
+ ),
3793
+ onClick: onToggle,
3794
+ children: /* @__PURE__ */ jsxRuntime.jsx(DisclosureIcon, { expanded })
3795
+ }
3796
+ );
3797
+ }
3580
3798
  function CollapsedChildMenu({
3581
3799
  item,
3582
3800
  activeChildId,
3583
3801
  label,
3584
3802
  onChildSelect,
3585
- classNames
3803
+ classNames,
3804
+ appearance,
3805
+ triggerVariant = "submenu",
3806
+ mobile = false
3586
3807
  }) {
3587
3808
  const enabledChildren = (item.children ?? []).filter(
3588
3809
  (child) => !child.disabled
3589
3810
  );
3590
- if (enabledChildren.length === 0) return null;
3811
+ if (!item.children?.length) return null;
3812
+ if (triggerVariant === "submenu" && (!onChildSelect || enabledChildren.length === 0))
3813
+ return null;
3814
+ const itemLabel = typeof item.label === "string" ? item.label : item.id;
3815
+ const itemTriggerDisabled = Boolean(item.disabled) || !onChildSelect || enabledChildren.length === 0;
3816
+ const domProps = item.domProps ?? {};
3591
3817
  return /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu__namespace.Root, { children: [
3592
- /* @__PURE__ */ jsxRuntime.jsx(DropdownMenu__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
3818
+ /* @__PURE__ */ jsxRuntime.jsx(DropdownMenu__namespace.Trigger, { asChild: true, children: triggerVariant === "item" && appearance ? /* @__PURE__ */ jsxRuntime.jsx(
3819
+ "button",
3820
+ {
3821
+ ...domProps,
3822
+ type: "button",
3823
+ disabled: itemTriggerDisabled,
3824
+ "aria-label": `${label}: ${itemLabel}`,
3825
+ title: label,
3826
+ "data-navigation-sidebar-item-id": item.id,
3827
+ "data-navigation-sidebar-disclosure-menu": "true",
3828
+ className: entryClassName({
3829
+ item,
3830
+ active: false,
3831
+ collapsed: !mobile,
3832
+ mobile,
3833
+ appearance,
3834
+ classNames,
3835
+ domClassName: domProps.className
3836
+ }),
3837
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3838
+ EntryContent,
3839
+ {
3840
+ item,
3841
+ collapsed: !mobile,
3842
+ mobile,
3843
+ classNames,
3844
+ endIndicator: mobile ? /* @__PURE__ */ jsxRuntime.jsx(DisclosureIcon, { expanded: false }) : void 0
3845
+ }
3846
+ )
3847
+ }
3848
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
3593
3849
  "button",
3594
3850
  {
3595
3851
  type: "button",
3852
+ disabled: triggerVariant === "disclosure" ? itemTriggerDisabled : void 0,
3853
+ "data-navigation-sidebar-disclosure-trigger": triggerVariant === "disclosure" ? item.id : void 0,
3596
3854
  className: cn(
3597
- "inline-flex h-8 w-6 shrink-0 items-center justify-center rounded-md text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring",
3855
+ "inline-flex h-8 shrink-0 items-center justify-center rounded-md border-0 bg-transparent text-muted-foreground shadow-none outline-none hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
3856
+ triggerVariant === "disclosure" ? "w-7" : "w-6",
3598
3857
  classNames?.childMenuTrigger
3599
3858
  ),
3600
- "aria-label": `${label}: ${typeof item.label === "string" ? item.label : item.id}`,
3859
+ "aria-label": `${label}: ${itemLabel}`,
3601
3860
  title: label,
3602
- children: /* @__PURE__ */ jsxRuntime.jsx(MoreIcon2, {})
3861
+ children: triggerVariant === "disclosure" ? /* @__PURE__ */ jsxRuntime.jsx(DisclosureIcon, { expanded: false }) : /* @__PURE__ */ jsxRuntime.jsx(MoreIcon2, {})
3603
3862
  }
3604
3863
  ) }),
3605
3864
  /* @__PURE__ */ jsxRuntime.jsx(DropdownMenu__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -3622,7 +3881,7 @@ function CollapsedChildMenu({
3622
3881
  "flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
3623
3882
  child.id === activeChildId && "bg-accent text-accent-foreground"
3624
3883
  ),
3625
- onSelect: () => onChildSelect(child, item),
3884
+ onSelect: () => onChildSelect?.(child, item),
3626
3885
  children: [
3627
3886
  hasRenderableNode(child.icon) ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: child.icon }) : null,
3628
3887
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: child.label })
@@ -3643,22 +3902,81 @@ function RouteContent({
3643
3902
  routeItems,
3644
3903
  activeItemId,
3645
3904
  activeChildId,
3905
+ expandedItemIds,
3906
+ defaultExpandedItemIds = [],
3646
3907
  itemAs,
3647
3908
  onItemSelect,
3648
3909
  onChildSelect,
3910
+ onExpandedItemIdsChange,
3649
3911
  collapsedChildMenuLabel = "Open submenu",
3912
+ disclosureLabel = "Toggle submenu",
3650
3913
  ariaLabel,
3651
3914
  classNames
3652
3915
  } = props;
3653
- return /* @__PURE__ */ jsxRuntime.jsx("nav", { "aria-label": ariaLabel, children: /* @__PURE__ */ jsxRuntime.jsx("ul", { className: cn("grid list-none gap-2 p-0", classNames?.list), children: routeItems.map((item) => {
3654
- const active = item.id === activeItemId;
3916
+ const disclosureId = react.useId();
3917
+ const collapsibleItemIds = react.useMemo(
3918
+ () => validateRouteItems(routeItems),
3919
+ [routeItems]
3920
+ );
3921
+ const [internalExpandedItemIds, setInternalExpandedItemIds] = react.useState(
3922
+ () => defaultExpandedItemIds
3923
+ );
3924
+ const isExpandedControlled = expandedItemIds !== void 0;
3925
+ const resolvedExpandedItemIds = expandedItemIds ?? internalExpandedItemIds;
3926
+ const normalizedExpandedItemIds = react.useMemo(
3927
+ () => resolvedExpandedItemIds.filter(
3928
+ (itemId) => collapsibleItemIds.has(itemId)
3929
+ ),
3930
+ [collapsibleItemIds, resolvedExpandedItemIds]
3931
+ );
3932
+ const expandedItemIdSet = react.useMemo(
3933
+ () => new Set(normalizedExpandedItemIds),
3934
+ [normalizedExpandedItemIds]
3935
+ );
3936
+ const toggleItemExpanded = react.useCallback(
3937
+ (item) => {
3938
+ if (item.disabled || !collapsibleItemIds.has(item.id)) return;
3939
+ const nextExpandedItemIds = expandedItemIdSet.has(item.id) ? normalizedExpandedItemIds.filter((itemId) => itemId !== item.id) : [...normalizedExpandedItemIds, item.id];
3940
+ if (!isExpandedControlled) {
3941
+ setInternalExpandedItemIds(nextExpandedItemIds);
3942
+ }
3943
+ onExpandedItemIdsChange?.(nextExpandedItemIds);
3944
+ },
3945
+ [
3946
+ collapsibleItemIds,
3947
+ expandedItemIdSet,
3948
+ isExpandedControlled,
3949
+ normalizedExpandedItemIds,
3950
+ onExpandedItemIdsChange
3951
+ ]
3952
+ );
3953
+ return /* @__PURE__ */ jsxRuntime.jsx("nav", { "aria-label": ariaLabel, children: /* @__PURE__ */ jsxRuntime.jsx("ul", { className: cn("grid list-none gap-2 p-0", classNames?.list), children: routeItems.map((item, itemIndex) => {
3954
+ const disclosure = isDisclosureRouteItem(item);
3955
+ const active = !disclosure && item.id === activeItemId;
3956
+ const hasChildren = Boolean(item.children?.length);
3957
+ const childrenCollapsible = collapsibleItemIds.has(item.id);
3958
+ const childrenExpanded = !childrenCollapsible || expandedItemIdSet.has(item.id);
3959
+ const showItemActions = !collapsed && hasRenderableNode(item.actions);
3960
+ const compoundItemRow = collapsed && hasChildren && Boolean(onChildSelect) || !collapsed && childrenCollapsible && !disclosure || showItemActions;
3961
+ const childListId = `${disclosureId}-route-children-${itemIndex}`;
3655
3962
  return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "grid min-w-0 gap-1", children: [
3656
- /* @__PURE__ */ jsxRuntime.jsxs(
3963
+ collapsed && disclosure ? /* @__PURE__ */ jsxRuntime.jsx(
3964
+ CollapsedChildMenu,
3965
+ {
3966
+ item,
3967
+ activeChildId,
3968
+ label: collapsedChildMenuLabel,
3969
+ onChildSelect,
3970
+ classNames,
3971
+ appearance,
3972
+ triggerVariant: "item"
3973
+ }
3974
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(
3657
3975
  "div",
3658
3976
  {
3659
- className: cn(
3660
- collapsed && Boolean(item.children?.length) && Boolean(onChildSelect) && "flex min-w-0 items-center gap-0.5"
3661
- ),
3977
+ "data-navigation-sidebar-item-row": compoundItemRow ? item.id : void 0,
3978
+ "data-active": compoundItemRow ? String(active) : void 0,
3979
+ className: compoundItemRow ? routeRowClassName({ active, appearance }) : void 0,
3662
3980
  children: [
3663
3981
  /* @__PURE__ */ jsxRuntime.jsx(
3664
3982
  EntryButton,
@@ -3666,12 +3984,25 @@ function RouteContent({
3666
3984
  item,
3667
3985
  active,
3668
3986
  collapsed,
3669
- itemAs,
3987
+ embedded: compoundItemRow,
3988
+ itemAs: disclosure ? "button" : itemAs,
3670
3989
  appearance,
3671
3990
  classNames,
3672
- onSelect: onItemSelect ? () => onItemSelect(item) : void 0
3991
+ onSelect: disclosure ? () => toggleItemExpanded(item) : onItemSelect ? () => onItemSelect(item) : void 0,
3992
+ ariaControls: !collapsed && childrenCollapsible && disclosure ? childListId : void 0,
3993
+ ariaExpanded: !collapsed && childrenCollapsible && disclosure ? childrenExpanded : void 0,
3994
+ endIndicator: !collapsed && disclosure && !showItemActions ? /* @__PURE__ */ jsxRuntime.jsx(DisclosureIcon, { expanded: childrenExpanded }) : void 0
3673
3995
  }
3674
3996
  ),
3997
+ showItemActions ? /* @__PURE__ */ jsxRuntime.jsx(
3998
+ RouteActions,
3999
+ {
4000
+ itemId: item.id,
4001
+ actions: item.actions,
4002
+ kind: "item",
4003
+ className: classNames?.itemActions
4004
+ }
4005
+ ) : null,
3675
4006
  collapsed && onChildSelect ? /* @__PURE__ */ jsxRuntime.jsx(
3676
4007
  CollapsedChildMenu,
3677
4008
  {
@@ -3681,28 +4012,71 @@ function RouteContent({
3681
4012
  onChildSelect,
3682
4013
  classNames
3683
4014
  }
4015
+ ) : null,
4016
+ !collapsed && childrenCollapsible && (!disclosure || showItemActions) ? /* @__PURE__ */ jsxRuntime.jsx(
4017
+ RouteChildrenToggle,
4018
+ {
4019
+ item,
4020
+ expanded: childrenExpanded,
4021
+ controls: childListId,
4022
+ label: disclosureLabel,
4023
+ onToggle: () => toggleItemExpanded(item),
4024
+ classNames
4025
+ }
3684
4026
  ) : null
3685
4027
  ]
3686
4028
  }
3687
4029
  ),
3688
- !collapsed && item.children?.length ? /* @__PURE__ */ jsxRuntime.jsx(
4030
+ !collapsed && hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
3689
4031
  "ul",
3690
4032
  {
4033
+ id: childListId,
4034
+ hidden: !childrenExpanded,
4035
+ style: childrenExpanded ? void 0 : { display: "none" },
3691
4036
  className: cn(
3692
4037
  "ml-5 grid list-none gap-0.5 border-l border-border/70 py-1 pl-3",
3693
4038
  classNames?.childList
3694
4039
  ),
3695
- children: item.children.map((child) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
3696
- ChildEntry,
3697
- {
3698
- child,
3699
- parent: item,
3700
- active: child.id === activeChildId,
3701
- interactive: Boolean(onChildSelect),
3702
- onSelect: onChildSelect,
3703
- classNames
3704
- }
3705
- ) }, child.id))
4040
+ children: item.children?.map((child) => {
4041
+ const hasChildActions = hasRenderableNode(child.actions);
4042
+ const childActive = child.id === activeChildId;
4043
+ const childEntry = /* @__PURE__ */ jsxRuntime.jsx(
4044
+ ChildEntry,
4045
+ {
4046
+ child,
4047
+ parent: item,
4048
+ active: childActive,
4049
+ interactive: Boolean(onChildSelect),
4050
+ onSelect: onChildSelect,
4051
+ appearance,
4052
+ classNames,
4053
+ embedded: hasChildActions
4054
+ }
4055
+ );
4056
+ return /* @__PURE__ */ jsxRuntime.jsx("li", { children: hasChildActions ? /* @__PURE__ */ jsxRuntime.jsxs(
4057
+ "div",
4058
+ {
4059
+ "data-navigation-sidebar-child-row": child.id,
4060
+ "data-active": String(childActive),
4061
+ className: routeRowClassName({
4062
+ active: childActive,
4063
+ appearance
4064
+ }),
4065
+ children: [
4066
+ childEntry,
4067
+ /* @__PURE__ */ jsxRuntime.jsx(
4068
+ RouteActions,
4069
+ {
4070
+ itemId: child.id,
4071
+ actions: child.actions,
4072
+ kind: "child",
4073
+ className: classNames?.childActions
4074
+ }
4075
+ )
4076
+ ]
4077
+ }
4078
+ ) : childEntry }, child.id);
4079
+ })
3706
4080
  }
3707
4081
  ) : null
3708
4082
  ] }, item.id);
@@ -3736,23 +4110,116 @@ function MobileNavigation({
3736
4110
  classNames?.mobileNav,
3737
4111
  props.mobile.className
3738
4112
  ),
3739
- children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
3740
- EntryButton,
3741
- {
3742
- item,
3743
- active: props.kind === "catalog" ? item.active ?? item.id === props.activeItemId : item.id === props.activeItemId,
3744
- collapsed: false,
3745
- itemAs: props.itemAs,
3746
- appearance,
3747
- classNames: {
3748
- ...classNames,
3749
- mobileItem: cn(classNames?.mobileItem, props.mobile?.itemClassName)
4113
+ children: items.map((item) => {
4114
+ const mobileClassNames = {
4115
+ ...classNames,
4116
+ mobileItem: cn(
4117
+ classNames?.mobileItem,
4118
+ props.mobile?.itemClassName
4119
+ )
4120
+ };
4121
+ const routeItem = props.kind === "route" ? item : void 0;
4122
+ const showRouteActions = routeItem !== void 0 && hasRenderableNode(routeItem.actions);
4123
+ if (props.kind === "route" && isDisclosureRouteItem(item)) {
4124
+ return showRouteActions ? /* @__PURE__ */ jsxRuntime.jsxs(
4125
+ "div",
4126
+ {
4127
+ "data-navigation-sidebar-item-row": item.id,
4128
+ "data-active": "false",
4129
+ className: cn(
4130
+ routeRowClassName({ active: false, appearance }),
4131
+ "shrink-0"
4132
+ ),
4133
+ children: [
4134
+ /* @__PURE__ */ jsxRuntime.jsx(
4135
+ EntryPresentation,
4136
+ {
4137
+ item,
4138
+ appearance,
4139
+ classNames: mobileClassNames
4140
+ }
4141
+ ),
4142
+ /* @__PURE__ */ jsxRuntime.jsx(
4143
+ RouteActions,
4144
+ {
4145
+ itemId: item.id,
4146
+ actions: item.actions,
4147
+ kind: "item",
4148
+ className: classNames?.itemActions,
4149
+ mobile: true
4150
+ }
4151
+ ),
4152
+ /* @__PURE__ */ jsxRuntime.jsx(
4153
+ CollapsedChildMenu,
4154
+ {
4155
+ item,
4156
+ activeChildId: props.activeChildId,
4157
+ label: props.collapsedChildMenuLabel ?? "Open submenu",
4158
+ onChildSelect: props.onChildSelect,
4159
+ classNames: mobileClassNames,
4160
+ appearance,
4161
+ triggerVariant: "disclosure",
4162
+ mobile: true
4163
+ }
4164
+ )
4165
+ ]
4166
+ },
4167
+ item.id
4168
+ ) : /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsx(
4169
+ CollapsedChildMenu,
4170
+ {
4171
+ item,
4172
+ activeChildId: props.activeChildId,
4173
+ label: props.collapsedChildMenuLabel ?? "Open submenu",
4174
+ onChildSelect: props.onChildSelect,
4175
+ classNames: mobileClassNames,
4176
+ appearance,
4177
+ triggerVariant: "item",
4178
+ mobile: true
4179
+ }
4180
+ ) }, item.id);
4181
+ }
4182
+ const active = props.kind === "catalog" ? item.active ?? item.id === props.activeItemId : item.id === props.activeItemId;
4183
+ const entryButton = /* @__PURE__ */ jsxRuntime.jsx(
4184
+ EntryButton,
4185
+ {
4186
+ item,
4187
+ active,
4188
+ collapsed: false,
4189
+ embedded: showRouteActions,
4190
+ itemAs: props.itemAs,
4191
+ appearance,
4192
+ classNames: mobileClassNames,
4193
+ mobile: true,
4194
+ onSelect: props.kind === "route" && props.onItemSelect ? () => props.onItemSelect?.(item) : void 0
4195
+ }
4196
+ );
4197
+ return showRouteActions && routeItem ? /* @__PURE__ */ jsxRuntime.jsxs(
4198
+ "div",
4199
+ {
4200
+ "data-navigation-sidebar-item-row": item.id,
4201
+ "data-active": String(active),
4202
+ className: cn(
4203
+ routeRowClassName({ active, appearance }),
4204
+ "shrink-0"
4205
+ ),
4206
+ children: [
4207
+ entryButton,
4208
+ /* @__PURE__ */ jsxRuntime.jsx(
4209
+ RouteActions,
4210
+ {
4211
+ itemId: routeItem.id,
4212
+ actions: routeItem.actions,
4213
+ kind: "item",
4214
+ className: classNames?.itemActions,
4215
+ mobile: true
4216
+ }
4217
+ )
4218
+ ]
3750
4219
  },
3751
- mobile: true,
3752
- onSelect: props.kind === "route" && props.onItemSelect ? () => props.onItemSelect?.(item) : void 0
3753
- },
3754
- item.id
3755
- ))
4220
+ item.id
4221
+ ) : /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: entryButton }, item.id);
4222
+ })
3756
4223
  }
3757
4224
  );
3758
4225
  }