@propeller-commerce/propeller-v2-react-ui 0.11.0 → 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,57 @@ 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
+
11
62
  ## [0.11.0] - 2026-07-30
12
63
 
13
64
  ### Fixed
package/dist/index.cjs CHANGED
@@ -12845,6 +12845,7 @@ 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
12851
  const [openPath, setOpenPath] = React6.useState([]);
@@ -12900,11 +12901,20 @@ function Menu(rawProps) {
12900
12901
  return openPath[level] === categoryId;
12901
12902
  }
12902
12903
  function getMenuStyle() {
12903
- 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;
12904
12914
  }
12905
12915
  function getRenderableDepth() {
12906
12916
  const requested = Math.max(1, props.depth ?? 3);
12907
- const cap = getMenuStyle() === "dropdown-horizontal" ? 3 : getMenuStyle() === "accordion" ? Number.POSITIVE_INFINITY : 4;
12917
+ const cap = getMenuStyle() === "accordion" ? Number.POSITIVE_INFINITY : 5;
12908
12918
  if (process.env.NODE_ENV !== "production" && requested > cap) {
12909
12919
  console.warn(
12910
12920
  `[Menu] depth={${requested}} exceeds what menuStyle="${getMenuStyle()}" can lay out (${cap}); rendering ${cap} levels. Use menuStyle="accordion" for deeper trees.`
@@ -13000,7 +13010,7 @@ function Menu(rawProps) {
13000
13010
  "button",
13001
13011
  {
13002
13012
  type: "button",
13003
- className: "propeller-menu__toggle px-4 py-3",
13013
+ className: "propeller-menu__toggle cursor-pointer px-4 py-3 text-muted-foreground transition-colors hover:text-foreground",
13004
13014
  "aria-expanded": expanded,
13005
13015
  "aria-label": getCategoryName(cat),
13006
13016
  onClick: () => toggleAt(level, cat.categoryId),
@@ -13022,6 +13032,31 @@ function Menu(rawProps) {
13022
13032
  if (!props.graphqlClient) return;
13023
13033
  fetchMenu(props.categoryId, userKey);
13024
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
+ }
13025
13060
  return /* @__PURE__ */ jsxRuntime.jsxs(
13026
13061
  "div",
13027
13062
  {
@@ -13039,7 +13074,7 @@ function Menu(rawProps) {
13039
13074
  "nav",
13040
13075
  {
13041
13076
  className: `propeller-menu__nav hidden md:block ${props.menuClass || ""}`,
13042
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex bg-popover border border-border shadow-lg", children: renderColumns(menuCategories, 0) })
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) })
13043
13078
  }
13044
13079
  ) : null,
13045
13080
  !isLoading && !hasError && menuCategories.length > 0 && getMenuStyle() === "jumbotron" ? /* @__PURE__ */ jsxRuntime.jsxs(
@@ -13050,7 +13085,7 @@ function Menu(rawProps) {
13050
13085
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-menu__tabs flex items-center border-b border-border", children: menuCategories?.map((l1, idx) => /* @__PURE__ */ jsxRuntime.jsx(
13051
13086
  "button",
13052
13087
  {
13053
- className: `propeller-menu__tab 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"}`,
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"}`,
13054
13089
  "data-level": "1",
13055
13090
  "data-active": isOpenAt(0, l1.categoryId) ? "true" : "false",
13056
13091
  onMouseEnter: (event) => openAt(0, l1.categoryId),
@@ -13092,7 +13127,13 @@ function Menu(rawProps) {
13092
13127
  ]
13093
13128
  }
13094
13129
  ) : null,
13095
- !isLoading && !hasError && menuCategories.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("nav", { className: `propeller-menu__nav md:hidden ${props.menuClass || ""}`, children: renderAccordion(menuCategories, 0) }) : null
13130
+ !isLoading && !hasError && menuCategories.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
13131
+ "nav",
13132
+ {
13133
+ className: `propeller-menu__nav ${getMenuStyle() === "accordion" ? "block" : "md:hidden"} ${props.menuClass || ""}`,
13134
+ children: renderAccordion(menuCategories, 0)
13135
+ }
13136
+ ) : null
13096
13137
  ]
13097
13138
  }
13098
13139
  );