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,163 @@
# InputGroup
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs --> - input-group.variants — `inputGroupVariants`, `input…
- [input-group.variants](./input-group.variants.md) — `inputGroupVariants`, `inputGroupAddonVariants`, `inputGroupInputVariants`, `inputGroupTextareaVariants`, and `inputGro…
- input-group.variants — `inputGroupVariants`, `input…
- [input-group.variants](./input-group.variants.md) — `inputGroupVariants`, `inputGroupAddonVariants`, `inputGroupInputVariants`, `inputGroupTextareaVariants`, and `inputGro…
- input-group.variants — `inputGroupVariants`, `input…
- [input-group.variants](./input-group.variants.md) — `inputGroupVariants`, `inputGroupAddonVariants`, `inputGroupInputVariants`, `inputGroupTextareaVariants`, and `inputGro…
- input-group.variants — `inputGroupVariants`, `input…
- [input-group.variants](./input-group.variants.md) — `inputGroupVariants`, `inputGroupAddonVariants`, `inputGroupInputVariants`, `inputGroupTextareaVariants`, and `inputGro…
- [input-group.variants](./input-group.variants.md) — `inputGroupVariants`, `inputGroupAddonVariants`, `inputGroupInputVariants`, `inputGroupTextareaVariants`, and `inputGro…
## Purpose
`<InputGroup>` is the chassis container that wraps an input alongside leading/trailing addons (icons, prefix/suffix text, inline buttons). It hoists the focus ring and invalid border onto the wrapper itself so the inner control can drop its own ring; the dedicated `<InputGroupInput>` and `<InputGroupTextarea>` primitives strip the standard border / shadow / focus ring so the wrapper carries them.
Per the shadcn skill rule, NEVER drop a raw `<Input>` or `<Textarea>` inside `<InputGroup>` — the inner control needs the wrapped surface variants to remove its own border and focus ring. The chassis provides `<InputGroupInput>` and `<InputGroupTextarea>` for the input case. Inline buttons inside inputs use `<InputGroupAddon position="none">` + `<InputGroupButton>` (renders an icon-button without a separator border).
## Features
- Wrapper container shares focus ring + invalid border with every child via `focus-within:` and `data-[invalid=true]:`.
- `<InputGroupAddon>` with three positions (`start` / `end` / `none`) controlling which separator border is drawn.
- `<InputGroupInput>` strips the standard `<Input>` border / shadow / focus ring.
- `<InputGroupTextarea>` strips the standard `<Textarea>` border / shadow / focus ring.
- `<InputGroupButton>` for inline icon-buttons (toggle password visibility, clear input, attach file).
- `placeholder` on `<InputGroupInput>` / `<InputGroupTextarea>` typed as `TranslationKey | undefined` and resolved via active i18n.
- `data-invalid="true"` and `data-disabled="true"` on the wrapper drive cva selectors.
- Refs forwarded on every primitive.
- `motion-reduce:transition-none` collapses transitions for `prefers-reduced-motion: reduce`.
- Theme-driven variant overrides via `useThemedVariants("InputGroup" | "InputGroupAddon" | "InputGroupInput" | "InputGroupTextarea" | "InputGroupButton")`.
## Usage: Basic Example
```tsx
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@suaveplan/react-ui";
export function EmailField() {
return (
<InputGroup>
<InputGroupAddon>@</InputGroupAddon>
<InputGroupInput type="email" />
</InputGroup>
);
}
```
## Usage: Advanced Example
```tsx
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@suaveplan/react-ui";
import { EyeIcon, EyeOffIcon } from "lucide-react";
import { useState } from "react";
export function PasswordWithReveal() {
const [show, setShow] = useState(false);
const [pwd, setPwd] = useState("");
const tooShort = pwd.length > 0 && pwd.length < 8;
return (
<InputGroup invalid={tooShort}>
<InputGroupAddon>Pwd</InputGroupAddon>
<InputGroupInput
type={show ? "text" : "password"}
value={pwd}
aria-invalid={tooShort}
onChange={(e) => setPwd(e.currentTarget.value)}
/>
<InputGroupAddon position="none">
<InputGroupButton
aria-label={show ? "Hide password" : "Show password"}
onClick={() => setShow((s) => !s)}
>
{show ? <EyeOffIcon /> : <EyeIcon />}
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
);
}
```
## API Reference
### `<InputGroup>`
`forwardRef<HTMLDivElement, InputGroupProps>` — renders a `<div>` wrapper.
#### `InputGroupProps`
| Prop | Type | Default | Description |
|---|---|---|---|
| `invalid` | `boolean` | `false` | Drives `data-invalid="true"` on the wrapper so border + focus ring flip to destructive. |
| `disabled` | `boolean` | `false` | Drives `data-disabled="true"` for opacity decay. Pass to inner `<InputGroupInput>` separately for native semantics. |
| _all native_ `HTMLAttributes<HTMLDivElement>` | _native_ | _native_ | Forwarded as-is. |
### `<InputGroupAddon>`
`forwardRef<HTMLDivElement, InputGroupAddonProps>` — renders a `<div>` for decorative content.
#### `InputGroupAddonProps`
| Prop | Type | Default | Description |
|---|---|---|---|
| `position` | `"start" \| "end" \| "none"` | `"start"` | Which side the addon hugs and which separator border is drawn (`none` = no separator, used for inline buttons). |
### `<InputGroupInput>`
`forwardRef<HTMLInputElement, InputGroupInputProps>` — renders an `<input>` with the standard border / shadow / focus ring stripped.
#### `InputGroupInputProps`
Inherits every prop from `InputHTMLAttributes<HTMLInputElement>` minus `size` / `placeholder`. Adds:
| Prop | Type | Default | Description |
|---|---|---|---|
| `placeholder` | `TranslationKey` | _none_ | Resolved via active i18n. |
### `<InputGroupTextarea>`
`forwardRef<HTMLTextAreaElement, InputGroupTextareaProps>` — renders a `<textarea>` with the standard border / shadow / focus ring stripped.
#### `InputGroupTextareaProps`
Inherits every prop from `TextareaHTMLAttributes<HTMLTextAreaElement>` minus `placeholder`. Adds:
| Prop | Type | Default | Description |
|---|---|---|---|
| `placeholder` | `TranslationKey` | _none_ | Resolved via active i18n. |
### `<InputGroupButton>`
`forwardRef<HTMLButtonElement, InputGroupButtonProps>` — renders a small icon-button with `type="button"` by default so it does not accidentally submit the surrounding form.
#### `InputGroupButtonProps`
Inherits every prop from `ButtonHTMLAttributes<HTMLButtonElement>`. The default `type="button"` can be overridden if intentional.
#### Cva resolvers
`inputGroupVariants`, `inputGroupAddonVariants`, `inputGroupInputVariants`, `inputGroupTextareaVariants`, and `inputGroupButtonVariants` are all exported as bare cva resolvers for external composition.
## Implementation Notes
- Per the shadcn skill rule, the chassis enforces the `<InputGroupInput>` / `<InputGroupTextarea>` wrappers — raw `<Input>` / `<Textarea>` inside `<InputGroup>` carries an inner border + focus ring that double up with the wrapper's own.
- The `position="none"` value on `<InputGroupAddon>` is the supported way to render an inline button without a separator border. The button supplies its own padding so the addon does not need one.
- `<InputGroupButton>` defaults to `type="button"` so a button rendered inside an input group does not submit the surrounding form when clicked. Override with `type="submit"` only when intentional.
- The wrapper carries the focus ring via `focus-within:` so any inner element gaining focus surfaces the ring on the wrapper. The inner `<InputGroupInput>` removes its own ring via `focus:ring-0` to avoid the visual double-ring.
- Refs target the underlying DOM element on every primitive — consumers can `.focus()` the input, button, addon, or wrapper programmatically.