Phone input
canonPhone number typed as a country-code prefix plus a national-number mask; an old value the mask cannot read is shown editable as-is.
Merged to origin/main 28 September 2026 (acrm MR !4085, "fix(web): telefon jako maska s předvolbou (PhoneInput); vzor telefonu do @acrm/ui"), under the owner's binding rule of 14 Sep 2026 (".claude/rules/design-system.md" §"Vstupy a mobil"): a structured value is a mask, never free text. AD 28 Sep 2026 moved the whole phone pattern into @acrm/ui (source=package): `lib/dial-codes.ts` (pure functions — `DIAL_CODES`, `DIAL_MASKS` per country, `maskNational`, `maskTemplate`, `splitDial`, `phoneDigitsFor`, `phoneComplete`, `searchDialCodes`), `DialCodePicker` (country-code chip + search) and two inputs sharing that mask: `PhoneInput` (this card's primary component — a FORM FIELD, `.acrm-inputwrap.acrm-maskwrap.acrm-phonewrap`) and `MaskedPhoneInput` (the row input of the Multi-value editor, `.phe-*`, no `.acrm-inputwrap` frame of its own). Both were previously app-only (`apps/web/src/components/multivalue/`); `masked-input`'s registry entry called this move out as pending — it is done as of !4085, and this is that promised card. Anatomy: the country code is a prefix inside the field (`DialCodePicker`, chip + search), the national number is masked per country from `DIAL_MASKS` (CZ/SK 3·3·3, DE/AT 3·4·4, fallback 3·3·3 for an unlisted code), with the same visible ghost template pattern as `masked-input` (`.acrm-mask-ghost`, typed part transparent, rest muted underscores). Below 720px `DialCodePicker` renders the canonical `BottomSheet` ("Předvolba" title, grab handle, search, full-width Hotovo, backdrop closes, portais into the modal's own dialog when opened from one) instead of the desktop `Popover` — rule 14 Sep 2026 item 5/6 for every mobile overlay; `useSheetMode()` picks the mode. The selected country is kept even with an empty number (a picked country used to snap back to +420 once the digits were cleared — fixed same MR). Value is the display string "+420 601 123 456" (identical to the app's `formatPhone`), so the stored data format is unchanged. An old value the mask cannot read without loss (several numbers "a; b", letters) is shown as-is via the `legacy` branch and stays editable, never rejected or blanked. `phoneComplete(value)` gates saving — an invalid or incomplete phone never blocks the rest of the form; the host simply does not send it (edit keeps the previous stored value). `aria-describedby` now comes from the enclosing `<Field>`/`<DrawerField>` automatically via `useFieldDescribedBy()` unless the caller passes its own `describedBy` (see `field` sub-entry). Used in: Nová firma (`FirmyPage.tsx`), Nová objednávka (`NewOrderForm.tsx`), Nastavení › Profil ("Telefon" and "Telefon 2", `NastaveniPage.tsx`), and SIM record evidence (`TelefonyPage.tsx`) — there the number is the record's key, so the host saves only a valid number or the unchanged old one, never a half-typed new one.
Used in 7 files · 7 areas · 9× — components/multivalue, components/orders, components/org, pages/NastaveniPage, pages/TelefonyPage, pages/komunikace …
@acrm/ui <PhoneInput> <DialCodePicker> <MaskedPhoneInput>.acrm-inputwrap .acrm-maskwrap .acrm-maskwrap.is-error .acrm-phonewrap +12SourceStoryChangelog since 28. 9. 2026
- 28. 10. 2026polish(ds): volby (optchip, dialer chip) mají na dotyku výšku --tap-minapps/web/src/ds/inputs.cssŠtěpán · 1aeb472bc
- 17. 10. 2026refactor(ds): tokeny podle významu — vrstvy, pohyb, focus ring, text na barvě (beze změny vzhledu)apps/web/src/ds/inputs.cssŠtěpán · 8cda81b26
- 4. 10. 2026fix(ui): upozornění červeně se čtvercovým glyfem, žádná hnědá (A3a — sdílené, komunikace, nastavení, veřejné)apps/web/src/ds/inputs.cssŠtěpán · 0a39047cc
- 3. 10. 2026refactor(ds): majetek + telefony = form-less dialog s „Další údaje"packages/ui/src/components/PhoneInput.tsxŠtěpán · ed6a142b2
- 3. 10. 2026feat(ucetnictvi): vysvětlivky W4a — účetnictví do (i); Field: doplněk popisku vedle <label>apps/web/src/ds/inputs.cssŠtěpán · 716aabc80
- 3. 10. 2026chore(ds): pryč slider (deprecated, 0 použití) a poslední mrtvé pravidlo .pp-atapps/web/src/ds/inputs.cssŠtěpán · 416bc2c09
- 3. 10. 2026refactor(ds): MultiSelect → Select multiple (kanon nahrazuje, vizuál 1:1)apps/web/src/ds/inputs.cssŠtěpán · 898e1e106
- 2. 10. 2026polish(ds): glyfy railu, přepínače navigace a × štítku na --ico-* (parita s boxem)apps/web/src/ds/inputs.cssŠtěpán · 566eec74c
- 1. 10. 2026fix(web): výběr záznamu bez Dialogu — rail schůzky, intro, obchod u objednávky, přidat obchod v poštěapps/web/src/ds/inputs.cssŠtěpán · b56c5c03b
- 1. 10. 2026refactor(ui): Combobox pryč — poslední volání na RecordSelect, smazán z @acrm/uiapps/web/src/ds/inputs.cssŠtěpán · aedeca283
- 1. 10. 2026fix(web): radky pod seznamem s hledanim licuji se sloupcem radku (akce PickerMenu, CheckMenu)apps/web/src/ds/inputs.cssŠtěpán · fca0ca71e
- 1. 10. 2026fix(ui): suplik roste i kdyz panel naroste po otevreni; bez Zadne vysledky u nabidnute akceapps/web/src/ds/inputs.cssŠtěpán · 7970f5145
- 1. 10. 2026fix(ui): muted radek - ztlumi se jen znacka, jmeno zustava citelne (AD: kontrast 2,85:1)apps/web/src/ds/inputs.cssŠtěpán · 09c55a7b3
- 1. 10. 2026feat(ui): RecordOption muted + subTone warn - tlumeny radek a varovny druhy udaj z kanonuapps/web/src/ds/inputs.cssŠtěpán · 7e686deb9
- 1. 10. 2026refactor(ui): holy Popover -> kanon (vlna 3) + nabidka v dialogu = panel v suplikuapps/web/src/ds/inputs.cssŠtěpán · 5a55c030f
- 29. 9. 2026fix(ui): „×" v Record selectu je tlačítko vedle triggeru, ne span role=button uvnitřapps/web/src/ds/inputs.cssŠtěpán · 2ea16ab8c
- 28. 9. 2026fix(ds-stories): audit DS — stories 1:1 s appkouapps/web/src/ds/inputs.cssŠtěpán · 251278e32
- 28. 9. 2026fix(ui): položka nabídky = jeden řádek, bez šedé vysvětlivky (ACRM-375–377)apps/web/src/ds/inputs.cssŠtěpán · 1a87decfc
- 28. 9. 2026fix(ds): zaškrtnutý checkbox (is-on/is-mixed) v tmavé = akcentní výplňapps/web/src/ds/inputs.cssŠtěpán · 0ee663488