@propeller-commerce/propeller-v2-react-ui 0.10.1 → 0.11.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.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,93 @@ once it reaches 1.0. Until then (the `0.x` line) the public API may change
8
8
  between minor versions; breaking changes are called out below and in
9
9
  [MIGRATION.md](./MIGRATION.md).
10
10
 
11
+ ## [0.11.1] - 2026-07-30
12
+
13
+ ### Fixed
14
+
15
+ - **`menuStyle="accordion"` rendered nothing above `md`.** The accordion is the
16
+ mobile drawer for every style, so its `<nav>` carried `md:hidden` and rendered
17
+ unconditionally — while the desktop branches were gated on `menuStyle`.
18
+ Selecting it therefore matched no desktop branch and hid the mobile one,
19
+ leaving an empty panel. It now renders at every breakpoint when chosen
20
+ explicitly, and stays `md:hidden` as the mobile presentation of the others.
21
+ This is what 0.11.0's depth warning already recommended for deep trees.
22
+ - **`menuStyle` values with no renderer failed silently.** An unrecognised
23
+ style matched no branch and produced an empty panel with nothing to indicate
24
+ why. It now falls back to `accordion` — the style that can display any tree —
25
+ and warns in development, pointing at `renderMenu`.
26
+ - **The accordion's expand control didn't look clickable.** Tailwind v4 resets
27
+ buttons to `cursor: default`, so the chevron — the only control that expands a
28
+ branch — gave no affordance. Added `cursor-pointer` plus a hover colour shift,
29
+ and the same to the jumbotron tabs, which had it too.
30
+ - **A deep flyout could run off the viewport with no way to reach it.** The
31
+ columns row now has `max-w-[100vw] overflow-x-auto`, so a tree wider than the
32
+ screen degrades to a scroll instead of being unreachable.
33
+
34
+ ### Added
35
+
36
+ - **`renderMenu` — render the menu yourself.** The built-in styles are three
37
+ arrangements of the same tree; this is the escape hatch for a fourth. It
38
+ receives a `MenuRenderContext` carrying the categories, the open-path state
39
+ and the same helpers the built-ins use (`getCategoryUrl` honouring
40
+ `getUrl`/`configuration`, `handleItemClick` firing `onMenuItemClick`,
41
+ `openAt`/`toggleAt`/`isOpenAt`), so a custom menu gets working open/close
42
+ behaviour and URL building without reimplementing them and drifting. Return
43
+ `null` to fall through to the `menuStyle` rendering. `MenuStyle` and
44
+ `MenuRenderContext` are exported.
45
+ - **First tests for `Menu`** (9 cases), covering the failures above: they all
46
+ passed type-checking and rendered *something*, so only a DOM assertion catches
47
+ them.
48
+
49
+ ### Changed
50
+
51
+ - **`dropdown-horizontal` removed.** It was referenced only in the depth-cap
52
+ logic and never had a renderer, so it advertised a style that drew nothing.
53
+ Callers using it now hit the unknown-style fallback (accordion + warning); use
54
+ `renderMenu` for a genuinely custom layout.
55
+ - **`dropdown-vertical` caps at 5 levels, up from 4.** Each level is a 256px
56
+ column, so five total 1280px — within a 1440px desktop. Paired with the
57
+ overflow guard above for narrower screens.
58
+ - **`menuStyle` is typed `MenuStyle | (string & {})`** rather than `string`: the
59
+ built-ins autocomplete while a custom value still type-checks for use with
60
+ `renderMenu`.
61
+
62
+ ## [0.11.0] - 2026-07-30
63
+
64
+ ### Fixed
65
+
66
+ - **`<Menu depth>` was inert past three levels.** `depth` was threaded into
67
+ `useMenu`, which correctly built a recursive query to any depth, but every
68
+ style rendered exactly three hand-coded levels. `depth={4}` and beyond fetched
69
+ data that was never displayed — extra payload for nothing, with no warning
70
+ that the prop was only half-honoured. All styles now render to `depth`.
71
+ - **The expand chevron promised levels that never rendered.** It keyed off "has
72
+ children" alone, so a category at the deepest rendered level showed an
73
+ affordance that did nothing. It is now also gated on the child being within
74
+ `depth`.
75
+
76
+ ### Changed
77
+
78
+ - **Per-level menu state replaced with a single open path.** The four
79
+ positional state slots (`hoveredL1Id` / `hoveredL2Id` / `expandedL1` /
80
+ `expandedL2`) are now one `openPath: number[]`. "Opening a shallower item
81
+ closes the deeper ones" is structural (`slice(0, level)`) rather than a
82
+ hand-written reset per level, which grew quadratically and was easy to get
83
+ wrong when adding a level.
84
+ - **`data-level` is emitted to whatever depth renders** instead of a hardcoded
85
+ `1`/`2`/`3`, so consumer CSS can target arbitrary depth.
86
+ - **Flyout styles cap the levels they can lay out** — 4 for
87
+ `dropdown-vertical`, 3 for `dropdown-horizontal`/`jumbotron`, since each level
88
+ is another horizontal column and beyond that they run off-screen. The
89
+ accordion nests vertically and is uncapped. Exceeding a cap logs a
90
+ `console.warn` in development (previously the prop silently did nothing).
91
+
92
+ `depth` still defaults to 3, and all fifteen `propeller-menu__*` class hooks are
93
+ unchanged, so a three-level menu keeps its existing structure and styling. Some
94
+ Tailwind utility classes internal to the component differ where per-level
95
+ values (`pl-8` / `pl-12` indentation) became a computed `padding-left`; styling
96
+ that targets the `propeller-menu__*` hooks is unaffected.
97
+
11
98
  ## [0.10.1] - 2026-07-30
