@eocrm/design-system 0.6.22 → 0.6.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- # `Kbd`
1
+ # `Kbd` — keyboard-shortcut chips
2
2
 
3
3
  Inline keyboard-shortcut display: one `<kbd>` chip per key, joined with a faint `+` separator. Use for shortcut hints in tooltips, command palettes, search inputs, and help/shortcut sheets.
4
4
 
@@ -1,4 +1,4 @@
1
- # `<MoneyInput>`
1
+ # `<MoneyInput>` — currency picker + formatted decimal-string amount
2
2
 
3
3
  Money field: currency picker (`CurrencySelect`) + formatted amount `Input`, controlled on one `value: MoneyValue` (`{ amount: string | null; currency: string }`), `onChange(value)`. `amount` is a **canonical decimal string** (`"1234.50"`, `"-3"`) or `null` — never a JS number. `currencies` (ISO 4217, required) lists what the picker offers; exactly one renders a static prefix instead. The amount groups as you type in the locale's separators, keeps the caret where you are editing, rejects keystrokes past the currency's precision (2 for EUR, 0 for JPY, 3 for KWD) — a digit typed inside a full fraction overwrites the next one. Backspace/Delete step over group separators, and over the decimal while a fraction follows it (select the decimal to remove it). Blur pads the fraction (`12` → `12.00`). Switching currency rounds the amount half-up to the new precision (and pads it, as blur would). An external `amount` that is not a canonical decimal string displays empty with a dev warning. In comma-decimal locales the numpad `.` key types the decimal; a paste or drop is parsed on its own, then spliced over the selection. It must be exactly one number, optionally with currency symbols, a label word before it (`Total`, `Итого:`) and a currency word after it (an ISO code or `руб.`/`р.` — never a magnitude like `K`, `млн`, `million`) and a sign (`-5`, `−5`, `+5`, `5-`, `(1,234.50)`). Grouping must be unambiguous — first group 1–3 digits, then exactly 3, one kind of group character (`,` `.` space, NBSP, apostrophe) — and with both `.` and `,` the last one is the decimal whatever the locale. Anything doubtful (`12,34` or `1.234` in en, `15.01.2024`, `$5K`, `5 10`, a line break, extra words, a second number, stray parentheses) is rejected with no change, as is a partial paste that adds a second decimal, a sign, or fraction digits past the precision. A whole-value paste truncates to the precision (`$1,234.5678` → `1,234.56`). A negative paste must replace the whole value and needs `allowNegative` (or an already-negative value). `allowNegative` permits a leading `-`; without it, adding a `-` (typed or pasted) is rejected rather than silently dropped, and an existing external negative keeps its sign while edited. `onChange` fires on every edit and currency switch — never for blur-without-edit or external value changes. `name` emits hidden `<name>` / `<name>_currency` inputs.
4
4
 
@@ -1,4 +1,4 @@
1
- # `<PhoneInput>`
1
+ # `<PhoneInput>` — E.164 phone field with country picker
2
2
 
