npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

finform

v1.6.44

Published

Пакет для управления инпутами с поддержкой валидации и отображения ошибок в попапе.

Readme

Документация по компонентам формы

Пакет для управления инпутами с поддержкой валидации и отображения ошибок в попапе.

Содержание

Установка и импорт

Импорт компонентов

import { 
  InputContainer,
  Input,
  TextArea,
  NumberInput,
  MaskedInput,
  Select,
  MultiSelect,
  Search,
  MultipleSearch,
  GroupSelect,
  GroupSearch,
  GroupSelectWithSearch,
  DateInput,
  DateTimeInput,
  FileInput,
  Checkbox,
  RoundCheckbox
} from 'finform';

Важно: Компоненты Checkbox и RoundCheckbox не требуют обертки в InputContainer.

Типы данных

Option

Интерфейс для опций в выпадающих списках и поиске:

interface Option {
  id: string | number;  // Уникальный идентификатор опции
  name: string;          // Отображаемое название опции
}

GroupOption

Интерфейс для группированных опций:

interface GroupOption {
  id: string | number;   // Уникальный идентификатор группы
  name: string;          // Название группы
  items: Option[];       // Массив опций в группе
}

Основные концепции

Все компоненты формы должны быть обернуты в InputContainer, который обеспечивает:

  • Отображение placeholder'а
  • Визуальное оформление состояния фокуса
  • Отображение ошибок в попапе при наведении на иконку ошибки
  • Иконки и дополнительные элементы управления

Структура использования

import { InputContainer, Input } from 'finform';

<InputContainer error={errorMessage}>
  <Input
    id="field-id"
    placeholder="Введите значение"
    value={value}
    onChange={setValue}
  />
</InputContainer>

InputContainer

Основной контейнер для всех инпутов. Обеспечивает единообразное оформление и обработку ошибок.

Props

| Проп | Тип | По умолчанию | Описание | |------|-----|--------------|----------| | children | React.ReactNode | - | Единственный дочерний компонент (Input, Select, TextArea и т.д.) | | className | string | '' | Дополнительные CSS классы | | style | React.CSSProperties | {} | Инлайн стили | | error | string \| null | null | Текст ошибки для отображения в попапе |

Важно: error должен быть строкой (не массивом) для корректного отображения в попапе. При наведении на иконку ошибки появляется попап с текстом ошибки.

Компоненты

Текстовые инпуты

Input

import { InputContainer, Input } from 'finform';

const [value, setValue] = useState('');
const [error, setError] = useState<string | null>(null);

<InputContainer error={error}>
  <Input
    id="username"
    placeholder="Имя пользователя"
    value={value}
    onChange={setValue}
  />
</InputContainer>

Props

| Проп | Тип | По умолчанию | Описание | |------|-----|--------------|----------| | id | string | - | Уникальный идентификатор поля | | name | string | - | Имя поля для формы | | value | string \| number \| null | - | Значение инпута | | onChange | (value: string) => void | - | Обработчик изменения значения | | placeholder | string | - | Текст placeholder'а | | disabled | boolean | false | Отключение поля | | className | string | '' | Дополнительные CSS классы | | style | React.CSSProperties | {} | Инлайн стили | | type | string | 'text' | Тип HTML инпута (text, email, password и т.д.) | | autoComplete | string | 'off' | Автозаполнение браузера | | icon | boolean | true | Показывать ли иконку в InputContainer | | onKeyDown | (e: KeyboardEvent) => void | - | Обработчик нажатия клавиш | | onBlur | (e: FocusEvent) => void | - | Обработчик потери фокуса | | onClick | (e: MouseEvent) => void | - | Обработчик клика | | required | boolean | false | Обязательное поле |

Примечание: Пропсы focused, setFocused, error передаются автоматически через InputContainer и не должны указываться вручную.

TextArea

import { InputContainer, TextArea } from 'finform';

const [value, setValue] = useState('');
const [error, setError] = useState<string | null>(null);

<InputContainer error={error}>
  <TextArea
    id="description"
    placeholder="Описание"
    value={value}
    onChange={setValue}
    rows={5}
    autoResize={true}
  />
</InputContainer>

Props