12
99
 
13
100
  ### Fixed
package/dist/index.cjs CHANGED
@@ -12845,12 +12845,10 @@ function ItemsOverview(props) {
12845
12845
  ] });
12846
12846
  }
12847
12847
  var ItemsOverview_default = ItemsOverview;
12848
+ var RENDERED_STYLES = ["dropdown-vertical", "jumbotron", "accordion"];
12848
12849
  function Menu(rawProps) {
12849
12850
  const props = useInfraProps(rawProps);
12850
- const [hoveredL1Id, setHoveredL1Id] = React6.useState(null);
12851
- const [hoveredL2Id, setHoveredL2Id] = React6.useState(null);
12852
- const [expandedL1, setExpandedL1] = React6.useState(null);
12853
- const [expandedL2, setExpandedL2] = React6.useState(null);
12851
+ const [openPath, setOpenPath] = React6.useState([]);
12854
12852
  const userKey = props.user ? "contactId" in props.user ? `c${props.user.contactId}` : `u${props.user.customerId}` : "";
12855
12853
  const hasPrefetchedTree = Array.isArray(props.tree);
12856
12854
  const { categories: fetchedCategories, loading: fetchedLoading, error: menuError, fetchMenu } = useMenu({
@@ -12887,21 +12885,178 @@ function Menu(rawProps) {
12887
12885
  });
12888
12886
  }
12889
12887
  }