3
3
  International phone field: searchable country picker (DS `Select`) + national-number `Input`, controlled on a single `value: string | null` (**E.164**), `onChange(e164 | null)`. `defaultCountry` (ISO alpha-2) seeds the picker when empty; `countries` restricts the list; `size`/`invalid`/`disabled` pass through. Country names are localized via `Intl.DisplayNames`; metadata/validation via `libphonenumber-js`. Validate with the exported `isValidPhone(e164)` and drive `invalid` (or wrap in `<Field error>`). `countryDisplay` sets how the SELECTED country shows in the trigger — `"code"` (default, `+1`), `"iso"` (`US +1`), `"name"` (`United States +1`), or `"flag"` (`🇺🇸 +1`; emoji flags don't render on Windows Chrome/Edge). Dropdown rows always show the full name + code (searchable); opening the picker selects the text for instant type-to-search; the country can't be cleared. The picker's accessible name is composed from the group's (`<Field label="Phone">` → "Phone Country"), so it never collides with a form's own Country field; `countryLabel` overrides it. Store the emitted E.164, not the formatted display.
4
4
 
@@ -54,3 +54,10 @@ Use for per-file upload bars, wizard step indicators, form-completion meters and
54
54
  - ❌ A hand-rolled `<div style={{ width: `${n}%`, … }}>` bar — use the primitive.
55
55
  - ❌ `<Progress role="…">` — `role` is locked to `progressbar`; TypeScript rejects the override.
56
56
  - ❌ A separate consumer-rendered label element — the `label` slot already handles spacing, font size and tabular-nums for stable digit widths.
57
+
58
+ ## Progress hard rule
59
+
60
+ - ❌ Hand-rolled `<div style={{ width: '${n}%', background: '#xxx' }}>` progress bars — use `<Progress value={n}>`.
61
+ - ❌ Hand-rolled spinning `<svg>` per page (every Saving-state, every loader). Use `<CircularProgress />` indeterminate.
62
+ - ❌ `<Progress tone="success" value={100}>` or `<Progress tone="success" />` to "celebrate" completion. Tones communicate STATE during progress (warning at 85%, danger at 95%), not success-on-done. A finished bar is just a finished bar — leave the default tone.
63
+ - ❌ `<CircularProgress value={0}>` to render an empty circle for "not started yet." `value={0}` is determinate (0% done). The intent is usually indeterminate — omit `value` entirely.
@@ -61,3 +61,11 @@ If you need a size or tone the primitive doesn't expose, that is a token-vocabul
61
61
  - `<Text as="h2">`: Text doesn't accept heading tags; use `<Title order={2}>`.
62
62
  - Wrapping a `<Title>` in `<Text>` for tone/weight tweaks: pass tone/weight to the `<Title>`. `size="inherit"` is for runs INSIDE a heading, not for wrapping it.
63
63
  - Nesting `<Text>` in another `<Text>` with the default `as="p"`: the inner `<p>` inside the outer `<p>` is invalid HTML and triggers React's DOM nesting warning. Use `<Text as="span" tone="...">` for an inline override inside a paragraph.
64
+
65
+ ## Typography hard rule
66
+
67
+ - ❌ `style={{ fontSize: 'var(--font-size-sm)' }}` / `style={{ color: 'var(--color-fg-muted)' }}` — use `<Text size="sm" tone="muted">`.
68
+ - ❌ Raw `<h1>` / `<h2>` / `<h3>` — use `<Title order={N}>`.
69
+ - ❌ `<Text style={{ color: '#someHex' }}>` — pick a tone from the whitelist.
70
+ - For semantic emphasis (the bold _is_ the meaning — e.g. a user's name in a notification, an error keyword), use `<strong>` or `<em>` inside `<Text>`. For visual-only weight changes (a medium-weight name in a list because hierarchy says so, not because the name is emphatic), use `<Text weight="medium">`. Pick the one that matches _why_ the text is heavy.
71
+ - If the size / tone / weight you need isn't on `<Title>` or `<Text>`, **that's a token-vocabulary conversation, not a component-skipping conversation.**
@@ -52,3 +52,7 @@
52
52
  - `<h2 className={styles.title}>`: use `<Title order={2}>`; consumer SCSS should never name a typography class.
53
53
  - `<Title order={1} size="xs">`: usually a sign the heading hierarchy is wrong. Bump the order up instead of shrinking a low-order heading.
54
54
  - Skipping heading levels (`order={1}` then `order={4}`) hurts screen-reader users; use sequential orders.
55
+
56
+ ## Typography hard rule
57
+
58
+ No raw `<h1>` / `<h2>` / `<h3>`, no inline `style` font sizes or colours, `<strong>` / `<em>` vs `weight`: see [Text.md](Text.md#typography-hard-rule).