@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.
- package/llms.txt +194 -3
- 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={
|
|
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.
|
|
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
|
|