Gallery folders have no drag-reorder affordance — folders appear in fixed insertion order, user with 8 folders in a non-ideal order has to delete and recreate them to reorder #235

Closed
opened 2026-10-04 02:32:09 +00:00 by Mark · 1 comment
Owner

Finding

src/components/gallery/FolderNav.tsx renders folders as buttons in a flex items-center gap-2 overflow-x-auto row. The order is folders.map((folder, i) => ...) — the array order. There's no drag handle, no reorder UI.

grep -n 'onFolderDragOver' src/components/gallery/FolderNav.tsx — the prop exists (line 14), and the folder button has onDragOver and onDrop for receiving designs (not for reordering the folders themselves). The "drag over" is for designs being moved INTO the folder, not for folders being dragged.

A user with 8 folders in creation order:

  • "First draft", "Project A", "Project B", "Logo", "Archived", "Personal", "WIP", "Done"
  • Wants to alphabetize: "Archived", "Done", "First draft", "Logo", "Personal", "Project A", "Project B", "WIP"
  • Has to delete each one, recreate in the right order

grep -n 'sortFolders\|reorderFolder' src/ returns 0 hits.

Where

  • FolderNav: src/components/gallery/FolderNav.tsx (no folder drag handle)
  • Folder drag handlers: onFolderDragOver, onFolderDragLeave, onFolderDrop (all on e.dataTransfer of incoming designs, not folder reorder)
  • No reorder: grep -n 'reorderFolder' src/ returns 0

Suggested fix shape

