Rail

canon

The record panel: context actions, 34px key-value rows edited in place (several values = several rows), tags, description and relations.

Merged 14 September 2026 (AD, RD-1): Key-value row, Editable field and Multi-value were separate pages; they are parts of this one card. The app copy of the row (KeyValueRow, .pr-row) is being replaced by the rail row and deleted. A value with href/onOpen never opens the editor on click and keeps a permanently visible pencil (AD FA10-re); errors are the caller’s toast. The multi-value editor popover lives in the app (components/multivalue/*, ds/multi-value.css). Mobile sheet merged 15 September 2026 (MR !3539): below 720px `MultiValuePopover` renders as a bottom sheet instead of an anchored popover (`.phe-scrim`, `.phe-grab`, `.phe-sheethead`, `.phe-sheetfoot` in `ds/responsive.css`) so it can be closed and confirmed without a keyboard — see "Mobile" in rail.usage.md / rail.a11y.md. Stories rebuilt from the real person rail 15 September 2026 (acrm !3544): four quiet icon actions, Detail záznamu / Procesy / Seznamy, no empty section header; the rail has no shortcuts, so its „With icon & kbd" story and both its baselines are gone. Meeting attendees composition (owner 25.–27. Sep 2026, acrm !4014/!3985, all in `apps/web/src/ds/meeting-record.css` — not yet ported into record-rail.css): field `type: 'number'` for a small bounded count paired with `useConfirm` (speaker count, range 1–20 per the diarization service, raised from 10 on acrm !3985); a relation row's right-aligned talk-share bar (56px `--line-200` track, 4ch tabular-nums percentage) — candidate for a named "share bar" element; an unassigned-voice row with `--sp-3` icon-to-text / `--sp-4` name-to-action spacing; and a scoped exception (`.rr-list--attendees`) that keeps the removable reference's × contextual on touch instead of the global `ds.css` always-visible rule, so it never sits permanently over the share bar. Touch (AD 28 Sep 2026, acrm !4111): the description measures its real clamp (.rr-desc-v.is-clamped, ResizeObserver) and shows the chevron only then; on hover: none copy and the chevron are always visible with 44px hit areas, and copy ↔ pencil keep a 30px gap so two 44px targets never overlap (the rule beats --sp-2). Overrides live in apps/web/src/ds/responsive.css. Registry repaired 17 Oct 2026 (review F4/F11): attached css detail-field.css.

Used in 29 files · 20 areas · 79× — components/PersonaProfilePanel, components/clen-tymu, components/company, components/deal, components/decisions, components/dialer …

Import

import { RecordRail, EditableField, useInlineEdit, RailRows, RailRow } from '@acrm/ui'

Props

RecordRail packages/ui/src/components/RecordRail.tsx

NameTypeDefaultDescription
actionsRRAction[]—vrstva 1 — kontextové akce (tlačítka); bez nich se akční řádek vynechá
titlestring'Detail záznamu'titulek KV sekce (default „Detail záznamu")
countnumber—Počet v hlavičce KV sekce — span.cnt hned za titulkem, TÝŽ prvek i třídy jako u relace (RRRelation.count). Sekce polí ho potřebuje tam, kde nese seznam věcí stejného druhu (schůzka → „Nahrávky 2", FA198); u běžného „Detail záznamu" se nepředává a nic se nekreslí.
fields *RRField[]——
importantCountnumber—kolik polí je „nejdůležitějších" — zbytek za „Zobrazit všechny hodnoty"
onSave(changes: Record<string, string>) => void | Promise<void>—uložení změněného pole {key: value}; bez něj je panel read-only
relationsRRRelation[]—vrstva 3 — relace (Seznamy)
onCopy(value: string, label: string) => void—Vzetí hodnoty do schránky u polí s copyable. Zápis i potvrzení řeší VOLAJÍCÍ — toast je vrstva appky, ne DS, a rail by si ho jinak musel natáhnout dovnitř. Bez handleru se ikona nekreslí vůbec.
renderRef(item: RRRelationItem) => ReactNode—Čím se kreslí reference v řádcích relací. Bez něj se kreslí <EntityRef> přesně jako dosud, takže existující volající se nemění ani vizuálně, ani chováním. ⚠️ Proč render-prop a ne id na položce, ze kterého by si rail náhled natáhl sám: DS balíček NESMÍ znát doménový tvar API — @acrm/ui na @acrm/shared závislost nemá a je to záměr (hlavička packages/shared/src/entity-preview.ts). Takhle rail dál ví jen to, že kreslí referenci, a ČÍM ji kreslí, rozhoduje appka. RRRelationItem.id je pro DS neprůhledný řetězec, který jen propustí ven.
labelsRRLabels——
localestring—BCP-47 locale pro formátování čísel v editoru (P476). Nechat prázdné je NORMÁLNÍ stav: default se bere z <html lang> (documentLocale), který appka drží v souladu s aktivním jazykem — díky tomu se oddělovače přepnou ve všech railech naráz a nové pole na to nemůže zapomenout. Prop je tu pro toho, kdo potřebuje jiné locale než zbytek dokumentu.
collapsiblebooleantrue—
defaultOpenbooleantrue—
childrenReactNode—obsah pod KV sekcí (nad oddělovačem relací)
leadReactNode—Řádky NAD poli sekce (v témže .rr-rows, mimo „Zobrazit vše" — jsou vždy vidět). Pro blok, který skládá řádky z víc zdrojů (náhled obchodu: parametry procesu + pole osoby) pod jeden „Zobrazit vše". Kandidát DS (AD): zpětně kompatibilní, bez něj se nic nemění.
leadAfternumber0Kolik viditelných polí stojí NAD lead (výchozí 0 = lead je první). Náhled obchodu: kontakt (Telefon, E-mail) nahoře, pod ním blok obchodu, pak zbytek polí pod „Zobrazit vše". Kandidát DS (AD).
headlessbooleanfalseBez hlavičky sekce (titulek + chevron): blok nemá nadpis a nejde sbalit. Kandidát DS (AD).

EditableField packages/ui/src/components/RecordRail.tsx

NameTypeDefaultDescription
f *RRField——
commit *(key: string, value: string) => void | Promise<void>—commit jednoho pole; reject = hodnota se nezmění
labelsRRLabels——
onCopy(value: string, label: string) => void——
localestringdocumentLocale()locale pro formátování čísla v poli — viz RecordRailProps.locale.
classNamestring'v'Třída hodnotového slotu — vnějšího <span>. Default v je slot řádku railu (.rr-row .v, record-rail.css). Konzument MIMO .rr-row si pojmenuje svůj vlastní slot: parametr rozbaleného procesu má .pl-v (process-list.css), který drží tytéž míry. ⚠️ Ať je to cokoli, musí to být position: relative — fajfka po uložení (.rr-saved) je uvnitř absolutně.

useInlineEdit

No props extracted (not in props.json).

RailRows packages/ui/src/components/RecordRail.tsx

NameTypeDefaultDescription
fieldsRRField[]—Pole railu (jako RecordRail.fields); nebo místo nich children = <RailRow>.
childrenReactNode——
onSave(changes: Record<string, string>) => void | Promise<void>—Uložení jednoho pole; bez něj jsou řádky read-only.
onCopy(value: string, label: string) => void——
labelsRRLabels——
localestring——
classNamestring—Třída na kořeni .rr-rows.
childrenAfternumber0children se vloží za prvních N polí (výchozí 0 = před pole).
wideboolean—Sekce hlavního sloupce detailu: širší sloupec klíče (--rr-key-w 160 px, na telefonu 108 px), ať se delší klíče (podmínky smluv) neuřezávají. Rail sám je úzký a zůstává na 108 px.

RailRow packages/ui/src/components/RecordRail.tsx

NameTypeDefaultDescription
iconstring——
label *ReactNode——
placeholderstring'—'—
topboolean—Víceřádková hodnota s klíčem nahoře (jako popisové pole railu).
wrapboolean—Hodnota se smí zalomit na víc řádků (místo zkrácení): řádek se zarovná nahoru a dostane svislý vnitřní okraj. Pro hodnoty typu „stará → nová“, popisy (AD 3. 10. 2026).
actionsReactNode—Akce řádku vpravo za hodnotou (.rr-acts) — stejný kontrakt jako tužka/copy railu: skryté, odkryjí se na hover/fokus řádku; na dotyku a ≤720 px trvale viditelné s terčem --tap-min, na telefonu pod hodnotou vpravo. Místo je rezervované, řádek při najetí neposkočí.
childrenReactNode——

CSS classes

.rr-row.rr-sec-h.rr-relrow.rr-dot.rr-status.rr-tags.rr-actions.rr-desc.rr-row .ri.rr-row .k.rr-row .v.rr-ev.rr-ev-pen.rr-ev-copy.rr-edit.rr-vals.rr-val.rr-allvals.chf-lbl.phe-row.phe-type.phe-addrow.phe-x.phe-copy.phe-scrim.phe-grab.phe-sheethead.phe-sheetfootis-clamped

Files