mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 23:16:21 +00:00
feat(floorplan-editor): height slider thumb adopts the colour of the band under it
Feedback was the amber thumb looked generic / off-the-shelf and didnt visually tie to the gradient. The thumb now picks its fill from tileFill of the selected height, so picking 0 shows a blue bead, picking 12 a green one, picking 26 a purple one, and so on across the full HEIGHT_SCHEME palette. - Fill: radial gradient on the band colour with a soft white highlight at top-left and a darker rim at the bottom-right for a beaded look. The highlight intensity adapts to the base colour (stronger on dark hues, dimmer on light) so it never washes out. - Text contrast: a perceptual-luma heuristic (Rec.601, plain arithmetic, no colour lib) flips between text-zinc-900 and text-white at the right threshold so the height number stays legible on every colour the picker can land on. A matching textShadow seals the deal on the borderline hues. - Ring on drag is now zinc-900 + scale-110 (clear gesture feedback even when the underlying colour is similar to white). - Test added: thumb fill at h=0 must differ from h=13, so any future regression that pins the thumb to a single colour fails the suite.
This commit is contained in:
@@ -121,4 +121,22 @@ describe('FloorplanHeightPicker', () =>
|
||||
|
||||
restore();
|
||||
});
|
||||
|
||||
it('thumb fill matches the tile colour at the picked height', () =>
|
||||
{
|
||||
// h=0 is solid blue (#0065ff in COLORMAP). Re-render at a
|
||||
// different height and assert the recorded thumb colour
|
||||
// changes — i.e., the thumb tracks the band underneath.
|
||||
const { rerender } = render(<FloorplanHeightPicker selectedH={ 0 } onSelect={ () => undefined } />);
|
||||
|
||||
const colourAtZero = screen.getByTestId('height-thumb').getAttribute('data-thumb-color');
|
||||
|
||||
rerender(<FloorplanHeightPicker selectedH={ 13 } onSelect={ () => undefined } />);
|
||||
|
||||
const colourAtThirteen = screen.getByTestId('height-thumb').getAttribute('data-thumb-color');
|
||||
|
||||
expect(colourAtZero).toBeTruthy();
|
||||
expect(colourAtThirteen).toBeTruthy();
|
||||
expect(colourAtZero).not.toBe(colourAtThirteen);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,26 @@ const TRACK_H = 260;
|
||||
const THUMB_DIAM = 28;
|
||||
const RAIL_GUTTER = 4;
|
||||
|
||||
/**
|
||||
* Perceptual-luminance heuristic. Returns true if a hex colour is
|
||||
* 'light enough' that black text reads better than white. Uses the
|
||||
* Rec. 601 luma coefficients — good enough for a UI affordance,
|
||||
* cheap to compute, no dep on a colour lib.
|
||||
*/
|
||||
const isLightColor = (hex: string): boolean =>
|
||||
{
|
||||
const c = hex.replace('#', '');
|
||||
|
||||
if(c.length !== 6) return true;
|
||||
|
||||
const r = parseInt(c.slice(0, 2), 16);
|
||||
const g = parseInt(c.slice(2, 4), 16);
|
||||
const b = parseInt(c.slice(4, 6), 16);
|
||||
const luma = (0.299 * r) + (0.587 * g) + (0.114 * b);
|
||||
|
||||
return luma > 160;
|
||||
};
|
||||
|
||||
/**
|
||||
* Vertical brush-height slider.
|
||||
*
|
||||
@@ -107,6 +127,8 @@ export const FloorplanHeightPicker: FC<Props> = ({ selectedH, onSelect }) =>
|
||||
}, [ isDragging, heightFromClientY, onSelect, selectedH ]);
|
||||
|
||||
const thumbPct = ((HEIGHT_BRUSH_MAX - selectedH) / (count - 1)) * 100;
|
||||
const thumbColor = tileFill({ h: selectedH, blocked: false });
|
||||
const thumbTextDark = isLightColor(thumbColor);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -136,14 +158,23 @@ export const FloorplanHeightPicker: FC<Props> = ({ selectedH, onSelect }) =>
|
||||
/>
|
||||
<div
|
||||
data-testid="height-thumb"
|
||||
className={ `absolute left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full flex items-center justify-center text-[11px] font-bold text-zinc-900 tabular-nums pointer-events-none transition-shadow ${ isDragging ? 'ring-2 ring-zinc-900' : isHovering ? 'ring-2 ring-white/80' : '' }` }
|
||||
data-thumb-color={ thumbColor }
|
||||
className={ `absolute left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full flex items-center justify-center text-[11px] font-bold tabular-nums pointer-events-none transition-[box-shadow,transform] ${ thumbTextDark ? 'text-zinc-900' : 'text-white' } ${ isDragging ? 'ring-2 ring-zinc-900 scale-110' : isHovering ? 'ring-2 ring-white' : '' }` }
|
||||
style={ {
|
||||
width: THUMB_DIAM,
|
||||
height: THUMB_DIAM,
|
||||
top: `${ thumbPct }%`,
|
||||
background: 'radial-gradient(circle at 35% 30%, #fff7c4 0%, #facc15 55%, #ca8a04 100%)',
|
||||
border: '2px solid #78350f',
|
||||
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.25), inset 0 -2px 3px rgba(0, 0, 0, 0.18)'
|
||||
// Thumb fill picks up the colour of the band
|
||||
// under it — visual continuity with the
|
||||
// gradient so users see the colour of the
|
||||
// height they're picking, not a generic
|
||||
// amber chip. Radial highlight + bottom
|
||||
// shadow give it a beaded look without
|
||||
// hiding the underlying colour.
|
||||
background: `radial-gradient(circle at 32% 28%, ${ thumbTextDark ? 'rgba(255, 255, 255, 0.85)' : 'rgba(255, 255, 255, 0.55)' } 0%, ${ thumbColor } 45%, ${ thumbColor } 78%, rgba(0, 0, 0, 0.25) 100%)`,
|
||||
border: '2px solid rgba(0, 0, 0, 0.55)',
|
||||
boxShadow: '0 2px 5px rgba(0, 0, 0, 0.35), inset 0 -2px 3px rgba(0, 0, 0, 0.25), inset 0 1px 2px rgba(255, 255, 255, 0.4)',
|
||||
textShadow: thumbTextDark ? '0 1px 0 rgba(255, 255, 255, 0.6)' : '0 1px 1px rgba(0, 0, 0, 0.55)'
|
||||
} }
|
||||
>
|
||||
{ selectedH }
|
||||
|
||||
Reference in New Issue
Block a user