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,110 @@
# Label
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs --> - label.variants — `labelVariants` is the cva (class-…
- [label.variants](./label.variants.md) — `labelVariants` is the cva (class-variance-authority) configuration that powers every `<Label>` render. It is split fro…
- label.variants — `labelVariants` is the cva (class-…
- [label.variants](./label.variants.md) — `labelVariants` is the cva (class-variance-authority) configuration that powers every `<Label>` render. It is split fro…
- label.variants — `labelVariants` is the cva (class-…
- [label.variants](./label.variants.md) — `labelVariants` is the cva (class-variance-authority) configuration that powers every `<Label>` render. It is split fro…
- label.variants — `labelVariants` is the cva (class-…
- [label.variants](./label.variants.md) — `labelVariants` is the cva (class-variance-authority) configuration that powers every `<Label>` render. It is split fro…
- [label.variants](./label.variants.md) — `labelVariants` is the cva (class-variance-authority) configuration that powers every `<Label>` render. It is split fro…
## Purpose
`<Label>` is the chassis form-label primitive for `@suaveplan/react-ui`. It wraps `@radix-ui/react-label` so consumers inherit the Radix labelling contract (a click on the label focuses the matched input via `htmlFor`, and synthetic click events originating inside the label are not double-fired on the input) while threading variant resolution through `useThemedVariants("Label")` and surfacing a `data-disabled="true"` mirror so visual disabled state can be represented without needing a sibling form control.
The label children are deliberately NOT translated by the chassis. Labels routinely interleave dynamic inline elements (`<strong>`, `<span>`, `<Trans>`, count badges, asterisks for required fields) and forcing the children through a single string-key lookup would prevent that composition. Consumers compose translation calls in their own JSX, e.g. `<Label htmlFor="email">{t('app.email.label')}</Label>` or `<Label htmlFor="email"><Trans i18nKey="app.email.label" /></Label>`.
## Features
- Wraps `@radix-ui/react-label` for full Radix labelling semantics (focus management on click, no double-fire of synthetic events).
- Forwards refs to the underlying `<label>` element.
- All native `LabelHTMLAttributes` are forwarded as-is via the Radix primitive's props passthrough.
- `disabled` boolean prop writes `data-disabled="true"` on the rendered label so the cva config's `data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-70` rules activate.
- `peer-disabled:` Tailwind variant baked into the cva base so a label adjacent to a `peer`-classed input picks up the disabled styling automatically when the input is disabled.
- Theme variant overrides via `useThemedVariants("Label", labelVariants)`. A `ThemePlugin.variantOverrides.Label` may add brand-new variants (e.g. `variant: "section-heading"`).
- No translation lookup on children — composition is consumer-driven.
## Usage: Basic Example
```tsx
import { Label, Input } from "@suaveplan/react-ui";
export function EmailField() {
return (
<div className="flex flex-col gap-1">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" />
</div>
);
}
```
## Usage: Advanced Example
```tsx
import { Label, Input } from "@suaveplan/react-ui";
import { useTranslation } from "@suaveplan/react-i18n";
export function PasswordField({ disabled }: { disabled: boolean }) {
const { t } = useTranslation();
return (
<div className="flex flex-col gap-1">
<Label htmlFor="password" disabled={disabled}>
{t("app.password.label")}{" "}
<span aria-hidden="true" className="text-destructive">
*
</span>
</Label>
<Input
id="password"
type="password"
disabled={disabled}
className="peer"
autoComplete="current-password"
/>
</div>
);
}
```
## API Reference
### `<Label>`
`forwardRef<HTMLLabelElement, LabelProps>` — renders a Radix `<Label.Root>` element (which itself renders a native `<label>` with the labelling enhancements applied).
#### `LabelProps`
| Prop | Type | Default | Description |
|---|---|---|---|
| `htmlFor` | `string` | _none_ | Required. The id of the form control this label is bound to. |
| `variant` | `"default"` | `"default"` | Visual variant; the chassis ships only `default` so themes carry the variation. |
| `size` | `"default"` | `"default"` | Sizing variant; the chassis ships only `default` so themes carry the variation. |
| `disabled` | `boolean` | `undefined` | When `true`, writes `data-disabled="true"` so the cva opacity decay activates. |
| _all_ `ComponentPropsWithoutRef<typeof LabelPrimitive.Root>` | _native_ | _native_ | Forwarded as-is to Radix. |
#### `labelVariants`
The bare cva resolver. Exported so external consumers (`@suaveplan/react-ui-theme-*`, ejected source under `npx shadcn@latest add @suaveplan/label`) can re-use the variants directly without going through `useThemedVariants`.
#### Errors
The Label component does NOT call `useTranslation()` so it does not interact with the i18n provider at all. Consumers that wrap `<Trans>` or `useTranslation` themselves inherit the upstream provider's no-throw-in-production contract.
## Implementation Notes
- The chassis re-exports Radix's labelling semantics rather than re-implementing them: the click-on-label-focuses-input + click-event-bubble-suppression behaviour is non-trivial to get right, and Radix is the upstream-of-record.
- `data-disabled="true"` is written when the prop is `true`, omitted when `false`/`undefined`. The CVA selector convention is `data-[disabled=true]:…`, so an explicit `data-disabled="false"` would not match but it would clutter the DOM.
- The `peer-disabled:cursor-not-allowed peer-disabled:opacity-70` rules in the cva base let consumers wire a label adjacent to a `peer`-classed input — when the input is disabled, the label picks up the visual decay automatically without requiring the consumer to thread the `disabled` prop through both elements.
- The variants module is intentionally separate (`label.variants.ts`) so an ejected source copy keeps the cva config in a dedicated file, mirroring the upstream shadcn convention.
- `useThemedVariants("Label", labelVariants)` is called unconditionally so the hook order is stable across renders. The hook's memoisation key is `(themeName, "Label")` so a re-render with the same theme returns the same resolver reference.
- Refs forward to the underlying `<label>` (Radix `Label.Root` is implemented as a `<label>` element, not a custom-element wrapper).

View File

@@ -0,0 +1,48 @@
# Label Variants (cva config)
## Purpose
`labelVariants` is the cva (class-variance-authority) configuration that powers every `<Label>` render. It is split from `label.tsx` into its own module so an ejected source copy under `npx shadcn@latest add @suaveplan/label` keeps the cva configuration in a dedicated file — exactly mirroring the upstream shadcn convention. Theme overrides flow through `useThemedVariants("Label", labelVariants)`, so the runtime cva resolver can be replaced or extended by an active `ThemePlugin` without touching the component source.
The module exports a single `labelVariants` callable resolver (`(props?) => string`) tagged with the component name `"Label"` via `defineVariants`. Consumers that want the bare resolver — for example to render the same label styling on a non-`<label>` element such as `<legend>` — import `labelVariants` directly and apply the result via `cn()` themselves.
## Features
- Single `default` cva variant + single `default` size — the shadcn upstream Label recipe is structurally simple; the chassis follows suit so themes carry the variation.
- `peer-disabled:cursor-not-allowed peer-disabled:opacity-70` so a label adjacent to a `peer`-classed disabled input automatically picks up the visual decay without consumer plumbing.
- `data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-70` so the chassis-supplied `disabled` prop drives the same visual decay even without a sibling input.
- `text-sm font-medium leading-none` for the chassis baseline label typography.
## Usage: Basic Example
```ts
import { labelVariants } from "@suaveplan/react-ui";
const className = labelVariants({ variant: "default", size: "default" });
```
## Usage: Advanced Example
```ts
import { labelVariants, cn } from "@suaveplan/react-ui";
export function FieldsetLegend({ children }: { children: React.ReactNode }) {
return (
<legend className={cn(labelVariants({}), "px-2 text-base")}>
{children}
</legend>
);
}
```
## API Reference
### `labelVariants(props?: VariantProps<typeof labelVariants>): string`
Returns the cva-merged class string for the supplied variant + size combination. Both fields are optional; omitted fields fall back to the cva `defaultVariants` (`variant: "default"`, `size: "default"`).
## Implementation Notes
- The resolver is tagged with `componentName: "Label"` (non-enumerable, non-writable) via `defineVariants` so debug tooling can identify the cva instance at runtime.
- The chassis prefers the `peer-disabled` pattern over the chassis `disabled` prop when wiring labels next to inputs — passing `disabled` to both elements is redundant and risks drift between visual and functional state.
- The cva base is intentionally minimal — labels routinely interleave dynamic inline elements (asterisks for required fields, badge counts, helper-text indicators) and the cva config does not attempt to style any of those; consumer composition owns them.