Add a drag handle (GripVertical icon, matching the per-row drag handle in the workspace's ObjectList per #182) to each folder button:

<button onClick={...}>
  <GripVertical size={10} className="drag-handle" data-folder-drag-handle={folder.id} />
  {folder.name}
  <span>{count}</span>
</button>

The drag handle has the draggable HTML attribute and a dragstart handler:

<span
  draggable
  onDragStart={(e) => {
    e.dataTransfer.setData('folder-id', folder.id);
    e.dataTransfer.effectAllowed = 'move';
  }}
  onDragEnd={onFolderDragEnd}
>
  <GripVertical size={10} />
</span>

A drop zone (the folder button itself, or a "between" gap) accepts the drag and reorders the folders array:

onDragOver={(e) => {
  e.preventDefault();
  // Calculate which "slot" the drag is over
}}
onDrop={(e) => {
  const draggedId = e.dataTransfer.getData('folder-id');
  onReorderFolders(draggedId, targetIndex);
}}

onReorderFolders dispatches a REORDER_FOLDERS action that updates the folders array and persists to localStorage.

Severity

Low. The user can work around with delete + recreate. Power users with many folders notice.

Discovered by

Gap analysis (2026-10-02) of gallery folder management.

  • #234 (open) — Folder rename/delete. Same FolderNav surface.
  • The designs' drag-and-drop reorder is in place (per #148). The folders' reorder is the parallel feature.
  • The ObjectList reorder in the workspace (per #182) is the pattern to mirror.
## Finding `src/components/gallery/FolderNav.tsx` renders folders as buttons in a `flex items-center gap-2 overflow-x-auto` row. The order is `folders.map((folder, i) => ...)` — the array order. There's no drag handle, no reorder UI. `grep -n 'onFolderDragOver' src/components/gallery/FolderNav.tsx` — the prop exists (line 14), and the folder button has `onDragOver` and `onDrop` for receiving designs (not for reordering the folders themselves). The "drag over" is for designs being moved INTO the folder, not for folders being dragged. A user with 8 folders in creation order: - "First draft", "Project A", "Project B", "Logo", "Archived", "Personal", "WIP", "Done" - Wants to alphabetize: "Archived", "Done", "First draft", "Logo", "Personal", "Project A", "Project B", "WIP" - Has to delete each one, recreate in the right order `grep -n 'sortFolders\|reorderFolder' src/` returns 0 hits. ## Where - FolderNav: `src/components/gallery/FolderNav.tsx` (no folder drag handle) - Folder drag handlers: `onFolderDragOver`, `onFolderDragLeave`, `onFolderDrop` (all on `e.dataTransfer` of incoming designs, not folder reorder) - No reorder: `grep -n 'reorderFolder' src/` returns 0 ## Suggested fix shape Add a drag handle (GripVertical icon, matching the per-row drag handle in the workspace's ObjectList per #182) to each folder button: ```tsx <button onClick={...}> <GripVertical size={10} className="drag-handle" data-folder-drag-handle={folder.id} /> {folder.name} <span>{count}</span> </button> ``` The drag handle has the `draggable` HTML attribute and a `dragstart` handler: ```tsx <span draggable onDragStart={(e) => { e.dataTransfer.setData('folder-id', folder.id); e.dataTransfer.effectAllowed = 'move'; }} onDragEnd={onFolderDragEnd} > <GripVertical size={10} /> </span> ``` A drop zone (the folder button itself, or a "between" gap) accepts the drag and reorders the folders array: ```tsx onDragOver={(e) => { e.preventDefault(); // Calculate which "slot" the drag is over }} onDrop={(e) => { const draggedId = e.dataTransfer.getData('folder-id'); onReorderFolders(draggedId, targetIndex); }} ``` `onReorderFolders` dispatches a `REORDER_FOLDERS` action that updates the folders array and persists to localStorage. ## Severity Low. The user can work around with delete + recreate. Power users with many folders notice. ## Discovered by Gap analysis (2026-10-02) of gallery folder management. ## Related - #234 (open) — Folder rename/delete. Same FolderNav surface. - The designs' drag-and-drop reorder is in place (per #148). The folders' reorder is the parallel feature. - The ObjectList reorder in the workspace (per #182) is the pattern to mirror.
Author
Owner

Shipped in commit 25b08cc on main.

What changed:

  • src/components/gallery/FolderNav.tsx: each custom (user-owned) folder button now renders a GripVertical drag handle on the left. Dropping the handle onto another folder button or onto the leading drop slot reorders the folder in the custom-folder slice. Default folders (All Designs / Unsorted / Favorites) stay pinned at positions 0–2 and do not expose a handle.
  • src/pages/Gallery.tsx: new handleReorderFolders(draggedId, targetIndex) splits the folders array into defaults + customs, splices the dragged id out of the custom slice, and re-inserts it at the target index. Persistence is automatic via the existing useEffect([folders]) -> saveFolders chain.

Pipeline isolation: folder-reorder uses a custom MIME payload (application/x-folder-id) distinct from the existing text/plain design-into-folder payload — dropping a design onto a folder still moves the design, and dropping a folder onto another folder reorders the folders.

Tests: 7 new in src/components/gallery/issue-235-folder-reorder.test.tsx. Full suite 1199/1199 (1 pre-existing skip). TypeScript clean, ESLint clean.

Shipped in commit 25b08cc on `main`. **What changed:** - `src/components/gallery/FolderNav.tsx`: each custom (user-owned) folder button now renders a `GripVertical` drag handle on the left. Dropping the handle onto another folder button or onto the leading drop slot reorders the folder in the custom-folder slice. Default folders (`All Designs` / `Unsorted` / `Favorites`) stay pinned at positions 0–2 and do not expose a handle. - `src/pages/Gallery.tsx`: new `handleReorderFolders(draggedId, targetIndex)` splits the folders array into defaults + customs, splices the dragged id out of the custom slice, and re-inserts it at the target index. Persistence is automatic via the existing `useEffect([folders]) -> saveFolders` chain. **Pipeline isolation:** folder-reorder uses a custom MIME payload (`application/x-folder-id`) distinct from the existing `text/plain` design-into-folder payload — dropping a design onto a folder still moves the design, and dropping a folder onto another folder reorders the folders. **Tests:** 7 new in `src/components/gallery/issue-235-folder-reorder.test.tsx`. Full suite 1199/1199 (1 pre-existing skip). TypeScript clean, ESLint clean.
Mark closed this issue 2026-10-04 07:40:11 +00:00
Sign in to join this conversation.
No description provided.