the colour picker stays inside the panel

It was `<input type="color">`, and the browser's own picker is a window-level popup positioned by the
browser. In a panel near the right edge it opened off the side of the window and could not be reached —
and nothing in CSS can move it, because it is not in the document.

Swatches and a hex field instead, in a panel anchored to the RIGHT of its trigger so it grows inward
and cannot leave the screen the same way.

Deliberately not a gradient area and a hue strip. That is what the native control does well, and
rebuilding it badly to fix a positioning problem would be a worse control than the one being replaced.
Text over a photograph is white or black almost every time; the hex field is there for the times it is
not.

Closes on `pointerdown` anywhere else rather than `click`, so it closes on the way down and a press
meant for something behind it does not land twice. Captured, because a control below may stop
propagation for its own reasons.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-31 00:39:12 +01:00
co-authored by Claude Opus 5
parent b3032093fe
commit e0a5f1b653
2 changed files with 119 additions and 9 deletions
+4 -9
View File
@@ -2,6 +2,7 @@ import { useRef, useState } from 'react';
import { Plus, Trash2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { ColorField } from './ColorField';
/**
* Text laid over a picture, positioned by dragging it.
@@ -225,15 +226,9 @@ export const OverlayEditor = ({
<option value="right">Right</option>
</select>
{/* A native colour input: it is the one control every platform already knows how to draw,
and a hand-rolled picker here would be worse on every one of them. */}
<input
type="color"
value={overlay.color}
onChange={(event) => update(index, { color: event.currentTarget.value })}
className="size-8 shrink-0 cursor-pointer rounded border border-input bg-transparent p-0.5"
title="Colour"
/>
{/* Not `<input type="color">`. The browser's picker is a window-level popup and opened off
the side of the window in a panel near the right edge — see ColorField. */}
<ColorField value={overlay.color} onChange={(color) => update(index, { color })} />
<Button
size="sm"