@reformer/ui-kit 8.0.0 → 9.0.0-beta.2

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,94 +1,82 @@
1
1
  # @reformer/ui-kit
2
2
 
3
- Headless UI components for `@reformer/core` - build accessible, fully customizable form interfaces.
3
+ Styled, ready-to-use form components for `@reformer/core`, built with Tailwind CSS and Radix UI.
4
+
5
+ Where [`@reformer/cdk`](https://www.npmjs.com/package/@reformer/cdk) gives you headless primitives,
6
+ `@reformer/ui-kit` gives you drop-in, accessible, pre-styled controls (inputs, selects, checkboxes,
7
+ buttons, layout) that bind straight to a ReFormer `FieldNode`.
4
8
 
5
9
  ## Features
6
10
 
7
- - **Headless** - No default styles, complete design freedom
8
- - **Compound Components** - Declarative, composable API
9
- - **Render Props** - Full control over rendering
10
- - **TypeScript** - Fully typed with generics support
11
- - **Tree-shakable** - Import only what you need
11
+ - **Styled out of the box** Tailwind CSS design tokens, sensible defaults
12
+ - **Accessible** built on Radix UI primitives (Select, Slot, …)
13
+ - **Form-aware** controlled `value` / `onChange` that plug into `useFormControl`
14
+ - **TypeScript** fully typed, discriminated props (e.g. `Input` narrows by `type`)
15
+ - **Tree-shakable** import the whole kit or individual components via subpaths
12
16
 
13
17
  ## Installation
14
18
 
15
19
  ```bash
16
- npm install @reformer/cdk @reformer/core
20
+ npm install @reformer/ui-kit @reformer/core
17
21
  ```
18
22
 
19
- ## Components
23
+ `@reformer/ui-kit` renders with Tailwind CSS — make sure Tailwind is configured in your app so the
24
+ component classes are generated. `@reformer/cdk` and `@reformer/renderer-react` are optional peers
25
+ (needed only if you use the re-exported `FormArray` / `FormWizard`).
20
26
 
21
- ### FormArray
27
+ ## Components
22
28
 
23
- Manage dynamic form arrays with add/remove functionality.
29
+ | Component | Purpose |
30
+ | ----------------------------- | --------------------------------------------------------- |
31
+ | `Input` | Text / email / number input (discriminated by `type`) |
32
+ | `InputMask` | Masked text input |
33
+ | `InputPassword` | Password input with show/hide toggle |
34
+ | `Textarea` | Multi-line text input |
35
+ | `Select` | Radix-based dropdown (`+ SelectItem`, `SelectTrigger`, …) |
36
+ | `Checkbox` | Boolean checkbox |
37
+ | `RadioGroup` | Single-choice radio group |
38
+ | `Button` | Styled button |
39
+ | `Box` / `Section` | Layout containers |
40
+ | `Collapsible` | Expand/collapse container |
41
+ | `AsyncBoundary` | Loading / error boundary for async UI |
42
+ | `FormField` | Label + control + error wrapper |
43
+ | `ErrorState` / `LoadingState` | State display components (`@reformer/ui-kit/state`) |
44
+
45
+ ## Imports
24
46
 
25
47
  ```tsx
26
- import { FormArray } from '@reformer/cdk/form-array';
27
-
28
- <FormArray.Root control={form.items}>
29
- <FormArray.Empty>
30
- <p>No items yet</p>
31
- </FormArray.Empty>
32
-
33
- <FormArray.List>
34
- {({ control, index, remove }) => (
35
- <div>
36
- <span>Item #{index + 1}</span>
37
- <ItemForm control={control} />
38
- <button onClick={remove}>Remove</button>
39
- </div>
40
- )}
41
- </FormArray.List>
42
-
43
- <FormArray.AddButton>Add Item</FormArray.AddButton>
44
- </FormArray.Root>;
45
- ```
48
+ // The whole kit
49
+ import { Input, Select, Checkbox, Button, FormField } from '@reformer/ui-kit';
46
50
 
47
- ### FormWizard
48
-
49
- Build multi-step form wizards with validation.
50
-
51
- ```tsx
52
- import { FormWizard } from '@reformer/cdk/form-wizard';
53
-
54
- <FormWizard form={form} config={config}>
55
- <FormWizard.Indicator steps={STEPS}>
56
- {({ steps, goToStep }) => (
57
- <nav>
58
- {steps.map((step) => (
59
- <button
60
- key={step.number}
61
- onClick={() => goToStep(step.number)}
62
- disabled={!step.canNavigate}
63
- >
64
- {step.title}
65
- </button>
66
- ))}
67
- </nav>
68
- )}
69
- </FormWizard.Indicator>
70
-
71
- <FormWizard.Step component={Step1} control={form} />
72
- <FormWizard.Step component={Step2} control={form} />
73
-
74
- <FormWizard.Actions onSubmit={handleSubmit}>
75
- {({ prev, next, submit, isFirstStep, isLastStep }) => (
76
- <div>
77
- {!isFirstStep && <button {...prev}>Back</button>}
78
- {!isLastStep ? <button {...next}>Next</button> : <button {...submit}>Submit</button>}
79
- </div>
80
- )}
81
- </FormWizard.Actions>
82
- </FormWizard>;
51
+ // Or individual components via subpaths (tree-shaking)
52
+ import { Input } from '@reformer/ui-kit/input';
53
+ import { Select } from '@reformer/ui-kit/select';
54
+ import { Checkbox } from '@reformer/ui-kit/checkbox';
83
55
  ```
84
56
 
85
- ## Hooks
57
+ ## Quick example
86
58
 
87
- For full customization, use the hooks directly:
59
+ Bind a component to a ReFormer field via `useFormControl`:
88
60
 
89
61
  ```tsx
90
- import { useFormArray } from '@reformer/cdk/form-array';
91
- import { useFormWizard } from '@reformer/cdk/form-wizard';
62
+ import { useFormControl, type FieldNode } from '@reformer/core';
63
+ import { Input } from '@reformer/ui-kit';
64
+
65
+ function EmailField({ control }: { control: FieldNode<string> }) {
66
+ const { value, disabled, errors, shouldShowError } = useFormControl(control);
67
+
68
+ return (
69
+ <Input
70
+ type="email"
71
+ value={value}
72
+ disabled={disabled}
73
+ onChange={(v) => control.setValue(v ?? '')}
74
+ onBlur={() => control.markAsTouched()}
75
+ aria-invalid={shouldShowError}
76
+ placeholder={shouldShowError ? errors[0]?.message : 'you@example.com'}
77
+ />
78
+ );
79
+ }
92
80
  ```
93
81
 
94
82
  ## License
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- interface ErrorStateProps {
2
+ export interface ErrorStateProps {
3
3
  /** Текст ошибки, показывается под заголовком. */
4
4
  error: string;
5
5
  /** Заголовок блока ошибки. По умолчанию `'Ошибка загрузки'`. */
@@ -28,4 +28,3 @@ interface ErrorStateProps {
28
28
  * ```
29
29
  */
30
30
  export declare function ErrorState({ error, title, onRetry, retryLabel, className, }: ErrorStateProps): ReactNode;
31
- export {};
@@ -1,2 +1,4 @@
1
1
  export { ErrorState } from './error-state';
2
2
  export { LoadingState } from './loading-state';
3
+ export type { ErrorStateProps } from './error-state';
4
+ export type { LoadingStateProps } from './loading-state';
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- interface LoadingStateProps {
2
+ export interface LoadingStateProps {
3
3
  /** Основной текст. По умолчанию `'Загрузка данных...'`. */
4
4
  title?: string;
5
5
  /** Вспомогательный текст под спиннером. По умолчанию `'Пожалуйста, подождите'`. */
@@ -23,4 +23,3 @@ interface LoadingStateProps {
23
23
  * ```
24
24
  */
25
25
  export declare function LoadingState({ title, subtitle, className, }: LoadingStateProps): ReactNode;
26
- export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  /** Props компонента {@link Checkbox}. */
3
- export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'type'> {
3
+ export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'type' | 'checked' | 'defaultChecked'> {
4
4
  /** Дополнительный CSS-класс для самого input. */
5
5
  className?: string;
6
6
  /** Состояние чекбокса. `undefined` рендерится как `false`. */
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  /** Props компонента {@link InputMask}. */
3
- export interface InputMaskProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange'> {
3
+ export interface InputMaskProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'defaultValue'> {
4
4
  /** Дополнительный CSS-класс. */
5
5
  className?: string;
6
6
  /** Текущее значение поля. `null`/`undefined` рендерится как пустое поле. */
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Чистая логика буферизации ввода для числового `<input>` ({@link Input} c `type="number"`).
3
+ *
4
+ * Проблема, которую решает буфер: контролируемый числовой input, чьё
5
+ * отображение выводится напрямую из `number.toString()`, теряет промежуточные
6
+ * и неканонические состояния ввода. Пока пользователь набирает, `Number(...)`
7
+ * канонизирует строку, а `toString()` схлопывает хвостовые нули и точку:
8
+ *
9
+ * - `"1.50"` → `Number` `1.5` → `toString()` `"1.5"` (хвостовой ноль пропал);
10
+ * - `"1."` → `Number` `1` → `toString()` `"1"` (точку не удержать);
11
+ * - `"0.05"`→ промежуточные `"0."`, `"0.0"` схлопываются в `"0"`;
12
+ * - `"-"` → `Number` `NaN` → эмита нет, поле «прыгает» назад.
13
+ *
14
+ * Модуль хранит сырую строку и отдаёт её на отображение, пока она согласована
15
+ * с текущим `props.value`, а во `onChange` эмитит только распарсенное число.
16
+ * Логика вынесена отдельно от React-компонента, чтобы её можно было покрыть
17
+ * unit-тестами без DOM.
18
+ */
19
+ /** Результат разбора сырой строки числового поля. */
20
+ export type NumberParse =
21
+ /** Пустая строка — во `onChange` уходит `null`. */
22
+ {
23
+ kind: 'empty';
24
+ }
25
+ /** Незавершённый ввод (`"-"`, `"."`, `"1e"`) — `NaN`, эмитить нельзя. */
26
+ | {
27
+ kind: 'partial';
28
+ }
29
+ /** Валидное число. */
30
+ | {
31
+ kind: 'number';
32
+ value: number;
33
+ };
34
+ /**
35
+ * Разбирает сырую строку из числового `<input>` так же, как её увидел бы
36
+ * `onChange`: пустая строка → `empty`, `NaN` (частичный ввод) → `partial`,
37
+ * иначе → `number`.
38
+ */
39
+ export declare function parseNumberInput(raw: string): NumberParse;
40
+ /** Результат {@link resolveEmittedNumber}: эмитить ли значение и какое. */
41
+ export type NumberEmit = {
42
+ emit: true;
43
+ value: number | null;
44
+ } | {
45
+ emit: false;
46
+ };
47
+ /**
48
+ * Вычисляет, что нужно передать во `onChange` для сырой строки, с учётом
49
+ * зажима отрицательных к `0` при `min >= 0`. Частичный ввод не эмитится
50
+ * (`emit: false`), так что поле не откатывается на каждой промежуточной клавише.
51
+ *
52
+ * @param raw Текущее строковое значение `<input>`.
53
+ * @param min Значение атрибута `min` (число) или `undefined`, если не задан.
54
+ */
55
+ export declare function resolveEmittedNumber(raw: string, min: number | undefined): NumberEmit;
56
+ /**
57
+ * Вычисляет строку для отображения в контролируемом числовом `<input>`.
58
+ *
59
+ * Если локальный сырой буфер согласован с текущим `value` — возвращает буфер,
60
+ * сохраняя неканоническое форматирование (`"1.50"`, `"1."`, ведущие нули) и
61
+ * промежуточный частичный ввод (`"-"`, `"."`, `"1e"`). Иначе возвращает
62
+ * каноническое `value.toString()`, чтобы внешнее изменение `value` (reset,
63
+ * пересчёт другого поля) всегда выигрывало у устаревшего буфера.
64
+ *
65
+ * @param rawBuffer Последняя сырая строка из `onChange`, или `null`, если поле
66
+ * ещё не редактировалось локально.
67
+ * @param value Текущее контролируемое значение (уже зажатое к `0`, если был `min`).
68
+ */
69
+ export declare function deriveNumberDisplay(rawBuffer: string | null, value: number | null | undefined): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  /** Props компонента {@link InputPassword}. */
3
- export interface InputPasswordProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'type'> {
3
+ export interface InputPasswordProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'type' | 'defaultValue'> {
4
4
  /** Дополнительный CSS-класс. */
5
5
  className?: string;
6
6
  /** Текущее значение пароля. `null`/`undefined` рендерится как пустое поле. */
@@ -1,29 +1,41 @@
1
1
  import * as React from 'react';
2
- /** Props компонента {@link Input}. */
3
- export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange'> {
2
+ /** Общие props компонента {@link Input}, не зависящие от `type`. */
3
+ interface InputBaseProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'defaultValue' | 'type'> {
4
4
  /** Дополнительный CSS-класс (мерджится с дефолтными Tailwind-классами через `tailwind-merge`). */
5
5
  className?: string;
6
- /**
7
- * Текущее значение поля. Для `type='number'` ожидается `number | null`,
8
- * для остальных — `string | null`. `null`/`undefined` рендерится как пустое поле.
9
- */
10
- value?: string | number | null;
11
- /**
12
- * Обработчик изменений. Получает уже распарсенное значение:
13
- * - для `type='number'` — `number` или `null` (для пустой строки),
14
- * - иначе — `string` или `null`.
15
- * `NaN` не прокидывается. При `min >= 0` отрицательные значения превращаются в `0`.
16
- */
17
- onChange?: (value: string | number | null) => void;
18
6
  /** Срабатывает при потере фокуса. Используется `FormField` для пометки `touched`. */
19
7
  onBlur?: () => void;
20
- /** HTML-тип input. Для `'number'` включается специальный парсинг значения. */
21
- type?: 'text' | 'email' | 'number' | 'tel' | 'url' | 'password';
22
8
  /** Подсказка внутри поля. */
23
9
  placeholder?: string;
24
10
  /** Блокирует ввод и редактирование. */
25
11
  disabled?: boolean;
26
12
  }
13
+ /** Props числового поля (`type="number"`): `value`/`onChange` работают с `number`. */
14
+ export interface NumberInputProps extends InputBaseProps {
15
+ /** HTML-тип input. Включает специальный парсинг значения. */
16
+ type: 'number';
17
+ /** Текущее значение. `null`/`undefined` рендерится как пустое поле. */
18
+ value?: number | null;
19
+ /**
20
+ * Обработчик изменений. Получает уже распарсенное `number` или `null` (для пустой строки).
21
+ * `NaN` не прокидывается. При `min >= 0` отрицательные значения превращаются в `0`.
22
+ */
23
+ onChange?: (value: number | null) => void;
24
+ }
25
+ /** Props строкового поля (любой `type`, кроме `number`): `value`/`onChange` — строковые. */
26
+ export interface TextInputProps extends InputBaseProps {
27
+ /** HTML-тип input. По умолчанию `'text'`. */
28
+ type?: 'text' | 'email' | 'tel' | 'url' | 'password';
29
+ /** Текущее значение. `null`/`undefined` рендерится как пустое поле. */
30
+ value?: string | null;
31
+ /** Обработчик изменений. Получает строку или `null` (для пустой строки). */
32
+ onChange?: (value: string | null) => void;
33
+ }
34
+ /**
35
+ * Props компонента {@link Input} — дискриминированный union по `type`:
36
+ * `type="number"` даёт `number | null` для `value`/`onChange`, любой другой `type` — `string | null`.
37
+ */
38
+ export type InputProps = NumberInputProps | TextInputProps;
27
39
  /**
28
40
  * Текстовое поле ввода. Контролируемый компонент с тривиальным API: `value`/`onChange`
29
41
  * получает строку (или число для `type="number"`). Все нативные `<input>`-атрибуты
@@ -44,7 +56,7 @@ export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElem
44
56
  * <Input
45
57
  * type="number"
46
58
  * value={age}
47
- * onChange={(v) => setAge(v as number | null)}
59
+ * onChange={setAge}
48
60
  * min={0}
49
61
  * placeholder="Возраст"
50
62
  * />
@@ -62,7 +74,7 @@ export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElem
62
74
  * type="email"
63
75
  * value={value}
64
76
  * disabled={disabled}
65
- * onChange={(v) => control.setValue((v ?? '') as string)}
77
+ * onChange={(v) => control.setValue(v ?? '')}
66
78
  * onBlur={() => control.markAsTouched()}
67
79
  * aria-invalid={shouldShowError}
68
80
  * placeholder={shouldShowError ? errors[0]?.message : 'you@example.com'}
@@ -20,6 +20,12 @@ export interface RadioGroupProps extends Omit<React.HTMLAttributes<HTMLDivElemen
20
20
  options: RadioOption[];
21
21
  /** Блокирует все варианты. */
22
22
  disabled?: boolean;
23
+ /**
24
+ * Общий `name` для всех radio группы — обеспечивает нативный одиночный выбор и
25
+ * навигацию стрелками между вариантами. Если не задан, выводится из `id` /
26
+ * `data-testid`, а в крайнем случае генерируется автоматически.
27
+ */
28
+ name?: string;
23
29
  /** Test-id (используется как для контейнера, так и как префикс для каждого radio). */
24
30
  'data-testid'?: string;
25
31
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import * as SelectPrimitive from '@radix-ui/react-select';
4
4
  export type { ResourceConfig, ResourceLoadParams, ResourceItem, ResourceResult, ResourceStrategy, NormalizedOption, } from './select-resource';
5
5
  /** Props компонента {@link Select}. */
6
- export interface SelectProps<T> extends Omit<React.ComponentProps<typeof SelectPrimitive.Root>, 'value' | 'onValueChange'> {
6
+ export interface SelectProps extends Omit<React.ComponentProps<typeof SelectPrimitive.Root>, 'value' | 'onValueChange'> {
7
7
  /** Дополнительный CSS-класс для триггера. */
8
8
  className?: string;
9
9
  /** Выбранное значение. Всегда строка из `option.value`. `null` — ничего не выбрано. */
@@ -13,7 +13,7 @@ export interface SelectProps<T> extends Omit<React.ComponentProps<typeof SelectP
13
13
  /** Срабатывает при закрытии дропдауна (через `onOpenChange(false)`). */
14
14
  onBlur?: () => void;
15
15
  /** Асинхронный источник опций. Если задан вместе с `options`, приоритет у `options`. */
16
- resource?: ResourceConfig<T>;
16
+ resource?: ResourceConfig<unknown>;
17
17
  /**
18
18
  * Inline-варианты. `value` приводится к строке. Опции с одинаковым `group`
19
19
  * объединяются в `SelectGroup` с `SelectLabel` (см. рецепт grouped options).
@@ -97,9 +97,14 @@ export interface SelectProps<T> extends Omit<React.ComponentProps<typeof SelectP
97
97
  * <Select value={country} onChange={setCountry} resource={countries} />
98
98
  * ```
99
99
  */
100
- declare const Select: React.ForwardRefExoticComponent<SelectProps<any> & {
100
+ declare const Select: React.ForwardRefExoticComponent<SelectProps & {
101
+ id?: string;
101
102
  'data-testid'?: string;
102
103
  'aria-invalid'?: boolean | "true" | "false";
104
+ 'aria-labelledby'?: string;
105
+ 'aria-describedby'?: string;
106
+ 'aria-errormessage'?: string;
107
+ 'aria-required'?: boolean | "true" | "false";
103
108
  } & React.RefAttributes<HTMLButtonElement>>;
104
109
  /**
105
110
  * Группа `<SelectItem>` с заголовком {@link SelectLabel}. Тонкая обёртка над
@@ -185,7 +190,7 @@ declare function SelectValue({ ...props }: React.ComponentProps<typeof SelectPri
185
190
  * </SelectTrigger>
186
191
  * ```
187
192
  */
188
- declare function SelectTrigger({ className, size, children, 'aria-label': ariaLabel, ...props }: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
193
+ declare function SelectTrigger({ className, size, children, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, ...props }: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
189
194
  size?: 'sm' | 'default';
190
195
  }): import("react/jsx-runtime").JSX.Element;
191
196
  /**
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  /** Props компонента {@link Textarea}. */
3
- export interface TextareaProps extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'value' | 'onChange'> {
3
+ export interface TextareaProps extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'value' | 'onChange' | 'defaultValue'> {
4
4
  /** Дополнительный CSS-класс. */
5
5
  className?: string;
6
6
  /** Текущее значение. `null`/`undefined` рендерится как пустое поле. */
package/dist/index.d.ts CHANGED
@@ -27,6 +27,8 @@ export { Textarea } from './components/ui/textarea';
27
27
  export type { TextareaProps } from './components/ui/textarea';
28
28
  export { ErrorState } from './components/state/error-state';
29
29
  export { LoadingState } from './components/state/loading-state';
30
+ export type { ErrorStateProps } from './components/state/error-state';
31
+ export type { LoadingStateProps } from './components/state/loading-state';
30
32
  export { FormArraySection } from './components/form-array/form-array-section';
31
33
  export type { FormArraySectionProps } from './components/form-array/form-array-section';
32
34
  export { FormWizard } from './components/form-wizard/form-wizard';
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import { RadioGroup as J } from "./ui/radio-group.js";
14
14
  import { Section as O } from "./ui/section.js";
15
15
  import { Select as X, SelectContent as Y, SelectGroup as _, SelectItem as $, SelectLabel as ee, SelectScrollDownButton as oe, SelectScrollUpButton as te, SelectTrigger as re, SelectValue as ne } from "./ui/select.js";
16
16
  import { Textarea as ie } from "./ui/textarea.js";
17
- import { E as ae, L as ce } from "./loading-state-4VeOE6iN.js";
17
+ import { E as ae, L as ce } from "./loading-state-BOBeK9tp.js";
18
18
  import { F as pe } from "./form-array-section-D1c2AQzE.js";
19
19
  import { F as me, a as xe, b as ge, S as ue } from "./form-wizard-C-yRYqTI.js";
20
20
  const f = () => /* @__PURE__ */ o(
@@ -0,0 +1,49 @@
1
+ import { jsx as e, jsxs as l } from "react/jsx-runtime";
2
+ import { Button as s } from "./ui/button.js";
3
+ function o({
4
+ error: r,
5
+ title: a = "Ошибка загрузки",
6
+ onRetry: t,
7
+ retryLabel: d = "Повторить",
8
+ className: i
9
+ }) {
10
+ return /* @__PURE__ */ e(
11
+ "div",
12
+ {
13
+ "data-testid": "error-state",
14
+ role: "alert",
15
+ "aria-live": "assertive",
16
+ className: i ?? "w-full",
17
+ children: /* @__PURE__ */ l("div", { className: "bg-red-50 border border-red-200 rounded-lg p-6 text-center space-y-4", children: [
18
+ /* @__PURE__ */ e("div", { className: "text-red-600 text-5xl", children: "!" }),
19
+ /* @__PURE__ */ e("div", { className: "text-xl font-semibold text-red-800", children: a }),
20
+ /* @__PURE__ */ e("div", { className: "text-red-700", children: r }),
21
+ t && /* @__PURE__ */ e(s, { onClick: t, children: d })
22
+ ] })
23
+ }
24
+ );
25
+ }
26
+ function m({
27
+ title: r = "Загрузка данных...",
28
+ subtitle: a = "Пожалуйста, подождите",
29
+ className: t
30
+ }) {
31
+ return /* @__PURE__ */ e(
32
+ "div",
33
+ {
34
+ "data-testid": "loading-state",
35
+ role: "status",
36
+ "aria-live": "polite",
37
+ className: t ?? "w-full flex items-center justify-center p-12",
38
+ children: /* @__PURE__ */ l("div", { className: "text-center space-y-4", children: [
39
+ /* @__PURE__ */ e("div", { className: "inline-block h-12 w-12 animate-spin rounded-full border-4 border-solid border-blue-600 border-r-transparent" }),
40
+ /* @__PURE__ */ e("div", { className: "text-lg text-gray-600", children: r }),
41
+ /* @__PURE__ */ e("div", { className: "text-sm text-gray-500", children: a })
42
+ ] })
43
+ }
44
+ );
45
+ }
46
+ export {
47
+ o as E,
48
+ m as L
49
+ };
@@ -0,0 +1 @@
1
+ export {};
package/dist/state.js CHANGED
@@ -1,4 +1,4 @@
1
- import { E as t, L as o } from "./loading-state-4VeOE6iN.js";
1
+ import { E as t, L as o } from "./loading-state-BOBeK9tp.js";
2
2
  export {
3
3
  t as ErrorState,
4
4
  o as LoadingState
@@ -1,35 +1,56 @@
1
- import { jsxs as p, jsx as a } from "react/jsx-runtime";
2
- import * as h from "react";
3
- import { c as f } from "../utils-DtaLkIY8.js";
4
- const x = h.forwardRef(
5
- ({ className: r, value: o, onChange: t, onBlur: d, label: e, disabled: s, "data-testid": c, ...i }, n) => {
6
- const l = (m) => {
7
- t?.(m.target.checked);
1
+ import { jsxs as y, jsx as r } from "react/jsx-runtime";
2
+ import * as d from "react";
3
+ import { c as g } from "../utils-DtaLkIY8.js";
4
+ const v = d.forwardRef(
5
+ ({
6
+ className: t,
7
+ value: i,
8
+ onChange: o,
9
+ onBlur: s,
10
+ label: e,
11
+ disabled: c,
12
+ "data-testid": n,
13
+ id: l,
14
+ "aria-labelledby": m,
15
+ ...p
16
+ }, b) => {
17
+ const h = d.useId(), a = l ?? h, u = e ? void 0 : m, f = (x) => {
18
+ o?.(x.target.checked);
8
19
  };
9
- return /* @__PURE__ */ p("div", { className: "flex items-center gap-2", children: [
10
- /* @__PURE__ */ a(
20
+ return /* @__PURE__ */ y("div", { className: "flex items-center gap-2", children: [
21
+ /* @__PURE__ */ r(
11
22
  "input",
12
23
  {
13
- ref: n,
24
+ ref: b,
14
25
  type: "checkbox",
15
- checked: o || !1,
16
- disabled: s,
17
- className: f(
26
+ id: a,
27
+ checked: i || !1,
28
+ disabled: c,
29
+ className: g(
18
30
  "h-4 w-4 rounded border-gray-300 text-primary focus:ring-2 focus:ring-primary",
19
31
  "disabled:cursor-not-allowed disabled:opacity-50",
20
- r
32
+ "aria-invalid:border-destructive aria-invalid:ring-destructive",
33
+ t
21
34
  ),
22
- onChange: l,
23
- onBlur: d,
24
- "data-testid": c,
25
- ...i
35
+ onChange: f,
36
+ onBlur: s,
37
+ "data-testid": n,
38
+ "aria-labelledby": u,
39
+ ...p
26
40
  }
27
41
  ),
28
- e && /* @__PURE__ */ a("label", { className: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", children: e })
42
+ e && /* @__PURE__ */ r(
43
+ "label",
44
+ {
45
+ htmlFor: a,
46
+ className: "text-sm font-medium leading-none cursor-pointer peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
47
+ children: e
48
+ }
49
+ )
29
50
  ] });
30
51
  }
31
52
  );
32
- x.displayName = "Checkbox";
53
+ v.displayName = "Checkbox";
33
54
  export {
34
- x as Checkbox
55
+ v as Checkbox
35
56
  };