Record select
canonSearch-driven choice from records: framed field, or panel anchored to a rail row or a chip. Sections separated by a named hairline, one value = accent check.
Absorbed Rail picker on 12 September 2026 (AD, RP-1; acrm !3425–!3443): with anchorRef the panel hangs under a rail row or a chip instead of a framed trigger, so the rail has no picker of its own and RailPickerMenu is deleted. Also new: sections whose name sits IN the hairline (only when there are two or more; a single name would be the forbidden heading above a menu), selected pinned on top, accent check for one value (F261), diacritics-folded search over sub and keywords, quick-create footer, minWidth, loading/empty/busy, UserAvatar for a colleague. Known limit: the section name is var(--label) in --fg-muted = 2.85:1 light / 3.63:1 dark, below AA in both themes — the lighter tone is the AD's explicit choice of 12 September 2026 (ACRM-227, acrm !3460), made with the numbers in front of him; the group is announced from role=group + aria-label, not from this text. The hand-made panel shell is blocked by the validator rule recordselect-shell-handmade. Slot wrapOption(option, row) (acrm !3913, AD 26 Sep 2026): the host may WRAP an option row in an element that clicks nothing and does not change its size (hover preview EntityPreviewChip); selection, keys and look stay with RecordSelect. Used by RecordLinksChip — the one links chip of shape-A dialogs (task, deal, meeting, note), selected records show the record preview on hover.
Used in 46 files · 31 areas · 52× — components/RecordLinksChip, components/ReservationControl, components/TagsEditor, components/accounting, components/acl, components/clen-tymu …
@acrm/ui <RecordSelect>.acrm-recordselect .acrm-recordselect-trigger .acrm-recordselect-pop .acrm-recordselect-search +11SourceStoryLegacy card Inputs.htmlImport
import { RecordSelect } from '@acrm/ui'
Props
RecordSelect packages/ui/src/components/RecordSelect.tsx
| Name | Type | Default | Description |
|---|---|---|---|
options * | RecordOption[] | — | — |
value * | string | string[] | null | undefined | — | multi=false → string|null; multi=true → string[]. |
onChange * | (value: string | string[]) => void | — | — |
multi | boolean | false | — |
placeholder | string | 'Doplnit…' | — |
searchPlaceholder | string | 'Hledat záznamy…' | — |
className | string | — | — |
ariaLabel | string | — | — |
fullWidth | boolean | false | Trigger na plnou šířku kontejneru (jinak default 280px). |
clearable | boolean | false | Zobraz „×" pro vymazání hodnoty (single: → '', multi: → []). |
disabled | boolean | false | Zamkne výběr (načítání / jediná možnost) — trigger disabled, neotevře se. |
anchorRef | RefObject<HTMLElement | null> | — | Cizí kotva místo rámovaného pole (hodnota řádku railu, chip). Namontováno = otevřeno; Esc, klik mimo kotvu i panel a výběr u jedné hodnoty volají onClose. |
onClose | () => void | — | — |
onQueryChange | (query: string) => void | — | Hledání běží na serveru: text jde ven, options přijdou zpět. |
loading | boolean | false | Místo seznamu „Načítám…". |
error | { text: string; onRetry: () => void; } | — | Zdroj voleb selhal (AD 30. 9. 2026): místo seznamu JEDEN řádek — věta a akce „Zkusit znovu" (kompaktní ghost tlačítko), ne šedá vysvětlivka a ne prázdná nabídka. |
emptyText | string | — | Text prázdného číselníku (bez hledání). Nenalezeno je vždy „Žádné výsledky". |
onCreate | (text: string) => void | — | Patička „Založit „…"" (+ Enter) — nabídne se, když napsané přesně nesedí na žádnou položku. |
createLabel | (text: string) => string | — | Text patičky onCreate místo „Založit „…"" — např. „Použít text „…"" u volného textu produktu. Bez vysvětlivky v řádku (AD 1. 10. 2026): rozdíl nese glyf createIcon. |
createIcon | string | 'plus' | Glyf patičky onCreate (výchozí plus), např. type u volného textu. |
createPosition | 'top' | 'bottom' | 'bottom' | Kde stojí řádek onCreate: bottom = patička (výchozí), top = první řádek seznamu — napsaná e-mailová adresa „Nový kontakt: …" (AD 1. 10. 2026): fulltext na adresu vrací hlavně šum, takže odpovědí je akce. V režimu přidávání ho pak Enter bere jako první. |
filter | boolean | true | false = volby už vyfiltroval server (onQueryChange) a klient je podruhé nefiltruje — jinak by zahodil shody, které server našel podle údaje mimo jméno (telefon, IČO). Používá se JEN s hledáním na serveru. emptyText pak platí i pro prázdnou odpověď na dotaz („Napište alespoň 3 znaky"). |
keepOpen | boolean | false | Režim přidávání (AD 1. 10. 2026): výběr zavolá onChange, vymaže dotaz a panel zůstane otevřený s fokusem v hledání pro další položku. Bez checkboxů — není to vícevýběr sady, vybraná položka se u hostitele stává chipem a ze seznamu zmizí. Na telefonu v šuplíku dialogu má panel dole Hotovo jako vícevýběr; Esc a tap mimo zavírají. |
busy | boolean | false | Zápis běží (fronta mutací) — jen aria-busy, volby zůstávají klikací. |
minWidth | number | — | Minimální šířka panelu u úzké kotvy (chip); jinak šířka = kotva. |
maxWidth | number | — | Strop šířky panelu u široké kotvy — pole přes celý řádek dialogu (Komu, Řešitel): panel návrhů by jinak měl šířku celého řádku (1021 px na 1440). |
wrapOption | (option: RecordOption, row: ReactElement<unknown, string | JSXElementConstructor<any>>) => ReactNode | — | Obal řádku volby (26. 9. 2026, ArtDirector): host smí řádek obalit vlastním prvkem, který sám nic neklikne — typicky hover náhled záznamu (EntityPreviewChip). Výběr, klávesy i vzhled řádku dál řídí RecordSelect; bez propu je řádek beze změny. Obal NESMÍ mít vlastní onClick ani měnit rozměr řádku (jen naslouchá najetí a fokusu). |
query | string | — | Řízený dotaz (AD 1. 10. 2026, stejné pravidlo jako PickerMenuPanel přes useControlledQuery): když je query zadané, panel dotaz nedrží a jen hlásí změny přes onQueryChange. |
searchField | boolean | true | false = panel bez vlastního hledání: píše se v poli VOLAJÍCÍHO (našeptávač u pole — Komu, Řešitel, položky obchodu; bod B, AD 1. 10. 2026). Dotaz pak přichází přes query, panel se nekreslí jako šuplík (je to našeptávání při psaní, ne výběr) a klávesnice jde z fieldRef. |
fieldRef | RefObject<HTMLInputElement | null> | — | Pole volajícího u searchField={false}: fokus v něm zůstává, ↑↓ posouvají aktivní řádek (aria-activedescendant), Enter/Tab vybere aktivní, Esc zavře. Pole dostane roli combobox. |
CSS classes
Files
packages/ui/src/components/RecordSelect.tsxpackages/ui/src/lib/fold-text.tsapps/web/src/ds/inputs.cssapps/web/src/ds.css