Skip to content

Commit

Permalink
fix: correct variant prop type merging logic
Browse files Browse the repository at this point in the history
- update Compose interface to use new MergeVariantProp utility type
- clean up some other types
  • Loading branch information
lunelson committed Apr 7, 2024
1 parent 5e4b5b8 commit b61bf93
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 17 deletions.
36 changes: 36 additions & 0 deletions packages/cva/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,42 @@ describe("compose", () => {
"shadow-md gap-3 adhoc-class",
);
});
test("should correctly merge variant props", () => {
const A = cva({
base: "A",
variants: {
intent: {
primary: "primary-A",
},
},
});

const B = cva({
base: "B",
variants: {
intent: {
primary: "primary-B",
secondary: "secondary-B",
},
},
});

const C = cva({
base: "C",
variants: {
intent: {
tertiary: "tertiary-C",
},
},
});

const composedABC = compose(A, B, C);

expectTypeOf(composedABC).toBeFunction();
expectTypeOf(composedABC).parameter(0).toMatchTypeOf<{
intent?: "primary" | "secondary" | "tertiary";
}>();
});
});

describe("cva", () => {
Expand Down
42 changes: 25 additions & 17 deletions packages/cva/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,36 @@ type ClassValue =
| null
| boolean
| undefined;
type ClassDictionary = Record<string, any>;
type ClassDictionary = Record<string, unknown>;
type ClassArray = ClassValue[];

/* Utils
---------------------------------- */

type OmitUndefined<T> = T extends undefined ? never : T;
type StringToBoolean<T> = T extends "true" | "false" ? boolean : T;
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I,
) => void
? I
: never;
type MergeVariantProps<Types extends object[]> = Types extends [
infer First,
...infer Rest,
]
? First extends object
? Rest extends object[]
? {
[K in
| keyof First
| keyof MergeVariantProps<Rest>]: K extends keyof First
? K extends keyof MergeVariantProps<Rest>
? First[K] | Exclude<MergeVariantProps<Rest>[K], First[K]>
: First[K]
: K extends keyof MergeVariantProps<Rest>
? MergeVariantProps<Rest>[K]
: never;
}
: never
: never
: object;

export type VariantProps<Component extends (...args: any) => any> = Omit<
export type VariantProps<Component extends (...args: any[]) => unknown> = Omit<
OmitUndefined<Parameters<Component>[0]>,
"class" | "className"
>;
Expand All @@ -42,16 +57,9 @@ export type VariantProps<Component extends (...args: any) => any> = Omit<

export interface Compose {
<T extends ReturnType<CVA>[]>(
...components: [...T]
...components: T
): (
props?: (
| UnionToIntersection<
{
[K in keyof T]: VariantProps<T[K]>;
}[number]
>
| undefined
) &
props?: Partial<MergeVariantProps<{ [K in keyof T]: VariantProps<T[K]> }>> &
CVAClassProp,
) => string;
}
Expand Down Expand Up @@ -145,7 +153,7 @@ export interface DefineConfig {
/* Exports
============================================ */

const falsyToString = <T extends unknown>(value: T) =>
const falsyToString = (value: unknown) =>
typeof value === "boolean" ? `${value}` : value === 0 ? "0" : value;

export const defineConfig: DefineConfig = (options) => {
Expand Down

0 comments on commit b61bf93

Please sign in to comment.