@arturton/react-form-constructor 0.1.3 → 0.2.0

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/README.md CHANGED
@@ -1,153 +1,1784 @@
1
- # React Form Constructor
1
+ # 🎨 React Form Constructor
2
2
 
3
- > Минималистичный конструктор форм для React на базе **react-hook-form**, с удобной декларацией полей, валидацией и масками ввода.
3
+ > Мощный и гибкий конструктор форм для React с двумя подходами: **JSX-based** для полного контроля и **JSON-based** для скорости разработки. Интегрирует **react-hook-form**, **react-number-format** и поддерживает полную кастомизацию.
4
4
 
5
- ## Зачем это нужно
5
+ ## Особенности
6
6
 
7
- Когда форма состоит из набора однотипных полей, удобнее описывать её **данными**, а не JSX-шаблонами. Библиотека помогает:
7
+ - **Два подхода**: выбери тот, что подходит для твоего случая
8
+ - 🎯 **Валидация**: встроенная, кастомная, асинхронная
9
+ - 🔢 **Маски ввода**: телефоны, карты, форматированные числа
10
+ - 🎨 **Полная кастомизация**: стили, классы, кастомные компоненты
11
+ - ⚡ **Производительность**: оптимизирована для больших форм
12
+ - 📦 **Современный стек**: React 18+, TypeScript, ESM/CJS
8
13
 
9
- - быстро собрать форму из массива полей;
10
- - подключить валидацию `react-hook-form` без лишнего кода;
11
- - использовать маски ввода через `react-number-format`;
12
- - гибко стилизовать форму через классы;
13
- - при необходимости перейти на «ручной» рендер через `children`.
14
-
15
- ## Установка
14
+ ## 📦 Установка
16
15
 
17
16
  ```bash
18
- npm i react-form-constructor
17
+ npm install @arturton/react-form-constructor
18
+ ```
19
+
20
+ ### Требования
21
+
22
+ - React `^18` или `^19`
23
+ - react-hook-form `^7`
24
+ - react-number-format `^5`
25
+
26
+ ## 🚀 Два подхода к созданию форм
27
+
28
+ ### Подход 1️⃣: JSX-based (FormProvider)
29
+
30
+ **Когда использовать**: Сложные формы с кастомной разметкой, нестандартными элементами управления, специфичными требованиями к макету.
31
+
32
+ **Преимущества**:
33
+
34
+ - 🎨 Полный контроль над разметкой
35
+ - 🔧 Гибкость в позиционировании элементов
36
+ - 🎯 Комбинируй с любыми React компонентами
37
+ - 📖 Читаемая иерархия JSX
38
+
39
+ **Пример**:
40
+
41
+ ```tsx
42
+ import {
43
+ FormProvider,
44
+ FormInputLayout,
45
+ FormLabel,
46
+ FormInput,
47
+ FormError,
48
+ FormButton,
49
+ } from "react-form-constructor";
50
+
51
+ type LoginForm = {
52
+ email: string;
53
+ password: string;
54
+ };
55
+
56
+ export function LoginForm() {
57
+ return (
58
+ <FormProvider<LoginForm>
59
+ funSubmit={(data) => console.log(data)}
60
+ className="max-w-md mx-auto p-6 bg-white rounded-lg shadow"
61
+ >
62
+ <div className="mb-6">
63
+ <FormInputLayout
64
+ name="email"
65
+ required="Email обязателен"
66
+ pattern={{
67
+ value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
68
+ message: "Введите корректный email",
69
+ }}
70
+ className="mb-4"
71
+ >
72
+ <FormLabel className="block text-sm font-semibold mb-2">
73
+ Email адрес
74
+ </FormLabel>
75
+ <FormInput
76
+ type="email"
77
+ placeholder="вы@example.com"
78
+ className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
79
+ classNameError="border-red-500 bg-red-50"
80
+ />
81
+ <FormError className="text-red-600 text-sm mt-1" />
82
+ </FormInputLayout>
83
+ </div>
84
+
85
+ <div className="mb-6">
86
+ <FormInputLayout
87
+ name="password"
88
+ required="Пароль обязателен"
89
+ minLength={{ value: 6, message: "Минимум 6 символов" }}
90
+ className="mb-4"
91
+ >
92
+ <FormLabel className="block text-sm font-semibold mb-2">
93
+ Пароль
94
+ </FormLabel>
95
+ <FormPasswordInput
96
+ placeholder="••••••••"
97
+ className="w-full"
98
+ inputClassName="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
99
+ classNameError="border-red-500 bg-red-50"
100
+ />
101
+ <FormError className="text-red-600 text-sm mt-1" />
102
+ </FormInputLayout>
103
+ </div>
104
+
105
+ <FormButton
106
+ className="w-full px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition font-semibold"
107
+ disabledError
108
+ >
109
+ Вход
110
+ </FormButton>
111
+ </FormProvider>
112
+ );
113
+ }
19
114
  ```
20
115
 
21
- ## Быстрый старт
116
+ ---
117
+
118
+ ### Подход 2️⃣: JSON-based (FormLayout)
119
+
120
+ **Когда использовать**: Быстрое прототипирование, стандартные формы, когда скорость разработки важнее максимальной гибкости.
121
+
122
+ **Преимущества**:
123
+
124
+ - ⚡ Быстрое создание форм (70% экономия кода)
125
+ - 📊 Конфиг отдельно от компонента
126
+ - ♻️ Легко переиспользовать конфигурации
127
+ - 🎯 Меньше шаблонного кода
128
+
129
+ **Пример**:
22
130
 
23
131
  ```tsx
24
132
  import { FormLayout, type FormField } from "react-form-constructor";
25
133
 
26
134
  type LoginForm = {
27
- phone: string;
135
+ email: string;
28
136
  password: string;
29
137
  };
30
138
 
31
- const fields: FormField<LoginForm>[] = [
139
+ const loginFields: FormField<LoginForm>[] = [
32
140
  {
33
- label: "Телефон",
34
- placeholder: "+7 (___) ___-__-__",
35
- key: "phone",
36
- required: "Введите телефон",
37
- maska: {
38
- required: "Введите телефон",
39
- format: "+7 (###) ###-##-##",
40
- mask: "_",
141
+ key: "email",
142
+ label: "Email адрес",
143
+ type: "email",
144
+ placeholder: "вы@example.com",
145
+ required: "Email обязателен",
146
+ pattern: {
147
+ value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
148
+ message: "Введите корректный email",
41
149
  },
150
+ inputClass:
151
+ "w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500",
152
+ labelClass: "block text-sm font-semibold mb-2",
153
+ errorClass: "text-red-600 text-sm mt-1",
42
154
  },
43
155
  {
156
+ key: "password",
44
157
  label: "Пароль",
158
+ type: "password",
45
159
  placeholder: "••••••••",
46
- key: "password",
47
- required: "Введите пароль",
160
+ required: "Пароль обязателен",
48
161
  minLength: { value: 6, message: "Минимум 6 символов" },
49
- type: "password",
162
+ inputClass:
163
+ "w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500",
164
+ labelClass: "block text-sm font-semibold mb-2",
165
+ errorClass: "text-red-600 text-sm mt-1",
50
166
  },
51
167
  ];
52
168
 
53
- export function Login() {
169
+ export function LoginForm() {
54
170
  return (
55
171
  <FormLayout<LoginForm>
56
- formData={fields}
172
+ formData={loginFields}
57
173
  funSubmit={(data) => console.log(data)}
58
- formClass="form"
59
- buttonClass="btn"
174
+ formClass="max-w-md mx-auto p-6 bg-white rounded-lg shadow"
175
+ containerClass="space-y-6"
176
+ buttonClass="w-full px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition font-semibold"
177
+ buttonName="Вход"
178
+ disabledOnError
60
179
  />
61
180
  );
62
181
  }
63
182
  ```
64
183
 
65
- ## Как это работает
184
+ ---
185
+
186
+ ## 📚 Компоненты FormProvider
187
+
188
+ ### FormProvider
66
189
 