12890
- function setHoveredL1(id) {
12891
- setHoveredL1Id(id);
12892
- setHoveredL2Id(null);
12888
+ function openAt(level, categoryId) {
12889
+ setOpenPath((prev) => {
12890
+ const next = prev.slice(0, level);
12891
+ if (categoryId !== null) next.push(categoryId);
12892
+ return next;
12893
+ });
12893
12894
  }
12894
- function setHoveredL2(id) {
12895
- setHoveredL2Id(id);
12895
+ function toggleAt(level, categoryId) {
12896
+ setOpenPath(
12897
+ (prev) => prev[level] === categoryId ? prev.slice(0, level) : [...prev.slice(0, level), categoryId]
12898
+ );
12899
+ }
12900
+ function isOpenAt(level, categoryId) {
12901
+ return openPath[level] === categoryId;
12896
12902
  }
12897
12903
  function getMenuStyle() {
12898
- return props.menuStyle || "dropdown-vertical";
12904
+ const requested = props.menuStyle || "dropdown-vertical";
12905
+ if (!RENDERED_STYLES.includes(requested)) {
12906
+ if (process.env.NODE_ENV !== "production") {
12907
+ console.warn(
12908
+ `[Menu] menuStyle="${requested}" has no built-in renderer (available: ${RENDERED_STYLES.join(", ")}); falling back to "accordion". Pass \`renderMenu\` to draw a custom style.`
12909
+ );
12910
+ }
12911
+ return "accordion";
12912
+ }
12913
+ return requested;
12914
+ }
12915
+ function getRenderableDepth() {
12916
+ const requested = Math.max(1, props.depth ?? 3);
12917
+ const cap = getMenuStyle() === "accordion" ? Number.POSITIVE_INFINITY : 5;
12918
+ if (process.env.NODE_ENV !== "production" && requested > cap) {
12919
+ console.warn(
12920
+ `[Menu] depth={${requested}} exceeds what menuStyle="${getMenuStyle()}" can lay out (${cap}); rendering ${cap} levels. Use menuStyle="accordion" for deeper trees.`
12921
+ );
12922
+ }
12923
+ return Math.min(requested, cap);
12924
+ }
12925
+ const maxDepth = getRenderableDepth();
12926
+ function renderChevron(cat, level, rotated = false) {
12927
+ if (getSubCategories(cat).length === 0 || level + 1 >= maxDepth) return null;
12928
+ return /* @__PURE__ */ jsxRuntime.jsx(
12929
+ "svg",
12930
+ {
12931
+ className: `propeller-menu__chevron w-3.5 h-3.5 flex-shrink-0 transition-transform ${rotated ? "rotate-180" : ""}`,
12932
+ viewBox: "0 0 24 24",
12933
+ fill: "none",
12934
+ stroke: "currentColor",
12935
+ strokeWidth: "2",
12936
+ strokeLinecap: "round",
12937
+ strokeLinejoin: "round",
12938
+ "aria-hidden": "true",
12939
+ children: rotated ? /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "6 9 12 15 18 9" }) : /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "9 18 15 12 9 6" })
12940
+ }
12941
+ );
12942
+ }
12943
+ function renderColumns(items, level) {
12944
+ if (!items.length || level >= maxDepth) return [];
12945
+ const openId = openPath[level];
12946
+ const openCat = items.find((c) => c.categoryId === openId);
12947
+ const column = /* @__PURE__ */ jsxRuntime.jsx(
12948
+ "ul",
12949
+ {
12950
+ className: `propeller-menu__list w-64 py-1 flex-shrink-0 ${level + 1 < maxDepth ? "border-r border-border" : ""}`,
12951
+ "data-level": level + 1,
12952
+ children: items.map((cat, idx) => /* @__PURE__ */ jsxRuntime.jsx(
12953
+ "li",
12954
+ {
12955
+ className: "propeller-menu__item",
12956
+ "data-level": level + 1,
12957
+ "data-active": isOpenAt(level, cat.categoryId) ? "true" : "false",
12958
+ onMouseEnter: () => openAt(level, cat.categoryId),
12959
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
12960
+ "a",
12961
+ {
12962
+ href: getCategoryUrl(cat),
12963
+ onClick: (e) => handleItemClick(cat, e),
12964
+ className: `propeller-menu__link flex items-center justify-between px-4 py-2.5 text-sm transition-colors ${isOpenAt(level, cat.categoryId) ? "bg-accent text-accent-foreground" : "text-foreground hover:bg-accent/50"}`,
12965
+ children: [
12966
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "propeller-menu__label", children: getCategoryName(cat) }),
12967
+ renderChevron(cat, level)
12968
+ ]
12969
+ }
12970
+ )
12971
+ },
12972
+ `l${level}-${cat.categoryId}-${idx}`
12973
+ ))
12974
+ },
12975
+ `col-${level}`
12976
+ );
12977
+ const deeper = openCat ? renderColumns(getSubCategories(openCat), level + 1) : [];
12978
+ return [column, ...deeper];
12979
+ }
12980
+ function renderAccordion(items, level) {
12981
+ if (!items.length || level >= maxDepth) return null;
12982
+ return /* @__PURE__ */ jsxRuntime.jsx(
12983
+ "ul",
12984
+ {
12985
+ className: `propeller-menu__list ${level === 0 ? "divide-y divide-border" : level === 1 ? "bg-accent/30" : "bg-accent/20"}`,
12986
+ "data-level": level + 1,
12987
+ children: items.map((cat, idx) => {
12988
+ const children = getSubCategories(cat);
12989
+ const expanded = isOpenAt(level, cat.categoryId);
12990
+ const canExpand = children.length > 0 && level + 1 < maxDepth;
12991
+ return /* @__PURE__ */ jsxRuntime.jsxs(
12992
+ "li",
12993
+ {
12994
+ className: "propeller-menu__item",
12995
+ "data-level": level + 1,
12996
+ "data-expanded": expanded ? "true" : "false",
12997
+ children: [
12998
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
12999
+ /* @__PURE__ */ jsxRuntime.jsx(
13000
+ "a",
13001
+ {
13002
+ href: getCategoryUrl(cat),
13003
+ onClick: (e) => handleItemClick(cat, e),
13004
+ className: "propeller-menu__link flex-1 px-4 py-3 text-sm text-foreground",
13005
+ style: { paddingLeft: `${1 + level}rem` },
13006
+ children: getCategoryName(cat)
13007
+ }
13008
+ ),
13009
+ canExpand ? /* @__PURE__ */ jsxRuntime.jsx(
13010
+ "button",
13011
+ {
13012
+ type: "button",
13013
+ className: "propeller-menu__toggle cursor-pointer px-4 py-3 text-muted-foreground transition-colors hover:text-foreground",
13014
+ "aria-expanded": expanded,
13015
+ "aria-label": getCategoryName(cat),
13016
+ onClick: () => toggleAt(level, cat.categoryId),
13017
+ children: renderChevron(cat, level, expanded)
13018
+ }
13019
+ ) : null
13020
+ ] }),
13021
+ expanded && canExpand ? renderAccordion(children, level + 1) : null
13022
+ ]
13023
+ },
13024
+ `l${level}-${cat.categoryId}-${idx}`
13025
+ );
13026
+ })
13027
+ }
13028
+ );
12899
13029
  }
