classname-variants 1.4.1 → 1.6.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/.claude/settings.local.json +12 -0
- package/README.md +23 -1
- package/lib/example/preact.js +1 -1
- package/lib/example/react.d.ts +6 -0
- package/lib/example/react.js +7 -1
- package/lib/index.d.ts +3 -0
- package/lib/index.js +7 -4
- package/lib/preact.d.ts +8 -1
- package/lib/preact.js +5 -5
- package/lib/react.d.ts +8 -1
- package/lib/react.js +5 -5
- package/llms.txt +163 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,8 @@ Library to create type-safe components that render their class name based on a s
|
|
|
7
7
|
- ⚛️ Supports React, Preact and vanilla DOM
|
|
8
8
|
- 🛡️ Fully type-safe and excellent auto completion support
|
|
9
9
|
- ✅ Supports both optional and required variants
|
|
10
|
-
-
|
|
10
|
+
- 🔗 Supports custom strategies like [tailwind-merge](https://www.npmjs.com/package/tailwind-merge)
|
|
11
|
+
- 🪶 [Light-weight](https://bundlephobia.com/package/classname-variants) without any dependencies
|
|
11
12
|
|
|
12
13
|

|
|
13
14
|
|
|
@@ -232,6 +233,23 @@ The component can then be rendered as button or as anchor or even as custom comp
|
|
|
232
233
|
</>
|
|
233
234
|
```
|
|
234
235
|
|
|
236
|
+
### Using a custom strategy to combine class names
|
|
237
|
+
|
|
238
|
+
The built-in strategy for combining multiple class names into one string is simple and straightforward:
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
(classes) => classes.filter(Boolean).join(" ");
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
If you [want](https://github.com/dcastil/tailwind-merge/blob/main/docs/when-and-how-to-use-it.md), you can use a custom strategy like tailwind-merge instead:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
import { classNames } from "classname-variants";
|
|
248
|
+
import { twMerge } from "tailwind-merge";
|
|
249
|
+
|
|
250
|
+
classNames.combine = twMerge;
|
|
251
|
+
```
|
|
252
|
+
|
|
235
253
|
# Tailwind IntelliSense
|
|
236
254
|
|
|
237
255
|
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.
|
|
@@ -263,6 +281,10 @@ You can then add the following line to your `settings.json`:
|
|
|
263
281
|
|
|
264
282
|
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).
|
|
265
283
|
|
|
284
|
+
# For AI Assistants
|
|
285
|
+
|
|
286
|
+
For comprehensive technical documentation optimized for LLMs, see [`llms.txt`](./llms.txt).
|
|
287
|
+
|
|
266
288
|
# License
|
|
267
289
|
|
|
268
290
|
MIT
|
package/lib/example/preact.js
CHANGED
|
@@ -63,7 +63,7 @@ export function WithErrors() {
|
|
|
63
63
|
}
|
|
64
64
|
export function PreactApp() {
|
|
65
65
|
return (h("div", { className: "flex justify-center items-center pt-8 gap-4 flex-wrap" },
|
|
66
|
-
h(Button, { size: "medium", onClick: console.log }, "
|
|
66
|
+
h(Button, { size: "medium", onClick: console.log }, "Neutral"),
|
|
67
67
|
h(Button, { size: "medium", rounded: true }, "Neutral + Rounded"),
|
|
68
68
|
h(Button, { size: "medium", color: "accent", outlined: true }, "Accent + Outlined"),
|
|
69
69
|
h(Button, { size: "medium", color: "accent", disabled: true }, "Disabled"),
|
package/lib/example/react.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
export declare const StyledWithoutVariants: <As extends React.ElementType<any> = "div">(props: {
|
|
3
|
+
as?: As | undefined;
|
|
4
|
+
} & Omit<React.ComponentProps<As>, "as"> & {} & {}) => React.ReactElement<any, string | React.JSXElementConstructor<any>> | null;
|
|
5
|
+
export declare const TestBaseOnly: <As extends React.ElementType<any> = "div">(props: {
|
|
6
|
+
as?: As | undefined;
|
|
7
|
+
} & Omit<React.ComponentProps<As>, "as"> & {} & {}) => React.ReactElement<any, string | React.JSXElementConstructor<any>> | null;
|
|
2
8
|
export declare const ExpectErrors: <As extends React.ElementType<any> = "div">(props: {
|
|
3
9
|
as?: As | undefined;
|
|
4
10
|
} & Omit<React.ComponentProps<As>, "as" | "color"> & {
|
package/lib/example/react.js
CHANGED
|
@@ -34,6 +34,12 @@ const Button = styled("button", {
|
|
|
34
34
|
color: "neutral",
|
|
35
35
|
},
|
|
36
36
|
});
|
|
37
|
+
export const StyledWithoutVariants = styled("div", {
|
|
38
|
+
base: "bg-white",
|
|
39
|
+
});
|
|
40
|
+
export const TestBaseOnly = styled("div", {
|
|
41
|
+
base: "text-red-500 font-bold",
|
|
42
|
+
});
|
|
37
43
|
export const ExpectErrors = styled("div", {
|
|
38
44
|
variants: {
|
|
39
45
|
color: {
|
|
@@ -63,7 +69,7 @@ export function WithErrors() {
|
|
|
63
69
|
}
|
|
64
70
|
export function ReactApp() {
|
|
65
71
|
return (React.createElement("div", { className: "flex justify-center items-center pt-8 gap-4 flex-wrap" },
|
|
66
|
-
React.createElement(Button, { size: "medium", onClick: console.log }, "
|
|
72
|
+
React.createElement(Button, { size: "medium", onClick: console.log }, "Neutral"),
|
|
67
73
|
React.createElement(Button, { size: "medium", rounded: true }, "Neutral + Rounded"),
|
|
68
74
|
React.createElement(Button, { size: "medium", color: "accent", outlined: true }, "Accent + Outlined"),
|
|
69
75
|
React.createElement(Button, { size: "medium", color: "accent", disabled: true }, "Disabled"),
|
package/lib/index.d.ts
CHANGED
|
@@ -101,6 +101,9 @@ export type VariantOptions<C extends VariantsConfig<V>, V extends Variants = C["
|
|
|
101
101
|
export type Simplify<T> = {
|
|
102
102
|
[K in keyof T]: T[K];
|
|
103
103
|
};
|
|
104
|
+
export declare const classNames: {
|
|
105
|
+
combine: (...classes: Array<string | undefined | null | false | 0>) => string;
|
|
106
|
+
};
|
|
104
107
|
export declare function variants<C extends VariantsConfig<V>, V extends Variants = C["variants"]>(config: Simplify<C>): (props: VariantOptions<C>) => string;
|
|
105
108
|
/**
|
|
106
109
|
* No-op function to mark template literals as tailwind strings.
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export const classNames = {
|
|
2
|
+
combine: (...classes) => classes.filter(Boolean).join(" "),
|
|
3
|
+
};
|
|
1
4
|
export function variants(config) {
|
|
2
5
|
const { base, variants, compoundVariants, defaultVariants } = config;
|
|
3
6
|
const isBooleanVariant = (name) => {
|
|
@@ -6,7 +9,7 @@ export function variants(config) {
|
|
|
6
9
|
};
|
|
7
10
|
return (props) => {
|
|
8
11
|
var _a;
|
|
9
|
-
const
|
|
12
|
+
const classes = base ? [base] : [];
|
|
10
13
|
const getSelected = (name) => {
|
|
11
14
|
var _a, _b;
|
|
12
15
|
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);
|
|
@@ -14,15 +17,15 @@ export function variants(config) {
|
|
|
14
17
|
for (let name in variants) {
|
|
15
18
|
const selected = getSelected(name);
|
|
16
19
|
if (selected !== undefined)
|
|
17
|
-
|
|
20
|
+
classes.push((_a = variants[name]) === null || _a === void 0 ? void 0 : _a[selected]);
|
|
18
21
|
}
|
|
19
22
|
for (let { variants, className } of compoundVariants !== null && compoundVariants !== void 0 ? compoundVariants : []) {
|
|
20
23
|
const isSelected = (name) => getSelected(name) === variants[name];
|
|
21
24
|
if (Object.keys(variants).every(isSelected)) {
|
|
22
|
-
|
|
25
|
+
classes.push(className);
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
|
-
return
|
|
28
|
+
return classNames.combine(...classes);
|
|
26
29
|
};
|
|
27
30
|
}
|
|
28
31
|
/**
|
package/lib/preact.d.ts
CHANGED
|
@@ -20,7 +20,14 @@ type AsProps<T extends ElementType = ElementType> = {
|
|
|
20
20
|
as?: T;
|
|
21
21
|
};
|
|
22
22
|
type PolymorphicComponentProps<V, T extends ElementType> = AsProps<T> & Omit<ComponentProps<T>, "as" | keyof V> & V;
|
|
23
|
-
export declare function styled<T extends ElementType, C extends VariantsConfig<V>, V extends Variants = VariantsOf<C, C["variants"]>>(type: T, config: string |
|
|
23
|
+
export declare function styled<T extends ElementType, C extends VariantsConfig<V>, V extends Variants = VariantsOf<C, C["variants"]>>(type: T, config: string | {
|
|
24
|
+
base: string;
|
|
25
|
+
} | Simplify<C>): <As extends ElementType = T>(props: PolymorphicComponentProps<typeof config extends string ? {} : typeof config extends {
|
|
26
|
+
base: string;
|
|
27
|
+
variants?: undefined;
|
|
28
|
+
compoundVariants?: undefined;
|
|
29
|
+
defaultVariants?: undefined;
|
|
30
|
+
} ? {} : VariantOptions<C>, As>) => VNode | null;
|
|
24
31
|
/**
|
|
25
32
|
* No-op function to mark template literals as tailwind strings.
|
|
26
33
|
*/
|
package/lib/preact.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { h } from "preact";
|
|
2
2
|
import { forwardRef } from "preact/compat";
|
|
3
|
-
import { variants, } from "./index.js";
|
|
3
|
+
import { variants, classNames, } from "./index.js";
|
|
4
4
|
export function variantProps(config) {
|
|
5
5
|
const variantClassName = variants(config);
|
|
6
6
|
return (props) => {
|
|
@@ -12,16 +12,16 @@ export function variantProps(config) {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
// Add the optionally passed class/className prop for chaining
|
|
15
|
-
result.class =
|
|
16
|
-
.filter(Boolean)
|
|
17
|
-
.join(" ");
|
|
15
|
+
result.class = classNames.combine(variantClassName(props), props.class, props.className);
|
|
18
16
|
return result;
|
|
19
17
|
};
|
|
20
18
|
}
|
|
21
19
|
export function styled(type, config) {
|
|
22
20
|
const styledProps = typeof config === "string"
|
|
23
21
|
? variantProps({ base: config, variants: {} })
|
|
24
|
-
:
|
|
22
|
+
: "variants" in config
|
|
23
|
+
? variantProps(config)
|
|
24
|
+
: variantProps({ base: config.base, variants: {} });
|
|
25
25
|
const Component = forwardRef(({ as, ...props }, ref) => {
|
|
26
26
|
return h(as !== null && as !== void 0 ? as : type, { ...styledProps(props), ref });
|
|
27
27
|
});
|
package/lib/react.d.ts
CHANGED
|
@@ -18,7 +18,14 @@ type AsProps<T extends ElementType = ElementType> = {
|
|
|
18
18
|
as?: T;
|
|
19
19
|
};
|
|
20
20
|
type PolymorphicComponentProps<V, T extends ElementType> = AsProps<T> & Omit<ComponentProps<T>, "as" | keyof V> & V;
|
|
21
|
-
export declare function styled<T extends ElementType, C extends VariantsConfig<V>, V extends Variants = VariantsOf<C, C["variants"]>>(type: T, config: string |
|
|
21
|
+
export declare function styled<T extends ElementType, C extends VariantsConfig<V>, V extends Variants = VariantsOf<C, C["variants"]>>(type: T, config: string | {
|
|
22
|
+
base: string;
|
|
23
|
+
} | Simplify<C>): <As extends ElementType = T>(props: PolymorphicComponentProps<typeof config extends string ? {} : typeof config extends {
|
|
24
|
+
base: string;
|
|
25
|
+
variants?: undefined;
|
|
26
|
+
compoundVariants?: undefined;
|
|
27
|
+
defaultVariants?: undefined;
|
|
28
|
+
} ? {} : VariantOptions<C>, As>) => ReactElement | null;
|
|
22
29
|
/**
|
|
23
30
|
* No-op function to mark template literals as tailwind strings.
|
|
24
31
|
*/
|
package/lib/react.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createElement, forwardRef, } from "react";
|
|
2
|
-
import { variants, } from "./index.js";
|
|
2
|
+
import { variants, classNames, } from "./index.js";
|
|
3
3
|
export function variantProps(config) {
|
|
4
4
|
const variantClassName = variants(config);
|
|
5
5
|
return (props) => {
|
|
@@ -11,16 +11,16 @@ export function variantProps(config) {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
// Add the optionally passed className prop for chaining
|
|
14
|
-
result.className =
|
|
15
|
-
.filter(Boolean)
|
|
16
|
-
.join(" ");
|
|
14
|
+
result.className = classNames.combine(variantClassName(props), props.className);
|
|
17
15
|
return result;
|
|
18
16
|
};
|
|
19
17
|
}
|
|
20
18
|
export function styled(type, config) {
|
|
21
19
|
const styledProps = typeof config === "string"
|
|
22
20
|
? variantProps({ base: config, variants: {} })
|
|
23
|
-
:
|
|
21
|
+
: "variants" in config
|
|
22
|
+
? variantProps(config)
|
|
23
|
+
: variantProps({ base: config.base, variants: {} });
|
|
24
24
|
const Component = forwardRef(({ as, ...props }, ref) => {
|
|
25
25
|
return createElement(as !== null && as !== void 0 ? as : type, { ...styledProps(props), ref });
|
|
26
26
|
});
|
package/llms.txt
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# classname-variants
|
|
2
|
+
|
|
3
|
+
A TypeScript library for creating type-safe variant-based className generators for React, Preact, and vanilla DOM.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
This library provides a variant API for generating class names based on component props. It's designed to work with CSS frameworks like Tailwind CSS but supports any class naming system. The core concept is defining variants (different states/styles) and having the library generate the appropriate class names based on provided props.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
### Core Module (src/index.ts)
|
|
12
|
+
- `variants()` - Core function that creates a className generator based on variant configuration
|
|
13
|
+
- `VariantsConfig` - TypeScript interface defining the structure of variant configurations
|
|
14
|
+
- `classNames.combine` - Default strategy for combining class names (can be overridden with tools like tailwind-merge)
|
|
15
|
+
- `tw` - Tagged template literal helper for Tailwind CSS IntelliSense
|
|
16
|
+
|
|
17
|
+
### Framework Integrations
|
|
18
|
+
- `src/react.ts` - React-specific bindings with `styled()` and `variantProps()` functions
|
|
19
|
+
- `src/preact.ts` - Preact-specific bindings (similar to React but uses Preact's JSX)
|
|
20
|
+
|
|
21
|
+
### Key Features
|
|
22
|
+
- Type-safe variant definitions with full TypeScript inference
|
|
23
|
+
- Support for required and optional variants
|
|
24
|
+
- Boolean variants (true/false)
|
|
25
|
+
- Default variants
|
|
26
|
+
- Compound variants (combinations of multiple variant states)
|
|
27
|
+
- Polymorphic components with "as" prop
|
|
28
|
+
- Custom class name combination strategies
|
|
29
|
+
|
|
30
|
+
## File Structure
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
src/
|
|
34
|
+
├── index.ts # Core variant logic and types
|
|
35
|
+
├── react.ts # React bindings (styled, variantProps)
|
|
36
|
+
├── preact.ts # Preact bindings (similar to React)
|
|
37
|
+
└── example/ # Example implementations
|
|
38
|
+
├── index.ts # Vanilla example
|
|
39
|
+
├── react.tsx # React example
|
|
40
|
+
└── preact.tsx # Preact example
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Package Structure
|
|
44
|
+
|
|
45
|
+
- ESM modules only
|
|
46
|
+
- Multiple entry points: main, react, preact
|
|
47
|
+
- Fully typed with TypeScript declarations
|
|
48
|
+
|
|
49
|
+
## Key Concepts
|
|
50
|
+
|
|
51
|
+
### Variant Configuration
|
|
52
|
+
```ts
|
|
53
|
+
{
|
|
54
|
+
base?: string, // Always applied classes
|
|
55
|
+
variants: { // Variant definitions
|
|
56
|
+
[variantName]: {
|
|
57
|
+
[optionName]: "class-names"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
defaultVariants?: {...}, // Default values
|
|
61
|
+
compoundVariants?: [...] // Conditional class combinations
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### TypeScript Magic
|
|
66
|
+
The library uses advanced TypeScript features to:
|
|
67
|
+
- Infer variant prop types from configuration
|
|
68
|
+
- Make variants required unless they have defaults or are boolean
|
|
69
|
+
- Convert "true"/"false" keys to boolean props
|
|
70
|
+
- Provide full autocomplete and type checking
|
|
71
|
+
|
|
72
|
+
## Usage Examples
|
|
73
|
+
|
|
74
|
+
### NPM Installation & Imports
|
|
75
|
+
```bash
|
|
76
|
+
npm install classname-variants
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
// Vanilla/core
|
|
81
|
+
import { variants, tw } from "classname-variants"
|
|
82
|
+
|
|
83
|
+
// React
|
|
84
|
+
import { styled, tw } from "classname-variants/react"
|
|
85
|
+
|
|
86
|
+
// Preact
|
|
87
|
+
import { styled, tw } from "classname-variants/preact"
|
|
88
|
+
|
|
89
|
+
// Low-level API (rarely used directly)
|
|
90
|
+
import { variantProps } from "classname-variants/react"
|
|
91
|
+
import { variantProps } from "classname-variants/preact"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Styled Components Without Variants
|
|
95
|
+
Simple string-based styling for components that don't need variants:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
const Card = styled("div", "bg-white p-4 border-2 rounded-lg");
|
|
99
|
+
const CustomCard = styled(CustomComponent, "bg-white p-4 border-2 rounded-lg");
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Polymorphic Components with "as" Prop
|
|
103
|
+
Change the underlying element/component while keeping the same styles:
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
const Button = styled("button", { variants: {...} });
|
|
107
|
+
|
|
108
|
+
// Usage
|
|
109
|
+
<Button>I'm a button</Button>
|
|
110
|
+
<Button as="a" href="/">I'm a link!</Button>
|
|
111
|
+
<Button as={Link} to="/">I'm a router Link</Button>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Tailwind CSS Integration
|
|
115
|
+
|
|
116
|
+
#### Using tailwind-merge for Conflict Resolution
|
|
117
|
+
```ts
|
|
118
|
+
import { classNames } from "classname-variants";
|
|
119
|
+
import { twMerge } from "tailwind-merge";
|
|
120
|
+
|
|
121
|
+
// Override default combination strategy
|
|
122
|
+
classNames.combine = twMerge;
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
#### IDE Support with Tagged Templates
|
|
126
|
+
```ts
|
|
127
|
+
import { variants, tw } from "classname-variants";
|
|
128
|
+
|
|
129
|
+
const button = variants({
|
|
130
|
+
base: tw`px-5 py-2 text-white`,
|
|
131
|
+
variants: {
|
|
132
|
+
color: {
|
|
133
|
+
neutral: tw`bg-slate-500 hover:bg-slate-400`,
|
|
134
|
+
accent: tw`bg-teal-500 hover:bg-teal-400`,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Add to VS Code settings.json:
|
|
141
|
+
```json
|
|
142
|
+
"tailwindCSS.experimental.classRegex": ["tw`(.+?)`"]
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Advantages vs Alternatives
|
|
146
|
+
|
|
147
|
+
### vs clsx/classnames
|
|
148
|
+
- **Type Safety**: Full TypeScript inference for variant props
|
|
149
|
+
- **Variant System**: Built-in support for component variants vs manual conditional logic
|
|
150
|
+
- **Default Values**: Automatic handling of default variants
|
|
151
|
+
- **Compound Variants**: Built-in support for variant combinations
|
|
152
|
+
|
|
153
|
+
### vs class-variance-authority (cva)
|
|
154
|
+
- **Zero Dependencies**: No external dependencies vs cva's clsx dependency
|
|
155
|
+
- **Framework Integration**: Built-in React/Preact components with `styled()` API
|
|
156
|
+
- **Polymorphic Support**: Native "as" prop support for component flexibility
|
|
157
|
+
- **TypeScript Focus**: Designed TypeScript-first with advanced type inference
|
|
158
|
+
|
|
159
|
+
## Dependencies
|
|
160
|
+
|
|
161
|
+
- Zero runtime dependencies
|
|
162
|
+
- Development dependencies: React, Preact, TypeScript
|
|
163
|
+
- Designed to work with any CSS framework (Tailwind, CSS modules, etc.)
|