Skip to content

Commit 65ee73c

Browse files
authored
fix: export defaultConfig as value and remove responsiveVariants (#284)
- Fix TypeScript error: defaultConfig cannot be used as a value - Add explicit value export in index.d.ts - Remove defaultConfig from types.d.ts and config.d.ts to avoid type-only export conflicts - Remove responsiveVariants feature (no longer supported in Tailwind CSS v4) - Remove responsiveVariants from config.js - Remove getScreenVariantValues function and related logic from core.js - Simplify getVariantValue to handle only string variant keys - Remove responsiveVariants tests from defaultConfig.test.ts - Add comprehensive tests for defaultConfig - Test value import/export - Test property modifications - Test integration with tv and createTV functions
1 parent e531070 commit 65ee73c

File tree

6 files changed

+188
-104
lines changed

6 files changed

+188
-104
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import {expect, describe, test, beforeEach, afterEach} from "@jest/globals";
2+
3+
import {defaultConfig, tv, createTV} from "../index";
4+
5+
describe("defaultConfig", () => {
6+
// Store original values to restore after each test
7+
const originalTwMerge = defaultConfig.twMerge ?? true;
8+
const originalTwMergeConfig = {...defaultConfig.twMergeConfig};
9+
10+
beforeEach(() => {
11+
// Reset to original values before each test
12+
defaultConfig.twMerge = originalTwMerge;
13+
defaultConfig.twMergeConfig = {...originalTwMergeConfig};
14+
});
15+
16+
afterEach(() => {
17+
// Ensure cleanup after each test
18+
defaultConfig.twMerge = originalTwMerge;
19+
defaultConfig.twMergeConfig = {...originalTwMergeConfig};
20+
});
21+
22+
test("should be importable as a value (not just a type)", () => {
23+
expect(defaultConfig).toBeDefined();
24+
expect(typeof defaultConfig).toBe("object");
25+
expect(defaultConfig).toHaveProperty("twMerge");
26+
expect(defaultConfig).toHaveProperty("twMergeConfig");
27+
});
28+
29+
test("should have default values", () => {
30+
expect(defaultConfig.twMerge).toBe(true);
31+
expect(defaultConfig.twMergeConfig).toEqual({});
32+
});
33+
34+
test("should allow modification of twMergeConfig", () => {
35+
const customConfig = {
36+
extend: {
37+
theme: {
38+
spacing: ["medium", "large"],
39+
},
40+
},
41+
};
42+
43+
defaultConfig.twMergeConfig = customConfig;
44+
45+
expect(defaultConfig.twMergeConfig).toEqual(customConfig);
46+
expect(defaultConfig.twMergeConfig.extend?.theme?.spacing).toEqual(["medium", "large"]);
47+
});
48+
49+
test("should allow modification of twMerge property", () => {
50+
defaultConfig.twMerge = false;
51+
expect(defaultConfig.twMerge).toBe(false);
52+
53+
defaultConfig.twMerge = true;
54+
expect(defaultConfig.twMerge).toBe(true);
55+
});
56+
57+
test("should affect tv behavior when twMergeConfig is modified", () => {
58+
// Set up a custom twMergeConfig
59+
defaultConfig.twMergeConfig = {
60+
extend: {
61+
theme: {
62+
spacing: ["medium", "large"],
63+
},
64+
},
65+
};
66+
67+
const button = tv({
68+
base: "px-medium py-large",
69+
});
70+
71+
// The custom config should be used
72+
expect(button()).toBeDefined();
73+
});
74+
75+
test("should allow nested modifications of twMergeConfig", () => {
76+
defaultConfig.twMergeConfig = {
77+
extend: {
78+
theme: {
79+
spacing: ["small"],
80+
},
81+
},
82+
};
83+
84+
// Modify nested properties
85+
if (defaultConfig.twMergeConfig.extend?.theme) {
86+
defaultConfig.twMergeConfig.extend.theme.spacing = ["small", "medium", "large"];
87+
}
88+
89+
expect(defaultConfig.twMergeConfig.extend?.theme?.spacing).toEqual([
90+
"small",
91+
"medium",
92+
"large",
93+
]);
94+
});
95+
96+
test("should work with createTV when defaultConfig is modified", () => {
97+
defaultConfig.twMerge = false;
98+
99+
const tv = createTV({});
100+
const h1 = tv({
101+
base: "text-3xl font-bold text-blue-400 text-xl text-blue-200",
102+
});
103+
104+
// Since defaultConfig.twMerge is false and no override is provided,
105+
// classes should not be merged
106+
expect(h1()).toContain("text-3xl");
107+
expect(h1()).toContain("text-xl");
108+
});
109+
110+
test("should allow setting twMergeConfig with extend.classGroups", () => {
111+
const configWithClassGroups = {
112+
extend: {
113+
classGroups: {
114+
shadow: [
115+
{
116+
shadow: ["small", "medium", "large"],
117+
},
118+
],
119+
},
120+
},
121+
};
122+
123+
defaultConfig.twMergeConfig = configWithClassGroups;
124+
125+
expect(defaultConfig.twMergeConfig.extend?.classGroups).toBeDefined();
126+
expect(defaultConfig.twMergeConfig.extend?.classGroups?.shadow).toEqual([
127+
{shadow: ["small", "medium", "large"]},
128+
]);
129+
});
130+
131+
test("should persist modifications across multiple tv calls", () => {
132+
defaultConfig.twMergeConfig = {
133+
extend: {
134+
theme: {
135+
spacing: ["custom-spacing"],
136+
},
137+
},
138+
};
139+
140+
const button1 = tv({base: "px-custom-spacing"});
141+
const button2 = tv({base: "py-custom-spacing"});
142+
143+
// Both should use the modified config
144+
expect(button1()).toBeDefined();
145+
expect(button2()).toBeDefined();
146+
});
147+
148+
test("should allow complete replacement of twMergeConfig object", () => {
149+
const newConfig = {
150+
extend: {
151+
theme: {
152+
opacity: ["disabled"],
153+
spacing: ["unit", "unit-2"],
154+
},
155+
},
156+
};
157+
158+
defaultConfig.twMergeConfig = newConfig;
159+
160+
expect(defaultConfig.twMergeConfig).toEqual(newConfig);
161+
expect(defaultConfig.twMergeConfig.extend?.theme?.opacity).toEqual(["disabled"]);
162+
expect(defaultConfig.twMergeConfig.extend?.theme?.spacing).toEqual(["unit", "unit-2"]);
163+
});
164+
});

src/config.d.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,3 @@ export type TWMConfig = {
2121
};
2222

2323
export type TVConfig = TWMConfig;
24-
25-
export declare const defaultConfig: TVConfig;

src/config.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
export const defaultConfig = {
22
twMerge: true,
33
twMergeConfig: {},
4-
responsiveVariants: false,
54
};

src/core.js

Lines changed: 6 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
isEmptyObject,
44
falsyToString,
55
mergeObjects,
6-
removeExtraSpaces,
76
flatMergeArrays,
87
joinObjects,
98
cx,
@@ -82,48 +81,7 @@ export const getTailwindVariants = (cn) => {
8281
);
8382
}
8483

