@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.
@@ -1636,6 +1636,30 @@ function MoreIcon2() {
1636
1636
  }
1637
1637
  );
1638
1638
  }
1639
+ function DisclosureIcon({ expanded }) {
1640
+ return /* @__PURE__ */ jsxRuntime.jsx(
1641
+ "svg",
1642
+ {
1643
+ "aria-hidden": "true",
1644
+ viewBox: "0 0 20 20",
1645
+ className: cn(
1646
+ "h-4 w-4 transition-transform motion-reduce:transition-none",
1647
+ expanded && "rotate-180"
1648
+ ),
1649
+ fill: "none",
1650
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1651
+ "path",
1652
+ {
1653
+ d: "m6 8 4 4 4-4",
1654
+ stroke: "currentColor",
1655
+ strokeWidth: "1.6",
1656
+ strokeLinecap: "round",
1657
+ strokeLinejoin: "round"
1658
+ }
1659
+ )
1660
+ }
1661
+ );
1662
+ }
1639
1663
  function assignRef(ref, node) {
1640
1664
  if (typeof ref === "function") {
1641
1665
  ref(node);
@@ -1791,6 +1815,94 @@ function resolveNavigationTreeClassNames(classNames) {
1791
1815
  )
1792
1816
  };
1793
1817
  }
