UNPKG

jade-garden

Version:

Class utilities to compose class names and variants

473 lines (472 loc) 16.2 kB
import { ClassValue as CV } from 'clsx'; /** * Represents the minimum structure to work with class names. */ export type ClassNameValue = string | string[]; /** * Represents the `class` and `className` props for `cva` and `sva`. * Ensures that only one of `class` or `className` is present. */ export type ClassProp = { class?: ClassValue; className?: never; } | { class?: never; className?: ClassValue; }; /** * **This is the `ClassValue` type from [clsx](https://github.com/lukeed/clsx/tree/master)**. * - Represents a value that can be used as a class name. * - It can be a `string`, `number`, `bigint`, `null`, `boolean`, `undefined`, an array of ClassValue, or a Record with values of `any`. */ export type ClassValue = CV; /** * Represents a function that merges class names. * * @param {...ClassValue[]} classes - An array of class names to merge. * @returns {string} The merged class name string. */ export type MergeClassFn = (...classes: ClassValue[]) => string; export type NestedTraits = Partial<Record<string, Partial<Record<PropertyKey, ClassNameValue>> | ClassNameValue>>; /** * Removes undefined from a type. * * @template T - The type to omit undefined from. * @returns {T extends undefined ? never : T} The type with undefined removed. */ type OmitUndefined<T> = T extends undefined ? never : T; /** * Represents a dictionary where keys are strings and values are ClassValue. */ export type RecordClassValue = Record<string, ClassValue>; /** * Converts "true" or "false" string literals to boolean types. * Otherwise, returns the original type. * * @template T - The type to convert. * @returns {T extends "true" | "false" ? boolean : T} The converted type. */ export type StringToBoolean<T> = T extends "true" | "false" ? boolean : T; /** * Extracts the variant props from a component's props type, excluding `class` and `className`. * * @template Component - The type of the component function. * @returns {Omit<OmitUndefined<Parameters<Component>[0]>, "class" | "className">} The extracted variant props type. */ export type VariantProps<Component extends (...args: any) => any> = Omit<OmitUndefined<Parameters<Component>[0]>, "class" | "className">; /** * Represents the variant configurations for `cva`. * Each variant is a record of class names, keyed by variant values. * * @example * ```ts * { * size: { * small: "text-sm py-1 px-2", * medium: "text-base py-2 px-4", * }, * intent: { * primary: "bg-blue-500 text-white", * secondary: "bg-gray-200 text-gray-800", * } * } * ``` */ export type Variant = Record<string, RecordClassValue>; /** * Represents the schema for variant props in `cva`. * Each variant prop is typed as a StringToBoolean of the corresponding variant value keys. * * @template V - The type of variants. * @example * ```ts * type ButtonVariants = CVAVariants<{ * size: { small: string; medium: string }; * intent: { primary: string; secondary: string }; * }>; * // ButtonVariants = { size?: "small" | "medium"; intent?: "primary" | "secondary" }; * ``` */ export type CVAVariants<V extends Variant> = { [K in keyof V]?: StringToBoolean<keyof V[K]>; }; /** * Represents the configuration object for the `cva` function. * * @template V - The type of variants. * @property {string=} name - Optional component name. * @property {ClassValue=} base - The base class name for the component. * @property {V=} variants - Variants allow you to create multiple versions of the same component. * @property {(V extends Variant ? (CVAVariants<V> | { [K in keyof V]?: StringToBoolean<keyof V[K]> | StringToBoolean<keyof V[K]>[]; }) & ClassProp : ClassProp)[]=} compoundVariants - Compound variants allow you to apply classes to multiple variants at once. * @property {CVAVariants<V>=} defaultVariants - Default variants allow you to set default variants for a component. * * @example * ```ts * const buttonConfig: CVAConfig<{ * size: { small: string; medium: string }; * intent: { primary: string; secondary: string }; * }> = { * base: "rounded-md", * variants: { * size: { * small: "text-sm", * medium: "text-base" * }, * intent: { * primary: "bg-blue-500", * secondary: "bg-gray-200" * } * }, * compoundVariants: [ * { * size: "small", * intent: "primary", * class: "font-bold" * } * ], * defaultVariants: { * size: "medium", * intent: "primary" * } * }; * ``` */ export type CVAConfig<V extends Variant> = { /** * The name of the cva component. */ name?: string; /** * The base class name for the component. */ base?: ClassValue; /** * Variants allow you to create multiple versions of the same component. */ variants?: V; /** * Compound variants allow you to apply classes to multiple variants at once. */ compoundVariants?: (V extends Variant ? (CVAVariants<V> | { [K in keyof V]?: StringToBoolean<keyof V[K]> | StringToBoolean<keyof V[K]>[]; }) & ClassProp : ClassProp)[]; /** * Default variants allow you to set default variants for a component. */ defaultVariants?: CVAVariants<V>; }; /** * Represents the return type of the CVA function. * * @template V - The type of variants. * @returns {(props?: V extends Variant ? CVAVariants<V> & ClassProp : ClassProp) => string} A function that generates class names based on props. */ export type CVAReturnType<V extends Variant> = (props?: V extends Variant ? CVAVariants<V> & ClassProp : ClassProp) => string; /** * Provides type safety for the `data` prop within the `traits` function * when used in a CVA (Composable Variant Authority) context. This type * helps define the structure of data attributes based on component properties. * Each property in the generic `T` represents a component property, and its value * defines the possible values for that property and how they can be used to * generate `data-*` attributes. * * @template T - An object where keys are component property names (e.g., "size", "variant") * and values define the possible values for generating `data-*` attributes. * * @example * ```ts * type ButtonCVATraits = CVATraits<{ * size: "small" | "medium" | "large"; * variant: "primary" | "secondary"; * }>; * * // When using with the traits function: * const buttonClasses = traits<ButtonCVATraits>({ * data: { * size: { * small: "p-2", * medium: "p-4", * large: "p-8" * }, * variant: { * primary: "red-500", * secondary: "blue-500" * } * } * }); * // "data-[size=small]:p-2 data-[size=medium]:p-4 data-[size=large]:p-8 data-[variant=primary]:red-500 data-[variant=secondary]:blue-500" * ``` */ export type CVATraits<T extends Record<string, any>> = { [K in keyof T]?: T[K] extends "" ? ClassNameValue | Partial<Record<PropertyKey, ClassNameValue>> : T[K] extends "number" ? Partial<Record<PropertyKey, ClassNameValue>> : T[K] extends string ? string extends T[K] ? never : Partial<Record<T[K], ClassNameValue>> : never; }; /** * Creates a class variant authority (cva) function. * Generates class names based on provided variants and props. * * @template V - The type of variants. * @param {CVAConfig<V>} config - The cva configuration object. * @returns {CVAReturnType<V>} A function that generates class names based on props. */ export type CVA = <V extends Variant = {}>(config: CVAConfig<V>) => CVAReturnType<V>; /** * Represents the class values for slots, where keys are slot names and values are class names. * * @template RCV - The type of record class values. * @example * ```ts * type ButtonSlots = SlotsClassValue<{ root: string; item: string }>; * // ButtonSlots = { root?: ClassValue; item?: ClassValue }; * ``` */ type SlotsClassValue<RCV extends RecordClassValue> = { [K in keyof RCV]?: ClassValue; }; /** * Represents the default variants for a component. * * @template RCV - The type of record class values. * @example * ```ts * { * size: { * small: { * root: "text-sm" * }, * medium: { * root: "text-base" * } * } * } * ``` */ type DefaultVariants<RCV extends RecordClassValue> = { [key: string]: { [key: string]: SlotsClassValue<RCV>; }; }; /** * Represents the variants for a component. * * @template RCV - The type of record class values. * @template V - The type of variants. * @example * ```ts * type ButtonVariants = Variants< * { * root: string; * item: string; * }, * { * size: { * small: { * root: string; * }; * medium: { * root: string; * }; * }; * } * >; * // type ButtonVariants = * // | { size?: { small?: { root?: ClassValue }; medium?: { root?: ClassValue } } } * // | { size: { small: { root: ClassValue }; medium: { root: ClassValue } } }; * ``` */ export type Variants<RCV extends RecordClassValue, V extends DefaultVariants<RCV> = DefaultVariants<RCV>> = { [K in keyof V]?: { [K2 in keyof V[K]]?: SlotsClassValue<RCV>; }; } | DefaultVariants<RCV>; /** * Reusable type for compound styles that apply based on multiple variant combinations. * * @template RCV - The type of record class values. * @template V - The type of variants. * @example * ```ts * type ButtonCompound = CompoundBase< * { root: string; item: string }, * { size: { small: { root: string }; medium: { root: string } } } * >; * // ButtonCompound = { size?: "small" | "medium" | ("small" | "medium")[] }; * ``` */ type CompoundBase<RCV extends RecordClassValue, V extends Variants<RCV>> = { [K in keyof V]?: StringToBoolean<keyof V[K]> | StringToBoolean<keyof V[K]>[]; }; /** * Represents the props for a component with variants and slots. * * @template RCV - The type of record class values. * @template V - The type of variants. * @example * ```ts * type ButtonProps = SVAVariants< * { root: string; item: string }, * { size: { small: { root: string }; medium: { root: string } } } * >; * // ButtonProps = { size?: "small" | "medium" }; * ``` */ export type SVAVariants<RCV extends RecordClassValue, V extends Variants<RCV>> = { [K in keyof V]?: StringToBoolean<keyof V[K]>; }; /** * Represents the configuration object for the `sva` function. * * @template RCV - The type of record class values. * @template V - The type of variants. * @property {string=} name - Optional component name. * @property {S=} slots - Slots allow you to separate a component into multiple parts. * @property {V=} variants - Variants allow you to create multiple versions of the same component. * @property {Array<CompoundBase<RCV, V> & ( { class?: SlotsClassValue<RCV>; className?: never; } | { class?: never; className?: SlotsClassValue<RCV>; } )>=} compoundVariants - Compound variants allow you to apply classes to multiple variants at once. * @property {Array<{ slots: Array<keyof S> } & CompoundBase<RCV, V> & ClassProp>=} compoundSlots - Compound slots allow you to apply classes to multiple slots at once. * @property {SVAVariants<RCV, V>=} defaultVariants - Default variants allow you to set default variants for a component. * * @example * ```ts * const buttonConfig: SVAConfig< * { root: string; item: string }, * { size: { small: { root: string }; medium: { root: string } } } * > = { * slots: { * root: "flex", * item: "px-2 py-1" * }, * variants: { * size: { * small: { * root: "text-sm" * }, * medium: { * root: "text-base" * } * } * }, * compoundVariants: [{ size: "small", class: { root: "font-bold" } }], * compoundSlots: [{ slots: ["root", "item"], class: "rounded" }], * defaultVariants: { * size: "medium" * } * }; * ``` */ export type SVAConfig<RCV extends RecordClassValue, V extends Variants<RCV>> = { /** * The name of the sva component. */ name?: string; /** * Slots allow you to separate a component into multiple parts. */ slots?: RCV; /** * Variants allow you to create multiple versions of the same component. */ variants?: V; /** * Compound variants allow you to apply classes to multiple variants at once. */ compoundVariants?: Array<CompoundBase<RCV, V> & ({ class?: SlotsClassValue<RCV>; className?: never; } | { class?: never; className?: SlotsClassValue<RCV>; })>; /** * Compound slots allow you to apply classes to multiple slots at once. */ compoundSlots?: Array<{ slots: Array<keyof RCV>; } & CompoundBase<RCV, V> & ClassProp>; /** * Default variants allow you to set default variants for a component. */ defaultVariants?: SVAVariants<RCV, V>; }; /** * Represents the return type of the SVA function. * * @template RCV - The type of record class values. * @template V - The type of variants. * @returns {(props?: SVAVariants<RCV, V>) => { [K in keyof S]: (slotProps?: SVAVariants<RCV, V> & ClassProp) => string }} A function that generates slot-specific class names based on props. */ export type SVAReturnType<RCV extends RecordClassValue, V extends Variants<RCV>> = (props?: SVAVariants<RCV, V>) => { [K in keyof RCV]: (slotProps?: SVAVariants<RCV, V> & ClassProp) => string; }; /** * Provides type safety for the `data` prop within the `traits` function * when used in an SVA (Slots Variant Authority) context. This type helps define * the structure of data attributes for different component slots based on their * specific properties. * * **Important:** When using `SVATraits` with the `traits` function, you will typically * access the type definition for a specific slot from `SVATraits` as the generic * parameter for `traits`. This allows you to provide type-safe data for that slot's * attributes. * * @template Slots - A string or union of strings representing the available slot names (e.g., "root", "item"). * @template T - An object where keys are slot names, and values are objects defining * the possible properties and their value-to-class name mappings for generating `data-*` attributes for that slot. * * @example * ```ts * type AccordionSVATraits = SVATraits< * "root" | "itemTrigger" | "itemContent", * { * root?: { * orientation?: "horizontal" | "vertical"; * }; * itemTrigger?: { * state?: "open" | "closed"; * disabled?: ""; * }; * itemContent?: { * state?: "open" | "closed"; * }; * } * >; * * // When using with the traits function: * const rootClasses = traits<AccordionSVATraits["root"]>({ * className: "flex", * data: { * orientation: { * horizontal: "flex-row", * vertical: "flex-col" * } * } * }); * // "flex data-[orientation=horizontal]:flex-row data-[orientation=vertical]:flex-col" * * const triggerClasses = traits<AccordionSVATraits["itemTrigger"]>({ * data: { * state: { * open: "block", * closed: "hidden" * }, * disabled: "cursor-none" * } * }); * // "data-[state=open]:block data-[state=closed]:hidden data-[disabled]:cursor-none" * ``` */ export type SVATraits<Slots extends string, T extends { [S in Slots]?: Record<string, any>; }> = { [K in keyof T]?: { [P in keyof T[K]]?: T[K][P] extends "" ? ClassNameValue | Partial<Record<PropertyKey, ClassNameValue>> : T[K][P] extends "number" ? Partial<Record<PropertyKey, ClassNameValue>> : T[K][P] extends string ? string extends T[K][P] ? never : Partial<Record<T[K][P], ClassNameValue>> : never; }; }; /** * Creates a slots variants authority (SVA) function for a component. * * @template RCV - The type of record class values. * @template V - The type of variants. * @param {SVAConfig<RCV, V>} config - Configuration options for the SVA function. * @returns {SVAReturnType<RCV, V>} The return type of the SVA function. */ export type SVA = <RCV extends RecordClassValue, V extends Variants<RCV>>(config: SVAConfig<RCV, V>) => SVAReturnType<RCV, V>; export {};