first commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user