@hectasquare/medusa-plugin-category-tree 1.0.6 → 1.0.7

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.
@@ -6,6 +6,7 @@ export interface CanvasDockProps {
6
6
  onSelectStorefront: (val: string) => void;
7
7
  direction: "TB" | "LR";
8
8
  onToggleDirection: () => void;
9
+ onSelectDirection?: (val: "TB" | "LR") => void;
9
10
  searchQuery: string;
10
11
  onSearchChange: (q: string) => void;
11
12
  onExpandAll: () => void;
@@ -4,9 +4,13 @@ exports.CanvasDock = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const ui_1 = require("@medusajs/ui");
6
6
  const icons_1 = require("@medusajs/icons");
7
- const CanvasDock = ({ salesChannels, selectedStorefront, onSelectStorefront, direction, onToggleDirection, searchQuery, onSearchChange, onExpandAll, onCollapseAll, onFocusRoot, selectedCount = 0, onExpandSelected, onCollapseSelected, isBoxSelect = false, onToggleBoxSelect, onFitView, onOpenCreateRoot, dirtyCount, isSaving, onSave, onReset, }) => {
8
- return ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-2 items-center pointer-events-auto", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2.5 px-3.5 py-2 bg-white/90 dark:bg-neutral-900/90 backdrop-blur-md border border-ui-border-base rounded-2xl shadow-xl transition-all", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5 border-r border-ui-border-base pr-2.5", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-[11px] font-semibold text-ui-fg-muted uppercase tracking-wider", children: "Store:" }), (0, jsx_runtime_1.jsxs)("select", { value: selectedStorefront, onChange: (e) => onSelectStorefront(e.target.value), className: "text-xs bg-ui-bg-subtle text-ui-fg-base border border-ui-border-base rounded-lg px-2 py-1 font-medium focus:outline-none cursor-pointer", children: [(0, jsx_runtime_1.jsx)("option", { value: "all", children: "All Storefronts" }), salesChannels.map((sc) => ((0, jsx_runtime_1.jsx)("option", { value: sc.id, children: sc.name }, sc.id)))] })] }), (0, jsx_runtime_1.jsx)("div", { className: "flex items-center border-r border-ui-border-base pr-2.5", children: (0, jsx_runtime_1.jsx)("button", { type: "button", title: direction === "TB" ? "Switch to Mind Map (Left-to-Right)" : "Switch to Vertical Tree (Top-to-Bottom)", onClick: onToggleDirection, className: "flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-lg bg-ui-bg-subtle text-ui-fg-base hover:bg-ui-bg-base border border-ui-border-base transition-colors", children: (0, jsx_runtime_1.jsx)("span", { children: direction === "TB" ? "⬇ Vertical" : "➡ Mind Map" }) }) }), (0, jsx_runtime_1.jsxs)("div", { className: "relative w-36 sm:w-44", children: [(0, jsx_runtime_1.jsx)("input", { type: "text", placeholder: "Search categories\u2026", value: searchQuery, onChange: (e) => onSearchChange(e.target.value), className: "w-full text-xs pl-7 pr-2.5 py-1 rounded-lg bg-ui-bg-subtle border border-ui-border-base text-ui-fg-base focus:bg-ui-bg-base focus:border-ui-border-interactive focus:outline-none transition-all placeholder:text-ui-fg-muted" }), (0, jsx_runtime_1.jsx)(icons_1.MagnifyingGlass, { className: "absolute left-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-ui-fg-muted pointer-events-none" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1 border-l border-ui-border-base pl-2.5", children: [selectedCount > 0 ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", title: `Expand selected category (${selectedCount} selected)`, onClick: onExpandSelected || onExpandAll, className: "px-2 py-1 text-xs font-medium bg-ui-bg-interactive text-ui-fg-on-color rounded-md transition-colors", children: ["Expand (", selectedCount, ")"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", title: `Collapse selected category (${selectedCount} selected)`, onClick: onCollapseSelected || onCollapseAll, className: "px-2 py-1 text-xs font-medium bg-ui-bg-subtle text-ui-fg-base hover:bg-ui-bg-base border border-ui-border-base rounded-md transition-colors", children: ["Collapse (", selectedCount, ")"] })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("button", { type: "button", title: "Expand All Categories", onClick: onExpandAll, className: "px-2 py-1 text-xs font-medium text-ui-fg-subtle hover:text-ui-fg-base hover:bg-ui-bg-subtle rounded-md transition-colors", children: "Expand All" }), (0, jsx_runtime_1.jsx)("button", { type: "button", title: "Collapse All Subcategories", onClick: onCollapseAll, className: "px-2 py-1 text-xs font-medium text-ui-fg-subtle hover:text-ui-fg-base hover:bg-ui-bg-subtle rounded-md transition-colors", children: "Collapse All" })] })), (0, jsx_runtime_1.jsx)("button", { type: "button", title: "Focus on Root Categories", onClick: onFocusRoot, className: "px-2 py-1 text-xs font-medium text-ui-fg-subtle hover:text-ui-fg-base hover:bg-ui-bg-subtle rounded-md transition-colors", children: "Root" })] }), onToggleBoxSelect && ((0, jsx_runtime_1.jsx)("div", { className: "flex items-center border-l border-ui-border-base pl-2.5", children: (0, jsx_runtime_1.jsx)("button", { type: "button", title: isBoxSelect ? "Box Selection Active (Drag to outline cards). Click to switch to Pan" : "Click to enable Box Select marquee (Outline cards to move together)", onClick: onToggleBoxSelect, className: `px-2 py-1 text-xs font-medium rounded-md transition-colors ${isBoxSelect
7
+ const CanvasDock = ({ salesChannels, selectedStorefront, onSelectStorefront, direction, onToggleDirection, onSelectDirection, searchQuery, onSearchChange, onExpandAll, onCollapseAll, onFocusRoot, selectedCount = 0, onExpandSelected, onCollapseSelected, isBoxSelect = false, onToggleBoxSelect, onZoomIn, onZoomOut, onFitView, onOpenCreateRoot, dirtyCount, isSaving, onSave, onReset, }) => {
8
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-2 items-center pointer-events-auto", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2.5 px-3.5 py-2 bg-white/90 dark:bg-neutral-900/90 backdrop-blur-md border border-ui-border-base rounded-2xl shadow-xl transition-all", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5 border-r border-ui-border-base pr-2.5", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-[11px] font-semibold text-ui-fg-muted uppercase tracking-wider", children: "Store:" }), (0, jsx_runtime_1.jsxs)("select", { value: selectedStorefront, onChange: (e) => onSelectStorefront(e.target.value), className: "text-xs bg-ui-bg-subtle text-ui-fg-base border border-ui-border-base rounded-lg px-2 py-1 font-medium focus:outline-none cursor-pointer", children: [(0, jsx_runtime_1.jsx)("option", { value: "all", children: "All Storefronts" }), salesChannels.map((sc) => ((0, jsx_runtime_1.jsx)("option", { value: sc.id, children: sc.name }, sc.id)))] })] }), (0, jsx_runtime_1.jsx)("div", { className: "flex items-center border-r border-ui-border-base pr-2.5", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center p-0.5 bg-ui-bg-subtle border border-ui-border-base rounded-lg gap-0.5", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => (onSelectDirection ? onSelectDirection("TB") : onToggleDirection()), className: `px-2 py-0.5 text-xs font-medium rounded-md transition-all ${direction === "TB"
9
+ ? "bg-ui-bg-base text-ui-fg-base shadow-xs font-semibold"
10
+ : "text-ui-fg-muted hover:text-ui-fg-base"}`, title: "Vertical Tree Layout (Default)", children: "\u2B07 Vertical" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => (onSelectDirection ? onSelectDirection("LR") : onToggleDirection()), className: `px-2 py-0.5 text-xs font-medium rounded-md transition-all ${direction === "LR"
11
+ ? "bg-ui-bg-base text-ui-fg-base shadow-xs font-semibold"
12
+ : "text-ui-fg-muted hover:text-ui-fg-base"}`, title: "Mind Map Layout (Horizontal)", children: "\u27A1 Mind Map" })] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "relative w-36 sm:w-44", children: [(0, jsx_runtime_1.jsx)("input", { type: "text", placeholder: "Search categories\u2026", value: searchQuery, onChange: (e) => onSearchChange(e.target.value), className: "w-full text-xs pl-7 pr-2.5 py-1 rounded-lg bg-ui-bg-subtle border border-ui-border-base text-ui-fg-base focus:bg-ui-bg-base focus:border-ui-border-interactive focus:outline-none transition-all placeholder:text-ui-fg-muted" }), (0, jsx_runtime_1.jsx)(icons_1.MagnifyingGlass, { className: "absolute left-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-ui-fg-muted pointer-events-none" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1 border-l border-ui-border-base pl-2.5", children: [selectedCount > 0 ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", title: `Expand selected category (${selectedCount} selected)`, onClick: onExpandSelected || onExpandAll, className: "px-2 py-1 text-xs font-medium bg-ui-bg-interactive text-ui-fg-on-color rounded-md transition-colors", children: ["Expand (", selectedCount, ")"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", title: `Collapse selected category (${selectedCount} selected)`, onClick: onCollapseSelected || onCollapseAll, className: "px-2 py-1 text-xs font-medium bg-ui-bg-subtle text-ui-fg-base hover:bg-ui-bg-base border border-ui-border-base rounded-md transition-colors", children: ["Collapse (", selectedCount, ")"] })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("button", { type: "button", title: "Expand All Categories", onClick: onExpandAll, className: "px-2 py-1 text-xs font-medium text-ui-fg-subtle hover:text-ui-fg-base hover:bg-ui-bg-subtle rounded-md transition-colors", children: "Expand All" }), (0, jsx_runtime_1.jsx)("button", { type: "button", title: "Collapse All Subcategories", onClick: onCollapseAll, className: "px-2 py-1 text-xs font-medium text-ui-fg-subtle hover:text-ui-fg-base hover:bg-ui-bg-subtle rounded-md transition-colors", children: "Collapse All" })] })), (0, jsx_runtime_1.jsx)("button", { type: "button", title: "Focus on Root Categories", onClick: onFocusRoot, className: "px-2 py-1 text-xs font-medium text-ui-fg-subtle hover:text-ui-fg-base hover:bg-ui-bg-subtle rounded-md transition-colors", children: "Root" })] }), onToggleBoxSelect && ((0, jsx_runtime_1.jsx)("div", { className: "flex items-center border-l border-ui-border-base pl-2.5", children: (0, jsx_runtime_1.jsx)("button", { type: "button", title: isBoxSelect ? "Box Selection Active (Drag to outline cards). Click to switch to Pan" : "Click to enable Box Select marquee (Outline cards to move together)", onClick: onToggleBoxSelect, className: `px-2 py-1 text-xs font-medium rounded-md transition-colors ${isBoxSelect
9
13
  ? "bg-blue-600 text-white shadow-xs"
10
- : "text-ui-fg-subtle hover:text-ui-fg-base hover:bg-ui-bg-subtle"}`, children: "\u26F6 Box Select" }) })), (0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-1 border-l border-ui-border-base pl-2.5", children: (0, jsx_runtime_1.jsx)("button", { type: "button", title: "Fit View (Center)", onClick: onFitView, className: "p-1 text-ui-fg-subtle hover:text-ui-fg-base hover:bg-ui-bg-subtle rounded-md transition-colors", children: (0, jsx_runtime_1.jsx)(icons_1.ArrowsPointingOut, { className: "w-3.5 h-3.5" }) }) }), (0, jsx_runtime_1.jsx)("div", { className: "border-l border-ui-border-base pl-2.5", children: (0, jsx_runtime_1.jsxs)(ui_1.Button, { size: "small", variant: "secondary", onClick: onOpenCreateRoot, className: "flex items-center gap-1 text-xs h-7", children: [(0, jsx_runtime_1.jsx)(icons_1.Plus, { className: "w-3.5 h-3.5" }), (0, jsx_runtime_1.jsx)("span", { children: "New Root" })] }) })] }), dirtyCount > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 px-4 py-1.5 bg-blue-600 text-white rounded-full shadow-lg text-xs font-medium animate-in fade-in slide-in-from-top-2 duration-200", children: [(0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-1.5", children: [(0, jsx_runtime_1.jsx)("span", { className: "w-2 h-2 rounded-full bg-white animate-pulse" }), (0, jsx_runtime_1.jsxs)("span", { children: [dirtyCount, " categor", dirtyCount === 1 ? "y" : "ies", " repositioned"] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1 border-l border-blue-400 pl-2", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: onReset, disabled: isSaving, className: "px-2 py-0.5 rounded hover:bg-blue-700 transition-colors text-blue-100 hover:text-white", children: "Reset" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: onSave, disabled: isSaving, className: "px-3 py-0.5 rounded-full bg-white text-blue-700 font-semibold hover:bg-blue-50 transition-colors shadow-xs", children: isSaving ? "Saving…" : "Save Hierarchy" })] })] }))] }));
14
+ : "text-ui-fg-subtle hover:text-ui-fg-base hover:bg-ui-bg-subtle"}`, children: "\u26F6 Box Select" }) })), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1 border-l border-ui-border-base pl-2.5", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", title: "Zoom In", onClick: onZoomIn, className: "w-5 h-5 flex items-center justify-center text-xs font-bold text-ui-fg-subtle hover:text-ui-fg-base hover:bg-ui-bg-subtle rounded transition-colors", children: "+" }), (0, jsx_runtime_1.jsx)("button", { type: "button", title: "Zoom Out", onClick: onZoomOut, className: "w-5 h-5 flex items-center justify-center text-xs font-bold text-ui-fg-subtle hover:text-ui-fg-base hover:bg-ui-bg-subtle rounded transition-colors", children: "\u2212" }), (0, jsx_runtime_1.jsx)("button", { type: "button", title: "Fit View (Center)", onClick: onFitView, className: "p-1 text-ui-fg-subtle hover:text-ui-fg-base hover:bg-ui-bg-subtle rounded-md transition-colors", children: (0, jsx_runtime_1.jsx)(icons_1.ArrowsPointingOut, { className: "w-3.5 h-3.5" }) })] }), (0, jsx_runtime_1.jsx)("div", { className: "border-l border-ui-border-base pl-2.5", children: (0, jsx_runtime_1.jsxs)(ui_1.Button, { size: "small", variant: "secondary", onClick: onOpenCreateRoot, className: "flex items-center gap-1 text-xs h-7", children: [(0, jsx_runtime_1.jsx)(icons_1.Plus, { className: "w-3.5 h-3.5" }), (0, jsx_runtime_1.jsx)("span", { children: "New Root" })] }) })] }), dirtyCount > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 px-4 py-1.5 bg-blue-600 text-white rounded-full shadow-lg text-xs font-medium animate-in fade-in slide-in-from-top-2 duration-200", children: [(0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-1.5", children: [(0, jsx_runtime_1.jsx)("span", { className: "w-2 h-2 rounded-full bg-white animate-pulse" }), (0, jsx_runtime_1.jsxs)("span", { children: [dirtyCount, " categor", dirtyCount === 1 ? "y" : "ies", " repositioned"] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1 border-l border-blue-400 pl-2", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: onReset, disabled: isSaving, className: "px-2 py-0.5 rounded hover:bg-blue-700 transition-colors text-blue-100 hover:text-white", children: "Reset" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: onSave, disabled: isSaving, className: "px-3 py-0.5 rounded-full bg-white text-blue-700 font-semibold hover:bg-blue-50 transition-colors shadow-xs", children: isSaving ? "Saving…" : "Save Hierarchy" })] })] }))] }));
11
15
  };
12
16
  exports.CanvasDock = CanvasDock;
@@ -1,8 +1,41 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.CategoryProductsManager = void 0;
4
37
  const jsx_runtime_1 = require("react/jsx-runtime");
5
- const react_1 = require("react");
38
+ const react_1 = __importStar(require("react"));
6
39
  const ui_1 = require("@medusajs/ui");
7
40
  const icons_1 = require("@medusajs/icons");
8
41
  const sdk_1 = require("../../lib/sdk");
@@ -26,6 +59,10 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
26
59
  const [catalogSelectedIds, setCatalogSelectedIds] = (0, react_1.useState)(new Set());
27
60
  const [isAdding, setIsAdding] = (0, react_1.useState)(false);
28
61
  const PAGE_SIZE = 50;
62
+ const orderedProductIdsRef = react_1.default.useRef(orderedProductIds);
63
+ (0, react_1.useEffect)(() => {
64
+ orderedProductIdsRef.current = orderedProductIds;
65
+ }, [orderedProductIds]);
29
66
  // Initialize ranking and top sellers from category metadata
30
67
  (0, react_1.useEffect)(() => {
31
68
  if (initialMetadata) {
@@ -45,8 +82,8 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
45
82
  const fetched = res.products || [];
46
83
  setTotalCount(res.count ?? fetched.length);
47
84
  // Sort products according to orderedProductIds if available
48
- setProducts((prev) => {
49
- const orderMap = new Map(orderedProductIds.map((id, index) => [id, index]));
85
+ setProducts(() => {
86
+ const orderMap = new Map((orderedProductIdsRef.current || []).map((id, index) => [id, index]));
50
87
  return [...fetched].sort((a, b) => {
51
88
  const indexA = orderMap.has(a.id) ? orderMap.get(a.id) : 9999;
52
89
  const indexB = orderMap.has(b.id) ? orderMap.get(b.id) : 9999;
@@ -60,7 +97,7 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
60
97
  finally {
61
98
  setIsLoading(false);
62
99
  }
63
- }, [categoryId, orderedProductIds]);
100
+ }, [categoryId]);
64
101
  (0, react_1.useEffect)(() => {
65
102
  loadAssignedProducts();
66
103
  }, [loadAssignedProducts]);
@@ -234,13 +271,29 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
234
271
  next.add(p.id);
235
272
  return next;
236
273
  });
237
- }, className: "rounded border-ui-border-base cursor-pointer" }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1 font-mono", children: [(0, jsx_runtime_1.jsxs)("span", { className: "text-xs font-bold text-ui-fg-subtle px-1.5 py-0.5 rounded bg-ui-bg-subtle border border-ui-border-base", children: ["#", index + 1] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", title: "Move Up", disabled: index === 0, onClick: () => handleMoveProduct(index, "up"), className: "text-[9px] text-ui-fg-muted hover:text-ui-fg-base disabled:opacity-20 px-0.5 leading-none", children: "\u25B2" }), (0, jsx_runtime_1.jsx)("button", { type: "button", title: "Move Down", disabled: index === displayedProducts.length - 1, onClick: () => handleMoveProduct(index, "down"), className: "text-[9px] text-ui-fg-muted hover:text-ui-fg-base disabled:opacity-20 px-0.5 leading-none", children: "\u25BC" })] })] }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2.5 min-w-0", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-9 h-9 rounded-md border border-ui-border-base overflow-hidden bg-ui-bg-subtle flex-shrink-0 flex items-center justify-center", children: p.thumbnail ? ((0, jsx_runtime_1.jsx)("img", { src: p.thumbnail, alt: p.title, className: "w-full h-full object-cover" })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-[9px] text-ui-fg-muted", children: "IMG" })) }), (0, jsx_runtime_1.jsx)("div", { className: "flex flex-col min-w-0", children: (0, jsx_runtime_1.jsx)("span", { className: "font-semibold text-ui-fg-base truncate leading-snug", title: p.title, children: p.title }) })] }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => handleToggleTopSeller(p.id, p.title), className: `inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[11px] font-semibold border transition-all ${isTopSeller
274
+ }, className: "rounded border-ui-border-base cursor-pointer" }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1 font-mono", children: [(0, jsx_runtime_1.jsxs)("span", { className: "text-xs font-bold text-ui-fg-subtle px-1.5 py-0.5 rounded bg-ui-bg-subtle border border-ui-border-base", children: ["#", index + 1] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", title: "Move Up", disabled: index === 0, onClick: (e) => {
275
+ e.preventDefault();
276
+ e.stopPropagation();
277
+ handleMoveProduct(index, "up");
278
+ }, className: "text-[9px] text-ui-fg-muted hover:text-ui-fg-base disabled:opacity-20 px-0.5 leading-none cursor-pointer", children: "\u25B2" }), (0, jsx_runtime_1.jsx)("button", { type: "button", title: "Move Down", disabled: index === displayedProducts.length - 1, onClick: (e) => {
279
+ e.preventDefault();
280
+ e.stopPropagation();
281
+ handleMoveProduct(index, "down");
282
+ }, className: "text-[9px] text-ui-fg-muted hover:text-ui-fg-base disabled:opacity-20 px-0.5 leading-none cursor-pointer", children: "\u25BC" })] })] }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2.5 min-w-0", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-9 h-9 rounded-md border border-ui-border-base overflow-hidden bg-ui-bg-subtle flex-shrink-0 flex items-center justify-center", children: p.thumbnail ? ((0, jsx_runtime_1.jsx)("img", { src: p.thumbnail, alt: p.title, className: "w-full h-full object-cover" })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-[9px] text-ui-fg-muted", children: "IMG" })) }), (0, jsx_runtime_1.jsx)("div", { className: "flex flex-col min-w-0", children: (0, jsx_runtime_1.jsx)("span", { className: "font-semibold text-ui-fg-base truncate leading-snug", title: p.title, children: p.title }) })] }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: (e) => {
283
+ e.preventDefault();
284
+ e.stopPropagation();
285
+ handleToggleTopSeller(p.id, p.title);
286
+ }, className: `inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[11px] font-semibold border transition-all ${isTopSeller
238
287
  ? "bg-amber-500/15 text-amber-700 dark:text-amber-400 border-amber-300 dark:border-amber-700 shadow-2xs"
239
288
  : "bg-ui-bg-subtle text-ui-fg-muted border-ui-border-base hover:text-amber-600 hover:border-amber-300"}`, title: isTopSeller
