HSL: click a colour chip to open its panel, the way PICK does
The colour band chips only aimed the mixer before; now they open the same panel the eyedropper opens, seeded with the colour the chip is named after (hexToRgb turns that readout back into the RGB bytes the picker stores). A pick already on the photo keeps its spot — only a mixer with nowhere to hang takes the middle of the frame. The HSL column itself is untouched: 8 chips, the rule, IMAGE, HUE/SAT/LUM, the readout and RESET all stay where they were.
This commit is contained in:
@@ -175,6 +175,13 @@ function hslToHex(h: number, s: number, l: number): string {
|
||||
return `#${chan(0)}${chan(8)}${chan(4)}`;
|
||||
}
|
||||
|
||||
// The way back: a band chip hands the eyedropper's readout the colour it is
|
||||
// named after, and that readout is RGB bytes.
|
||||
function hexToRgb(hex: string): { r: number; g: number; b: number } {
|
||||
const n = parseInt(hex.slice(1), 16);
|
||||
return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
|
||||
}
|
||||
|
||||
// The knobs that drive a colour get a swatch under their ruler, so the hue is
|
||||
// visible next to the number. TEMP is the only one left on this path: TINT's row
|
||||
// lives in the sidebar's WB panel now, so its ±10 axis draws no ruler here.
|
||||
@@ -2862,7 +2869,16 @@ export function Workspace() {
|
||||
color: hslToHex(b.hue, 70, 50),
|
||||
active: hslBand === b.id,
|
||||
amberValue: hslBand !== b.id && moved,
|
||||
onClick: () => setHslBand(b.id),
|
||||
// A band chip aims the mixer AND opens its panel, the same panel
|
||||
// the eyedropper opens: the colour it starts from is the colour
|
||||
// the chip is named after, and this band's knobs push it from
|
||||
// there. A pick already on the photo keeps its spot — only a
|
||||
// mixer with nowhere to hang takes the middle of the frame.
|
||||
onClick: () => {
|
||||
setHslBand(b.id);
|
||||
setSample(hexToRgb(hslToHex(b.hue, 70, 50)));
|
||||
setPickedAt((at) => at ?? { fx: 0.5, fy: 0.5 });
|
||||
},
|
||||
};
|
||||
}),
|
||||
// The divider keeps the three image-wide sliders out of the colour
|
||||
@@ -3249,7 +3265,7 @@ export function Workspace() {
|
||||
aria-pressed={recipe.name === entry.name}
|
||||
onClick={() => applyRecipe({ ...(entry.recipe as Recipe), name: entry.name })}
|
||||
>
|
||||
{entry.name}
|
||||
<span className="chip-label">{entry.name}</span>
|
||||
</button>
|
||||
{/* Only this account can delete its own file — the API
|
||||
scopes the delete by user, the button mirrors it. */}
|
||||
@@ -3261,7 +3277,7 @@ export function Workspace() {
|
||||
aria-label={t('save.delete')}
|
||||
onClick={() => void deleteRecipeEntry(entry)}
|
||||
>
|
||||
✕
|
||||
<span className="chip-label" aria-hidden="true">✕</span>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
@@ -3359,7 +3375,7 @@ export function Workspace() {
|
||||
disabled={locating}
|
||||
onClick={() => locateMe()}
|
||||
>
|
||||
{locating ? t('wm.locating') : t('wm.useLocation')}
|
||||
<span className="chip-label">{locating ? t('wm.locating') : t('wm.useLocation')}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="chip-row">
|
||||
|
||||
@@ -613,14 +613,14 @@ export function Landing() {
|
||||
className={`chip${mode === 'light' ? ' on' : ''}`}
|
||||
onClick={() => setMode('light')}
|
||||
>
|
||||
Light
|
||||
<span className="chip-label">Light</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`chip${mode === 'dark' ? ' on' : ''}`}
|
||||
onClick={() => setMode('dark')}
|
||||
>
|
||||
Dark
|
||||
<span className="chip-label">Dark</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -628,7 +628,7 @@ export function Landing() {
|
||||
data-key="theme-auto"
|
||||
onClick={() => setMode('auto')}
|
||||
>
|
||||
Auto
|
||||
<span className="chip-label">Auto</span>
|
||||
</button>
|
||||
</div>
|
||||
{/* The same colour groups the workspace offers; the page's own
|
||||
@@ -642,7 +642,7 @@ export function Landing() {
|
||||
className={`chip${accent === entry.id ? ' on' : ''}`}
|
||||
onClick={() => setAccent(entry.id)}
|
||||
>
|
||||
{entry.label}
|
||||
<span className="chip-label">{entry.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -684,7 +684,7 @@ export function Library() {
|
||||
title={t('lib.subsHint')}
|
||||
onClick={() => setDeep((prev) => !prev)}
|
||||
>
|
||||
{t(deep ? 'lib.subsHide' : 'lib.subsShow')}
|
||||
<span className="chip-label">{t(deep ? 'lib.subsHide' : 'lib.subsShow')}</span>
|
||||
</button>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
@@ -99,7 +99,12 @@ export function ChipRow({ chips, grid }: { chips: ChipDef[]; grid?: boolean }) {
|
||||
}}
|
||||
>
|
||||
{chip.color ? <span className="chip-dot" data-color={chip.color} style={{ background: chip.color }} /> : null}
|
||||
{chip.label}
|
||||
{/* The name is a box, not the bare text node it used to be: a flex
|
||||
container turns a loose text node into an anonymous item of its
|
||||
own, and on iOS that item stops being painted until the column
|
||||
it sits in is touched. Same fault, same shape as the panel head
|
||||
(see `dev-panel-label` in DevelopPanels). No rule of its own. */}
|
||||
<span className="chip-label">{chip.label}</span>
|
||||
{chip.value ? <span className="val">{chip.value}</span> : null}
|
||||
{chip.pro ? <span className="pro">PRO</span> : null}
|
||||
</button>
|
||||
|
||||
@@ -72,7 +72,10 @@ export function ToolRail({
|
||||
}}
|
||||
>
|
||||
<span className="glyph" aria-hidden="true">{entry.glyph}</span>
|
||||
{t(entry.label)}
|
||||
{/* A box, not a bare text node: on a phone the glyph is display:none
|
||||
and the name would be the button's only child — an anonymous flex
|
||||
item, which is the one thing iOS stops painting until a touch. */}
|
||||
<span className="rail-label">{t(entry.label)}</span>
|
||||
</button>
|
||||
))}
|
||||
{/* The build this page came off, at the foot of the rail: an image on the
|
||||
|
||||
@@ -155,9 +155,13 @@ export function TopBar({
|
||||
{menu === 'theme' ? (
|
||||
<div className="popover">
|
||||
<div className="chip-row">
|
||||
<button type="button" className={`chip${mode === 'light' ? ' on' : ''}`} onClick={() => setMode('light')}>Light</button>
|
||||
<button type="button" className={`chip${mode === 'dark' ? ' on' : ''}`} onClick={() => setMode('dark')}>Dark</button>
|
||||
<button type="button" className={`chip${mode === 'auto' ? ' on' : ''}`} data-key="theme-auto" onClick={() => setMode('auto')}>Auto</button>
|
||||
{/* The name is a span, not a bare text node: a chip is a flex row,
|
||||
and a loose text node in one is an anonymous item — the shape
|
||||
iOS stops painting until the page is touched (same as the
|
||||
panel heads and ChipRow's chips). */}
|
||||
<button type="button" className={`chip${mode === 'light' ? ' on' : ''}`} onClick={() => setMode('light')}><span className="chip-label">Light</span></button>
|
||||
<button type="button" className={`chip${mode === 'dark' ? ' on' : ''}`} onClick={() => setMode('dark')}><span className="chip-label">Dark</span></button>
|
||||
<button type="button" className={`chip${mode === 'auto' ? ' on' : ''}`} data-key="theme-auto" onClick={() => setMode('auto')}><span className="chip-label">Auto</span></button>
|
||||
</div>
|
||||
<div className="chip-row" style={{ marginTop: 8 }}>
|
||||
{ACCENTS.map((entry) => (
|
||||
@@ -167,7 +171,7 @@ export function TopBar({
|
||||
className={`chip${accent === entry.id ? ' on' : ''}`}
|
||||
onClick={() => setAccent(entry.id)}
|
||||
>
|
||||
{entry.label}
|
||||
<span className="chip-label">{entry.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -204,7 +208,7 @@ export function TopBar({
|
||||
setMenu(null);
|
||||
}}
|
||||
>
|
||||
{entry.label}
|
||||
<span className="chip-label">{entry.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user