tailwind-styled-v4 1.0.1 → 4.0.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/dist/animate.cjs +252 -0
- package/dist/animate.cjs.map +1 -0
- package/dist/animate.d.cts +117 -0
- package/dist/animate.d.ts +117 -0
- package/dist/animate.js +245 -0
- package/dist/animate.js.map +1 -0
- package/dist/astTransform-ua-eapqs.d.cts +41 -0
- package/dist/astTransform-ua-eapqs.d.ts +41 -0
- package/dist/compiler.cjs +3594 -0
- package/dist/compiler.cjs.map +1 -0
- package/dist/compiler.d.cts +716 -0
- package/dist/compiler.d.ts +716 -0
- package/dist/compiler.js +3535 -0
- package/dist/compiler.js.map +1 -0
- package/dist/css.cjs +71 -0
- package/dist/css.cjs.map +1 -0
- package/dist/css.d.cts +45 -0
- package/dist/css.d.ts +45 -0
- package/dist/css.js +62 -0
- package/dist/css.js.map +1 -0
- package/dist/devtools.cjs +959 -0
- package/dist/devtools.cjs.map +1 -0
- package/dist/devtools.d.cts +22 -0
- package/dist/devtools.d.ts +22 -0
- package/dist/devtools.js +952 -0
- package/dist/devtools.js.map +1 -0
- package/dist/index.cjs +1058 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +584 -0
- package/dist/index.d.ts +449 -980
- package/dist/index.js +1021 -3
- package/dist/index.js.map +1 -1
- package/dist/next.cjs +268 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +45 -0
- package/dist/next.d.ts +45 -0
- package/dist/next.js +261 -0
- package/dist/next.js.map +1 -0
- package/dist/plugins.cjs +396 -0
- package/dist/plugins.cjs.map +1 -0
- package/dist/plugins.d.cts +231 -0
- package/dist/plugins.d.ts +231 -0
- package/dist/plugins.js +381 -0
- package/dist/plugins.js.map +1 -0
- package/dist/preset.cjs +129 -0
- package/dist/preset.cjs.map +1 -0
- package/dist/preset.d.cts +249 -0
- package/dist/preset.d.ts +249 -0
- package/dist/preset.js +124 -0
- package/dist/preset.js.map +1 -0
- package/dist/theme.cjs +154 -0
- package/dist/theme.cjs.map +1 -0
- package/dist/theme.d.cts +181 -0
- package/dist/theme.d.ts +181 -0
- package/dist/theme.js +148 -0
- package/dist/theme.js.map +1 -0
- package/dist/turbopackLoader.cjs +2689 -0
- package/dist/turbopackLoader.cjs.map +1 -0
- package/dist/turbopackLoader.d.cts +22 -0
- package/dist/turbopackLoader.d.ts +22 -0
- package/dist/turbopackLoader.js +2681 -0
- package/dist/turbopackLoader.js.map +1 -0
- package/dist/vite.cjs +105 -0
- package/dist/vite.cjs.map +1 -0
- package/dist/vite.d.cts +22 -0
- package/dist/vite.d.ts +22 -0
- package/dist/vite.js +96 -0
- package/dist/vite.js.map +1 -0
- package/dist/webpackLoader.cjs +2670 -0
- package/dist/webpackLoader.cjs.map +1 -0
- package/dist/webpackLoader.d.cts +24 -0
- package/dist/webpackLoader.d.ts +24 -0
- package/dist/webpackLoader.js +2662 -0
- package/dist/webpackLoader.js.map +1 -0
- package/package.json +62 -32
- package/CHANGELOG.md +0 -75
- package/LICENSE +0 -21
- package/README.md +0 -608
- package/dist/cli/init.js +0 -208
- package/dist/compiler/index.d.mts +0 -214
- package/dist/compiler/index.d.ts +0 -214
- package/dist/compiler/index.js +0 -546
- package/dist/compiler/index.js.map +0 -1
- package/dist/compiler/index.mjs +0 -504
- package/dist/compiler/index.mjs.map +0 -1
- package/dist/index.d.mts +0 -1115
- package/dist/index.mjs +0 -4
- package/dist/index.mjs.map +0 -1
- package/dist/turbopack-loader.js +0 -232
- package/dist/webpack-loader.js +0 -213
package/README.md
DELETED
|
@@ -1,608 +0,0 @@
|
|
|
1
|
-
# 🎨 tailwind-styled-v4
|
|
2
|
-
|
|
3
|
-
> **styled-components meets Tailwind CSS** — dengan type safety penuh, zero DOM pollution, dan zero-runtime build output.
|
|
4
|
-
|
|
5
|
-
```tsx
|
|
6
|
-
const Button = tw.button({
|
|
7
|
-
base: "px-4 py-2 rounded-lg font-medium transition",
|
|
8
|
-
variants: {
|
|
9
|
-
variant: {
|
|
10
|
-
primary: "bg-blue-500 text-white hover:bg-blue-600",
|
|
11
|
-
ghost: "bg-transparent border hover:bg-zinc-800",
|
|
12
|
-
},
|
|
13
|
-
size: { sm: "h-8 text-sm", lg: "h-12 text-base" },
|
|
14
|
-
},
|
|
15
|
-
defaultVariants: { variant: "primary", size: "sm" },
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
<Button variant="primary" size="lg" p={4} rounded="card" />
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## ✨ Fitur Utama
|
|
24
|
-
|
|
25
|
-
| Fitur | Keterangan |
|
|
26
|
-
|-------|------------|
|
|
27
|
-
| 🧱 **styled-components API** | Familiar, chainable, SSR-safe |
|
|
28
|
-
| 🎭 **Variant System** | `variants`, `compoundVariants`, `defaultVariants` |
|
|
29
|
-
| 📐 **Responsive Props** | `p={{ sm: 2, lg: 8 }}` — responsive object syntax |
|
|
30
|
-
| 🎨 **Design Tokens** | `bg="primary"` → `bg-blue-600` via theme store |
|
|
31
|
-
| ⚡ **Zero-Runtime** | Build-time transform → static `className` strings |
|
|
32
|
-
| 🔌 **Plugin System** | Register custom prop resolvers |
|
|
33
|
-
| 🛡️ **Type Safe** | Full TypeScript generics & inference |
|
|
34
|
-
| 🚫 **Zero DOM Pollution** | Props tidak bocor ke HTML attributes |
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## 📦 Instalasi
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
npm install tailwind-styled-v4 styled-components tailwind-merge
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## 🚀 Quick Start
|
|
47
|
-
|
|
48
|
-
### 1️⃣ Template Literal (paling simpel)
|
|
49
|
-
|
|
50
|
-
```tsx
|
|
51
|
-
import { tw } from "tailwind-styled-v4"
|
|
52
|
-
|
|
53
|
-
const Box = tw.div`p-4 bg-zinc-900 rounded-xl`
|
|
54
|
-
const Title = tw.h1`text-2xl font-bold text-white`
|
|
55
|
-
const Badge = tw.span`px-2 py-1 rounded-full text-xs bg-blue-500 text-white`
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### 2️⃣ Object Config + Variants
|
|
59
|
-
|
|
60
|
-
```tsx
|
|
61
|
-
const Button = tw.button({
|
|
62
|
-
base: "inline-flex items-center font-medium transition rounded-lg",
|
|
63
|
-
variants: {
|
|
64
|
-
variant: {
|
|
65
|
-
primary: "bg-blue-500 text-white hover:bg-blue-600",
|
|
66
|
-
ghost: "bg-transparent border border-zinc-700 hover:bg-zinc-800",
|
|
67
|
-
danger: "bg-red-500 text-white hover:bg-red-600",
|
|
68
|
-
},
|
|
69
|
-
size: {
|
|
70
|
-
sm: "h-8 px-3 text-sm",
|
|
71
|
-
md: "h-10 px-4 text-sm",
|
|
72
|
-
lg: "h-12 px-6 text-base",
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
compoundVariants: [
|
|
76
|
-
{ variant: "primary", size: "lg", class: "shadow-lg shadow-blue-500/30" },
|
|
77
|
-
],
|
|
78
|
-
defaultVariants: { variant: "primary", size: "md" },
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
// Usage
|
|
82
|
-
<Button variant="ghost" size="lg" />
|
|
83
|
-
<Button variant="danger" />
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### 3️⃣ Prop Shorthand (responsive-ready)
|
|
87
|
-
|
|
88
|
-
```tsx
|
|
89
|
-
// Scalar
|
|
90
|
-
<Box p={4} bg="zinc-900" rounded="xl" flex />
|
|
91
|
-
|
|
92
|
-
// Responsive object
|
|
93
|
-
<Box p={{ base: 2, md: 4, lg: 8 }} cols={{ sm: 1, md: 2, lg: 3 }} />
|
|
94
|
-
|
|
95
|
-
// Design token
|
|
96
|
-
<Box bg="primary" text="foreground" rounded="card" shadow="modal" />
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### 4️⃣ Extend Component
|
|
100
|
-
|
|
101
|
-
```tsx
|
|
102
|
-
const Card = tw.div`p-6 rounded-2xl bg-zinc-900`
|
|
103
|
-
const HeroCard = Card.extend`shadow-2xl ring-1 ring-zinc-700`
|
|
104
|
-
const DarkCard = Card.withVariants({
|
|
105
|
-
variants: { elevated: { true: "shadow-2xl", false: "" } }
|
|
106
|
-
})
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### 5️⃣ Wrap Komponen Apapun
|
|
110
|
-
|
|
111
|
-
```tsx
|
|
112
|
-
// shadcn/ui, Radix, komponen custom
|
|
113
|
-
const TwButton = withTw(Button, "px-4 py-2")
|
|
114
|
-
const TwDialog = tw(Dialog.Content)`p-6 rounded-2xl`
|
|
115
|
-
|
|
116
|
-
<TwButton p={4} rounded="xl" bg="zinc-900" />
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
---
|
|
120
|
-
|
|
121
|
-
## 🎭 Variant System
|
|
122
|
-
|
|
123
|
-
### Basic Variants
|
|
124
|
-
|
|
125
|
-
```tsx
|
|
126
|
-
const Alert = tw.div({
|
|
127
|
-
base: "p-4 rounded-lg border",
|
|
128
|
-
variants: {
|
|
129
|
-
status: {
|
|
130
|
-
info: "bg-sky-50 border-sky-200 text-sky-800",
|
|
131
|
-
success: "bg-emerald-50 border-emerald-200 text-emerald-800",
|
|
132
|
-
warning: "bg-amber-50 border-amber-200 text-amber-800",
|
|
133
|
-
danger: "bg-red-50 border-red-200 text-red-800",
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
defaultVariants: { status: "info" },
|
|
137
|
-
})
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Compound Variants
|
|
141
|
-
|
|
142
|
-
```tsx
|
|
143
|
-
// Class tambahan ketika 2+ variant aktif bersamaan
|
|
144
|
-
compoundVariants: [
|
|
145
|
-
{ variant: "primary", size: "lg", class: "shadow-lg shadow-blue-500/30" },
|
|
146
|
-
{ variant: "ghost", size: "sm", class: "opacity-70" },
|
|
147
|
-
]
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### withVariants — tambah variant ke komponen existing
|
|
151
|
-
|
|
152
|
-
```tsx
|
|
153
|
-
const EnhancedButton = Button.withVariants({
|
|
154
|
-
variants: {
|
|
155
|
-
loading: { true: "opacity-50 cursor-not-allowed", false: "" }
|
|
156
|
-
}
|
|
157
|
-
})
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
---
|
|
161
|
-
|
|
162
|
-
## 📐 Prop Engine
|
|
163
|
-
|
|
164
|
-
Semua prop otomatis di-convert ke Tailwind class. Tidak perlu `className="..."`.
|
|
165
|
-
|
|
166
|
-
### Spacing
|
|
167
|
-
```tsx
|
|
168
|
-
<Box p={4} px={6} py={2} m="auto" mx={4} gap={3} />
|
|
169
|
-
// → p-4 px-6 py-2 m-auto mx-4 gap-3
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
### Flexbox & Grid
|
|
173
|
-
```tsx
|
|
174
|
-
<Box flex items="center" justify="between" wrap />
|
|
175
|
-
<Box cols={3} rows={2} colSpan={2} gap={4} />
|
|
176
|
-
// → flex items-center justify-between flex-wrap
|
|
177
|
-
// → grid-cols-3 grid-rows-2 col-span-2 gap-4
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
### Colors & Typography
|
|
181
|
-
```tsx
|
|
182
|
-
<Box bg="zinc-900" text="white" border ring="blue-500" />
|
|
183
|
-
<Box fontSize="heading" font="sans" leading="relaxed" tracking="tight" />
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
### Layout & Position
|
|
187
|
-
```tsx
|
|
188
|
-
<Box pos="absolute" top={0} right={0} z={50} inset="x-4" />
|
|
189
|
-
<Box overflow="hidden" rounded="xl" shadow="card" opacity={90} />
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### Responsive Object
|
|
193
|
-
```tsx
|
|
194
|
-
// Semua prop mendukung responsive object
|
|
195
|
-
<Box
|
|
196
|
-
p={{ base: 2, sm: 4, md: 6, lg: 8 }}
|
|
197
|
-
cols={{ base: 1, md: 2, lg: 3 }}
|
|
198
|
-
hidden={{ base: true, md: false }}
|
|
199
|
-
/>
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
---
|
|
203
|
-
|
|
204
|
-
## 🎨 Theme & Design Tokens
|
|
205
|
-
|
|
206
|
-
### Setup
|
|
207
|
-
|
|
208
|
-
```tsx
|
|
209
|
-
// app/layout.tsx
|
|
210
|
-
import { loadDefaultTheme } from "tailwind-styled-v4"
|
|
211
|
-
loadDefaultTheme()
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### Custom Theme
|
|
215
|
-
|
|
216
|
-
```tsx
|
|
217
|
-
import { setTheme } from "tailwind-styled-v4"
|
|
218
|
-
|
|
219
|
-
setTheme({
|
|
220
|
-
colors: {
|
|
221
|
-
primary: "blue-600",
|
|
222
|
-
secondary: "violet-600",
|
|
223
|
-
brand: "cyan-400",
|
|
224
|
-
background: "zinc-950",
|
|
225
|
-
surface: "zinc-900",
|
|
226
|
-
muted: "zinc-500",
|
|
227
|
-
},
|
|
228
|
-
radius: {
|
|
229
|
-
card: "2xl",
|
|
230
|
-
button: "lg",
|
|
231
|
-
badge: "full",
|
|
232
|
-
input: "md",
|
|
233
|
-
},
|
|
234
|
-
spacing: {
|
|
235
|
-
section: "16",
|
|
236
|
-
page: "24",
|
|
237
|
-
},
|
|
238
|
-
duration: {
|
|
239
|
-
fast: "150",
|
|
240
|
-
normal: "200",
|
|
241
|
-
slow: "300",
|
|
242
|
-
},
|
|
243
|
-
})
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### Penggunaan Token
|
|
247
|
-
|
|
248
|
-
```tsx
|
|
249
|
-
// Token otomatis di-resolve
|
|
250
|
-
<Box bg="primary" /> // → bg-blue-600
|
|
251
|
-
<Box rounded="card" /> // → rounded-2xl
|
|
252
|
-
<Box p="section" /> // → p-16
|
|
253
|
-
<Box duration="fast" /> // → duration-150
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
---
|
|
257
|
-
|
|
258
|
-
## 🎯 cv() — Standalone Class Variants
|
|
259
|
-
|
|
260
|
-
Cocok untuk **shadcn/ui**, **Radix UI**, atau komponen headless apapun tanpa styled-components.
|
|
261
|
-
|
|
262
|
-
```tsx
|
|
263
|
-
import { cv } from "tailwind-styled-v4"
|
|
264
|
-
|
|
265
|
-
const buttonVariants = cv({
|
|
266
|
-
base: "inline-flex items-center rounded-lg font-medium transition",
|
|
267
|
-
variants: {
|
|
268
|
-
variant: {
|
|
269
|
-
primary: "bg-blue-500 text-white hover:bg-blue-600",
|
|
270
|
-
ghost: "bg-transparent border hover:bg-zinc-800",
|
|
271
|
-
},
|
|
272
|
-
size: {
|
|
273
|
-
sm: "h-8 px-3 text-sm",
|
|
274
|
-
md: "h-10 px-4 text-sm",
|
|
275
|
-
lg: "h-12 px-6 text-base",
|
|
276
|
-
},
|
|
277
|
-
},
|
|
278
|
-
compoundVariants: [
|
|
279
|
-
{ variant: "primary", size: "lg", class: "shadow-lg" }
|
|
280
|
-
],
|
|
281
|
-
defaultVariants: { variant: "primary", size: "md" },
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
// Pakai di komponen apapun
|
|
285
|
-
function Button({ variant, size, className, ...props }) {
|
|
286
|
-
return <button className={buttonVariants({ variant, size, className })} {...props} />
|
|
287
|
-
}
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
---
|
|
291
|
-
|
|
292
|
-
## 🔌 Plugin System
|
|
293
|
-
|
|
294
|
-
### Built-in Plugins
|
|
295
|
-
|
|
296
|
-
```tsx
|
|
297
|
-
import { loadBuiltinPlugins } from "tailwind-styled-v4"
|
|
298
|
-
loadBuiltinPlugins()
|
|
299
|
-
|
|
300
|
-
// Setelah load, props berikut tersedia:
|
|
301
|
-
<Box motionSafe backdropBlur="md" gradientFrom="blue-500" gradientTo="purple-500" />
|
|
302
|
-
<Box scrollSmooth printHidden autoRows="fr" />
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
### Tailwind v4 Plugins
|
|
306
|
-
|
|
307
|
-
```tsx
|
|
308
|
-
import { loadAllV4Plugins } from "tailwind-styled-v4"
|
|
309
|
-
loadAllV4Plugins()
|
|
310
|
-
|
|
311
|
-
// Typography
|
|
312
|
-
<article prose="lg" proseDark proseMaxW={false} />
|
|
313
|
-
// → prose prose-lg dark:prose-invert max-w-none
|
|
314
|
-
|
|
315
|
-
// Forms
|
|
316
|
-
<input formInput />
|
|
317
|
-
<select formSelect />
|
|
318
|
-
|
|
319
|
-
// Aspect Ratio
|
|
320
|
-
<div aspectRatio="video" /> // → aspect-video
|
|
321
|
-
<div aspectRatio="square" /> // → aspect-square
|
|
322
|
-
|
|
323
|
-
// Animations
|
|
324
|
-
<div enterAnim="fade-up" /> // → animate-in fade-in slide-in-from-bottom-4
|
|
325
|
-
<div exitAnim="fade" /> // → animate-out fade-out
|
|
326
|
-
|
|
327
|
-
// Interactivity
|
|
328
|
-
<button hoverBg="zinc-800" hoverScale focusRing="blue-500" activeScale />
|
|
329
|
-
|
|
330
|
-
// Container Queries
|
|
331
|
-
<div cqContainer /> // → @container
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
### Custom Plugin
|
|
335
|
-
|
|
336
|
-
```tsx
|
|
337
|
-
import { registerPlugin } from "tailwind-styled-v4"
|
|
338
|
-
|
|
339
|
-
registerPlugin({
|
|
340
|
-
name: "glass",
|
|
341
|
-
props: {
|
|
342
|
-
glass: (v) => v === true
|
|
343
|
-
? "backdrop-blur-md bg-white/10 border border-white/20"
|
|
344
|
-
: `backdrop-blur-${v} bg-white/10`,
|
|
345
|
-
},
|
|
346
|
-
})
|
|
347
|
-
|
|
348
|
-
// Langsung bisa dipakai
|
|
349
|
-
<Box glass />
|
|
350
|
-
<Box glass="xl" />
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
---
|
|
354
|
-
|
|
355
|
-
## ⚡ Zero-Runtime Mode
|
|
356
|
-
|
|
357
|
-
### Next.js
|
|
358
|
-
|
|
359
|
-
```ts
|
|
360
|
-
// next.config.ts
|
|
361
|
-
import { withTailwindStyled } from "tailwind-styled-v4/compiler"
|
|
362
|
-
|
|
363
|
-
export default withTailwindStyled({
|
|
364
|
-
mode: "zero-runtime", // transform tw.div`...` → static className
|
|
365
|
-
addDataAttr: true, // tambah data-tw="hash" untuk DevTools
|
|
366
|
-
})(nextConfig)
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
### Vite
|
|
370
|
-
|
|
371
|
-
```ts
|
|
372
|
-
// vite.config.ts
|
|
373
|
-
import { tailwindStyledPlugin } from "tailwind-styled-v4/compiler"
|
|
374
|
-
|
|
375
|
-
export default defineConfig({
|
|
376
|
-
plugins: [
|
|
377
|
-
react(),
|
|
378
|
-
tailwindStyledPlugin({ mode: "zero-runtime" }),
|
|
379
|
-
],
|
|
380
|
-
})
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
**Before:**
|
|
384
|
-
```tsx
|
|
385
|
-
const Box = tw.div`p-4 bg-zinc-900 rounded-xl`
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
**After (build output):**
|
|
389
|
-
```tsx
|
|
390
|
-
const Box = styled.div.attrs(() => ({ className: "p-4 bg-zinc-900 rounded-xl" }))``
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
> 🚀 Nol JS runtime cost — `className` jadi static string, bukan computed di setiap render.
|
|
394
|
-
|
|
395
|
-
---
|
|
396
|
-
|
|
397
|
-
## 🛡️ shouldForwardProp
|
|
398
|
-
|
|
399
|
-
Semua tw props **otomatis diblokir** dari forwarding ke DOM. Tidak ada warning `Unknown prop` di console.
|
|
400
|
-
|
|
401
|
-
```tsx
|
|
402
|
-
// ✅ Tidak ada warning di DOM
|
|
403
|
-
<Box p={4} flex cols={3} bg="zinc-900" />
|
|
404
|
-
|
|
405
|
-
// Transient props ($) selalu di-forward
|
|
406
|
-
<Box $isActive={true} />
|
|
407
|
-
|
|
408
|
-
// Manual block/allow
|
|
409
|
-
import { blockProp, allowProp } from "tailwind-styled-v4"
|
|
410
|
-
blockProp("customProp")
|
|
411
|
-
allowProp("size") // kalau mau size sampai ke DOM
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
---
|
|
415
|
-
|
|
416
|
-
## 🧪 Validation (Dev Only)
|
|
417
|
-
|
|
418
|
-
```tsx
|
|
419
|
-
import { validateVariantProps, buildSchema } from "tailwind-styled-v4"
|
|
420
|
-
|
|
421
|
-
const schema = buildSchema("button", {
|
|
422
|
-
variant: { primary: "...", ghost: "..." },
|
|
423
|
-
size: { sm: "...", lg: "..." },
|
|
424
|
-
})
|
|
425
|
-
|
|
426
|
-
validateVariantProps({ variant: "invalid" }, schema)
|
|
427
|
-
// [tw-error] Invalid prop "variant"="invalid" on <button>
|
|
428
|
-
// Expected one of: "primary" | "ghost"
|
|
429
|
-
// Got: "invalid"
|
|
430
|
-
// Hint: Add "invalid" to the variants config, or fix the typo.
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
> ⚠️ Validation hanya aktif di `NODE_ENV !== "production"` — **zero overhead di production**.
|
|
434
|
-
|
|
435
|
-
---
|
|
436
|
-
|
|
437
|
-
## 🔧 Utils
|
|
438
|
-
|
|
439
|
-
### cx / cxm
|
|
440
|
-
|
|
441
|
-
```tsx
|
|
442
|
-
import { cx, cxm } from "tailwind-styled-v4"
|
|
443
|
-
|
|
444
|
-
// cx — lightweight class joiner
|
|
445
|
-
cx("p-4", isActive && "bg-blue-500", ["rounded", "shadow"])
|
|
446
|
-
cx({ "p-4": true, "hidden": false })
|
|
447
|
-
|
|
448
|
-
// cxm — cx + tailwind-merge (conflict resolution)
|
|
449
|
-
cxm("p-2", "p-4") // → "p-4" (p-2 dihapus)
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
### Class Parser
|
|
453
|
-
|
|
454
|
-
```tsx
|
|
455
|
-
import { parseClassString, splitModifiers, groupByModifier } from "tailwind-styled-v4"
|
|
456
|
-
|
|
457
|
-
splitModifiers("p-4 hover:bg-blue-500 md:p-8")
|
|
458
|
-
// → { base: ["p-4"], modifiers: ["hover:bg-blue-500", "md:p-8"] }
|
|
459
|
-
|
|
460
|
-
groupByModifier("p-4 hover:bg-blue-500 hover:text-white")
|
|
461
|
-
// → { "": ["p-4"], hover: ["bg-blue-500", "text-white"] }
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
### Safelist Generator
|
|
465
|
-
|
|
466
|
-
```tsx
|
|
467
|
-
import { generateSafelist } from "tailwind-styled-v4/compiler"
|
|
468
|
-
|
|
469
|
-
generateSafelist({
|
|
470
|
-
cwd: process.cwd(),
|
|
471
|
-
scanDirs: ["src", "app", "components"],
|
|
472
|
-
outputPath: "tailwind.safelist.json",
|
|
473
|
-
})
|
|
474
|
-
// ✓ Safelist: 247 classes → /project/tailwind.safelist.json
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
---
|
|
478
|
-
|
|
479
|
-
## 📁 Struktur Engine
|
|
480
|
-
|
|
481
|
-
```
|
|
482
|
-
src/
|
|
483
|
-
├── core/
|
|
484
|
-
│ ├── tw.ts 🎯 Proxy-free tw object (v2 fix)
|
|
485
|
-
│ ├── createComponent.ts 🏗️ Single styled-chain factory
|
|
486
|
-
│ ├── templateParser.ts 📝 Tagged template literal parser
|
|
487
|
-
│ ├── cv.ts 🎭 Standalone class variants
|
|
488
|
-
│ ├── withTw.ts 🔌 HOC wrapper untuk komponen apapun
|
|
489
|
-
│ └── styledFactory.ts 🏭 Thin styled-components wrapper
|
|
490
|
-
│
|
|
491
|
-
├── runtime/
|
|
492
|
-
│ ├── propEngine.ts ⚙️ 80+ prop → class resolvers
|
|
493
|
-
│ ├── variantEngine.ts 🎭 Variant resolution
|
|
494
|
-
│ ├── compoundVariant.ts 🔀 Compound variant resolution
|
|
495
|
-
│ ├── responsiveEngine.ts📐 Responsive object handler
|
|
496
|
-
│ └── classResolver.ts 🔗 Full pipeline orchestrator
|
|
497
|
-
│
|
|
498
|
-
├── theme/
|
|
499
|
-
│ ├── themeStore.ts 🎨 Global token store
|
|
500
|
-
│ ├── tokenResolver.ts 🔑 Token → class segment resolver
|
|
501
|
-
│ └── defaultTheme.ts 📦 Ready-to-use semantic tokens
|
|
502
|
-
│
|
|
503
|
-
├── plugins/
|
|
504
|
-
│ ├── pluginEngine.ts 🔌 Plugin registry & resolver
|
|
505
|
-
│ ├── registerPlugin.ts 📋 Public plugin registration API
|
|
506
|
-
│ ├── builtinPlugins.ts 🧰 Animation, grid, backdrop, gradient
|
|
507
|
-
│ └── tailwindV4Plugins.ts 🆕 Typography, forms, aspect, animations
|
|
508
|
-
│
|
|
509
|
-
├── compiler/
|
|
510
|
-
│ ├── astTransform.ts 🔄 Source code transformer (zero-runtime)
|
|
511
|
-
│ ├── transformTw.ts 🛠️ Transform orchestrator
|
|
512
|
-
│ ├── vitePlugin.ts ⚡ Vite/Rollup plugin
|
|
513
|
-
│ ├── swcPlugin.ts 🔧 Next.js SWC transform
|
|
514
|
-
│ ├── webpackLoader.ts 📦 Webpack loader
|
|
515
|
-
│ └── classExtractor.ts 🔍 Class extraction for safelist
|
|
516
|
-
│
|
|
517
|
-
├── validation/
|
|
518
|
-
│ └── propValidator.ts 🛡️ Dev-only prop validation
|
|
519
|
-
│
|
|
520
|
-
└── utils/
|
|
521
|
-
├── cx.ts 🔗 Lightweight class joiner
|
|
522
|
-
├── classParser.ts 🔤 Class string utilities
|
|
523
|
-
├── hash.ts #️⃣ Stable hash generator
|
|
524
|
-
├── merge.ts 🔀 twMerge wrapper
|
|
525
|
-
└── isObject.ts 🔍 Type guard utility
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
---
|
|
529
|
-
|
|
530
|
-
## 🧪 Test Coverage
|
|
531
|
-
|
|
532
|
-
```
|
|
533
|
-
228 tests — 228 pass ✅
|
|
534
|
-
|
|
535
|
-
🔵 templateParser 9/9
|
|
536
|
-
🔵 themeStore 7/7
|
|
537
|
-
🔵 tokenResolver 7/7
|
|
538
|
-
🔵 defaultTheme 2/2
|
|
539
|
-
🔵 responsiveEngine 7/7
|
|
540
|
-
🔵 variantEngine 5/5
|
|
541
|
-
🔵 compoundVariant 5/5
|
|
542
|
-
🔵 propEngine 15/15
|
|
543
|
-
🔵 classResolver 2/2
|
|
544
|
-
🔵 pluginEngine 4/4
|
|
545
|
-
🔵 registerPlugin 3/3
|
|
546
|
-
🔵 builtinPlugins 7/7
|
|
547
|
-
🔵 tailwindV4Plugins 14/14
|
|
548
|
-
🔵 cv 4/4
|
|
549
|
-
🔵 propValidator 6/6
|
|
550
|
-
🔵 astTransform 10/10
|
|
551
|
-
🔵 utils 9/9
|
|
552
|
-
🔵 withTw 5/5
|
|
553
|
-
🔵 styledFactory 6/6
|
|
554
|
-
🔵 classParser 10/10
|
|
555
|
-
🔵 cx / cxm 8/8
|
|
556
|
-
🔵 classExtractor 4/4
|
|
557
|
-
🔵 generateSafelist 4/4
|
|
558
|
-
🔵 barrel exports 67/67
|
|
559
|
-
🔵 end-to-end 5/5
|
|
560
|
-
```
|
|
561
|
-
|
|
562
|
-
---
|
|
563
|
-
|
|
564
|
-
## 📋 Changelog v2
|
|
565
|
-
|
|
566
|
-
### 🔴 Bug Fixes
|
|
567
|
-
|
|
568
|
-
**`tw` undefined setelah bundle split (Vite/tsup)**
|
|
569
|
-
```
|
|
570
|
-
BEFORE: export const tw = new Proxy(fn, { get })
|
|
571
|
-
→ Proxy + tsup splitting = tw undefined di Next.js/Vite
|
|
572
|
-
|
|
573
|
-
AFTER: Object.assign(twCallable, { div, button, ... })
|
|
574
|
-
→ Plain object, transparan ke semua bundler
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
**Double wrap SSR hydration mismatch**
|
|
578
|
-
```
|
|
579
|
-
BEFORE: styled(tag)`` → styled(BaseStyled).attrs()``
|
|
580
|
-
→ Double wrap = SSR hydration mismatch + SC v6 error
|
|
581
|
-
|
|
582
|
-
AFTER: styled(tag).withConfig().attrs()``
|
|
583
|
-
→ Single chain, satu SC instance per component
|
|
584
|
-
```
|
|
585
|
-
|
|
586
|
-
**shouldForwardProp hardcoded**
|
|
587
|
-
```
|
|
588
|
-
BEFORE: Hanya blokir variant names yang hardcoded
|
|
589
|
-
→ User variant keys bocor ke DOM
|
|
590
|
-
|
|
591
|
-
AFTER: Built dynamically dari variant keys aktual
|
|
592
|
-
→ Zero DOM pollution apapun nama variant-nya
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
**TypeScript generic inference**
|
|
596
|
-
```
|
|
597
|
-
BEFORE: TwTagFactory tidak generic
|
|
598
|
-
→ "Binding element '$isHover' implicitly has 'any' type"
|
|
599
|
-
|
|
600
|
-
AFTER: TwTagFactory<P> generic function
|
|
601
|
-
→ Full type inference di template interpolations
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
---
|
|
605
|
-
|
|
606
|
-
## 📝 License
|
|
607
|
-
|
|
608
|
-
MIT © tailwind-styled-v4
|