@reformer/ui-kit 1.0.0-beta.3 → 1.0.0-beta.4

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.
Files changed (2) hide show
  1. package/llms.txt +194 -3
  2. package/package.json +1 -1
package/llms.txt CHANGED
@@ -13,6 +13,7 @@
13
13
  - 06-troubleshooting.md — Troubleshooting / FAQ
14
14
  - 07-form-wizard.md — FormWizard — multi-step форма
15
15
  - 08-form-array-section.md — FormArraySection — UI для FormArray
16
+ - 09-input-mask.md — InputMask — поля с маской ввода
16
17
  - API Reference (auto-generated from JSDoc)
17
18
 
18
19
  ## 1. Installation
@@ -1488,7 +1489,7 @@ const hasValue = Boolean(value);
1488
1489
  import { useRef } from 'react';
1489
1490
  import { FormWizard, type FormWizardStep } from '@reformer/ui-kit/form-wizard';
1490
1491
  import type { FormWizardHandle } from '@reformer/cdk/form-wizard';
1491
- import type { FormProxy } from '@reformer/core';
1492
+ import type { FormProxy, ValidationSchemaFn } from '@reformer/core';
1492
1493
 
1493
1494
  // Используйте `type`, не `interface`, для structural-совместимости с
1494
1495
  // FormFields constraint внутри FormWizard generic'а.
@@ -1512,19 +1513,64 @@ const steps: FormWizardStep<MyForm>[] = [
1512
1513
  { number: 3, title: 'Готово', icon: '✓', body: <ConfirmationView /> },
1513
1514
  ];
1514
1515
 
1516
+ // ⚠️ КРИТИЧНО: `stepValidations` это **Record<number, ValidationSchemaFn<T>>**,
1517
+ // НЕ array. Если объявить как array `[step1Fn, step2Fn]` — FormWizard молча
1518
+ // пропустит валидацию, "Далее" сработает без проверок. Silent no-op, без
1519
+ // runtime warning.
1520
+ const STEP_VALIDATIONS: Record<number, ValidationSchemaFn<MyForm>> = {
1521
+ 1: (path) => {
1522
+ required(path.email);
1523
+ email(path.email);
1524
+ },
1525
+ 2: (path) => {
1526
+ required(path.password);
1527
+ minLength(path.password, 8);
1528
+ },
1529
+ };
1530
+
1531
+ const fullValidation: ValidationSchemaFn<MyForm> = (path) => {
1532
+ STEP_VALIDATIONS[1](path);
1533
+ STEP_VALIDATIONS[2](path);
1534
+ required(path.confirmation);
1535
+ };
1536
+
1515
1537
  // ref типизируется явно типом формы; constraint `T extends Record<string, any>`
1516
1538
  // позволяет nullable-поля (`number | null`) внутри MyForm без TS-ошибок.
1517
1539
  const navRef = useRef<FormWizardHandle<MyForm>>(null);
1518
1540
 
1541
+ // ВАЖНО: prop-level `onSubmit` имеет signature `() => void | Promise<void>` —
1542
+ // БЕЗ аргумента values. Это by-design (см. FormWizardActionsProps в @reformer/cdk).
1543
+ // Чтобы получить values — читай их из form внутри handler:
1544
+ const handleSubmit = async () => {
1545
+ const values = form.getValue();
1546
+ await api.submit(values);
1547
+ };
1548
+
1519
1549
  <FormWizard
1520
1550
  ref={navRef}
1521
1551
  form={form}
1522
1552
  config={{ stepValidations: STEP_VALIDATIONS, fullValidation }}
1523
1553
  steps={steps}
1524
- onSubmit={async (values) => api.submit(values)}
1554
+ onSubmit={handleSubmit}
1525
1555
  />;
