@gentleduck/variants
Advanced tools
+13
-3
@@ -90,2 +90,14 @@ //#region src/variants.types.d.ts | ||
| /** | ||
| * Removes an array type from a union type. | ||
| * Used to exclude `class` and `className` from `VariantProps`. | ||
| * @template T - A union type. | ||
| * @example | ||
| * ```ts | ||
| * type Props = { class?: string; className?: string } | ||
| * type PropsWithoutArray = RemoveArray<Props> | ||
| * // => { class?: string; className?: string } | ||
| * ``` | ||
| */ | ||
| type RemoveArray<T> = T extends any[] ? never : T; | ||
| /** | ||
| * Extracts only the variant-related props from a CVA function’s signature, | ||
@@ -108,3 +120,3 @@ * omitting `class` and `className`. | ||
| */ | ||
| type VariantProps<T> = T extends ((props?: infer P) => string) ? { [K in keyof P as K extends 'class' | 'className' ? never : K]: P[K] } : never; | ||
| type VariantProps<T> = T extends ((props?: infer P) => string) ? { [K in keyof P as K extends 'class' | 'className' ? never : K]: RemoveArray<P[K]> } : never; | ||
| /** | ||
@@ -134,3 +146,2 @@ * A dictionary mapping CSS class names to boolean flags. | ||
| type ClassValue = string | number | boolean | ClassDictionary | ClassArray; | ||
| //#endregion | ||
@@ -187,5 +198,4 @@ //#region src/variants.d.ts | ||
| }), maybeOptions?: VariantsOptions<TVariants>): (props?: CvaProps<TVariants>) => string; | ||
| //#endregion | ||
| export { ClassArray, ClassDictionary, ClassValue, CvaProps, VariantParams, VariantProps, VariantsOptions, cva }; | ||
| //# sourceMappingURL=index.d.ts.map |
+1
-91
@@ -1,92 +0,2 @@ | ||
| /** | ||
| * Build a stable cache key by serializing props entries in sorted order. | ||
| * | ||
| * @template TVariants | ||
| * The mapping of variant names to their allowed string/string[] classes. | ||
| * | ||
| * @param {CvaProps<TVariants>} props | ||
| * The props object passed into the CVA function (variant selections + class/className). | ||
| * | ||
| * @returns {string} | ||
| * A deterministic string key used for memoization. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * getCacheKey({ intent: 'primary', size: ['sm', 'md'], className: 'mt-4' }) | ||
| * // => "className:mt-4|intent:primary|size:[sm,md]" | ||
| * ``` | ||
| */function e(e){let t=Object.entries(e),n=``;for(let e=0;e<t.length;e++){let[r,i]=t[e];Array.isArray(i)?n+=`${r}:[${i.map(String).join(`,`)}]`:n+=`${r}:${String(i)}`,e<t.length-1&&(n+=`|`)}return n} | ||
| /** | ||
| * Recursively flattens any supported `ClassValue` into individual CSS tokens. | ||
| * | ||
| * Supports: | ||
| * - primitive strings/numbers/booleans (whitespace-split) | ||
| * - nested arrays of `ClassValue` | ||
| * - dictionaries `{ className: boolean }` for conditional classes | ||
| * | ||
| * @param {ClassValue | undefined} input | ||
| * The value to flatten into tokens. | ||
| * @param {string[]} tokens | ||
| * The accumulator array receiving each CSS token. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * const out: string[] = [] | ||
| * flattenClasses( | ||
| * [ | ||
| * 'px-4 py-2', | ||
| * { 'text-bold': true, invisible: false }, | ||
| * ['hover:bg-red-500', ['active:scale-95']], | ||
| * ], | ||
| * out | ||
| * ) | ||
| * // out => ['px-4','py-2','text-bold','hover:bg-red-500','active:scale-95'] | ||
| * ``` | ||
| */function t(e,n){if(e!=null){if(typeof e==`string`||typeof e==`number`||typeof e==`boolean`){let t=String(e).split(/\s+/);for(let e=0;e<t.length;e++){let r=t[e];r&&n.push(r)}return}if(Array.isArray(e)){for(let r=0;r<e.length;r++)t(e[r],n);return}for(let t in e)Object.hasOwn(e,t)&&e[t]&&n.push(t)}} | ||
| /** | ||
| * Creates a Class Variance Authority (CVA) function for composing class names | ||
| * based on a base string, variants, defaultVariants, and compoundVariants. | ||
| * | ||
| * Supports two call signatures: | ||
| * - `cva(base: string, options: VariantsOptions<TVariants>)` | ||
| * - `cva(options: VariantsOptions<TVariants> & { base: string })` | ||
| * | ||
| * @template TVariants | ||
| * A record mapping variant keys to a record of allowed values and their classes. | ||
| * | ||
| * @param {string | (VariantsOptions<TVariants> & { base?: string })} baseOrOptions | ||
| * Either the base class string, or an options object including `base`. | ||
| * @param {VariantsOptions<TVariants>} [maybeOptions] | ||
| * The options object when using the two-arg signature. | ||
| * | ||
| * @returns {(props?: CvaProps<TVariants>) => string} | ||
| * A function that, given variant props and optional `class`/`className`, returns | ||
| * the deduplicated, memoized className string. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * const button = cva('btn px-4 py-2', { | ||
| * variants: { | ||
| * intent: { primary: 'bg-blue-500 text-white', danger: 'bg-red-500' }, | ||
| * size: { sm: 'text-sm', lg: 'text-lg' }, | ||
| * }, | ||
| * defaultVariants: { intent: 'primary', size: 'sm' }, | ||
| * compoundVariants: [ | ||
| * { | ||
| * intent: ['primary','danger'], | ||
| * size: 'lg', | ||
| * className: 'uppercase', | ||
| * }, | ||
| * ], | ||
| * }) | ||
| * | ||
| * // uses defaults + compound match | ||
| * button() | ||
| * // => 'btn px-4 py-2 bg-blue-500 text-white text-sm uppercase' | ||
| * | ||
| * // overrides size + adds custom classes | ||
| * button({ size: 'lg', class: ['mt-4','shadow'] }) | ||
| * // => 'btn px-4 py-2 bg-blue-500 text-white text-lg uppercase mt-4 shadow' | ||
| * ``` | ||
| */function n(n,r){let i=typeof n==`string`?{base:n,...r}:n,{base:a=``,variants:o,defaultVariants:s={},compoundVariants:c=[]}=i,l=new Map;return(n={})=>{let r=e(n),i=l.get(r);if(i)return i;let u=[],d=new Set;t(a,u);let f={...s,...n};for(let e in o){let n=f[e];if(n==null||n===`unset`)continue;let r=o[e][String(n)];t(r,u)}for(let e=0;e<c.length;e++){let n=c[e],r=!0;for(let e in n){if(e===`class`||e===`className`)continue;let t=n[e],i=f[e];if(Array.isArray(t)&&i){if(!t.includes(i.toString())){r=!1;break}}else if(i!==t){r=!1;break}}r&&(t(n.class,u),t(n.className,u))}t(n.className,u),t(n.class,u);for(let e=0;e<u.length;e++)d.add(u[e]);let p=Array.from(d).join(` `);return l.set(r,p),p}}export{n as cva}; | ||
| function e(e){let t=Object.entries(e),n=``;for(let e=0;e<t.length;e++){let[r,i]=t[e];Array.isArray(i)?n+=`${r}:[${i.map(String).join(`,`)}]`:n+=`${r}:${String(i)}`,e<t.length-1&&(n+=`|`)}return n}function t(e,n){if(e!=null){if(typeof e==`string`||typeof e==`number`||typeof e==`boolean`){let t=String(e).split(/\s+/);for(let e=0;e<t.length;e++){let r=t[e];r&&n.push(r)}return}if(Array.isArray(e)){for(let r=0;r<e.length;r++)t(e[r],n);return}for(let t in e)Object.hasOwn(e,t)&&e[t]&&n.push(t)}}function n(n,r){let i=typeof n==`string`?{base:n,...r}:n,{base:a=``,variants:o,defaultVariants:s={},compoundVariants:c=[]}=i,l=new Map;return(n={})=>{let r=e(n),i=l.get(r);if(i)return i;let u=[],d=new Set;t(a,u);let f={...s,...n};for(let e in o){let n=f[e];if(n==null||n===`unset`)continue;let r=o[e][String(n)];t(r,u)}for(let e=0;e<c.length;e++){let n=c[e],r=!0;for(let e in n){if(e===`class`||e===`className`)continue;let t=n[e],i=f[e];if(Array.isArray(t)&&i){if(!t.includes(i.toString())){r=!1;break}}else if(i!==t){r=!1;break}}r&&(t(n.class,u),t(n.className,u))}t(n.className,u),t(n.class,u);for(let e=0;e<u.length;e++)d.add(u[e]);let p=Array.from(d).join(` `);return l.set(r,p),p}}export{n as cva}; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","names":["props: CvaProps<TVariants>","input: ClassValue | undefined","tokens: string[]","baseOrOptions: string | (VariantsOptions<TVariants> & { base?: string })","maybeOptions?: VariantsOptions<TVariants>"],"sources":["../src/variants.ts"],"sourcesContent":["import type { ClassValue, CvaProps, VariantsOptions } from './variants.types'\n\n/**\n * Build a stable cache key by serializing props entries in sorted order.\n *\n * @template TVariants\n * The mapping of variant names to their allowed string/string[] classes.\n *\n * @param {CvaProps<TVariants>} props\n * The props object passed into the CVA function (variant selections + class/className).\n *\n * @returns {string}\n * A deterministic string key used for memoization.\n *\n * @example\n * ```ts\n * getCacheKey({ intent: 'primary', size: ['sm', 'md'], className: 'mt-4' })\n * // => \"className:mt-4|intent:primary|size:[sm,md]\"\n * ```\n */\nfunction getCacheKey<TVariants extends Record<string, Record<string, string | string[]>>>(\n props: CvaProps<TVariants>,\n): string {\n const entries = Object.entries(props) as [string, ClassValue][]\n\n let key = ''\n for (let i = 0; i < entries.length; i++) {\n const [k, v] = entries[i]\n if (Array.isArray(v)) {\n key += `${k}:[${v.map(String).join(',')}]`\n } else {\n key += `${k}:${String(v)}`\n }\n if (i < entries.length - 1) key += '|'\n }\n return key\n}\n\n/**\n * Recursively flattens any supported `ClassValue` into individual CSS tokens.\n *\n * Supports:\n * - primitive strings/numbers/booleans (whitespace-split)\n * - nested arrays of `ClassValue`\n * - dictionaries `{ className: boolean }` for conditional classes\n *\n * @param {ClassValue | undefined} input\n * The value to flatten into tokens.\n * @param {string[]} tokens\n * The accumulator array receiving each CSS token.\n *\n * @example\n * ```ts\n * const out: string[] = []\n * flattenClasses(\n * [\n * 'px-4 py-2',\n * { 'text-bold': true, invisible: false },\n * ['hover:bg-red-500', ['active:scale-95']],\n * ],\n * out\n * )\n * // out => ['px-4','py-2','text-bold','hover:bg-red-500','active:scale-95']\n * ```\n */\nfunction flattenClasses(input: ClassValue | undefined, tokens: string[]): void {\n if (input === undefined || input === null) return\n\n // primitive values\n if (typeof input === 'string' || typeof input === 'number' || typeof input === 'boolean') {\n const parts = String(input).split(/\\s+/)\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]\n if (part) tokens.push(part)\n }\n return\n }\n\n // arrays of ClassValue\n if (Array.isArray(input)) {\n for (let i = 0; i < input.length; i++) {\n flattenClasses(input[i], tokens)\n }\n return\n }\n\n // object dictionary `{ className: true }`\n for (const key in input) {\n if (Object.hasOwn(input, key) && input[key]) {\n tokens.push(key)\n }\n }\n}\n\n/**\n * Creates a Class Variance Authority (CVA) function for composing class names\n * based on a base string, variants, defaultVariants, and compoundVariants.\n *\n * Supports two call signatures:\n * - `cva(base: string, options: VariantsOptions<TVariants>)`\n * - `cva(options: VariantsOptions<TVariants> & { base: string })`\n *\n * @template TVariants\n * A record mapping variant keys to a record of allowed values and their classes.\n *\n * @param {string | (VariantsOptions<TVariants> & { base?: string })} baseOrOptions\n * Either the base class string, or an options object including `base`.\n * @param {VariantsOptions<TVariants>} [maybeOptions]\n * The options object when using the two-arg signature.\n *\n * @returns {(props?: CvaProps<TVariants>) => string}\n * A function that, given variant props and optional `class`/`className`, returns\n * the deduplicated, memoized className string.\n *\n * @example\n * ```ts\n * const button = cva('btn px-4 py-2', {\n * variants: {\n * intent: { primary: 'bg-blue-500 text-white', danger: 'bg-red-500' },\n * size: { sm: 'text-sm', lg: 'text-lg' },\n * },\n * defaultVariants: { intent: 'primary', size: 'sm' },\n * compoundVariants: [\n * {\n * intent: ['primary','danger'],\n * size: 'lg',\n * className: 'uppercase',\n * },\n * ],\n * })\n *\n * // uses defaults + compound match\n * button()\n * // => 'btn px-4 py-2 bg-blue-500 text-white text-sm uppercase'\n *\n * // overrides size + adds custom classes\n * button({ size: 'lg', class: ['mt-4','shadow'] })\n * // => 'btn px-4 py-2 bg-blue-500 text-white text-lg uppercase mt-4 shadow'\n * ```\n */\nexport function cva<TVariants extends Record<string, Record<string, string | string[]>>>(\n baseOrOptions: string | (VariantsOptions<TVariants> & { base?: string }),\n maybeOptions?: VariantsOptions<TVariants>,\n): (props?: CvaProps<TVariants>) => string {\n // Normalize the two possible call signatures\n const config = typeof baseOrOptions === 'string' ? { base: baseOrOptions, ...maybeOptions } : baseOrOptions\n\n const { base = '', variants, defaultVariants = {}, compoundVariants = [] } = config\n\n // Memoization cache keyed by serialized props\n const cache = new Map<string, string>()\n\n return (props: CvaProps<TVariants> = {} as CvaProps<TVariants>): string => {\n // 1) Memo lookup\n const cacheKey = getCacheKey(props)\n const memo = cache.get(cacheKey)\n if (memo) return memo\n\n const tokens: string[] = []\n const seen = new Set<string>()\n\n // 2) Base classes\n flattenClasses(base, tokens)\n\n // 3) Merge defaults + incoming props\n const merged = { ...defaultVariants, ...props } as Record<keyof TVariants, ClassValue>\n\n // 4) Apply variant-specific classes\n for (const variantName in variants) {\n const v = merged[variantName]\n if (v == null || v === 'unset') continue\n const cls = variants[variantName][String(v)]\n flattenClasses(cls, tokens)\n }\n\n // 5) Apply compoundVariants when all conditions match\n for (let i = 0; i < compoundVariants.length; i++) {\n const cv = compoundVariants[i as number]\n let match = true\n\n for (const key in cv) {\n if (key === 'class' || key === 'className') continue\n\n const cond = cv[key as keyof typeof cv]\n const actual = merged[key as keyof typeof merged]\n\n // array- or single-value condition\n if (Array.isArray(cond) && actual) {\n if (!cond.includes(actual.toString())) {\n match = false\n break\n }\n } else if (actual !== cond) {\n match = false\n break\n }\n }\n if (!match) continue\n\n flattenClasses(cv.class, tokens)\n flattenClasses(cv.className, tokens)\n }\n\n // 6) Finally append any `className` or `class` from props\n flattenClasses(props.className, tokens)\n flattenClasses(props.class, tokens)\n\n // 7) Deduplicate & join\n for (let i = 0; i < tokens.length; i++) {\n seen.add(tokens[i as number])\n }\n const result = Array.from(seen).join(' ')\n\n cache.set(cacheKey, result)\n return result\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;EAoBA,SAAS,EACPA,EAAAA,CAIA,IAFM,EAAU,OAAO,QAAQ,EAAA,CAE3B,EAAM,GACV,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,OAAQ,IAAK,CACvC,GAAM,CAAC,EAAG,EAAE,CAAG,EAAQ,GAMvB,AALI,MAAM,QAAQ,EAAA,CAChB,IAAA,EAAU,EAAE,IAAI,EAAE,IAAI,OAAA,CAAQ,KAAK,IAAA,CAAK,GAExC,IAAA,EAAU,EAAE,GAAG,OAAO,EAAA,CAAA,EAEpB,EAAI,EAAQ,OAAS,IAAG,GAAO,IACpC,CACD,OAAO,CACR;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BD,SAAS,EAAeC,EAA+BC,EAAAA,CACjD,MAAiC,KAGrC,WAAW,GAAU,iBAAmB,GAAU,iBAAmB,GAAU,UAAW,CACxF,IAAM,EAAQ,OAAO,EAAA,CAAO,MAAM,MAAA,CAClC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,OAAQ,IAAK,CACrC,IAAM,EAAO,EAAM,GACnB,AAAI,GAAM,EAAO,KAAK,EAAA,AACvB,CACD,MACD,CAGD,GAAI,MAAM,QAAQ,EAAA,CAAQ,CACxB,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,OAAQ,IAChC,EAAe,EAAM,GAAI,EAAA,CAE3B,MACD,CAGD,IAAK,IAAM,KAAO,EAChB,AAAI,OAAO,OAAO,EAAO,EAAA,EAAQ,EAAM,IACrC,EAAO,KAAK,EAAA,AAbf,CAgBF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgDD,SAAgB,EACdC,EACAC,EAAAA,CAQA,IALM,SAAgB,GAAkB,SAAW,CAAE,KAAM,EAAe,GAAG,CAAc,EAAG,EAExF,CAAE,OAAO,GAAI,WAAU,kBAAkB,CAAE,EAAE,mBAAmB,CAAE,EAAE,CAAG,EAGvE,EAAQ,IAAI,IAElB,MAAO,CAACJ,EAA6B,CAAE,IAAA,CAGrC,IADM,EAAW,EAAY,EAAA,CACvB,EAAO,EAAM,IAAI,EAAA,CACvB,GAAI,EAAM,OAAO,EAGjB,IADME,EAAmB,CAAE,EACrB,EAAO,IAAI,IAGjB,EAAe,EAAM,EAAA,CAGrB,IAAM,EAAS,CAAE,GAAG,EAAiB,GAAG,CAAO,EAG/C,IAAK,IAAM,KAAe,EAAU,CAClC,IAAM,EAAI,EAAO,GACjB,GAAI,GAAK,MAAQ,IAAM,QAAS,SAChC,IAAM,EAAM,EAAS,GAAa,OAAO,EAAA,EACzC,EAAe,EAAK,EAAA,AACrB,CAGD,IAAK,IAAI,EAAI,EAAG,EAAI,EAAiB,OAAQ,IAAK,CAEhD,IADM,EAAK,EAAiB,GACxB,GAAQ,EAEZ,IAAK,IAAM,KAAO,EAAI,CACpB,GAAI,IAAQ,SAAW,IAAQ,YAAa,SAG5C,IADM,EAAO,EAAG,GACV,EAAS,EAAO,GAGtB,GAAI,MAAM,QAAQ,EAAA,EAAS,OACpB,EAAK,SAAS,EAAO,UAAA,CAAA,CAAa,CACrC,GAAQ,EACR,KACD,UACQ,IAAW,EAAM,CAC1B,GAAQ,EACR,KACD,CACF,CACI,IAEL,EAAe,EAAG,MAAO,EAAA,CACzB,EAAe,EAAG,UAAW,EAAA,CAC9B,CAID,AADA,EAAe,EAAM,UAAW,EAAA,CAChC,EAAe,EAAM,MAAO,EAAA,CAG5B,IAAK,IAAI,EAAI,EAAG,EAAI,EAAO,OAAQ,IACjC,EAAK,IAAI,EAAO,GAAA,CAElB,IAAM,EAAS,MAAM,KAAK,EAAA,CAAM,KAAK,IAAA,CAGrC,MADA,GAAM,IAAI,EAAU,EAAA,CACb,CACR,CACF"} | ||
| {"version":3,"file":"index.js","names":["getCacheKey","props","entries","Object","key","i","length","k","v","Array","isArray","map","String","join","flattenClasses","input","tokens","undefined","parts","split","part","push","hasOwn","cva","baseOrOptions","maybeOptions","config","base","variants","defaultVariants","compoundVariants","cache","Map","cacheKey","memo","get","seen","Set","merged","variantName","cls","cv","match","cond","actual","includes","toString","class","className","add","result","from","set"],"sources":["../src/variants.ts"],"sourcesContent":["import type { ClassValue, CvaProps, VariantsOptions } from './variants.types'\n\n/**\n * Build a stable cache key by serializing props entries in sorted order.\n *\n * @template TVariants\n * The mapping of variant names to their allowed string/string[] classes.\n *\n * @param {CvaProps<TVariants>} props\n * The props object passed into the CVA function (variant selections + class/className).\n *\n * @returns {string}\n * A deterministic string key used for memoization.\n *\n * @example\n * ```ts\n * getCacheKey({ intent: 'primary', size: ['sm', 'md'], className: 'mt-4' })\n * // => \"className:mt-4|intent:primary|size:[sm,md]\"\n * ```\n */\nfunction getCacheKey<TVariants extends Record<string, Record<string, string | string[]>>>(\n props: CvaProps<TVariants>,\n): string {\n const entries = Object.entries(props) as [string, ClassValue][]\n\n let key = ''\n for (let i = 0; i < entries.length; i++) {\n const [k, v] = entries[i]\n if (Array.isArray(v)) {\n key += `${k}:[${v.map(String).join(',')}]`\n } else {\n key += `${k}:${String(v)}`\n }\n if (i < entries.length - 1) key += '|'\n }\n return key\n}\n\n/**\n * Recursively flattens any supported `ClassValue` into individual CSS tokens.\n *\n * Supports:\n * - primitive strings/numbers/booleans (whitespace-split)\n * - nested arrays of `ClassValue`\n * - dictionaries `{ className: boolean }` for conditional classes\n *\n * @param {ClassValue | undefined} input\n * The value to flatten into tokens.\n * @param {string[]} tokens\n * The accumulator array receiving each CSS token.\n *\n * @example\n * ```ts\n * const out: string[] = []\n * flattenClasses(\n * [\n * 'px-4 py-2',\n * { 'text-bold': true, invisible: false },\n * ['hover:bg-red-500', ['active:scale-95']],\n * ],\n * out\n * )\n * // out => ['px-4','py-2','text-bold','hover:bg-red-500','active:scale-95']\n * ```\n */\nfunction flattenClasses(input: ClassValue | undefined, tokens: string[]): void {\n if (input === undefined || input === null) return\n\n // primitive values\n if (typeof input === 'string' || typeof input === 'number' || typeof input === 'boolean') {\n const parts = String(input).split(/\\s+/)\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]\n if (part) tokens.push(part)\n }\n return\n }\n\n // arrays of ClassValue\n if (Array.isArray(input)) {\n for (let i = 0; i < input.length; i++) {\n flattenClasses(input[i], tokens)\n }\n return\n }\n\n // object dictionary `{ className: true }`\n for (const key in input) {\n if (Object.hasOwn(input, key) && input[key]) {\n tokens.push(key)\n }\n }\n}\n\n/**\n * Creates a Class Variance Authority (CVA) function for composing class names\n * based on a base string, variants, defaultVariants, and compoundVariants.\n *\n * Supports two call signatures:\n * - `cva(base: string, options: VariantsOptions<TVariants>)`\n * - `cva(options: VariantsOptions<TVariants> & { base: string })`\n *\n * @template TVariants\n * A record mapping variant keys to a record of allowed values and their classes.\n *\n * @param {string | (VariantsOptions<TVariants> & { base?: string })} baseOrOptions\n * Either the base class string, or an options object including `base`.\n * @param {VariantsOptions<TVariants>} [maybeOptions]\n * The options object when using the two-arg signature.\n *\n * @returns {(props?: CvaProps<TVariants>) => string}\n * A function that, given variant props and optional `class`/`className`, returns\n * the deduplicated, memoized className string.\n *\n * @example\n * ```ts\n * const button = cva('btn px-4 py-2', {\n * variants: {\n * intent: { primary: 'bg-blue-500 text-white', danger: 'bg-red-500' },\n * size: { sm: 'text-sm', lg: 'text-lg' },\n * },\n * defaultVariants: { intent: 'primary', size: 'sm' },\n * compoundVariants: [\n * {\n * intent: ['primary','danger'],\n * size: 'lg',\n * className: 'uppercase',\n * },\n * ],\n * })\n *\n * // uses defaults + compound match\n * button()\n * // => 'btn px-4 py-2 bg-blue-500 text-white text-sm uppercase'\n *\n * // overrides size + adds custom classes\n * button({ size: 'lg', class: ['mt-4','shadow'] })\n * // => 'btn px-4 py-2 bg-blue-500 text-white text-lg uppercase mt-4 shadow'\n * ```\n */\nexport function cva<TVariants extends Record<string, Record<string, string | string[]>>>(\n baseOrOptions: string | (VariantsOptions<TVariants> & { base?: string }),\n maybeOptions?: VariantsOptions<TVariants>,\n): (props?: CvaProps<TVariants>) => string {\n // Normalize the two possible call signatures\n const config = typeof baseOrOptions === 'string' ? { base: baseOrOptions, ...maybeOptions } : baseOrOptions\n\n const { base = '', variants, defaultVariants = {}, compoundVariants = [] } = config\n\n // Memoization cache keyed by serialized props\n const cache = new Map<string, string>()\n\n return (props: CvaProps<TVariants> = {} as CvaProps<TVariants>): string => {\n // 1) Memo lookup\n const cacheKey = getCacheKey(props)\n const memo = cache.get(cacheKey)\n if (memo) return memo\n\n const tokens: string[] = []\n const seen = new Set<string>()\n\n // 2) Base classes\n flattenClasses(base, tokens)\n\n // 3) Merge defaults + incoming props\n const merged = { ...defaultVariants, ...props } as Record<keyof TVariants, ClassValue>\n\n // 4) Apply variant-specific classes\n for (const variantName in variants) {\n const v = merged[variantName]\n if (v == null || v === 'unset') continue\n const cls = variants[variantName][String(v)]\n flattenClasses(cls, tokens)\n }\n\n // 5) Apply compoundVariants when all conditions match\n for (let i = 0; i < compoundVariants.length; i++) {\n const cv = compoundVariants[i as number]\n let match = true\n\n for (const key in cv) {\n if (key === 'class' || key === 'className') continue\n\n const cond = cv[key as keyof typeof cv]\n const actual = merged[key as keyof typeof merged]\n\n // array- or single-value condition\n if (Array.isArray(cond) && actual) {\n if (!cond.includes(actual.toString())) {\n match = false\n break\n }\n } else if (actual !== cond) {\n match = false\n break\n }\n }\n if (!match) continue\n\n flattenClasses(cv.class, tokens)\n flattenClasses(cv.className, tokens)\n }\n\n // 6) Finally append any `className` or `class` from props\n flattenClasses(props.className, tokens)\n flattenClasses(props.class, tokens)\n\n // 7) Deduplicate & join\n for (let i = 0; i < tokens.length; i++) {\n seen.add(tokens[i as number])\n }\n const result = Array.from(seen).join(' ')\n\n cache.set(cacheKey, result)\n return result\n }\n}\n"],"mappings":"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"} |
+32
-32
| { | ||
| "name": "@gentleduck/variants", | ||
| "author": "wilddcuk", | ||
| "bugs": { | ||
| "url": "https://github.com/gentleduck/ui/issues" | ||
| }, | ||
| "description": "A package for creating variants of components, providing a simple and efficient way to create variants of components.", | ||
| "types": "./dist/index.d.ts", | ||
| "main": "./dist/index.js", | ||
| "private": false, | ||
| "version": "0.1.12", | ||
| "type": "module", | ||
| "devDependencies": { | ||
| "@changesets/cli": "^2.27.7", | ||
| "clsx": "^1.2.1", | ||
| "cva": "1.0.0-beta.3", | ||
| "husky": "^9.1.7", | ||
| "tsdown": "^0.11", | ||
| "@gentleduck/tsdown-config": "0.0.1", | ||
| "@gentleduck/typescript-config": "0.1.0" | ||
| }, | ||
| "engines": { | ||
| "node": ">=22.0.0" | ||
| }, | ||
| "exports": { | ||
| ".": "./dist/index.js" | ||
| }, | ||
| "files": [ | ||
| "dist" | ||
| ], | ||
| "exports": { | ||
| ".": "./dist/index.js" | ||
| }, | ||
| "homepage": "https://github.com/gentleduck/ui/tree/master/packages/duck-variants#readme", | ||
| "keywords": [ | ||
@@ -33,4 +44,9 @@ "class-variants", | ||
| ], | ||
| "author": "wilddcuk", | ||
| "license": "MIT", | ||
| "main": "./dist/index.js", | ||
| "name": "@gentleduck/variants", | ||
| "peerDependencies": { | ||
| "typescript": "^5.8.3" | ||
| }, | ||
| "private": false, | ||
| "repository": { | ||
@@ -40,27 +56,11 @@ "type": "git", | ||
| }, | ||
| "bugs": { | ||
| "url": "https://github.com/gentleduck/ui/issues" | ||
| }, | ||
| "homepage": "https://github.com/gentleduck/ui/tree/master/packages/duck-variants#readme", | ||
| "devDependencies": { | ||
| "@changesets/cli": "^2.27.7", | ||
| "clsx": "^1.2.1", | ||
| "cva": "1.0.0-beta.3", | ||
| "husky": "^9.1.7", | ||
| "tsdown": "^0.11", | ||
| "@gentleduck/typescript-config": "0.1.0", | ||
| "@gentleduck/tsdown-config": "0.0.1" | ||
| }, | ||
| "peerDependencies": { | ||
| "typescript": "^5.8.3" | ||
| }, | ||
| "engines": { | ||
| "node": ">=22.0.0" | ||
| }, | ||
| "type": "module", | ||
| "types": "./dist/index.d.ts", | ||
| "version": "0.1.13", | ||
| "scripts": { | ||
| "build": "tsdown", | ||
| "ci": "pnpm run lint && pnpm run format && pnpm run build", | ||
| "clean": "git clean -xdf .cache .turbo node_modules dist", | ||
| "build": "tsdown", | ||
| "format": "biome format --write ./", | ||
| "lint": "biome lint --write ./", | ||
| "format": "biome format --write ./", | ||
| "ci": "pnpm run lint && pnpm run format && pnpm run build", | ||
| "release": "changeset version", | ||
@@ -67,0 +67,0 @@ "test": "vitest" |
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
27370
-6.74%201
-27.7%