Все пропсы из Input, плюс:

| Проп | Тип | По умолчанию | Описание | |------|-----|--------------|----------| | rows | number | 3 | Количество строк | | autoResize | boolean | true | Автоматическое изменение высоты при вводе | | disableResize | boolean | false | Отключить возможность ручного изменения размера |

Особенности:

  • При autoResize={true} высота автоматически подстраивается под содержимое
  • При disableResize={true} отключается возможность ручного изменения размера через UI

NumberInput

import { InputContainer, NumberInput } from 'finform';

const [value, setValue] = useState<number | null>(null);
const [error, setError] = useState<string | null>(null);

<InputContainer error={error}>
  <NumberInput
    id="amount"
    placeholder="Сумма"
    value={value}
    onChange={setValue}
    min={0}
    max={1000000}
    decimals={2}
  />
</InputContainer>

Props

Все пропсы из Input, плюс:

| Проп | Тип | По умолчанию | Описание | |------|-----|--------------|----------| | min | number \| false | false | Минимальное значение | | max | number \| false | false | Максимальное значение | | decimals | number \| false | false | Количество знаков после запятой | | innerError | object \| null | - | Внутренняя ошибка валидации (используется компонентом) | | setInnerError | (error: object \| null) => void | - | Установка внутренней ошибки (используется компонентом) |

Особенности:

  • Автоматическое форматирование чисел с пробелами для разделения тысяч (например: 1 000 000)
  • Поддержка отрицательных чисел
  • Автоматическая валидация при вводе с отображением ошибок через innerError
  • При превышении max или min автоматически показывается ошибка
  • Поддержка как точки, так и запятой в качестве разделителя дробной части

MaskedInput

import { InputContainer, MaskedInput } from 'finform';

const [value, setValue] = useState('');
const [error, setError] = useState<string | null>(null);

<InputContainer error={error}>
  <MaskedInput
    id="phone"
    placeholder="Телефон"
    value={value}
    onChange={setValue}
    mask="+7 (999) 999-99-99"
  />
</InputContainer>

Props

Все пропсы из Input, плюс:

| Проп | Тип | По умолчанию | Описание | |------|-----|--------------|----------| | mask | string | - | Маска ввода (символ 9 означает цифру) |

Примеры масок:

  • "+7 (999) 999-99-99" — телефон
  • "99.99.9999" — дата
  • "9999 9999 9999 9999" — номер карты

Выпадающие списки

Select

import { InputContainer, Select } from 'finform';

interface Option {
  id: string | number;
  name: string;
}

const options: Option[] = [
  { id: 1, name: 'Опция 1' },
  { id: 2, name: 'Опция 2' },
];

const [value, setValue] = useState<number | null>(null);
const [error, setError] = useState<string | null>(null);

<InputContainer error={error}>
  <Select
    id="category"
    placeholder="Выберите категорию"
    value={value}
    onChange={(option) => setValue(option?.id || null)}
    options={options}
  />
</InputContainer>

Важно: onChange получает объект Option целиком, а не только значение.

Props

| Проп | Тип | По умолчанию | Описание | |------|-----|--------------|----------| | id | string | - | Уникальный идентификатор поля | | name | string | - | Имя поля для формы | | value | string \| number \| null | - | ID выбранной опции | | onChange | (option: Option \| null) => void | - | Обработчик выбора опции (получает весь объект Option) | | options | Option[] | [] | Массив опций для выбора | | placeholder | string | - | Текст placeholder'а | | disabled | boolean | false | Отключение поля | | className | string | '' | Дополнительные CSS классы | | style | React.CSSProperties | {} | Инлайн стили | | icon | boolean | true | Показывать ли иконку в InputContainer | | addButton | any | false | Кнопка добавления новой опции (отображается в списке) |

MultiSelect

import { InputContainer, MultiSelect } from 'finform';

const options: Option[] = [
  { id: 1, name: 'Опция 1' },
  { id: 2, name: 'Опция 2' },
];

const [values, setValues] = useState<number[]>([]);
const [error, setError] = useState<string | null>(null);

