UNPKG

cvb

Version:

Class Variance Builder 👷🏼

210 lines 7.75 kB
export type ClassValue = string | boolean | null | undefined; export type ClassDictionary = Record<string, any>; export type Pretty<T> = { [K in keyof T]: T[K]; } & {}; type StringToBoolean<T> = T extends 'true' | 'false' ? boolean : T; type OneOrMore<T> = T | Array<T>; type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; type OmitUndefined<T> = T extends undefined ? never : T; /** * Extract the variant as optional props from a `cvb` or `svb` function. * Intended to be used with a JSX component. */ export type VariantProps<Component extends (...args: any) => any> = Omit<OmitUndefined<Parameters<Component>[0]>, 'class' | 'className'>; export interface Compose { <T extends ReturnType<RecipeCreatorFn>[]>(...components: [...T]): (props?: (UnionToIntersection<{ [K in keyof T]: VariantProps<T[K]>; }[number]> | undefined) & ClassProp) => string; } export interface CX { (...inputs: ClassValue[]): string; } export type CXOptions = Parameters<CX>; export type CXReturn = ReturnType<CX>; export type ClassProp = { class?: ClassValue; className?: never; } | { class?: never; className?: ClassValue; }; export type RecipeVariantRecord = Record<any, Record<any, ClassValue>>; export type RecipeSelection<T extends RecipeVariantRecord | SlotRecipeVariantRecord<string>> = { [K in keyof T]?: StringToBoolean<keyof T[K]> | undefined; }; export type RecipeVariantFn<T extends RecipeVariantRecord> = (props?: RecipeSelection<T> & ClassProp) => string; export type RecipeCompoundSelection<T> = { [K in keyof T]?: OneOrMore<StringToBoolean<keyof T[K]>> | undefined; }; export type RecipeCompoundVariant<T> = T & ClassProp; export interface RecipeDefinition<T extends RecipeVariantRecord = RecipeVariantRecord> { /** * Base styles applied to every instance of the component, regardless of variant selection. * These form the foundation of your component's appearance. * * @example * base: 'flex items-center justify-center rounded-md font-medium transition-colors', */ base?: ClassValue; /** * A collection of variant groups, each containing mutually exclusive styling options. * Variants allow your component to adapt to different contexts and requirements. * * @example * variants: { * size: { * sm: 'text-sm px-2 py-1', * md: 'text-base px-4 py-2', * }, * color: { * primary: 'bg-blue-500 text-white hover:bg-blue-600', * secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300' * }, * rounded: { * true: 'rounded-full', * false: 'rounded-md' * } * } */ variants?: T; /** * Styles applied when specific combinations of variants are selected. * This enables complex conditional styling based on multiple variant selections. * * @example * compoundVariants: [ * { * // Apply when size is small and color is primary * size: 'sm', * color: 'primary', * class: 'uppercase tracking-wider' * }, * { * // Apply when size is either md or lg and rounded is true * size: ['md', 'lg'], * rounded: true, * class: 'shadow-md' * } * ] */ compoundVariants?: Pretty<RecipeCompoundVariant<RecipeCompoundSelection<T>>>[]; /** * Predefined variant selections to apply when no specific variants are provided. * These act as the component's factory settings for a consistent default appearance. * * @example * defaultVariants: { * size: 'md', * color: 'primary', * rounded: false * } */ defaultVariants?: RecipeSelection<T>; } export type RecipeCreatorFn = <T extends RecipeVariantRecord>(config: RecipeDefinition<T>) => RecipeVariantFn<T>; export type SlotRecord<S extends string, T> = Partial<Record<S, T>>; export type SlotRecipeVariantRecord<S extends string> = Record<any, Record<any, SlotRecord<S, ClassValue>>>; export type SlotRecipeVariantFn<S extends string, T extends SlotRecipeVariantRecord<S>> = (props?: RecipeSelection<T> & SlotClassProp<S>) => SlotRecord<S, string>; export type SlotClassProp<S extends string> = { class?: SlotRecord<S, ClassValue>; className?: never; } | { class?: never; className?: SlotRecord<S, ClassValue>; }; export type SlotRecipeCompoundVariant<S extends string, T> = T & SlotClassProp<S>; export interface SlotRecipeDefinition<S extends string = string, T extends SlotRecipeVariantRecord<S> = SlotRecipeVariantRecord<S>> { /** * An array of named slots that make up the component. * Each slot represents a distinct part of the component that can be styled independently. * * @example * slots: ['root', 'header', 'body'] */ slots: S[] | Readonly<S[]>; /** * Base styles applied to each slot, regardless of any variants selected. * These styles form the foundation of your component's appearance. * * @example * base: { * root: 'flex flex-col border rounded-lg overflow-hidden', * header: 'p-4 font-semibold border-b bg-gray-50', * body: 'p-4', * } */ base?: SlotRecord<S, ClassValue>; /** * Multi-variant styles organized by variant groups and options. * Each variant option contains styles for specific slots, allowing different * parts of your component to respond to the same variant changes. * * @example * variants: { * size: { * sm: { root: 'text-sm', header: 'py-2' }, * lg: { root: 'text-lg', header: 'py-4' } * }, * rounded: { * true: { root: 'rounded-full' } * } * } */ variants?: T; /** * Conditional styles that apply only when a specific combination of variants is selected. * This enables complex styling logic that depends on multiple variant selections. * * @example * compoundVariants: [ * { * size: 'sm', * rounded: true, * class: { root: 'shadow-sm', header: 'uppercase' } * }, * { * size: ['md', 'lg'], * rounded: false, * class: { root: 'shadow-md' } * } * ] */ compoundVariants?: Pretty<SlotRecipeCompoundVariant<S, RecipeCompoundSelection<T>>>[]; /** * Predefined variant selections to apply when no specific variants are provided. * These act as the component's factory settings, ensuring consistent appearance * even without explicit configuration. * * @example * defaultVariants: { * size: 'md', * rounded: false * } */ defaultVariants?: RecipeSelection<T>; } export type SlotRecipeCreatorFn = <S extends string, T extends SlotRecipeVariantRecord<S>>(config: SlotRecipeDefinition<S, T>) => SlotRecipeVariantFn<S, T>; export interface DefineConfigOptions { hooks?: { /** * A hook function called after class names are concatenated but before they're returned. * This enables post-processing of the final class string, such as deduplication or * resolving class conflicts. * * @param className - The fully concatenated class string generated by CVB functions * @returns The processed class string that will be returned to the caller */ onComplete?: (className: string) => string; }; } export interface DefineConfig { (options?: DefineConfigOptions): { compose: Compose; cx: CX; cvb: RecipeCreatorFn; svb: SlotRecipeCreatorFn; }; } export {}; //# sourceMappingURL=types.d.ts.map