@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.
@@ -1,5 +1,5 @@
1
1
  import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
2
- import { forwardRef, useRef, useState, useMemo, useCallback, useEffect, createContext, useContext } from 'react';
2
+ import { forwardRef, useRef, useState, useMemo, useCallback, useEffect, createContext, Fragment as Fragment$1, useId, useContext } from 'react';
3
3
  import { twMerge } from 'tailwind-merge';
4
4
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
5
5
 
@@ -1614,6 +1614,30 @@ function MoreIcon2() {
1614
1614
  }
1615
1615
  );
1616
1616
  }
1617
+ function DisclosureIcon({ expanded }) {
1618
+ return /* @__PURE__ */ jsx(
1619
+ "svg",
1620
+ {
1621
+ "aria-hidden": "true",
1622
+ viewBox: "0 0 20 20",
1623
+ className: cn(
1624
+ "h-4 w-4 transition-transform motion-reduce:transition-none",
1625
+ expanded && "rotate-180"
1626
+ ),
1627
+ fill: "none",
1628
+ children: /* @__PURE__ */ jsx(
1629
+ "path",
1630
+ {
1631
+ d: "m6 8 4 4 4-4",
1632
+ stroke: "currentColor",
1633
+ strokeWidth: "1.6",
1634
+ strokeLinecap: "round",
1635
+ strokeLinejoin: "round"
1636
+ }
1637
+ )
1638
+ }
1639
+ );
1640
+ }
1617
1641
  function assignRef(ref, node) {
1618
1642
  if (typeof ref === "function") {
1619
1643
  ref(node);
@@ -1769,6 +1793,94 @@ function resolveNavigationTreeClassNames(classNames) {
1769
1793
  )
1770
1794
  };
1771
1795
  }