<InputContainer error={error}>
  <MultiSelect
    id="tags"
    placeholder="Выберите теги"
    values={values}
    onChange={(option) => {
      setValues(prev => 
        prev.includes(option.id) 
          ? prev.filter(id => id !== option.id)
          : [...prev, option.id]
      );
    }}
    onChangeAll={(selectAll) => {
      setValues(selectAll ? options.map(o => o.id) : []);
    }}
    options={options}
  />
</InputContainer>

Props

| Проп | Тип | По умолчанию | Описание | |------|-----|--------------|----------| | id | string | - | Уникальный идентификатор поля | | name | string | - | Имя поля для формы | | values | (string \| number)[] | [] | Массив ID выбранных опций | | onChange | (option: Option) => void | - | Обработчик выбора/снятия опции | | onChangeAll | (selectAll: boolean) => void | - | Обработчик выбора всех/снятия всех опций | | options | Option[] | [] | Массив опций для выбора | | placeholder | string | - | Текст placeholder'а | | disabled | boolean | false | Отключение поля | | className | string | '' | Дополнительные CSS классы | | style | React.CSSProperties | {} | Инлайн стили | | icon | boolean | true | Показывать ли иконку в InputContainer |

Особенности:

  • onChange вызывается при клике на опцию — нужно самостоятельно управлять массивом values
  • onChangeAll вызывается при клике на "Выбрать все" — передается true для выбора всех, false для снятия всех

Поиск

Search

import { InputContainer, Search } from 'finform';

const [search, setSearch] = useState('');
const [value, setValue] = useState<number | null>(null);
const [error, setError] = useState<string | null>(null);

<InputContainer error={error}>
  <Search
    id="user-search"
    placeholder="Поиск пользователя"
    value={value}
    search={search}
    onChange={(option) => {
      setValue(option?.id || null);
      setSearch(option?.name || '');
    }}
    onSearch={setSearch}
    options={filteredOptions}
    loading={isLoading}
    clearOnClickOutside={true}
  />
</InputContainer>

Props

Все пропсы из Select, плюс:

| Проп | Тип | По умолчанию | Описание | |------|-----|--------------|----------| | search | string | - | Текущее значение строки поиска | | onSearch | (text: string) => void | - | Обработчик изменения текста поиска | | loading | boolean | false | Индикатор загрузки данных | | clearOnClickOutside | boolean | true | Очищать поле поиска при клике вне компонента, если опция не выбрана |

Особенности:

  • При вводе текста в поле поиска вызывается onSearch — используйте это для фильтрации опций
  • Если введенный текст совпадает с названием опции при клике вне компонента, опция автоматически выбирается
  • При loading={true} отображается индикатор загрузки вместо иконки

MultipleSearch

import { InputContainer, MultipleSearch } from 'finform';

const [search, setSearch] = useState('');
const [values, setValues] = useState<number[]>([]);
const [error, setError] = useState<string | null>(null);

<InputContainer error={error}>
  <MultipleSearch
    id="users-search"
    placeholder="Поиск пользователей"
    values={values}
    search={search}
    onChange={(option) => {
      setValues(prev => 
        prev.includes(option.id) 
          ? prev.filter(id => id !== option.id)
          : [...prev, option.id]
      );
    }}
    onSearch={setSearch}
    options={filteredOptions}
    loading={isLoading}
  />
</InputContainer>

Props

Все пропсы из Search, но onChange работает как в MultiSelect:

| Проп | Тип | По умолчанию | Описание | |------|-----|--------------|----------| | values | (string \| number)[] | [] | Массив ID выбранных опций | | onChange | (option: Option) => void | - | Обработчик выбора/снятия опции |

Особенности:

  • Выбранные опции отображаются как теги внутри поля
  • Работает аналогично MultiSelect, но с возможностью поиска

Группированные списки

GroupSelect

import { InputContainer, GroupSelect } from 'finform';

interface GroupOption {
  id: string | number;
  name: string;
  items: Option[];
}

const groupOptions: GroupOption[] = [
  {
    id: 'group1',
    name: 'Группа 1',
    items: [
      { id: 1, name: 'Опция 1.1' },
      { id: 2, name: 'Опция 1.2' },
    ]
  },
];

const [value, setValue] = useState<number | null>(null);
const [error, setError] = useState<string | null>(null);

