EditorRightPanel: Path Properties gets Start/End color inputs + 'Swap colors' button (#350 UI layer) #395

Closed
opened 2026-10-05 11:09:52 +00:00 by Mark · 0 comments
Owner

Parent-Issue: #350

Depends on #393 (type layer — gradient field) and #394 (renderers consume it). Do this issue only after both land and the canvas actually draws gradients.

Scope (UI layer — type and renderer already in place)

The Path Properties section of src/components/editor/EditorRightPanel.tsx currently shows a single Thread Color picker bound to the selected path's threadColor. Add:

  1. A Start Color picker (defaults to the existing threadColor value)
  2. An End Color picker (also defaults to threadColor)
  3. A "Swap colors" button that swaps the two values (this is what #350's title literally asks for)
  4. A "Use gradient" toggle (or auto-enable when Start ≠ End; pick whichever matches the editor's existing toggle style — look at the closed toggle at line 230-233 for the pattern)

All three operations must flow through the same setPaths + pushHistory pair used by every other per-path mutation in this panel (e.g., the satin-width slider, the closed toggle), so they are undoable with a single Ctrl+Z.

Concrete change

In EditorRightPanel.tsx, find the Thread Color section in the Path Properties area (around line 670-700 — there is a section-open map keyed by string ids including 'thread-color' per the comment at line 672). Add a sibling section that:

  • reads selectedPath.gradient if defined, else synthesizes { startColor: threadColor, endColor: threadColor } for display
  • writes via a new onUpdateSelectedPathGradient(gradient: { startColor, endColor } | undefined) callback wired up in Editor.tsx next to onUpdateSelectedPathSatinWidth (line 79-86), onTogglePathClosed (line 233), etc.
  • the callback updates paths[i].gradient, then calls setPaths(...) and pushHistory(...) once

The swap button:

const onSwapColors = () => {
  if (!selectedPath.gradient) return;
  onUpdateSelectedPathGradient({
    startColor: selectedPath.gradient.endColor,
    endColor: selectedPath.gradient.startColor,
  });
};

Button UX — copy the existing per-row action button style (e.g., the Reverse button pattern at the bottom of Path Properties):

<button
  onClick={onSwapColors}
  disabled={!selectedPath.gradient}
  data-testid="swap-colors"
  title="Swap start and end colors"
>
  <ArrowRightLeft size={12} /> Swap
</button>

When the user hasn't enabled the gradient, the swap button is disabled. When startColor === endColor, the swap is a no-op visually — that's fine, no special-case needed.

Files to touch

  • src/components/editor/EditorRightPanel.tsx — UI additions
  • src/components/editor/Editor.tsx — wire up onUpdateSelectedPathGradient (parallel to onUpdateSelectedPathSatinWidth at line 79-86)
  • src/components/editor/__tests__/issue-350.test.tsx (or follow the existing per-issue test filename convention — see issue-339.test.tsx, issue-341.test.tsx, issue-349.test.tsx for the pattern) — test the swap function plus the disabled state

Acceptance

  • pnpm test passes including the new test.
  • A user with a satin path can pick Start = red, End = blue, see a red→blue gradient on the canvas (this requires #394 already shipped — confirm origin/main has the renderer changes), click Swap, see blue→red in place, Ctrl+Z once to revert.
  • A path without gradient set continues to render as a single solid color (no UI regression).

Reference patterns

  • Satin width slider + Apply-to-all callback (lines 79-86, 503-506) — copy the dispatch shape.
  • Reverse-path button (the geometry reverse from #316 — already shipped, see EditorRightPanel.tsx for the ArrowRightLeft icon button placement) — copy the button visual.
  • Reverse button uses <ArrowRightLeft> from lucide-react — the Swap button reuses the same icon.

Discovery check

Confirm before starting:

grep -n 'satinWidth\|onTogglePathClosed' src/components/editor/Editor.tsx src/components/editor/EditorRightPanel.tsx | head -20

The patterns cited above are accurate as of 2026-10-05.

**Parent-Issue:** #350 Depends on #393 (type layer — `gradient` field) and #394 (renderers consume it). Do this issue only after both land and the canvas actually draws gradients. ## Scope (UI layer — type and renderer already in place) The Path Properties section of `src/components/editor/EditorRightPanel.tsx` currently shows a single `Thread Color` picker bound to the selected path's `threadColor`. Add: 1. A Start Color picker (defaults to the existing `threadColor` value) 2. An End Color picker (also defaults to `threadColor`) 3. A "Swap colors" button that swaps the two values (this is what #350's title literally asks for) 4. A "Use gradient" toggle (or auto-enable when Start ≠ End; pick whichever matches the editor's existing toggle style — look at the `closed` toggle at line 230-233 for the pattern) All three operations must flow through the same `setPaths + pushHistory` pair used by every other per-path mutation in this panel (e.g., the satin-width slider, the closed toggle), so they are undoable with a single Ctrl+Z. ## Concrete change In `EditorRightPanel.tsx`, find the Thread Color section in the Path Properties area (around line 670-700 — there is a section-open map keyed by string ids including `'thread-color'` per the comment at line 672). Add a sibling section that: - reads `selectedPath.gradient` if defined, else synthesizes `{ startColor: threadColor, endColor: threadColor }` for display - writes via a new `onUpdateSelectedPathGradient(gradient: { startColor, endColor } | undefined)` callback wired up in `Editor.tsx` next to `onUpdateSelectedPathSatinWidth` (line 79-86), `onTogglePathClosed` (line 233), etc. - the callback updates `paths[i].gradient`, then calls `setPaths(...)` and `pushHistory(...)` once The swap button: ```tsx const onSwapColors = () => { if (!selectedPath.gradient) return; onUpdateSelectedPathGradient({ startColor: selectedPath.gradient.endColor, endColor: selectedPath.gradient.startColor, }); }; ``` Button UX — copy the existing per-row action button style (e.g., the Reverse button pattern at the bottom of Path Properties): ```tsx <button onClick={onSwapColors} disabled={!selectedPath.gradient} data-testid="swap-colors" title="Swap start and end colors" > <ArrowRightLeft size={12} /> Swap </button> ``` When the user hasn't enabled the gradient, the swap button is disabled. When `startColor === endColor`, the swap is a no-op visually — that's fine, no special-case needed. ## Files to touch - `src/components/editor/EditorRightPanel.tsx` — UI additions - `src/components/editor/Editor.tsx` — wire up `onUpdateSelectedPathGradient` (parallel to `onUpdateSelectedPathSatinWidth` at line 79-86) - `src/components/editor/__tests__/issue-350.test.tsx` (or follow the existing per-issue test filename convention — see `issue-339.test.tsx`, `issue-341.test.tsx`, `issue-349.test.tsx` for the pattern) — test the swap function plus the disabled state ## Acceptance - `pnpm test` passes including the new test. - A user with a satin path can pick Start = red, End = blue, see a red→blue gradient on the canvas (this requires #394 already shipped — confirm `origin/main` has the renderer changes), click Swap, see blue→red in place, Ctrl+Z once to revert. - A path without `gradient` set continues to render as a single solid color (no UI regression). ## Reference patterns - Satin width slider + Apply-to-all callback (lines 79-86, 503-506) — copy the dispatch shape. - Reverse-path button (the geometry reverse from #316 — already shipped, see EditorRightPanel.tsx for the ArrowRightLeft icon button placement) — copy the button visual. - Reverse button uses `<ArrowRightLeft>` from lucide-react — the Swap button reuses the same icon. ## Discovery check Confirm before starting: ``` grep -n 'satinWidth\|onTogglePathClosed' src/components/editor/Editor.tsx src/components/editor/EditorRightPanel.tsx | head -20 ``` The patterns cited above are accurate as of 2026-10-05.
Mark 2026-10-05 21:22:06 +00:00
Sign in to join this conversation.
No description provided.