@hectasquare/medusa-plugin-category-tree 1.0.1 → 1.0.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.
Files changed (21) hide show
  1. package/.medusa/server/src/admin/components/category-tree/canvas-dock.d.ts +5 -0
  2. package/.medusa/server/src/admin/components/category-tree/canvas-dock.js +4 -2
  3. package/.medusa/server/src/admin/components/category-tree/category-node.js +12 -2
  4. package/.medusa/server/src/admin/components/category-tree/category-tree-canvas.js +42 -3
  5. package/.medusa/server/src/admin/components/category-tree/edit-category-drawer.js +5 -1
  6. package/.medusa/server/src/admin/components/category-tree/lib/layout-engine.d.ts +9 -1
  7. package/.medusa/server/src/admin/components/category-tree/lib/layout-engine.js +116 -30
  8. package/.medusa/server/src/admin/components/category-tree/lib/tree-state.d.ts +14 -0
  9. package/.medusa/server/src/admin/components/category-tree/lib/tree-state.js +95 -0
  10. package/.medusa/server/src/admin/components/category-tree/tier-bars.d.ts +4 -2
  11. package/.medusa/server/src/admin/components/category-tree/tier-bars.js +9 -4
  12. package/.medusa/server/src/admin/components/category-tree/types.d.ts +4 -0
  13. package/package.json +1 -1
  14. package/src/admin/components/category-tree/canvas-dock.tsx +71 -18
  15. package/src/admin/components/category-tree/category-node.tsx +48 -9
  16. package/src/admin/components/category-tree/category-tree-canvas.tsx +56 -2
  17. package/src/admin/components/category-tree/edit-category-drawer.tsx +54 -0
  18. package/src/admin/components/category-tree/lib/layout-engine.ts +152 -25
  19. package/src/admin/components/category-tree/lib/tree-state.ts +112 -0
  20. package/src/admin/components/category-tree/tier-bars.tsx +12 -5
  21. package/src/admin/components/category-tree/types.ts +4 -0
@@ -19,6 +19,7 @@ export type CategoryTreeNode = {
19
19
  sibling_rank: number;
20
20
  children_count: number;
21
21
  has_children: boolean;
22
+ branch_color?: string;
22
23
  };