<InputContainer error={error}>
  <GroupSelect
    id="grouped-select"
    placeholder="Выберите из группы"
    value={value}
    onChange={(option) => setValue(option?.id || null)}
    options={groupOptions}
  />
</InputContainer>

Props

Все пропсы из Select, но options имеет тип GroupOption[]:

| Проп | Тип | По умолчанию | Описание | |------|-----|--------------|----------| | options | GroupOption[] | [] | Массив группированных опций |

Особенности:

  • Опции отображаются сгруппированными по группам
  • Названия групп отображаются как заголовки

GroupSearch

import { InputContainer, GroupSearch } from 'finform';

const [search, setSearch] = useState('');
const [value, setValue] = useState<number | null>(null);
const [error, setError] = useState<string | null>(null);

<InputContainer error={error}>
  <GroupSearch
    id="grouped-search"
    placeholder="Поиск в группах"
    value={value}
    search={search}
    onChange={(option) => {
      setValue(option?.id || null);
      setSearch(option?.name || '');
    }}
    onSearch={setSearch}
    options={groupOptions}
    loading={isLoading}
  />
</InputContainer>

Props

Все пропсы из GroupSelect, плюс:

| Проп | Тип | По умолчанию | Описание | |------|-----|--------------|----------| | search | string | - | Текущее значение строки поиска | | onSearch | (text: string) => void | - | Обработчик изменения текста поиска | | loading | boolean | false | Индикатор загрузки данных |

Особенности:

  • Поиск работает по опциям внутри всех групп
  • Группы остаются видимыми при поиске

GroupSelectWithSearch

import { InputContainer, GroupSelectWithSearch } from 'finform';

const [search, setSearch] = useState('');
const [value, setValue] = useState<number | null>(null);
const [error, setError] = useState<string | null>(null);

<InputContainer error={error}>
  <GroupSelectWithSearch
    id="grouped-select-search"
    placeholder="Выберите с поиском"
    value={value}
    search={search}
    onChange={(option) => {
      setValue(option?.id || null);
      setSearch(option?.name || '');
    }}
    onSearch={setSearch}
    options={groupOptions}
    loading={isLoading}
  />
</InputContainer>

Props

Все пропсы из GroupSearch и GroupSelect.

Особенности:

  • Комбинирует возможности группировки и поиска
  • Поиск работает по всем опциям во всех группах

Дата и время

DateInput

import { InputContainer, DateInput } from 'finform';

const [value, setValue] = useState<Date | null>(null);
const [error, setError] = useState<string | null>(null);

<InputContainer error={error}>
  <DateInput
    id="birthdate"
    placeholder="Дата рождения"
    value={value}
    onChange={setValue}
    defaultDate="2024-01-01"
  />
</InputContainer>

Props

| Проп | Тип | По умолчанию | Описание | |------|-----|--------------|----------| | id | string | - | Уникальный идентификатор поля | | name | string | - | Имя поля для формы | | value | Date \| null | - | Выбранная дата | | onChange | (date: Date \| null) => void | - | Обработчик изменения даты | | placeholder | string | - | Текст placeholder'а | | disabled | boolean | false | Отключение поля | | className | string | '' | Дополнительные CSS классы | | style | React.CSSProperties | {} | Инлайн стили | | defaultDate | string | - | Дата по умолчанию в формате YYYY-MM-DD | | icon | boolean | true | Показывать ли иконку в InputContainer | | innerError | object \| null | - | Внутренняя ошибка валидации (используется компонентом) | | setInnerError | (error: object \| null) => void | - | Установка внутренней ошибки (используется компонентом) |

Особенности:

  • Использует библиотеку Flatpickr для выбора даты
  • Поддерживает различные форматы даты
  • При неверном формате ввода автоматически показывается ошибка через innerError

DateTimeInput

import { InputContainer, DateTimeInput } from 'finform';

const [value, setValue] = useState<Date | null>(null);
const [error, setError] = useState<string | null>(null);

<InputContainer error={error}>
  <DateTimeInput
    id="datetime"
    placeholder="Дата и время"
    value={value}
    onChange={setValue}
  />
</InputContainer>

Props

Все пропсы из DateInput.

