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,88 @@
# `useThemedVariants()` — resolve theme-aware cva variants
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Resolves the active theme's variant overrides for a named component
and returns a memoised cva resolver. Components that compose with the
chassis theme system call this hook rather than importing a
component's `buttonVariants` directly so a downstream theme can swap
or extend the cva config without forking the source. The hook
implements the **type-safety trade-off** documented in the cross-
cutting design §4.2 — default-typed calls return base variants only;
narrowed calls allow brand-new variants with a documented runtime
fall-through.
## Features
- Reads the active `<ThemeProvider>` context for the live theme
- Merges `theme.variantOverrides[componentName]` on top of the base
cva resolver supplied as the second argument
- Memoised per `(themeName, componentName)` so repeated renders share
one resolver reference
- OTel histogram `react_ui.themed_variants.resolve_duration_ms{component}`
on cache miss
- Default-typed call returns the base cva surface
- Narrowed call (`useThemedVariants<'dracula', 'Button'>('Button')`)
exposes brand-new theme-specific variants at the type level
## Usage: Basic example
```tsx
import { useThemedVariants } from "@suaveplan/react-ui/theme/use-themed-variants";
import { cva } from "class-variance-authority";
const buttonVariants = cva("inline-flex h-9 items-center", {
variants: { size: { sm: "px-3", md: "px-4" } },
defaultVariants: { size: "md" },
});
function MyButton() {
const variants = useThemedVariants("Button", buttonVariants);
return <button className={variants({ size: "md" })} type="button" />;
}
```
## Usage: Advanced example
```tsx
import { useThemedVariants } from "@suaveplan/react-ui/theme/use-themed-variants";
declare module "@suaveplan/react-ui/theme" {
interface ThemableVariants {
readonly Button: (props?: { size?: "sm" | "md" | "lg" }) => string;
}
}
// Default-typed: only base sm/md/lg are reachable
const v = useThemedVariants("Button");
v({ size: "lg" });
// Narrowed: brand-new dracula-only `xl` size compiles, but
// silently falls through to undefined when the active theme is
// not 'dracula' (documented runtime behaviour).
const dv = useThemedVariants<"dracula", "Button">("Button");
dv({ size: "xl" } as { size: "xl" });
```
## API Reference
- `useThemedVariants<TThemeName, TComponentName>(componentName, base?)`
- `componentName`: registered component key (autocompleted from
`ThemableVariants`); arbitrary string strings still typecheck
- `base?: CvaResolver` — base resolver used when no theme override
exists; defaults to a no-op resolver that returns `''`
- Returns the typed resolver per the type-safety trade-off
## Implementation notes
The cache lives in a `useRef<CachedEntry | null>` so the resolver
reference is stable across renders for the same `(theme, component)`
tuple. The merge strategy is intentionally simple — when the theme
declares an override for the named component, the override resolver
WINS outright. Consumers needing layered semantics (override augments
base rather than replacing) can use cva's own `compose()` primitive
inside their override declaration. The histogram only fires on a cache
MISS so the steady-state read is zero-cost.