Color picker
canonGrid of colour swatches (radiogroup, arrows, optional 'no colour' row) and its canonical wrapper: popover on desktop, sheet on the phone, InSheet panel inside a dialog.
acrm !4337 (one swatch grid instead of four copies: EventLabelPicker, EventTypeFilter, MailLabelDialog use ColorPicker; SegmentIconPicker keeps its own panel and uses ColorSwatches for SEGMENT_TONES — validator exception), !4347 (phone: swatches span the width, 44 px targets, segment icon tiles capped at --tap-min), !4354 (stories + usage/a11y), !4363 (a11y: 'Bez barvy' is a radio of the same group with a check on the right; roving tabindex — one Tab stop on the chosen option; focus ring and selection ring read apart). Canon 1. 10. 2026 (AD) after before/after app screenshots (1440 + 390, light/dark) and the States story. Custom hex colour in the segment picker unchanged; dropping it is a separate owner decision.
Used in 6 files · 5 areas · 6× — components/calendar, components/mail, components/meetings, components/segment, pages/TymyPage
Default
export const Default = () => (
<Host>
<Labeled label="event label — 6 × 4 grid, the chosen colour carries a ring; a pick closes the panel">
<Panel label="Barva štítku">
<Grid value="cobalt" />
</Panel>
</Labeled>
</Host>
);Variants
export const Variants = () => (
<Host>
<div style={row}>
<Labeled label="grid (default) — 24-colour palette, 24 px round swatches, 6 columns, 'Bez barvy' optional">
<Panel label="Barva štítku">
<Grid value="tomato" />
</Panel>
</Labeled>
<Labeled label="tones — one row of the 7 segment tones (variant='tones'); a custom hex stays a separate input next to it">
<div
className="acrm-popover segpick-pop"
role="dialog"
aria-label="Barva segmentu"
style={{ position: 'static' }}
>
<Tones value="purple" />
</div>
</Labeled>
</div>
</Host>
);States
export const States = () => (
<Host>
<div style={grid}>
<div style={row}>
<Labeled label="nothing selected — no ring anywhere; the first swatch takes focus on open">
<Panel label="Barva štítku">
<Grid value={null} />
</Panel>
</Labeled>
<Labeled label="'Bez barvy' chosen — a radio of the same group, check on the right; the one Tab stop">
<Panel label="Barva typu">
<Grid value={null} />
<NoneRow pressed />
</Panel>
</Labeled>
<Labeled label="colour chosen — 'Bez barvy' below is not checked (no check, slot kept)">
<Panel label="Barva typu">
<Grid value="sage" />
<NoneRow pressed={false} />
</Panel>
</Labeled>
<Labeled label="keyboard focus on a swatch (Tomato) while another (Sage) is chosen — focus ring and selection ring read apart">
<Panel label="Barva štítku">
<Grid value="sage" focus="tomato" />
</Panel>
</Labeled>
</div>
<Note>
The selection is a ring (canvas gap + ink ring), not a check mark: a white check on the
light tones (banana, citron) measured 2.1:1, below the 3:1 of a graphic object. Arrows move
focus across the grid (up/down jump a row; down from the last row onto "Bez barvy", up
back), Home and End go to the ends. One Tab stop (roving tabindex): the chosen option, else
the first swatch.
</Note>
</div>
</Host>
);Composition
export const Composition = () => (
<Host>
<Labeled label="phone (390 px) — 'Nový štítek' dialog, colour field opened: bottom sheet with grab, title (= ariaLabel), 6 × 1fr grid with 44 px targets, 'Bez barvy', full-width Hotovo">
<div
style={{
position: 'relative',
width: 390,
height: 640,
overflow: 'hidden',
border: '1px solid var(--border)',
background: 'var(--surface-100)',
}}
>
<div style={{ padding: 'var(--sp-4)', display: 'grid', gap: 'var(--sp-4)' }}>
<div style={{ font: 'var(--h2)', color: 'var(--ink-900)' }}>Nový štítek</div>
<div style={{ display: 'grid', gap: 'var(--sp-1)' }}>
<span style={{ font: 'var(--label)', color: 'var(--fg-secondary)' }}>Název</span>
<span style={{ font: 'var(--p)', color: 'var(--ink-800)' }}>Schůzka s klientem</span>
</div>
<div style={{ display: 'grid', gap: 'var(--sp-1)' }}>
<span style={{ font: 'var(--label)', color: 'var(--fg-secondary)' }}>Barva</span>
<span
className="ctype-sw sw-cobalt"
style={{
width: 'var(--sp-6)',
height: 'var(--sp-6)',
borderRadius: 'var(--radius-pill)',
}}
/>
</div>
</div>
<div style={{ position: 'absolute', inset: 0, background: 'var(--ov-scrim)' }} />
<div
className="acrm-sheet story-cp-sheet"
role="dialog"
aria-label="Barva štítku"
style={SHEET}
>
<style>{PHONE_CSS}</style>
<span
aria-hidden="true"
style={{
display: 'block',
width: 'var(--sp-7)',
height: 'var(--sp-1)',
margin: '0 auto var(--sp-3)',
borderRadius: 'var(--radius-pill)',
background: 'var(--line-200)',
}}
/>
<div style={{ marginBottom: 'var(--sp-3)', font: 'var(--h2)', color: 'var(--ink-900)' }}>
Barva štítku
</div>
<Grid value="cobalt" />
<NoneRow pressed={false} />
<div style={{ marginTop: 'var(--sp-3)', display: 'grid' }}>
<Button variant="primary" touch style={{ width: '100%', height: 'var(--tap-min)' }}>
Hotovo
</Button>
</div>
</div>
</div>
</Labeled>
</Host>
);