Особенности:

  • Позволяет выбрать как дату, так и время
  • Формат отображения включает время

Файлы

FileInput

import { InputContainer, FileInput } from 'finform';

const [file, setFile] = useState<File | null>(null);
const [error, setError] = useState<string | null>(null);

<InputContainer error={error}>
  <FileInput
    id="document"
    placeholder="Выберите файл"
    value={file}
    onChange={(fileData) => {
      // fileData содержит: { size, name, content }
      // content - это base64 строка
      setFile(fileData);
    }}
    valueText={file?.name || ''}
    accept=".pdf,.doc,.docx"
  />
</InputContainer>

Props

| Проп | Тип | По умолчанию | Описание | |------|-----|--------------|----------| | id | string | - | Уникальный идентификатор поля | | name | string | - | Имя поля для формы | | value | object \| null | - | Объект с данными файла (не используется напрямую) | | onChange | (fileData: { size: number, name: string, content: string }) => void | - | Обработчик выбора файла | | valueText | string | '' | Текст для отображения имени файла | | placeholder | string | - | Текст placeholder'а | | disabled | boolean | false | Отключение поля | | className | string | '' | Дополнительные CSS классы | | style | React.CSSProperties | {} | Инлайн стили | | accept | string | - | Разрешенные типы файлов (например: ".pdf,.doc,.docx" или "image/*") |

Особенности:

  • Файл автоматически конвертируется в base64 строку
  • onChange получает объект с полями:
    • size — размер файла в байтах
    • name — имя файла
    • content — содержимое файла в формате base64 (data URL)
  • Используйте valueText для отображения имени выбранного файла
  • Placeholder всегда активен для FileInput

Чекбоксы

Checkbox

import { Checkbox } from 'finform';

const [checked, setChecked] = useState(false);

<Checkbox
  id="agree"
  checked={checked}
  toggleCallback={(checked) => setChecked(checked)}
  text="Я согласен с условиями"
/>

Props

| Проп | Тип | По умолчанию | Описание | |------|-----|--------------|----------| | id | string | - | Уникальный идентификатор чекбокса | | name | string | - | Имя для группировки чекбоксов | | value | string \| number | - | Значение чекбокса (для форм) | | checked | boolean | false | Состояние чекбокса | | toggleCallback | (checked: boolean, event: Event) => void | - | Обработчик изменения состояния | | text | string | - | Текст рядом с чекбоксом | | textStyle | React.CSSProperties | {} | Стили для текста | | checkboxStyle | React.CSSProperties | {} | Стили для самого чекбокса | | style | React.CSSProperties | {} | Общий стиль контейнера |

Особенности:

  • Не требует обертки в InputContainer
  • Используйте toggleCallback вместо onChange для обработки изменений
  • Для группировки чекбоксов используйте одинаковый name и разные value

RoundCheckbox

import { RoundCheckbox } from 'finform';

const [checked, setChecked] = useState(false);

<RoundCheckbox
  id="option"
  checked={checked}
  toggleCallback={(checked) => setChecked(checked)}
/>

Props

Все пропсы из Checkbox, но без text, textStyle (круглый чекбокс обычно используется без текста).

Особенности:

  • Визуально отличается от обычного Checkbox — имеет круглую форму
  • Обычно используется для выбора опций без текста рядом

Обработка ошибок

Важно: Для корректного отображения ошибки в попапе необходимо передать строку в проп error компонента InputContainer. При наведении на иконку ошибки появляется попап с текстом ошибки.

Пример 1: Простая валидация

import { InputContainer, Input } from 'finform';
import { useState } from 'react';

function MyForm() {
  const [value, setValue] = useState('');
  const [error, setError] = useState<string | null>(null);

  const handleSubmit = () => {
    if (!value.trim()) {
      setError('Поле обязательно для заполнения');
      return;
    }
    
    if (value.length < 3) {
      setError('Минимальная длина 3 символа');
      return;
    }
    
    setError(null);
    // Отправка формы
  };

  return (
    <InputContainer error={error}>
      <Input
        id="field"
        placeholder="Введите значение"
        value={value}
        onChange={(newValue) => {
          setValue(newValue);
          // Очищаем ошибку при изменении значения
          if (error) setError(null);
        }}
      />
    </InputContainer>
  );
}

