web: the mixer hangs a panel on the colour it read, and STRAIGHTEN becomes a scale on the photo

This commit is contained in:
2026-09-18 18:27:28 +07:00
parent 10466e122a
commit 27035c4acb
5 changed files with 401 additions and 41 deletions
+58
View File
@@ -14,6 +14,9 @@ export interface ChipDef {
amberValue?: boolean;
pro?: boolean;
disabled?: boolean;
// CSS colour drawn as a dot on the chip's leading edge — the HSL mixer's band
// chips name a colour, so they show it too.
color?: string;
// Present when the chip itself can be dragged somewhere: the payload the drop
// target reads back (the recipe's id, for the FAVORITED rail button).
drag?: string;
@@ -62,6 +65,7 @@ export function ChipRow({ chips }: { chips: ChipDef[] }) {
}
onClick={chip.onClick}
>
{chip.color ? <span className="chip-dot" data-color={chip.color} style={{ background: chip.color }} /> : null}
{chip.label}
{chip.value ? <span className="val">{chip.value}</span> : null}
{chip.pro ? <span className="pro">PRO</span> : null}
@@ -123,3 +127,57 @@ export function SliderRow({ slider }: { slider: SliderDef }) {
</div>
);
}
// The same ruler without its own column: one knob of the mixer's inline panel,
// where HUE/SAT/LUM sit side by side under the colour they are moving. Wheel
// steps it, double-click puts it back to 0.
export function MiniSlider({
label,
value,
onChange,
onReset,
}: {
label: string;
value: number;
onChange: (value: number) => void;
onReset: () => void;
}) {
const ref = useRef<HTMLDivElement>(null);
const live = useRef({ value, onChange });
live.current = { value, onChange };
useEffect(() => {
const el = ref.current;
if (!el) return;
const onWheel = (e: WheelEvent) => {
// The panel lives on the stage, whose own wheel zooms the photo: the
// knob takes the notch and keeps it.
e.preventDefault();
e.stopPropagation();
const s = live.current;
const next = Math.max(-10, Math.min(10, s.value + (e.deltaY < 0 ? 1 : -1)));
if (next !== s.value) s.onChange(next);
};
el.addEventListener('wheel', onWheel, { passive: false });
return () => el.removeEventListener('wheel', onWheel);
}, []);
return (
<div className="mini-slider" ref={ref} data-key={`hsl-knob-${label}`}>
<div className="mini-head">
<span>{label}</span>
<b className={value !== 0 ? 'amber' : ''} data-key={`hsl-knob-${label}-value`}>
{value > 0 ? `+${value}` : String(value)}
</b>
</div>
<input
type="range"
min={-10}
max={10}
step={1}
value={value}
aria-label={label}
onChange={(e) => onChange(Number(e.target.value))}
onDoubleClick={onReset}
/>
</div>
);
}