responsive-class-variants 1.0.0

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 ADDED
@@ -0,0 +1,63 @@
1
+ # Responsive Class Variants
2
+
3
+ rcv helps you create responsive class variants
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add responsive-class-variants
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ const getButtonVariants = rcv({
15
+ base: "px-4 py-2 rounded",
16
+ variants: {
17
+ intent: {
18
+ primary: "bg-blue-500 text-white",
19
+ secondary: "bg-gray-200 text-gray-800"
20
+ },
21
+ size: {
22
+ sm: "text-sm",
23
+ lg: "text-lg"
24
+ },
25
+ disabled: {
26
+ true: "opacity-50 cursor-not-allowed"
27
+ }
28
+ }
29
+ });
30
+
31
+ // Usage:
32
+ getButtonVariants({ intent: "primary", size: "lg", disabled: true })
33
+ // Or with responsive values:
34
+ getButtonVariants({ intent: { initial: "primary", md: "secondary" } })
35
+
36
+ ```
37
+
38
+ ## Custom breakpoints (via createRcv)
39
+
40
+ ```ts
41
+ const rcv = createRcv(['mobile', 'tablet', 'desktop']);
42
+
43
+ const getButtonVariants = rcv({
44
+ base: "px-4 py-2 rounded",
45
+ variants: {
46
+ intent: {
47
+ primary: "bg-blue-500 text-white",
48
+ secondary: "bg-gray-200 text-gray-800"
49
+ }
50
+ }
51
+ });
52
+
53
+ // Usage with custom breakpoints:
54
+ getButtonVariants({ intent: { initial: "primary", mobile: "secondary", desktop: "primary" } })
55
+ ```
56
+
57
+ ## onComplete callback (via createRcv)
58
+
59
+ You can pass an optional onComplete callback to the createRcv function. This callback will be called with the generated classes. Helpful if you want to pass your classes to a library like tailwindMerge.
60
+
61
+ ```ts
62
+ const rcv = createRcv(['mobile', 'tablet', 'desktop'], (classes) => twMerge(classes));
63
+ ```
@@ -0,0 +1,127 @@
1
+ export type DefaultBreakpoints = "sm" | "md" | "lg" | "xl";
2
+ export type Breakpoints = DefaultBreakpoints;
3
+ export type BreakpointsMap<V, B extends string = DefaultBreakpoints> = {
4
+ initial: V;
5
+ } & Partial<{
6
+ [breakpoint in B]: V;
7
+ }>;
8
+ export type ResponsiveValue<T, B extends string = DefaultBreakpoints> = T | BreakpointsMap<T, B>;
9
+ /**
10
+ * Maps a ResponsiveValue to a new ResponsiveValue using the provided mapper function. Singular values are passed through as is.
11
+ *
12
+ * @template V The type of the original value
13
+ * @template T The type of the mapped value
14
+ * @template B The type of breakpoints
15
+ * @param {ResponsiveValue<V, B>} value - The original ResponsiveValue to be mapped
16
+ * @param {function(V): T} mapper - A function that maps a ResponsiveValue to a new ResponsiveValue
17
+ * @returns {ResponsiveValue<T, B>} A new ResponsiveValue with the mapped values
18
+ *
19
+ *
20
+ * @example
21
+ * const sizes = {
22
+ * initial: 'md',
23
+ * sm: 'lg',
24
+ * }
25
+ *
26
+ * const output = mapResponsiveValue(sizes, size => {
27
+ * switch (size) {
28
+ * case 'initial':
29
+ * return 'sm';
30
+ * case 'sm':
31
+ * return 'md';
32
+ * }
33
+ * });
34
+ *
35
+ * // console.log(output)
36
+ * {
37
+ * initial: 'sm',
38
+ * sm: 'md',
39
+ * }
40
+ */
41
+ export declare const mapResponsiveValue: <V, T, B extends string = DefaultBreakpoints>(value: ResponsiveValue<V, B>, mapper: (value: V) => T) => ResponsiveValue<T, B>;
42
+ /**
43
+ * Start of rcv and types
44
+ */
45
+ type VariantValue = Record<string, string>;
46
+ type VariantConfig = Record<string, VariantValue>;
47
+ type StringBoolean = "true" | "false";
48
+ type BooleanVariant = Partial<Record<StringBoolean, string>>;
49
+ type VariantPropValue<T, B extends string> = T extends BooleanVariant ? ResponsiveValue<boolean, B> | undefined : T extends Record<string, unknown> ? ResponsiveValue<keyof T, B> : never;
50
+ type VariantProps<T extends VariantConfig, B extends string> = {
51
+ [K in keyof T]: VariantPropValue<T[K], B>;
52
+ } & {
53
+ className?: string;
54
+ };
55
+ type ResponsiveClassesConfig<T extends VariantConfig, B extends string> = {
56
+ base: string;
57
+ variants?: T;
58
+ compoundVariants?: Partial<VariantProps<T, B>>[];
59
+ onComplete?: (classes: string) => void;
60
+ };
61
+ /**
62
+ * Creates a function that generates classes based on variant configurations and responsive props
63
+ *
64
+ * @template T - Type extending VariantConfig (Record of variant names to their possible values and corresponding classes)
65
+ * @template B - The breakpoints type
66
+ *
67
+ * @param config - Configuration object for variants
68
+ * @param config.base - Base classes that are always applied
69
+ * @param config.variants - Object containing variant definitions where each key is a variant name
70
+ * and value is either a string of class names, an object mapping variant values to class names,
71
+ * or an object with true/false keys for boolean variants
72
+ * @param config.compoundVariants - Optional array of compound variants that apply additional classes
73
+ * when multiple variants have specific values
74
+ * @param config.onComplete - Optional callback function that receives the generated classes
75
+ *
76
+ * @returns A function that accepts variant props and returns classes with twMerge
77
+ *
78
+ * @example
79
+ * const getButtonVariants = rcv({
80
+ * base: "px-4 py-2 rounded",
81
+ * variants: {
82
+ * intent: {
83
+ * primary: "bg-blue-500 text-white",
84
+ * secondary: "bg-gray-200 text-gray-800"
85
+ * },
86
+ * size: {
87
+ * sm: "text-sm",
88
+ * lg: "text-lg"
89
+ * },
90
+ * disabled: {
91
+ * true: "opacity-50 cursor-not-allowed"
92
+ * }
93
+ * }
94
+ * });
95
+ *
96
+ * // Usage:
97
+ * getButtonVariants({ intent: "primary", size: "lg", disabled: true })
98
+ * // Or with responsive values:
99
+ * getButtonVariants({ intent: { initial: "primary", md: "secondary" } })
100
+ */
101
+ export declare const rcv: <T extends VariantConfig, B extends string = DefaultBreakpoints>({ base, variants, compoundVariants, onComplete, }: ResponsiveClassesConfig<T, B>) => ({ className, ...props }: VariantProps<T, B>) => string | void;
102
+ /**
103
+ * Creates a custom rcv function with custom breakpoints and an optional onComplete callback
104
+ *
105
+ * @template B - The custom breakpoints type
106
+ * @param breakpoints - Array of custom breakpoint names
107
+ * @param onComplete - Optional callback function that receives the generated classes
108
+ * @returns A function that creates rcv with custom breakpoints
109
+ *
110
+ * @example
111
+ * const customRcv = createRcv(['mobile', 'tablet', 'desktop']);
112
+ *
113
+ * const getButtonVariants = customRcv({
114
+ * base: "px-4 py-2 rounded",
115
+ * variants: {
116
+ * intent: {
117
+ * primary: "bg-blue-500 text-white",
118
+ * secondary: "bg-gray-200 text-gray-800"
119
+ * }
120
+ * }
121
+ * });
122
+ *
123
+ * // Usage with custom breakpoints:
124
+ * getButtonVariants({ intent: { initial: "primary", mobile: "secondary", desktop: "primary" } })
125
+ */
126
+ export declare const createRcv: <B extends string>(_breakpoints: readonly B[], onComplete?: (classes: string) => void) => <T extends VariantConfig>(config: ResponsiveClassesConfig<T, B>) => ({ className, ...props }: VariantProps<T, B>) => string | void;
127
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,150 @@
1
+ import { clsx } from "clsx";
2
+ const isSingularValue = (value) => !isBreakpointsMap(value);
3
+ const isBreakpointsMap = (value) => typeof value === "object" && value != null && !Array.isArray(value);
4
+ /**
5
+ * Maps a ResponsiveValue to a new ResponsiveValue using the provided mapper function. Singular values are passed through as is.
6
+ *
7
+ * @template V The type of the original value
8
+ * @template T The type of the mapped value
9
+ * @template B The type of breakpoints
10
+ * @param {ResponsiveValue<V, B>} value - The original ResponsiveValue to be mapped
11
+ * @param {function(V): T} mapper - A function that maps a ResponsiveValue to a new ResponsiveValue
12
+ * @returns {ResponsiveValue<T, B>} A new ResponsiveValue with the mapped values
13
+ *
14
+ *
15
+ * @example
16
+ * const sizes = {
17
+ * initial: 'md',
18
+ * sm: 'lg',
19
+ * }
20
+ *
21
+ * const output = mapResponsiveValue(sizes, size => {
22
+ * switch (size) {
23
+ * case 'initial':
24
+ * return 'sm';
25
+ * case 'sm':
26
+ * return 'md';
27
+ * }
28
+ * });
29
+ *
30
+ * // console.log(output)
31
+ * {
32
+ * initial: 'sm',
33
+ * sm: 'md',
34
+ * }
35
+ */
36
+ export const mapResponsiveValue = (value, mapper) => isSingularValue(value)
37
+ ? mapper(value)
38
+ : Object.fromEntries(Object.entries(value).map(([breakpoint, value]) => [
39
+ breakpoint,
40
+ mapper(value),
41
+ ]));
42
+ /**
43
+ * Creates a function that generates classes based on variant configurations and responsive props
44
+ *
45
+ * @template T - Type extending VariantConfig (Record of variant names to their possible values and corresponding classes)
46
+ * @template B - The breakpoints type
47
+ *
48
+ * @param config - Configuration object for variants
49
+ * @param config.base - Base classes that are always applied
50
+ * @param config.variants - Object containing variant definitions where each key is a variant name
51
+ * and value is either a string of class names, an object mapping variant values to class names,
52
+ * or an object with true/false keys for boolean variants
53
+ * @param config.compoundVariants - Optional array of compound variants that apply additional classes
54
+ * when multiple variants have specific values
55
+ * @param config.onComplete - Optional callback function that receives the generated classes
56
+ *
57
+ * @returns A function that accepts variant props and returns classes with twMerge
58
+ *
59
+ * @example
60
+ * const getButtonVariants = rcv({
61
+ * base: "px-4 py-2 rounded",
62
+ * variants: {
63
+ * intent: {
64
+ * primary: "bg-blue-500 text-white",
65
+ * secondary: "bg-gray-200 text-gray-800"
66
+ * },
67
+ * size: {
68
+ * sm: "text-sm",
69
+ * lg: "text-lg"
70
+ * },
71
+ * disabled: {
72
+ * true: "opacity-50 cursor-not-allowed"
73
+ * }
74
+ * }
75
+ * });
76
+ *
77
+ * // Usage:
78
+ * getButtonVariants({ intent: "primary", size: "lg", disabled: true })
79
+ * // Or with responsive values:
80
+ * getButtonVariants({ intent: { initial: "primary", md: "secondary" } })
81
+ */
82
+ export const rcv = ({ base, variants, compoundVariants, onComplete, }) => ({ className, ...props }) => {
83
+ const responsiveClasses = Object.entries(props)
84
+ .map(([key, propValue]) => {
85
+ const variant = variants?.[key];
86
+ const value = typeof propValue === "boolean" ? String(propValue) : propValue;
87
+ // Handle undefined values
88
+ if (!value)
89
+ return undefined;
90
+ const variantValue = variant?.[value];
91
+ // Handle string values
92
+ if (typeof variantValue === "string") {
93
+ return variantValue;
94
+ }
95
+ // Handle responsive values
96
+ return Object.entries(value)
97
+ .map(([breakpoint, value]) => {
98
+ // If the breakpoint is initial, return the variant value without breakpoint prefix
99
+ if (breakpoint === "initial") {
100
+ return variants?.[key]?.[value];
101
+ }
102
+ // Otherwise, return the variant value with the breakpoint prefix
103
+ return variants?.[key]?.[value]
104
+ ?.split(" ")
105
+ .map((className) => `${breakpoint}:${className}`)
106
+ .join(" ");
107
+ })
108
+ .join(" ");
109
+ })
110
+ .filter(Boolean)
111
+ .join(" ");
112
+ const compoundClasses = compoundVariants
113
+ ?.map(({ className, ...compound }) => {
114
+ if (Object.entries(compound).every(([key, value]) => props[key] === String(value) || props[key] === value)) {
115
+ return className;
116
+ }
117
+ return undefined;
118
+ })
119
+ .filter(Boolean);
120
+ const classes = clsx(base, responsiveClasses, compoundClasses, className);
121
+ return onComplete ? onComplete(classes) : classes;
122
+ };
123
+ /**
124
+ * Creates a custom rcv function with custom breakpoints and an optional onComplete callback
125
+ *
126
+ * @template B - The custom breakpoints type
127
+ * @param breakpoints - Array of custom breakpoint names
128
+ * @param onComplete - Optional callback function that receives the generated classes
129
+ * @returns A function that creates rcv with custom breakpoints
130
+ *
131
+ * @example
132
+ * const customRcv = createRcv(['mobile', 'tablet', 'desktop']);
133
+ *
134
+ * const getButtonVariants = customRcv({
135
+ * base: "px-4 py-2 rounded",
136
+ * variants: {
137
+ * intent: {
138
+ * primary: "bg-blue-500 text-white",
139
+ * secondary: "bg-gray-200 text-gray-800"
140
+ * }
141
+ * }
142
+ * });
143
+ *
144
+ * // Usage with custom breakpoints:
145
+ * getButtonVariants({ intent: { initial: "primary", mobile: "secondary", desktop: "primary" } })
146
+ */
147
+ export const createRcv = (_breakpoints, onComplete) => {
148
+ return (config) => rcv({ ...config, onComplete });
149
+ };
150
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"/","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAe5B,MAAM,eAAe,GAAG,CACvB,KAA4B,EACf,EAAE,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;AAE1C,MAAM,gBAAgB,GAAG,CACxB,KAA4B,EACI,EAAE,CAClC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CACjC,KAA4B,EAC5B,MAAuB,EACC,EAAE,CAC1B,eAAe,CAAC,KAAK,CAAC;IACrB,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;IACf,CAAC,CAAE,MAAM,CAAC,WAAW,CACnB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;QAClD,UAAU;QACV,MAAM,CAAC,KAAK,CAAC;KACb,CAAC,CACuB,CAAC;AA+B9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,CAAC,MAAM,GAAG,GACf,CAAiE,EAChE,IAAI,EACJ,QAAQ,EACR,gBAAgB,EAChB,UAAU,GACqB,EAAE,EAAE,CACpC,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAsB,EAAE,EAAE;IAC/C,MAAM,iBAAiB,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;SAC7C,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,SAAS,CAA6C,EAAE,EAAE;QACrE,MAAM,OAAO,GAAG,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;QAChC,MAAM,KAAK,GACV,OAAO,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAEhE,0BAA0B;QAC1B,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAE7B,MAAM,YAAY,GAAG,OAAO,EAAE,CAAC,KAA2B,CAAC,CAAC;QAE5D,uBAAuB;QACvB,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;YACtC,OAAO,YAAY,CAAC;QACrB,CAAC;QAED,2BAA2B;QAC3B,OAAO,MAAM,CAAC,OAAO,CAAC,KAAsC,CAAC;aAC3D,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,KAAK,CAAC,EAAE,EAAE;YAC5B,mFAAmF;YACnF,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC9B,OAAO,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,KAA6B,CAAC,CAAC;YACzD,CAAC;YACD,iEAAiE;YACjE,OAAO,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,KAA6B,CAAC;gBACtD,EAAE,KAAK,CAAC,GAAG,CAAC;iBACX,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,GAAG,UAAU,IAAI,SAAS,EAAE,CAAC;iBAChD,IAAI,CAAC,GAAG,CAAC,CAAC;QACb,CAAC,CAAC;aACD,IAAI,CAAC,GAAG,CAAC,CAAC;IACb,CAAC,CAAC;SACD,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAC;IAEZ,MAAM,eAAe,GAAG,gBAAgB;QACvC,EAAE,GAAG,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,QAAQ,EAAE,EAAE,EAAE;QACpC,IACC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,KAAK,CAC7B,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAChB,KAAK,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,KAAK,CACrD,EACA,CAAC;YACF,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,OAAO,SAAS,CAAC;IAClB,CAAC,CAAC;SACD,MAAM,CAAC,OAAO,CAAC,CAAC;IAElB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,iBAAiB,EAAE,eAAe,EAAE,SAAS,CAAC,CAAC;IAC1E,OAAO,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;AACnD,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,CAAC,MAAM,SAAS,GAAG,CACxB,YAA0B,EAC1B,UAAsC,EACrC,EAAE;IACH,OAAO,CAA0B,MAAqC,EAAE,EAAE,CACzE,GAAG,CAAO,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;AACvC,CAAC,CAAC","sourcesContent":["import { clsx } from \"clsx\";\n\nexport type DefaultBreakpoints = \"sm\" | \"md\" | \"lg\" | \"xl\";\nexport type Breakpoints = DefaultBreakpoints;\n\nexport type BreakpointsMap<V, B extends string = DefaultBreakpoints> = {\n\tinitial: V;\n} & Partial<{\n\t[breakpoint in B]: V;\n}>;\n\nexport type ResponsiveValue<T, B extends string = DefaultBreakpoints> =\n\t| T\n\t| BreakpointsMap<T, B>;\n\nconst isSingularValue = <A, B extends string>(\n\tvalue: ResponsiveValue<A, B>,\n): value is A => !isBreakpointsMap(value);\n\nconst isBreakpointsMap = <A, B extends string>(\n\tvalue: ResponsiveValue<A, B>,\n): value is BreakpointsMap<A, B> =>\n\ttypeof value === \"object\" && value != null && !Array.isArray(value);\n\n/**\n * Maps a ResponsiveValue to a new ResponsiveValue using the provided mapper function. Singular values are passed through as is.\n *\n * @template V The type of the original value\n * @template T The type of the mapped value\n * @template B The type of breakpoints\n * @param {ResponsiveValue<V, B>} value - The original ResponsiveValue to be mapped\n * @param {function(V): T} mapper - A function that maps a ResponsiveValue to a new ResponsiveValue\n * @returns {ResponsiveValue<T, B>} A new ResponsiveValue with the mapped values\n *\n *\n * @example\n * const sizes = {\n * initial: 'md',\n * sm: 'lg',\n * }\n *\n * const output = mapResponsiveValue(sizes, size => {\n *\tswitch (size) {\n *\t\tcase 'initial':\n *\t\treturn 'sm';\n *\t\tcase 'sm':\n *\t\t\treturn 'md';\n *\t\t}\n *\t});\n *\n * // console.log(output)\n * {\n *\tinitial: 'sm',\n *\tsm: 'md',\n * }\n */\nexport const mapResponsiveValue = <V, T, B extends string = DefaultBreakpoints>(\n\tvalue: ResponsiveValue<V, B>,\n\tmapper: (value: V) => T,\n): ResponsiveValue<T, B> =>\n\tisSingularValue(value)\n\t\t? mapper(value)\n\t\t: (Object.fromEntries(\n\t\t\t\tObject.entries(value).map(([breakpoint, value]) => [\n\t\t\t\t\tbreakpoint,\n\t\t\t\t\tmapper(value),\n\t\t\t\t]),\n\t\t\t) as BreakpointsMap<T, B>);\n\n/**\n * Start of rcv and types\n */\n\ntype VariantValue = Record<string, string>;\ntype VariantConfig = Record<string, VariantValue>;\n\ntype StringBoolean = \"true\" | \"false\";\ntype BooleanVariant = Partial<Record<StringBoolean, string>>;\n\ntype VariantPropValue<T, B extends string> = T extends BooleanVariant\n\t? ResponsiveValue<boolean, B> | undefined\n\t: T extends Record<string, unknown>\n\t\t? ResponsiveValue<keyof T, B>\n\t\t: never;\n\ntype VariantProps<T extends VariantConfig, B extends string> = {\n\t[K in keyof T]: VariantPropValue<T[K], B>;\n} & {\n\tclassName?: string;\n};\n\ntype ResponsiveClassesConfig<T extends VariantConfig, B extends string> = {\n\tbase: string;\n\tvariants?: T;\n\tcompoundVariants?: Partial<VariantProps<T, B>>[];\n\tonComplete?: (classes: string) => void;\n};\n\n/**\n * Creates a function that generates classes based on variant configurations and responsive props\n *\n * @template T - Type extending VariantConfig (Record of variant names to their possible values and corresponding classes)\n * @template B - The breakpoints type\n *\n * @param config - Configuration object for variants\n * @param config.base - Base classes that are always applied\n * @param config.variants - Object containing variant definitions where each key is a variant name\n * and value is either a string of class names, an object mapping variant values to class names,\n * or an object with true/false keys for boolean variants\n * @param config.compoundVariants - Optional array of compound variants that apply additional classes\n * when multiple variants have specific values\n * @param config.onComplete - Optional callback function that receives the generated classes\n *\n * @returns A function that accepts variant props and returns classes with twMerge\n *\n * @example\n * const getButtonVariants = rcv({\n * base: \"px-4 py-2 rounded\",\n * variants: {\n * intent: {\n * primary: \"bg-blue-500 text-white\",\n * secondary: \"bg-gray-200 text-gray-800\"\n * },\n * size: {\n * sm: \"text-sm\",\n * lg: \"text-lg\"\n * },\n * disabled: {\n * true: \"opacity-50 cursor-not-allowed\"\n * }\n * }\n * });\n *\n * // Usage:\n * getButtonVariants({ intent: \"primary\", size: \"lg\", disabled: true })\n * // Or with responsive values:\n * getButtonVariants({ intent: { initial: \"primary\", md: \"secondary\" } })\n */\nexport const rcv =\n\t<T extends VariantConfig, B extends string = DefaultBreakpoints>({\n\t\tbase,\n\t\tvariants,\n\t\tcompoundVariants,\n\t\tonComplete,\n\t}: ResponsiveClassesConfig<T, B>) =>\n\t({ className, ...props }: VariantProps<T, B>) => {\n\t\tconst responsiveClasses = Object.entries(props)\n\t\t\t.map(([key, propValue]: [keyof T, VariantPropValue<T[keyof T], B>]) => {\n\t\t\t\tconst variant = variants?.[key];\n\t\t\t\tconst value =\n\t\t\t\t\ttypeof propValue === \"boolean\" ? String(propValue) : propValue;\n\n\t\t\t\t// Handle undefined values\n\t\t\t\tif (!value) return undefined;\n\n\t\t\t\tconst variantValue = variant?.[value as keyof VariantValue];\n\n\t\t\t\t// Handle string values\n\t\t\t\tif (typeof variantValue === \"string\") {\n\t\t\t\t\treturn variantValue;\n\t\t\t\t}\n\n\t\t\t\t// Handle responsive values\n\t\t\t\treturn Object.entries(value as Partial<BreakpointsMap<T, B>>)\n\t\t\t\t\t.map(([breakpoint, value]) => {\n\t\t\t\t\t\t// If the breakpoint is initial, return the variant value without breakpoint prefix\n\t\t\t\t\t\tif (breakpoint === \"initial\") {\n\t\t\t\t\t\t\treturn variants?.[key]?.[value as keyof typeof variant];\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Otherwise, return the variant value with the breakpoint prefix\n\t\t\t\t\t\treturn variants?.[key]?.[value as keyof typeof variant]\n\t\t\t\t\t\t\t?.split(\" \")\n\t\t\t\t\t\t\t.map((className) => `${breakpoint}:${className}`)\n\t\t\t\t\t\t\t.join(\" \");\n\t\t\t\t\t})\n\t\t\t\t\t.join(\" \");\n\t\t\t})\n\t\t\t.filter(Boolean)\n\t\t\t.join(\" \");\n\n\t\tconst compoundClasses = compoundVariants\n\t\t\t?.map(({ className, ...compound }) => {\n\t\t\t\tif (\n\t\t\t\t\tObject.entries(compound).every(\n\t\t\t\t\t\t([key, value]) =>\n\t\t\t\t\t\t\tprops[key] === String(value) || props[key] === value,\n\t\t\t\t\t)\n\t\t\t\t) {\n\t\t\t\t\treturn className;\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t})\n\t\t\t.filter(Boolean);\n\n\t\tconst classes = clsx(base, responsiveClasses, compoundClasses, className);\n\t\treturn onComplete ? onComplete(classes) : classes;\n\t};\n\n/**\n * Creates a custom rcv function with custom breakpoints and an optional onComplete callback\n *\n * @template B - The custom breakpoints type\n * @param breakpoints - Array of custom breakpoint names\n * @param onComplete - Optional callback function that receives the generated classes\n * @returns A function that creates rcv with custom breakpoints\n *\n * @example\n * const customRcv = createRcv(['mobile', 'tablet', 'desktop']);\n *\n * const getButtonVariants = customRcv({\n * base: \"px-4 py-2 rounded\",\n * variants: {\n * intent: {\n * primary: \"bg-blue-500 text-white\",\n * secondary: \"bg-gray-200 text-gray-800\"\n * }\n * }\n * });\n *\n * // Usage with custom breakpoints:\n * getButtonVariants({ intent: { initial: \"primary\", mobile: \"secondary\", desktop: \"primary\" } })\n */\n\nexport const createRcv = <B extends string>(\n\t_breakpoints: readonly B[],\n\tonComplete?: (classes: string) => void,\n) => {\n\treturn <T extends VariantConfig>(config: ResponsiveClassesConfig<T, B>) =>\n\t\trcv<T, B>({ ...config, onComplete });\n};\n"]}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "responsive-class-variants",
3
+ "version": "1.0.0",
4
+ "description": "rcv helps you create responsive class variants",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/benebene84/responsive-class-variants"
14
+ },
15
+ "keywords": [
16
+ "responsive",
17
+ "class",
18
+ "variants",
19
+ "tailwind",
20
+ "tailwindcss"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "test": "vitest",
25
+ "test:ci": "vitest run",
26
+ "lint": "biome check .",
27
+ "lint:fix": "biome check . --write"
28
+ },
29
+ "author": "Benedikt Sperl",
30
+ "license": "ISC",
31
+ "packageManager": "pnpm@9.14.2+sha512.6e2baf77d06b9362294152c851c4f278ede37ab1eba3a55fda317a4a17b209f4dbb973fb250a77abc463a341fcb1f17f17cfa24091c4eb319cda0d9b84278387",
32
+ "devDependencies": {
33
+ "@biomejs/biome": "2.0.6",
34
+ "clsx": "^2.1.1",
35
+ "lefthook": "^1.11.14",
36
+ "typescript": "^5.8.3",
37
+ "vitest": "^3.2.4"
38
+ }
39
+ }