1526
1556
  ```
1527
1557
 
1558
+ ### Альтернатива — imperative submit с values
1559
+
1560
+ `navRef.current?.submit(callback)` — отдельный API, ПРИНИМАЕТ `(values: T) =>` callback.
1561
+ Удобно для save-and-exit flow:
1562
+
1563
+ ```tsx
1564
+ // FormWizardHandle.submit<R>(cb: (values: T) => Promise<R> | R): Promise<R | null>
1565
+ const handleSaveAndExit = async () => {
1566
+ const result = await navRef.current?.submit((values) => api.saveDraft(values));
1567
+ if (result) router.push('/dashboard');
1568
+ };
1569
+ ```
1570
+
1571
+ Различие — два разных entry point: `<FormWizard onSubmit={...}>` (no-arg, для submit-button click)
1572
+ и `navRef.current?.submit(values => ...)` (с values, для programmatic submit).
1573
+
1528
1574
  ## 38. Полиморфный `step.body`
1529
1575
 
1530
1576
  `body` принимает три формы (runtime-discriminated):
@@ -1565,6 +1611,38 @@ const renderSchema = (path) => ({
1565
1611
 
1566
1612
  ui-kit FormWizard детектирует RenderNode (объект с `.component` без React-element-маркера) и оборачивает в `RenderNodeComponent` с `form={form}`.
1567
1613
 
1614
+ ### ⚠️ RenderNode body требует RenderContextProvider
1615
+
1616
+ Когда `step.body` это `RenderNode<T>` (renderer-react / renderer-json flow), **FormWizard ДОЛЖЕН быть обёрнут в `<RenderContextProvider>`** или находиться внутри `<FormRenderer>`. Иначе runtime-ошибка:
1617
+
1618
+ ```
1619
+ useRenderContext must be used within RenderContextProvider (FormRenderer)
1620
+ ```
1621
+
1622
+ `RenderNodeComponent` (которым FormWizard рендерит body) использует context — без провайдера контекст не найден.
1623
+
1624
+ **Canonical mounting** для renderer-react:
1625
+
1626
+ ```tsx
1627
+ import { FormRenderer } from '@reformer/renderer-react';
1628
+ import { FormField } from '@reformer/ui-kit';
1629
+
1630
+ // FormWizard как root render-node — FormRenderer уже даёт RenderContextProvider:
1631
+ <FormRenderer render={schema} settings={{ fieldWrapper: FormField }} />
1632
+ ```
1633
+
1634
+ **Если FormWizard рендерится напрямую** (не как root render-node, а внутри обычного React-tree, но с RenderNode bodies) — оберни вручную:
1635
+
1636
+ ```tsx
1637
+ import { RenderContextProvider } from '@reformer/renderer-react';
1638
+
1639
+ <RenderContextProvider settings={{ fieldWrapper: FormField }}>
1640
+ <FormWizard form={form} steps={steps} ... />
1641
+ </RenderContextProvider>
1642
+ ```
1643
+
1644
+ **TS-flow body (FC компоненты) — провайдер НЕ нужен**: ui-kit FormWizard рендерит FC напрямую без render-pipeline.
1645
+
1568
1646
  ## 40. JSON
1569
1647
 
1570
1648
  ```jsonc
@@ -1766,7 +1844,120 @@ initialValue={{ type: 'apartment', description: '', estimatedValue: 0 }}
1766
1844
 
1767
1845
  FieldConfig попадает в значение поля → Textarea рендерит `[object Object]`, Checkbox флипается в `true`. Compiler/тесты не ловят.
1768
1846
 
