first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,117 @@
# Input
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
- [input-number](./input-number/input-number.md) — <!-- begin:auto-docs --> _(no submodules)_- [input](./input.md) — <!-- begin:auto-docs --> _(no submodules)_- input-number — <!-- begin:auto-docs --> _(no submodu…
- [input.variants](./input.variants.md) — `inputVariants` is the cva (class-variance-authority) configuration that powers every `<Input>` render. It is split fro…
- [input-number](./input-number/input-number.md) — <!-- begin:auto-docs --> _(no submodules)_- [input](./input.md) — <!-- begin:auto-docs --> _(no submodules)_- [input.variants](./input.variants.md) — `inputVariants` is the cva (class-variance-authority) configuration that powers every `<Input>` render. It is split fro…
- [input-number](./input-number/input-number.md) — <!-- begin:auto-docs --> _(no submodules)_- [input](./input.md) — <!-- begin:auto-docs --> _(no submodules)_- input-number — <!-- begin:au…
- [input.variants](./input.variants.md) — `inputVariants` is the cva (class-variance-authority) configuration that powers every `<Input>` render. It is split fro…
- [input-number](./input-number/input-number.md) — <!-- begin:auto-docs --> _(no submodules)_- [input](./input.md) — <!-- begin:auto-docs --> _(no submodules)_- input-number — <!-- begin:auto-docs --> _(no submod…
- [input.variants](./input.variants.md) — `inputVariants` is the cva (class-variance-authority) configuration that powers every `<Input>` render. It is split fro…
- [input-number](./input-number/input-number.md) — <!-- begin:auto-docs --> _(no submodules)_- [input](./input.md) — <!-- begin:auto-docs --> _(no submodules)_- [input.variants](./input.variants.md) — `inputVariants` is the cva (class-variance-authority) configuration that powers every `<Input>` render. It is split fro…
## Purpose
`<Input>` is the chassis primitive form input for `@suaveplan/react-ui`. It renders a native `<input>` element with theme-driven variant resolution, forwards refs to the underlying DOM node, and threads its `placeholder` through the active i18n instance so consumers can never accidentally ship a hardcoded English placeholder. The component is intentionally thin — the shadcn upstream Input recipe is structurally simple and the chassis follows suit, exposing a single `default` variant + `default` size at the cva level so themes carry the visual variation rather than forcing the component to enumerate it.
`<Input>` participates in the same `data-*` attribute convention as the rest of `@suaveplan/react-ui`: `data-invalid="true"` and `data-disabled="true"` are written when the corresponding props are set, so theme overrides can target error state via a single CSS selector without depending on `aria-invalid` (which the consumer may or may not have set independently).
## Features
- Forwards refs to the underlying `<input>` so consumers can focus, select, measure, or animate it directly.
- All native `InputHTMLAttributes` are forwarded as-is (`onChange`, `value`, `defaultValue`, `name`, `autoComplete`, `inputMode`, `pattern`, etc.).
- `placeholder` typed as `TranslationKey | undefined` — the chassis enforces that the placeholder text is a registered translation key, never a raw English literal. Passing `undefined` skips the attribute entirely.
- `invalid` boolean prop drives `data-invalid="true"` on the rendered element so the cva config's `data-[invalid=true]:border-destructive` rule activates.
- `disabled` mirrors as `data-disabled="true"` for the parity with shadcn's selector convention.
- Theme variant overrides via `useThemedVariants("Input", inputVariants)`. A `ThemePlugin.variantOverrides.Input` may add brand-new size or appearance variants (see the type-safety trade-off in `proposal.md` §"Type-safety trade-off").
- `motion-reduce:transition-none` collapses focus/border transitions for users with `prefers-reduced-motion: reduce`.
- Focus styling via `focus-visible:ring-2 focus-visible:ring-ring` — keyboard users get a visible ring; pointer focus does not.
- File-input styling via `file:border-0 file:bg-transparent file:text-sm file:font-medium` so `<Input type="file">` inherits a sensible button look.
## Usage: Basic Example
```tsx
import { Input } from "@suaveplan/react-ui";
export function NameField({
onChange,
}: {
onChange: (next: string) => void;
}) {
return (
<Input
name="full-name"
autoComplete="name"
onChange={(event) => onChange(event.currentTarget.value)}
/>
);
}
```
## Usage: Advanced Example
```tsx
import { Input } from "@suaveplan/react-ui";
export function EmailField({
value,
error,
onChange,
}: {
value: string;
error: string | undefined;
onChange: (next: string) => void;
}) {
return (
<Input
type="email"
name="email"
autoComplete="email"
inputMode="email"
placeholder="react-ui.empty.noResults"
value={value}
invalid={error !== undefined}
aria-invalid={error !== undefined}
aria-describedby={error !== undefined ? "email-error" : undefined}
onChange={(event) => onChange(event.currentTarget.value)}
/>
);
}
```
## API Reference
### `<Input>`
`forwardRef<HTMLInputElement, InputProps>` — renders a `<input type={type}>` (default `"text"`).
#### `InputProps`
| Prop | Type | Default | Description |
|---|---|---|---|
| `variant` | `"default"` | `"default"` | Visual variant. The chassis ships only `default`; themes may extend. |
| `size` | `"default"` | `"default"` | Sizing variant. The chassis ships only `default`; themes may extend. |
| `placeholder` | `TranslationKey \| undefined` | `undefined` | Translation key resolved against the active i18n instance. Hardcoded literals are rejected at compile time. |
| `invalid` | `boolean` | `undefined` | When `true`, writes `data-invalid="true"` on the element so the cva error rules activate. |
| `disabled` | `boolean` | `undefined` | Forwarded to the native `<input>` AND mirrored as `data-disabled="true"`. |
| `type` | _native_ | `"text"` | Native input type. |
| _all native_ `InputHTMLAttributes<HTMLInputElement>` | _native_ | _native_ | Forwarded as-is. |
#### `inputVariants`
The bare cva resolver. Exported so external consumers (`@suaveplan/react-ui-theme-*`, ejected source under `npx shadcn@latest add @suaveplan/input`) can re-use the variants directly without going through `useThemedVariants`.
#### Errors
- `useTranslation()` is called unconditionally inside the component. When the surrounding tree lacks a `<TranslationProvider>` (or `<ReactUiProvider>`), the hook returns its identity-fallback shape — `t(key)` returns the key as the literal. This matches the `react-i18n` "no-throw in production" contract; the component renders, but the rendered placeholder is the raw key.
## Implementation Notes
- The variants module is intentionally separate (`input.variants.ts`) so an ejected source copy keeps the cva config in a dedicated file, mirroring the upstream shadcn convention.
- `useThemedVariants("Input", inputVariants)` is called unconditionally so the hook order is stable across renders. The hook's memoisation key is `(themeName, "Input")` so a re-render with the same theme returns the same resolver reference.
- `placeholder` is omitted entirely when `undefined`, NOT set to the empty string. An empty placeholder attribute would still allocate the placeholder pseudo-element in the user-agent stylesheet and could collide with screen-reader heuristics.
- `data-invalid` and `data-disabled` are written when `true`, omitted entirely when `false` or `undefined`. The CVA selector convention is `data-[invalid=true]:…` so an explicit `data-invalid="false"` would not match, but it would clutter the rendered HTML.
- The chassis does NOT inject any `aria-invalid` attribute. Consumers who want `aria-invalid="true"` MUST pass it themselves alongside `invalid`. This separation lets the component represent purely visual error state (e.g. "the user has not yet attempted to submit, but we want to indicate the field is required").
- `<Input type="file">` inherits the file-input styling rules from the cva base; consumers do not need to layer additional classes for the upload button.