classname-variants 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,6 +6,8 @@ The library is framework agnostic and can be used with any kind of CSS flavor.
6
6
 
7
7
  It is especially useful though if used with [Tailwind](https://tailwindcss.com/) and React, as it provides some [dedicated helpers](#React) and even allows for a _styled-components_ like API, but with [class names instead of styles](#bonus-styled-components-but-with-class-names-)!
8
8
 
9
+ [![Edit classname-variants/react](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/classname-variants-react-3bzjl?fontsize=14&hidenavigation=1&theme=dark)
10
+
9
11
  # Basics
10
12
 
11
13
  Let's aussume we want to build a button component with Tailwind CSS that comes in different sizes and colors.
@@ -136,7 +138,8 @@ const buttonProps = variantProps({
136
138
  This way a compontents' props (or part of them) can be directly spread into the target element. All variant-related props are used to construct the `className` property while all other props are passed through verbatim:
137
139
 
138
140
  ```tsx
139
- type Props = SX.IntrinsicElements["button"] & VariantProps<typeof buttonProps>;
141
+ type Props = SX.IntrinsicElements["button"] &
142
+ VariantPropsOf<typeof buttonProps>;
140
143
 
141
144
  function Button(props: Props) {
142
145
  return <button {...buttonProps(props)} />;
@@ -168,6 +171,50 @@ const Button = styled("button", {
168
171
  });
169
172
  ```
170
173
 
174
+ You can also style other custom React components as long as the accept a `className` prop.
175
+
176
+ # Tailwind IntelliSense
177
+
178
+ In order to get auto-completion for the CSS classes themselves, you can use the [Tailwind CSS IntelliSense](https://github.com/tailwindlabs/tailwindcss-intellisense) plugin for VS Code. In order to make it recognize the strings inside your variants config, you have to somehow mark them and configure the plugin accordingly.
179
+
180
+ One way of doing so is by using tagged template literals:
181
+
182
+ ```ts
183
+ const tw = String.raw;
184
+
185
+ const button = variants({
186
+ base: tw`px-5 py-2 text-white`,
187
+ variants: {
188
+ color: {
189
+ neutral: tw`bg-slate-500 hover:bg-slate-400`,
190
+ accent: tw`bg-teal-500 hover:bg-teal-400`,
191
+ },
192
+ },
193
+ });
194
+ ```
195
+
196
+ ```ts
197
+ const tw = String.raw;
198
+
199
+ const button = variants({
200
+ base: tw.px(5).py(2).text.white,
201
+ variants: {
202
+ color: {
203
+ neutral: tw.bg.slate(500).hover(tw.bg.slate.400),
204
+ accent: tw.bg.teal(500).hover(tw.bg.teal(400)),
205
+ },
206
+ },
207
+ });
208
+ ```
209
+
210
+ You can then add the following line to your `settings.json`:
211
+
212
+ ```
213
+ "tailwindCSS.experimental.classRegex": ["tw`(.+?)`"]
214
+ ```
215
+
216
+ In order to get type coverage even for your Tailwind classes you can use a tool like [tailwind-ts](https://github.com/mathieutu/tailwind-ts).
217
+
171
218
  # License
172
219
 
173
220
  MIT
package/example.tsx CHANGED
@@ -2,38 +2,60 @@ import React from "react";
2
2
  import ReactDOM from "react-dom";
3
3
  import { styled } from "./src/react";
4
4
 
5
+ function CustomComponent({
6
+ title,
7
+ ...props
8
+ }: {
9
+ className?: string;
10
+ title: string;
11
+ }) {
12
+ return <div {...props}>{title}</div>;
13
+ }
14
+
15
+ const Card = styled(CustomComponent, {
16
+ base: "bg-white p-4 border-2 rounded-lg",
17
+ variants: {},
18
+ });
19
+
5
20
  const Button = styled("button", {
6
- base: "rounded-md px-5 py-2 text-white",
21
+ base: "px-5 py-2 text-white disabled:bg-gray-400 disabled:text-gray-300",
7
22
  variants: {
8
23
  color: {
9
- neutral: "bg-gray-500",
10
- accent: "bg-teal-500",
24
+ neutral: "bg-slate-500 hover:bg-slate-400",
25
+ accent: "bg-teal-500 hover:bg-teal-400",
11
26
  },
12
27
  outlined: {
13
28
  true: "border-2",
14
29
  },
30
+ rounded: {
31
+ true: "rounded-full",
32
+ false: "rounded-sm",
33
+ },
15
34
  },
16
35
  compoundVariants: [
17
36
  {
18
- variants: {
19
- color: "accent",
20
- outlined: true,
21
- },
22
- className: "border-teal-800",
37
+ variants: { color: "accent", outlined: true },
38
+ className: "border-teal-600",
23
39
  },
24
40
  ],
41
+ defaultVariants: {
42
+ color: "neutral",
43
+ },
25
44
  });
26
45
 
27
46
  function App() {
28
47
  return (
29
- <>
30
- <Button color="neutral" onClick={console.log}>
31
- Neutral
32
- </Button>
33
- <Button color="accent" outlined onClick={console.log}>
48
+ <div className="flex justify-center items-center pt-8 gap-4 flex-wrap">
49
+ <Button onClick={console.log}>Accent</Button>
50
+ <Button rounded>Neutral + Rounded</Button>
51
+ <Button color="accent" outlined>
34
52
  Accent + Outlined
35
53
  </Button>
36
- </>
54
+ <Button color="accent" disabled>
55
+ Disabled
56
+ </Button>
57
+ <Card title="Hello" />
58
+ </div>
37
59
  );
38
60
  }
39
61
 
package/lib/index.d.ts CHANGED
@@ -1,18 +1,105 @@
1
- export declare type VariantDefinitions = Record<string, string>;
2
- export declare type Variants = Record<string, VariantDefinitions>;
3
- export declare type VariantsConfig<V extends Variants> = {
1
+ /**
2
+ * Definition of the available variants and their options.
3
+ * @example
4
+ * {
5
+ * color: {
6
+ * white: "bg-white"
7
+ * green: "bg-green-500",
8
+ * },
9
+ * size: {
10
+ * small: "text-xs",
11
+ * large: "text-lg"
12
+ * }
13
+ * }
14
+ */
15
+ export declare type Variants = Record<string, Record<string, string>>;
16
+ /**
17
+ * Configuration including defaults and compound variants.
18
+ */
19
+ export interface VariantsConfig<V extends Variants> {
4
20
  base?: string;
5
- variants?: V;
21
+ variants: V;
6
22
  compoundVariants?: CompoundVariant<V>[];
7
- defaultVariants?: VariantSelection<V>;
8
- };
9
- export declare type CompoundVariant<V extends Variants> = {
10
- variants: VariantSelection<V>;
23
+ defaultVariants?: Partial<OptionsOf<V>>;
24
+ }
25
+ /**
26
+ * Rules for class names that are applied for certain variant combinations.
27
+ */
28
+ export interface CompoundVariant<V extends Variants> {
29
+ variants: Partial<OptionsOf<V>>;
11
30
  className: string;
31
+ }
32
+ /**
33
+ * Only the boolean variants, i.e. ones that have "true" or "false" as options.
34
+ */
35
+ declare type BooleanVariants<V extends Variants> = {
36
+ [K in keyof V as V[K] extends {
37
+ true: any;
38
+ } | {
39
+ false: any;
40
+ } ? K : never]: V[K];
41
+ };
42
+ /**
43
+ * Only the variants for which a default options is set.
44
+ */
45
+ declare type DefaultVariants<C extends VariantsConfig<V>, V extends Variants = C["variants"]> = {
46
+ [K in keyof V as K extends keyof C["defaultVariants"] ? K : never]: C["variants"][K];
47
+ };
48
+ /**
49
+ * Names of all optional variants, i.e. booleans or ones with default options.
50
+ */
51
+ declare type OptionalVariantNames<C extends VariantsConfig<V>, V extends Variants = C["variants"]> = keyof BooleanVariants<V> | keyof DefaultVariants<C>;
52
+ /**
53
+ * Possible options for all the optional variants.
54
+ *
55
+ * @example
56
+ * {
57
+ * color?: "white" | "green",
58
+ * rounded?: boolean | undefined
59
+ * }
60
+ */
61
+ declare type OptionalOptions<C extends VariantsConfig<V>, V extends Variants = C["variants"]> = {
62
+ [K in keyof V as K extends OptionalVariantNames<C> ? K : never]?: OptionsOf<V>[K];
63
+ };
64
+ /**
65
+ * Possible options for all required variants.
66
+ *
67
+ * @example {
68
+ * size: "small" | "large"
69
+ * }
70
+ */
71
+ declare type RequiredOptions<C extends VariantsConfig<V>, V extends Variants = C["variants"]> = {
72
+ [K in keyof V as K extends OptionalVariantNames<C> ? never : K]: OptionsOf<V>[K];
73
+ };
74
+ /**
75
+ * Utility type to extract the possible options.
76
+ * Converts "true" | "false" options into booleans.
77
+ *
78
+ * @example
79
+ * OptionsOf<{
80
+ * size: { small: "text-xs"; large: "text-lg" };
81
+ * rounded: { true: "rounded-full" }
82
+ * }>
83
+ * ==>
84
+ * {
85
+ * size: "text-xs" | "text-lg";
86
+ * rounded: boolean;
87
+ * }
88
+ */
89
+ declare type OptionsOf<V extends Variants> = {
90
+ [K in keyof V]: keyof V[K] extends "true" | "false" ? boolean : keyof V[K];
12
91
  };
13
- export declare type VariantSelection<V extends Variants> = {
14
- [VariantName in keyof V]?: StringToBool<keyof V[VariantName]>;
92
+ /**
93
+ * Extracts the possible options.
94
+ */
95
+ export declare type VariantOptions<C extends VariantsConfig<V>, V extends Variants = C["variants"]> = RequiredOptions<C> & OptionalOptions<C>;
96
+ /**
97
+ * Without this conversion step, defaultVariants and compoundVariants will
98
+ * allow extra keys, i.e. non-existent variants.
99
+ * See https://github.com/sindresorhus/type-fest/blob/main/source/simplify.d.ts
100
+ */
101
+ export declare type Simplify<T> = {
102
+ [K in keyof T]: T[K];
15
103
  };
16
- declare type StringToBool<T> = T extends "true" | "false" ? boolean : T;
17
- export declare function variants<V extends Variants>({ base, variants, compoundVariants, defaultVariants, }: VariantsConfig<V>): (props: VariantSelection<V>) => string;
104
+ export declare function variants<C extends VariantsConfig<V>, V extends Variants = C["variants"]>(config: Simplify<C>): (props: VariantOptions<C>) => string;
18
105
  export {};
package/lib/index.js CHANGED
@@ -1,18 +1,26 @@
1
- export function variants({ base, variants, compoundVariants, defaultVariants, }) {
1
+ export function variants(config) {
2
+ const { base, variants, compoundVariants, defaultVariants } = config;
3
+ const isBooleanVariant = (name) => {
4
+ const v = variants === null || variants === void 0 ? void 0 : variants[name];
5
+ return v && ("false" in v || "true" in v);
6
+ };
2
7
  return (props) => {
8
+ var _a;
3
9
  const res = [base];
4
- const getSelected = (name) => { var _a; return (_a = props[name]) !== null && _a !== void 0 ? _a : defaultVariants === null || defaultVariants === void 0 ? void 0 : defaultVariants[name]; };
10
+ const getSelected = (name) => {
11
+ var _a, _b;
12
+ return (_b = (_a = props[name]) !== null && _a !== void 0 ? _a : defaultVariants === null || defaultVariants === void 0 ? void 0 : defaultVariants[name]) !== null && _b !== void 0 ? _b : (isBooleanVariant(name) ? false : undefined);
13
+ };
5
14
  for (let name in variants) {
6
15
  const selected = getSelected(name);
7
- const options = variants[name];
8
- if (selected)
9
- res.push(options[selected]);
16
+ if (selected !== undefined)
17
+ res.push((_a = variants[name]) === null || _a === void 0 ? void 0 : _a[selected]);
10
18
  }
11
19
  for (let { variants, className } of compoundVariants !== null && compoundVariants !== void 0 ? compoundVariants : []) {
12
- const isSelected = (name) => getSelected(name) == variants[name];
13
- const allSelected = Object.keys(variants).every(isSelected);
14
- if (allSelected)
20
+ const isSelected = (name) => getSelected(name) === variants[name];
21
+ if (Object.keys(variants).every(isSelected)) {
15
22
  res.push(className);
23
+ }
16
24
  }
17
25
  return res.filter(Boolean).join(" ");
18
26
  };
package/lib/react.d.ts CHANGED
@@ -1,9 +1,18 @@
1
1
  import { ComponentProps, ElementType, ForwardRefExoticComponent, PropsWithoutRef } from "react";
2
- import { Variants, VariantsConfig, VariantSelection } from ".";
3
- export declare type VariantProps<T> = T extends (props: infer VariantSelection) => any ? VariantSelection : never;
4
- export declare function variantProps<V extends Variants>(config: VariantsConfig<V>): <T extends VariantSelection<V>>(props: T) => {
2
+ import { Variants, VariantsConfig, VariantOptions, Simplify } from ".";
3
+ /**
4
+ * Utility type to infer the first argument of a variantProps function.
5
+ */
6
+ export declare type VariantPropsOf<T> = T extends (props: infer P) => any ? P : never;
7
+ /**
8
+ * Type for the variantProps() argument – consists of the VariantOptions and an optional className for chaining.
9
+ */
10
+ declare type VariantProps<C extends VariantsConfig<V>, V extends Variants = C["variants"]> = VariantOptions<C> & {
11
+ className?: string;
12
+ };
13
+ export declare function variantProps<C extends VariantsConfig<V>, V extends Variants = C["variants"]>(config: Simplify<C>): <P extends VariantProps<C, C["variants"]>>(props: P) => {
5
14
  className: string;
6
- } & Omit<T, keyof V>;
7
- declare type StyledComponent<P extends ElementType, V extends Variants> = ForwardRefExoticComponent<PropsWithoutRef<ComponentProps<P> & VariantSelection<V>> & React.RefAttributes<P>>;
8
- export declare function styled<P extends ElementType, V extends Variants>(type: P, config: VariantsConfig<V>): StyledComponent<P, V>;
15
+ } & Omit<P, keyof C["variants"]>;
16
+ declare type StyledComponent<T extends ElementType, C extends VariantsConfig<V>, V extends Variants = C["variants"]> = ForwardRefExoticComponent<PropsWithoutRef<ComponentProps<T> & VariantOptions<C>> & React.RefAttributes<T>>;
17
+ export declare function styled<T extends ElementType, C extends VariantsConfig<V>, V extends Variants = C["variants"]>(type: T, config: Simplify<C>): StyledComponent<T, C>;
9
18
  export {};
package/lib/react.js CHANGED
@@ -1,15 +1,19 @@
1
1
  import { createElement, forwardRef, } from "react";
2
- import { variants } from ".";
2
+ import { variants, } from ".";
3
3
  export function variantProps(config) {
4
- const mkClass = variants(config);
4
+ const variantClassName = variants(config);
5
5
  return (props) => {
6
- const className = mkClass(props);
7
- const result = { className };
6
+ const result = {};
7
+ // Pass-through all unrelated props
8
8
  for (let prop in props) {
9
9
  if (config.variants && !(prop in config.variants)) {
10
10
  result[prop] = props[prop];
11
11
  }
12
12
  }
13
+ // Add the optionally passed className prop for chaining
14
+ result.className = [props.className, variantClassName(props)]
15
+ .filter(Boolean)
16
+ .join(" ");
13
17
  return result;
14
18
  };
15
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "classname-variants",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Variant API for plain class names",
5
5
  "author": "Felix Gnass <fgnass@gmail.com>",
6
6
  "license": "MIT",
@@ -8,9 +8,30 @@
8
8
  "type": "module",
9
9
  "main": "lib/index.js",
10
10
  "types": "lib/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./lib/index.js",
14
+ "type": "./lib/index.d.ts"
15
+ },
16
+ "./react": {
17
+ "import": "./lib/react.js",
18
+ "type": "./lib/react.d.ts"
19
+ }
20
+ },
21
+ "typesVersions": {
22
+ "*": {
23
+ "*": [
24
+ "./lib/index.d.ts"
25
+ ],
26
+ "react": [
27
+ "./lib/react.d.ts"
28
+ ]
29
+ }
30
+ },
11
31
  "scripts": {
12
32
  "build": "tsc",
13
- "start": "npx vite"
33
+ "start": "npx vite",
34
+ "prepublishOnly": "npm run build"
14
35
  },
15
36
  "keywords": [
16
37
  "tailwind",
package/src/index.ts CHANGED
@@ -1,46 +1,165 @@
1
- export type VariantDefinitions = Record<string, string>;
1
+ /**
2
+ * Definition of the available variants and their options.
3
+ * @example
4
+ * {
5
+ * color: {
6
+ * white: "bg-white"
7
+ * green: "bg-green-500",
8
+ * },
9
+ * size: {
10
+ * small: "text-xs",
11
+ * large: "text-lg"
12
+ * }
13
+ * }
14
+ */
15
+ export type Variants = Record<string, Record<string, string>>;
2
16
 
3
- export type Variants = Record<string, VariantDefinitions>;
4
-
5
- export type VariantsConfig<V extends Variants> = {
17
+ /**
18
+ * Configuration including defaults and compound variants.
19
+ */
20
+ export interface VariantsConfig<V extends Variants> {
6
21
  base?: string;
7
- variants?: V;
22
+ variants: V;
8
23
  compoundVariants?: CompoundVariant<V>[];
9
- defaultVariants?: VariantSelection<V>;
10
- };
24
+ defaultVariants?: Partial<OptionsOf<V>>;
25
+ }
11
26
 
12
- export type CompoundVariant<V extends Variants> = {
13
- variants: VariantSelection<V>;
27
+ /**
28
+ * Rules for class names that are applied for certain variant combinations.
29
+ */
30
+ export interface CompoundVariant<V extends Variants> {
31
+ variants: Partial<OptionsOf<V>>;
14
32
  className: string;
33
+ }
34
+
35
+ /**
36
+ * Only the boolean variants, i.e. ones that have "true" or "false" as options.
37
+ */
38
+ type BooleanVariants<V extends Variants> = {
39
+ [K in keyof V as V[K] extends { true: any } | { false: any }
40
+ ? K
41
+ : never]: V[K];
42
+ };
43
+
44
+ /**
45
+ * Only the variants for which a default options is set.
46
+ */
47
+ type DefaultVariants<
48
+ C extends VariantsConfig<V>,
49
+ V extends Variants = C["variants"]
50
+ > = {
51
+ [K in keyof V as K extends keyof C["defaultVariants"]
52
+ ? K
53
+ : never]: C["variants"][K];
54
+ };
55
+
56
+ /**
57
+ * Names of all optional variants, i.e. booleans or ones with default options.
58
+ */
59
+ type OptionalVariantNames<
60
+ C extends VariantsConfig<V>,
61
+ V extends Variants = C["variants"]
62
+ > = keyof BooleanVariants<V> | keyof DefaultVariants<C>;
63
+
64
+ /**
65
+ * Possible options for all the optional variants.
66
+ *
67
+ * @example
68
+ * {
69
+ * color?: "white" | "green",
70
+ * rounded?: boolean | undefined
71
+ * }
72
+ */
73
+ type OptionalOptions<
74
+ C extends VariantsConfig<V>,
75
+ V extends Variants = C["variants"]
76
+ > = {
77
+ [K in keyof V as K extends OptionalVariantNames<C>
78
+ ? K
79
+ : never]?: OptionsOf<V>[K];
80
+ };
81
+
82
+ /**
83
+ * Possible options for all required variants.
84
+ *
85
+ * @example {
86
+ * size: "small" | "large"
87
+ * }
88
+ */
89
+ type RequiredOptions<
90
+ C extends VariantsConfig<V>,
91
+ V extends Variants = C["variants"]
92
+ > = {
93
+ [K in keyof V as K extends OptionalVariantNames<C>
94
+ ? never
95
+ : K]: OptionsOf<V>[K];
15
96
  };
16
97
 
17
- export type VariantSelection<V extends Variants> = {
18
- [VariantName in keyof V]?: StringToBool<keyof V[VariantName]>;
98
+ /**
99
+ * Utility type to extract the possible options.
100
+ * Converts "true" | "false" options into booleans.
101
+ *
102
+ * @example
103
+ * OptionsOf<{
104
+ * size: { small: "text-xs"; large: "text-lg" };
105
+ * rounded: { true: "rounded-full" }
106
+ * }>
107
+ * ==>
108
+ * {
109
+ * size: "text-xs" | "text-lg";
110
+ * rounded: boolean;
111
+ * }
112
+ */
113
+ type OptionsOf<V extends Variants> = {
114
+ [K in keyof V]: keyof V[K] extends "true" | "false" ? boolean : keyof V[K];
19
115
  };
20
116
 
21
- type StringToBool<T> = T extends "true" | "false" ? boolean : T;
117
+ /**
118
+ * Extracts the possible options.
119
+ */
120
+ export type VariantOptions<
121
+ C extends VariantsConfig<V>,
122
+ V extends Variants = C["variants"]
123
+ > = RequiredOptions<C> & OptionalOptions<C>;
124
+
125
+ /**
126
+ * Without this conversion step, defaultVariants and compoundVariants will
127
+ * allow extra keys, i.e. non-existent variants.
128
+ * See https://github.com/sindresorhus/type-fest/blob/main/source/simplify.d.ts
129
+ */
130
+ export type Simplify<T> = {
131
+ [K in keyof T]: T[K];
132
+ };
22
133
 
23
- export function variants<V extends Variants>({
24
- base,
25
- variants,
26
- compoundVariants,
27
- defaultVariants,
28
- }: VariantsConfig<V>) {
29
- return (props: VariantSelection<V>) => {
134
+ export function variants<
135
+ C extends VariantsConfig<V>,
136
+ V extends Variants = C["variants"]
137
+ >(config: Simplify<C>) {
138
+ const { base, variants, compoundVariants, defaultVariants } = config;
139
+
140
+ const isBooleanVariant = (name: keyof V) => {
141
+ const v = variants?.[name];
142
+ return v && ("false" in v || "true" in v);
143
+ };
144
+
145
+ return (props: VariantOptions<C>) => {
30
146
  const res = [base];
31
147
 
32
- const getSelected = (name: string) =>
33
- props[name] ?? defaultVariants?.[name];
148
+ const getSelected = (name: keyof V) =>
149
+ (props as any)[name] ??
150
+ defaultVariants?.[name] ??
151
+ (isBooleanVariant(name) ? false : undefined);
34
152
 
35
153
  for (let name in variants) {
36
154
  const selected = getSelected(name);
37
- const options: any = variants[name];
38
- if (selected) res.push(options[selected]);
155
+ if (selected !== undefined) res.push(variants[name]?.[selected]);
39
156
  }
157
+
40
158
  for (let { variants, className } of compoundVariants ?? []) {
41
- const isSelected = (name: string) => getSelected(name) == variants[name];
42
- const allSelected = Object.keys(variants).every(isSelected);
43
- if (allSelected) res.push(className);
159
+ const isSelected = (name: string) => getSelected(name) === variants[name];
160
+ if (Object.keys(variants).every(isSelected)) {
161
+ res.push(className);
162
+ }
44
163
  }
45
164
  return res.filter(Boolean).join(" ");
46
165
  };
package/src/react.ts CHANGED
@@ -7,40 +7,67 @@ import {
7
7
  PropsWithoutRef,
8
8
  } from "react";
9
9
 
10
- import { Variants, variants, VariantsConfig, VariantSelection } from ".";
10
+ import {
11
+ Variants,
12
+ variants,
13
+ VariantsConfig,
14
+ VariantOptions,
15
+ Simplify,
16
+ } from ".";
17
+
18
+ /**
19
+ * Utility type to infer the first argument of a variantProps function.
20
+ */
21
+ export type VariantPropsOf<T> = T extends (props: infer P) => any ? P : never;
22
+
23
+ /**
24
+ * Type for the variantProps() argument – consists of the VariantOptions and an optional className for chaining.
25
+ */
26
+ type VariantProps<
27
+ C extends VariantsConfig<V>,
28
+ V extends Variants = C["variants"]
29
+ > = VariantOptions<C> & { className?: string };
11
30
 
12
- export type VariantProps<T> = T extends (props: infer VariantSelection) => any
13
- ? VariantSelection
14
- : never;
31
+ export function variantProps<
32
+ C extends VariantsConfig<V>,
33
+ V extends Variants = C["variants"]
34
+ >(config: Simplify<C>) {
35
+ const variantClassName = variants<C>(config);
36
+ return <P extends VariantProps<C>>(props: P) => {
37
+ const result: any = {};
15
38
 
16
- export function variantProps<V extends Variants>(config: VariantsConfig<V>) {
17
- const mkClass = variants(config);
18
- return <T extends VariantSelection<V>>(props: T) => {
19
- const className = mkClass(props);
20
- const result: any = { className };
39
+ // Pass-through all unrelated props
21
40
  for (let prop in props) {
22
41
  if (config.variants && !(prop in config.variants)) {
23
42
  result[prop] = props[prop];
24
43
  }
25
44
  }
26
- return result as { className: string } & Omit<T, keyof V>;
45
+
46
+ // Add the optionally passed className prop for chaining
47
+ result.className = [props.className, variantClassName(props)]
48
+ .filter(Boolean)
49
+ .join(" ");
50
+
51
+ return result as { className: string } & Omit<P, keyof C["variants"]>;
27
52
  };
28
53
  }
29
54
 
30
55
  type StyledComponent<
31
- P extends ElementType,
32
- V extends Variants
56
+ T extends ElementType,
57
+ C extends VariantsConfig<V>,
58
+ V extends Variants = C["variants"]
33
59
  > = ForwardRefExoticComponent<
34
- PropsWithoutRef<ComponentProps<P> & VariantSelection<V>> &
35
- React.RefAttributes<P>
60
+ PropsWithoutRef<ComponentProps<T> & VariantOptions<C>> &
61
+ React.RefAttributes<T>
36
62
  >;
37
63
 
38
- export function styled<P extends ElementType, V extends Variants>(
39
- type: P,
40
- config: VariantsConfig<V>
41
- ): StyledComponent<P, V> {
64
+ export function styled<
65
+ T extends ElementType,
66
+ C extends VariantsConfig<V>,
67
+ V extends Variants = C["variants"]
68
+ >(type: T, config: Simplify<C>): StyledComponent<T, C> {
42
69
  const styledProps = variantProps(config);
43
- return forwardRef<P, ComponentProps<P> & VariantSelection<V>>((props, ref) =>
70
+ return forwardRef<T, ComponentProps<T> & VariantOptions<C>>((props, ref) =>
44
71
  createElement(type, { ...styledProps(props), ref })
45
72
  );
46
73
  }
@@ -1,5 +0,0 @@
1
- {
2
- "internal": true,
3
- "module": "../lib/react.js",
4
- "types": "../lib/react.d.ts"
5
- }