@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,246 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: styra-shadcn-integration
|
|
3
|
+
description: >
|
|
4
|
+
Integrate @ntatoud/styra into a shadcn/ui project — replaces class-variance-authority (CVA)
|
|
5
|
+
and clsx+twMerge cn helper with a single createStyra({ merge: twMerge }) instance.
|
|
6
|
+
Covers lib/styra.ts shared factory pattern, swapping cva/VariantProps imports, replacing
|
|
7
|
+
cn from @/lib/utils, tailwind-merge deduplication via MergeFn, and removing clsx dependency.
|
|
8
|
+
Preempts: per-component createStyra instances, double-merge with twMerge, keeping clsx after migration.
|
|
9
|
+
type: composition
|
|
10
|
+
library: "@ntatoud/styra"
|
|
11
|
+
library_version: "0.1.0"
|
|
12
|
+
requires:
|
|
13
|
+
- styra-getting-started
|
|
14
|
+
sources:
|
|
15
|
+
- "ntatoud/styra:packages/styra/src/index.ts"
|
|
16
|
+
- "ntatoud/styra:packages/styra/README.md"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Integrating @ntatoud/styra with shadcn/ui
|
|
20
|
+
|
|
21
|
+
This skill builds on styra-getting-started. Read it first for foundational concepts.
|
|
22
|
+
|
|
23
|
+
## 1. Integration Setup
|
|
24
|
+
|
|
25
|
+
Create a single shared `lib/styra.ts` file. Every component in the project imports from this file — never call `createStyra` more than once.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
// lib/styra.ts
|
|
29
|
+
import { createStyra } from "@ntatoud/styra";
|
|
30
|
+
import { twMerge } from "tailwind-merge";
|
|
31
|
+
|
|
32
|
+
export const { styra, cn } = createStyra({ merge: twMerge });
|
|
33
|
+
export type { VariantProps } from "@ntatoud/styra";
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Install `tailwind-merge` if not already present:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
pnpm add tailwind-merge
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
After this, `clsx` and `class-variance-authority` can be removed:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
pnpm remove class-variance-authority clsx
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Delete (or empty out) `lib/utils.ts` if it only contained the old `cn` helper.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 2. Core Integration Patterns
|
|
53
|
+
|
|
54
|
+
### Pattern 1 — Migrating a shadcn/ui component (Button)
|
|
55
|
+
|
|
56
|
+
Replace the CVA import and the `@/lib/utils` cn import. Pass `class` directly into the builder call — no outer `cn()` wrapper needed.
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
// components/ui/button.tsx — AFTER
|
|
60
|
+
import * as React from "react"
|
|
61
|
+
import { Slot } from "@radix-ui/react-slot"
|
|
62
|
+
import { styra, cn, type VariantProps } from "@/lib/styra"
|
|
63
|
+
|
|
64
|
+
const buttonVariants = styra("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50")
|
|
65
|
+
.variants({
|
|
66
|
+
variant: {
|
|
67
|
+
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
|
68
|
+
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
|
69
|
+
outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
|
70
|
+
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
|
71
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
72
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
73
|
+
},
|
|
74
|
+
size: {
|
|
75
|
+
default: "h-9 px-4 py-2",
|
|
76
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
77
|
+
lg: "h-10 rounded-md px-8",
|
|
78
|
+
icon: "h-9 w-9",
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
.defaults({ variant: "default", size: "default" })
|
|
82
|
+
|
|
83
|
+
interface ButtonProps
|
|
84
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
85
|
+
VariantProps<typeof buttonVariants> {
|
|
86
|
+
asChild?: boolean
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
90
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
91
|
+
const Comp = asChild ? Slot : "button"
|
|
92
|
+
return (
|
|
93
|
+
<Comp
|
|
94
|
+
className={buttonVariants({ variant, size, class: className })}
|
|
95
|
+
ref={ref}
|
|
96
|
+
{...props}
|
|
97
|
+
/>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
Button.displayName = "Button"
|
|
102
|
+
|
|
103
|
+
export { Button, buttonVariants }
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Key change: `cn(buttonVariants({ variant, size, className }))` becomes `buttonVariants({ variant, size, class: className })`. The merge function configured in `createStyra` handles deduplication internally.
|
|
107
|
+
|
|
108
|
+
### Pattern 2 — Migrating a component that uses cn standalone
|
|
109
|
+
|
|
110
|
+
Some shadcn/ui components call `cn()` directly for conditional classes outside of a variant builder. Import `cn` from `@/lib/styra` — it is a full clsx-compatible helper that also runs `twMerge`.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
// components/ui/badge.tsx — AFTER
|
|
114
|
+
import { styra, cn, type VariantProps } from "@/lib/styra"
|
|
115
|
+
|
|
116
|
+
const badgeVariants = styra("inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold")
|
|
117
|
+
.variants({
|
|
118
|
+
variant: {
|
|
119
|
+
default: "border-transparent bg-primary text-primary-foreground",
|
|
120
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground",
|
|
121
|
+
destructive: "border-transparent bg-destructive text-destructive-foreground",
|
|
122
|
+
outline: "text-foreground",
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
.defaults({ variant: "default" })
|
|
126
|
+
|
|
127
|
+
interface BadgeProps
|
|
128
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
129
|
+
VariantProps<typeof badgeVariants> {}
|
|
130
|
+
|
|
131
|
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
132
|
+
return (
|
|
133
|
+
<div className={badgeVariants({ variant, class: className })} {...props} />
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// cn is still available for ad-hoc conditional class merging elsewhere in the file
|
|
138
|
+
export function SomethingElse({ isActive }: { isActive: boolean }) {
|
|
139
|
+
return <div className={cn("base-class", isActive && "active-class")} />
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export { Badge, badgeVariants }
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Pattern 3 — Replacing lib/utils.ts cn across non-component files
|
|
146
|
+
|
|
147
|
+
Files that imported `cn` from `@/lib/utils` for layout or utility purposes update their import path only — the function signature is identical.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
// BEFORE
|
|
151
|
+
import { cn } from "@/lib/utils";
|
|
152
|
+
|
|
153
|
+
// AFTER
|
|
154
|
+
import { cn } from "@/lib/styra";
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
No call-site changes are required. `cn` from styra accepts the same `ClassValue[]` spread that `clsx` accepts.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 3. Common Mistakes
|
|
162
|
+
|
|
163
|
+
### [CRITICAL] Creating a new createStyra instance per component file
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
// WRONG — components/ui/button.tsx
|
|
167
|
+
import { createStyra } from "@ntatoud/styra"
|
|
168
|
+
import { twMerge } from "tailwind-merge"
|
|
169
|
+
|
|
170
|
+
const { styra, cn } = createStyra({ merge: twMerge }) // new instance every file
|
|
171
|
+
|
|
172
|
+
const buttonVariants = styra("...").variants({ ... })
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
// CORRECT — components/ui/button.tsx
|
|
177
|
+
import { styra, cn, type VariantProps } from "@/lib/styra" // shared instance
|
|
178
|
+
|
|
179
|
+
const buttonVariants = styra("...").variants({ ... })
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Each `createStyra` call produces an isolated instance. Multiple instances are valid for isolated design systems but wasteful when one shared instance covers the entire project. Always import from the shared `lib/styra.ts`.
|
|
183
|
+
|
|
184
|
+
Source: `createStyra` in `packages/styra/src/index.ts`
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
### [CRITICAL] Double-merging with twMerge after the builder call
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
// WRONG — components/ui/button.tsx
|
|
192
|
+
import { twMerge } from "tailwind-merge"
|
|
193
|
+
import { styra, type VariantProps } from "@/lib/styra"
|
|
194
|
+
|
|
195
|
+
function Button({ className, variant, size, ...props }) {
|
|
196
|
+
return (
|
|
197
|
+
<button
|
|
198
|
+
className={twMerge(buttonVariants({ variant, size, class: className }))}
|
|
199
|
+
// ^^^^^^^^ twMerge already ran inside buttonVariants()
|
|
200
|
+
{...props}
|
|
201
|
+
/>
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
// CORRECT — components/ui/button.tsx
|
|
208
|
+
import { styra, type VariantProps } from "@/lib/styra"
|
|
209
|
+
|
|
210
|
+
function Button({ className, variant, size, ...props }) {
|
|
211
|
+
return (
|
|
212
|
+
<button
|
|
213
|
+
className={buttonVariants({ variant, size, class: className })}
|
|
214
|
+
{...props}
|
|
215
|
+
/>
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`createStyra({ merge: twMerge })` wires the merge function into every builder call. Wrapping the result in `twMerge()` again is redundant and can produce unexpected output for complex Tailwind class conflicts.
|
|
221
|
+
|
|
222
|
+
Source: `MergeFn` wiring in `packages/styra/src/index.ts`
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
### [MODERATE] Keeping clsx as a dependency after migration
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
// WRONG — some-file.ts
|
|
230
|
+
import { clsx } from "clsx"; // redundant after migration
|
|
231
|
+
import { cn } from "@/lib/styra";
|
|
232
|
+
|
|
233
|
+
const classes = cn(clsx(["a", condition && "b"]), "c");
|
|
234
|
+
// ^^^^ unnecessary double-wrap
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
// CORRECT — some-file.ts
|
|
239
|
+
import { cn } from "@/lib/styra";
|
|
240
|
+
|
|
241
|
+
const classes = cn("a", condition && "b", "c");
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`cn` from a `createStyra` instance is a drop-in replacement for `clsx` with the merge function applied on top. Remove `clsx` from `package.json` after migrating all import sites.
|
|
245
|
+
|
|
246
|
+
Source: `cn` implementation in `packages/styra/src/index.ts`
|