67
- - **`FormLayout`** создаёт форму и сам регистрирует поля через `react-hook-form`.
68
- - Каждое поле описывается типом `FormField<T>` и привязывается к ключу `keyof T`.
69
- - Если передан `formData`, библиотека сама отрисует поля и кнопку отправки.
70
- - Если `formData` не передан — можно рендерить **свои поля** через `children` и использовать `useFormContext()`.
190
+ Контейнер для всей формы. Инициализирует `react-hook-form` и предоставляет контекст для всех полей.
71
191
 
72
- ## Пример с кастомной разметкой (children)
192
+ **Props**:
73
193
 
74
194
  ```tsx
75
- import { FormLayout, useFormContext } from "react-form-constructor";
195
+ interface FormProviderProps<T extends object = any> {
196
+ // Обработчик отправки формы
197
+ funSubmit: (data: T) => void;
76
198
 
77
- function CustomField() {
78
- const { register, errors } = useFormContext<{ email: string }>();
199
+ // Дочерние элементы
200
+ children: React.ReactNode;
79
201
 
80
- return (
81
- <div>
82
- <input {...register("email", { required: "Введите email" })} />
83
- {errors.email && <span>{errors.email.message}</span>}
84
- </div>
85
- );
202
+ // CSS класс для элемента <form>
203
+ className?: string;
204
+
205
+ // Получить доступ к методам react-hook-form
206
+ setFormApi?: (formMethods: any) => void;
86
207
  }
208
+ ```
87
209
 
