Tab strip
canonControlled tablist that scrolls and fades when the tabs do not fit; no overflow menu, no disabled tab.
Used in 10 files · 7 areas · 10× — components/record, components/setkani, pages/ClenTymuPage, pages/MeetingDetailPage, pages/ProjectDetailPage, pages/TrashPage …
@acrm/ui <TabStrip>.acrm-tabs .acrm-tab .acrm-tabswrap .acrm-tabsedge +1SourceStoryLegacy card tabs.htmlDefault
export const Default = () => {
const [tab, setTab] = useState('prehled');
return <TabStrip tabs={PLAIN} value={tab} onChange={setTab} />;
};Variants
labels only — the plain strip
with icons — for a record where the tabs are kinds of content
with counts — the number is how much is inside; zero is shown, not hidden (hiding empty tabs is Record tabs' job, not the strip's)
export const Variants = () => {
const [a, setA] = useState('prehled');
const [b, setB] = useState('ukoly');
return (
<div style={{ display: 'grid', gap: 'var(--sp-6)' }}>
<Labeled text="labels only — the plain strip">
<TabStrip tabs={PLAIN} value={a} onChange={setA} />
</Labeled>
<Labeled text="with icons — for a record where the tabs are kinds of content">
<TabStrip tabs={WITH_ICONS} value={a} onChange={setA} />
</Labeled>
<Labeled text="with counts — the number is how much is inside; zero is shown, not hidden (hiding empty tabs is Record tabs' job, not the strip's)">
<TabStrip tabs={WITH_COUNTS} value={b} onChange={setB} />
</Labeled>
</div>
);
};States
active — the underline and the darker label; click another tab and it moves
one tab — the strip still renders; a single tab means the switch has nothing to offer and usually should not be there at all
export const States = () => {
const [tab, setTab] = useState('aktivita');
const [many, setMany] = useState('k0');
return (
<div style={{ display: 'grid', gap: 'var(--sp-6)' }}>
<Labeled text="active — the underline and the darker label; click another tab and it moves">
<TabStrip tabs={PLAIN} value={tab} onChange={setTab} />
</Labeled>
<Labeled text="overflow — more tabs than fit: the strip scrolls, the edge fades and a chevron appears on the side that has more; click it to nudge by ~60 % of the width">
<div style={{ maxWidth: 420 }}>
<TabStrip tabs={MANY} value={many} onChange={setMany} />
</div>
</Labeled>
<Labeled text="one tab — the strip still renders; a single tab means the switch has nothing to offer and usually should not be there at all">
<TabStrip
tabs={[{ key: 'prehled', label: 'Přehled' }]}
value="prehled"
onChange={() => {}}
/>
</Labeled>
</div>
);
};With icon & kbd
icon on the left, count on the right — both are slots of the same row, and both are optional
export const WithIconAndKbd = () => {
const [tab, setTab] = useState('posta');
return (
<div style={{ display: 'grid', gap: 'var(--sp-5)' }}>
<Labeled text="icon on the left, count on the right — both are slots of the same row, and both are optional">
<TabStrip
tabs={[
{ key: 'prehled', label: 'Přehled', icon: 'home' },
{ key: 'posta', label: 'Pošta', icon: 'mail', count: 12 },
{ key: 'ukoly', label: 'Úkoly', icon: 'clipboard-list', count: 3 },
]}
value={tab}
onChange={setTab}
/>
</Labeled>
<span style={label}>
Tabs carry no keyboard shortcut chips. Tab lands on the active tab, arrows and Home/End move
focus, and Enter or Space switches (see the Accessibility tab).
</span>
</div>
);
};Composition
export const Composition = () => {
const [tab, setTab] = useState('aktivita');
return (
<div
style={{
border: '1px solid var(--border)',
borderRadius: 'var(--radius-lg)',
overflow: 'hidden',
background: 'var(--bg-canvas)',
}}
>
<div className="tbar">
<div className="tbar-id">
<span className="tbar-h">Josef Novák</span>
</div>
<span className="vspace" />
<Button>
<Icon name="pencil" />
Upravit
</Button>
</div>
<div style={{ padding: '0 var(--sp-5)' }}>
<TabStrip tabs={WITH_COUNTS} value={tab} onChange={setTab} panelId="josef-tabpanel" />
</div>
{/* The panel lives in the same component, so the strip can point at it: `panelId` gives
the active tab `aria-controls`, the panel names itself after that tab. */}
<div
id="josef-tabpanel"
role="tabpanel"
aria-labelledby={`josef-tabpanel-tab-${tab}`}
style={{ padding: 'var(--sp-5)', color: 'var(--fg-muted)', font: 'var(--small)' }}
>
Obsah záložky „{WITH_COUNTS.find((t) => t.key === tab)?.label}“ — the strip sits under the
record's top bar and switches only the content below it.
</div>
</div>
);
};