240
289
  ? "Click to remove from Top Commercial Sellers"
241
290
  : "Click to mark as Top Commercial Seller (Reflected in Storefront 2)", children: [(0, jsx_runtime_1.jsx)("span", { children: isTopSeller ? "★" : "☆" }), (0, jsx_runtime_1.jsx)("span", { children: isTopSeller ? "Top Seller" : "Normal" })] }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3 text-ui-fg-muted", children: p.collection?.title || "-" }), (0, jsx_runtime_1.jsxs)("td", { className: "py-2.5 px-3 text-ui-fg-subtle", children: [p.variants?.length || 0, " variant(s)"] }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsx)("span", { className: `inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium ${p.status === "published"
242
291
  ? "bg-emerald-50 text-emerald-700 border border-emerald-200 dark:bg-emerald-950/50 dark:text-emerald-300"
243
- : "bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-400"}`, children: p.status }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3 text-right", children: (0, jsx_runtime_1.jsx)("button", { type: "button", title: "Remove from category", onClick: () => handleRemoveProducts([p.id]), className: "p-1 text-ui-fg-muted hover:text-rose-600 hover:bg-rose-50 dark:hover:bg-rose-950/50 rounded transition-colors", children: (0, jsx_runtime_1.jsx)(icons_1.Trash, { className: "w-3.5 h-3.5" }) }) })] }, p.id));
292
+ : "bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-400"}`, children: p.status }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3 text-right", children: (0, jsx_runtime_1.jsx)("button", { type: "button", title: "Remove from category", onClick: (e) => {
293
+ e.preventDefault();
294
+ e.stopPropagation();
295
+ handleRemoveProducts([p.id]);
296
+ }, className: "p-1 text-ui-fg-muted hover:text-rose-600 hover:bg-rose-50 dark:hover:bg-rose-950/50 rounded transition-colors", children: (0, jsx_runtime_1.jsx)(icons_1.Trash, { className: "w-3.5 h-3.5" }) }) })] }, p.id));
244
297
  }) })] })) }), focusModalOpen && ((0, jsx_runtime_1.jsxs)("div", { className: "fixed inset-0 z-50 flex flex-col bg-white dark:bg-neutral-950 animate-in fade-in-50", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between px-6 py-3 border-b border-ui-border-base bg-ui-bg-base flex-shrink-0", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setFocusModalOpen(false), className: "p-1.5 rounded-lg text-ui-fg-muted hover:text-ui-fg-base hover:bg-ui-bg-subtle transition-colors", title: "Close (ESC)", children: (0, jsx_runtime_1.jsx)("span", { className: "text-sm font-bold", children: "\u2715" }) }), (0, jsx_runtime_1.jsx)("span", { className: "text-[11px] font-mono font-medium text-ui-fg-muted px-1.5 py-0.5 rounded bg-ui-bg-subtle border border-ui-border-base", children: "ESC" }), (0, jsx_runtime_1.jsxs)("h2", { className: "text-sm font-semibold text-ui-fg-base", children: ["Add Products to Category: ", categoryName] })] }), (0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "relative w-64", children: [(0, jsx_runtime_1.jsx)("input", { type: "text", placeholder: "Search products\u2026", value: searchQuery, onChange: (e) => {
245
298
  setSearchQuery(e.target.value);
246
299
  setCatalogPage(0);
@@ -265,12 +265,12 @@ const CanvasInner = () => {
265
265
  };
266
266
  return ((0, jsx_runtime_1.jsxs)("div", { className: "relative w-full h-[calc(100vh-56px)] overflow-hidden bg-[#fafafa] dark:bg-[#09090b]", children: [(0, jsx_runtime_1.jsx)("div", { className: `absolute top-4 left-1/2 -translate-x-1/2 z-10 transition-all duration-200 pointer-events-none ${editDrawerOpen || createDrawerOpen
267
267
  ? "opacity-0 -translate-y-6 scale-95 pointer-events-none"
268
- : "opacity-100"}`, children: (0, jsx_runtime_1.jsx)(canvas_dock_1.CanvasDock, { salesChannels: salesChannels, selectedStorefront: selectedStorefront, onSelectStorefront: setSelectedStorefront, direction: direction, onToggleDirection: () => setDirection((d) => (d === "TB" ? "LR" : "TB")), searchQuery: searchQuery, onSearchChange: setSearchQuery, onExpandAll: handleExpandAll, onCollapseAll: handleCollapseAll, onFocusRoot: handleFocusRoot, selectedCount: selectedNodeIds.length, onExpandSelected: handleExpandSelected, onCollapseSelected: handleCollapseSelected, isBoxSelect: isBoxSelect, onToggleBoxSelect: () => setIsBoxSelect((prev) => !prev), onZoomIn: () => zoomIn({ duration: 300 }), onZoomOut: () => zoomOut({ duration: 300 }), onFitView: () => fitView({ padding: 0.25, duration: 400 }), onOpenCreateRoot: () => {
268
+ : "opacity-100"}`, children: (0, jsx_runtime_1.jsx)(canvas_dock_1.CanvasDock, { salesChannels: salesChannels, selectedStorefront: selectedStorefront, onSelectStorefront: setSelectedStorefront, direction: direction, onToggleDirection: () => setDirection((d) => (d === "TB" ? "LR" : "TB")), onSelectDirection: (dir) => setDirection(dir), searchQuery: searchQuery, onSearchChange: setSearchQuery, onExpandAll: handleExpandAll, onCollapseAll: handleCollapseAll, onFocusRoot: handleFocusRoot, selectedCount: selectedNodeIds.length, onExpandSelected: handleExpandSelected, onCollapseSelected: handleCollapseSelected, isBoxSelect: isBoxSelect, onToggleBoxSelect: () => setIsBoxSelect((prev) => !prev), onZoomIn: () => zoomIn({ duration: 300 }), onZoomOut: () => zoomOut({ duration: 300 }), onFitView: () => fitView({ padding: 0.25, duration: 400 }), onOpenCreateRoot: () => {
269
269
  setCreateParentCategory(null);
270
270
  setCreateDrawerOpen(true);
271
271
  }, dirtyCount: dirtyMoves.length, isSaving: isSaving, onSave: handleSave, onReset: handleReset }) }), (0, jsx_runtime_1.jsxs)(react_2.ReactFlow, { nodes: nodes, edges: edges, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, onConnect: onConnect, onEdgesDelete: onEdgesDelete, onSelectionChange: (0, react_1.useCallback)(({ nodes }) => {
272
272
  setSelectedNodeIds(nodes.map((n) => n.id));
273
- }, []), selectionMode: react_2.SelectionMode.Partial, selectionOnDrag: isBoxSelect, panOnDrag: isBoxSelect ? [1, 2] : true, elementsSelectable: true, nodeTypes: nodeTypes, edgeTypes: edgeTypes, minZoom: 0.2, maxZoom: 2.0, defaultEdgeOptions: { type: "smoothstep" }, fitView: true, proOptions: { hideAttribution: true }, className: "touch-none", children: [(0, jsx_runtime_1.jsx)(react_2.Background, { color: "#cbd5e1", gap: 20, size: 1.25 }), (0, jsx_runtime_1.jsx)(react_2.Controls, { showInteractive: false, className: "!bottom-4 !left-4 !bg-white/90 dark:!bg-neutral-900/90 !backdrop-blur-md !border !border-ui-border-base !rounded-xl !shadow-md !overflow-hidden" }), (0, jsx_runtime_1.jsx)(react_2.MiniMap, { nodeColor: () => "var(--bg-interactive, #3b82f6)", maskColor: "rgba(0, 0, 0, 0.08)", className: "!bottom-4 !right-4 !bg-white/90 dark:!bg-neutral-900/90 !backdrop-blur-md !border !border-ui-border-base !rounded-xl !shadow-md !overflow-hidden", zoomable: true, pannable: true })] }), (0, jsx_runtime_1.jsx)(create_category_drawer_1.CreateCategoryDrawer, { open: createDrawerOpen, onOpenChange: setCreateDrawerOpen, parentCategory: createParentCategory, salesChannels: salesChannels, onSuccess: (newCat) => {
273
+ }, []), selectionMode: react_2.SelectionMode.Partial, selectionOnDrag: isBoxSelect, panOnDrag: isBoxSelect ? [1, 2] : true, elementsSelectable: true, nodeTypes: nodeTypes, edgeTypes: edgeTypes, minZoom: 0.2, maxZoom: 2.0, defaultEdgeOptions: { type: "smoothstep" }, fitView: true, proOptions: { hideAttribution: true }, className: "touch-none", children: [(0, jsx_runtime_1.jsx)(react_2.Background, { color: "#cbd5e1", gap: 20, size: 1.25 }), (0, jsx_runtime_1.jsx)(react_2.Controls, { showInteractive: false, className: "!bottom-4 !left-4 !bg-[#18181b] !border !border-neutral-700 !rounded-xl !shadow-2xl !overflow-hidden [&_.react-flow\\_\\_controls-button]:!bg-[#18181b] [&_.react-flow\\_\\_controls-button]:!border-b [&_.react-flow\\_\\_controls-button]:!border-neutral-800 [&_.react-flow\\_\\_controls-button:hover]:!bg-[#27272a] [&_.react-flow\\_\\_controls-button_svg]:!fill-white [&_.react-flow\\_\\_controls-button_svg]:!stroke-white [&_.react-flow\\_\\_controls-button_path]:!fill-white" }), (0, jsx_runtime_1.jsx)(react_2.MiniMap, { nodeColor: () => "var(--bg-interactive, #3b82f6)", maskColor: "rgba(0, 0, 0, 0.25)", className: "!bottom-4 !right-4 !bg-[#18181b]/95 !backdrop-blur-md !border !border-neutral-700 !rounded-xl !shadow-xl !overflow-hidden", zoomable: true, pannable: true })] }), (0, jsx_runtime_1.jsx)(create_category_drawer_1.CreateCategoryDrawer, { open: createDrawerOpen, onOpenChange: setCreateDrawerOpen, parentCategory: createParentCategory, salesChannels: salesChannels, onSuccess: (newCat) => {
274
274
  setCategories((prev) => [...prev, newCat]);
275
275
  setOriginalCategories((prev) => [...prev, newCat]);
276
276
  } }), (0, jsx_runtime_1.jsx)(edit_category_drawer_1.EditCategoryDrawer, { open: editDrawerOpen, onOpenChange: setEditDrawerOpen, category: editingCategory, salesChannels: salesChannels, onSuccess: (updatedCat) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hectasquare/medusa-plugin-category-tree",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "AAA Interactive Category Tree & Mind Map for Medusa v2 Admin with drag-and-drop reparenting, tier indicators, superscript ranks, multi-storefront filtering, and NotebookLM-style subtree collapse.",
5
5
  "author": "HectaSquare Team",
6
6
  "license": "Apache-2.0",
@@ -14,6 +14,7 @@ export interface CanvasDockProps {
14
14
 
15
15
  direction: "TB" | "LR"
16
16
  onToggleDirection: () => void
17
+ onSelectDirection?: (val: "TB" | "LR") => void
17
18
 
18
19
  searchQuery: string
19
20
  onSearchChange: (q: string) => void
@@ -47,6 +48,7 @@ export const CanvasDock: React.FC<CanvasDockProps> = ({
47
48
  onSelectStorefront,
48
49
  direction,
49
50
  onToggleDirection,
51
+ onSelectDirection,
50
52
  searchQuery,
51
53
  onSearchChange,
52
54
  onExpandAll,
@@ -57,6 +59,8 @@ export const CanvasDock: React.FC<CanvasDockProps> = ({
57
59
  onCollapseSelected,
58
60
  isBoxSelect = false,
59
61
  onToggleBoxSelect,
62
+ onZoomIn,
63
+ onZoomOut,
60
64
  onFitView,
61
65
  onOpenCreateRoot,
62
66
  dirtyCount,
@@ -86,14 +90,32 @@ export const CanvasDock: React.FC<CanvasDockProps> = ({
86
90
  </div>
87
91
 
88
92
  <div className="flex items-center border-r border-ui-border-base pr-2.5">
89
- <button
90
- type="button"
91
- title={direction === "TB" ? "Switch to Mind Map (Left-to-Right)" : "Switch to Vertical Tree (Top-to-Bottom)"}
92
- onClick={onToggleDirection}
93
- className="flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-lg bg-ui-bg-subtle text-ui-fg-base hover:bg-ui-bg-base border border-ui-border-base transition-colors"
94
- >
95
- <span>{direction === "TB" ? "⬇ Vertical" : "➡ Mind Map"}</span>
96
- </button>
93
+ <div className="flex items-center p-0.5 bg-ui-bg-subtle border border-ui-border-base rounded-lg gap-0.5">
94
+ <button
95
+ type="button"
96
+ onClick={() => (onSelectDirection ? onSelectDirection("TB") : onToggleDirection())}
97
+ className={`px-2 py-0.5 text-xs font-medium rounded-md transition-all ${
98
+ direction === "TB"
99
+ ? "bg-ui-bg-base text-ui-fg-base shadow-xs font-semibold"
100
+ : "text-ui-fg-muted hover:text-ui-fg-base"
101
+ }`}
102
+ title="Vertical Tree Layout (Default)"
103
+ >
104
+ ⬇ Vertical
105
+ </button>
106
+ <button
107
+ type="button"
108
+ onClick={() => (onSelectDirection ? onSelectDirection("LR") : onToggleDirection())}
109
+ className={`px-2 py-0.5 text-xs font-medium rounded-md transition-all ${
110
+ direction === "LR"
111
+ ? "bg-ui-bg-base text-ui-fg-base shadow-xs font-semibold"
112
+ : "text-ui-fg-muted hover:text-ui-fg-base"
113
+ }`}
114
+ title="Mind Map Layout (Horizontal)"
115
+ >
116
+ ➡ Mind Map
117
+ </button>
118
+ </div>
97
119
  </div>
98
120
 
99
121
  <div className="relative w-36 sm:w-44">
@@ -178,6 +200,22 @@ export const CanvasDock: React.FC<CanvasDockProps> = ({
178
200
  )}
179
201
 
180
202
  <div className="flex items-center gap-1 border-l border-ui-border-base pl-2.5">
203
+ <button
204
+ type="button"
205
+ title="Zoom In"
206
+ onClick={onZoomIn}
207
+ className="w-5 h-5 flex items-center justify-center text-xs font-bold text-ui-fg-subtle hover:text-ui-fg-base hover:bg-ui-bg-subtle rounded transition-colors"
208
+ >
209
+ +
210
+ </button>
211
+ <button
212
+ type="button"
213
+ title="Zoom Out"
214
+ onClick={onZoomOut}
215
+ className="w-5 h-5 flex items-center justify-center text-xs font-bold text-ui-fg-subtle hover:text-ui-fg-base hover:bg-ui-bg-subtle rounded transition-colors"
216
+ >
217
+ −
218
+ </button>
181
219
  <button
182
220
  type="button"
183
221
  title="Fit View (Center)"
@@ -59,6 +59,11 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
59
59
 
60
60
  const PAGE_SIZE = 50
61
61
 
62
+ const orderedProductIdsRef = React.useRef<string[]>(orderedProductIds)
63
+ useEffect(() => {
64
+ orderedProductIdsRef.current = orderedProductIds
65
+ }, [orderedProductIds])
66
+
62
67
  // Initialize ranking and top sellers from category metadata
63
68
  useEffect(() => {
64
69
  if (initialMetadata) {
@@ -82,8 +87,8 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
82
87
  setTotalCount(res.count ?? fetched.length)
83
88
 
84
89
  // Sort products according to orderedProductIds if available
85
- setProducts((prev) => {
86
- const orderMap = new Map(orderedProductIds.map((id, index) => [id, index]))
90
+ setProducts(() => {
91
+ const orderMap = new Map((orderedProductIdsRef.current || []).map((id, index) => [id, index]))
87
92
  return [...fetched].sort((a, b) => {
88
93
  const indexA = orderMap.has(a.id) ? orderMap.get(a.id)! : 9999
89
94
  const indexB = orderMap.has(b.id) ? orderMap.get(b.id)! : 9999
@@ -95,7 +100,7 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
95
100
  } finally {
96
101
  setIsLoading(false)
97
102
  }
98
- }, [categoryId, orderedProductIds])
103
+ }, [categoryId])
99
104
 
100
105
  useEffect(() => {
101
106
  loadAssignedProducts()
@@ -404,8 +409,12 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
404
409
  type="button"
405
410
  title="Move Up"
406
411
  disabled={index === 0}
407
- onClick={() => handleMoveProduct(index, "up")}
408
- className="text-[9px] text-ui-fg-muted hover:text-ui-fg-base disabled:opacity-20 px-0.5 leading-none"
412
+ onClick={(e) => {
413
+ e.preventDefault()
414
+ e.stopPropagation()
415
+ handleMoveProduct(index, "up")
416
+ }}
417
+ className="text-[9px] text-ui-fg-muted hover:text-ui-fg-base disabled:opacity-20 px-0.5 leading-none cursor-pointer"
409
418
  >
410
419
  ▲
411
420
  </button>
@@ -413,8 +422,12 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
413
422
  type="button"
414
423
  title="Move Down"
415
424
  disabled={index === displayedProducts.length - 1}
416
- onClick={() => handleMoveProduct(index, "down")}
417
- className="text-[9px] text-ui-fg-muted hover:text-ui-fg-base disabled:opacity-20 px-0.5 leading-none"
425
+ onClick={(e) => {
426
+ e.preventDefault()
427
+ e.stopPropagation()
428
+ handleMoveProduct(index, "down")
429
+ }}
430
+ className="text-[9px] text-ui-fg-muted hover:text-ui-fg-base disabled:opacity-20 px-0.5 leading-none cursor-pointer"
418
431
  >
419
432
  ▼
420
433
  </button>
@@ -444,7 +457,11 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
444
457
  <td className="py-2.5 px-3">
445
458
  <button
446
459
  type="button"
447
- onClick={() => handleToggleTopSeller(p.id, p.title)}
460
+ onClick={(e) => {
461
+ e.preventDefault()
462
+ e.stopPropagation()
463
+ handleToggleTopSeller(p.id, p.title)
464
+ }}
448
465
  className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[11px] font-semibold border transition-all ${
449
466
  isTopSeller
450
467
  ? "bg-amber-500/15 text-amber-700 dark:text-amber-400 border-amber-300 dark:border-amber-700 shadow-2xs"
@@ -485,7 +502,11 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
485
502
  <button
486
503
  type="button"
487
504
  title="Remove from category"
488
- onClick={() => handleRemoveProducts([p.id])}
505
+ onClick={(e) => {
506
+ e.preventDefault()
507
+ e.stopPropagation()
508
+ handleRemoveProducts([p.id])
509
+ }}
489
510
  className="p-1 text-ui-fg-muted hover:text-rose-600 hover:bg-rose-50 dark:hover:bg-rose-950/50 rounded transition-colors"
490
511
  >
491
512
  <Trash className="w-3.5 h-3.5" />
@@ -357,6 +357,7 @@ const CanvasInner: React.FC = () => {
357
357
  onSelectStorefront={setSelectedStorefront}
358
358
  direction={direction}
359
359
  onToggleDirection={() => setDirection((d) => (d === "TB" ? "LR" : "TB"))}
360
+ onSelectDirection={(dir) => setDirection(dir)}
360
361
  searchQuery={searchQuery}
361
362
  onSearchChange={setSearchQuery}
362
363
  onExpandAll={handleExpandAll}
@@ -407,12 +408,12 @@ const CanvasInner: React.FC = () => {
407
408
  <Background color="#cbd5e1" gap={20} size={1.25} />
408
409
  <Controls
409
410
  showInteractive={false}
410
- className="!bottom-4 !left-4 !bg-white/90 dark:!bg-neutral-900/90 !backdrop-blur-md !border !border-ui-border-base !rounded-xl !shadow-md !overflow-hidden"
411
+ className="!bottom-4 !left-4 !bg-[#18181b] !border !border-neutral-700 !rounded-xl !shadow-2xl !overflow-hidden [&_.react-flow\_\_controls-button]:!bg-[#18181b] [&_.react-flow\_\_controls-button]:!border-b [&_.react-flow\_\_controls-button]:!border-neutral-800 [&_.react-flow\_\_controls-button:hover]:!bg-[#27272a] [&_.react-flow\_\_controls-button_svg]:!fill-white [&_.react-flow\_\_controls-button_svg]:!stroke-white [&_.react-flow\_\_controls-button_path]:!fill-white"
411
412
  />
412
413
  <MiniMap
413
414
  nodeColor={() => "var(--bg-interactive, #3b82f6)"}
414
- maskColor="rgba(0, 0, 0, 0.08)"
415
- className="!bottom-4 !right-4 !bg-white/90 dark:!bg-neutral-900/90 !backdrop-blur-md !border !border-ui-border-base !rounded-xl !shadow-md !overflow-hidden"
415
+ maskColor="rgba(0, 0, 0, 0.25)"
416
+ className="!bottom-4 !right-4 !bg-[#18181b]/95 !backdrop-blur-md !border !border-neutral-700 !rounded-xl !shadow-xl !overflow-hidden"
416
417
  zoomable
417
418
  pannable
418
419
  />