Record drawer
canonThe side panel that opens a record next to its listing without leaving it; the listing behind stays live. The only drawer in the canon.
React implementation lives in the app (apps/web/src/components/drawer/RecordDrawer.tsx); DrawerForm shares only the .pdr shell, not .pdr-sec/.pdr-row. AD 14 September 2026 (OV-2): the only drawer — Drawer form is deprecated. The catalog stories and the overlays proposal still show an OLD body (.pdr-sec/.pdr-row sections) and an old two-button footer („Přidat do segmentu" / „Odemknout informace"). Measured on deployed dev (Lidé → row click): the body is the record rail in drawer variant (PersonRail variant="drawer": stav spolupráce, quick actions, nejbližší aktivita, quick note, Aktivita/Poznámky tabs, Detail záznamu) and the footer holds one primary „Otevřít osobu" — the same card the ↗ in the header opens in a new tab. Stories must be rebuilt from the real call site. Delivered 14 September 2026 (OV-2b, acrm !3515), verified on dev: the footer is gone; ↗ in the head opens the detail in the same tab with the listing context (onOpenDetail), Ctrl/⌘+click opens a new tab; the dead „Odemknout informace" button in segments went with it. Catalog stories still show the old body and footer — to be rebuilt.
Used in 6 files · 6 areas · 7× — components/setkani, pages/FirmyPage, pages/LidePage, pages/PipelinePage, pages/SegmentDetailPage, pages/telefony
apps/web <RecordDrawer>.pdr .pdr-rail .pdr-sec .pdr-row +1SourceStoryLegacy card Person Drawer.htmlLegacy card Company Drawer.htmlLegacy card Deal Drawer.htmlDefault
export const Default = () => (
<Stage>
<PersonDrawer />
</Stage>
);Variants
export const Variants = () => (
<div style={{ display: 'grid', gap: 'var(--sp-5)' }}>
<Labeled text="person — avatar in the head, fact strip, sections of rows">
<Stage h={440}>
<PersonDrawer />
</Stage>
</Labeled>
<Labeled text="without the fact strip — the drawer is only as tall as its content (there is no footer to fall back on, see the header comment)">
<Stage h={360}>
<PersonDrawer facts={false} />
</Stage>
</Labeled>
<Labeled text="company — square mark instead of a round avatar">
<Stage h={360}>
<aside className="pdr" aria-label="Náhled — Brno Tech a.s.">
<div className="pdr-top">
<div className="pdr-top-id">
<span className="pdr-top-av square">
<Icon name="building" />
</span>
<span className="pdr-top-name">Brno Tech a.s.</span>
</div>
<IconButton icon="x" title="Zavřít" />
</div>
<div className="pdr-body">
<div className="pdr-sec">
<h3 className="pdr-sec-t">Firma</h3>
<div className="pdr-rows">
<Row icon="world" k="Web">
brnotech.cz
</Row>
<Row icon="users" k="Kontakty">
12
</Row>
<Row icon="user" k="Vlastník">
Eva Dvořáková
</Row>
</div>
</div>
</div>
</aside>
</Stage>
</Labeled>
</div>
);States
export const States = () => (
<div style={{ display: 'grid', gap: 'var(--sp-5)' }}>
<Labeled text="loading — one sentence in the middle, the head keeps the close button">
<Stage h={300}>
<aside className="pdr" aria-busy="true" aria-label="Načítání náhledu">
<div className="pdr-top">
<div className="pdr-top-id" />
<IconButton icon="x" title="Zavřít" />
</div>
<div
className="pdr-body"
style={{
display: 'grid',
placeItems: 'center',
color: 'var(--fg-muted)',
font: 'var(--cell)',
}}
>
Načítám náhled…
</div>
</aside>
</Stage>
</Labeled>
<Labeled text="failed — the drawer says what happened and offers the retry, it does not close itself">
<Stage h={340}>
<aside className="pdr" aria-label="Náhled — chyba">
<div className="pdr-top">
<div className="pdr-top-id" />
<IconButton icon="x" title="Zavřít" />
</div>
<div
className="pdr-body"
style={{
display: 'grid',
placeItems: 'center',
textAlign: 'center',
gap: 'var(--sp-4)',
color: 'var(--fg-muted)',
padding: 'var(--sp-6)',
}}
>
<Icon name="alert-triangle" size={28} />
<div style={{ font: 'var(--h3)', color: 'var(--fg)' }}>Náhled se nenačetl</div>
<div>Detail záznamu se nepodařilo načíst. Zkuste to prosím znovu.</div>
<button className="acrm-btn default" type="button">
<Icon name="refresh" />
Zkusit znovu
</button>
</div>
</aside>
</Stage>
</Labeled>
<Labeled text="masked value — a locked record shows what it may">
<Stage h={260}>
<aside className="pdr" aria-label="Náhled — Josef Novák">
<Top name="Josef Novák" />
<div className="pdr-body">
<div className="pdr-sec">
<h3 className="pdr-sec-t">Kontakt</h3>
<div className="pdr-rows">
<Row icon="phone" k="Telefon">
<span className="mask">+420 6•• ••• •••</span>
</Row>
<Row icon="mail" k="E-mail">
<span className="mask">j•••••@brnotech.cz</span>
</Row>
</div>
</div>
</div>
</aside>
</Stage>
</Labeled>
</div>
);Composition
export const Composition = () => {
const [open, setOpen] = useState(true);
const rows = ['Josef Novák', 'Eva Dvořáková', 'Petr Malý'];
return (
<div
style={{
display: 'grid',
gridTemplateColumns: open ? '1fr auto' : '1fr',
height: 440,
border: '1px solid var(--border)',
borderRadius: 'var(--radius-md)',
overflow: 'hidden',
background: 'var(--bg-canvas)',
}}
>
<div
style={{
padding: 'var(--sp-4)',
display: 'grid',
gap: 'var(--sp-2)',
alignContent: 'start',
}}
>
{rows.map((r) => (
<button
key={r}
type="button"
className="acrm-popover-item"
onClick={() => setOpen(true)}
style={{ width: '100%' }}
>
<span className="tx">
<span className="nm">{r}</span>
</span>
</button>
))}
<span style={label}>
The list keeps working next to the drawer — that is why the drawer has no scrim.
</span>
</div>
{open ? (
<aside className="pdr" aria-label="Náhled — Josef Novák">
<Top name="Josef Novák" onClose={() => setOpen(false)} />
<div className="pdr-body">
<div className="pdr-sec">
<h3 className="pdr-sec-t">Kontakt</h3>
<div className="pdr-rows">
<Row icon="phone" k="Telefon">
+420 605 123 456
</Row>
<Row icon="mail" k="E-mail">
[email protected]
</Row>
</div>
</div>
</div>
</aside>
) : null}
</div>
);
};