1796
+ function entryClassName({
1797
+ item,
1798
+ active,
1799
+ collapsed,
1800
+ mobile,
1801
+ embedded = false,
1802
+ appearance,
1803
+ classNames,
1804
+ domClassName
1805
+ }) {
1806
+ return cn(
1807
+ "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",
1808
+ appearance.itemHeight,
1809
+ appearance.itemPadding,
1810
+ 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",
1811
+ collapsed && !mobile && "justify-center px-0",
1812
+ item.disabled && "pointer-events-none opacity-50",
1813
+ classNames?.item,
1814
+ active && classNames?.itemActive,
1815
+ mobile && classNames?.mobileItem,
1816
+ domClassName,
1817
+ item.className
1818
+ );
1819
+ }
1820
+ function EntryContent({
1821
+ item,
1822
+ collapsed,
1823
+ mobile,
1824
+ classNames,
1825
+ endIndicator
1826
+ }) {
1827
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1828
+ hasRenderableNode(item.icon) ? /* @__PURE__ */ jsx(
1829
+ "span",
1830
+ {
1831
+ className: cn(
1832
+ "flex size-5 shrink-0 items-center justify-center [&_svg]:size-full",
1833
+ classNames?.itemIcon
1834
+ ),
1835
+ "aria-hidden": "true",
1836
+ children: item.icon
1837
+ }
1838
+ ) : null,
1839
+ mobile || !collapsed ? /* @__PURE__ */ jsxs(
1840
+ "span",
1841
+ {
1842
+ className: cn(
1843
+ "flex min-w-0 flex-1 flex-col",
1844
+ classNames?.itemContent
1845
+ ),
1846
+ children: [
1847
+ /* @__PURE__ */ jsx(
1848
+ "span",
1849
+ {
1850
+ className: cn(
1851
+ "min-w-0 truncate font-semibold",
1852
+ classNames?.itemLabel
1853
+ ),
1854
+ children: item.label
1855
+ }
1856
+ ),
1857
+ hasRenderableNode(item.description) ? /* @__PURE__ */ jsx(
1858
+ "span",
1859
+ {
1860
+ className: cn(
1861
+ "mt-0.5 truncate text-xs font-normal",
1862
+ classNames?.itemDescription
1863
+ ),
1864
+ children: item.description
1865
+ }
1866
+ ) : null
1867
+ ]
1868
+ }
1869
+ ) : null,
1870
+ (mobile || !collapsed) && hasRenderableNode(item.meta) ? /* @__PURE__ */ jsx(
1871
+ "span",
1872
+ {
1873
+ className: cn(
1874
+ "shrink-0 text-xs text-muted-foreground",
1875
+ classNames?.itemMeta
1876
+ ),
1877
+ children: item.meta
1878
+ }
1879
+ ) : null,
1880
+ (mobile || !collapsed) && hasRenderableNode(item.endContent) ? /* @__PURE__ */ jsx("span", { className: cn("shrink-0", classNames?.itemEndContent), children: item.endContent }) : null,
1881
+ (mobile || !collapsed) && hasRenderableNode(endIndicator) ? /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center justify-center", children: endIndicator }) : null
1882
+ ] });
1883
+ }
1772
1884
  function EntryButton({
1773
1885
  item,
1774
1886
  active,
@@ -1777,7 +1889,11 @@ function EntryButton({
1777
1889
  appearance,
1778
1890
  classNames,
1779
1891
  mobile = false,
1780
- onSelect
1892
+ embedded = false,
1893
+ onSelect,
1894
+ ariaControls,
1895
+ ariaExpanded,
1896
+ endIndicator
1781
1897
  }) {
1782
1898
  const ItemComponent = itemAs ?? (item.href ? "a" : "button");
1783
1899
  const title = getNodeTitle(item.label, item.title);
@@ -1794,90 +1910,90 @@ function EntryButton({
1794
1910
  ...domProps,
1795
1911
  ...item.to ? { to: item.to } : {},
1796
1912
  ...item.href ? { href: item.href } : {},
1797
- ...ItemComponent === "button" ? { type: "button" } : {},
1913
+ ...ItemComponent === "button" ? { type: "button", disabled: item.disabled } : {},
1798
1914
  title,
1799
1915
  "aria-current": active ? "page" : void 0,
1800
1916
  "aria-disabled": item.disabled || void 0,
1917
+ "aria-controls": ariaControls,
1918
+ "aria-expanded": ariaExpanded,
1801
1919
  tabIndex: item.disabled ? -1 : domProps.tabIndex,
1802
1920
  onClick: handleClick
1803
1921
  };
1804
- return /* @__PURE__ */ jsxs(
1922
+ return /* @__PURE__ */ jsx(
1805
1923
  ItemComponent,
1806
1924
  {
1807
1925
  ...componentProps,
1808
1926
  "data-navigation-sidebar-item-id": item.id,
1809
1927
  "data-active": active ? "true" : "false",
1810
- className: cn(
1811
- "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",
1812
- appearance.itemHeight,
1813
- appearance.itemPadding,
1814
- active ? activeVariantClassName(appearance.activeVariant) : "text-muted-foreground hover:border-border/70 hover:bg-muted/60 hover:text-foreground",
1815
- collapsed && !mobile && "justify-center px-0",
1816
- item.disabled && "pointer-events-none opacity-50",
1817
- classNames?.item,
1818
- active && classNames?.itemActive,
1819
- mobile && classNames?.mobileItem,
1820
- domProps.className,
1821
- item.className
1822
- ),
1823
- children: [
1824
- hasRenderableNode(item.icon) ? /* @__PURE__ */ jsx(
1825
- "span",
1826
- {
1827
- className: cn(
1828
- "flex size-5 shrink-0 items-center justify-center [&_svg]:size-full",
1829
- classNames?.itemIcon
1830
- ),
1831
- "aria-hidden": "true",
1832
- children: item.icon
1833
- }
1834
- ) : null,
1835
- mobile || !collapsed ? /* @__PURE__ */ jsxs(
1836
- "span",
1837
- {
1838
- className: cn(
1839
- "flex min-w-0 flex-1 flex-col",
1840
- classNames?.itemContent
1841
- ),
1842
- children: [
1843
- /* @__PURE__ */ jsx(
1844
- "span",
1845
- {
1846
- className: cn(
1847
- "min-w-0 truncate font-semibold",
1848
- classNames?.itemLabel
1849
- ),
1850
- children: item.label
1851
- }
1852
- ),
1853
- hasRenderableNode(item.description) ? /* @__PURE__ */ jsx(
1854
- "span",
1855
- {
1856
- className: cn(
1857
- "mt-0.5 truncate text-xs font-normal",
1858
- classNames?.itemDescription
1859
- ),
1860
- children: item.description
1861
- }
1862
- ) : null
1863
- ]
1864
- }
1865
- ) : null,
1866
- (mobile || !collapsed) && hasRenderableNode(item.meta) ? /* @__PURE__ */ jsx(
1867
- "span",
1868
- {
1869
- className: cn(
1870
- "shrink-0 text-xs text-muted-foreground",
1871
- classNames?.itemMeta
1872
- ),
1873
- children: item.meta
1874
- }
1875
- ) : null,
1876
- (mobile || !collapsed) && hasRenderableNode(item.endContent) ? /* @__PURE__ */ jsx("span", { className: cn("shrink-0", classNames?.itemEndContent), children: item.endContent }) : null
1877
- ]
1928
+ "data-expanded": ariaExpanded === void 0 ? void 0 : String(ariaExpanded),
1929
+ className: entryClassName({
1930
+ item,
1931
+ active,
1932
+ collapsed,
1933
+ mobile,
1934
+ embedded,
1935
+ appearance,
1936
+ classNames,
1937
+ domClassName: domProps.className
1938
+ }),
1939
+ children: /* @__PURE__ */ jsx(
1940
+ EntryContent,
1941
+ {
1942
+ item,
1943
+ collapsed,
1944
+ mobile,
1945
+ classNames,
1946
+ endIndicator
1947
+ }
1948
+ )
1878
1949
  }
1879
1950
  );
1880
1951
  }
1952
+ function EntryPresentation({
1953
+ item,
1954
+ appearance,
1955
+ classNames
1956
+ }) {
1957
+ const domProps = item.domProps ?? {};
1958
+ return /* @__PURE__ */ jsx(
1959
+ "div",
1960
+ {
1961
+ ...domProps,
1962
+ "data-navigation-sidebar-item-id": item.id,
1963
+ "aria-disabled": item.disabled || void 0,
1964
+ title: getNodeTitle(item.label, item.title),
1965
+ className: entryClassName({
1966
+ item,
1967
+ active: false,
1968
+ collapsed: false,
1969
+ mobile: true,
1970
+ embedded: true,
1971
+ appearance,
1972
+ classNames,
1973
+ domClassName: domProps.className
1974
+ }),
1975
+ children: /* @__PURE__ */ jsx(
1976
+ EntryContent,
1977
+ {
1978
+ item,
1979
+ collapsed: false,
1980
+ mobile: true,
1981
+ classNames
1982
+ }
1983
+ )
1984
+ }
1985
+ );
1986
+ }
1987
+ function routeRowClassName({
1988
+ active,
1989
+ appearance
1990
+ }) {
1991
+ return cn(
1992
+ "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",
1993
+ appearance.itemHeight,
1994
+ active ? activeVariantClassName(appearance.activeVariant) : "text-muted-foreground hover:border-border/70 hover:bg-muted/60 hover:text-foreground"
1995
+ );
1996
+ }
1881
1997
  function filterCatalogItems(items, activeFilterId) {
1882
1998
  if (!activeFilterId) return items;
1883
1999
  return items.filter(
@@ -2009,7 +2125,9 @@ function ChildEntry({
2009
2125
  active,
2010
2126
  interactive,
2011
2127
  onSelect,
2012
- classNames
2128
+ appearance,
2129
+ classNames,
2130
+ embedded = false
2013
2131
  }) {
2014
2132
  const Component = interactive ? "button" : "div";
2015
2133
  const domProps = child.domProps ?? {};
@@ -2024,8 +2142,11 @@ function ChildEntry({
2024
2142
  title: getNodeTitle(child.label, child.title),
2025
2143
  disabled: interactive ? child.disabled : void 0,
2026
2144
  className: cn(
2027
- "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",
2028
- active ? "border-primary/35 bg-primary/10 text-primary" : "text-muted-foreground hover:border-border hover:bg-muted/60 hover:text-foreground",
2145
+ "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",
2146
+ appearance.itemHeight,
2147
+ appearance.itemPadding,
2148
+ "pl-11",
2149
+ 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",
2029
2150
  child.disabled && "pointer-events-none opacity-50",
2030
2151
  classNames?.childItem,
2031
2152
  active && classNames?.childItemActive,
@@ -2037,7 +2158,7 @@ function ChildEntry({
2037
2158
  hasRenderableNode(child.icon) ? /* @__PURE__ */ jsx(
2038
2159
  "span",
2039
2160
  {
2040
- className: "flex size-4 shrink-0 items-center justify-center",
2161
+ className: "flex size-5 shrink-0 items-center justify-center [&_svg]:size-full",
2041
2162
  "aria-hidden": "true",
2042
2163
  children: child.icon
2043
2164
  }
@@ -2047,29 +2168,167 @@ function ChildEntry({
2047
2168
  }
2048
2169
  );
2049
2170
  }
2171
+ function RouteActions({
2172
+ itemId,
2173
+ actions,
2174
+ kind,
2175
+ className,
2176
+ mobile = false
2177
+ }) {
2178
+ if (!hasRenderableNode(actions)) return null;
2179
+ return /* @__PURE__ */ jsx(
2180
+ "div",
2181
+ {
2182
+ "data-navigation-sidebar-item-actions": kind === "item" ? itemId : void 0,
2183
+ "data-navigation-sidebar-child-actions": kind === "child" ? itemId : void 0,
2184
+ "data-navigation-sidebar-actions-mobile": mobile ? "true" : void 0,
2185
+ className: cn(
2186
+ "ml-auto flex shrink-0 items-center gap-1 [&>button]:border-0 [&>button]:bg-transparent [&>button]:shadow-none [&>button:hover]:bg-muted",
2187
+ className
2188
+ ),
2189
+ children: actions
2190
+ }
2191
+ );
2192
+ }
2193
+ function isDisclosureRouteItem(item) {
2194
+ return item.interaction === "disclosure";
2195
+ }
2196
+ function validateRouteItems(routeItems) {
2197
+ const itemIds = /* @__PURE__ */ new Set();
2198
+ const childIds = /* @__PURE__ */ new Set();
2199
+ const collapsibleItemIds = /* @__PURE__ */ new Set();
2200
+ for (const item of routeItems) {
2201
+ const itemId = item.id;
2202
+ if (itemIds.has(itemId)) {
2203
+ throw new RangeError(
2204
+ `NavigationSidebar route item ids must be unique; received "${itemId}" more than once.`
2205
+ );
2206
+ }
2207
+ itemIds.add(itemId);
2208
+ const disclosure = isDisclosureRouteItem(item);
2209
+ if (disclosure) {
2210
+ if (!item.children.length) {
2211
+ throw new RangeError(
2212
+ `NavigationSidebar disclosure item "${item.id}" must include at least one child.`
2213
+ );
2214
+ }
2215
+ if (item.href !== void 0 || item.to !== void 0 || item.onClick !== void 0) {
2216
+ throw new TypeError(
2217
+ `NavigationSidebar disclosure item "${itemId}" cannot define href, to, or onClick.`
2218
+ );
2219
+ }
2220
+ collapsibleItemIds.add(item.id);
2221
+ } else if (item.childrenPresentation === "collapsible") {
2222
+ if (!item.children?.length) {
2223
+ throw new RangeError(
2224
+ `NavigationSidebar collapsible route item "${item.id}" must include at least one child.`
2225
+ );
2226
+ }
2227
+ collapsibleItemIds.add(item.id);
2228
+ }
2229
+ for (const child of item.children ?? []) {
2230
+ if (childIds.has(child.id)) {
2231
+ throw new RangeError(
2232
+ `NavigationSidebar route child ids must be unique; received "${child.id}" more than once.`
2233
+ );
2234
+ }
2235
+ childIds.add(child.id);
2236
+ }
2237
+ }
2238
+ return collapsibleItemIds;
2239
+ }
2240
+ function RouteChildrenToggle({
2241
+ item,
2242
+ expanded,
2243
+ controls,
2244
+ label,
2245
+ onToggle,
2246
+ classNames
2247
+ }) {
2248
+ const itemLabel = typeof item.label === "string" ? item.label : item.id;
2249
+ return /* @__PURE__ */ jsx(
2250
+ "button",
2251
+ {
2252
+ type: "button",
2253
+ disabled: item.disabled,
2254
+ "aria-expanded": expanded,
2255
+ "aria-controls": controls,
2256
+ "aria-label": `${label}: ${itemLabel}`,
2257
+ title: label,
2258
+ "data-navigation-sidebar-disclosure-trigger": item.id,
2259
+ className: cn(
2260
+ "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",
2261
+ classNames?.childMenuTrigger
2262
+ ),
2263
+ onClick: onToggle,
2264
+ children: /* @__PURE__ */ jsx(DisclosureIcon, { expanded })
2265
+ }
2266
+ );
2267
+ }
2050
2268
  function CollapsedChildMenu({
2051
2269
  item,
2052
2270
  activeChildId,
2053
2271
  label,
2054
2272
  onChildSelect,
2055
- classNames
2273
+ classNames,
2274
+ appearance,
2275
+ triggerVariant = "submenu",
2276
+ mobile = false
2056
2277
  }) {
2057
2278
  const enabledChildren = (item.children ?? []).filter(
2058
2279
  (child) => !child.disabled
2059
2280
  );
2060
- if (enabledChildren.length === 0) return null;
2281
+ if (!item.children?.length) return null;
2282
+ if (triggerVariant === "submenu" && (!onChildSelect || enabledChildren.length === 0))
2283
+ return null;
2284
+ const itemLabel = typeof item.label === "string" ? item.label : item.id;
2285
+ const itemTriggerDisabled = Boolean(item.disabled) || !onChildSelect || enabledChildren.length === 0;
2286
+ const domProps = item.domProps ?? {};
2061
2287
  return /* @__PURE__ */ jsxs(DropdownMenu.Root, { children: [
2062
- /* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
2288
+ /* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, children: triggerVariant === "item" && appearance ? /* @__PURE__ */ jsx(
2063
2289
  "button",
2064
2290
  {
2291
+ ...domProps,
2065
2292
  type: "button",
2293
+ disabled: itemTriggerDisabled,
2294
+ "aria-label": `${label}: ${itemLabel}`,
2295
+ title: label,
2296
+ "data-navigation-sidebar-item-id": item.id,
2297
+ "data-navigation-sidebar-disclosure-menu": "true",
2298
+ className: entryClassName({
2299
+ item,
2300
+ active: false,
2301
+ collapsed: !mobile,
2302
+ mobile,
2303
+ appearance,
2304
+ classNames,
2305
+ domClassName: domProps.className
2306
+ }),
2307
+ children: /* @__PURE__ */ jsx(
2308
+ EntryContent,
2309
+ {
2310
+ item,
2311
+ collapsed: !mobile,
2312
+ mobile,
2313
+ classNames,
2314
+ endIndicator: mobile ? /* @__PURE__ */ jsx(DisclosureIcon, { expanded: false }) : void 0
2315
+ }
2316
+ )
2317
+ }
2318
+ ) : /* @__PURE__ */ jsx(
2319
+ "button",
2320
+ {
2321
+ type: "button",
2322
+ disabled: triggerVariant === "disclosure" ? itemTriggerDisabled : void 0,
2323
+ "data-navigation-sidebar-disclosure-trigger": triggerVariant === "disclosure" ? item.id : void 0,
2066
2324
  className: cn(
2067
- "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",
2325
+ "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",
2326
+ triggerVariant === "disclosure" ? "w-7" : "w-6",
2068
2327
  classNames?.childMenuTrigger
2069
2328
  ),
2070
- "aria-label": `${label}: ${typeof item.label === "string" ? item.label : item.id}`,
2329
+ "aria-label": `${label}: ${itemLabel}`,
2071
2330
  title: label,
2072
- children: /* @__PURE__ */ jsx(MoreIcon2, {})
2331
+ children: triggerVariant === "disclosure" ? /* @__PURE__ */ jsx(DisclosureIcon, { expanded: false }) : /* @__PURE__ */ jsx(MoreIcon2, {})
2073
2332
  }
2074
2333
  ) }),
2075
2334
  /* @__PURE__ */ jsx(DropdownMenu.Portal, { children: /* @__PURE__ */ jsx(
@@ -2092,7 +2351,7 @@ function CollapsedChildMenu({
2092
2351
  "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",
2093
2352
  child.id === activeChildId && "bg-accent text-accent-foreground"
2094
2353
  ),
2095
- onSelect: () => onChildSelect(child, item),
2354
+ onSelect: () => onChildSelect?.(child, item),
2096
2355
  children: [
2097
2356
  hasRenderableNode(child.icon) ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: child.icon }) : null,
2098
2357
  /* @__PURE__ */ jsx("span", { className: "truncate", children: child.label })
@@ -2113,22 +2372,81 @@ function RouteContent({
2113
2372
  routeItems,
2114
2373
  activeItemId,
2115
2374
  activeChildId,
2375
+ expandedItemIds,
2376
+ defaultExpandedItemIds = [],
2116
2377
  itemAs,
2117
2378
  onItemSelect,
2118
2379
  onChildSelect,
2380
+ onExpandedItemIdsChange,
2119
2381
  collapsedChildMenuLabel = "Open submenu",
2382
+ disclosureLabel = "Toggle submenu",
2120
2383
  ariaLabel,
2121
2384
  classNames
2122
2385
  } = props;
2123
- return /* @__PURE__ */ jsx("nav", { "aria-label": ariaLabel, children: /* @__PURE__ */ jsx("ul", { className: cn("grid list-none gap-2 p-0", classNames?.list), children: routeItems.map((item) => {
2124
- const active = item.id === activeItemId;
2386
+ const disclosureId = useId();
2387
+ const collapsibleItemIds = useMemo(
2388
+ () => validateRouteItems(routeItems),
2389
+ [routeItems]
2390
+ );
2391
+ const [internalExpandedItemIds, setInternalExpandedItemIds] = useState(
2392
+ () => defaultExpandedItemIds
2393
+ );
2394
+ const isExpandedControlled = expandedItemIds !== void 0;
2395
+ const resolvedExpandedItemIds = expandedItemIds ?? internalExpandedItemIds;
2396
+ const normalizedExpandedItemIds = useMemo(
2397
+ () => resolvedExpandedItemIds.filter(
2398
+ (itemId) => collapsibleItemIds.has(itemId)
2399
+ ),
2400
+ [collapsibleItemIds, resolvedExpandedItemIds]
2401
+ );
2402
+ const expandedItemIdSet = useMemo(
2403
+ () => new Set(normalizedExpandedItemIds),
2404
+ [normalizedExpandedItemIds]
2405
+ );
2406
+ const toggleItemExpanded = useCallback(
2407
+ (item) => {
2408
+ if (item.disabled || !collapsibleItemIds.has(item.id)) return;
2409
+ const nextExpandedItemIds = expandedItemIdSet.has(item.id) ? normalizedExpandedItemIds.filter((itemId) => itemId !== item.id) : [...normalizedExpandedItemIds, item.id];
2410
+ if (!isExpandedControlled) {
2411
+ setInternalExpandedItemIds(nextExpandedItemIds);
2412
+ }
2413
+ onExpandedItemIdsChange?.(nextExpandedItemIds);
2414
+ },
2415
+ [
2416
+ collapsibleItemIds,
2417
+ expandedItemIdSet,
2418
+ isExpandedControlled,
2419
+ normalizedExpandedItemIds,
2420
+ onExpandedItemIdsChange
2421
+ ]
2422
+ );
2423
+ return /* @__PURE__ */ jsx("nav", { "aria-label": ariaLabel, children: /* @__PURE__ */ jsx("ul", { className: cn("grid list-none gap-2 p-0", classNames?.list), children: routeItems.map((item, itemIndex) => {
2424
+ const disclosure = isDisclosureRouteItem(item);
2425
+ const active = !disclosure && item.id === activeItemId;
2426
+ const hasChildren = Boolean(item.children?.length);
2427
+ const childrenCollapsible = collapsibleItemIds.has(item.id);
2428
+ const childrenExpanded = !childrenCollapsible || expandedItemIdSet.has(item.id);
2429
+ const showItemActions = !collapsed && hasRenderableNode(item.actions);
2430
+ const compoundItemRow = collapsed && hasChildren && Boolean(onChildSelect) || !collapsed && childrenCollapsible && !disclosure || showItemActions;
2431
+ const childListId = `${disclosureId}-route-children-${itemIndex}`;
2125
2432
  return /* @__PURE__ */ jsxs("li", { className: "grid min-w-0 gap-1", children: [
2126
- /* @__PURE__ */ jsxs(
2433
+ collapsed && disclosure ? /* @__PURE__ */ jsx(
2434
+ CollapsedChildMenu,
2435
+ {
2436
+ item,
2437
+ activeChildId,
2438
+ label: collapsedChildMenuLabel,
2439
+ onChildSelect,
2440
+ classNames,
2441
+ appearance,
2442
+ triggerVariant: "item"
2443
+ }
2444
+ ) : /* @__PURE__ */ jsxs(
2127
2445
  "div",
2128
2446
  {
2129
- className: cn(
2130
- collapsed && Boolean(item.children?.length) && Boolean(onChildSelect) && "flex min-w-0 items-center gap-0.5"
2131
- ),
2447
+ "data-navigation-sidebar-item-row": compoundItemRow ? item.id : void 0,
2448
+ "data-active": compoundItemRow ? String(active) : void 0,
2449
+ className: compoundItemRow ? routeRowClassName({ active, appearance }) : void 0,
2132
2450
  children: [
2133
2451
  /* @__PURE__ */ jsx(
2134
2452
  EntryButton,
@@ -2136,12 +2454,25 @@ function RouteContent({
2136
2454
  item,
2137
2455
  active,
2138
2456
  collapsed,
2139
- itemAs,
2457
+ embedded: compoundItemRow,
2458
+ itemAs: disclosure ? "button" : itemAs,
2140
2459
  appearance,
2141
2460
  classNames,
2142
- onSelect: onItemSelect ? () => onItemSelect(item) : void 0
2461
+ onSelect: disclosure ? () => toggleItemExpanded(item) : onItemSelect ? () => onItemSelect(item) : void 0,
2462
+ ariaControls: !collapsed && childrenCollapsible && disclosure ? childListId : void 0,
2463
+ ariaExpanded: !collapsed && childrenCollapsible && disclosure ? childrenExpanded : void 0,
2464
+ endIndicator: !collapsed && disclosure && !showItemActions ? /* @__PURE__ */ jsx(DisclosureIcon, { expanded: childrenExpanded }) : void 0
2143
2465
  }
2144
2466
  ),
2467
+ showItemActions ? /* @__PURE__ */ jsx(
2468
+ RouteActions,
2469
+ {
2470
+ itemId: item.id,
2471
+ actions: item.actions,
2472
+ kind: "item",
2473
+ className: classNames?.itemActions
2474
+ }
2475
+ ) : null,
2145
2476
  collapsed && onChildSelect ? /* @__PURE__ */ jsx(
2146
2477
  CollapsedChildMenu,
2147
2478
  {
@@ -2151,28 +2482,71 @@ function RouteContent({
2151
2482
  onChildSelect,
2152
2483
  classNames
2153
2484
  }
2485
+ ) : null,
2486
+ !collapsed && childrenCollapsible && (!disclosure || showItemActions) ? /* @__PURE__ */ jsx(
2487
+ RouteChildrenToggle,
2488
+ {
2489
+ item,
2490
+ expanded: childrenExpanded,
2491
+ controls: childListId,
2492
+ label: disclosureLabel,
2493
+ onToggle: () => toggleItemExpanded(item),
2494
+ classNames
2495
+ }
2154
2496
  ) : null
2155
2497
  ]
2156
2498
  }
2157
2499
  ),
2158
- !collapsed && item.children?.length ? /* @__PURE__ */ jsx(
2500
+ !collapsed && hasChildren ? /* @__PURE__ */ jsx(
2159
2501
  "ul",
2160
2502
  {
2503
+ id: childListId,
2504
+ hidden: !childrenExpanded,
2505
+ style: childrenExpanded ? void 0 : { display: "none" },
2161
2506
  className: cn(
2162
2507
  "ml-5 grid list-none gap-0.5 border-l border-border/70 py-1 pl-3",
2163
2508
  classNames?.childList
2164
2509
  ),
2165
- children: item.children.map((child) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
2166
- ChildEntry,
2167
- {
2168
- child,
2169
- parent: item,
2170
- active: child.id === activeChildId,
2171
- interactive: Boolean(onChildSelect),
2172
- onSelect: onChildSelect,
2173
- classNames
2174
- }
2175
- ) }, child.id))
2510
+ children: item.children?.map((child) => {
2511
+ const hasChildActions = hasRenderableNode(child.actions);
2512
+ const childActive = child.id === activeChildId;
2513
+ const childEntry = /* @__PURE__ */ jsx(
2514
+ ChildEntry,
2515
+ {
2516
+ child,
2517
+ parent: item,
2518
+ active: childActive,
2519
+ interactive: Boolean(onChildSelect),
2520
+ onSelect: onChildSelect,
2521
+ appearance,
2522
+ classNames,
2523
+ embedded: hasChildActions
2524
+ }
2525
+ );
2526
+ return /* @__PURE__ */ jsx("li", { children: hasChildActions ? /* @__PURE__ */ jsxs(
2527
+ "div",
2528
+ {
2529
+ "data-navigation-sidebar-child-row": child.id,
2530
+ "data-active": String(childActive),
2531
+ className: routeRowClassName({
2532
+ active: childActive,
2533
+ appearance
2534
+ }),
2535
+ children: [
2536
+ childEntry,
2537
+ /* @__PURE__ */ jsx(
2538
+ RouteActions,
2539
+ {
2540
+ itemId: child.id,
2541
+ actions: child.actions,
2542
+ kind: "child",
2543
+ className: classNames?.childActions
2544
+ }
2545
+ )
2546
+ ]
2547
+ }
2548
+ ) : childEntry }, child.id);
2549
+ })
2176
2550
  }
2177
2551
  ) : null
2178
2552
  ] }, item.id);
@@ -2206,23 +2580,116 @@ function MobileNavigation({
2206
2580
  classNames?.mobileNav,
2207
2581
  props.mobile.className
2208
2582
  ),
2209
- children: items.map((item) => /* @__PURE__ */ jsx(
2210
- EntryButton,
2211
- {
2212
- item,
2213
- active: props.kind === "catalog" ? item.active ?? item.id === props.activeItemId : item.id === props.activeItemId,
2214
- collapsed: false,
2215
- itemAs: props.itemAs,
2216
- appearance,
2217
- classNames: {
2218
- ...classNames,
2219
- mobileItem: cn(classNames?.mobileItem, props.mobile?.itemClassName)
2583
+ children: items.map((item) => {
2584
+ const mobileClassNames = {
2585
+ ...classNames,
2586
+ mobileItem: cn(
2587
+ classNames?.mobileItem,
2588
+ props.mobile?.itemClassName
2589
+ )
2590
+ };
2591
+ const routeItem = props.kind === "route" ? item : void 0;
2592
+ const showRouteActions = routeItem !== void 0 && hasRenderableNode(routeItem.actions);
2593
+ if (props.kind === "route" && isDisclosureRouteItem(item)) {
2594
+ return showRouteActions ? /* @__PURE__ */ jsxs(
2595
+ "div",
2596
+ {
2597
+ "data-navigation-sidebar-item-row": item.id,
2598
+ "data-active": "false",
2599
+ className: cn(
2600
+ routeRowClassName({ active: false, appearance }),
2601
+ "shrink-0"
2602
+ ),
2603
+ children: [
2604
+ /* @__PURE__ */ jsx(
2605
+ EntryPresentation,
2606
+ {
2607
+ item,
2608
+ appearance,
2609
+ classNames: mobileClassNames
2610
+ }
2611
+ ),
2612
+ /* @__PURE__ */ jsx(
2613
+ RouteActions,
2614
+ {
2615
+ itemId: item.id,
2616
+ actions: item.actions,
2617
+ kind: "item",
2618
+ className: classNames?.itemActions,
2619
+ mobile: true
2620
+ }
2621
+ ),
2622
+ /* @__PURE__ */ jsx(
2623
+ CollapsedChildMenu,
2624
+ {
2625
+ item,
2626
+ activeChildId: props.activeChildId,
2627
+ label: props.collapsedChildMenuLabel ?? "Open submenu",
2628
+ onChildSelect: props.onChildSelect,
2629
+ classNames: mobileClassNames,
2630
+ appearance,
2631
+ triggerVariant: "disclosure",
2632
+ mobile: true
2633
+ }
2634
+ )
2635
+ ]
2636
+ },
2637
+ item.id
2638
+ ) : /* @__PURE__ */ jsx(Fragment$1, { children: /* @__PURE__ */ jsx(
2639
+ CollapsedChildMenu,
2640
+ {
2641
+ item,
2642
+ activeChildId: props.activeChildId,
2643
+ label: props.collapsedChildMenuLabel ?? "Open submenu",
2644
+ onChildSelect: props.onChildSelect,
2645
+ classNames: mobileClassNames,
2646
+ appearance,
2647
+ triggerVariant: "item",
2648
+ mobile: true
2649
+ }
2650
+ ) }, item.id);
2651
+ }
2652
+ const active = props.kind === "catalog" ? item.active ?? item.id === props.activeItemId : item.id === props.activeItemId;
2653
+ const entryButton = /* @__PURE__ */ jsx(
2654
+ EntryButton,
2655
+ {
2656
+ item,
2657
+ active,
2658
+ collapsed: false,
2659
+ embedded: showRouteActions,
2660
+ itemAs: props.itemAs,
2661
+ appearance,
2662
+ classNames: mobileClassNames,
2663
+ mobile: true,
2664
+ onSelect: props.kind === "route" && props.onItemSelect ? () => props.onItemSelect?.(item) : void 0
2665
+ }
2666
+ );
2667
+ return showRouteActions && routeItem ? /* @__PURE__ */ jsxs(
2668
+ "div",
2669
+ {
2670
+ "data-navigation-sidebar-item-row": item.id,
2671
+ "data-active": String(active),
2672
+ className: cn(
2673
+ routeRowClassName({ active, appearance }),
2674
+ "shrink-0"
2675
+ ),
2676
+ children: [
2677
+ entryButton,
2678
+ /* @__PURE__ */ jsx(
2679
+ RouteActions,
2680
+ {
2681
+ itemId: routeItem.id,
2682
+ actions: routeItem.actions,
2683
+ kind: "item",
2684
+ className: classNames?.itemActions,
2685
+ mobile: true
2686
+ }
2687
+ )
2688
+ ]
2220
2689
  },
2221
- mobile: true,
2222
- onSelect: props.kind === "route" && props.onItemSelect ? () => props.onItemSelect?.(item) : void 0
2223
- },
2224
- item.id
2225
- ))
2690
+ item.id
2691
+ ) : /* @__PURE__ */ jsx(Fragment$1, { children: entryButton }, item.id);
2692
+ })
2226
2693
  }
2227
2694
  );
2228
2695
  }