@propeller-commerce/propeller-v2-react-ui 0.11.0 → 0.11.2

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,71 @@ 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.2] - 2026-07-31
12
+
13
+ ### Fixed
14
+
15
+ - **`<ProductSlider>` dropped `onLoginClick`, so its log-in action did
16
+ nothing.** In a semi-closed portal `ProductCard` replaces add-to-cart with
17
+ `<LoginToOrderButton>`, and the card resolves `onLoginClick` from the grid
18
+ config. `ProductGrid` puts it there; the slider never did — it forwards every
19
+ other card callback (`onProceedToCheckout`, `onRequestQuoteClick`,
20
+ `afterAddToCart`, …) but this one was missed when the semi-closed gating
21
+ landed in 0.8.0. Anonymous visitors got a button that looked clickable and
22
+ went nowhere, on every slider — cross-sells, up-sells and CMS product blocks.
23
+ It is now forwarded like the rest.
24
+
25
+ ## [0.11.1] - 2026-07-30
26
+
27
+ ### Fixed
28
+
29
+ - **`menuStyle="accordion"` rendered nothing above `md`.** The accordion is the
30
+ mobile drawer for every style, so its `<nav>` carried `md:hidden` and rendered
31
+ unconditionally — while the desktop branches were gated on `menuStyle`.
32
+ Selecting it therefore matched no desktop branch and hid the mobile one,
33
+ leaving an empty panel. It now renders at every breakpoint when chosen
34
+ explicitly, and stays `md:hidden` as the mobile presentation of the others.
35
+ This is what 0.11.0's depth warning already recommended for deep trees.
36
+ - **`menuStyle` values with no renderer failed silently.** An unrecognised
37
+ style matched no branch and produced an empty panel with nothing to indicate
38
+ why. It now falls back to `accordion` — the style that can display any tree —
39
+ and warns in development, pointing at `renderMenu`.
40
+ - **The accordion's expand control didn't look clickable.** Tailwind v4 resets
41
+ buttons to `cursor: default`, so the chevron — the only control that expands a
42
+ branch — gave no affordance. Added `cursor-pointer` plus a hover colour shift,
43
+ and the same to the jumbotron tabs, which had it too.
44
+ - **A deep flyout could run off the viewport with no way to reach it.** The
45
+ columns row now has `max-w-[100vw] overflow-x-auto`, so a tree wider than the
46
+ screen degrades to a scroll instead of being unreachable.
47
+
48
+ ### Added
49
+
50
+ - **`renderMenu` — render the menu yourself.** The built-in styles are three
51
+ arrangements of the same tree; this is the escape hatch for a fourth. It
52
+ receives a `MenuRenderContext` carrying the categories, the open-path state
53
+ and the same helpers the built-ins use (`getCategoryUrl` honouring
54
+ `getUrl`/`configuration`, `handleItemClick` firing `onMenuItemClick`,
55
+ `openAt`/`toggleAt`/`isOpenAt`), so a custom menu gets working open/close
56
+ behaviour and URL building without reimplementing them and drifting. Return
57
+ `null` to fall through to the `menuStyle` rendering. `MenuStyle` and
58
+ `MenuRenderContext` are exported.
59
+ - **First tests for `Menu`** (9 cases), covering the failures above: they all
60
+ passed type-checking and rendered *something*, so only a DOM assertion catches
61
+ them.
62
+
63
+ ### Changed
64
+
65
+ - **`dropdown-horizontal` removed.** It was referenced only in the depth-cap
66
+ logic and never had a renderer, so it advertised a style that drew nothing.
67
+ Callers using it now hit the unknown-style fallback (accordion + warning); use
68
+ `renderMenu` for a genuinely custom layout.
69
+ - **`dropdown-vertical` caps at 5 levels, up from 4.** Each level is a 256px
70
+ column, so five total 1280px — within a 1440px desktop. Paired with the
71
+ overflow guard above for narrower screens.
72
+ - **`menuStyle` is typed `MenuStyle | (string & {})`** rather than `string`: the
73
+ built-ins autocomplete while a custom value still type-checks for use with
74
+ `renderMenu`.
75
+
11
76
  ## [0.11.0] - 2026-07-30
12
77
 
13
78
  ### 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
  );
@@ -15996,6 +16037,7 @@ function ProductSlider(rawProps) {
15996
16037
  afterAddToCart: props.afterAddToCart,
15997
16038
  onProceedToCheckout: props.onProceedToCheckout,
15998
16039
  onRequestQuoteClick: props.onRequestQuoteClick,
16040
+ onLoginClick: props.onLoginClick,
15999
16041
  onToggleFavorite: (item, isFav) => props.onToggleFavorite?.(item, isFav),
16000
16042
  onProductClick: (p) => handleProductClick(p),
16001
16043
  onClusterClick: (c) => handleClusterClick(c),