23
24
  export type CategoryTreeResponse = {
24
25
  categories: CategoryTreeNode[];
@@ -35,6 +36,7 @@ export type CategoryNodeData = {
35
36
  tier: number;
36
37
  siblingRank: number;
37
38
  superscriptRank: string;
39
+ branchColor?: string;
38
40
  salesChannels: SalesChannelInfo[];
39
41
  isCollapsed: boolean;
40
42
  hiddenDescendantCount: number;
@@ -44,4 +46,6 @@ export type CategoryNodeData = {
44
46
  onAddChild: (parent: CategoryTreeNode) => void;
45
47
  onEdit: (category: CategoryTreeNode) => void;
46
48
  onDetach: (id: string) => void;
49
+ onMoveSiblingUp?: (id: string) => void;
50
+ onMoveSiblingDown?: (id: string) => void;
47
51
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hectasquare/medusa-plugin-category-tree",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
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": "MIT",
@@ -22,6 +22,13 @@ export interface CanvasDockProps {
22
22
  onCollapseAll: () => void
23
23
  onFocusRoot: () => void
24
24
 
25
+ selectedCount?: number
26
+ onExpandSelected?: () => void
27
+ onCollapseSelected?: () => void
28
+
29
+ isBoxSelect?: boolean
30
+ onToggleBoxSelect?: () => void
31
+
25
32
  onZoomIn: () => void
26
33
  onZoomOut: () => void
27
34
  onFitView: () => void
@@ -45,6 +52,11 @@ export const CanvasDock: React.FC<CanvasDockProps> = ({
45
52
  onExpandAll,
46
53
  onCollapseAll,
47
54
  onFocusRoot,
55
+ selectedCount = 0,
56
+ onExpandSelected,
57
+ onCollapseSelected,
58
+ isBoxSelect = false,
59
+ onToggleBoxSelect,
48
60
  onFitView,
49
61
  onOpenCreateRoot,
50
62
  dirtyCount,
@@ -84,7 +96,7 @@ export const CanvasDock: React.FC<CanvasDockProps> = ({
84
96
  </button>
85
97
  </div>
86
98
 
87
- <div className="relative w-40 sm:w-48">
99
+ <div className="relative w-36 sm:w-44">
88
100
  <input
89
101
  type="text"
90
102
  placeholder="Search categories…"
@@ -95,24 +107,47 @@ export const CanvasDock: React.FC<CanvasDockProps> = ({
95
107
  <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" />
96
108
  </div>
97
109
 
110
+ {/* Expand / Collapse Section with Selective awareness */}
98
111
  <div className="flex items-center gap-1 border-l border-ui-border-base pl-2.5">
99
- <button
100
- type="button"
101
- title="Expand All Categories"
102
- onClick={onExpandAll}
103
- 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"
104
- >
105
- Expand
106
- </button>
107
-
108
- <button
109
- type="button"
110
- title="Collapse All Subcategories"
111
- onClick={onCollapseAll}
112
- 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"
113
- >
114
- Collapse
115
- </button>
112
+ {selectedCount > 0 ? (
113
+ <>
114
+ <button
115
+ type="button"
116
+ title={`Expand selected category (${selectedCount} selected)`}
117
+ onClick={onExpandSelected || onExpandAll}
118
+ className="px-2 py-1 text-xs font-medium bg-ui-bg-interactive text-ui-fg-on-color rounded-md transition-colors"
119
+ >
120
+ Expand ({selectedCount})
121
+ </button>
122
+ <button
123
+ type="button"
124
+ title={`Collapse selected category (${selectedCount} selected)`}
125
+ onClick={onCollapseSelected || onCollapseAll}
126
+ 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"
127
+ >
128
+ Collapse ({selectedCount})
129
+ </button>
130
+ </>
131
+ ) : (
132
+ <>
133
+ <button
134
+ type="button"
135
+ title="Expand All Categories"
136
+ onClick={onExpandAll}
137
+ 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"
138
+ >
139
+ Expand All
140
+ </button>
141
+ <button
142
+ type="button"
143
+ title="Collapse All Subcategories"
144
+ onClick={onCollapseAll}
145
+ 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"
146
+ >
147
+ Collapse All
148
+ </button>
149
+ </>
150
+ )}
116
151
 
117
152
  <button
118
153
  type="button"
@@ -124,6 +159,24 @@ export const CanvasDock: React.FC<CanvasDockProps> = ({
124
159
  </button>
125
160
  </div>
126
161
 
162
+ {/* Box Selection / Marquee mode toggle */}
163
+ {onToggleBoxSelect && (
164
+ <div className="flex items-center border-l border-ui-border-base pl-2.5">
165
+ <button
166
+ type="button"
167
+ 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)"}
168
+ onClick={onToggleBoxSelect}
169
+ className={`px-2 py-1 text-xs font-medium rounded-md transition-colors ${
170
+ isBoxSelect
171
+ ? "bg-blue-600 text-white shadow-xs"
172
+ : "text-ui-fg-subtle hover:text-ui-fg-base hover:bg-ui-bg-subtle"
173
+ }`}
174
+ >
175
+ ⛶ Box Select
176
+ </button>
177
+ </div>
178
+ )}
179
+
127
180
  <div className="flex items-center gap-1 border-l border-ui-border-base pl-2.5">
128
181
  <button
129
182
  type="button"
@@ -15,7 +15,9 @@ export const CategoryNode = memo(({ id, data, selected }: CategoryNodeProps) =>
15
15
  const {
16
16
  category,
17
17
  tier,
18
+ siblingRank,
18
19
  superscriptRank,
20
+ branchColor,
19
21
  salesChannels,
20
22
  isCollapsed,
21
23
  hiddenDescendantCount,
@@ -25,6 +27,8 @@ export const CategoryNode = memo(({ id, data, selected }: CategoryNodeProps) =>
25
27
  onAddChild,
26
28
  onEdit,
27
29
  onDetach,
30
+ onMoveSiblingUp,
31
+ onMoveSiblingDown,
28
32
  } = data
29
33
 
30
34
  const isTB = direction === "TB"
@@ -42,6 +46,13 @@ export const CategoryNode = memo(({ id, data, selected }: CategoryNodeProps) =>
42
46
  ? "ring-2 ring-ui-border-interactive border-transparent shadow-lg"
43
47
  : "border-ui-border-base shadow-sm hover:shadow-md hover:border-ui-border-strong"
44
48
  } ${isDimmed ? "opacity-35 grayscale" : "opacity-100"}`}
49
+ style={
50
+ selected && branchColor
51
+ ? {
52
+ boxShadow: `0 0 0 2px ${branchColor}, 0 10px 15px -3px rgba(0, 0, 0, 0.1)`,
53
+ }
54
+ : undefined
55
+ }
45
56
  >
46
57
  <Handle
47
58
  type="target"
@@ -49,7 +60,7 @@ export const CategoryNode = memo(({ id, data, selected }: CategoryNodeProps) =>
49
60
  className="!w-2.5 !h-2.5 !bg-ui-border-interactive !border-2 !border-white dark:!border-neutral-900 transition-transform group-hover:scale-125"
50
61
  />
51
62
 
52
- <TierBars tier={tier} />
63
+ <TierBars tier={tier} color={branchColor} />
53
64
 
54
65
  <div className="flex flex-1 items-center gap-2.5 px-2.5 py-2 min-w-0 overflow-hidden">
55
66
  <div className="relative w-11 h-11 rounded-lg overflow-hidden flex-shrink-0 border border-ui-border-base bg-ui-bg-subtle flex items-center justify-center shadow-2xs">
@@ -74,20 +85,48 @@ export const CategoryNode = memo(({ id, data, selected }: CategoryNodeProps) =>
74
85
  </div>
75
86
 
76
87
  <div className="flex flex-col min-w-0 flex-1 justify-center">
77
- <div className="flex items-center gap-1.5 min-w-0">
88
+ <div className="flex items-center gap-1 min-w-0">
78
89
  <span
79
- className="text-xs font-semibold text-ui-fg-base truncate leading-snug"
90
+ className="text-xs font-semibold text-ui-fg-base truncate leading-snug flex-1 min-w-0"
80
91
  title={category.name}
81
92
  >
82
93
  {category.name}
83
94
  </span>
84
95
  {superscriptRank && (
85
- <span
86
- className="text-[11px] font-bold font-mono text-ui-fg-subtle px-1 py-0.5 rounded bg-ui-bg-subtle/80 flex-shrink-0"
87
- title={`Rank #${data.siblingRank} among siblings`}
88
- >
89
- {superscriptRank}
90
- </span>
96
+ <div className="flex items-center gap-0.5 flex-shrink-0">
97
+ <span
98
+ className="text-[11px] font-bold font-mono text-ui-fg-subtle px-1 py-0.5 rounded bg-ui-bg-subtle/80"
99
+ title={`Rank #${siblingRank} among siblings (Storefront sort order)`}
100
+ >
101
+ {superscriptRank}
102
+ </span>
103
+ {onMoveSiblingUp && (
104
+ <button
105
+ type="button"
106
+ title="Move Up in Storefront Order (↑)"
107
+ onClick={(e) => {
108
+ e.stopPropagation()
109
+ onMoveSiblingUp(id)
110
+ }}
111
+ className="w-3.5 h-3.5 flex items-center justify-center text-[10px] font-bold text-ui-fg-muted hover:text-ui-fg-base hover:bg-ui-bg-subtle rounded transition-colors"
112
+ >
113
+ ↑
114
+ </button>
115
+ )}
116
+ {onMoveSiblingDown && (
117
+ <button
118
+ type="button"
119
+ title="Move Down in Storefront Order (↓)"
120
+ onClick={(e) => {
121
+ e.stopPropagation()
122
+ onMoveSiblingDown(id)
123
+ }}
124
+ className="w-3.5 h-3.5 flex items-center justify-center text-[10px] font-bold text-ui-fg-muted hover:text-ui-fg-base hover:bg-ui-bg-subtle rounded transition-colors"
125
+ >
126
+ ↓
127
+ </button>
128
+ )}
129
+ </div>
91
130
  )}
92
131
  </div>
93
132
 
@@ -11,6 +11,7 @@ import {
11
11
  Node,
12
12
  useReactFlow,
13
13
  ReactFlowProvider,
14
+ SelectionMode,
14
15
  } from "@xyflow/react"
15
16
  import "@xyflow/react/dist/style.css"
16
17
  import { toast, TooltipProvider } from "@medusajs/ui"
@@ -24,11 +25,13 @@ import { CategoryTreeNode, SalesChannelInfo } from "./types"
24
25
  import {
25
26
  computeTiers,
26
27
  computeSiblingRanks,
28
+ computeBranchColors,
29
+ moveSiblingRank,
27
30
  wouldCreateCycle,
28
31
  getVisibleTree,
29
32
  computeMoves,
30
33
  } from "./lib/tree-state"
31
- import { computeDagreLayout } from "./lib/layout-engine"
34
+ import { computeTreeLayout } from "./lib/layout-engine"
32
35
  import { sdk } from "../../lib/sdk"
33
36
 
34
37
  const nodeTypes = {
@@ -52,6 +55,8 @@ const CanvasInner: React.FC = () => {
52
55
  const [selectedStorefront, setSelectedStorefront] = useState<string>("all")
53
56
  const [searchQuery, setSearchQuery] = useState("")
54
57
  const [collapsedIds, setCollapsedIds] = useState<Set<string>>(new Set())
58
+ const [isBoxSelect, setIsBoxSelect] = useState(false)
59
+ const [selectedNodeIds, setSelectedNodeIds] = useState<string[]>([])
55
60
 
56
61
  const [createDrawerOpen, setCreateDrawerOpen] = useState(false)
57
62
  const [createParentCategory, setCreateParentCategory] = useState<CategoryTreeNode | null>(null)
@@ -95,6 +100,7 @@ const CanvasInner: React.FC = () => {
95
100
 
96
101
  const tiers = computeTiers(categories)
97
102
  const siblingRanks = computeSiblingRanks(categories)
103
+ const branchColors = computeBranchColors(categories)
98
104
 
99
105
  const enriched: CategoryTreeNode[] = categories.map((cat) => {
100
106
  const kids = map.get(cat.id) || []
@@ -105,6 +111,7 @@ const CanvasInner: React.FC = () => {
105
111
  sibling_rank: rankInfo?.rankNum || 1,
106
112
  children_count: kids.length,
107
113
  has_children: kids.length > 0,
114
+ branch_color: branchColors.get(cat.id),
108
115
  }
109
116
  })
110
117
 
@@ -123,6 +130,32 @@ const CanvasInner: React.FC = () => {
123
130
  })
124
131
  }, [])
125
132
 
133
+ const handleExpandSelected = useCallback(() => {
134
+ if (selectedNodeIds.length === 0) return
135
+ setCollapsedIds((prev) => {
136
+ const next = new Set(prev)
137
+ selectedNodeIds.forEach((id) => next.delete(id))
138
+ return next
139
+ })
140
+ }, [selectedNodeIds])
141
+
142
+ const handleCollapseSelected = useCallback(() => {
143
+ if (selectedNodeIds.length === 0) return
144
+ setCollapsedIds((prev) => {
145
+ const next = new Set(prev)
146
+ selectedNodeIds.forEach((id) => next.add(id))
147
+ return next
148
+ })
149
+ }, [selectedNodeIds])
150
+
151
+ const handleMoveSiblingUp = useCallback((id: string) => {
152
+ setCategories((prev) => moveSiblingRank(prev, id, "up"))
153
+ }, [])
154
+
155
+ const handleMoveSiblingDown = useCallback((id: string) => {
156
+ setCategories((prev) => moveSiblingRank(prev, id, "down"))
157
+ }, [])
158
+
126
159
  const handleAddChild = useCallback((parent: CategoryTreeNode) => {
127
160
  setCreateParentCategory(parent)
128
161
  setCreateDrawerOpen(true)
@@ -171,6 +204,7 @@ const CanvasInner: React.FC = () => {
171
204
  tier: cat.tier,
172
205
  siblingRank: rankInfo?.rankNum || 1,
173
206
  superscriptRank: rankInfo?.superscript || "¹",
207
+ branchColor: cat.branch_color,
174
208
  salesChannels,
175
209
  isCollapsed: collapsedIds.has(cat.id),
176
210
  hiddenDescendantCount: cat.children_count,
@@ -180,6 +214,8 @@ const CanvasInner: React.FC = () => {
180
214
  onAddChild: handleAddChild,
181
215
  onEdit: handleEdit,
182
216
  onDetach: handleDetach,
217
+ onMoveSiblingUp: handleMoveSiblingUp,
218
+ onMoveSiblingDown: handleMoveSiblingDown,
183
219
  },
184
220
  }
185
221
  })
@@ -192,6 +228,10 @@ const CanvasInner: React.FC = () => {
192
228
  source: cat.parent_category_id,
193
229
  target: cat.id,
194
230
  type: "smoothstep",
231
+ style: {
232
+ stroke: cat.branch_color || "#64748b",
233
+ strokeWidth: 2,
234
+ },
195
235
  })
196
236
  }
197
237
  }
@@ -203,7 +243,7 @@ const CanvasInner: React.FC = () => {
203
243
  childrenMap
204
244
  )
205
245
 
206
- const { nodes: layoutedNodes, edges: layoutedEdges } = computeDagreLayout(
246
+ const { nodes: layoutedNodes, edges: layoutedEdges } = computeTreeLayout(
207
247
  visibleNodes,
208
248
  visibleEdges,
209
249
  { direction }
@@ -223,6 +263,8 @@ const CanvasInner: React.FC = () => {
223
263
  handleAddChild,
224
264
  handleEdit,
225
265
  handleDetach,
266
+ handleMoveSiblingUp,
267
+ handleMoveSiblingDown,
226
268
  setNodes,
227
269
  setEdges,
228
270
  ])
@@ -311,6 +353,11 @@ const CanvasInner: React.FC = () => {
311
353
  onExpandAll={handleExpandAll}
312
354
  onCollapseAll={handleCollapseAll}
313
355
  onFocusRoot={handleFocusRoot}
356
+ selectedCount={selectedNodeIds.length}
357
+ onExpandSelected={handleExpandSelected}
358
+ onCollapseSelected={handleCollapseSelected}
359
+ isBoxSelect={isBoxSelect}
360
+ onToggleBoxSelect={() => setIsBoxSelect((prev) => !prev)}
314
361
  onZoomIn={() => zoomIn({ duration: 300 })}
315
362
  onZoomOut={() => zoomOut({ duration: 300 })}
316
363
  onFitView={() => fitView({ padding: 0.25, duration: 400 })}
@@ -332,6 +379,13 @@ const CanvasInner: React.FC = () => {
332
379
  onEdgesChange={onEdgesChange}
333
380
  onConnect={onConnect}
334
381
  onEdgesDelete={onEdgesDelete}
382
+ onSelectionChange={useCallback(({ nodes }: { nodes: Node[] }) => {
383
+ setSelectedNodeIds(nodes.map((n) => n.id))
384
+ }, [])}
385
+ selectionMode={SelectionMode.Partial}
386
+ selectionOnDrag={isBoxSelect}
387
+ panOnDrag={isBoxSelect ? [1, 2] : true}
388
+ elementsSelectable={true}
335
389
  nodeTypes={nodeTypes}
336
390
  edgeTypes={edgeTypes}
337
391
  minZoom={0.2}
@@ -25,6 +25,7 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
25
25
  const [handle, setHandle] = useState("")
26
26
  const [description, setDescription] = useState("")
27
27
  const [imageUrl, setImageUrl] = useState("")
28
+ const [rank, setRank] = useState(0)
28
29
  const [isActive, setIsActive] = useState(true)
29
30
  const [isInternal, setIsInternal] = useState(false)
30
31
  const [selectedChannels, setSelectedChannels] = useState<string[]>([])
@@ -37,6 +38,7 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
37
38
  setHandle(category.handle || "")
38
39
  setDescription(category.description || "")
39
40
  setImageUrl(category.image_url || "")
41
+ setRank(category.rank ?? 0)
40
42
  setIsActive(Boolean(category.is_active))
41
43
  setIsInternal(Boolean(category.is_internal))
42
44
  setSelectedChannels(category.sales_channels || salesChannels.map((s) => s.id))
@@ -75,6 +77,7 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
75
77
  name: name.trim(),
76
78
  handle: handle.trim() || undefined,
77
79
  description: description.trim() || undefined,
80
+ rank: Number(rank) || 0,
78
81
  is_active: isActive,
79
82
  is_internal: isInternal,
80
83
  metadata: updatedMetadata,
@@ -86,6 +89,7 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
86
89
  handle: handle.trim(),
87
90
  description: description.trim() || null,
88
91
  image_url: imageUrl.trim() || category.image_url,
92
+ rank: Number(rank) || 0,
89
93
  sales_channels: selectedChannels,
90
94
  is_active: isActive,
91
95
  is_internal: isInternal,
@@ -156,6 +160,56 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
156
160
  />
157
161
  </div>
158
162
 
163
+ <div className="flex flex-col gap-1.5 p-3 rounded-lg bg-ui-bg-subtle border border-ui-border-base">
164
+ <div className="flex items-center justify-between">
165
+ <Label htmlFor="edit-rank" className="text-xs font-semibold">
166
+ Storefront Ranking (Sort Order)
167
+ </Label>
168
+ <span className="text-[10px] font-mono text-ui-fg-muted">
169
+ Controls Storefront 2 & Monfil
170
+ </span>
171
+ </div>
172
+ <div className="flex items-center gap-2">
173
+ <Input
174
+ id="edit-rank"
175
+ type="number"
176
+ size="small"
177
+ value={rank}
178
+ onChange={(e) => setRank(Number(e.target.value))}
179
+ className="w-24"
180
+ />
181
+ <div className="flex items-center gap-1">
182
+ <button
183
+ type="button"
184
+ onClick={() => setRank((r) => Math.max(0, r - 1))}
185
+ className="px-2 py-1 text-xs font-medium rounded border border-ui-border-base bg-ui-bg-base hover:bg-ui-bg-subtle transition-colors"
186
+ title="Move Earlier in Storefront"
187
+ >
188
+ ↑ Earlier
189
+ </button>
190
+ <button
191
+ type="button"
192
+ onClick={() => setRank((r) => r + 1)}
193
+ className="px-2 py-1 text-xs font-medium rounded border border-ui-border-base bg-ui-bg-base hover:bg-ui-bg-subtle transition-colors"
194
+ title="Move Later in Storefront"
195
+ >
196
+ ↓ Later
197
+ </button>
198
+ <button
199
+ type="button"
200
+ onClick={() => setRank(0)}
201
+ className="px-2 py-1 text-xs font-medium rounded border border-ui-border-base bg-ui-bg-base hover:bg-ui-bg-subtle transition-colors"
202
+ title="Set as First Category"
203
+ >
204
+ Top (#0)
205
+ </button>
206
+ </div>
207
+ </div>
208
+ <p className="text-[11px] text-ui-fg-muted leading-relaxed">
209
+ Categories are sorted by rank ASC (0, 1, 2...). Lower numbers appear first in your storefront navigation.
210
+ </p>
211
+ </div>
212
+
159
213
  <div className="flex flex-col gap-1.5">
160
214
  <Label htmlFor="edit-image" className="text-xs font-semibold">
161
215
  Category Image URL