Пример 2: Работа с массивом ошибок

Если валидация возвращает массив ошибок, используйте первую ошибку или объедините их:

import { InputContainer, Input } from 'finform';
import { useState } from 'react';

function MyForm() {
  const [value, setValue] = useState('');
  const [validationErrors, setValidationErrors] = useState<string[]>([]);

  // Использовать первую ошибку
  const errorMessage = validationErrors.length > 0 
    ? validationErrors[0] 
    : null;

  return (
    <InputContainer error={errorMessage}>
      <Input
        id="field"
        placeholder="Введите значение"
        value={value}
        onChange={setValue}
      />
    </InputContainer>
  );
}

Примеры полных форм

Пример 1: Форма регистрации

import { useState } from 'react';
import { 
  InputContainer, 
  Input, 
  Select, 
  DateInput, 
  Checkbox 
} from 'finform';

interface Option {
  id: number;
  name: string;
}

function RegistrationForm() {
  const [username, setUsername] = useState('');
  const [usernameError, setUsernameError] = useState<string | null>(null);
  
  const [email, setEmail] = useState('');
  const [emailError, setEmailError] = useState<string | null>(null);
  
  const [country, setCountry] = useState<number | null>(null);
  const [countryError, setCountryError] = useState<string | null>(null);
  
  const [birthdate, setBirthdate] = useState<Date | null>(null);
  const [birthdateError, setBirthdateError] = useState<string | null>(null);
  
  const [agreed, setAgreed] = useState(false);

  const countries: Option[] = [
    { id: 1, name: 'Россия' },
    { id: 2, name: 'Беларусь' },
    { id: 3, name: 'Казахстан' },
  ];

  const handleSubmit = () => {
    // Валидация
    if (!username.trim()) {
      setUsernameError('Имя пользователя обязательно');
      return;
    }
    
    if (!email.includes('@')) {
      setEmailError('Некорректный email');
      return;
    }
    
    if (!country) {
      setCountryError('Выберите страну');
      return;
    }
    
    if (!birthdate) {
      setBirthdateError('Укажите дату рождения');
      return;
    }
    
    if (!agreed) {
      alert('Необходимо согласие с условиями');
      return;
    }

    // Отправка формы
    console.log({ username, email, country, birthdate });
  };

  return (
    <form onSubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
      <InputContainer error={usernameError}>
        <Input
          id="username"
          placeholder="Имя пользователя"
          value={username}
          onChange={(value) => {
            setUsername(value);
            if (usernameError) setUsernameError(null);
          }}
        />
      </InputContainer>

      <InputContainer error={emailError}>
        <Input
          id="email"
          type="email"
          placeholder="Email"
          value={email}
          onChange={(value) => {
            setEmail(value);
            if (emailError) setEmailError(null);
          }}
        />
      </InputContainer>

      <InputContainer error={countryError}>
        <Select
          id="country"
          placeholder="Выберите страну"
          value={country}
          onChange={(option) => {
            setCountry(option?.id || null);
            if (countryError) setCountryError(null);
          }}
          options={countries}
        />
      </InputContainer>

      <InputContainer error={birthdateError}>
        <DateInput
          id="birthdate"
          placeholder="Дата рождения"
          value={birthdate}
          onChange={(date) => {
            setBirthdate(date);
            if (birthdateError) setBirthdateError(null);
          }}
        />
      </InputContainer>

      <Checkbox
        id="agree"
        checked={agreed}
        toggleCallback={setAgreed}
        text="Я согласен с условиями использования"
      />

      <button type="submit">Зарегистрироваться</button>
    </form>
  );
}

Пример 2: Форма с поиском и множественным выбором

import { useState, useMemo } from 'react';
import { InputContainer, Search, MultiSelect } from 'finform';

interface User {
  id: number;
  name: string;
}