12900
13030
  React6.useEffect(() => {
12901
13031
  if (hasPrefetchedTree) return;
12902
13032
  if (!props.graphqlClient) return;
12903
13033
  fetchMenu(props.categoryId, userKey);
12904
13034
  }, [hasPrefetchedTree, props.graphqlClient, props.categoryId, props.language, userKey, fetchMenu]);
13035
+ const customMenu = props.renderMenu ? props.renderMenu({
13036
+ categories: menuCategories,
13037
+ isLoading,
13038
+ hasError,
13039
+ maxDepth,
13040
+ getSubCategories,
13041
+ getCategoryName,
13042
+ getCategoryUrl,
13043
+ handleItemClick,
13044
+ openPath,
13045
+ isOpenAt,
13046
+ openAt,
13047
+ toggleAt
13048
+ }) : null;
13049
+ if (customMenu !== null && customMenu !== void 0) {
13050
+ return /* @__PURE__ */ jsxRuntime.jsx(
13051
+ "div",
13052
+ {
13053
+ className: `propeller-menu ${props.className || ""}`,
13054
+ "data-variant": props.menuStyle || "custom",
13055
+ "data-loading": isLoading ? "true" : "false",
13056
+ children: customMenu
13057
+ }
13058
+ );
13059
+ }
12905
13060
  return /* @__PURE__ */ jsxRuntime.jsxs(
12906
13061
  "div",
12907
13062
  {
@@ -12919,125 +13074,7 @@ function Menu(rawProps) {
12919
13074
  "nav",
12920
13075
  {
12921
13076
  className: `propeller-menu__nav hidden md:block ${props.menuClass || ""}`,
12922
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex bg-popover border border-border shadow-lg", children: [
12923
- /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "propeller-menu__list w-64 py-1 border-r border-border flex-shrink-0", "data-level": "1", children: menuCategories?.map((l1, idx) => /* @__PURE__ */ jsxRuntime.jsx(
12924
- "li",
12925
- {
12926
- className: "propeller-menu__item",
12927
- "data-level": "1",
12928
- "data-active": hoveredL1Id === l1.categoryId ? "true" : "false",
12929
- onMouseEnter: (event) => setHoveredL1(l1.categoryId),
12930
- children: /* @__PURE__ */ jsxRuntime.jsxs(
12931
- "a",
12932
- {
12933
- href: getCategoryUrl(l1),
12934
- onClick: (e) => handleItemClick(l1, e),
12935
- className: `propeller-menu__link flex items-center justify-between px-4 py-2.5 text-sm transition-colors ${hoveredL1Id === l1.categoryId ? "bg-accent text-accent-foreground" : "text-foreground hover:bg-accent/50"}`,
12936
- children: [
12937
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "propeller-menu__label font-medium truncate", children: getCategoryName(l1) }),
12938
- getSubCategories(l1).length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
12939
- "svg",
12940
- {
12941
- fill: "none",
12942
- stroke: "currentColor",
12943
- viewBox: "0 0 24 24",
12944
- className: "propeller-menu__chevron w-4 h-4 text-muted-foreground",
12945
- children: /* @__PURE__ */ jsxRuntime.jsx(
12946
- "path",
12947
- {
12948
- strokeLinecap: "round",
12949
- strokeLinejoin: "round",
12950
- d: "M9 5l7 7-7 7",
12951
- strokeWidth: 2
12952
- }
12953
- )
12954
- }
12955
- ) : null
12956
- ]
12957
- }
12958
- )
12959
- },
12960
- `l1-${l1.categoryId}-${idx}`
12961
- )) }),
12962
- menuCategories?.map(
12963
- (l1, idx) => hoveredL1Id === l1.categoryId && getSubCategories(l1).length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
12964
- "ul",
12965
- {
12966
- className: "propeller-menu__list w-64 py-1 border-r border-border flex-shrink-0",
12967
- "data-level": "2",
12968
- children: getSubCategories(l1)?.map((l2, idx2) => /* @__PURE__ */ jsxRuntime.jsx(
12969
- "li",
12970
- {
12971
- className: "propeller-menu__item",
12972
- "data-level": "2",
12973
- "data-active": hoveredL2Id === l2.categoryId ? "true" : "false",
12974
- onMouseEnter: (event) => setHoveredL2(l2.categoryId),
12975
- children: /* @__PURE__ */ jsxRuntime.jsxs(
12976
- "a",
12977
- {
12978
- href: getCategoryUrl(l2),
12979
- onClick: (e) => handleItemClick(l2, e),
12980
- className: `propeller-menu__link flex items-center justify-between px-4 py-2.5 text-sm transition-colors ${hoveredL2Id === l2.categoryId ? "bg-accent text-accent-foreground" : "text-foreground hover:bg-accent/50"}`,
12981
- children: [
12982
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "propeller-menu__label truncate", children: getCategoryName(l2) }),
12983
- getSubCategories(l2).length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
12984
- "svg",
12985
- {
12986
- fill: "none",
12987
- stroke: "currentColor",
12988
- viewBox: "0 0 24 24",
12989
- className: "propeller-menu__chevron w-4 h-4 text-muted-foreground",
12990
- children: /* @__PURE__ */ jsxRuntime.jsx(
12991
- "path",
12992
- {
12993
- strokeLinecap: "round",
12994
- strokeLinejoin: "round",
12995
- d: "M9 5l7 7-7 7",
12996
- strokeWidth: 2
12997
- }
12998
- )
12999
- }
13000
- ) : null
13001
- ]
13002
- }
13003
- )
13004
- },
13005
- `l2-${l2.categoryId}-${idx2}`
13006
- ))
13007
- },
13008
- `l1-sub-${l1.categoryId}-${idx}`
13009
- ) : null
13010
- ),
13011
- menuCategories?.map(
13012
- (l1, idx) => hoveredL1Id === l1.categoryId ? /* @__PURE__ */ jsxRuntime.jsx(React6__namespace.Fragment, { children: getSubCategories(l1)?.map(
13013
- (l2, idx2) => hoveredL2Id === l2.categoryId && getSubCategories(l2).length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
13014
- "ul",
13015
- {
13016
- className: "propeller-menu__list w-64 py-1 flex-shrink-0",
13017
- "data-level": "3",
13018
- children: getSubCategories(l2)?.map((l3, idx3) => /* @__PURE__ */ jsxRuntime.jsx(
13019
- "li",
13020
- {
13021
- className: "propeller-menu__item",
13022
- "data-level": "3",
13023
- children: /* @__PURE__ */ jsxRuntime.jsx(
13024
- "a",
13025
- {
13026
- className: "propeller-menu__link block px-4 py-2.5 text-sm text-foreground hover:bg-accent/50 transition-colors",
13027
- href: getCategoryUrl(l3),
13028
- onClick: (e) => handleItemClick(l3, e),
13029
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "propeller-menu__label", children: getCategoryName(l3) })
13030
- }
13031
- )
13032
- },
13033
- `l3-${l3.categoryId}-${idx3}`
13034
- ))
13035
- },
13036
- `l3-col-${l2.categoryId}-${idx2}`
13037
- ) : null
13038
- ) }, `l1-l3col-${l1.categoryId}-${idx}`) : null
13039
- )
13040
- ] })
13077
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex max-w-[100vw] overflow-x-auto bg-popover border border-border shadow-lg", children: renderColumns(menuCategories, 0) })
13041
13078
  }