1818
+ function entryClassName({
1819
+ item,
1820
+ active,
1821
+ collapsed,
1822
+ mobile,
1823
+ embedded = false,
1824
+ appearance,
1825
+ classNames,
1826
+ domClassName
1827
+ }) {
1828
+ return cn(
1829
+ "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",
1830
+ appearance.itemHeight,
1831
+ appearance.itemPadding,
1832
+ 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",
1833
+ collapsed && !mobile && "justify-center px-0",
1834
+ item.disabled && "pointer-events-none opacity-50",
1835
+ classNames?.item,
1836
+ active && classNames?.itemActive,
1837
+ mobile && classNames?.mobileItem,
1838
+ domClassName,
1839
+ item.className
1840
+ );
1841
+ }
1842
+ function EntryContent({
1843
+ item,
1844
+ collapsed,
1845
+ mobile,
1846
+ classNames,
1847
+ endIndicator
1848
+ }) {
1849
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1850
+ hasRenderableNode(item.icon) ? /* @__PURE__ */ jsxRuntime.jsx(
1851
+ "span",
1852
+ {
1853
+ className: cn(
1854
+ "flex size-5 shrink-0 items-center justify-center [&_svg]:size-full",
1855
+ classNames?.itemIcon
1856
+ ),
1857
+ "aria-hidden": "true",
1858
+ children: item.icon
1859
+ }
1860
+ ) : null,
1861
+ mobile || !collapsed ? /* @__PURE__ */ jsxRuntime.jsxs(
1862
+ "span",
1863
+ {
1864
+ className: cn(
1865
+ "flex min-w-0 flex-1 flex-col",
1866
+ classNames?.itemContent
1867
+ ),
1868
+ children: [
1869
+ /* @__PURE__ */ jsxRuntime.jsx(
1870
+ "span",
1871
+ {
1872
+ className: cn(
1873
+ "min-w-0 truncate font-semibold",
1874
+ classNames?.itemLabel
1875
+ ),
1876
+ children: item.label
1877
+ }
1878
+ ),
1879
+ hasRenderableNode(item.description) ? /* @__PURE__ */ jsxRuntime.jsx(
1880
+ "span",
1881
+ {
1882
+ className: cn(
1883
+ "mt-0.5 truncate text-xs font-normal",
1884
+ classNames?.itemDescription
1885
+ ),
1886
+ children: item.description
1887
+ }
1888
+ ) : null
1889
+ ]
1890
+ }
1891
+ ) : null,
1892
+ (mobile || !collapsed) && hasRenderableNode(item.meta) ? /* @__PURE__ */ jsxRuntime.jsx(
1893
+ "span",
1894
+ {
1895
+ className: cn(
1896
+ "shrink-0 text-xs text-muted-foreground",
1897
+ classNames?.itemMeta
1898
+ ),
1899
+ children: item.meta
1900
+ }
1901
+ ) : null,
1902
+ (mobile || !collapsed) && hasRenderableNode(item.endContent) ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("shrink-0", classNames?.itemEndContent), children: item.endContent }) : null,
1903
+ (mobile || !collapsed) && hasRenderableNode(endIndicator) ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-center justify-center", children: endIndicator }) : null
1904
+ ] });
1905
+ }
1794
1906
  function EntryButton({
1795
1907
  item,
1796
1908
  active,
@@ -1799,7 +1911,11 @@ function EntryButton({
1799
1911
  appearance,
1800
1912
  classNames,
1801
1913
  mobile = false,
1802
- onSelect
1914
+ embedded = false,
1915
+ onSelect,
1916
+ ariaControls,
1917
+ ariaExpanded,
1918
+ endIndicator
1803
1919
  }) {
1804
1920
  const ItemComponent = itemAs ?? (item.href ? "a" : "button");
1805
1921
  const title = getNodeTitle(item.label, item.title);
@@ -1816,90 +1932,90 @@ function EntryButton({
1816
1932
  ...domProps,
1817
1933
  ...item.to ? { to: item.to } : {},
1818
1934
  ...item.href ? { href: item.href } : {},
1819
- ...ItemComponent === "button" ? { type: "button" } : {},
1935
+ ...ItemComponent === "button" ? { type: "button", disabled: item.disabled } : {},
1820
1936
  title,
1821
1937
  "aria-current": active ? "page" : void 0,
1822
1938
  "aria-disabled": item.disabled || void 0,
1939
+ "aria-controls": ariaControls,
1940
+ "aria-expanded": ariaExpanded,
1823
1941
  tabIndex: item.disabled ? -1 : domProps.tabIndex,
1824
1942
  onClick: handleClick
1825
1943
  };
1826
- return /* @__PURE__ */ jsxRuntime.jsxs(
1944
+ return /* @__PURE__ */ jsxRuntime.jsx(
1827
1945
  ItemComponent,
1828
1946
  {
1829
1947
  ...componentProps,
1830
1948
  "data-navigation-sidebar-item-id": item.id,
1831
1949
  "data-active": active ? "true" : "false",
1832
- className: cn(
1833
- "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",
1834
- appearance.itemHeight,
1835
- appearance.itemPadding,
1836
- active ? activeVariantClassName(appearance.activeVariant) : "text-muted-foreground hover:border-border/70 hover:bg-muted/60 hover:text-foreground",
1837
- collapsed && !mobile && "justify-center px-0",
1838
- item.disabled && "pointer-events-none opacity-50",
1839
- classNames?.item,
1840
- active && classNames?.itemActive,
1841
- mobile && classNames?.mobileItem,
1842
- domProps.className,
1843
- item.className
1844
- ),
1845
- children: [
1846
- hasRenderableNode(item.icon) ? /* @__PURE__ */ jsxRuntime.jsx(
1847
- "span",
1848
- {
1849
- className: cn(
1850
- "flex size-5 shrink-0 items-center justify-center [&_svg]:size-full",
1851
- classNames?.itemIcon
1852
- ),
1853
- "aria-hidden": "true",
1854
- children: item.icon
1855
- }
1856
- ) : null,
1857
- mobile || !collapsed ? /* @__PURE__ */ jsxRuntime.jsxs(
1858
- "span",
1859
- {
1860
- className: cn(
1861
- "flex min-w-0 flex-1 flex-col",
1862
- classNames?.itemContent
1863
- ),
1864
- children: [
1865
- /* @__PURE__ */ jsxRuntime.jsx(
1866
- "span",
1867
- {
1868
- className: cn(
1869
- "min-w-0 truncate font-semibold",
1870
- classNames?.itemLabel
1871
- ),
1872
- children: item.label
1873
- }
1874
- ),
1875
- hasRenderableNode(item.description) ? /* @__PURE__ */ jsxRuntime.jsx(
1876
- "span",
1877
- {
1878
- className: cn(
1879
- "mt-0.5 truncate text-xs font-normal",
1880
- classNames?.itemDescription
1881
- ),
1882
- children: item.description
1883
- }
1884
- ) : null
1885
- ]
1886
- }
1887
- ) : null,
1888
- (mobile || !collapsed) && hasRenderableNode(item.meta) ? /* @__PURE__ */ jsxRuntime.jsx(
1889
- "span",
1890
- {
1891
- className: cn(
1892
- "shrink-0 text-xs text-muted-foreground",
1893
- classNames?.itemMeta
1894
- ),
1895
- children: item.meta
1896
- }
1897
- ) : null,
1898
- (mobile || !collapsed) && hasRenderableNode(item.endContent) ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("shrink-0", classNames?.itemEndContent), children: item.endContent }) : null
1899
- ]
1950
+ "data-expanded": ariaExpanded === void 0 ? void 0 : String(ariaExpanded),
1951
+ className: entryClassName({
1952
+ item,
1953
+ active,
1954
+ collapsed,
1955
+ mobile,
1956
+ embedded,
1957
+ appearance,
1958
+ classNames,
1959
+ domClassName: domProps.className
1960
+ }),
1961
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1962
+ EntryContent,
1963
+ {
1964
+ item,
1965
+ collapsed,
1966
+ mobile,
1967
+ classNames,
1968
+ endIndicator
1969
+ }
1970
+ )
1900
1971
  }
1901
1972
  );
1902
1973
  }
1974
+ function EntryPresentation({
1975
+ item,
1976
+ appearance,
1977
+ classNames
1978
+ }) {
1979
+ const domProps = item.domProps ?? {};
1980
+ return /* @__PURE__ */ jsxRuntime.jsx(
1981
+ "div",
1982
+ {
1983
+ ...domProps,
1984
+ "data-navigation-sidebar-item-id": item.id,
1985
+ "aria-disabled": item.disabled || void 0,
1986
+ title: getNodeTitle(item.label, item.title),
1987
+ className: entryClassName({
1988
+ item,
1989
+ active: false,
1990
+ collapsed: false,
1991
+ mobile: true,
1992
+ embedded: true,
1993
+ appearance,
1994
+ classNames,
1995
+ domClassName: domProps.className
1996
+ }),
1997
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1998
+ EntryContent,
1999
+ {
2000
+ item,
2001
+ collapsed: false,
2002
+ mobile: true,
2003
+ classNames
2004
+ }
2005
+ )
2006
+ }
2007
+ );
2008
+ }
2009
+ function routeRowClassName({
2010
+ active,
2011
+ appearance
2012
+ }) {
2013
+ return cn(
2014
+ "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",
2015
+ appearance.itemHeight,
2016
+ active ? activeVariantClassName(appearance.activeVariant) : "text-muted-foreground hover:border-border/70 hover:bg-muted/60 hover:text-foreground"
2017
+ );
2018
+ }
1903
2019
  function filterCatalogItems(items, activeFilterId) {
1904
2020
  if (!activeFilterId) return items;
1905
2021
  return items.filter(
@@ -2031,7 +2147,9 @@ function ChildEntry({
2031
2147
  active,
2032
2148
  interactive,
2033
2149
  onSelect,
2034
- classNames
2150
+ appearance,
2151
+ classNames,
2152
+ embedded = false
2035
2153
  }) {
2036
2154
  const Component = interactive ? "button" : "div";
2037
2155
  const domProps = child.domProps ?? {};
@@ -2046,8 +2164,11 @@ function ChildEntry({
2046
2164
  title: getNodeTitle(child.label, child.title),
2047
2165
  disabled: interactive ? child.disabled : void 0,
2048
2166
  className: cn(
2049
- "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",
2050
- active ? "border-primary/35 bg-primary/10 text-primary" : "text-muted-foreground hover:border-border hover:bg-muted/60 hover:text-foreground",
2167
+ "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",
2168
+ appearance.itemHeight,
2169
+ appearance.itemPadding,
2170
+ "pl-11",
2171
+ 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",
2051
2172
  child.disabled && "pointer-events-none opacity-50",
2052
2173
  classNames?.childItem,
2053
2174
  active && classNames?.childItemActive,
@@ -2059,7 +2180,7 @@ function ChildEntry({
2059
2180
  hasRenderableNode(child.icon) ? /* @__PURE__ */ jsxRuntime.jsx(
2060
2181
  "span",
2061
2182
  {
2062
- className: "flex size-4 shrink-0 items-center justify-center",
2183
+ className: "flex size-5 shrink-0 items-center justify-center [&_svg]:size-full",
2063
2184
  "aria-hidden": "true",
2064
2185
  children: child.icon
2065
2186
  }
@@ -2069,29 +2190,167 @@ function ChildEntry({
2069
2190
  }
2070
2191
  );
2071
2192
  }
2193
+ function RouteActions({
2194
+ itemId,
2195
+ actions,
2196
+ kind,
2197
+ className,
2198
+ mobile = false
2199
+ }) {
2200
+ if (!hasRenderableNode(actions)) return null;
2201
+ return /* @__PURE__ */ jsxRuntime.jsx(
2202
+ "div",
2203
+ {
2204
+ "data-navigation-sidebar-item-actions": kind === "item" ? itemId : void 0,
2205
+ "data-navigation-sidebar-child-actions": kind === "child" ? itemId : void 0,
2206
+ "data-navigation-sidebar-actions-mobile": mobile ? "true" : void 0,
2207
+ className: cn(
2208
+ "ml-auto flex shrink-0 items-center gap-1 [&>button]:border-0 [&>button]:bg-transparent [&>button]:shadow-none [&>button:hover]:bg-muted",
2209
+ className
2210
+ ),
2211
+ children: actions
2212
+ }
2213
+ );
2214
+ }
2215
+ function isDisclosureRouteItem(item) {
2216
+ return item.interaction === "disclosure";
2217
+ }
2218
+ function validateRouteItems(routeItems) {
2219
+ const itemIds = /* @__PURE__ */ new Set();
2220
+ const childIds = /* @__PURE__ */ new Set();
2221
+ const collapsibleItemIds = /* @__PURE__ */ new Set();
2222
+ for (const item of routeItems) {
2223
+ const itemId = item.id;
2224
+ if (itemIds.has(itemId)) {
2225
+ throw new RangeError(
2226
+ `NavigationSidebar route item ids must be unique; received "${itemId}" more than once.`
2227
+ );
2228
+ }
2229
+ itemIds.add(itemId);
2230
+ const disclosure = isDisclosureRouteItem(item);
2231
+ if (disclosure) {
2232
+ if (!item.children.length) {
2233
+ throw new RangeError(
2234
+ `NavigationSidebar disclosure item "${item.id}" must include at least one child.`
2235
+ );
2236
+ }
2237
+ if (item.href !== void 0 || item.to !== void 0 || item.onClick !== void 0) {
2238
+ throw new TypeError(
2239
+ `NavigationSidebar disclosure item "${itemId}" cannot define href, to, or onClick.`
2240
+ );
2241
+ }
2242
+ collapsibleItemIds.add(item.id);
2243
+ } else if (item.childrenPresentation === "collapsible") {
2244
+ if (!item.children?.length) {
2245
+ throw new RangeError(
2246
+ `NavigationSidebar collapsible route item "${item.id}" must include at least one child.`
2247
+ );
2248
+ }
2249
+ collapsibleItemIds.add(item.id);
2250
+ }
2251
+ for (const child of item.children ?? []) {
2252
+ if (childIds.has(child.id)) {
2253
+ throw new RangeError(
2254
+ `NavigationSidebar route child ids must be unique; received "${child.id}" more than once.`
2255
+ );
2256
+ }
2257
+ childIds.add(child.id);
2258
+ }
2259
+ }
2260
+ return collapsibleItemIds;
2261
+ }
2262
+ function RouteChildrenToggle({
2263
+ item,
2264
+ expanded,
2265
+ controls,
2266
+ label,
2267
+ onToggle,
2268
+ classNames
2269
+ }) {
2270
+ const itemLabel = typeof item.label === "string" ? item.label : item.id;
2271
+ return /* @__PURE__ */ jsxRuntime.jsx(
2272
+ "button",
2273
+ {
2274
+ type: "button",
2275
+ disabled: item.disabled,
2276
+ "aria-expanded": expanded,
2277
+ "aria-controls": controls,
2278
+ "aria-label": `${label}: ${itemLabel}`,
2279
+ title: label,
2280
+ "data-navigation-sidebar-disclosure-trigger": item.id,
2281
+ className: cn(
2282
+ "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",
2283
+ classNames?.childMenuTrigger
2284
+ ),
2285
+ onClick: onToggle,
2286
+ children: /* @__PURE__ */ jsxRuntime.jsx(DisclosureIcon, { expanded })
2287
+ }
2288
+ );
2289
+ }
2072
2290
  function CollapsedChildMenu({
2073
2291
  item,
2074
2292
  activeChildId,
2075
2293
  label,
2076
2294
  onChildSelect,
2077
- classNames
2295
+ classNames,
2296
+ appearance,
2297
+ triggerVariant = "submenu",
2298
+ mobile = false
2078
2299
  }) {
2079
2300
  const enabledChildren = (item.children ?? []).filter(
2080
2301
  (child) => !child.disabled
2081
2302
  );
2082
- if (enabledChildren.length === 0) return null;
2303
+ if (!item.children?.length) return null;
2304
+ if (triggerVariant === "submenu" && (!onChildSelect || enabledChildren.length === 0))
2305
+ return null;
2306
+ const itemLabel = typeof item.label === "string" ? item.label : item.id;
2307
+ const itemTriggerDisabled = Boolean(item.disabled) || !onChildSelect || enabledChildren.length === 0;
2308
+ const domProps = item.domProps ?? {};
2083
2309
  return /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu__namespace.Root, { children: [
2084
- /* @__PURE__ */ jsxRuntime.jsx(DropdownMenu__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
2310
+ /* @__PURE__ */ jsxRuntime.jsx(DropdownMenu__namespace.Trigger, { asChild: true, children: triggerVariant === "item" && appearance ? /* @__PURE__ */ jsxRuntime.jsx(
2085
2311
  "button",
2086
2312
  {
2313
+ ...domProps,
2087
2314
  type: "button",
2315
+ disabled: itemTriggerDisabled,
2316
+ "aria-label": `${label}: ${itemLabel}`,
2317
+ title: label,
2318
+ "data-navigation-sidebar-item-id": item.id,
2319
+ "data-navigation-sidebar-disclosure-menu": "true",
2320
+ className: entryClassName({
2321
+ item,
2322
+ active: false,
2323
+ collapsed: !mobile,
2324
+ mobile,
2325
+ appearance,
2326
+ classNames,
2327
+ domClassName: domProps.className
2328
+ }),
2329
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2330
+ EntryContent,
2331
+ {
2332
+ item,
2333
+ collapsed: !mobile,
2334
+ mobile,
2335
+ classNames,
2336
+ endIndicator: mobile ? /* @__PURE__ */ jsxRuntime.jsx(DisclosureIcon, { expanded: false }) : void 0
2337
+ }
2338
+ )
2339
+ }
2340
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
2341
+ "button",
2342
+ {
2343
+ type: "button",
2344
+ disabled: triggerVariant === "disclosure" ? itemTriggerDisabled : void 0,
2345
+ "data-navigation-sidebar-disclosure-trigger": triggerVariant === "disclosure" ? item.id : void 0,
2088
2346
  className: cn(
2089
- "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",
2347
+ "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",
2348
+ triggerVariant === "disclosure" ? "w-7" : "w-6",
2090
2349
  classNames?.childMenuTrigger
2091
2350
  ),
2092
- "aria-label": `${label}: ${typeof item.label === "string" ? item.label : item.id}`,
2351
+ "aria-label": `${label}: ${itemLabel}`,
2093
2352
  title: label,
2094
- children: /* @__PURE__ */ jsxRuntime.jsx(MoreIcon2, {})
2353
+ children: triggerVariant === "disclosure" ? /* @__PURE__ */ jsxRuntime.jsx(DisclosureIcon, { expanded: false }) : /* @__PURE__ */ jsxRuntime.jsx(MoreIcon2, {})
2095
2354
  }
2096
2355
  ) }),
2097
2356
  /* @__PURE__ */ jsxRuntime.jsx(DropdownMenu__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -2114,7 +2373,7 @@ function CollapsedChildMenu({
2114
2373
  "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",
2115
2374
  child.id === activeChildId && "bg-accent text-accent-foreground"
2116
2375
  ),
2117
- onSelect: () => onChildSelect(child, item),
2376
+ onSelect: () => onChildSelect?.(child, item),
2118
2377
  children: [
2119
2378
  hasRenderableNode(child.icon) ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: child.icon }) : null,
2120
2379
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: child.label })
@@ -2135,22 +2394,81 @@ function RouteContent({
2135
2394
  routeItems,
2136
2395
  activeItemId,
2137
2396
  activeChildId,
2397
+ expandedItemIds,
2398
+ defaultExpandedItemIds = [],
2138
2399
  itemAs,
2139
2400
  onItemSelect,
2140
2401
  onChildSelect,
2402
+ onExpandedItemIdsChange,
2141
2403
  collapsedChildMenuLabel = "Open submenu",
2404
+ disclosureLabel = "Toggle submenu",
2142
2405
  ariaLabel,
2143
2406
  classNames
2144
2407
  } = props;
2145
- 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) => {
2146
- const active = item.id === activeItemId;
2408
+ const disclosureId = react.useId();
2409
+ const collapsibleItemIds = react.useMemo(
2410
+ () => validateRouteItems(routeItems),
2411
+ [routeItems]
2412
+ );
2413
+ const [internalExpandedItemIds, setInternalExpandedItemIds] = react.useState(
2414
+ () => defaultExpandedItemIds
2415
+ );
2416
+ const isExpandedControlled = expandedItemIds !== void 0;
2417
+ const resolvedExpandedItemIds = expandedItemIds ?? internalExpandedItemIds;
2418
+ const normalizedExpandedItemIds = react.useMemo(
2419
+ () => resolvedExpandedItemIds.filter(
2420
+ (itemId) => collapsibleItemIds.has(itemId)
2421
+ ),
2422
+ [collapsibleItemIds, resolvedExpandedItemIds]
2423
+ );
2424
+ const expandedItemIdSet = react.useMemo(
2425
+ () => new Set(normalizedExpandedItemIds),
2426
+ [normalizedExpandedItemIds]
2427
+ );
2428
+ const toggleItemExpanded = react.useCallback(
2429
+ (item) => {
2430
+ if (item.disabled || !collapsibleItemIds.has(item.id)) return;
2431
+ const nextExpandedItemIds = expandedItemIdSet.has(item.id) ? normalizedExpandedItemIds.filter((itemId) => itemId !== item.id) : [...normalizedExpandedItemIds, item.id];
2432
+ if (!isExpandedControlled) {
2433
+ setInternalExpandedItemIds(nextExpandedItemIds);
2434
+ }
2435
+ onExpandedItemIdsChange?.(nextExpandedItemIds);
2436
+ },
2437
+ [
2438
+ collapsibleItemIds,
2439
+ expandedItemIdSet,
2440
+ isExpandedControlled,
2441
+ normalizedExpandedItemIds,
2442
+ onExpandedItemIdsChange
2443
+ ]
2444
+ );
2445
+ 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) => {
2446
+ const disclosure = isDisclosureRouteItem(item);
2447
+ const active = !disclosure && item.id === activeItemId;
2448
+ const hasChildren = Boolean(item.children?.length);
2449
+ const childrenCollapsible = collapsibleItemIds.has(item.id);
2450
+ const childrenExpanded = !childrenCollapsible || expandedItemIdSet.has(item.id);
2451
+ const showItemActions = !collapsed && hasRenderableNode(item.actions);
2452
+ const compoundItemRow = collapsed && hasChildren && Boolean(onChildSelect) || !collapsed && childrenCollapsible && !disclosure || showItemActions;
2453
+ const childListId = `${disclosureId}-route-children-${itemIndex}`;
2147
2454
  return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "grid min-w-0 gap-1", children: [
2148
- /* @__PURE__ */ jsxRuntime.jsxs(
2455
+ collapsed && disclosure ? /* @__PURE__ */ jsxRuntime.jsx(
2456
+ CollapsedChildMenu,
2457
+ {
2458
+ item,
2459
+ activeChildId,
2460
+ label: collapsedChildMenuLabel,
2461
+ onChildSelect,
2462
+ classNames,
2463
+ appearance,
2464
+ triggerVariant: "item"
2465
+ }
2466
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(
2149
2467
  "div",
2150
2468
  {
2151
- className: cn(
2152
- collapsed && Boolean(item.children?.length) && Boolean(onChildSelect) && "flex min-w-0 items-center gap-0.5"
2153
- ),
2469
+ "data-navigation-sidebar-item-row": compoundItemRow ? item.id : void 0,
2470
+ "data-active": compoundItemRow ? String(active) : void 0,
2471
+ className: compoundItemRow ? routeRowClassName({ active, appearance }) : void 0,
2154
2472
  children: [
2155
2473
  /* @__PURE__ */ jsxRuntime.jsx(
2156
2474
  EntryButton,
@@ -2158,12 +2476,25 @@ function RouteContent({
2158
2476
  item,
2159
2477
  active,
2160
2478
  collapsed,
2161
- itemAs,
2479
+ embedded: compoundItemRow,
2480
+ itemAs: disclosure ? "button" : itemAs,
2162
2481
  appearance,
2163
2482
  classNames,
2164
- onSelect: onItemSelect ? () => onItemSelect(item) : void 0
2483
+ onSelect: disclosure ? () => toggleItemExpanded(item) : onItemSelect ? () => onItemSelect(item) : void 0,
2484
+ ariaControls: !collapsed && childrenCollapsible && disclosure ? childListId : void 0,
2485
+ ariaExpanded: !collapsed && childrenCollapsible && disclosure ? childrenExpanded : void 0,
2486
+ endIndicator: !collapsed && disclosure && !showItemActions ? /* @__PURE__ */ jsxRuntime.jsx(DisclosureIcon, { expanded: childrenExpanded }) : void 0
2165
2487
  }
2166
2488
  ),
2489
+ showItemActions ? /* @__PURE__ */ jsxRuntime.jsx(
2490
+ RouteActions,
2491
+ {
2492
+ itemId: item.id,
2493
+ actions: item.actions,
2494
+ kind: "item",
2495
+ className: classNames?.itemActions
2496
+ }
2497
+ ) : null,
2167
2498
  collapsed && onChildSelect ? /* @__PURE__ */ jsxRuntime.jsx(
2168
2499
  CollapsedChildMenu,
2169
2500
  {
@@ -2173,28 +2504,71 @@ function RouteContent({
2173
2504
  onChildSelect,
2174
2505
  classNames
2175
2506
  }
2507
+ ) : null,
2508
+ !collapsed && childrenCollapsible && (!disclosure || showItemActions) ? /* @__PURE__ */ jsxRuntime.jsx(
2509
+ RouteChildrenToggle,
2510
+ {
2511
+ item,
2512
+ expanded: childrenExpanded,
2513
+ controls: childListId,
2514
+ label: disclosureLabel,
2515
+ onToggle: () => toggleItemExpanded(item),
2516
+ classNames
2517
+ }
2176
2518
  ) : null
2177
2519
  ]
2178
2520
  }
2179
2521
  ),
2180
- !collapsed && item.children?.length ? /* @__PURE__ */ jsxRuntime.jsx(
2522
+ !collapsed && hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
2181
2523
  "ul",
2182
2524
  {
2525
+ id: childListId,
2526
+ hidden: !childrenExpanded,
2527
+ style: childrenExpanded ? void 0 : { display: "none" },
2183
2528
  className: cn(
2184
2529
  "ml-5 grid list-none gap-0.5 border-l border-border/70 py-1 pl-3",
2185
2530
  classNames?.childList
2186
2531
  ),
2187
- children: item.children.map((child) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
2188
- ChildEntry,
2189
- {
2190
- child,
2191
- parent: item,
2192
- active: child.id === activeChildId,
2193
- interactive: Boolean(onChildSelect),
2194
- onSelect: onChildSelect,
2195
- classNames
2196
- }
2197
- ) }, child.id))
2532
+ children: item.children?.map((child) => {
2533
+ const hasChildActions = hasRenderableNode(child.actions);
2534
+ const childActive = child.id === activeChildId;
2535
+ const childEntry = /* @__PURE__ */ jsxRuntime.jsx(
2536
+ ChildEntry,
2537
+ {
2538
+ child,
2539
+ parent: item,
2540
+ active: childActive,
2541
+ interactive: Boolean(onChildSelect),
2542
+ onSelect: onChildSelect,
2543
+ appearance,
2544
+ classNames,
2545
+ embedded: hasChildActions
2546
+ }
2547
+ );
2548
+ return /* @__PURE__ */ jsxRuntime.jsx("li", { children: hasChildActions ? /* @__PURE__ */ jsxRuntime.jsxs(
2549
+ "div",
2550
+ {
2551
+ "data-navigation-sidebar-child-row": child.id,
2552
+ "data-active": String(childActive),
2553
+ className: routeRowClassName({
2554
+ active: childActive,
2555
+ appearance
2556
+ }),
2557
+ children: [
2558
+ childEntry,
2559
+ /* @__PURE__ */ jsxRuntime.jsx(
2560
+ RouteActions,
2561
+ {
2562
+ itemId: child.id,
2563
+ actions: child.actions,
2564
+ kind: "child",
2565
+ className: classNames?.childActions
2566
+ }
2567
+ )
2568
+ ]
2569
+ }
2570
+ ) : childEntry }, child.id);
2571
+ })
2198
2572
  }
2199
2573
  ) : null
2200
2574
  ] }, item.id);
@@ -2228,23 +2602,116 @@ function MobileNavigation({
2228
2602
  classNames?.mobileNav,
2229
2603
  props.mobile.className
2230
2604
  ),
2231
- children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
2232
- EntryButton,
2233
- {
2234
- item,
2235
- active: props.kind === "catalog" ? item.active ?? item.id === props.activeItemId : item.id === props.activeItemId,
2236
- collapsed: false,
2237
- itemAs: props.itemAs,
2238
- appearance,
2239
- classNames: {
2240
- ...classNames,
2241
- mobileItem: cn(classNames?.mobileItem, props.mobile?.itemClassName)
2605
+ children: items.map((item) => {
2606
+ const mobileClassNames = {
2607
+ ...classNames,
2608
+ mobileItem: cn(
2609
+ classNames?.mobileItem,
2610
+ props.mobile?.itemClassName
2611
+ )
2612
+ };
2613
+ const routeItem = props.kind === "route" ? item : void 0;
2614
+ const showRouteActions = routeItem !== void 0 && hasRenderableNode(routeItem.actions);
2615
+ if (props.kind === "route" && isDisclosureRouteItem(item)) {
2616
+ return showRouteActions ? /* @__PURE__ */ jsxRuntime.jsxs(
2617
+ "div",
2618
+ {
2619
+ "data-navigation-sidebar-item-row": item.id,
2620
+ "data-active": "false",
2621
+ className: cn(
2622
+ routeRowClassName({ active: false, appearance }),
2623
+ "shrink-0"
2624
+ ),
2625
+ children: [
2626
+ /* @__PURE__ */ jsxRuntime.jsx(
2627
+ EntryPresentation,
2628
+ {
2629
+ item,
2630
+ appearance,
2631
+ classNames: mobileClassNames
2632
+ }
2633
+ ),
2634
+ /* @__PURE__ */ jsxRuntime.jsx(
2635
+ RouteActions,
2636
+ {
2637
+ itemId: item.id,
2638
+ actions: item.actions,
2639
+ kind: "item",
2640
+ className: classNames?.itemActions,
2641
+ mobile: true
2642
+ }
2643
+ ),
2644
+ /* @__PURE__ */ jsxRuntime.jsx(
2645
+ CollapsedChildMenu,
2646
+ {
2647
+ item,
2648
+ activeChildId: props.activeChildId,
2649
+ label: props.collapsedChildMenuLabel ?? "Open submenu",
2650
+ onChildSelect: props.onChildSelect,
2651
+ classNames: mobileClassNames,
2652
+ appearance,
2653
+ triggerVariant: "disclosure",
2654
+ mobile: true
2655
+ }
2656
+ )
2657
+ ]
2658
+ },
2659
+ item.id
2660
+ ) : /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsx(
2661
+ CollapsedChildMenu,
2662
+ {
2663
+ item,
2664
+ activeChildId: props.activeChildId,
2665
+ label: props.collapsedChildMenuLabel ?? "Open submenu",
2666
+ onChildSelect: props.onChildSelect,
2667
+ classNames: mobileClassNames,
2668
+ appearance,
2669
+ triggerVariant: "item",
2670
+ mobile: true
2671
+ }
2672
+ ) }, item.id);
2673
+ }
2674
+ const active = props.kind === "catalog" ? item.active ?? item.id === props.activeItemId : item.id === props.activeItemId;
2675
+ const entryButton = /* @__PURE__ */ jsxRuntime.jsx(
2676
+ EntryButton,
2677
+ {
2678
+ item,
2679
+ active,
2680
+ collapsed: false,
2681
+ embedded: showRouteActions,
2682
+ itemAs: props.itemAs,
2683
+ appearance,
2684
+ classNames: mobileClassNames,
2685
+ mobile: true,
2686
+ onSelect: props.kind === "route" && props.onItemSelect ? () => props.onItemSelect?.(item) : void 0
2687
+ }
2688
+ );
2689
+ return showRouteActions && routeItem ? /* @__PURE__ */ jsxRuntime.jsxs(
2690
+ "div",
2691
+ {
2692
+ "data-navigation-sidebar-item-row": item.id,
2693
+ "data-active": String(active),
2694
+ className: cn(
2695
+ routeRowClassName({ active, appearance }),
2696
+ "shrink-0"
2697
+ ),
2698
+ children: [
2699
+ entryButton,
2700
+ /* @__PURE__ */ jsxRuntime.jsx(
2701
+ RouteActions,
2702
+ {
2703
+ itemId: routeItem.id,
2704
+ actions: routeItem.actions,
2705
+ kind: "item",
2706
+ className: classNames?.itemActions,
2707
+ mobile: true
2708
+ }
2709
+ )
2710
+ ]
2242
2711
  },
2243
- mobile: true,
2244
- onSelect: props.kind === "route" && props.onItemSelect ? () => props.onItemSelect?.(item) : void 0
2245
- },
2246
- item.id
2247
- ))
2712
+ item.id
2713
+ ) : /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: entryButton }, item.id);
2714
+ })
2248
2715
  }
2249
2716
  );
2250
2717
  }