1769
- ## 48. API Reference
1847
+ ## 48. Schema-driven (canonical pattern)
1848
+
1849
+ Объяви InputMask как `component` поля; передай `mask` в `componentProps`:
1850
+
1851
+ ```ts
1852
+ import { createForm, type FormSchema } from '@reformer/core';
1853
+ import { InputMask } from '@reformer/ui-kit';
1854
+
1855
+ type ContactForm = {
1856
+ phone: string;
1857
+ passport: string;
1858
+ inn: string;
1859
+ snils: string;
1860
+ };
1861
+
1862
+ const form = createForm<ContactForm>({
1863
+ form: {
1864
+ phone: {
1865
+ value: '',
1866
+ component: InputMask,
1867
+ componentProps: { label: 'Телефон', mask: '+7 (999) 999-99-99' },
1868
+ },
1869
+ passport: {
1870
+ value: '',
1871
+ component: InputMask,
1872
+ componentProps: { label: 'Серия и номер паспорта', mask: '9999 999999' },
1873
+ },
1874
+ inn: {
1875
+ value: '',
1876
+ component: InputMask,
1877
+ componentProps: { label: 'ИНН', mask: '999999999999' },
1878
+ },
1879
+ snils: {
1880
+ value: '',
1881
+ component: InputMask,
1882
+ componentProps: { label: 'СНИЛС', mask: '999-999-999 99' },
1883
+ },
1884
+ } satisfies FormSchema<ContactForm>,
1885
+ });
1886
+ ```
1887
+
1888
+ Render как обычно через `FormField`:
1889
+
1890
+ ```tsx
1891
+ import { FormField } from '@reformer/ui-kit';
1892
+
1893
+ <FormField control={form.phone} testId="phone" />
1894
+ <FormField control={form.passport} testId="passport" />
1895
+ <FormField control={form.inn} testId="inn" />
1896
+ <FormField control={form.snils} testId="snils" />
1897
+ ```
1898
+
1899
+ ## 49. Common masks
1900
+
1901
+ | Поле | Маска | Пример вывода |
1902
+ | ----------------------- | ---------------------- | ------------------- |
1903
+ | Телефон РФ | `+7 (999) 999-99-99` | `+7 (495) 123-45-67` |
1904
+ | Серия+номер паспорта | `9999 999999` | `4501 123456` |
1905
+ | Серия паспорта | `99 99` | `45 01` |
1906
+ | Номер паспорта | `999999` | `123456` |
1907
+ | Код подразделения | `999-999` | `770-001` |
1908
+ | ИНН (физлицо) | `999999999999` | `771234567890` |
1909
+ | ИНН (юрлицо) | `9999999999` | `7712345678` |
1910
+ | СНИЛС | `999-999-999 99` | `123-456-789 01` |
1911
+ | Почтовый индекс | `999999` | `123456` |
1912
+ | Дата (DD.MM.YYYY) | `99.99.9999` | `15.05.1990` |
1913
+ | Карта | `9999 9999 9999 9999` | `4111 1111 1111 1111` |
1914
+
1915
+ ## 50. Validation
1916
+
1917
+ `InputMask` пишет в значение **то, что ввёл пользователь** (с literal-символами маски).
1918
+ Для validation используй:
1919
+
1920
+ - `required(path.phone)` — на пустоту
1921
+ - `minLength(path.phone, 18)` — для проверки длины с literal-символами (телефон ровно 18 символов)
1922
+ - `pattern(path.phone, /^\+7 \(\d{3}\) \d{3}-\d{2}-\d{2}$/)` — точный pattern если нужно
1923
+
1924
+ ```ts
1925
+ const validation: ValidationSchemaFn<ContactForm> = (path) => {
1926
+ required(path.phone, { message: 'Телефон обязателен' });
1927
+ pattern(path.phone, /^\+7 \(\d{3}\) \d{3}-\d{2}-\d{2}$/, {
1928
+ message: 'Неверный формат телефона',
1929
+ });
1930
+
1931
+ required(path.inn);
1932
+ pattern(path.inn, /^\d{12}$/, { message: 'ИНН должен содержать 12 цифр' });
1933
+ };
1934
+ ```
1935
+
1936
+ ## 51. Advanced — strict mask через FormField + children slot
1937
+
1938
+ Если нужна автоматическая вставка literal-символов (true input-mask), используй
1939
+ библиотеку типа `react-input-mask` или `imask` как кастомный child в FormField:
1940
+
1941
+ ```tsx
1942
+ import { FormField } from '@reformer/ui-kit';
1943
+ import InputMask from 'react-input-mask';
1944
+
1945
+ <FormField control={form.phone} testId="phone">
1946
+ <InputMask mask="+7 (999) 999-99-99" maskChar="_" />
1947
+ </FormField>
1948
+ ```
1949
+
1950
+ `FormField` оборачивает child в `CdkFormField.Control asChild` и прокидывает
1951
+ `value` / `onChange` / `onBlur` / `aria-invalid`. См. рецепт
1952
+ [05-form-field-integration.md → Pattern 3](05-form-field-integration.md).
1953
+
1954
+ ## 52. See also
1955
+
1956
+ - [02-text-fields.md](02-text-fields.md) — обычный Input
1957
+ - [05-form-field-integration.md](05-form-field-integration.md) — FormField обёртка
1958
+ - [06-troubleshooting.md](06-troubleshooting.md) — «маска не вставляется автоматически» → используй `react-input-mask`
1959
+
1960
+ ## 53. API Reference
1770
1961
 
1771
1962
  _Auto-generated from JSDoc on public exports._
1772
1963
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reformer/ui-kit",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.4",
4
4
  "description": "Styled form components with Tailwind CSS and Radix UI for @reformer ecosystem",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",