function UserSelectionForm() {
  const [searchQuery, setSearchQuery] = useState('');
  const [selectedUser, setSelectedUser] = useState<number | null>(null);
  const [selectedTags, setSelectedTags] = useState<number[]>([]);
  const [isLoading, setIsLoading] = useState(false);

  const allUsers: User[] = [
    { id: 1, name: 'Иван Иванов' },
    { id: 2, name: 'Петр Петров' },
    { id: 3, name: 'Мария Сидорова' },
  ];

  const tags = [
    { id: 1, name: 'Важное' },
    { id: 2, name: 'Срочное' },
    { id: 3, name: 'Проект А' },
    { id: 4, name: 'Проект Б' },
  ];

  // Фильтрация пользователей по поисковому запросу
  const filteredUsers = useMemo(() => {
    if (!searchQuery.trim()) return [];
    return allUsers.filter(user => 
      user.name.toLowerCase().includes(searchQuery.toLowerCase())
    );
  }, [searchQuery]);

  return (
    <div>
      <InputContainer>
        <Search
          id="user-search"
          placeholder="Поиск пользователя"
          value={selectedUser}
          search={searchQuery}
          onChange={(option) => {
            setSelectedUser(option?.id || null);
            setSearchQuery(option?.name || '');
          }}
          onSearch={setSearchQuery}
          options={filteredUsers}
          loading={isLoading}
        />
      </InputContainer>

      <InputContainer>
        <MultiSelect
          id="tags"
          placeholder="Выберите теги"
          values={selectedTags}
          onChange={(option) => {
            setSelectedTags(prev => 
              prev.includes(option.id) 
                ? prev.filter(id => id !== option.id)
                : [...prev, option.id]
            );
          }}
          onChangeAll={(selectAll) => {
            setSelectedTags(selectAll ? tags.map(t => t.id) : []);
          }}
          options={tags}
        />
      </InputContainer>
    </div>
  );
}

Особенности работы компонентов

InputContainer

  • Обязательная обертка: Все компоненты (кроме Checkbox и RoundCheckbox) должны быть обернуты в InputContainer
  • Единственный дочерний элемент: InputContainer принимает только один дочерний компонент
  • Автоматическая передача пропсов: InputContainer автоматически передает дочернему компоненту пропсы focused, setFocused, error, innerError, setInnerError
  • Управление placeholder: Placeholder автоматически перемещается вверх при наличии значения или фокуса
  • Иконки: Иконки отображаются автоматически, можно отключить через проп icon={false}

NumberInput

  • Форматирование: Автоматически форматирует числа с пробелами для разделения тысяч
  • Валидация: Автоматически проверяет значения на соответствие min и max
  • Дробные числа: Поддерживает дробные числа с ограничением количества знаков через decimals
  • Разделители: Принимает как точку, так и запятую в качестве разделителя дробной части

FileInput

  • Base64 конвертация: Автоматически конвертирует файл в base64 строку
  • Возвращаемые данные: onChange получает объект { size, name, content }, где content — это data URL (base64)
  • Отображение имени: Используйте valueText для отображения имени выбранного файла

Search и MultipleSearch

  • Фильтрация: Компоненты не фильтруют опции автоматически — нужно реализовать фильтрацию в onSearch
  • Автовыбор: Если введенный текст точно совпадает с названием опции, она автоматически выбирается при клике вне компонента
  • Очистка: При clearOnClickOutside={true} поле поиска очищается, если опция не была выбрана

DateInput и DateTimeInput

  • Библиотека: Используют Flatpickr для выбора даты
  • Валидация: Автоматически валидируют формат ввода через innerError
  • Формат: Принимают и возвращают объекты Date

Checkbox и RoundCheckbox

  • Без InputContainer: Эти компоненты не требуют обертки в InputContainer
  • Группировка: Для группировки используйте одинаковый name и разные value
  • Обработчик: Используйте toggleCallback вместо onChange

Примечания

  • Все инпуты должны быть обернуты в InputContainer для корректной работы (кроме Checkbox и RoundCheckbox)
  • InputContainer принимает только один дочерний элемент
  • Для отображения ошибки в попапе передавайте строку в проп error компонента InputContainer
  • Ошибка автоматически скрывается при передаче null или пустой строки
  • Компоненты автоматически управляют состоянием фокуса — не передавайте focused и setFocused вручную
  • Для работы компонентов требуется настроенная тема styled-components (проверьте наличие ThemeProvider)