@ntatoud/styra 0.1.0 → 0.2.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 +8 -0
- package/dist/index.d.mts +10 -7
- package/dist/index.mjs +41 -7
- package/package.json +5 -2
- package/skills/styra-compound-variants/SKILL.md +305 -0
- package/skills/styra-define-variants/SKILL.md +218 -0
- package/skills/styra-getting-started/SKILL.md +252 -0
- package/skills/styra-migrate-from-cva/SKILL.md +314 -0
- package/skills/styra-shadcn-integration/SKILL.md +246 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: styra-getting-started
|
|
3
|
+
description: >
|
|
4
|
+
Full setup guide for @ntatoud/styra v0.1.0 — type-safe class variance builder.
|
|
5
|
+
Covers install, styra(base).variants().defaults() builder chain, class/className
|
|
6
|
+
override props with clsx syntax, createStyra({ merge: twMerge }) for Tailwind
|
|
7
|
+
projects, cn utility, and VariantProps type helper for component prop types.
|
|
8
|
+
Preempts: calling .variants() twice (runtime throw), wiring twMerge outside the factory.
|
|
9
|
+
type: lifecycle
|
|
10
|
+
library: "@ntatoud/styra"
|
|
11
|
+
library_version: "0.1.0"
|
|
12
|
+
sources:
|
|
13
|
+
- "ntatoud/styra:packages/styra/src/index.ts"
|
|
14
|
+
- "ntatoud/styra:packages/styra/README.md"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Getting Started with @ntatoud/styra
|
|
18
|
+
|
|
19
|
+
## 1. Setup
|
|
20
|
+
|
|
21
|
+
Install the package — no peer dependencies required.
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
# npm
|
|
25
|
+
npm install @ntatoud/styra
|
|
26
|
+
|
|
27
|
+
# pnpm
|
|
28
|
+
pnpm add @ntatoud/styra
|
|
29
|
+
|
|
30
|
+
# yarn
|
|
31
|
+
yarn add @ntatoud/styra
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Minimum viable example — a typed button component:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
// button.ts
|
|
38
|
+
import { styra, type VariantProps } from "@ntatoud/styra";
|
|
39
|
+
|
|
40
|
+
export const button = styra("btn")
|
|
41
|
+
.variants({
|
|
42
|
+
size: { sm: "text-sm px-2 py-1", md: "text-base px-4 py-2", lg: "text-lg px-6 py-3" },
|
|
43
|
+
intent: { primary: "bg-blue-600 text-white", danger: "bg-red-600 text-white" },
|
|
44
|
+
})
|
|
45
|
+
.defaults({ size: "md" });
|
|
46
|
+
|
|
47
|
+
export type ButtonProps = VariantProps<typeof button>;
|
|
48
|
+
// { size?: "sm" | "md" | "lg"; intent: "primary" | "danger" }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// usage
|
|
53
|
+
import { button } from "./button";
|
|
54
|
+
|
|
55
|
+
button({ intent: "primary" });
|
|
56
|
+
// → "btn text-base px-4 py-2 bg-blue-600 text-white"
|
|
57
|
+
|
|
58
|
+
button({ size: "sm", intent: "danger" });
|
|
59
|
+
// → "btn text-sm px-2 py-1 bg-red-600 text-white"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 2. Core Patterns
|
|
65
|
+
|
|
66
|
+
### Pattern 1 — Compound variants
|
|
67
|
+
|
|
68
|
+
`.compound()` applies extra classes when multiple variant conditions are met simultaneously.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { styra } from "@ntatoud/styra";
|
|
72
|
+
|
|
73
|
+
const button = styra("btn")
|
|
74
|
+
.variants({
|
|
75
|
+
size: { sm: "text-sm", lg: "text-lg" },
|
|
76
|
+
color: { red: "bg-red-500", blue: "bg-blue-500" },
|
|
77
|
+
})
|
|
78
|
+
.defaults({ size: "sm" })
|
|
79
|
+
.compound([{ size: "sm", color: "red", class: "ring-2 ring-red-300" }]);
|
|
80
|
+
|
|
81
|
+
button({ color: "red" });
|
|
82
|
+
// → "btn text-sm bg-red-500 ring-2 ring-red-300"
|
|
83
|
+
|
|
84
|
+
button({ size: "lg", color: "red" });
|
|
85
|
+
// → "btn text-lg bg-red-500" (compound rule does not match)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Pattern 2 — class and className override props
|
|
89
|
+
|
|
90
|
+
Both `class` and `className` are accepted. They support clsx syntax: strings, arrays, objects, and functions.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { styra } from "@ntatoud/styra";
|
|
94
|
+
|
|
95
|
+
const badge = styra("badge").variants({ color: { green: "bg-green-500", gray: "bg-gray-300" } });
|
|
96
|
+
|
|
97
|
+
badge({ color: "green", class: "rounded-full" });
|
|
98
|
+
// → "badge bg-green-500 rounded-full"
|
|
99
|
+
|
|
100
|
+
badge({ color: "gray", className: ["mt-2", { hidden: false, block: true }] });
|
|
101
|
+
// → "badge bg-gray-300 mt-2 block"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Pattern 3 — Boolean shorthand variants
|
|
105
|
+
|
|
106
|
+
Pass a plain string instead of a `{ true: "...", false: "..." }` map to get an opt-in boolean prop.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { styra } from "@ntatoud/styra";
|
|
110
|
+
|
|
111
|
+
const input = styra("input border").variants({
|
|
112
|
+
disabled: "opacity-50 cursor-not-allowed",
|
|
113
|
+
full: "w-full",
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
input({ disabled: true, full: true });
|
|
117
|
+
// → "input border opacity-50 cursor-not-allowed w-full"
|
|
118
|
+
|
|
119
|
+
input({});
|
|
120
|
+
// → "input border"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Pattern 4 — Tailwind Merge via createStyra
|
|
124
|
+
|
|
125
|
+
Create a project-scoped `styra` and `cn` that pipe every output through `twMerge`. Export them once and import everywhere.
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
// lib/styra.ts
|
|
129
|
+
import { createStyra } from "@ntatoud/styra";
|
|
130
|
+
import { twMerge } from "tailwind-merge";
|
|
131
|
+
|
|
132
|
+
export const { styra, cn } = createStyra({ merge: twMerge });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
// components/card.ts
|
|
137
|
+
import { styra, cn } from "@/lib/styra";
|
|
138
|
+
import type { VariantProps } from "@ntatoud/styra";
|
|
139
|
+
|
|
140
|
+
export const card = styra("p-4 rounded-lg")
|
|
141
|
+
.variants({ shadow: { sm: "shadow-sm", lg: "shadow-lg" } })
|
|
142
|
+
.defaults({ shadow: "sm" });
|
|
143
|
+
|
|
144
|
+
export type CardProps = VariantProps<typeof card>;
|
|
145
|
+
|
|
146
|
+
// cn accepts the same clsx-compatible syntax and uses twMerge internally
|
|
147
|
+
const classes = cn("p-4", ["rounded", { "border border-gray-200": true }]);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 3. Common Mistakes
|
|
153
|
+
|
|
154
|
+
### [CRITICAL] Calling .variants() twice on the same builder
|
|
155
|
+
|
|
156
|
+
**Wrong**
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { styra } from "@ntatoud/styra";
|
|
160
|
+
|
|
161
|
+
const button = styra("btn")
|
|
162
|
+
.variants({ size: { sm: "text-sm", lg: "text-lg" } })
|
|
163
|
+
.variants({ color: { red: "bg-red-500" } }); // throws at runtime
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Correct**
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import { styra } from "@ntatoud/styra";
|
|
170
|
+
|
|
171
|
+
const button = styra("btn").variants({
|
|
172
|
+
size: { sm: "text-sm", lg: "text-lg" },
|
|
173
|
+
color: { red: "bg-red-500" },
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`.variants()` can only be called once per builder. Calling it a second time throws `"styra: .variants() can only be called once per builder"`. Declare all variants in a single call.
|
|
178
|
+
|
|
179
|
+
Source: `ntatoud/styra:packages/styra/src/index.ts`
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
### [HIGH] Wiring twMerge outside the factory
|
|
184
|
+
|
|
185
|
+
**Wrong**
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
import { styra } from "@ntatoud/styra";
|
|
189
|
+
import { twMerge } from "tailwind-merge";
|
|
190
|
+
|
|
191
|
+
const button = styra("btn").variants({ size: { sm: "p-1", lg: "p-4" } });
|
|
192
|
+
|
|
193
|
+
// Bypasses the builder's class/className resolution — merges raw output only
|
|
194
|
+
const classes = twMerge(button({ size: "sm", class: "p-2" }));
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Correct**
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import { createStyra } from "@ntatoud/styra";
|
|
201
|
+
import { twMerge } from "tailwind-merge";
|
|
202
|
+
|
|
203
|
+
export const { styra, cn } = createStyra({ merge: twMerge });
|
|
204
|
+
|
|
205
|
+
const button = styra("btn").variants({ size: { sm: "p-1", lg: "p-4" } });
|
|
206
|
+
|
|
207
|
+
// twMerge runs on the full resolved class string, including class/className overrides
|
|
208
|
+
button({ size: "sm", class: "p-2" });
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Wrapping the output of `button(...)` in `twMerge()` directly means the merge runs after resolution but ignores how override props interact with base and variant classes. Use `createStyra({ merge: twMerge })` so every resolved output — including `class`/`className` overrides — is merged correctly.
|
|
212
|
+
|
|
213
|
+
Source: `ntatoud/styra:packages/styra/src/index.ts`
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
### [MEDIUM] Using raw inferred props instead of VariantProps for component typing
|
|
218
|
+
|
|
219
|
+
**Wrong**
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
import { styra } from "@ntatoud/styra";
|
|
223
|
+
|
|
224
|
+
const button = styra("btn").variants({ intent: { primary: "bg-blue-600" } });
|
|
225
|
+
|
|
226
|
+
// Parameters<typeof button>[0] includes class and className — leaks internal props
|
|
227
|
+
type ButtonProps = Parameters<typeof button>[0];
|
|
228
|
+
|
|
229
|
+
function Button(props: ButtonProps) {
|
|
230
|
+
/* ... */
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Correct**
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import { styra, type VariantProps } from "@ntatoud/styra";
|
|
238
|
+
|
|
239
|
+
const button = styra("btn").variants({ intent: { primary: "bg-blue-600" } });
|
|
240
|
+
|
|
241
|
+
// VariantProps strips class and className — only variant keys remain
|
|
242
|
+
export type ButtonProps = VariantProps<typeof button>;
|
|
243
|
+
// { intent: "primary" }
|
|
244
|
+
|
|
245
|
+
function Button(props: ButtonProps) {
|
|
246
|
+
/* ... */
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
`VariantProps<T>` is defined as `Omit<Parameters<T>[0], "class" | "className">`. Using raw `Parameters<T>[0]` exposes `class` and `className` as public component props, which are internal override props not meant for consumers.
|
|
251
|
+
|
|
252
|
+
Source: `ntatoud/styra:packages/styra/src/types.ts`
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: styra-migrate-from-cva
|
|
3
|
+
description: >
|
|
4
|
+
Migrate from CVA (class-variance-authority) to @ntatoud/styra. Maps cva() config object
|
|
5
|
+
syntax to styra builder chain: variants(), defaults(), compound(). Covers cx/clsx → cn,
|
|
6
|
+
VariantProps (unchanged), createStyra for merge config, boolean shorthand variants,
|
|
7
|
+
and negation in compound rules. Use when converting class-variance-authority imports
|
|
8
|
+
or cva() calls to the styra builder API.
|
|
9
|
+
type: lifecycle
|
|
10
|
+
library: "@ntatoud/styra"
|
|
11
|
+
library_version: "0.1.0"
|
|
12
|
+
sources:
|
|
13
|
+
- "ntatoud/styra:packages/styra/src/index.ts"
|
|
14
|
+
- "ntatoud/styra:packages/styra/README.md"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Migrate from CVA to styra
|
|
18
|
+
|
|
19
|
+
This skill covers replacing `class-variance-authority` with `@ntatoud/styra`. The core
|
|
20
|
+
difference: CVA uses a single config object; styra uses a builder chain.
|
|
21
|
+
|
|
22
|
+
## API Mapping
|
|
23
|
+
|
|
24
|
+
| CVA | styra |
|
|
25
|
+
| -------------------------------------------------------------- | ---------------------------------------------------- |
|
|
26
|
+
| `import { cva } from "class-variance-authority"` | `import { styra } from "@ntatoud/styra"` |
|
|
27
|
+
| `import { cx } from "class-variance-authority"` | `import { cn } from "@ntatoud/styra"` |
|
|
28
|
+
| `import { type VariantProps } from "class-variance-authority"` | `import { type VariantProps } from "@ntatoud/styra"` |
|
|
29
|
+
| `cva(base, { variants })` | `styra(base).variants({})` |
|
|
30
|
+
| `cva(base, { defaultVariants })` | `.defaults({})` |
|
|
31
|
+
| `cva(base, { compoundVariants })` | `.compound([])` |
|
|
32
|
+
| CVA + clsx + twMerge (manual) | `createStyra({ merge: twMerge })` |
|
|
33
|
+
|
|
34
|
+
`VariantProps<typeof fn>` is identical — no change in usage.
|
|
35
|
+
|
|
36
|
+
## Setup
|
|
37
|
+
|
|
38
|
+
Remove CVA, install styra:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
vp remove class-variance-authority
|
|
42
|
+
vp add @ntatoud/styra
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Patterns
|
|
46
|
+
|
|
47
|
+
### Basic variant component
|
|
48
|
+
|
|
49
|
+
Before:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
53
|
+
|
|
54
|
+
const button = cva("btn", {
|
|
55
|
+
variants: {
|
|
56
|
+
size: { sm: "text-sm", md: "text-md", lg: "text-lg" },
|
|
57
|
+
color: { red: "bg-red", blue: "bg-blue" },
|
|
58
|
+
},
|
|
59
|
+
defaultVariants: { size: "md" },
|
|
60
|
+
compoundVariants: [{ size: "sm", color: "red", class: "ring-red" }],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
type ButtonProps = VariantProps<typeof button>;
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
After:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { styra, type VariantProps } from "@ntatoud/styra";
|
|
70
|
+
|
|
71
|
+
const button = styra("btn")
|
|
72
|
+
.variants({
|
|
73
|
+
size: { sm: "text-sm", md: "text-md", lg: "text-lg" },
|
|
74
|
+
color: { red: "bg-red", blue: "bg-blue" },
|
|
75
|
+
})
|
|
76
|
+
.defaults({ size: "md" })
|
|
77
|
+
.compound([{ size: "sm", color: "red", class: "ring-red" }]);
|
|
78
|
+
|
|
79
|
+
type ButtonProps = VariantProps<typeof button>;
|
|
80
|
+
// → { size?: "sm" | "md" | "lg"; color: "red" | "blue" }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Class merging utility
|
|
84
|
+
|
|
85
|
+
Before:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { cx } from "class-variance-authority";
|
|
89
|
+
// or: import clsx from "clsx";
|
|
90
|
+
|
|
91
|
+
const classes = cx("base", condition && "extra");
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
After:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { cn } from "@ntatoud/styra";
|
|
98
|
+
|
|
99
|
+
const classes = cn("base", condition && "extra");
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Tailwind Merge integration
|
|
103
|
+
|
|
104
|
+
CVA does not natively support a merge function — projects typically wired up clsx + twMerge
|
|
105
|
+
manually alongside CVA. Styra integrates merge at creation time via `createStyra`.
|
|
106
|
+
|
|
107
|
+
Before (typical CVA + twMerge pattern):
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { cva } from "class-variance-authority";
|
|
111
|
+
import { twMerge } from "tailwind-merge";
|
|
112
|
+
import { clsx, type ClassValue } from "clsx";
|
|
113
|
+
|
|
114
|
+
function cn(...inputs: ClassValue[]) {
|
|
115
|
+
return twMerge(clsx(inputs));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const button = cva("btn rounded", {
|
|
119
|
+
variants: { size: { sm: "px-2", lg: "px-6" } },
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
After:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
// lib/styra.ts
|
|
127
|
+
import { createStyra } from "@ntatoud/styra";
|
|
128
|
+
import { twMerge } from "tailwind-merge";
|
|
129
|
+
|
|
130
|
+
export const { styra, cn } = createStyra({ merge: twMerge });
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
// button.ts
|
|
135
|
+
import { styra, cn, type VariantProps } from "./lib/styra";
|
|
136
|
+
|
|
137
|
+
const button = styra("btn rounded").variants({
|
|
138
|
+
size: { sm: "px-2", lg: "px-6" },
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
type ButtonProps = VariantProps<typeof button>;
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Import `styra` and `cn` from your local `lib/styra` module everywhere instead of from
|
|
145
|
+
`@ntatoud/styra` directly.
|
|
146
|
+
|
|
147
|
+
### Variants with no default (required prop)
|
|
148
|
+
|
|
149
|
+
If a variant has no entry in `.defaults()`, its prop is required in the inferred type.
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { styra, type VariantProps } from "@ntatoud/styra";
|
|
153
|
+
|
|
154
|
+
const badge = styra("badge")
|
|
155
|
+
.variants({
|
|
156
|
+
status: { info: "bg-blue", warn: "bg-yellow", error: "bg-red" },
|
|
157
|
+
size: { sm: "text-xs", md: "text-sm" },
|
|
158
|
+
})
|
|
159
|
+
.defaults({ size: "md" });
|
|
160
|
+
// size is optional (has default), status is required
|
|
161
|
+
|
|
162
|
+
type BadgeProps = VariantProps<typeof badge>;
|
|
163
|
+
// → { status: "info" | "warn" | "error"; size?: "sm" | "md" }
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Boolean shorthand variants (new in styra)
|
|
167
|
+
|
|
168
|
+
CVA did not support boolean variants. In styra, a variant value that is a string (not an
|
|
169
|
+
object) defines a boolean prop — the class applies when the prop is `true`.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import { styra, type VariantProps } from "@ntatoud/styra";
|
|
173
|
+
|
|
174
|
+
const button = styra("btn")
|
|
175
|
+
.variants({
|
|
176
|
+
size: { sm: "text-sm", lg: "text-lg" },
|
|
177
|
+
disabled: "opacity-50 pointer-events-none", // boolean shorthand
|
|
178
|
+
loading: "cursor-wait", // boolean shorthand
|
|
179
|
+
})
|
|
180
|
+
.defaults({ size: "sm" });
|
|
181
|
+
|
|
182
|
+
type ButtonProps = VariantProps<typeof button>;
|
|
183
|
+
// → { size?: "sm" | "lg"; disabled?: boolean; loading?: boolean }
|
|
184
|
+
|
|
185
|
+
button({ size: "lg", disabled: true });
|
|
186
|
+
// → "btn text-lg opacity-50 pointer-events-none"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
There is no CVA equivalent — this is new capability. Use it instead of manually adding
|
|
190
|
+
conditional classes for simple on/off states.
|
|
191
|
+
|
|
192
|
+
### Negation in compound rules (new in styra)
|
|
193
|
+
|
|
194
|
+
CVA compound variants only matched when all listed keys matched. Styra allows negation via
|
|
195
|
+
`{ not: value }` in compound entries.
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
import { styra, type VariantProps } from "@ntatoud/styra";
|
|
199
|
+
|
|
200
|
+
const button = styra("btn")
|
|
201
|
+
.variants({
|
|
202
|
+
disabled: "opacity-50 pointer-events-none",
|
|
203
|
+
size: { sm: "text-sm", lg: "text-lg" },
|
|
204
|
+
})
|
|
205
|
+
.compound([
|
|
206
|
+
// Apply hover class only when NOT disabled
|
|
207
|
+
{ disabled: { not: "true" }, class: "hover:opacity-80" },
|
|
208
|
+
// Apply ring only when size is sm AND NOT disabled
|
|
209
|
+
{ size: "sm", disabled: { not: "true" }, class: "ring-1" },
|
|
210
|
+
]);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
There is no CVA equivalent. When migrating compound variants that were previously guarded
|
|
214
|
+
by runtime logic, consider whether negation expresses the intent more cleanly.
|
|
215
|
+
|
|
216
|
+
## Common Mistakes
|
|
217
|
+
|
|
218
|
+
### Using the CVA config object syntax
|
|
219
|
+
|
|
220
|
+
Wrong — this is CVA syntax, not valid styra:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
// WRONG
|
|
224
|
+
import { styra } from "@ntatoud/styra";
|
|
225
|
+
|
|
226
|
+
const button = styra("btn", {
|
|
227
|
+
variants: { size: { sm: "text-sm" } },
|
|
228
|
+
defaultVariants: { size: "sm" },
|
|
229
|
+
compoundVariants: [{ size: "sm", class: "ring" }],
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Correct — styra uses a builder chain:
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
// CORRECT
|
|
237
|
+
import { styra } from "@ntatoud/styra";
|
|
238
|
+
|
|
239
|
+
const button = styra("btn")
|
|
240
|
+
.variants({ size: { sm: "text-sm" } })
|
|
241
|
+
.defaults({ size: "sm" })
|
|
242
|
+
.compound([{ size: "sm", class: "ring" }]);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Keeping `compoundVariants` as a key
|
|
246
|
+
|
|
247
|
+
Wrong:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
// WRONG — compoundVariants is a CVA key, not a styra method
|
|
251
|
+
const button = styra("btn")
|
|
252
|
+
.variants({ size: { sm: "text-sm" } })
|
|
253
|
+
.compoundVariants([{ size: "sm", class: "ring" }]);
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Correct: the method is `.compound()`.
|
|
257
|
+
|
|
258
|
+
### Keeping `defaultVariants` as a key
|
|
259
|
+
|
|
260
|
+
Wrong:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
// WRONG — defaultVariants is a CVA key, not a styra method
|
|
264
|
+
const button = styra("btn")
|
|
265
|
+
.variants({ size: { sm: "text-sm", md: "text-md" } })
|
|
266
|
+
.defaultVariants({ size: "md" });
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Correct: the method is `.defaults()`.
|
|
270
|
+
|
|
271
|
+
### Not using createStyra when twMerge is needed
|
|
272
|
+
|
|
273
|
+
Wrong — this bypasses styra's merge integration:
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
// WRONG — manually merging outside of styra
|
|
277
|
+
import { styra } from "@ntatoud/styra";
|
|
278
|
+
import { twMerge } from "tailwind-merge";
|
|
279
|
+
|
|
280
|
+
const button = styra("btn").variants({ size: { sm: "px-2", lg: "px-6" } });
|
|
281
|
+
|
|
282
|
+
// Calling twMerge separately
|
|
283
|
+
const cls = twMerge(button({ size: "sm" }), "px-4");
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Correct — configure merge once in `createStyra` and use the returned `styra` and `cn`:
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
// lib/styra.ts
|
|
290
|
+
import { createStyra } from "@ntatoud/styra";
|
|
291
|
+
import { twMerge } from "tailwind-merge";
|
|
292
|
+
export const { styra, cn } = createStyra({ merge: twMerge });
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Not using boolean shorthand for simple on/off variants
|
|
296
|
+
|
|
297
|
+
Suboptimal — mirrors old CVA workaround pattern:
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
// SUBOPTIMAL — using object syntax for a boolean state
|
|
301
|
+
const button = styra("btn").variants({
|
|
302
|
+
disabled: { true: "opacity-50", false: "" },
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Preferred — use boolean shorthand:
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
// PREFERRED
|
|
310
|
+
const button = styra("btn").variants({
|
|
311
|
+
disabled: "opacity-50 pointer-events-none",
|
|
312
|
+
});
|
|
313
|
+
// disabled prop is inferred as boolean
|
|
314
|
+
```
|