13042
13079
  ) : null,
13043
13080
  !isLoading && !hasError && menuCategories.length > 0 && getMenuStyle() === "jumbotron" ? /* @__PURE__ */ jsxRuntime.jsxs(
@@ -13048,22 +13085,22 @@ function Menu(rawProps) {
13048
13085
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-menu__tabs flex items-center border-b border-border", children: menuCategories?.map((l1, idx) => /* @__PURE__ */ jsxRuntime.jsx(
13049
13086
  "button",
13050
13087
  {
13051
- className: `propeller-menu__tab px-5 py-3 text-sm font-medium transition-colors border-b-2 ${hoveredL1Id === l1.categoryId ? "border-primary text-primary" : "border-transparent text-foreground hover:text-primary hover:border-primary/50"}`,
13088
+ className: `propeller-menu__tab cursor-pointer px-5 py-3 text-sm font-medium transition-colors border-b-2 ${isOpenAt(0, l1.categoryId) ? "border-primary text-primary" : "border-transparent text-foreground hover:text-primary hover:border-primary/50"}`,
13052
13089
  "data-level": "1",
13053
- "data-active": hoveredL1Id === l1.categoryId ? "true" : "false",
13054
- onMouseEnter: (event) => setHoveredL1(l1.categoryId),
13090
+ "data-active": isOpenAt(0, l1.categoryId) ? "true" : "false",
13091
+ onMouseEnter: (event) => openAt(0, l1.categoryId),
13055
13092
  onClick: (e) => handleItemClick(l1, e),
13056
13093
  children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "propeller-menu__label", children: getCategoryName(l1) })
13057
13094
  },
