
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
Пакет для управления инпутами с поддержкой валидации и отображения ошибок в попапе.
Пакет для управления инпутами с поддержкой валидации и отображения ошибок в попапе.
import {
InputContainer,
Input,
TextArea,
NumberInput,
MaskedInput,
Select,
MultiSelect,
Search,
MultipleSearch,
GroupSelect,
GroupSearch,
GroupSelectWithSearch,
DateInput,
DateTimeInput,
FileInput,
Checkbox,
RoundCheckbox
} from 'finform';
Важно: Компоненты Checkbox и RoundCheckbox не требуют обертки в InputContainer.
Интерфейс для опций в выпадающих списках и поиске:
interface Option {
id: string | number; // Уникальный идентификатор опции
name: string; // Отображаемое название опции
}
Интерфейс для группированных опций:
interface GroupOption {
id: string | number; // Уникальный идентификатор группы
name: string; // Название группы
items: Option[]; // Массив опций в группе
}
Все компоненты формы должны быть обернуты в InputContainer, который обеспечивает:
import { InputContainer, Input } from 'finform';
<InputContainer error={errorMessage}>
<Input
id="field-id"
placeholder="Введите значение"
value={value}
onChange={setValue}
/>
</InputContainer>
Основной контейнер для всех инпутов. Обеспечивает единообразное оформление и обработку ошибок.
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
children | React.ReactNode | - | Единственный дочерний компонент (Input, Select, TextArea и т.д.) |
className | string | '' | Дополнительные CSS классы |
style | React.CSSProperties | {} | Инлайн стили |
error | string | null | null | Текст ошибки для отображения в попапе |
Важно: error должен быть строкой (не массивом) для корректного отображения в попапе. При наведении на иконку ошибки появляется попап с текстом ошибки.
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>
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
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 и не должны указываться вручную.
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>
Все пропсы из Input, плюс:
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
rows | number | 3 | Количество строк |
autoResize | boolean | true | Автоматическое изменение высоты при вводе |
disableResize | boolean | false | Отключить возможность ручного изменения размера |
Особенности:
autoResize={true} высота автоматически подстраивается под содержимоеdisableResize={true} отключается возможность ручного изменения размера через UIimport { 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>
Все пропсы из Input, плюс:
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
min | number | false | false | Минимальное значение |
max | number | false | false | Максимальное значение |
decimals | number | false | false | Количество знаков после запятой |
innerError | object | null | - | Внутренняя ошибка валидации (используется компонентом) |
setInnerError | (error: object | null) => void | - | Установка внутренней ошибки (используется компонентом) |
Особенности:
1 000 000)innerErrormax или min автоматически показывается ошибка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>
Все пропсы из Input, плюс:
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
mask | string | - | Маска ввода (символ 9 означает цифру) |
Примеры масок:
"+7 (999) 999-99-99" — телефон"99.99.9999" — дата"9999 9999 9999 9999" — номер карты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 целиком, а не только значение.
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
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 | Кнопка добавления новой опции (отображается в списке) |
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>
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
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 вызывается при клике на опцию — нужно самостоятельно управлять массивом valuesonChangeAll вызывается при клике на "Выбрать все" — передается true для выбора всех, false для снятия всех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>
Все пропсы из Select, плюс:
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
search | string | - | Текущее значение строки поиска |
onSearch | (text: string) => void | - | Обработчик изменения текста поиска |
loading | boolean | false | Индикатор загрузки данных |
clearOnClickOutside | boolean | true | Очищать поле поиска при клике вне компонента, если опция не выбрана |
Особенности:
onSearch — используйте это для фильтрации опцийloading={true} отображается индикатор загрузки вместо иконки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>
Все пропсы из Search, но onChange работает как в MultiSelect:
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
values | (string | number)[] | [] | Массив ID выбранных опций |
onChange | (option: Option) => void | - | Обработчик выбора/снятия опции |
Особенности:
MultiSelect, но с возможностью поиска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>
Все пропсы из Select, но options имеет тип GroupOption[]:
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
options | GroupOption[] | [] | Массив группированных опций |
Особенности:
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>
Все пропсы из GroupSelect, плюс:
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
search | string | - | Текущее значение строки поиска |
onSearch | (text: string) => void | - | Обработчик изменения текста поиска |
loading | boolean | false | Индикатор загрузки данных |
Особенности:
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>
Все пропсы из GroupSearch и GroupSelect.
Особенности:
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>
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
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 | - | Установка внутренней ошибки (используется компонентом) |
Особенности:
innerErrorimport { 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>
Все пропсы из DateInput.
Особенности:
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>
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
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/*") |
Особенности:
onChange получает объект с полями:
size — размер файла в байтахname — имя файлаcontent — содержимое файла в формате base64 (data URL)valueText для отображения имени выбранного файлаFileInputimport { Checkbox } from 'finform';
const [checked, setChecked] = useState(false);
<Checkbox
id="agree"
checked={checked}
toggleCallback={(checked) => setChecked(checked)}
text="Я согласен с условиями"
/>
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
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 | {} | Общий стиль контейнера |
Особенности:
InputContainertoggleCallback вместо onChange для обработки измененийname и разные valueimport { RoundCheckbox } from 'finform';
const [checked, setChecked] = useState(false);
<RoundCheckbox
id="option"
checked={checked}
toggleCallback={(checked) => setChecked(checked)}
/>
Все пропсы из Checkbox, но без text, textStyle (круглый чекбокс обычно используется без текста).
Особенности:
Checkbox — имеет круглую формуВажно: Для корректного отображения ошибки в попапе необходимо передать строку в проп error компонента InputContainer. При наведении на иконку ошибки появляется попап с текстом ошибки.
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>
);
}
Если валидация возвращает массив ошибок, используйте первую ошибку или объедините их:
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>
);
}
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>
);
}
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>
);
}
Checkbox и RoundCheckbox) должны быть обернуты в InputContainerInputContainer принимает только один дочерний компонентInputContainer автоматически передает дочернему компоненту пропсы focused, setFocused, error, innerError, setInnerErroricon={false}min и maxdecimalsonChange получает объект { size, name, content }, где content — это data URL (base64)valueText для отображения имени выбранного файлаonSearchclearOnClickOutside={true} поле поиска очищается, если опция не была выбранаinnerErrorDateInputContainername и разные valuetoggleCallback вместо onChangeInputContainer для корректной работы (кроме Checkbox и RoundCheckbox)InputContainer принимает только один дочерний элементerror компонента InputContainernull или пустой строкиfocused и setFocused вручнуюThemeProvider)FAQs
Пакет для управления инпутами с поддержкой валидации и отображения ошибок в попапе.
The npm package finform receives a total of 124 weekly downloads. As such, finform popularity was classified as not popular.
We found that finform demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.