UNPKG

@use-pico/cls

Version:

Type-safe, composable styling system for React, Vue, Svelte, and vanilla JS

1,278 lines (1,275 loc) 55.5 kB
import * as tailwind_merge from 'tailwind-merge'; import { ClassNameValue } from 'tailwind-merge'; import * as react_jsx_runtime from 'react/jsx-runtime'; import { ReactNode } from 'react'; type ClassName = ClassNameValue; type SlotContract = readonly string[]; type TokenContract = readonly string[]; type VariantContract = Record<string, readonly string[]>; type ListOf<T> = [ T, ...T[] ]; type StringToBool<T extends string> = T extends "bool" ? boolean : T; type MergeRecords<A extends Record<string, readonly any[]>, B extends Record<string, readonly any[]>> = { [K in keyof A | keyof B]: K extends keyof B ? K extends keyof A ? [ ...A[K], ...B[K] ] : B[K] : K extends keyof A ? A[K] : never; }; type HasBaseInUseChain<Sub, Base> = Sub extends Base ? true : Sub extends { "~use"?: infer U; } ? HasBaseInUseChain<U, Base> : false; type Contract<TTokenContract extends TokenContract, TSlotContract extends SlotContract, TVariantContract extends VariantContract, TUse extends Contract<any, any, any> | unknown = unknown> = { tokens: TTokenContract; slot: TSlotContract; variant: TVariantContract; "~use"?: TUse; "~definition"?: Definition<any>; }; type ContractEx<TTokenContract extends TokenContract, TSlotContract extends SlotContract, TVariantContract extends VariantContract, TBaseContract extends Contract<any, any, any>> = { tokens: TTokenContract; slot: TSlotContract; variant: TVariantContract; "~use"?: TBaseContract; "~definition"?: Definition<any>; }; type TokensOf<T extends Contract<any, any, any>> = T extends { "~use"?: infer U; } ? U extends Contract<any, any, any> ? T["tokens"][number] | TokensOf<U> : T["tokens"][number] : T["tokens"][number]; type TokensOfList<T extends Contract<any, any, any>> = ListOf<TokensOf<T>>; type TokenDefinitionRequired<T extends Contract<any, any, any>> = { [K in T["tokens"][number]]: What<T>; }; type TokenDefinitionOptional<T extends Contract<any, any, any>> = Partial<Record<TokensOf<T>, What<T>>>; type SlotsOf<T extends Contract<any, any, any>> = T extends { "~use"?: infer U; } ? U extends Contract<any, any, any> ? T["slot"][number] | SlotsOf<U> : T["slot"][number] : T["slot"][number]; type SlotMapping<T extends Contract<any, any, any>> = { [K in SlotsOf<T>]?: What<T>; }; type WhatConfigFn<T extends Contract<any, any, any>> = (props: WhatUtil<T>) => Partial<CreateConfig<T>>; type ClsSlotFn<T extends Contract<any, any, any>> = (config?: WhatConfigFn<T>) => string; type ClsSlots<T extends Contract<any, any, any>> = { [K in SlotsOf<T>]: ClsSlotFn<T>; }; type Variants<T extends Contract<any, any, any>> = T extends { variant: infer V extends VariantContract; "~use"?: infer U; } ? U extends Contract<any, any, any> ? MergeRecords<Variants<U>, V> : V : {}; type VariantValueMapping<T extends Contract<any, any, any>> = { [K in keyof Variants<T>]: StringToBool<Variants<T>[K][number]>; }; type WhatClass = { class: ClassName; }; type WhatToken<T extends Contract<any, any, any>> = { token: TokensOfList<T>; }; type What<T extends Contract<any, any, any>> = WhatClass | WhatToken<T>; interface RuleDefinition<T extends Contract<any, any, any>> { override?: boolean; match?: Partial<VariantValueMapping<T>>; slot: SlotMapping<T>; } type MatchFn<TContract extends Contract<any, any, any>> = (match: RuleDefinition<TContract>["match"] | undefined, slot: SlotMapping<TContract>, override?: boolean) => RuleDefinition<TContract>; type MatchSlotFn<TContract extends Contract<any, any, any>> = (slot: SlotMapping<TContract>, override?: boolean) => RuleDefinition<TContract>; /** * Core utility interface that provides type-safe styling helpers for CLS definitions. * * This interface is passed to definition functions and provides three main categories * of utilities: `what` for creating styling values, `override` for destructive styling, * and `def` for accumulative styling. Each category serves a specific purpose in * the styling system. * * @template T - The contract type that defines the structure (tokens, slots, variants) * * @example * ```typescript * // Used in definition functions * const ButtonCls = cls(contract, ({ what, override, def }) => ({ * token: def.token({ * "color.bg.primary": what.css(["bg-blue-600"]) * }), * rules: [ * def.root({ * root: what.both(["px-4", "py-2"], ["color.bg.primary"]) * }) * ] * })); * ``` */ interface WhatUtil<T extends Contract<any, any, any>> { /** * Styling value creation utilities for building type-safe styling configurations. * * The `what` object provides helpers for creating CSS classes, token references, * variant values, and slot configurations. These utilities ensure type safety * and proper structure for all styling values. */ what: { /** * Creates a pure CSS class styling value. * * Use this when you want to apply CSS classes directly without any token references. * This is ideal for layout utilities, spacing, and other CSS-only styling. * * @param classes - CSS class names to apply * @returns A WhatClass object containing the CSS classes * * @example * ```typescript * // Pure CSS classes for layout * root: what.css(["inline-flex", "items-center", "rounded-md"]) * * // Pure CSS classes for spacing * root: what.css(["px-4", "py-2", "gap-2"]) * * // Pure CSS classes for utilities * root: what.css(["font-medium", "text-sm", "shadow-sm"]) * ``` */ css(classes: ClassName): WhatClass; /** * Creates a token reference styling value. * * Use this when you want to reference design tokens defined in your contract. * Tokens are resolved at runtime and can reference other tokens, creating * a flexible design system. * * @param tokens - Array of token names to reference * @returns A WhatToken object containing the token references * * @example * ```typescript * // Reference single token * root: what.token(["color.bg.primary"]) * * // Reference multiple tokens * root: what.token(["color.bg.primary", "color.text.primary"]) * * // Reference spacing tokens * root: what.token(["spacing.padding.md", "spacing.radius.sm"]) * ``` */ token(tokens: TokensOfList<T>): WhatToken<T>; /** * Creates a mixed styling value with both CSS classes and token references. * * Use this when you need both CSS classes (for layout/utilities) and design tokens * (for theming) in the same styling configuration. This is the most common pattern. * * @param classes - CSS class names for layout and utilities * @param tokens - Array of token names for theming * @returns A What object containing both CSS classes and token references * * @example * ```typescript * // Mixed styling: layout + theming * root: what.both( * ["inline-flex", "items-center", "rounded-md"], // Layout * ["color.bg.primary", "color.text.primary"] // Theming * ) * * // Mixed styling: utilities + design tokens * root: what.both( * ["font-medium", "text-sm", "shadow-sm"], // Utilities * ["spacing.padding.md", "color.bg.default"] // Design tokens * ) * ``` */ both(classes: ClassName, tokens: TokensOfList<T>): What<T>; /** * Creates type-safe variant values for conditional styling. * * Use this to specify variant combinations that will be used in rule matching. * The function ensures only valid variant values are used, providing compile-time * type safety. * * @param variant - Partial variant mapping with type-safe values * @returns A partial variant mapping for use in rules * * @example * ```typescript * // Single variant * what.variant({ size: "lg" }) * * // Multiple variants * what.variant({ size: "lg", variant: "primary" }) * * // Boolean variants * what.variant({ disabled: true, loading: false }) * * // Used in rules * def.rule( * what.variant({ size: "lg", variant: "primary" }), * { root: what.css(["px-6", "py-3"]) } * ) * ``` */ variant(variant: Partial<VariantValueMapping<T>>): Partial<VariantValueMapping<T>>; /** * Creates slot-specific styling configurations. * * Use this to create slot mappings that can be applied to specific component parts. * This is typically used in the `slot` property of configuration objects. * * @param slot - Slot mapping with styling for each slot * @returns A slot mapping for use in configurations * * @example * ```typescript * // Slot configuration in create() method * ButtonCls.create(({ what }) => ({ * slot: what.slot({ * root: what.css(["px-4", "py-2"]), * icon: what.css(["mr-2", "w-4", "h-4"]), * label: what.token(["color.text.primary"]) * }) * })) * ``` */ slot(slot: SlotMapping<T>): SlotMapping<T>; }; /** * Destructive styling utilities that replace previous styles. * * The `override` object provides helpers for creating styling rules that completely * replace previous styles rather than adding to them. This is useful when you need * to ensure clean styling without accumulation. */ override: { /** * Creates a root rule that replaces all previous styles for the specified slot. * * Use this when you want to completely replace the base styling for a slot. * This is destructive - it will clear any previous styles and apply only * the new ones. * * @param slot - Slot mapping with styling that will replace previous styles * @param override - Optional override flag (defaults to true for override mode) * @returns A rule definition for destructive styling * * @example * ```typescript * // Replace all root styles * override.root({ * root: what.css(["px-6", "py-3", "bg-red-500"]) * }) * * // Replace multiple slot styles * override.root({ * root: what.css(["px-4", "py-2"]), * icon: what.css(["w-5", "h-5"]), * label: what.token(["color.text.error"]) * }) * ``` */ root: MatchSlotFn<T>; /** * Creates a conditional rule that replaces styles when variants match. * * Use this when you want to completely replace styles for specific variant * combinations. This is destructive - it will clear any previous styles * and apply only the new ones when the condition is met. * * @param match - Variant condition that triggers the rule * @param slot - Slot mapping with styling that will replace previous styles * @param override - Optional override flag (defaults to true for override mode) * @returns A rule definition for conditional destructive styling * * @example * ```typescript * // Replace styles for primary variant * override.rule( * what.variant({ variant: "primary" }), * { * root: what.token(["color.bg.primary", "color.text.primary"]) * } * ) * * // Replace styles for large primary variant * override.rule( * what.variant({ size: "lg", variant: "primary" }), * { * root: what.both( * ["px-8", "py-4"], * ["color.bg.primary", "color.text.primary"] * ) * } * ) * ``` */ rule: MatchFn<T>; /** * Creates token overrides that replace existing token definitions. * * Use this when you want to completely replace token definitions rather than * merge with them. This is destructive - it will clear any previous token * definitions and apply only the new ones. * * @param token - Partial token definitions that will replace existing ones * @returns Partial token definitions for destructive token styling * * @example * ```typescript * // Replace token definitions * override.token({ * "color.bg.primary": what.css(["bg-indigo-600"]), * "color.text.primary": what.css(["text-indigo-50"]) * }) * ``` */ token(token: Partial<TokenDefinitionOptional<T>>): Partial<TokenDefinitionOptional<T>>; }; /** * Accumulative styling utilities that add to previous styles. * * The `def` object provides helpers for creating styling rules that add to * previous styles rather than replacing them. This is the default behavior * and is useful for building up styles incrementally. */ def: { /** * Creates a root rule that adds to the base styling for the specified slot. * * Use this when you want to add styling to the base configuration for a slot. * This is accumulative - it will add to any previous styles rather than * replacing them. * * @param slot - Slot mapping with styling that will be added to previous styles * @param override - Optional override flag (defaults to false for append mode) * @returns A rule definition for accumulative styling * * @example * ```typescript * // Add to base root styles * def.root({ * root: what.both( * ["inline-flex", "items-center", "rounded-md"], * ["color.text.default", "color.bg.default"] * ) * }) * * // Add to multiple slots * def.root({ * root: what.css(["px-4", "py-2"]), * icon: what.css(["mr-2", "w-4", "h-4"]), * label: what.css(["font-medium"]) * }) * ``` */ root: MatchSlotFn<T>; /** * Creates a conditional rule that adds styles when variants match. * * Use this when you want to add styling for specific variant combinations. * This is accumulative - it will add to any previous styles rather than * replacing them when the condition is met. * * @param match - Variant condition that triggers the rule * @param slot - Slot mapping with styling that will be added to previous styles * @param override - Optional override flag (defaults to false for append mode) * @returns A rule definition for conditional accumulative styling * * @example * ```typescript * // Add styles for large size * def.rule( * what.variant({ size: "lg" }), * { * root: what.css(["px-6", "py-3"]) * } * ) * * // Add styles for primary variant * def.rule( * what.variant({ variant: "primary" }), * { * root: what.token(["color.bg.primary", "color.text.primary"]) * } * ) * ``` */ rule: MatchFn<T>; /** * Creates token definitions that define the design system values. * * Use this to define the actual CSS values for your design tokens. * These token definitions are used throughout the styling system * and can be referenced by other tokens. * * @param token - Required token definitions for all declared tokens * @returns Required token definitions for the design system * * @example * ```typescript * // Define all required tokens * def.token({ * "color.text.default": what.css(["text-gray-900"]), * "color.text.primary": what.css(["text-white"]), * "color.bg.default": what.css(["bg-gray-100"]), * "color.bg.primary": what.css(["bg-blue-600"]), * "spacing.padding.md": what.css(["px-4", "py-2"]) * }) * ``` */ token(token: TokenDefinitionRequired<T>): TokenDefinitionRequired<T>; /** * Creates default variant values for the component. * * Use this to specify the default values for each variant when no * specific variant is provided. These defaults are used as fallbacks * and can be overridden at runtime. * * @param defaults - Default values for each variant * @returns Default variant mapping for the component * * @example * ```typescript * // Set default variants * def.defaults({ * size: "md", * variant: "default", * disabled: false, * loading: false * }) * ``` */ defaults(defaults: VariantValueMapping<T>): VariantValueMapping<T>; }; } /** * Extended styling definition for CLS extension operations. * * This type is identical to `Definition` and is used specifically when extending * existing CLS instances via the `extend()` method. It represents the complete * styling definition that provides concrete values for an extended contract, * ensuring that all declared tokens, rules, and defaults are properly defined. * * The key difference from `Definition` is the context in which it's used - it's * returned by extension definition functions and works with `WhatUtil` to * provide type safety for extended components. * * @template T - The contract type that defines the structure (tokens, slots, variants) * * @example * ```typescript * // Used in extend() operations with WhatUtil * const PrimaryButtonCls = ButtonCls.extend( * { * tokens: ["color.bg.primary", "color.text.primary"], * slot: ["root"], * variant: {} * }, * ({ what, def }) => ({ // Returns DefinitionEx with WhatUtil * token: def.token({ * "color.bg.primary": what.css(["bg-blue-600"]), * "color.text.primary": what.css(["text-white"]) * }), * rules: [ * def.root({ * root: what.token(["color.bg.primary", "color.text.primary"]) * }) * ], * defaults: {} * }) * ); * ``` */ type DefinitionEx<T extends Contract<any, any, any>> = { /** * Required token definitions for all declared tokens in the extended contract. * * This object contains all the design token definitions for the extended component. * Unlike the regular `Definition` type, this enforces that all tokens declared * in the extended contract must be defined, providing stronger type safety * for extension operations. * * Each token name declared in the extended contract must have a corresponding * definition that specifies the actual CSS classes or token references. * These tokens can reference tokens from the parent contract and support * recursive resolution. * * @example * ```typescript * // In extend() operation - all declared tokens must be defined * token: def.token({ * // New tokens declared in extended contract * "color.bg.primary": what.css(["bg-blue-600"]), * "color.text.primary": what.css(["text-white"]), * * // Can reference parent tokens * "button.primary": what.token([ * "color.bg.primary", // New token * "color.text.primary", // New token * "spacing.padding.md" // Parent token * ]) * }) * ``` */ token: TokenDefinitionRequired<T>; /** * Array of styling rules that define conditional styling for the extended component. * * Rules are processed in order and can either add to or replace previous styles * from the parent component. Each rule specifies a condition (variant combination) * and the styling to apply when that condition is met. Rules support both * accumulative (def) and destructive (override) styling modes. * * Extended components can add new rules, override existing rules, or extend * the styling behavior of the parent component. * * @example * ```typescript * rules: [ * // Override parent's root styles * override.root({ * root: what.token(["color.bg.primary", "color.text.primary"]) * }), * * // Add new variant rules * def.rule( * what.variant({ theme: "dark" }), * { * root: what.token(["color.bg.dark", "color.text.dark"]) * } * ), * * // Extend existing variant behavior * def.rule( * what.variant({ size: "lg" }), * { * root: what.css(["shadow-lg", "font-bold"]) * } * ) * ] * ``` */ rules: RuleDefinition<T>[]; /** * Default values for variants in the extended component. * * This object specifies the fallback values for each variant defined in the * extended contract. These defaults can override parent defaults and are used * when creating styled instances without explicit variant overrides. * Extended components can provide new defaults or override inherited defaults. * * @example * ```typescript * defaults: def.defaults({ * // Override parent defaults * variant: "primary", // Instead of parent's "default" * * // Keep parent defaults * size: "md", // Same as parent * * // Add new variant defaults * theme: "light", * loading: false * }) * ``` * * @example * ```typescript * // Usage with extended defaults * const slots = PrimaryButtonCls.create(); * // Uses extended defaults: variant="primary", size="md", theme="light" * * // Override extended defaults at runtime * const slots = PrimaryButtonCls.create(({ what }) => ({ * variant: what.variant({ variant: "secondary", theme: "dark" }) * })); * ``` */ defaults: VariantValueMapping<T>; }; /** * Complete styling definition that provides concrete values for a CLS contract. * * This type represents the implementation of a CLS contract, containing all the * styling values, rules, and defaults that determine how a component is actually * styled. It's returned by definition functions and used internally by CLS instances * to generate styled components. * * @template T - The contract type that defines the structure (tokens, slots, variants) * * @example * ```typescript * // Definition function returns this type * const ButtonCls = cls(contract, ({ what, def }) => ({ * token: def.token({ * "color.bg.primary": what.css(["bg-blue-600"]), * "color.text.primary": what.css(["text-white"]) * }), * rules: [ * def.root({ * root: what.both(["px-4", "py-2"], ["color.bg.primary", "color.text.primary"]) * }), * def.rule( * what.variant({ size: "lg" }), * { root: what.css(["px-6", "py-3"]) } * ) * ], * defaults: def.defaults({ * size: "md", * variant: "default" * }) * })); * ``` */ type Definition<T extends Contract<any, any, any>> = { /** * Token definitions that map token names to their CSS values. * * This object contains all the design token definitions for the component. * Each token name (as declared in the contract) must have a corresponding * definition that specifies the actual CSS classes or token references. * These tokens can be referenced throughout the styling system and support * recursive resolution (tokens can reference other tokens). * * @example * ```typescript * token: def.token({ * // Color tokens * "color.text.default": what.css(["text-gray-900"]), * "color.text.primary": what.css(["text-white"]), * "color.bg.default": what.css(["bg-gray-100"]), * "color.bg.primary": what.css(["bg-blue-600"]), * * // Spacing tokens * "spacing.padding.sm": what.css(["px-2", "py-1"]), * "spacing.padding.md": what.css(["px-4", "py-2"]), * "spacing.padding.lg": what.css(["px-6", "py-3"]), * * // Composite tokens that reference other tokens * "button.primary": what.token(["color.bg.primary", "color.text.primary"]), * "button.default": what.token(["color.bg.default", "color.text.default"]) * }) * ``` */ token: TokenDefinitionRequired<T>; /** * Array of styling rules that define conditional styling based on variants. * * Rules are processed in order and can either add to or replace previous styles * depending on the `override` flag. Each rule specifies a condition (variant * combination) and the styling to apply when that condition is met. Rules * support both accumulative (def) and destructive (override) styling modes. * * @example * ```typescript * rules: [ * // Base styles (accumulative) * def.root({ * root: what.both( * ["inline-flex", "items-center", "rounded-md"], * ["color.text.default", "color.bg.default"] * ), * icon: what.css(["mr-2", "w-4", "h-4"]), * label: what.css(["font-medium"]) * }), * * // Size variants (accumulative - adds to base) * def.rule( * what.variant({ size: "sm" }), * { root: what.css(["px-2", "py-1", "text-sm"]) } * ), * def.rule( * what.variant({ size: "lg" }), * { root: what.css(["px-6", "py-3", "text-lg"]) } * ), * * // Variant styles (destructive - replaces colors) * override.rule( * what.variant({ variant: "primary" }), * { root: what.token(["color.bg.primary", "color.text.primary"]) } * ), * * // Complex conditional styling * def.rule( * what.variant({ size: "lg", variant: "primary" }), * { * root: what.css(["shadow-lg"]), * icon: what.css(["w-5", "h-5"]) * } * ) * ] * ``` */ rules: RuleDefinition<T>[]; /** * Default values for variants when no specific variant is provided. * * This object specifies the fallback values for each variant defined in the * contract. These defaults are used when creating styled instances without * explicit variant overrides. Defaults can be overridden at runtime through * the `create()` method or component props. * * @example * ```typescript * defaults: def.defaults({ * // String variants * size: "md", * variant: "default", * theme: "light", * * // Boolean variants * disabled: false, * loading: false, * fullWidth: false * }) * ``` * * @example * ```typescript * // Usage with defaults * const slots = ButtonCls.create(); // Uses defaults: size="md", variant="default" * * // Override defaults at runtime * const slots = ButtonCls.create(({ what }) => ({ * variant: what.variant({ size: "lg", variant: "primary" }) * })); * ``` */ defaults: VariantValueMapping<T>; }; type CreateConfig<T extends Contract<any, any, any>> = { variant?: Partial<VariantValueMapping<T>>; slot?: SlotMapping<T>; override?: SlotMapping<T>; token?: Partial<TokenDefinitionOptional<T>>; }; /** * Standard component props interface that provides CLS integration capabilities. * * This helper type defines the standard structure for React component props that need * CLS styling integration. It provides optional `tva` and `cls` properties while * preserving all other props from the base type `P`. * * @template TCls - The CLS instance type for styling * @template P - The base props type to extend (defaults to `unknown`) * * @example * ```typescript * // Basic usage in a component * interface ButtonProps extends Component<typeof ButtonCls> { * children: ReactNode; * onClick?: () => void; * } * * const Button: FC<ButtonProps> = ({ tva = ButtonCls, cls, children, onClick }) => { * const slots = tva.create(cls); * return ( * <button className={slots.root()} onClick={onClick}> * {children} * </button> * ); * }; * ``` * * @example * ```typescript * // With custom styling configuration * const Button: FC<ButtonProps> = ({ tva = ButtonCls, cls, children, onClick }) => { * const slots = tva.create(cls, ({ what }) => ({ * variant: what.variant({ size: "lg", variant: "primary" }) * })); * * return ( * <button className={slots.root()} onClick={onClick}> * {children} * </button> * ); * }; * ``` */ type Component<TCls extends Cls<any>, P = unknown> = { /** * The CLS instance to use for styling. If not provided, the component should * use a default CLS instance. This allows components to be styled with different * CLS instances while maintaining type safety. * * @example * ```typescript * // Use default styling * <Button>Click me</Button> * * // Use extended CLS instance * <Button tva={ButtonCls.use(PrimaryButtonCls)}>Click me</Button> * ``` */ tva?: TCls; /** * Optional styling configuration function that receives the `WhatUtil` object * and returns partial configuration overrides. This allows for dynamic styling * based on component state or props. * * @example * ```typescript * // Static configuration * <Button cls={({ what }) => ({ * variant: what.variant({ size: "lg" }) * })}>Click me</Button> * * // Dynamic configuration based on props * <Button cls={({ what }) => ({ * variant: what.variant({ * size: size, * variant: isPrimary ? "primary" : "default" * }) * })}>Click me</Button> * ``` */ cls?: (props: WhatUtil<TCls["contract"]>) => Partial<CreateConfig<TCls["contract"]>>; } & Omit<P, "tva" | "cls">; /** * Extracts the slot functions type from a CLS instance for use in component props. * * This helper type takes a CLS instance and returns the type of its slot functions. * Each slot function accepts an optional configuration and returns a CSS class string. * This is commonly used in React components to provide type-safe access to styling slots. * * @template TCls - The CLS instance type * * @example * ```typescript * // Given a CLS with slots: ["root", "icon", "label"] * interface ButtonProps { * slots?: ComponentSlots<typeof ButtonCls>; * } * * // Usage in component * const Button: FC<ButtonProps> = ({ slots = ButtonCls.create() }) => { * return ( * <button className={slots.root()}> * <Icon className={slots.icon()} /> * <span className={slots.label()}>Click me</span> * </button> * ); * }; * ``` * * @example * ```typescript * // With custom configuration * const Button: FC<ButtonProps> = ({ slots = ButtonCls.create() }) => { * const customSlots = slots(({ what }) => ({ * variant: what.variant({ size: "lg", variant: "primary" }) * })); * * return ( * <button className={customSlots.root()}> * <Icon className={customSlots.icon()} /> * <span className={customSlots.label()}>Click me</span> * </button> * ); * }; * ``` */ type ComponentSlots<TCls extends Cls<any>> = ClsSlots<TCls["contract"]>; /** * Extracts the value type for a specific variant from a CLS instance. * * This helper type takes a CLS instance and a variant name, then returns the appropriate * value type for that variant. For boolean variants (defined with "bool" in the contract), * it returns `boolean`. For all other variants, it returns the string literal type. * * @template TCls - The CLS instance type * @template TVariant - The name of the variant to extract the type for * * @example * ```typescript * // Given a CLS with variants: { size: ["sm", "md", "lg"], disabled: ["bool"] } * type ButtonSize = VariantOf<typeof ButtonCls, "size">; // "sm" | "md" | "lg" * type ButtonDisabled = VariantOf<typeof ButtonCls, "disabled">; // boolean * ``` * * @example * ```typescript * // Usage in component props * interface ButtonProps { * size?: VariantOf<typeof ButtonCls, "size">; * disabled?: VariantOf<typeof ButtonCls, "disabled">; * } * ``` */ type VariantOf<TCls extends Cls<any>, TVariant extends keyof TCls["contract"]["variant"]> = StringToBool<TCls["contract"]["variant"][TVariant][number]>; /** * Main CLS instance interface that provides styling capabilities through contracts and definitions. * * This interface represents a complete CLS instance that combines a contract (structure) * with a definition (styling values) to provide a type-safe styling system. It offers * methods for creating styled instances, extending functionality, and managing inheritance. * * @template T - The contract type that defines the structure (tokens, slots, variants) * * @example * ```typescript * // Create a CLS instance * const ButtonCls = cls(contract, definition); * * // Use the instance to create styled components * const slots = ButtonCls.create(); * const customSlots = ButtonCls.create(({ what }) => ({ * variant: what.variant({ size: "lg", variant: "primary" }) * })); * ``` */ interface Cls<T extends Contract<any, any, any>> { /** * Creates a styled instance with optional configuration overrides. * * This method generates slot functions that can be called to get CSS class strings. * It accepts optional user and internal configuration functions that can override * the default styling behavior. * * @param userConfigFn - Optional user configuration function for runtime overrides * @param internalConfigFn - Optional internal configuration function for component logic * @returns An object with slot functions that return CSS class strings * * @example * ```typescript * // Basic usage - use default styling * const slots = ButtonCls.create(); * const className = slots.root(); // "inline-flex items-center text-gray-900 bg-gray-100" * * // With user configuration * const slots = ButtonCls.create(({ what }) => ({ * variant: what.variant({ size: "lg", variant: "primary" }) * })); * const className = slots.root(); // "inline-flex items-center text-white bg-blue-600 px-6 py-3" * * // With both user and internal configuration * const slots = ButtonCls.create( * ({ what }) => ({ variant: what.variant({ size: "lg" }) }), // User config * ({ what }) => ({ slot: { root: what.css(["internal-class"]) } }) // Internal config * ); * ``` */ create(userConfigFn?: WhatConfigFn<T>, internalConfigFn?: WhatConfigFn<T>): ClsSlots<T>; /** * Extends the current CLS instance with additional functionality. * * This method creates a new CLS instance that inherits from the current one, * adding new tokens, slots, or variants while preserving the existing functionality. * The new instance can override inherited values and add new styling rules. * * @template TTokenContract - Additional token contract * @template TSlotContract - Additional slot contract * @template TVariantContract - Additional variant contract * @param contract - Extended contract with new tokens, slots, and variants * @param definition - Function that receives WhatUtil and returns the extended definition * @returns A new CLS instance with extended functionality * * @example * ```typescript * // Extend with new variants and tokens * const PrimaryButtonCls = ButtonCls.extend( * { * tokens: ["color.bg.primary", "color.text.primary"], * slot: ["root", "icon"], * variant: { * loading: ["bool"], * size: ["sm", "md", "lg", "xl"] * } * }, * ({ what, def }) => ({ * token: def.token({ * "color.bg.primary": what.css(["bg-blue-600"]), * "color.text.primary": what.css(["text-white"]) * }), * rules: [ * def.rule( * what.variant({ loading: true }), * { root: what.css(["opacity-75", "cursor-not-allowed"]) } * ) * ], * defaults: def.defaults({ loading: false, size: "md" }) * }) * ); * ``` */ extend<const TTokenContract extends TokenContract, const TSlotContract extends SlotContract, const TVariantContract extends VariantContract>(contract: Contract<TTokenContract, TSlotContract, TVariantContract, T>, definition: (props: WhatUtil<Contract<TTokenContract, TSlotContract, TVariantContract, T>>) => DefinitionEx<Contract<TTokenContract, TSlotContract, TVariantContract, T>>): Cls<ContractEx<TTokenContract, TSlotContract, TVariantContract, T>>; /** * Assigns a compatible CLS instance for type-safe inheritance. * * This method provides type-safe assignment of CLS instances that are derived * from the current instance. It ensures that only compatible instances can be * assigned, preventing runtime errors from incompatible styling systems. * * @template Sub - The sub-instance type that must be derived from the current instance * @param sub - A CLS instance that must be derived from the current instance * @returns The current CLS instance for method chaining * * @example * ```typescript * // Assign a compatible instance * const ButtonGroup = ButtonCls.use(PrimaryButtonCls); * * // This would cause a TypeScript error if PrimaryButtonCls is not derived from ButtonCls * // const InvalidGroup = ButtonCls.use(SomeOtherCls); // ❌ Type error * * // Use the assigned instance * const slots = ButtonGroup.create(); * ``` */ use<Sub extends Contract<any, any, any>>(sub: Cls<Sub> & { contract: HasBaseInUseChain<Sub, T> extends true ? unknown : [ "❌ Not derived from Base contract", { sub: Sub; base: T; } ]; }): Cls<T>; /** * Creates a configuration function for use in component props. * * This method generates a configuration function that can be passed to components * via the `cls` prop. It provides type-safe access to styling configuration * while ensuring compatibility with the current CLS instance. * * @template Sub - The sub-contract type (defaults to the current contract) * @param userConfigFn - Optional user configuration function * @param internalConfigFn - Optional internal configuration function * @returns A configuration function or undefined * * @example * ```typescript * // Create a configuration function for component props * const configFn = ButtonCls.cls(({ what }) => ({ * variant: what.variant({ size: "lg", variant: "primary" }) * })); * * // Use in component * <Button cls={configFn}>Click me</Button> * * // Or create inline * <Button cls={ButtonCls.cls(({ what }) => ({ * variant: what.variant({ size: "lg" }) * }))}>Click me</Button> * ``` */ cls<Sub extends Contract<any, any, any> = T>(userConfigFn?: { hack: WhatConfigFn<HasBaseInUseChain<Sub, T> extends true ? Sub : never>; }["hack"], internalConfigFn?: { hack: WhatConfigFn<HasBaseInUseChain<Sub, T> extends true ? Sub : never>; }["hack"]): WhatConfigFn<T> | undefined; /** * The contract that defines the structure of this CLS instance. * * Contains the tokens, slots, and variants that define what can be styled * and how the styling system is structured. * * @example * ```typescript * // Access contract information * console.log(ButtonCls.contract.tokens); // ["color.text.default", "color.bg.default", ...] * console.log(ButtonCls.contract.slot); // ["root", "label", "icon"] * console.log(ButtonCls.contract.variant); // { size: ["sm", "md", "lg"], variant: ["default", "primary"] } * ``` */ contract: T; /** * The definition that provides concrete styling values for this CLS instance. * * Contains the token definitions, styling rules, and default values that * determine how the component is actually styled. * * @example * ```typescript * // Access definition information * console.log(ButtonCls.definition.token); // { "color.text.default": {...}, ... } * console.log(ButtonCls.definition.rules); // Array of styling rules * console.log(ButtonCls.definition.defaults); // { size: "md", variant: "default" } * ``` */ definition: Definition<T>; } declare function cls<const TTokenContract extends TokenContract, const TSlotContract extends SlotContract, const TVariantContract extends VariantContract, const TContract extends Contract<TTokenContract, TSlotContract, TVariantContract, any>>(contract: TContract, definitionFn: (props: WhatUtil<TContract>) => Definition<TContract>): Cls<TContract>; declare namespace clx { namespace Internal { /** * Just forward class name type to prevent direct dependency on providing package. */ type ClassName = ClassNameValue; type SlotDef<TSlotKeys extends string> = Record<TSlotKeys, ClassName>; type VariantMap<TSlot extends SlotDef<any>, TUse extends ClsFn<any, any, any> | unknown = unknown> = { [K in string]: { [V in string]: Partial<Record<keyof SlotEx<TSlot, TUse>, ClassName>>; }; }; type VariantDef<TVariantKeys extends string, TSlot extends SlotDef<any>, TUse extends ClsFn<any, any, any> | unknown = unknown> = Record<TVariantKeys, Record<string, Partial<Record<keyof SlotEx<TSlot, TUse>, ClassName>>>>; type VariantKeys<TVariant> = TVariant extends VariantDef<infer K, any, any> ? K : never; type ValuesDef<TVariant extends Record<string, Record<string, any>>> = { [K in keyof TVariant]?: keyof TVariant[K] extends "bool" ? boolean : keyof TVariant[K]; }; /** * Compute slot from all slots (including uses - extensions). */ type SlotEx<TSlot extends SlotDef<any>, TUse extends (() => { "~type": { slot?: SlotDef<any>; }; }) | unknown = unknown> = TUse extends () => { "~type": { slot?: infer S; }; } ? TSlot & S : TSlot; /** * Compute variant from all variants (including uses - extensions). */ type VariantEx<TVariant extends Record<string, Record<string, any>>, TUse extends (() => { "~type": { variant?: Record<string, Record<string, any>>; }; }) | unknown = unknown> = TUse extends () => { "~type": { variant?: infer V; }; } ? TVariant & V : TVariant; /** * Compute default values from all variants (including uses - extensions). * * Current defaults are required, extensions are marked as optional. */ type DefaultsEx<TVariant extends Record<string, Record<string, any>>, TUse extends (() => { "~type": { variant?: Record<string, Record<string, any>>; }; }) | unknown = unknown> = Required<ValuesDef<TVariant>> & (TUse extends () => { "~type": { variant?: infer V; }; } ? ValuesDef<V & Record<string, Record<string, any>>> : {}); type SlotFn<TVariant extends Record<string, Record<string, any>>, TUse extends ClsFn<any, any, any> | unknown = unknown> = (values?: ValuesDef<VariantEx<TVariant, TUse>>, cls?: ClassName) => string; type Slots<TSlot extends SlotDef<any>, TVariant extends Record<string, Record<string, any>>, TUse extends ClsFn<any, any, any> | unknown = unknown> = { [K in keyof SlotEx<TSlot, TUse>]: SlotFn<TVariant, TUse>; }; interface Config<TVariantEx extends Record<string, Record<string, any>>> { /** * Cumulated default values from all variants (including uses - extensions). */ defaults: ValuesDef<TVariantEx>; /** * Combined cumulated defaults & current values provided to `tva()`. */ values: ValuesDef<TVariantEx>; } type Type<TSlotEx extends SlotEx<any, TUse>, TVariantEx extends VariantEx<any, TUse>, TUse extends ClsFn<any, any, any> | unknown> = { /** * Extension type for this variant. */ use?: TUse; /** * Cumulated slots from all extensions. */ slot?: TSlotEx; /** * Cumulated variants from all extensions. */ variant?: TVariantEx; }; type SlotCls<TSlot extends SlotDef<any>, TUse extends ClsFn<any, any, any> | unknown = unknown> = { [K in keyof SlotEx<TSlot, TUse>]?: ClassName; }; /** * Output of the factory method. */ type ClsFn<TSlot extends SlotDef<any>, TVariant extends Record<string, Record<string, any>>, TUse extends ClsFn<any, any, any> | unknown = unknown> = (variant?: ValuesDef<VariantEx<TVariant, TUse>>, cls?: SlotCls<TSlot, TUse>) => { /** * Individual slots for a component. Those slots are then * used to compute individual class names. */ slots: Slots<TSlot, TVariant, TUse>; /** * Configuration used internally. * * This property does not havy any practical use in runtime. */ "~config": Config<VariantEx<TVariant, TUse>>; /** * Used for inheritance and type checking. * * This property does not havy any practical use in runtime. */ "~type": Type<SlotEx<TSlot, TUse>, VariantEx<TVariant, TUse>, TUse>; }; /** * Matching rules. */ interface Match<TSlot extends SlotDef<any>, TVariant extends Record<string, Record<string, any>>, TUse extends ClsFn<any, any, any> | unknown = unknown> { /** * Conditions to match. * * All the provided values must match to apply the rule. */ if: ValuesDef<VariantEx<TVariant, TUse>>; /** * Classes to apply when all conditions are met. * * Keys are slot names. */ do: SlotCls<TSlot, TUse>; } } } declare namespace clx { type Class = Internal.ClassName; /** * Variants configuration. */ interface Config<TSlot extends Internal.SlotDef<any>, TVariant extends Internal.VariantMap<TSlot, TUse>, TUse extends Internal.ClsFn<any, any, any> | unknown = unknown> { /** * Extension of the component. * * When provided, it also provides full type-checking from all previous * extend variants. */ use?: TUse; /** * Define or override slots. * * Slot is a set of classes that are used to compute the final class name. * * By a convention and simpler use, even single-slot component must be placed here. */ slot: TSlot; /** * Define or override variants. * * Variants MUST be defined per slot. Each variant value maps to a record of slot -> class names. */ variant: TVariant; /** * Matching rules. * * Those are used to compute dynamic class names based on the current state (input values). */ match?: Internal.Match<TSlot, TVariant, TUse>[]; /** * Default values. * * They're (cleverly) required to prevent surprises when using variants. */ defaults: Internal.DefaultsEx<TVariant, TUse>; } /** * When used in components, this is a safe way how to extend component props. * * It omits `variant`, `tva`, and `cls` props from the parent props. */ type Props<TCls extends Internal.ClsFn<any, any, any>, P = unknown> = { variant?: Internal.ValuesDef<Internal.VariantEx<ReturnType<TCls>["~type"]["variant"], TCls>>; tva?: TCls; cls?: { [K in keyof Internal.SlotEx<ReturnType<TCls>["~type"]["slot"], TCls>]?: Class; }; } & Omit<P, "variant" | "tva" | "cls">; /** * Extract `variant`, `tva`, and `cls` types from the given props. */ type Extract<TProps extends Props<any>> = Pick<TProps, "variant" | "tva" | "cls">; /** * Drop `cls` types from the gi