tailwind-variants 3.2.1 → 3.3.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 CHANGED
@@ -24,57 +24,30 @@
24
24
  - Composition support
25
25
  - Fully typed
26
26
  - Framework agnostic
27
- - Automatic conflict resolution
28
- - Tailwindcss V4 support
27
+ - Built-in conflict resolution
28
+ - Tailwind CSS v4 support
29
29
 
30
- ## Documentation
31
-
32
- For full documentation, visit [tailwind-variants.org](https://tailwind-variants.org)
33
-
34
- > ❕ Note: `Tailwindcss V4` no longer supports the `config.content.transform` so we remove the `responsive variants` feature
35
- >
36
- > If you want to use `responsive variants`, you need to add it manually to your classname.
37
-
38
- ## Quick Start
39
-
40
- 1. Installation:
41
- To use Tailwind Variants in your project, you can install it as a dependency:
30
+ ## Installation
42
31
 
43
32
  ```bash
44
- yarn add tailwind-variants
45
- # or
46
33
  npm i tailwind-variants
47
34
  # or
48
- pnpm add tailwind-variants
49
- ```
50
-
51
- **Optional:** If you want automatic conflict resolution, also install `tailwind-merge`:
52
-
53
- ```bash
54
- yarn add tailwind-merge
55
- # or
56
- npm i tailwind-merge
35
+ yarn add tailwind-variants
57
36
  # or
58
- pnpm add tailwind-merge
37
+ pnpm add tailwind-variants
59
38
  ```
60
39
 
61
- > **⚠️ Compatibility Note:** Supports Tailwind CSS v4.x (requires `tailwind-merge` v3.x). If you use Tailwind CSS v3.x, use tailwind-variants v0.x with [tailwind-merge v2.6.0](https://github.com/dcastil/tailwind-merge/tree/v2.6.0).
40
+ **Lite mode:** import from `tailwind-variants/lite` for a smaller bundle without conflict resolution.
62
41
 
63
- > **💡 Lite mode (v3):** For smaller bundle size and faster runtime without conflict resolution, use the `/lite` import:
64
- >
65
- > ```js
66
- > import {tv} from "tailwind-variants/lite";
67
- > ```
42
+ **Upgrading?**
68
43
 
69
- > **⚠️ Upgrading?**
70
- >
71
- > - From v2 to v3: See the [v3 migration guide](./MIGRATION-V3.md)
72
- > - From v1 to v2: See the [v2 migration guide](./MIGRATION-V2.md)
44
+ - v2 → v3: [migration guide](./.docs/migrations/v2-to-v3.md)
45
+ - v1 → v2: [migration guide](./.docs/migrations/v1-to-v2.md)
73
46
 
74
- 2. Usage:
47
+ ## Quick Start
75
48
 
76
49
  ```js
77
- import {tv} from "tailwind-variants";
50
+ import { tv } from "tailwind-variants";
78
51
 
79
52
  const button = tv({
80
53
  base: "font-medium bg-blue-500 text-white rounded-full active:opacity-80",
@@ -101,20 +74,113 @@ const button = tv({
101
74
  },
102
75
  });
103
76
 
104
- return <button className={button({size: "sm", color: "secondary"})}>Click me</button>;
77
+ button({ size: "sm", color: "secondary" });
78
+ // => "font-medium rounded-full active:opacity-80 bg-purple-500 text-white text-sm px-3 py-1"
79
+ ```
80
+
81
+ > **Note:** Tailwind CSS v4 no longer supports `config.content.transform`, so responsive variants
82
+ > were removed. Add responsive classes to your class names manually if needed.
83
+
84
+ ## Conflict Resolution
85
+
86
+ Conflict resolution is built into the default entry — no extra package is required. It is available
87
+ on `tv`, `createTV`, `cn`, and `cnMerge` (`cx` and `/lite` do not merge).
88
+
89
+ ```js
90
+ import { tv, cn } from "tailwind-variants";
91
+
92
+ cn("px-2", "px-4"); // => "px-4"
93
+
94
+ tv({ base: "px-2", variants: { size: { lg: "px-4" } } })({ size: "lg" }); // => "px-4"
105
95
  ```
106
96
 
97
+ ### Custom configuration
98
+
99
+ Pass `twMergeConfig` to teach the resolver about custom utilities. Prefer `{ extend, override }` —
100
+ `extend` appends to the defaults, `override` replaces them:
101
+
102
+ ```ts
103
+ import { cnMerge, createTV, tv, type TWMergeConfig } from "tailwind-variants";
104
+
105
+ const twMergeConfig = {
106
+ extend: {
107
+ classGroups: {
108
+ elevation: ["elevation-low", "elevation-high"],
109
+ },
110
+ },
111
+ } satisfies TWMergeConfig;
112
+
113
+ tv({ base: "elevation-low", variants: { raised: { true: "elevation-high" } } }, { twMergeConfig });
114
+ createTV({ twMergeConfig });
115
+ cnMerge("elevation-low", "elevation-high")({ twMergeConfig }); // => "elevation-high"
116
+ ```
117
+
118
+ Disable merging with `{ twMerge: false }` on `tv`, `createTV`, or `cnMerge`.
119
+
120
+ ### Reusing a `tailwind-merge` config
121
+
122
+ If you already configure `extendTailwindMerge`, reuse the same config object as `twMergeConfig`
123
+ (pass the object — not the returned merge function):
124
+
125
+ ```ts
126
+ import { extendTailwindMerge } from "tailwind-merge";
127
+ import { createTV, type TWMergeConfig } from "tailwind-variants";
128
+
129
+ const mergeConfig = {
130
+ extend: {
131
+ classGroups: {
132
+ elevation: ["elevation-low", "elevation-high"],
133
+ },
134
+ },
135
+ } satisfies TWMergeConfig;
136
+
137
+ extendTailwindMerge(mergeConfig);
138
+ createTV({ twMergeConfig: mergeConfig });
139
+ ```
140
+
141
+ For `createTailwindMerge`, move the custom parts of the factory into `{ extend, override }` and pass
142
+ that object. Merge functions and full default configs cannot be passed directly.
143
+
144
+ ## Utility Functions
145
+
146
+ | Function | Behavior |
147
+ | --------- | ---------------------------------------------------- |
148
+ | `cx` | Concatenate class names (no merging) |
149
+ | `cn` | Concatenate and merge with the default config |
150
+ | `cnMerge` | Concatenate and merge, with optional per-call config |
151
+
152
+ ```js
153
+ import { cx, cn, cnMerge } from "tailwind-variants";
154
+
155
+ cx("px-2", "px-4"); // => "px-2 px-4"
156
+ cn("px-2", "px-4"); // => "px-4"
157
+ cnMerge("px-2", "px-4")({ twMerge: false }); // => "px-2 px-4"
158
+ ```
159
+
160
+ ## Documentation
161
+
162
+ For full documentation, visit [tailwind-variants.org](https://tailwind-variants.org).
163
+
107
164
  ## Acknowledgements
108
165
 
109
166
  - [**cva**](https://github.com/joe-bell/cva) ([Joe Bell](https://github.com/joe-bell))
110
- This project as started as an extension of Joe's work on `cva` a great tool for generating variants for a single element with Tailwind CSS. Big shoutout to [Joe Bell](https://github.com/joe-bell) and [contributors](https://github.com/joe-bell/cva/graphs/contributors) you guys rock! 🤘 - we recommend to use `cva` if don't need any of the **Tailwind Variants** features listed [here](https://www.tailwind-variants.org/docs/comparison).
167
+ This project started as an extension of Joe's work on `cva` a great tool for generating variants
168
+ for a single element with Tailwind CSS. Big shoutout to [Joe Bell](https://github.com/joe-bell) and
169
+ [contributors](https://github.com/joe-bell/cva/graphs/contributors)! If you don't need the
170
+ **Tailwind Variants** features listed
171
+ [here](https://www.tailwind-variants.org/docs/comparison), we recommend `cva`.
111
172
 
112
- - [**Stitches**](https://stitches.dev/) ([Modulz](https://modulz.app))
113
- The pioneers of the `variants` API movement. Inmense thanks to [Modulz](https://modulz.app) for their work on Stitches and the community around it. 🙏
173
+ - [**Stitches**](https://stitches.dev/) ([Modulz](https://modulz.app))
174
+ The pioneers of the `variants` API movement. Immense thanks to [Modulz](https://modulz.app) for
175
+ their work on Stitches and the community around it.
176
+
177
+ - [**tailwind-merge**](https://github.com/dcastil/tailwind-merge), [**clsx**](https://github.com/lukeed/clsx), and [**cnfast**](https://github.com/aidenybai/cnfast)
178
+ Conflict resolution draws on ideas and MIT-licensed work from these projects.
114
179
 
115
180
  ## Community
116
181
 
117
- We're excited to see the community adopt HeroUI, raise issues, and provide feedback. Whether it's a feature request, bug report, or a project to showcase, please get involved!
182
+ We're excited to see the community adopt HeroUI, raise issues, and provide feedback. Whether it's a
183
+ feature request, bug report, or a project to showcase, please get involved!
118
184
 
119
185
  - [Discord](https://discord.gg/9b6yyZKmH4)
120
186
  - [Twitter](https://twitter.com/getnextui)
@@ -124,17 +190,15 @@ We're excited to see the community adopt HeroUI, raise issues, and provide feedb
124
190
 
125
191
  Contributions are always welcome!
126
192
 
127
- Please follow our [contributing guidelines](./CONTRIBUTING.md).
128
-
129
- Please adhere to this project's [CODE_OF_CONDUCT](./CODE_OF_CONDUCT.md).
193
+ - [Contributing guidelines](./CONTRIBUTING.md)
194
+ - [Code of conduct](./CODE_OF_CONDUCT.md)
195
+ - [Security policy](./SECURITY.md)
130
196
 
131
197
  ## Authors
132
198
 
133
- - Junior garcia ([@jrgarciadev](https://github.com/jrgaciadev))
199
+ - Junior Garcia ([@jrgarciadev](https://github.com/jrgarciadev))
134
200
  - Tianen Pang ([@tianenpang](https://github.com/tianenpang))
135
201
 
136
202
  ## License
137
203
 
138
- Licensed under the MIT License.
139
-
140
- See [LICENSE](./LICENSE.md) for more information.
204
+ Licensed under the MIT License. See [LICENSE](./LICENSE.md) for more information.
@@ -0,0 +1,160 @@
1
+ 'use strict';
2
+
3
+ // src/internal/join-class-value.ts
4
+ var isArray = Array.isArray;
5
+ var joinClassValue = (value) => {
6
+ if (!value && value !== 0 && value !== 0n) return "";
7
+ if (typeof value === "string") return value;
8
+ if (typeof value === "number") {
9
+ if (value !== value) return "";
10
+ return "" + value;
11
+ }
12
+ if (typeof value === "bigint") return "" + value;
13
+ let result = "";
14
+ if (isArray(value)) {
15
+ const length = value.length;
16
+ for (let index = 0; index < length; index++) {
17
+ const item = value[index];
18
+ if (!item && item !== 0 && item !== 0n) continue;
19
+ const resolved = typeof item === "string" ? item : joinClassValue(item);
20
+ if (resolved) {
21
+ if (result) result += " ";
22
+ result += resolved;
23
+ }
24
+ }
25
+ return result;
26
+ }
27
+ if (typeof value === "object") {
28
+ for (const key in value) {
29
+ if (value[key]) {
30
+ if (result) result += " ";
31
+ result += key;
32
+ }
33
+ }
34
+ }
35
+ return result;
36
+ };
37
+
38
+ // src/utils.ts
39
+ var SPACE_REGEX = /\s+/g;
40
+ var isArray2 = Array.isArray;
41
+ var removeExtraSpaces = (str) => {
42
+ if (typeof str !== "string" || !str) return str;
43
+ return str.replace(SPACE_REGEX, " ").trim();
44
+ };
45
+ var stringNeedsNormalize = (str) => {
46
+ const len = str.length;
47
+ if (len === 0) return false;
48
+ const first = str.charCodeAt(0);
49
+ const last = str.charCodeAt(len - 1);
50
+ if (first === 32 || last === 32 || first >= 9 && first <= 13 || first === 160 || last >= 9 && last <= 13 || last === 160) {
51
+ return true;
52
+ }
53
+ for (let i = 0; i < len; i++) {
54
+ const code = str.charCodeAt(i);
55
+ if (code >= 9 && code <= 13 || code === 160) return true;
56
+ if (code === 32 && i + 1 < len && str.charCodeAt(i + 1) === 32) return true;
57
+ }
58
+ return false;
59
+ };
60
+ var cx = (...classnames) => {
61
+ const result = joinClassValue(classnames);
62
+ if (!result) return void 0;
63
+ return stringNeedsNormalize(result) ? removeExtraSpaces(result) : result;
64
+ };
65
+ var falsyToString = (value) => value === false ? "false" : value === true ? "true" : value === 0 ? "0" : value;
66
+ var isEmptyObject = (obj) => {
67
+ if (!obj || typeof obj !== "object") return true;
68
+ for (const _ in obj) return false;
69
+ return true;
70
+ };
71
+ var isEqual = (obj1, obj2) => {
72
+ if (obj1 === obj2) return true;
73
+ if (!obj1 || !obj2) return false;
74
+ const record1 = obj1;
75
+ const record2 = obj2;
76
+ const keys1 = Object.keys(record1);
77
+ const keys2 = Object.keys(record2);
78
+ if (keys1.length !== keys2.length) return false;
79
+ for (let i = 0; i < keys1.length; i++) {
80
+ const key = keys1[i];
81
+ if (!keys2.includes(key)) return false;
82
+ if (record1[key] !== record2[key]) return false;
83
+ }
84
+ return true;
85
+ };
86
+ var isBoolean = (value) => value === true || value === false;
87
+ var joinObjects = (obj1, obj2) => {
88
+ const target = obj1;
89
+ for (const key in obj2) {
90
+ if (Object.hasOwn(obj2, key)) {
91
+ const val2 = obj2[key];
92
+ if (key in target) {
93
+ target[key] = cx(target[key], val2);
94
+ } else {
95
+ target[key] = val2;
96
+ }
97
+ }
98
+ }
99
+ return obj1;
100
+ };
101
+ var flat = (arr, target) => {
102
+ for (let i = 0; i < arr.length; i++) {
103
+ const el = arr[i];
104
+ if (isArray2(el)) flat(el, target);
105
+ else if (el) target.push(el);
106
+ }
107
+ };
108
+ function flatArray(arr) {
109
+ const flattened = [];
110
+ flat(arr, flattened);
111
+ return flattened;
112
+ }
113
+ var flatMergeArrays = (...arrays) => {
114
+ const result = [];
115
+ flat(arrays, result);
116
+ const filtered = [];
117
+ for (let i = 0; i < result.length; i++) {
118
+ if (result[i]) filtered.push(result[i]);
119
+ }
120
+ return filtered;
121
+ };
122
+ var mergeObjects = (obj1, obj2) => {
123
+ const record1 = obj1;
124
+ const record2 = obj2;
125
+ const result = {};
126
+ for (const key in record1) {
127
+ const val1 = record1[key];
128
+ if (key in record2) {
129
+ const val2 = record2[key];
130
+ if (isArray2(val1) || isArray2(val2)) {
131
+ result[key] = flatMergeArrays(val2, val1);
132
+ } else if (typeof val1 === "object" && typeof val2 === "object" && val1 && val2) {
133
+ result[key] = mergeObjects(val1, val2);
134
+ } else {
135
+ result[key] = val2 + " " + val1;
136
+ }
137
+ } else {
138
+ result[key] = val1;
139
+ }
140
+ }
141
+ for (const key in record2) {
142
+ if (!(key in record1)) {
143
+ result[key] = record2[key];
144
+ }
145
+ }
146
+ return result;
147
+ };
148
+
149
+ exports.cx = cx;
150
+ exports.falsyToString = falsyToString;
151
+ exports.flat = flat;
152
+ exports.flatArray = flatArray;
153
+ exports.flatMergeArrays = flatMergeArrays;
154
+ exports.isBoolean = isBoolean;
155
+ exports.isEmptyObject = isEmptyObject;
156
+ exports.isEqual = isEqual;
157
+ exports.joinClassValue = joinClassValue;
158
+ exports.joinObjects = joinObjects;
159
+ exports.mergeObjects = mergeObjects;
160
+ exports.removeExtraSpaces = removeExtraSpaces;