@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.
@@ -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`