85-
const getScreenVariantValues = (screen, screenVariantValue, acc = [], slotKey) => {
86-
let result = acc;
87-
88-
if (typeof screenVariantValue === "string") {
89-
const cleaned = removeExtraSpaces(screenVariantValue);
90-
const parts = cleaned.split(" ");
91-
92-
for (let i = 0; i < parts.length; i++) {
93-
result.push(`${screen}:${parts[i]}`);
94-
}
95-
} else if (Array.isArray(screenVariantValue)) {
96-
for (let i = 0; i < screenVariantValue.length; i++) {
97-
result.push(`${screen}:${screenVariantValue[i]}`);
98-
}
99-
} else if (typeof screenVariantValue === "object" && typeof slotKey === "string") {
100-
if (slotKey in screenVariantValue) {
101-
const value = screenVariantValue[slotKey];
102-
103-
if (value && typeof value === "string") {
104-
const fixedValue = removeExtraSpaces(value);
105-
const parts = fixedValue.split(" ");
106-
const arr = [];
107-
108-
for (let i = 0; i < parts.length; i++) {
109-
arr.push(`${screen}:${parts[i]}`);
110-
}
111-
result[slotKey] = result[slotKey] ? result[slotKey].concat(arr) : arr;
112-
} else if (Array.isArray(value) && value.length > 0) {
113-
const arr = [];
114-
115-
for (let i = 0; i < value.length; i++) {
116-
arr.push(`${screen}:${value[i]}`);
117-
}
118-
result[slotKey] = arr;
119-
}
120-
}
121-
}
122-
123-
return result;
124-
};
125-
126-
const getVariantValue = (variant, vrs = variants, slotKey = null, slotProps = null) => {
84+
const getVariantValue = (variant, vrs = variants, _slotKey = null, slotProps = null) => {
12785
const variantObj = vrs[variant];
12886

12987
if (!variantObj || isEmptyObject(variantObj)) {
@@ -136,67 +94,16 @@ export const getTailwindVariants = (cn) => {
13694

13795
const variantKey = falsyToString(variantProp);
13896

139-
// responsive variants
140-
const responsiveVarsEnabled =
141-
(Array.isArray(config.responsiveVariants) && config.responsiveVariants.length > 0) ||
142-
config.responsiveVariants === true;
143-
144-
let defaultVariantProp = defaultVariants?.[variant];
145-
let screenValues = [];
146-
147-
if (typeof variantKey === "object" && responsiveVarsEnabled) {
148-
for (const [screen, screenVariantKey] of Object.entries(variantKey)) {
149-
const screenVariantValue = variantObj[screenVariantKey];
150-
151-
if (screen === "initial") {
152-
defaultVariantProp = screenVariantKey;
153-
continue;
154-
}
155-
156-
// if the screen is not in the responsiveVariants array, skip it
157-
if (
158-
Array.isArray(config.responsiveVariants) &&
159-
!config.responsiveVariants.includes(screen)
160-
) {
161-
continue;
162-
}
163-
164-
screenValues = getScreenVariantValues(
165-
screen,
166-
screenVariantValue,
167-
screenValues,
168-
slotKey,
169-
);
170-
}
97+
// If variant key is an object (responsive variants), ignore it as they're no longer supported
98+
if (typeof variantKey === "object") {
99+
return null;
171100
}
172101

173-
// If there is a variant key and it's not an object (screen variants),
174-
// we use the variant key and ignore the default variant.
175-
const key =
176-
variantKey != null && typeof variantKey != "object"
177-
? variantKey
178-
: falsyToString(defaultVariantProp);
102+
const defaultVariantProp = defaultVariants?.[variant];
103+
const key = variantKey != null ? variantKey : falsyToString(defaultVariantProp);
179104

180105
const value = variantObj[key || "false"];
181106

182-
if (
183-
typeof screenValues === "object" &&
184-
typeof slotKey === "string" &&
185-
screenValues[slotKey]
186-
) {
187-
return joinObjects(screenValues, value);
188-
}
189-
190-
if (screenValues.length > 0) {
191-
screenValues.push(value);
192-
193-
if (slotKey === "base") {
194-
return screenValues.join(" ");
195-
}
196-
197-
return screenValues;
198-
}
199-
200107
return value;
201108
};
202109

src/index.d.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,5 +101,23 @@ export declare const tv: TV;
101101
*/
102102
export declare function createTV(config: TVConfig): TV;
103103

104+
/**
105+
* Default configuration object for tailwind-variants.
106+
* Can be modified to set global defaults for all components.
107+
* @example
108+
* ```ts
109+
* import { defaultConfig } from "tailwind-variants";
110+
*
111+
* defaultConfig.twMergeConfig = {
112+
* extend: {
113+
* theme: {
114+
* spacing: ["medium", "large"],
115+
* },
116+
* },
117+
* };
118+
* ```
119+
*/
120+
export declare const defaultConfig: TVConfig;
121+
104122
// types
105123
export type {TVConfig, TWMConfig, TWMergeConfig};

src/types.d.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,8 +331,6 @@ export type TVLite = {
331331
}): TVReturnType<V, S, B, EV, ES, E>;
332332
};
333333

334-
export declare const defaultConfig: TVConfig;
335-
336334
export type VariantProps<Component extends (...args: any) => any> = Omit<
337335
OmitUndefined<Parameters<Component>[0]>,
338336
"class" | "className"

0 commit comments

Comments
 (0)