13058
13095
  `l1-${l1.categoryId}-${idx}`
13059
13096
  )) }),
13060
13097
  menuCategories?.map(
13061
- (l1, idx) => hoveredL1Id === l1.categoryId && getSubCategories(l1).length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
13098
+ (l1, idx) => isOpenAt(0, l1.categoryId) && getSubCategories(l1).length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
13062
13099
  "div",
13063
13100
  {
13064
13101
  className: "propeller-menu__panel bg-popover border border-border border-t-0 shadow-lg p-6",
13065
- onMouseEnter: (event) => setHoveredL1(l1.categoryId),
13066
- onMouseLeave: (event) => setHoveredL1(null),
13102
+ onMouseEnter: (event) => openAt(0, l1.categoryId),
13103
+ onMouseLeave: (event) => openAt(0, null),
13067
13104
  children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-menu__panel-grid grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6", children: getSubCategories(l1)?.map((l2, idx2) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "propeller-menu__group", "data-level": "2", children: [
13068
13105
  /* @__PURE__ */ jsxRuntime.jsx(
13069
13106
  "a",
@@ -13090,123 +13127,13 @@ function Menu(rawProps) {
13090
13127
  ]
13091
13128
  }
13092
13129
  ) : null,
13093
- !isLoading && !hasError && menuCategories.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("nav", { className: `propeller-menu__nav md:hidden ${props.menuClass || ""}`, children: /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "propeller-menu__list divide-y divide-border", "data-level": "1", children: menuCategories?.map((l1, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
13094
- "li",
13130
+ !isLoading && !hasError && menuCategories.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
13131
+ "nav",
13095
13132
  {
13096
- className: "propeller-menu__item",
13097
- "data-level": "1",
13098
- "data-expanded": expandedL1 === l1.categoryId ? "true" : "false",
13099
- children: [
13100
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
13101
- /* @__PURE__ */ jsxRuntime.jsx(
13102
- "a",
13103
- {
13104
- className: "propeller-menu__link flex-1 px-4 py-3 text-sm font-medium text-foreground",
13105
- href: getCategoryUrl(l1),
13106
- onClick: (e) => handleItemClick(l1, e),
13107
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "propeller-menu__label", children: getCategoryName(l1) })
13108
- }
13109
- ),
13110
- getSubCategories(l1).length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
13111
- "button",
13112
- {
13113
- type: "button",
13114
- className: "propeller-menu__toggle px-4 py-3 text-muted-foreground",
13115
- onClick: (event) => {
13116
- setExpandedL1(expandedL1 === l1.categoryId ? null : l1.categoryId);
13117
- },
13118
- children: /* @__PURE__ */ jsxRuntime.jsx(
13119
- "svg",
13120
- {
13121
- fill: "none",
13122
- stroke: "currentColor",
13123
- viewBox: "0 0 24 24",
13124
- className: `propeller-menu__chevron w-4 h-4 transition-transform ${expandedL1 === l1.categoryId ? "rotate-180" : ""}`,
13125
- children: /* @__PURE__ */ jsxRuntime.jsx(
13126
- "path",
13127
- {
13128
- strokeLinecap: "round",
13129
- strokeLinejoin: "round",
13130
- d: "m6 9 6 6 6-6",
13131
- strokeWidth: 2
13132
- }
13133
- )
13134
- }
13135
- )
13136
- }
13137
- ) : null
13138
- ] }),
13139
- expandedL1 === l1.categoryId && getSubCategories(l1).length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "propeller-menu__list bg-accent/30", "data-level": "2", children: getSubCategories(l1)?.map((l2, idx2) => /* @__PURE__ */ jsxRuntime.jsxs(
13140
- "li",
13141
- {
13142
- className: "propeller-menu__item",
13143
- "data-level": "2",
13144
- "data-expanded": expandedL2 === l2.categoryId ? "true" : "false",
13145
- children: [
13146
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
13147
- /* @__PURE__ */ jsxRuntime.jsx(
13148
- "a",
13149
- {
13150
- className: "propeller-menu__link flex-1 pl-8 pr-4 py-2.5 text-sm text-foreground",
13151
- href: getCategoryUrl(l2),
13152
- onClick: (e) => handleItemClick(l2, e),
13153
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "propeller-menu__label", children: getCategoryName(l2) })
13154
- }
13155
- ),
13156
- getSubCategories(l2).length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
13157
- "button",
13158
- {
13159
- type: "button",
13160
- className: "propeller-menu__toggle px-4 py-2.5 text-muted-foreground",
13161
- onClick: (event) => {
13162
- setExpandedL2(expandedL2 === l2.categoryId ? null : l2.categoryId);
13163
- },
13164
- children: /* @__PURE__ */ jsxRuntime.jsx(
13165
- "svg",
13166
- {
13167
- fill: "none",
13168
- stroke: "currentColor",
13169
- viewBox: "0 0 24 24",
13170
- className: `propeller-menu__chevron w-3.5 h-3.5 transition-transform ${expandedL2 === l2.categoryId ? "rotate-180" : ""}`,
13171
- children: /* @__PURE__ */ jsxRuntime.jsx(
13172
- "path",
13173
- {
13174
- strokeLinecap: "round",
13175
- strokeLinejoin: "round",
13176
- d: "m6 9 6 6 6-6",
13177
- strokeWidth: 2
13178
- }
13179
- )
13180
- }
13181
- )
13182
- }
13183
- ) : null
13184
- ] }),
13185
- expandedL2 === l2.categoryId && getSubCategories(l2).length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "propeller-menu__list bg-accent/20", "data-level": "3", children: getSubCategories(l2)?.map((l3, idx3) => /* @__PURE__ */ jsxRuntime.jsx(
13186
- "li",
13187
- {
13188
- className: "propeller-menu__item",
13189
- "data-level": "3",
13190
- children: /* @__PURE__ */ jsxRuntime.jsx(
13191
- "a",
13192
- {
13193
- className: "propeller-menu__link block pl-12 pr-4 py-2 text-sm text-muted-foreground hover:text-foreground transition-colors",
13194
- href: getCategoryUrl(l3),
13195
- onClick: (e) => handleItemClick(l3, e),
13196
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "propeller-menu__label", children: getCategoryName(l3) })
13197
- }
13198
- )
13199
- },
13200
- `l3-mobile-${l3.categoryId}-${idx3}`
13201
- )) }) : null
13202
- ]
13203
- },
13204
- `l2-mobile-${l2.categoryId}-${idx2}`
13205
- )) }) : null
13206
- ]
13207
- },
13208
- `l1-mobile-${l1.categoryId}-${idx}`
13209
- )) }) }) : null
13133
+ className: `propeller-menu__nav ${getMenuStyle() === "accordion" ? "block" : "md:hidden"} ${props.menuClass || ""}`,
13134
+ children: renderAccordion(menuCategories, 0)
13135
+ }
13136
+ ) : null
13210
13137
  ]
13211
13138
  }
13212
13139
  );