88
- export function CustomForm() {
89
- return (
90
- <FormLayout funSubmit={(data) => console.log(data)}>
91
- <CustomField />
92
- </FormLayout>
93
- );
210
+ **Пример**:
211
+
212
+ ```tsx
213
+ <FormProvider<MyForm>
214
+ funSubmit={(data) => {
215
+ console.log("Отправка:", data);
216
+ // Отправить на сервер
217
+ }}
218
+ className="flex flex-col gap-4"
219
+ setFormApi={(methods) => {
220
+ console.log("Form API доступен:", methods.register, methods.errors);
221
+ }}
222
+ >
223
+ {/* Поля формы */}
224
+ </FormProvider>
225
+ ```
226
+
227
+ ---
228
+
229
+ ### FormInputLayout
230
+
231
+ Контейнер для одного поля с поддержкой валидации. Обертка вокруг компонентов ввода.
232
+
233
+ **Props**:
234
+
235
+ ```tsx
236
+ interface FormInputLayoutProps<T extends object = any> {
237
+ // Имя поля (ключ в типе T)
238
+ name: keyof T;
239
+
240
+ // Обязательное поле (сообщение об ошибке)
241
+ required?: string | boolean;
242
+
243
+ // Минимальная длина строки
244
+ minLength?: { value: number; message: string };
245
+
246
+ // Максимальная длина строки
247
+ maxLength?: { value: number; message: string };
248
+
249
+ // Проверка регулярным выражением
250
+ pattern?: { value: RegExp; message: string };
251
+
252
+ // Кастомная функция валидации
253
+ validate?: (value: any) => boolean | string;
254
+
255
+ // Асинхронная валидация
256
+ validateAsync?: (value: any) => Promise<boolean | string>;
257
+
258
+ // Конфиг маски ввода (для FormMaskedInput)
259
+ maska?: { required: string; format: string; mask: string };
260
+
261
+ // CSS класс контейнера
262
+ className?: string;
263
+
264
+ // Дочерние элементы (FormLabel, FormInput, FormError)
265
+ children: React.ReactNode;
94
266
  }
95
267
  ```
96
268
 
97
- ## Валидация
269
+ **Пример**:
270
+
271
+ ```tsx
272
+ <FormInputLayout<ProfileForm>
273
+ name="email"
274
+ required="Email обязателен"
275
+ pattern={{
276
+ value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
277
+ message: "Некорректный email",
278
+ }}
279
+ className="mb-4"
280
+ >
281
+ <FormLabel className="font-semibold">Email</FormLabel>
282
+ <FormInput placeholder="your@email.com" className="input" />
283
+ <FormError className="text-red-600 text-sm" />
284
+ </FormInputLayout>
285
+ ```
286
+
287
+ ---
288
+
289
+ ### FormLabel
98
290
 
99
- Поддерживаются стандартные правила `react-hook-form`:
291
+ Компонент для рендера подписи поля.
100
292
 
101
- - `required`
102
- - `minLength`
103
- - `maxLength`
104
- - `pattern`
105
- - `validate`
293
+ **Props**:
294
+
295
+ ```tsx
296
+ interface FormLabelProps {
297
+ // CSS класс
298
+ className?: string;
106
299
 
107
- Пример:
300
+ // CSS класс при ошибке валидации
301
+ classNameError?: string;
108
302
 
109
- ```ts
110
- {
111
- key: "username",
112
- label: "Имя",
113
- required: "Обязательное поле",
114
- minLength: { value: 3, message: "Минимум 3 символа" },
115
- pattern: { value: /^[a-z]+$/i, message: "Только буквы" },
303
+ // Содержимое
304
+ children: React.ReactNode;
116
305
  }
117
306
  ```
118
307
 
119
- ## Маски ввода
308
+ **Пример**:
120
309
 
121
- Поле с маской задаётся через `maska`:
310
+ ```tsx
311
+ <FormLabel className="block text-sm font-bold mb-2">Ваше имя</FormLabel>
312
+ ```
122
313
 
123
- ```ts
124
- {
125
- key: "phone",
126
- label: "Телефон",
127
- maska: {
128
- required: "Введите телефон",
314
+ ---
315
+
316
+ ### FormInput
317
+
318
+ Базовый текстовый ввод с поддержкой различных типов.
319
+
320
+ **Props**:
321
+
322
+ ```tsx
323
+ interface FormInputProps {
324
+ // Тип инпута: text, email, password, url, tel, search и т.д.
325
+ type?: string;
326
+
327
+ // Плейсхолдер
328
+ placeholder?: string;
329
+
330
+ // CSS класс инпута
331
+ className?: string;
332
+
333
+ // CSS класс при ошибке
334
+ classNameError?: string;
335
+
336
+ // Отключить поле
337
+ disabled?: boolean;
338
+
339
+ // Значение по умолчанию
340
+ defaultValue?: string;
341
+
342
+ // Стандартные HTML атрибуты
343
+ [key: string]: any;
344
+ }
345
+ ```
346
+
347
+ **Пример**:
348
+
349
+ ```tsx
350
+ <FormInput
351
+ type="email"
352
+ placeholder="your@email.com"
353
+ className="w-full px-3 py-2 border rounded"
354
+ classNameError="border-red-500 bg-red-50"
355
+ />
356
+ ```
357
+
358
+ ---
359
+
360
+ ### FormPasswordInput
361
+
362
+ Специализированный ввод для паролей с кнопкой показать/скрыть.
363
+
364
+ **Props**:
365
+
366
+ ```tsx
367
+ interface FormPasswordInputProps {
368
+ // Плейсхолдер
369
+ placeholder?: string;
370
+
371
+ // CSS класс для контейнера
372
+ className?: string;
373
+
374
+ // CSS класс для инпута
375
+ inputClassName?: string;
376
+
377
+ // CSS класс при ошибке
378
+ classNameError?: string;
379
+
380
+ // React элемент для иконки видимости
381
+ visibleIcon?: React.ReactNode;
382
+
383
+ // React элемент для иконки скрытия
384
+ hiddenIcon?: React.ReactNode;
385
+
386
+ // CSS класс для иконки
387
+ iconClassName?: string;
388
+
389
+ // CSS класс для контейнера иконки
390
+ iconWrapperClassName?: string;
391
+
392
+ // Отключить поле
393
+ disabled?: boolean;
394
+
395
+ // Значение по умолчанию
396
+ defaultValue?: string;
397
+ }
398
+ ```
399
+
400
+ **Пример**:
401
+
402
+ ```tsx
403
+ <FormPasswordInput
404
+ placeholder="Введите пароль"
405
+ className="flex items-center gap-2"
406
+ inputClassName="flex-1 px-3 py-2 border rounded"
407
+ iconClassName="w-5 h-5 text-gray-500 cursor-pointer"
408
+ visibleIcon={<EyeIcon />}
409
+ hiddenIcon={<EyeOffIcon />}
410
+ />
411
+ ```
412
+
413
+ ---
414
+
415
+ ### FormTextarea
416
+
417
+ Многострочный ввод текста.
418
+
419
+ **Props**:
420
+
421
+ ```tsx
422
+ interface FormTextareaProps {
423
+ // Плейсхолдер
424
+ placeholder?: string;
425
+
426
+ // Количество строк
427
+ rows?: number;
428
+
429
+ // Количество столбцов
430
+ cols?: number;
431
+
432
+ // CSS класс
433
+ className?: string;
434
+
435
+ // CSS класс при ошибке
436
+ classNameError?: string;
437
+
438
+ // Отключить поле
439
+ disabled?: boolean;
440
+
441
+ // Значение по умолчанию
442
+ defaultValue?: string;
443
+ }
444
+ ```
445
+
446
+ **Пример**:
447
+
448
+ ```tsx
449
+ <FormTextarea
450
+ placeholder="Расскажите о себе..."
451
+ rows={5}
452
+ className="w-full px-3 py-2 border rounded resize-none"
453
+ />
454
+ ```
455
+
456
+ ---
457
+
458
+ ### FormMaskedInput
459
+
460
+ Ввод с форматированием и маской (телефон, карта, валюта и т.д.).
461
+
462
+ **Требует**: `maska` конфиг в `FormInputLayout`
463
+
464
+ **Props**:
465
+
466
+ ```tsx
467
+ interface FormMaskedInputProps {
468
+ // Плейсхолдер
469
+ placeholder?: string;
470
+
471
+ // CSS класс
472
+ className?: string;
473
+
474
+ // CSS класс при ошибке
475
+ classNameError?: string;
476
+
477
+ // Отключить поле
478
+ disabled?: boolean;
479
+ }
480
+ ```
481
+
482
+ **Пример**:
483
+
484
+ ```tsx
485
+ <FormInputLayout
486
+ name="phone"
487
+ maska={{
488
+ required: "Телефон обязателен",
129
489
  format: "+7 (###) ###-##-##",
130
490
  mask: "_",
131
- },
491
+ }}
492
+ >
493
+ <FormLabel>Номер телефона</FormLabel>
494
+ <FormMaskedInput
495
+ placeholder="+7 (___) ___-__-__"
496
+ className="w-full px-3 py-2 border rounded"
497
+ />
498
+ <FormError className="text-red-600 text-sm" />
499
+ </FormInputLayout>
500
+ ```
501
+
502
+ **Доступные маски**:
503
+
504
+ ```tsx
505
+ // Телефон РФ
506
+ maska={{ format: "+7 (###) ###-##-##", mask: "_" }}
507
+
508
+ // Номер кредитной карты
509
+ maska={{ format: "#### #### #### ####", mask: "_" }}
510
+
511
+ // Дата
512
+ maska={{ format: "##/##/####", mask: "_" }}
513
+
514
+ // Процент
515
+ maska={{ format: "###%", mask: "_" }}
516
+ ```
517
+
518
+ ---
519
+
520
+ ### FormSelect
521
+
522
+ Выпадающий список.
523
+
524
+ **Props**:
525
+
526
+ ```tsx
527
+ interface FormSelectProps {
528
+ // Список опций
529
+ options: Array<{
530
+ value: string | number;
531
+ label: string;
532
+ }>;
533
+
534
+ // Множественный выбор
535
+ multiple?: boolean;
536
+
537
+ // Плейсхолдер (пустой вариант)
538
+ placeholder?: string;
539
+
540
+ // CSS класс
541
+ className?: string;
542
+
543
+ // CSS класс при ошибке
544
+ classNameError?: string;
545
+
546
+ // Отключить поле
547
+ disabled?: boolean;
548
+
549
+ // Значение по умолчанию
550
+ defaultValue?: string | number | (string | number)[];
551
+ }
552
+ ```
553
+
554
+ **Пример**:
555
+
556
+ ```tsx
557
+ <FormInputLayout name="country">
558
+ <FormLabel>Страна</FormLabel>
559
+ <FormSelect
560
+ options={[
561
+ { value: "ru", label: "Россия" },
562
+ { value: "kz", label: "Казахстан" },
563
+ { value: "us", label: "США" },
564
+ ]}
565
+ placeholder="Выберите страну"
566
+ className="w-full px-3 py-2 border rounded"
567
+ />
568
+ <FormError className="text-red-600 text-sm" />
569
+ </FormInputLayout>
570
+ ```
571
+
572
+ ---
573
+
574
+ ### FormNumber
575
+
576
+ Ввод чисел с контролем мин/макс значений.
577
+
578
+ **Props**:
579
+
580
+ ```tsx
581
+ interface FormNumberProps {
582
+ // Минимальное значение
583
+ min?: number;
584
+
585
+ // Максимальное значение
586
+ max?: number;
587
+
588
+ // Шаг изменения
589
+ step?: number;
590
+
591
+ // Плейсхолдер
592
+ placeholder?: string;
593
+
594
+ // CSS класс
595
+ className?: string;
596
+
597
+ // CSS класс при ошибке
598
+ classNameError?: string;
599
+
600
+ // Отключить поле
601
+ disabled?: boolean;
602
+
603
+ // Значение по умолчанию
604
+ defaultValue?: number;
605
+ }
606
+ ```
607
+
608
+ **Пример**:
609
+
610
+ ```tsx
611
+ <FormInputLayout name="age">
612
+ <FormLabel>Возраст</FormLabel>
613
+ <FormNumber
614
+ min={18}
615
+ max={120}
616
+ step={1}
617
+ placeholder="Введите возраст"
618
+ className="w-full px-3 py-2 border rounded"
619
+ />
620
+ <FormError className="text-red-600 text-sm" />
621
+ </FormInputLayout>
622
+ ```
623
+
624
+ ---
625
+
626
+ ### FormDate
627
+
628
+ Ввод даты/времени.
629
+
630
+ **Props**:
631
+
632
+ ```tsx
633
+ interface FormDateProps {
634
+ // Тип: date, datetime-local, time, month, week
635
+ type?: "date" | "datetime-local" | "time" | "month" | "week";
636
+
637
+ // Минимальная дата (формат YYYY-MM-DD)
638
+ min?: string;
639
+
640
+ // Максимальная дата (формат YYYY-MM-DD)
641
+ max?: string;
642
+
643
+ // CSS класс
644
+ className?: string;
645
+
646
+ // CSS класс при ошибке
647
+ classNameError?: string;
648
+
649
+ // Отключить поле
650
+ disabled?: boolean;
651
+
652
+ // Значение по умолчанию
653
+ defaultValue?: string;
654
+ }
655
+ ```
656
+
657
+ **Пример**:
658
+
659
+ ```tsx
660
+ <FormInputLayout name="birthDate">
661
+ <FormLabel>Дата рождения</FormLabel>
662
+ <FormDate
663
+ type="date"
664
+ min="1900-01-01"
665
+ max={new Date().toISOString().split("T")[0]}
666
+ className="w-full px-3 py-2 border rounded"
667
+ />
668
+ <FormError className="text-red-600 text-sm" />
669
+ </FormInputLayout>
670
+ ```
671
+
672
+ ---
673
+
674
+ ### FormRange
675
+
676
+ Ползунок для выбора числового значения.
677
+
678
+ **Props**:
679
+
680
+ ```tsx
681
+ interface FormRangeProps {
682
+ // Тип: single (один ползунок) или double (два ползунка)
683
+ range?: "single" | "double";
684
+
685
+ // Минимальное значение
686
+ min?: number;
687
+
688
+ // Максимальное значение
689
+ max?: number;
690
+
691
+ // Шаг
692
+ step?: number;
693
+
694
+ // Показывать текущее значение
695
+ showValue?: boolean;
696
+
697
+ // CSS класс слайдера
698
+ className?: string;
699
+
700
+ // CSS класс контейнера
701
+ containerClassName?: string;
702
+
703
+ // CSS класс при ошибке
704
+ classNameError?: string;
705
+
706
+ // Отключить поле
707
+ disabled?: boolean;
708
+
709
+ // Значение по умолчанию
710
+ defaultValue?: number | [number, number];
711
+ }
712
+ ```
713
+
714
+ **Пример**:
715
+
716
+ ```tsx
717
+ <FormInputLayout name="rating">
718
+ <FormLabel>Оцените (0-10)</FormLabel>
719
+ <FormRange
720
+ range="single"
721
+ min={0}
722
+ max={10}
723
+ step={1}
724
+ showValue
725
+ className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
726
+ containerClassName="flex gap-4 items-center"
727
+ />
728
+ <FormError className="text-red-600 text-sm" />
729
+ </FormInputLayout>
730
+ ```
731
+
732
+ ---
733
+
734
+ ### FormFileInput
735
+
736
+ Загрузка файлов.
737
+
738
+ **Props**:
739
+
740
+ ```tsx
741
+ interface FormFileInputProps {
742
+ // MIME типы или расширения: image/*, .pdf, .doc,docx и т.д.
743
+ accept?: string;
744
+
745
+ // Множественная загрузка
746
+ multiple?: boolean;
747
+
748
+ // CSS класс
749
+ className?: string;
750
+
751
+ // CSS класс при ошибке
752
+ classNameError?: string;
753
+
754
+ // Отключить поле
755
+ disabled?: boolean;
756
+ }
757
+ ```
758
+
759
+ **Пример**:
760
+
761
+ ```tsx
762
+ <FormInputLayout name="avatar">
763
+ <FormLabel>Загрузить аватар</FormLabel>
764
+ <FormFileInput
765
+ accept="image/*"
766
+ className="w-full px-3 py-2 border rounded cursor-pointer"
767
+ />
768
+ <FormError className="text-red-600 text-sm" />
769
+ </FormInputLayout>
770
+
771
+ <FormInputLayout name="documents">
772
+ <FormLabel>Загрузить документы</FormLabel>
773
+ <FormFileInput
774
+ accept=".pdf,.doc,.docx"
775
+ multiple
776
+ className="w-full px-3 py-2 border rounded cursor-pointer"
777
+ />
778
+ <FormError className="text-red-600 text-sm" />
779
+ </FormInputLayout>
780
+ ```
781
+
782
+ ---
783
+
784
+ ### FormCheckbox
785
+
786
+ Одиночный флажок.
787
+
788
+ **Props**:
789
+
790
+ ```tsx
791
+ interface FormCheckboxProps {
792
+ // Значение при отмеченном состоянии
793
+ value?: string | number | boolean;
794
+
795
+ // Отмечено ли по умолчанию
796
+ defaultChecked?: boolean;
797
+
798
+ // Отключить
799
+ disabled?: boolean;
800
+
801
+ // CSS класс
802
+ className?: string;
803
+
804
+ // CSS класс при ошибке
805
+ classNameError?: string;
806
+ }
807
+ ```
808
+
809
+ **Пример**:
810
+
811
+ ```tsx
812
+ <FormInputLayout name="terms">
813
+ <label className="flex items-center gap-3">
814
+ <FormCheckbox value={true} className="w-5 h-5 cursor-pointer" />
815
+ <span>Я согласен с условиями использования</span>
816
+ </label>
817
+ <FormError className="text-red-600 text-sm" />
818
+ </FormInputLayout>
819
+ ```
820
+
821
+ ---
822
+
823
+ ### FormRadio
824
+
825
+ Кнопка-радио для выбора одного из вариантов.
826
+
827
+ **Props**:
828
+
829
+ ```tsx
830
+ interface FormRadioProps {
831
+ // Значение опции
832
+ value: string | number;
833
+
834
+ // Выбрано ли по умолчанию
835
+ defaultChecked?: boolean;
836
+
837
+ // Отключить
838
+ disabled?: boolean;
839
+
840
+ // CSS класс
841
+ className?: string;
842
+
843
+ // CSS класс при ошибке
844
+ classNameError?: string;
845
+ }
846
+ ```
847
+
848
+ **Пример**:
849
+
850
+ ```tsx
851
+ <FormInputLayout name="gender">
852
+ <FormLabel>Пол</FormLabel>
853
+ <div className="flex gap-4">
854
+ <label className="flex items-center gap-2">
855
+ <FormRadio value="male" className="w-5 h-5 cursor-pointer" />
856
+ <span>Мужской</span>
857
+ </label>
858
+ <label className="flex items-center gap-2">
859
+ <FormRadio value="female" className="w-5 h-5 cursor-pointer" />
860
+ <span>Женский</span>
861
+ </label>
862
+ <label className="flex items-center gap-2">
863
+ <FormRadio value="other" className="w-5 h-5 cursor-pointer" />
864
+ <span>Другое</span>
865
+ </label>
866
+ </div>
867
+ <FormError className="text-red-600 text-sm" />
868
+ </FormInputLayout>
869
+ ```
870
+
871
+ ---
872
+
873
+ ### FormError
874
+
875
+ Компонент для отображения сообщения об ошибке.
876
+
877
+ **Props**:
878
+
879
+ ```tsx
880
+ interface FormErrorProps {
881
+ // CSS класс
882
+ className?: string;
883
+
884
+ // Кастомное сообщение (по умолчанию берется из валидации)
885
+ children?: React.ReactNode;
886
+ }
887
+ ```
888
+
889
+ **Пример**:
890
+
891
+ ```tsx
892
+ <FormError className="text-red-600 text-sm font-medium mt-1" />
893
+ ```
894
+
895
+ ---
896
+
897
+ ### FormButton
898
+
899
+ Кнопка отправки формы.
900
+
901
+ **Props**:
902
+
903
+ ```tsx
904
+ interface FormButtonProps {
905
+ // Текст кнопки
906
+ children: React.ReactNode;
907
+
908
+ // CSS класс
909
+ className?: string;
910
+
911
+ // Отключить кнопку, если есть ошибки валидации
912
+ disabledError?: boolean;
913
+
914
+ // Стандартные HTML атрибуты button
915
+ [key: string]: any;
916
+ }
917
+ ```
918
+
919
+ **Пример**:
920
+
921
+ ```tsx
922
+ <FormButton
923
+ className="w-full px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 font-semibold transition disabled:opacity-50"
924
+ disabledError
925
+ >
926
+ Отправить
927
+ </FormButton>
928
+ ```
929
+
930
+ ---
931
+
932
+ ## 📋 Компоненты FormLayout
933
+
934
+ ### FormLayout
935
+
936
+ Компонент для рендера формы из JSON конфига. Автоматически регистрирует все поля и управляет их состоянием.
937
+
938
+ **Props**:
939
+
940
+ ```tsx
941
+ interface FormLayoutProps<T extends object = any> {
942
+ // Массив конфигураций полей
943
+ formData: FormField<T>[];
944
+
945
+ // Обработчик отправки
946
+ funSubmit: (data: T) => void;
947
+
948
+ // Начальные значения формы
949
+ defaultValues?: T | Partial<T>;
950
+
951
+ // Обработчик ошибок валидации
952
+ onError?: (errors: any) => void;
953
+
954
+ // CSS класс для элемента <form>
955
+ formClass?: string;
956
+
957
+ // CSS класс для контейнера полей
958
+ containerClass?: string;
959
+
960
+ // CSS класс для кнопки отправки
961
+ buttonClass?: string;
962
+
963
+ // Текст кнопки отправки
964
+ buttonName?: string;
965
+
966
+ // Глобальный класс для всех лейблов
967
+ labelClass?: string;
968
+
969
+ // Глобальный класс для всех инпутов
970
+ inputClass?: string;
971
+
972
+ // Глобальный класс для всех сообщений об ошибках
973
+ errorClass?: string;
974
+
975
+ // Дополнительные пропсы для кнопки
976
+ submitButtonProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
977
+
978
+ // Отключить кнопку, если есть ошибки
979
+ disabledOnError?: boolean;
980
+ }
981
+ ```
982
+
983
+ ---
984
+
985
+ ### FormField
986
+
987
+ Конфиг для одного поля формы.
988
+
989
+ **Props**:
990
+
991
+ ```tsx
992
+ interface FormField<T extends object = any> {
993
+ // ⭐ Обязательные
994
+ key: keyof T; // Ключ поля в типе T
995
+ type?: FormFieldType; // Тип: text, email, password, number, date, textarea, select, checkbox, radio, file, range, mask
996
+
997
+ // 🏷️ Лейбл и плейсхолдер
998
+ label?: string; // Текст лейбла
999
+ placeholder?: string; // Плейсхолдер
1000
+
1001
+ // ✅ Валидация
1002
+ required?: string | boolean; // Обязательное поле
1003
+ minLength?: { value: number; message: string }; // Мин. длина
1004
+ maxLength?: { value: number; message: string }; // Макс. длина
1005
+ pattern?: { value: RegExp; message: string }; // Регулярное выражение
1006
+ validate?: (value: any) => boolean | string; // Кастомная валидация
1007
+ validateAsync?: (value: any) => Promise<boolean | string>; // Асинхронная валидация
1008
+
1009
+ // 🔢 Для типов number, range
1010
+ min?: number; // Минимальное значение
1011
+ max?: number; // Максимальное значение
1012
+ step?: number; // Шаг
1013
+
1014
+ // 📋 Для типов select
1015
+ options?: Array<{ value: string | number; label: string }>; // Опции
1016
+ multiple?: boolean; // Множественный выбор
1017
+
1018
+ // 🔘 Для типа radio
1019
+ radioOptions?: Array<{ value: string | number; label: string }>; // Опции
1020
+
1021
+ // 📁 Для типа file
1022
+ accept?: string; // MIME типы/расширения
1023
+
1024
+ // 🎭 Для типа mask
1025
+ maska?: { required: string; format: string; mask: string }; // Конфиг маски
1026
+
1027
+ // 🎨 Стили
1028
+ containerClass?: string; // Класс контейнера поля
1029
+ labelClass?: string; // Класс лейбла
1030
+ inputClass?: string; // Класс инпута
1031
+ errorClass?: string; // Класс сообщения об ошибке
1032
+ classNameError?: string; // Класс инпута при ошибке
1033
+
1034
+ // 🔧 Прочие
1035
+ disabled?: boolean; // Отключить поле
1036
+ defaultChecked?: boolean; // Для checkbox/radio
1037
+ rows?: number; // Кол-во строк для textarea
1038
+ showValue?: boolean; // Показать значение для range
1039
+ render?: (props: any) => React.ReactNode; // Кастомный рендер
1040
+ }
1041
+ ```
1042
+
1043
+ ---
1044
+
1045
+ ## 🎯 Примеры форм
1046
+
1047
+ ### Пример 1: Регистрация (FormProvider)
1048
+
1049
+ ```tsx
1050
+ import {
1051
+ FormProvider,
1052
+ FormInputLayout,
1053
+ FormLabel,
1054
+ FormInput,
1055
+ FormPasswordInput,
1056
+ FormCheckbox,
1057
+ FormError,
1058
+ FormButton,
1059
+ } from "react-form-constructor";
1060
+
1061
+ type SignUpForm = {
1062
+ username: string;
1063
+ email: string;
1064
+ password: string;
1065
+ confirmPassword: string;
1066
+ terms: boolean;
1067
+ };
1068
+
1069
+ export function SignUp() {
1070
+ return (
1071
+ <FormProvider<SignUpForm>
1072
+ funSubmit={(data) => {
1073
+ console.log("Регистрация:", data);
1074
+ }}
1075
+ className="max-w-2xl mx-auto p-8 bg-gradient-to-br from-blue-50 to-indigo-50 rounded-xl shadow-lg"
1076
+ >
1077
+ <h1 className="text-3xl font-bold mb-8 text-gray-900">Создать аккаунт</h1>
1078
+
1079
+ <FormInputLayout
1080
+ name="username"
1081
+ required="Имя пользователя обязательно"
1082
+ minLength={{ value: 3, message: "Минимум 3 символа" }}
1083
+ maxLength={{ value: 20, message: "Максимум 20 символов" }}
1084
+ className="mb-6"
1085
+ >
1086
+ <FormLabel className="block text-sm font-semibold mb-2 text-gray-700">
1087
+ Имя пользователя
1088
+ </FormLabel>
1089
+ <FormInput
1090
+ placeholder="john_doe"
1091
+ className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
1092
+ />
1093
+ <FormError className="text-red-600 text-sm mt-1" />
1094
+ </FormInputLayout>
1095
+
1096
+ <FormInputLayout
1097
+ name="email"
1098
+ required="Email обязателен"
1099
+ pattern={{
1100
+ value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
1101
+ message: "Введите корректный email",
1102
+ }}
1103
+ className="mb-6"
1104
+ >
1105
+ <FormLabel className="block text-sm font-semibold mb-2 text-gray-700">
1106
+ Email
1107
+ </FormLabel>
1108
+ <FormInput
1109
+ type="email"
1110
+ placeholder="your@email.com"
1111
+ className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
1112
+ />
1113
+ <FormError className="text-red-600 text-sm mt-1" />
1114
+ </FormInputLayout>
1115
+
1116
+ <FormInputLayout
1117
+ name="password"
1118
+ required="Пароль обязателен"
1119
+ minLength={{ value: 8, message: "Минимум 8 символов" }}
1120
+ validate={(value) => {
1121
+ if (!/[A-Z]/.test(value)) return "Добавьте заглавную букву";
1122
+ if (!/[0-9]/.test(value)) return "Добавьте цифру";
1123
+ return true;
1124
+ }}
1125
+ className="mb-6"
1126
+ >
1127
+ <FormLabel className="block text-sm font-semibold mb-2 text-gray-700">
1128
+ Пароль
1129
+ </FormLabel>
1130
+ <FormPasswordInput
1131
+ placeholder="••••••••"
1132
+ className="flex items-center gap-2"
1133
+ inputClassName="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
1134
+ />
1135
+ <p className="text-xs text-gray-500 mt-1">
1136
+ Минимум 8 символов, заглавная буква и цифра
1137
+ </p>
1138
+ <FormError className="text-red-600 text-sm mt-1" />
1139
+ </FormInputLayout>
1140
+
1141
+ <FormInputLayout
1142
+ name="terms"
1143
+ required="Вы должны согласиться"
1144
+ className="mb-8"
1145
+ >
1146
+ <label className="flex items-start gap-3">
1147
+ <FormCheckbox value={true} className="w-5 h-5 mt-1 cursor-pointer" />
1148
+ <span className="text-sm text-gray-700">
1149
+ Я согласен с{" "}
1150
+ <a href="#" className="text-blue-600 hover:underline">
1151
+ условиями использования
1152
+ </a>{" "}
1153
+ и{" "}
1154
+ <a href="#" className="text-blue-600 hover:underline">
1155
+ политикой конфиденциальности
1156
+ </a>
1157
+ </span>
1158
+ </label>
1159
+ <FormError className="text-red-600 text-sm mt-1" />
1160
+ </FormInputLayout>
1161
+
1162
+ <FormButton
1163
+ className="w-full px-6 py-3 bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-bold rounded-lg hover:shadow-lg transition disabled:opacity-50"
1164
+ disabledError
1165
+ >
1166
+ Создать аккаунт
1167
+ </FormButton>
1168
+ </FormProvider>
1169
+ );
1170
+ }
1171
+ ```
1172
+
1173
+ ### Пример 2: Профиль пользователя (FormLayout)
1174
+
1175
+ ```tsx
1176
+ import { FormLayout, type FormField } from "react-form-constructor";
1177
+
1178
+ type UserProfile = {
1179
+ firstName: string;
1180
+ lastName: string;
1181
+ email: string;
1182
+ phone: string;
1183
+ country: string;
1184
+ bio: string;
1185
+ avatar: FileList;
1186
+ };
1187
+
1188
+ const profileFields: FormField<UserProfile>[] = [
1189
+ {
1190
+ key: "firstName",
1191
+ label: "Имя",
1192
+ type: "text",
1193
+ placeholder: "Иван",
1194
+ required: "Имя обязательно",
1195
+ minLength: { value: 2, message: "Минимум 2 символа" },
1196
+ inputClass: "w-full px-4 py-2 border border-gray-300 rounded-lg",
1197
+ labelClass: "block text-sm font-semibold mb-2",
1198
+ errorClass: "text-red-600 text-sm mt-1",
1199
+ },
1200
+ {
1201
+ key: "lastName",
1202
+ label: "Фамилия",
1203
+ type: "text",
1204
+ placeholder: "Петров",
1205
+ required: "Фамилия обязательна",
1206
+ minLength: { value: 2, message: "Минимум 2 символа" },
1207
+ inputClass: "w-full px-4 py-2 border border-gray-300 rounded-lg",
1208
+ labelClass: "block text-sm font-semibold mb-2",
1209
+ errorClass: "text-red-600 text-sm mt-1",
1210
+ },
1211
+ {
1212
+ key: "email",
1213
+ label: "Email",
1214
+ type: "email",
1215
+ placeholder: "ivan@example.com",
1216
+ required: "Email обязателен",
1217
+ pattern: {
1218
+ value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
1219
+ message: "Некорректный email",
1220
+ },
1221
+ inputClass: "w-full px-4 py-2 border border-gray-300 rounded-lg",
1222
+ labelClass: "block text-sm font-semibold mb-2",
1223
+ errorClass: "text-red-600 text-sm mt-1",
1224
+ },
1225
+ {
1226
+ key: "phone",
1227
+ label: "Телефон",
1228
+ type: "mask",
1229
+ placeholder: "+7 (___) ___-__-__",
1230
+ maska: {
1231
+ required: "Телефон обязателен",
1232
+ format: "+7 (###) ###-##-##",
1233
+ mask: "_",
1234
+ },
1235
+ inputClass: "w-full px-4 py-2 border border-gray-300 rounded-lg",
1236
+ labelClass: "block text-sm font-semibold mb-2",
1237
+ errorClass: "text-red-600 text-sm mt-1",
1238
+ },
1239
+ {
1240
+ key: "country",
1241
+ label: "Страна",
1242
+ type: "select",
1243
+ placeholder: "Выберите страну",
1244
+ options: [
1245
+ { value: "ru", label: "Россия" },
1246
+ { value: "kz", label: "Казахстан" },
1247
+ { value: "by", label: "Беларусь" },
1248
+ { value: "ua", label: "Украина" },
1249
+ ],
1250
+ inputClass: "w-full px-4 py-2 border border-gray-300 rounded-lg",
1251
+ labelClass: "block text-sm font-semibold mb-2",
1252
+ errorClass: "text-red-600 text-sm mt-1",
1253
+ },
1254
+ {
1255
+ key: "bio",
1256
+ label: "О себе",
1257
+ type: "textarea",
1258
+ placeholder: "Расскажите о себе...",
1259
+ maxLength: { value: 500, message: "Максимум 500 символов" },
1260
+ inputClass:
1261
+ "w-full px-4 py-2 border border-gray-300 rounded-lg resize-none",
1262
+ labelClass: "block text-sm font-semibold mb-2",
1263
+ errorClass: "text-red-600 text-sm mt-1",
1264
+ rows: 4,
1265
+ },
1266
+ {
1267
+ key: "avatar",
1268
+ label: "Аватар",
1269
+ type: "file",
1270
+ accept: "image/*",
1271
+ inputClass: "w-full px-4 py-2 border border-gray-300 rounded-lg",
1272
+ labelClass: "block text-sm font-semibold mb-2",
1273
+ errorClass: "text-red-600 text-sm mt-1",
1274
+ },
1275
+ ];
1276
+
1277
+ export function UserProfile() {
1278
+ return (
1279
+ <div className="max-w-2xl mx-auto p-8">
1280
+ <h1 className="text-3xl font-bold mb-8">Мой профиль</h1>
1281
+
1282
+ <FormLayout<UserProfile>
1283
+ formData={profileFields}
1284
+ funSubmit={(data) => {
1285
+ console.log("Обновление профиля:", data);
1286
+ }}
1287
+ containerClass="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6"
1288
+ formClass="bg-white rounded-lg shadow p-8"
1289
+ buttonClass="w-full px-6 py-3 bg-blue-600 text-white font-bold rounded-lg hover:bg-blue-700 transition"
1290
+ buttonName="Сохранить изменения"
1291
+ />
1292
+ </div>
1293
+ );
1294
+ }
1295
+ ```
1296
+
1297
+ ---
1298
+
1299
+ ## 🎓 Типичные сценарии
1300
+
1301
+ ### Сценарий 1: Валидация пароля
1302
+
1303
+ ```tsx
1304
+ <FormInputLayout
1305
+ name="password"
1306
+ required="Пароль обязателен"
1307
+ validate={(value) => {
1308
+ if (!/[A-Z]/.test(value)) return "Добавьте заглавную букву";
1309
+ if (!/[a-z]/.test(value)) return "Добавьте строчную букву";
1310
+ if (!/[0-9]/.test(value)) return "Добавьте цифру";
1311
+ if (!/[!@#$%^&*]/.test(value)) return "Добавьте спецсимвол (!@#$%^&*)";
1312
+ return true;
1313
+ }}
1314
+ >
1315
+ <FormLabel>Пароль</FormLabel>
1316
+ <FormPasswordInput
1317
+ placeholder="••••••••"
1318
+ className="w-full px-3 py-2 border rounded"
1319
+ />
1320
+ <FormError className="text-red-600 text-sm mt-1" />
1321
+ </FormInputLayout>
1322
+ ```
1323
+
1324
+ ### Сценарий 2: Асинхронная валидация
1325
+
1326
+ ```tsx
1327
+ <FormInputLayout
1328
+ name="username"
1329
+ validateAsync={async (value) => {
1330
+ const response = await fetch(`/api/check-username?name=${value}`);
1331
+ const data = await response.json();
1332
+ return data.available ? true : "Имя пользователя занято";
1333
+ }}
1334
+ >
1335
+ <FormLabel>Имя пользователя</FormLabel>
1336
+ <FormInput
1337
+ placeholder="john_doe"
1338
+ className="w-full px-3 py-2 border rounded"
1339
+ />
1340
+ <FormError className="text-red-600 text-sm mt-1" />
1341
+ </FormInputLayout>
1342
+ ```
1343
+
1344
+ ### Сценарий 3: Условное отображение
1345
+
1346
+ ```tsx
1347
+ const MyForm = () => {
1348
+ const [showExtraField, setShowExtraField] = useState(false);
1349
+
1350
+ return (
1351
+ <FormProvider<MyForm>
1352
+ funSubmit={(data) => console.log(data)}
1353
+ setFormApi={(methods) => {
1354
+ // Следить за изменениями поля
1355
+ const subscription = methods.watch((data) => {
1356
+ setShowExtraField(data.type === "other");
1357
+ });
1358
+ return () => subscription.unsubscribe();
1359
+ }}
1360
+ >
1361
+ <FormInputLayout name="type">
1362
+ <FormLabel>Выберите тип</FormLabel>
1363
+ <FormSelect
1364
+ options={[
1365
+ { value: "personal", label: "Личное" },
1366
+ { value: "business", label: "Бизнес" },
1367
+ { value: "other", label: "Другое" },
1368
+ ]}
1369
+ className="w-full px-3 py-2 border rounded"
1370
+ />
1371
+ </FormInputLayout>
1372
+
1373
+ {showExtraField && (
1374
+ <FormInputLayout name="otherDescription">
1375
+ <FormLabel>Опишите тип</FormLabel>
1376
+ <FormTextarea
1377
+ placeholder="Описание..."
1378
+ className="w-full px-3 py-2 border rounded"
1379
+ />
1380
+ <FormError className="text-red-600 text-sm mt-1" />
1381
+ </FormInputLayout>
1382
+ )}
1383
+ </FormProvider>
1384
+ );
1385
+ };
1386
+ ```
1387
+
1388
+ ---
1389
+
1390
+ ## 📖 Дополнительные ресурсы
1391
+
1392
+ - [react-hook-form документация](https://react-hook-form.com/)
1393
+ - [react-number-format документация](https://www.npmjs.com/package/react-number-format)
1394
+
1395
+ ## 📄 Лицензия
1396
+
1397
+ MIT — см. файл [LICENSE](./LICENSE)
1398
+
1399
+ ---
1400
+
1401
+ **Версия**: 0.1.5
1402
+ **Последнее обновление**: Январь 2026
1403
+
1404
+ Используйте `FormProvider` и набор компонентов для построения формы через `children`.
1405
+
1406
+ ### Пример
1407
+
1408
+ ```tsx
1409
+ import {
1410
+ FormProvider,
1411
+ FormInputLayout,
1412
+ FormLabel,
1413
+ FormInput,
1414
+ FormPasswordInput,
1415
+ FormTextarea,
1416
+ FormMaskedInput,
1417
+ FormSelect,
1418
+ FormNumber,
1419
+ FormDate,
1420
+ FormRange,
1421
+ FormFileInput,
1422
+ FormCheckbox,
1423
+ FormRadio,
1424
+ FormError,
1425
+ FormButton,
1426
+ } from "react-form-constructor";
1427
+
1428
+ type ProfileForm = {
1429
+ name: string;
1430
+ password: string;
1431
+ description?: string;
1432
+ phone?: string;
1433
+ country?: string;
1434
+ age?: number;
1435
+ birthDate?: string;
1436
+ rating?: number;
1437
+ avatar?: FileList;
1438
+ terms?: boolean;
1439
+ gender?: string;
1440
+ };
1441
+
1442
+ export function Profile() {
1443
+ return (
1444
+ <FormProvider<ProfileForm>
1445
+ funSubmit={(data) => console.log(data)}
1446
+ className="flex flex-col gap-4"
1447
+ >
1448
+ <FormInputLayout name="name" required="Введите имя">
1449
+ <FormLabel>Имя</FormLabel>
1450
+ <FormInput placeholder="Введите имя" className="input" />
1451
+ <FormError className="error" />
1452
+ </FormInputLayout>
1453
+
1454
+ <FormInputLayout name="password" required="Введите пароль">
1455
+ <FormLabel>Пароль</FormLabel>
1456
+ <FormPasswordInput
1457
+ placeholder="Введите пароль"
1458
+ className="input-wrap"
1459
+ inputClassName="input"
1460
+ />
1461
+ <FormError className="error" />
1462
+ </FormInputLayout>
1463
+
1464
+ <FormInputLayout name="description">
1465
+ <FormLabel>Описание</FormLabel>
1466
+ <FormTextarea rows={4} className="textarea" />
1467
+ <FormError className="error" />
1468
+ </FormInputLayout>
1469
+
1470
+ <FormInputLayout
1471
+ name="phone"
1472
+ maska={{
1473
+ required: "Телефон обязателен",
1474
+ format: "+7 (###) ###-##-##",
1475
+ mask: "_",
1476
+ }}
1477
+ >
1478
+ <FormLabel>Телефон</FormLabel>
1479
+ <FormMaskedInput className="input" />
1480
+ <FormError className="error" />
1481
+ </FormInputLayout>
1482
+
1483
+ <FormInputLayout name="country" required="Выберите страну">
1484
+ <FormLabel>Страна</FormLabel>
1485
+ <FormSelect
1486
+ placeholder="Выберите"
1487
+ options={[
1488
+ { value: "ru", label: "Россия" },
1489
+ { value: "kz", label: "Казахстан" },
1490
+ ]}
1491
+ className="select"
1492
+ />
1493
+ <FormError className="error" />
1494
+ </FormInputLayout>
1495
+
1496
+ <FormInputLayout name="age">
1497
+ <FormLabel>Возраст</FormLabel>
1498
+ <FormNumber min={1} max={120} step={1} className="input" />
1499
+ <FormError className="error" />
1500
+ </FormInputLayout>
1501
+
1502
+ <FormInputLayout name="birthDate">
1503
+ <FormLabel>Дата рождения</FormLabel>
1504
+ <FormDate type="date" className="input" />
1505
+ <FormError className="error" />
1506
+ </FormInputLayout>
1507
+
1508
+ <FormInputLayout name="rating">
1509
+ <FormLabel>Рейтинг</FormLabel>
1510
+ <FormRange range="single" min={0} max={10} showValue className="w-60" />
1511
+ <FormError className="error" />
1512
+ </FormInputLayout>
1513
+
1514
+ <FormInputLayout name="avatar">
1515
+ <FormLabel>Аватар</FormLabel>
1516
+ <FormFileInput accept="image/*" className="input" />
1517
+ <FormError className="error" />
1518
+ </FormInputLayout>
1519
+
1520
+ <FormInputLayout name="gender" required="Выберите пол">
1521
+ <FormLabel>Пол</FormLabel>
1522
+ <div className="flex gap-4">
1523
+ <label className="flex items-center gap-2">
1524
+ <FormRadio value="male" /> Мужской
1525
+ </label>
1526
+ <label className="flex items-center gap-2">
1527
+ <FormRadio value="female" /> Женский
1528
+ </label>
1529
+ </div>
1530
+ <FormError className="error" />
1531
+ </FormInputLayout>
1532
+
1533
+ <FormInputLayout name="terms">
1534
+ <label className="flex items-center gap-2">
1535
+ <FormCheckbox value={true} /> Я согласен с условиями
1536
+ </label>
1537
+ <FormError className="error" />
1538
+ </FormInputLayout>
1539
+
1540
+ <FormButton className="btn" disabledError>
1541
+ Отправить
1542
+ </FormButton>
1543
+ </FormProvider>
1544
+ );
1545
+ }
1546
+ ```
1547
+
1548
+ ### Компоненты (Provider)
1549
+
1550
+ #### FormProvider
1551
+
1552
+ **Props**
1553
+
1554
+ - `funSubmit: (data) => void` — обработчик отправки формы.
1555
+ - `children: React.ReactNode` — поля формы.
1556
+ - `className?: string` — класс формы.
1557
+ - `setFormApi?: (formMethods) => void` — доступ к `register`, `errors`, `control`, `values`.
1558
+
1559
+ #### FormInputLayout
1560
+
1561
+ **Props**
1562
+
1563
+ - `name: string` — имя поля (ключ в данных).
1564
+ - `required?: string` — сообщение об обязательности.
1565
+ - `minLength?: { value: number; message: string }` — минимальная длина.
1566
+ - `maxLength?: { value: number; message: string }` — максимальная длина.
1567
+ - `pattern?: { value: RegExp; message: string }` — регулярное выражение.
1568
+ - `validate?: (value) => boolean | string` — кастомная проверка.
1569
+ - `maska?: { required: string; format: string; mask: string }` — маска ввода (для `FormMaskedInput`).
1570
+ - `className?: string` — класс контейнера.
1571
+
1572
+ #### FormInput
1573
+
1574
+ **Props**
1575
+
1576
+ - `placeholder?: string` — плейсхолдер.
1577
+ - `className?: string` — класс инпута.
1578
+ - `classNameError?: string` — класс ошибки.
1579
+ - `type?: string` — тип (`text`, `email`, `password`, …).
1580
+
1581
+ #### FormPasswordInput
1582
+
1583
+ **Props**
1584
+
1585
+ - `placeholder?: string`
1586
+ - `className?: string` — класс контейнера.
1587
+ - `inputClassName?: string` — класс инпута.
1588
+ - `classNameError?: string` — класс ошибки.
1589
+ - `visibleIcon?: ReactNode` — иконка видимого пароля.
1590
+ - `hiddenIcon?: ReactNode` — иконка скрытого пароля.
1591
+ - `iconClassName?: string` — класс иконки.
1592
+ - `iconWrapperClassName?: string` — класс контейнера иконки.
1593
+
1594
+ #### FormTextarea
1595
+
1596
+ **Props**
1597
+
1598
+ - `placeholder?: string`
1599
+ - `className?: string`
1600
+ - `classNameError?: string`
1601
+ - `rows?: number`
1602
+ - `cols?: number`
1603
+
1604
+ #### FormMaskedInput
1605
+
1606
+ **Props**
1607
+
1608
+ - `placeholder?: string`
1609
+ - `className?: string`
1610
+ - `classNameError?: string`
1611
+
1612
+ > Использует `maska` из `FormInputLayout`.
1613
+
1614
+ #### FormSelect
1615
+
1616
+ **Props**
1617
+
1618
+ - `options: { value: string | number; label: string }[]` — список опций.
1619
+ - `multiple?: boolean` — множественный выбор.
1620
+ - `placeholder?: string` — пустой вариант.
1621
+ - `className?: string`
1622
+ - `classNameError?: string`
1623
+
1624
+ #### FormNumber
1625
+
1626
+ **Props**
1627
+
1628
+ - `placeholder?: string`
1629
+ - `className?: string`
1630
+ - `classNameError?: string`
1631
+ - `min?: number`
1632
+ - `max?: number`
1633
+ - `step?: number`
1634
+
1635
+ #### FormDate
1636
+
1637
+ **Props**
1638
+
1639
+ - `type?: "date" | "datetime-local" | "time" | "month" | "week"`
1640
+ - `min?: string`
1641
+ - `max?: string`
1642
+ - `className?: string`
1643
+ - `classNameError?: string`
1644
+
1645
+ #### FormRange
1646
+
1647
+ **Props**
1648
+
1649
+ - `range?: "single" | "double"` — один ползунок или два.
1650
+ - `min?: number`
1651
+ - `max?: number`
1652
+ - `step?: number`
1653
+ - `showValue?: boolean` — показать значения.
1654
+ - `className?: string` — класс слайдера.
1655
+ - `containerClassName?: string` — класс контейнера.
1656
+ - `classNameError?: string`
1657
+
1658
+ #### FormFileInput
1659
+
1660
+ **Props**
1661
+
1662
+ - `accept?: string` — MIME типы/расширения.
1663
+ - `multiple?: boolean`
1664
+ - `className?: string`
1665
+ - `classNameError?: string`
1666
+
1667
+ #### FormCheckbox
1668
+
1669
+ **Props**
1670
+
1671
+ - `value?: string | number | boolean`
1672
+ - `defaultChecked?: boolean`
1673
+ - `disabled?: boolean`
1674
+ - `className?: string`
1675
+ - `classNameError?: string`
1676
+
1677
+ #### FormRadio
1678
+
1679
+ **Props**
1680
+
1681
+ - `value: string | number`
1682
+ - `defaultChecked?: boolean`
1683
+ - `disabled?: boolean`
1684
+ - `className?: string`
1685
+ - `classNameError?: string`
1686
+
1687
+ #### FormButton
1688
+
1689
+ **Props**
1690
+
1691
+ - `className?: string`
1692
+ - `disabledError?: boolean` — отключить кнопку, если есть ошибки.
1693
+
1694
+ #### FormError
1695
+
1696
+ **Props**
1697
+
1698
+ - `className?: string`
1699
+
1700
+ #### FormLabel
1701
+
1702
+ **Props**
1703
+
1704
+ - `className?: string`
1705
+ - `classNameError?: string`
1706
+
1707
+ ## Метод 2: JSON (FormLayout)
1708
+
1709
+ `FormLayout` рендерит форму по массиву `formData` и сам регистрирует поля.
1710
+
1711
+ ### Пример
1712
+
1713
+ ```tsx
1714
+ import { FormLayout, type FormField } from "react-form-constructor";
1715
+
1716
+ type LoginForm = {
1717
+ phone: string;
1718
+ password: string;
1719
+ };
1720
+
1721
+ const fields: FormField<LoginForm>[] = [
1722
+ {
1723
+ label: "Телефон",
1724
+ placeholder: "+7 (___) ___-__-__",
1725
+ key: "phone",
1726
+ required: "Введите телефон",
1727
+ maska: {
1728
+ required: "Введите телефон",
1729
+ format: "+7 (###) ###-##-##",
1730
+ mask: "_",
1731
+ },
1732
+ },
1733
+ {
1734
+ label: "Пароль",
1735
+ placeholder: "••••••••",
1736
+ key: "password",
1737
+ required: "Введите пароль",
1738
+ minLength: { value: 6, message: "Минимум 6 символов" },
1739
+ type: "password",
1740
+ },
1741
+ ];
1742
+
1743
+ export function Login() {
1744
+ return (
1745
+ <FormLayout<LoginForm>
1746
+ formData={fields}
1747
+ funSubmit={(data) => console.log(data)}
1748
+ formClass="form"
1749
+ buttonClass="btn"
1750
+ />
1751
+ );
132
1752
  }
133
1753
  ```
134
1754
 
135
- ## Стилизация
1755
+ ### FormField
136
1756
 
137
- Доступны классы для быстрого оформления:
1757
+ **Поля**
138
1758
 
139
- - `formClass` — класс формы
140
- - `buttonClass` — класс кнопки
141
- - `inputClass` — класс поля
142
- - `labelClass` — класс лейбла
143
- - `errorClass` класс текста ошибки
1759
+ - `label: string` — текст лейбла.
1760
+ - `placeholder: string` — плейсхолдер.
1761
+ - `key: keyof T` — имя поля.
1762
+ - `required?: string` — сообщение об обязательности.
1763
+ - `minLength?: { value: number; message: string }`
1764
+ - `maxLength?: { value: number; message: string }`
1765
+ - `pattern?: { value: RegExp; message: string }`
1766
+ - `validate?: any` — кастомная проверка.
1767
+ - `type?: string` — тип инпута.
1768
+ - `maska?: { required: string; format: string; mask: string }` — маска.
1769
+ - `textarea?: boolean` — textarea вместо input.
1770
+ - `register?: object` — доп. настройки `react-hook-form`.
1771
+ - `inputClass?: any` — класс инпута.
1772
+ - `labelClass?: any` — класс лейбла.
1773
+ - `errorClass?: any` — класс ошибки.
144
1774
 
145
- ## Публичный API
1775
+ ### FormLayoutProps
146
1776
 
147
- - `FormLayout` рендерит форму по массиву `formData` или принимает `children`.
148
- - `InputForm` — базовый элемент поля (экспортируется по умолчанию).
149
- - `useFormContext` — доступ к `react-hook-form` контексту внутри ваших компонентов.
150
- - Типы: `FormField`, `FormLayoutProps`, `FormContextValue`.
1777
+ - `funSubmit: (data: T) => void` submit.
1778
+ - `formClass?: string` — класс формы.
1779
+ - `buttonClass?: string` — класс кнопки.
1780
+ - `buttonName?: string` — текст кнопки.
1781
+ - `formData: FormField<T>[]` — массив полей.
151
1782
 
152
1783
  ## Требования
153
1784