Skip to content

Commit d57c9b7

Browse files
committed
typescript should throw an error when passed compoundVariants or defaultVariants with non-existing variants
1 parent 21d20ab commit d57c9b7

File tree

5 files changed

+53
-17
lines changed

5 files changed

+53
-17
lines changed

.changeset/smooth-humans-appear.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'react-tailwind-variants': patch
3+
---
4+
5+
typescript should throw an error when passed compoundVariants or defaultVariants with non-existing variants

src/react.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
type VariantsSchema,
1515
type VariantOptions,
1616
variants,
17+
Simplify,
1718
} from './variants';
1819

1920
const StyledComponentConfigKey = '$$tailwindVariantsConfig';
@@ -30,7 +31,7 @@ export type StyledComponent<
3031
export function variantProps<
3132
C extends VariantsConfig<V>,
3233
V extends VariantsSchema = NonNullable<C['variants']>
33-
>(config: C) {
34+
>(config: Simplify<C>) {
3435
const variantsHandler = variants(config);
3536

3637
type Props = VariantOptions<C> & {
@@ -45,7 +46,7 @@ export function variantProps<
4546
if (
4647
!('variants' in config) ||
4748
!config.variants ||
48-
!(prop in config.variants)
49+
!(prop in (config.variants as V))
4950
) {
5051
result[prop] = props[prop];
5152
}
@@ -71,7 +72,7 @@ export function styled<
7172
T extends ElementType,
7273
C extends VariantsConfig<V>,
7374
V extends VariantsSchema = NonNullable<C['variants']>
74-
>(baseType: T, config: C) {
75+
>(baseType: T, config: Simplify<C>) {
7576
const propsHandler = variantProps(config);
7677

7778
type ConfigVariants = VariantOptions<C>;

src/variants.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type OmitByValue<T, Value> = {
1212
[P in keyof T as T[P] extends Value ? never : P]: T[P];
1313
};
1414
type StringToBoolean<T> = T extends 'true' | 'false' ? boolean : T;
15+
export type Simplify<T> = { [KeyType in keyof T]: T[KeyType] };
1516

1617
export type ClassNameValue = string | null | undefined | ClassNameValue[];
1718

@@ -31,12 +32,14 @@ export type ClassNameValue = string | null | undefined | ClassNameValue[];
3132
*/
3233
export type VariantsSchema = Record<string, Record<string, ClassNameValue>>;
3334

34-
export interface VariantsConfig<V extends VariantsSchema> {
35+
export type VariantsConfig<V extends VariantsSchema> = {
3536
base?: ClassNameValue;
3637
variants?: V;
37-
defaultVariants?: keyof V extends never ? never : Partial<Variants<V>>;
38-
compoundVariants?: keyof V extends never ? never : CompoundVariant<V>[];
39-
}
38+
defaultVariants?: keyof V extends never
39+
? Record<string, never>
40+
: Partial<Variants<V>>;
41+
compoundVariants?: keyof V extends never ? never[] : CompoundVariant<V>[];
42+
};
4043

4144
/**
4245
* Rules for class names that are applied for certain variant combinations.
@@ -109,7 +112,7 @@ type VariantsHandlerFn<P> = PickRequiredKeys<P> extends never
109112
export function variants<
110113
C extends VariantsConfig<V>,
111114
V extends VariantsSchema = NonNullable<C['variants']>
112-
>(config: C) {
115+
>(config: Simplify<C>) {
113116
const { base, variants, compoundVariants, defaultVariants } = config;
114117

115118
if (!('variants' in config) || !config.variants) {
@@ -118,7 +121,7 @@ export function variants<
118121
}
119122

120123
function isBooleanVariant(name: keyof V) {
121-
const variant = variants?.[name];
124+
const variant = (variants as V)?.[name];
122125
return variant && ('false' in variant || 'true' in variant);
123126
}
124127

test/react.spec.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,19 @@ styled('div', {
144144
},
145145
});
146146

147+
styled('div', {
148+
variants: {
149+
color: {
150+
neutral: 'grey',
151+
accent: 'hotpink',
152+
},
153+
},
154+
defaultVariants: {
155+
// @ts-expect-error
156+
test: 'invalid',
157+
},
158+
});
159+
147160
styled('div', {
148161
variants: {
149162
color: {

test/variants.spec.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ describe('variants', () => {
99
describe('without variants', () => {
1010
test('with compoundVariants', () => {
1111
const withoutBaseWithCompoundVariants = variants({
12-
// @ts-expect-error
13-
compoundVariants: [],
12+
compoundVariants: [
13+
// @ts-expect-error
14+
{
15+
variants: { color: 'primary' },
16+
className: 'text-primary-500',
17+
},
18+
],
1419
});
1520
expect(
1621
withoutBaseWithCompoundVariants({ className: 'bg-blue-500 foobar' })
@@ -23,8 +28,10 @@ describe('variants', () => {
2328

2429
test('with defaultVariants', () => {
2530
const withoutBaseWithDefaultVariants = variants({
26-
// @ts-expect-error
27-
defaultVariants: {},
31+
defaultVariants: {
32+
// @ts-expect-error
33+
color: 'primary',
34+
},
2835
});
2936
expect(
3037
withoutBaseWithDefaultVariants({ className: 'bg-blue-500 foobar' })
@@ -496,8 +503,13 @@ describe('variants', () => {
496503
test('with compoundVariants', () => {
497504
const withoutBaseWithCompoundVariants = variants({
498505
base: 'text-white bg-black',
499-
// @ts-expect-error
500-
compoundVariants: [],
506+
compoundVariants: [
507+
// @ts-expect-error
508+
{
509+
variants: { color: 'primary' },
510+
className: 'text-primary-500',
511+
},
512+
],
501513
});
502514
expect(
503515
withoutBaseWithCompoundVariants({ className: 'bg-blue-500 foobar' })
@@ -517,8 +529,10 @@ describe('variants', () => {
517529
test('with defaultVariants', () => {
518530
const withoutBaseWithDefaultVariants = variants({
519531
base: 'text-white bg-black',
520-
// @ts-expect-error
521-
defaultVariants: {},
532+
defaultVariants: {
533+
// @ts-expect-error
534+
color: 'primary',
535+
},
522536
});
523537
expect(
524538
withoutBaseWithDefaultVariants({ className: 'bg-blue-500 foobar' })

0 commit comments

Comments
 (0)