UNPKG

react-class-variants

Version:

Type-safe React variants API for dynamic CSS class composition

213 lines (210 loc) 9.32 kB
import { ElementType, ComponentPropsWithRef, HTMLAttributes, Ref, ReactNode, ReactElement, JSX } from 'react'; type PickRequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K; }[keyof T]; type OmitByValue<T, Value> = { [P in keyof T as T[P] extends Value ? never : P]: T[P]; }; type StringToBoolean<T> = T extends 'true' | 'false' ? boolean : T; type Simplify<T> = { [K in keyof T]: T[K]; }; type Prettify<T> = { [K in keyof T]: T[K]; } & {}; type Exact<T, Shape> = T extends Shape ? Exclude<keyof T, keyof Shape> extends never ? T : never : never; type ClassNameValue = string | null | undefined | ClassNameValue[]; /** * Definition of the available variants and their options. * @example * { * color: { * white: "bg-white" * green: "bg-green-500", * }, * size: { * small: "text-xs", * large: "text-lg" * } * } */ type VariantsSchema = Record<string, Record<string, ClassNameValue>>; type VariantsConfig<V extends VariantsSchema> = { base?: ClassNameValue; variants?: V; defaultVariants?: keyof V extends never ? Record<string, never> : Partial<Variants<V>>; compoundVariants?: keyof V extends never ? never[] : CompoundVariant<V>[]; }; /** * Rules for class names that are applied for certain variant combinations. */ interface CompoundVariant<V extends VariantsSchema> { variants: Partial<VariantsMulti<V>>; className: ClassNameValue; } /** * Maps variant names to their possible values (with boolean string conversion). */ type Variants<V extends VariantsSchema> = { [Variant in keyof V]: StringToBoolean<keyof V[Variant]>; }; /** * Like Variants but allows arrays of values for compound variant matching. */ type VariantsMulti<V extends VariantsSchema> = { [Variant in keyof V]: StringToBoolean<keyof V[Variant]> | StringToBoolean<keyof V[Variant]>[]; }; /** * Only the boolean variants, i.e. ones that have "true" or "false" as options. */ type BooleanVariants<C extends VariantsConfig<V>, V extends VariantsSchema = NonNullable<C['variants']>> = { [Variant in keyof V as V[Variant] extends { true: any; } | { false: any; } ? Variant : never]: V[Variant]; }; /** * Only the variants for which a default options is set. */ type DefaultVariants<C extends VariantsConfig<V>, V extends VariantsSchema = NonNullable<C['variants']>> = { [Variant in keyof V as Variant extends keyof OmitByValue<C['defaultVariants'], undefined> ? Variant : never]: V[Variant]; }; /** * Names of all optional variants, i.e. booleans or ones with default options. */ type OptionalVariantNames<C extends VariantsConfig<V>, V extends VariantsSchema = NonNullable<C['variants']>> = keyof BooleanVariants<C, V> | keyof DefaultVariants<C, V>; type VariantOptions<C extends VariantsConfig<V>, V extends VariantsSchema = NonNullable<C['variants']>> = keyof V extends never ? {} : Required<Omit<Variants<V>, OptionalVariantNames<C, V>>> & Partial<Pick<Variants<V>, OptionalVariantNames<C, V>>>; interface VariantFactoryOptions { onClassesMerged?: (className: string) => string; } type VariantsResolverArgs<P> = PickRequiredKeys<P> extends never ? [props?: P] : [props: P]; /** * Type for the variants resolver function with config metadata. */ type VariantsResolverFn<C extends VariantsConfig<V>, V extends VariantsSchema> = ((...args: VariantsResolverArgs<{ className?: ClassNameValue; } & Omit<VariantOptions<C, V>, 'className'>>) => string) & { /** * @internal * Type-only property to store configuration for type extraction. * This property does not exist at runtime. */ __config?: C; }; /** * Type for the variant props resolver function with config metadata. */ type VariantPropsResolverFn<C extends VariantComponentConfig<V>, V extends VariantsSchema> = (<P extends Omit<VariantOptions<C, V>, 'className'> & { className?: string; }>(props: P) => { className: string; } & Omit<P, 'className' | '__config' | (C['forwardProps'] extends (keyof V)[] ? Exclude<keyof V, C['forwardProps'][number]> : keyof V)>) & { /** * @internal * Type-only property to store configuration for type extraction. * This property does not exist at runtime. */ __config?: C; }; /** * Configuration for variant component with optional render prop control. */ type VariantComponentConfig<V extends VariantsSchema> = VariantsConfig<V> & { withoutRenderProp?: boolean; forwardProps?: (keyof V)[]; }; /** * Base props for a variant component, combining variant options with component props. */ type BaseVariantComponentProps<T extends ElementType, C extends VariantComponentConfig<V>, V extends VariantsSchema> = VariantOptions<C, V> & Omit<ComponentPropsWithRef<T>, keyof VariantOptions<C, V>>; /** * Extracts the full configuration from a variant function, resolver, or component. * @template T - The variant function, resolver, or component type * @returns The VariantsConfig or VariantComponentConfig type * * @example * // Works with variants() * const buttonVariants = variants({ * base: 'btn', * variants: { color: { primary: 'bg-blue' } } * }); * type Config1 = ExtractVariantConfig<typeof buttonVariants>; * * // Works with variantPropsResolver() * const resolveProps = variantPropsResolver({ ... }); * type Config2 = ExtractVariantConfig<typeof resolveProps>; * * // Works with variantComponent() * const Button = variantComponent('button', { ... }); * type Config3 = ExtractVariantConfig<typeof Button>; */ type ExtractVariantConfig<T> = T extends { __config?: infer Config; } ? Config extends VariantsConfig<any> ? Prettify<Config> : never : never; /** * Extracts variant options type from a variant function, resolver, or component. * @template T - The variant function, resolver, or component type * @returns The VariantOptions type * * @example * // Works with variants() * const buttonVariants = variants({ * variants: { color: { primary: 'bg-blue', secondary: 'bg-gray' } } * }); * type Options1 = ExtractVariantOptions<typeof buttonVariants>; * // { color: 'primary' | 'secondary' } * * // Works with variantPropsResolver() * const resolveProps = variantPropsResolver({ * variants: { size: { small: 'text-sm', large: 'text-lg' } }, * defaultVariants: { size: 'small' } * }); * type Options2 = ExtractVariantOptions<typeof resolveProps>; * // { size?: 'small' | 'large' } * * // Works with variantComponent() * const Button = variantComponent('button', { * variants: { color: { primary: 'bg-blue' } } * }); * type Options3 = ExtractVariantOptions<typeof Button>; * // { color: 'primary' } */ type ExtractVariantOptions<T> = T extends { __config?: infer Config; } ? Config extends VariantsConfig<infer Schema> ? Prettify<VariantOptions<Config, Schema>> : never : never; /** * Render prop type. * @template P Props * @example * const children: RenderPropFn = (props) => <div {...props} />; */ type RenderPropFn<P = HTMLAttributes<any> & { ref?: Ref<any>; }> = (props: P) => ReactNode; type RenderPropType<C extends VariantComponentConfig<V>, V extends VariantsSchema> = RenderPropFn<Prettify<{ className: string; ref?: Ref<any>; } & (C['forwardProps'] extends (keyof VariantOptions<C, V>)[] ? Pick<VariantOptions<C, V>, C['forwardProps'][number]> : {}) & Omit<HTMLAttributes<any>, 'render' | 'className' | keyof VariantOptions<C, V>>>> | ReactElement; /** * Component props with optional render prop. */ type VariantComponentPropsWithRender<P, C extends VariantComponentConfig<V>, V extends VariantsSchema> = Simplify<{ render?: RenderPropType<C, V>; } & P>; type VariantComponentType<T extends ElementType, C extends VariantComponentConfig<V>, V extends VariantsSchema = NonNullable<C['variants']>> = (T extends keyof JSX.IntrinsicElements ? C extends { withoutRenderProp: true; } ? (props: BaseVariantComponentProps<T, C, V>) => ReactElement : (props: VariantComponentPropsWithRender<BaseVariantComponentProps<T, C, V>, C, V>) => ReactNode : (props: BaseVariantComponentProps<T, C, V>) => ReactElement) & { /** * @internal * Type-only property to store component configuration for type extraction. * This property does not exist at runtime. */ __config?: C; }; declare function defineConfig(options?: VariantFactoryOptions): { readonly variants: <C extends VariantsConfig<V>, V extends VariantsSchema = NonNullable<C["variants"]>>(config: Exact<Simplify<C>, VariantsConfig<V>>) => VariantsResolverFn<C, V>; readonly variantPropsResolver: <C extends VariantComponentConfig<V>, V extends VariantsSchema = NonNullable<C["variants"]>>(config: Exact<Simplify<C>, VariantComponentConfig<V>>) => VariantPropsResolverFn<C, V>; readonly variantComponent: <T extends ElementType, C extends VariantComponentConfig<V>, V extends VariantsSchema = NonNullable<C["variants"]>>(elementType: T, config: Exact<Simplify<C>, VariantComponentConfig<V>>) => VariantComponentType<T, C, V>; }; export { type BaseVariantComponentProps, type ClassNameValue, type ExtractVariantConfig, type ExtractVariantOptions, type VariantComponentConfig, type VariantComponentPropsWithRender, type VariantComponentType, type VariantFactoryOptions, type VariantOptions, type VariantsConfig, type VariantsSchema, defineConfig };