@hulla/style 0.2.2 → 0.4.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 +3 -537
- package/dist/cjs/index.cjs +1 -1
- package/dist/es/index.mjs +1 -1
- package/dist/index.d.ts +5 -62
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -1,541 +1,7 @@
|
|
|
1
1
|
# @hulla/style
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
> A unified, type-safe styling library that works with any CSS framework or methodology.
|
|
3
|
+
Canonical documentation lives in the repository root README:
|
|
5
4
|
|
|
6
|
-
[
|
|
7
|
-
[](https://opensource.org/licenses/MIT)
|
|
8
|
-
|
|
9
|
-
## Why @hulla/style?
|
|
10
|
-
|
|
11
|
-
**@hulla/style** is a tiny (~1KB), zero-dependency library that unifies class name composition with powerful variant management. Unlike other solutions, it works consistently across any composer (clsx, tailwind-merge, etc.) and provides first-class TypeScript support.
|
|
12
|
-
|
|
13
|
-
### The Problem
|
|
14
|
-
|
|
15
|
-
When building component libraries, you often need to:
|
|
16
|
-
- Compose class names conditionally
|
|
17
|
-
- Define component variants (sizes, colors, states)
|
|
18
|
-
- Combine multiple variants together
|
|
19
|
-
- Use different CSS frameworks (Tailwind, vanilla CSS, CSS modules)
|
|
20
|
-
- Ensure type safety for all variants
|
|
21
|
-
|
|
22
|
-
Most libraries solve only part of this puzzle, forcing you to combine multiple tools or compromise on features.
|
|
23
|
-
|
|
24
|
-
### The Solution
|
|
25
|
-
|
|
26
|
-
**@hulla/style** provides a unified API that:
|
|
27
|
-
- ✅ **Works with any composer** - Use with clsx, tailwind-merge, or vanilla strings
|
|
28
|
-
- ✅ **Handles complex types** - Objects, arrays, nested structures work everywhere
|
|
29
|
-
- ✅ **Type-safe variants** - Get autocomplete and type checking for all variants
|
|
30
|
-
- ✅ **Composable architecture** - Mix variants, groups, and raw strings seamlessly
|
|
31
|
-
- ✅ **Framework agnostic** - Works with React, Vue, Astro, Svelte, or plain HTML
|
|
32
|
-
- ✅ **Zero dependencies** - Tiny bundle size, no external deps required
|
|
33
|
-
- ✅ **Extensible** - Customize serialization and composition behavior
|
|
34
|
-
|
|
35
|
-
## Comparison with Alternatives
|
|
36
|
-
|
|
37
|
-
| Feature | @hulla/style | clsx/classnames | cva | tailwind-variants |
|
|
38
|
-
|---------|--------------|-----------------|-----|-------------------|
|
|
39
|
-
| Class composition | ✅ | ✅ | ❌ | ❌ |
|
|
40
|
-
| Variant management | ✅ | ❌ | ✅ | ✅ |
|
|
41
|
-
| Variant groups | ✅ | ❌ | ❌ | Limited |
|
|
42
|
-
| Object syntax support | ✅ Everywhere | ✅ Only cn | ❌ | ❌ |
|
|
43
|
-
| Works with any composer | ✅ | N/A | ❌ tw only | ❌ tw only |
|
|
44
|
-
| Customizable serialization | ✅ | ❌ | ❌ | ❌ |
|
|
45
|
-
| Bundle size | ~1KB | ~1KB | ~2.5KB | ~5KB |
|
|
46
|
-
| TypeScript support | ✅ Full | Partial | ✅ Full | ✅ Full |
|
|
47
|
-
| Framework agnostic | ✅ | ✅ | ✅ | ❌ React only |
|
|
48
|
-
|
|
49
|
-
## Installation
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
npm install @hulla/style
|
|
53
|
-
# or
|
|
54
|
-
pnpm add @hulla/style
|
|
55
|
-
# or
|
|
56
|
-
yarn add @hulla/style
|
|
57
|
-
# or
|
|
58
|
-
bun add @hulla/style
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## Quick Start
|
|
62
|
-
|
|
63
|
-
```typescript
|
|
64
|
-
import { style, type VariantProps } from '@hulla/style'
|
|
65
|
-
|
|
66
|
-
// Create your style utilities
|
|
67
|
-
const { cn, variant, variantGroup } = style()
|
|
68
|
-
|
|
69
|
-
// Use cn for simple class composition
|
|
70
|
-
const buttonClass = cn('px-4 py-2', 'rounded', 'bg-blue-500')
|
|
71
|
-
// => "px-4 py-2 rounded bg-blue-500"
|
|
72
|
-
|
|
73
|
-
// Define variants for reusable component styles
|
|
74
|
-
const button = variant({
|
|
75
|
-
name: 'variant',
|
|
76
|
-
classes: {
|
|
77
|
-
primary: 'bg-blue-500 text-white',
|
|
78
|
-
secondary: 'bg-gray-500 text-white',
|
|
79
|
-
},
|
|
80
|
-
base: 'px-4 py-2 rounded font-semibold',
|
|
81
|
-
default: 'primary'
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
button.css() // => "px-4 py-2 rounded font-semibold bg-blue-500 text-white"
|
|
85
|
-
button.css('secondary') // => "px-4 py-2 rounded font-semibold bg-gray-500 text-white"
|
|
86
|
-
|
|
87
|
-
type Props = VariantProps<typeof button> // { variant?: 'primary' | 'secondary' }
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Core Concepts
|
|
91
|
-
|
|
92
|
-
### 1. Class Name Composition (`cn`)
|
|
93
|
-
|
|
94
|
-
The `cn` function composes class names, supporting strings, arrays, objects, Sets, and Maps:
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
const { cn } = style()
|
|
98
|
-
|
|
99
|
-
// Strings
|
|
100
|
-
cn('foo', 'bar') // => "foo bar"
|
|
101
|
-
|
|
102
|
-
// Arrays
|
|
103
|
-
cn(['foo', 'bar']) // => "foo bar"
|
|
104
|
-
|
|
105
|
-
// Objects (keys with truthy values)
|
|
106
|
-
cn({ foo: true, bar: false, baz: true }) // => "foo baz"
|
|
107
|
-
|
|
108
|
-
// Mixed
|
|
109
|
-
cn('base', ['hover:bg-blue'], { active: true, disabled: false })
|
|
110
|
-
// => "base hover:bg-blue active"
|
|
111
|
-
|
|
112
|
-
// Nested
|
|
113
|
-
cn('base', ['text-lg', { bold: true, italic: false }])
|
|
114
|
-
// => "base text-lg bold"
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### 2. Variants
|
|
118
|
-
|
|
119
|
-
Variants define reusable component styles with different states:
|
|
120
|
-
|
|
121
|
-
```typescript
|
|
122
|
-
const button = variant({
|
|
123
|
-
name: 'size',
|
|
124
|
-
classes: {
|
|
125
|
-
sm: 'text-sm px-2 py-1',
|
|
126
|
-
md: 'text-base px-4 py-2',
|
|
127
|
-
lg: 'text-lg px-6 py-3',
|
|
128
|
-
},
|
|
129
|
-
base: 'rounded font-semibold transition-colors',
|
|
130
|
-
default: 'md'
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
button.css('sm') // => "rounded font-semibold transition-colors text-sm px-2 py-1"
|
|
134
|
-
button.css('md') // => "rounded font-semibold transition-colors text-base px-4 py-2"
|
|
135
|
-
button.css() // => "rounded font-semibold transition-colors text-base px-4 py-2" (default)
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
#### Array Classes
|
|
139
|
-
|
|
140
|
-
```typescript
|
|
141
|
-
const button = variant({
|
|
142
|
-
name: 'variant',
|
|
143
|
-
classes: {
|
|
144
|
-
primary: ['bg-blue-500', 'text-white', 'hover:bg-blue-600'],
|
|
145
|
-
secondary: ['bg-gray-500', 'text-white', 'hover:bg-gray-600'],
|
|
146
|
-
},
|
|
147
|
-
default: 'primary'
|
|
148
|
-
})
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
#### Object Classes
|
|
152
|
-
|
|
153
|
-
```typescript
|
|
154
|
-
const button = variant({
|
|
155
|
-
name: 'state',
|
|
156
|
-
classes: {
|
|
157
|
-
active: { 'bg-blue-500': true, 'text-white': true, 'opacity-50': false },
|
|
158
|
-
disabled: { 'bg-gray-300': true, 'cursor-not-allowed': true },
|
|
159
|
-
},
|
|
160
|
-
default: 'active'
|
|
161
|
-
})
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
#### TypeScript Integration
|
|
165
|
-
|
|
166
|
-
```typescript
|
|
167
|
-
import type { VariantProps } from '@hulla/style'
|
|
168
|
-
|
|
169
|
-
const button = variant({
|
|
170
|
-
name: 'variant',
|
|
171
|
-
classes: {
|
|
172
|
-
primary: 'bg-blue-500',
|
|
173
|
-
secondary: 'bg-gray-500',
|
|
174
|
-
},
|
|
175
|
-
default: 'primary'
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
type ButtonProps = VariantProps<typeof button>
|
|
179
|
-
// ButtonProps = { variant?: 'primary' | 'secondary' }
|
|
180
|
-
|
|
181
|
-
function Button({ variant }: ButtonProps) {
|
|
182
|
-
return <button className={button.css(variant)} />
|
|
183
|
-
}
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
### 3. Variant Groups
|
|
187
|
-
|
|
188
|
-
Combine multiple variants for more complex component APIs:
|
|
189
|
-
|
|
190
|
-
```typescript
|
|
191
|
-
const size = variant({
|
|
192
|
-
name: 'size',
|
|
193
|
-
classes: {
|
|
194
|
-
sm: 'text-sm px-2 py-1',
|
|
195
|
-
md: 'text-base px-4 py-2',
|
|
196
|
-
lg: 'text-lg px-6 py-3',
|
|
197
|
-
},
|
|
198
|
-
default: 'md'
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
const variant = variant({
|
|
202
|
-
name: 'variant',
|
|
203
|
-
classes: {
|
|
204
|
-
primary: 'bg-blue-500 text-white',
|
|
205
|
-
secondary: 'bg-gray-500 text-white',
|
|
206
|
-
danger: 'bg-red-500 text-white',
|
|
207
|
-
},
|
|
208
|
-
default: 'primary'
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
const buttonStyles = variantGroup(size, variant)
|
|
212
|
-
|
|
213
|
-
// Use with defaults
|
|
214
|
-
buttonStyles.css({})
|
|
215
|
-
// => "text-base px-4 py-2 bg-blue-500 text-white"
|
|
216
|
-
|
|
217
|
-
// Override specific variants
|
|
218
|
-
buttonStyles.css({ size: 'lg', variant: 'danger' })
|
|
219
|
-
// => "text-lg px-6 py-3 bg-red-500 text-white"
|
|
220
|
-
|
|
221
|
-
// TypeScript support
|
|
222
|
-
type ButtonProps = VariantProps<typeof buttonStyles>
|
|
223
|
-
// ButtonProps = { size?: 'sm' | 'md' | 'lg', variant?: 'primary' | 'secondary' | 'danger' }
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
### 4. Composing Everything Together
|
|
227
|
-
|
|
228
|
-
Mix `cn`, variants, and variant groups seamlessly:
|
|
229
|
-
|
|
230
|
-
```typescript
|
|
231
|
-
const { cn, variant, variantGroup } = style()
|
|
232
|
-
|
|
233
|
-
const size = variant({
|
|
234
|
-
name: 'size',
|
|
235
|
-
classes: { sm: 'text-sm', lg: 'text-lg' },
|
|
236
|
-
default: 'sm'
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
const color = variant({
|
|
240
|
-
name: 'color',
|
|
241
|
-
classes: { blue: 'text-blue-500', red: 'text-red-500' },
|
|
242
|
-
default: 'blue'
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
const styles = variantGroup(size, color)
|
|
246
|
-
|
|
247
|
-
// Compose with additional classes
|
|
248
|
-
const finalClass = cn(
|
|
249
|
-
'base-class',
|
|
250
|
-
styles.css({ size: 'lg', color: 'red' }),
|
|
251
|
-
'hover:opacity-80',
|
|
252
|
-
{ active: true }
|
|
253
|
-
)
|
|
254
|
-
// => "base-class text-lg text-red-500 hover:opacity-80 active"
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
## Advanced Usage
|
|
258
|
-
|
|
259
|
-
### Custom Composers
|
|
260
|
-
|
|
261
|
-
Use @hulla/style with your preferred class name library:
|
|
262
|
-
|
|
263
|
-
```typescript
|
|
264
|
-
import { style } from '@hulla/style'
|
|
265
|
-
import { twMerge } from 'tailwind-merge'
|
|
266
|
-
import { clsx } from 'clsx'
|
|
267
|
-
|
|
268
|
-
// With tailwind-merge (handles Tailwind class conflicts)
|
|
269
|
-
const { cn, variant, variantGroup } = style({ composer: twMerge })
|
|
270
|
-
|
|
271
|
-
// With clsx
|
|
272
|
-
const { cn, variant, variantGroup } = style({ composer: clsx })
|
|
273
|
-
|
|
274
|
-
// Objects, arrays, and nested structures work with ANY composer!
|
|
275
|
-
cn({ 'text-blue-500': true, 'bg-white': false }, ['px-4', 'py-2'])
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
### Custom Serialization
|
|
279
|
-
|
|
280
|
-
Override how class names are serialized:
|
|
281
|
-
|
|
282
|
-
```typescript
|
|
283
|
-
import { style, defaultComposer } from '@hulla/style'
|
|
284
|
-
|
|
285
|
-
const { cn, variant, variantGroup } = style({
|
|
286
|
-
serializer: (input) => {
|
|
287
|
-
// Custom logic to convert input to string
|
|
288
|
-
if (typeof input === 'string') return input
|
|
289
|
-
// ... your custom serialization
|
|
290
|
-
return ''
|
|
291
|
-
},
|
|
292
|
-
composer: defaultComposer
|
|
293
|
-
})
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
### Variants Without Defaults
|
|
297
|
-
|
|
298
|
-
For more explicit APIs, create variants without defaults:
|
|
299
|
-
|
|
300
|
-
```typescript
|
|
301
|
-
const button = variant({
|
|
302
|
-
name: 'variant',
|
|
303
|
-
classes: {
|
|
304
|
-
primary: 'bg-blue-500',
|
|
305
|
-
secondary: 'bg-gray-500',
|
|
306
|
-
},
|
|
307
|
-
// No default specified
|
|
308
|
-
})
|
|
309
|
-
|
|
310
|
-
// TypeScript enforces passing a variant
|
|
311
|
-
button.css('primary') // ✅ OK
|
|
312
|
-
button.css() // ❌ TypeScript error: prop is required
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
## Real-World Examples
|
|
316
|
-
|
|
317
|
-
### React Button Component
|
|
318
|
-
|
|
319
|
-
```tsx
|
|
320
|
-
import { style } from '@hulla/style'
|
|
321
|
-
import type { VariantProps } from '@hulla/style'
|
|
322
|
-
import { twMerge } from 'tailwind-merge'
|
|
323
|
-
|
|
324
|
-
const { cn, variant, variantGroup } = style({ composer: twMerge })
|
|
325
|
-
|
|
326
|
-
const buttonSize = variant({
|
|
327
|
-
name: 'size',
|
|
328
|
-
classes: {
|
|
329
|
-
sm: 'text-sm px-3 py-1.5',
|
|
330
|
-
md: 'text-base px-4 py-2',
|
|
331
|
-
lg: 'text-lg px-6 py-3',
|
|
332
|
-
},
|
|
333
|
-
default: 'md'
|
|
334
|
-
})
|
|
335
|
-
|
|
336
|
-
const buttonVariant = variant({
|
|
337
|
-
name: 'variant',
|
|
338
|
-
classes: {
|
|
339
|
-
primary: 'bg-blue-500 hover:bg-blue-600 text-white',
|
|
340
|
-
secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-900',
|
|
341
|
-
danger: 'bg-red-500 hover:bg-red-600 text-white',
|
|
342
|
-
},
|
|
343
|
-
default: 'primary'
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
const buttonStyles = variantGroup(buttonSize, buttonVariant)
|
|
347
|
-
|
|
348
|
-
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
|
|
349
|
-
VariantProps<typeof buttonStyles>
|
|
350
|
-
|
|
351
|
-
export function Button({ size, variant, className, children, ...props }: ButtonProps) {
|
|
352
|
-
return (
|
|
353
|
-
<button
|
|
354
|
-
className={cn(
|
|
355
|
-
'rounded font-semibold transition-colors disabled:opacity-50',
|
|
356
|
-
buttonStyles.css({ size, variant }),
|
|
357
|
-
className
|
|
358
|
-
)}
|
|
359
|
-
{...props}
|
|
360
|
-
>
|
|
361
|
-
{children}
|
|
362
|
-
</button>
|
|
363
|
-
)
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// Usage
|
|
367
|
-
<Button size="lg" variant="danger" className="custom-class">
|
|
368
|
-
Delete
|
|
369
|
-
</Button>
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
### Astro Component
|
|
373
|
-
|
|
374
|
-
```astro
|
|
375
|
-
---
|
|
376
|
-
import { style } from '@hulla/style'
|
|
377
|
-
import type { VariantProps } from '@hulla/style'
|
|
378
|
-
|
|
379
|
-
const { variant, variantGroup } = style()
|
|
380
|
-
|
|
381
|
-
const size = variant({
|
|
382
|
-
name: 'size',
|
|
383
|
-
classes: {
|
|
384
|
-
sm: 'text-sm px-2 py-1',
|
|
385
|
-
md: 'text-base px-4 py-2',
|
|
386
|
-
},
|
|
387
|
-
default: 'md'
|
|
388
|
-
})
|
|
389
|
-
|
|
390
|
-
const color = variant({
|
|
391
|
-
name: 'color',
|
|
392
|
-
classes: {
|
|
393
|
-
primary: 'bg-blue-500 text-white',
|
|
394
|
-
secondary: 'bg-gray-500 text-white',
|
|
395
|
-
},
|
|
396
|
-
default: 'primary'
|
|
397
|
-
})
|
|
398
|
-
|
|
399
|
-
const buttonStyles = variantGroup(size, color)
|
|
400
|
-
|
|
401
|
-
type Props = VariantProps<typeof buttonStyles>
|
|
402
|
-
const props = Astro.props
|
|
403
|
-
---
|
|
404
|
-
|
|
405
|
-
<button class={buttonStyles.css(props)}>
|
|
406
|
-
<slot />
|
|
407
|
-
</button>
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
### Vue Component
|
|
411
|
-
|
|
412
|
-
```vue
|
|
413
|
-
<script setup lang="ts">
|
|
414
|
-
import { style } from '@hulla/style'
|
|
415
|
-
import type { VariantProps } from '@hulla/style'
|
|
416
|
-
|
|
417
|
-
const { cn, variant, variantGroup } = style()
|
|
418
|
-
|
|
419
|
-
const size = variant({
|
|
420
|
-
name: 'size',
|
|
421
|
-
classes: {
|
|
422
|
-
sm: 'text-sm px-2 py-1',
|
|
423
|
-
md: 'text-base px-4 py-2',
|
|
424
|
-
},
|
|
425
|
-
default: 'md'
|
|
426
|
-
})
|
|
427
|
-
|
|
428
|
-
const buttonVariant = variant({
|
|
429
|
-
name: 'variant',
|
|
430
|
-
classes: {
|
|
431
|
-
primary: 'bg-blue-500 text-white',
|
|
432
|
-
secondary: 'bg-gray-500 text-white',
|
|
433
|
-
},
|
|
434
|
-
default: 'primary'
|
|
435
|
-
})
|
|
436
|
-
|
|
437
|
-
const buttonStyles = variantGroup(size, buttonVariant)
|
|
438
|
-
|
|
439
|
-
type ButtonProps = VariantProps<typeof buttonStyles>
|
|
440
|
-
|
|
441
|
-
interface Props extends ButtonProps {
|
|
442
|
-
class?: string
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
const props = withDefaults(defineProps<Props>(), {})
|
|
446
|
-
|
|
447
|
-
const classes = computed(() =>
|
|
448
|
-
cn(
|
|
449
|
-
'rounded transition-colors',
|
|
450
|
-
buttonStyles.css({ size: props.size, variant: props.variant }),
|
|
451
|
-
props.class
|
|
452
|
-
)
|
|
453
|
-
)
|
|
454
|
-
</script>
|
|
455
|
-
|
|
456
|
-
<template>
|
|
457
|
-
<button :class="classes">
|
|
458
|
-
<slot />
|
|
459
|
-
</button>
|
|
460
|
-
</template>
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
## API Reference
|
|
464
|
-
|
|
465
|
-
### `style(config?)`
|
|
466
|
-
|
|
467
|
-
Creates style utilities with optional configuration.
|
|
468
|
-
|
|
469
|
-
```typescript
|
|
470
|
-
const { cn, variant, variantGroup } = style({
|
|
471
|
-
serializer?: (input: ClassName) => string,
|
|
472
|
-
composer?: (...strings: string[]) => string
|
|
473
|
-
})
|
|
474
|
-
```
|
|
475
|
-
|
|
476
|
-
**Parameters:**
|
|
477
|
-
- `config.serializer` - Custom function to serialize class name inputs to strings
|
|
478
|
-
- `config.composer` - Custom function to compose strings (e.g., `clsx`, `twMerge`)
|
|
479
|
-
|
|
480
|
-
**Returns:**
|
|
481
|
-
- `cn` - Function to compose class names
|
|
482
|
-
- `variant` - Function to create variants
|
|
483
|
-
- `variantGroup` - Function to create variant groups
|
|
484
|
-
|
|
485
|
-
### `cn(...classes)`
|
|
486
|
-
|
|
487
|
-
Composes class names from various input types.
|
|
488
|
-
|
|
489
|
-
```typescript
|
|
490
|
-
cn(
|
|
491
|
-
'string',
|
|
492
|
-
['array', 'of', 'strings'],
|
|
493
|
-
{ objectKey: boolean },
|
|
494
|
-
nestedStructures
|
|
495
|
-
)
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
### `variant(definition)`
|
|
499
|
-
|
|
500
|
-
Creates a variant with multiple style options.
|
|
501
|
-
|
|
502
|
-
```typescript
|
|
503
|
-
const myVariant = variant({
|
|
504
|
-
name: string, // Variant name (for variantGroup)
|
|
505
|
-
classes: Record<string, ClassName>, // Style definitions
|
|
506
|
-
base?: string, // Base classes applied to all variants
|
|
507
|
-
default?: keyof classes // Default variant (optional)
|
|
508
|
-
})
|
|
509
|
-
|
|
510
|
-
myVariant.css(key?) // Returns class string
|
|
511
|
-
myVariant.params // Access variant definition
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
### `variantGroup(...variants)`
|
|
515
|
-
|
|
516
|
-
Combines multiple variants into a single API.
|
|
517
|
-
|
|
518
|
-
```typescript
|
|
519
|
-
const group = variantGroup(variant1, variant2, ...)
|
|
520
|
-
|
|
521
|
-
group.css(props) // Returns composed class string
|
|
522
|
-
group.params // Access all variant definitions
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
### Type Helpers
|
|
526
|
-
|
|
527
|
-
```typescript
|
|
528
|
-
import type { VariantProps, ClassName, Serializer, Composer } from '@hulla/style'
|
|
529
|
-
|
|
530
|
-
// Extract props type from variant or variant group
|
|
531
|
-
type Props = VariantProps<typeof myVariantOrGroup>
|
|
532
|
-
```
|
|
533
|
-
|
|
534
|
-
## License
|
|
535
|
-
|
|
536
|
-
MIT © [Samuel Hulla](https://hulla.dev)
|
|
537
|
-
|
|
538
|
-
## Contributing
|
|
539
|
-
|
|
540
|
-
Contributions are welcome! Please check out our [GitHub repository](https://github.com/hulladev/style).
|
|
5
|
+
- [../../README.md](../../README.md)
|
|
541
6
|
|
|
7
|
+
This package-level README intentionally stays minimal so docs are maintained in one place.
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function t(t){let e=function t(e){if("string"==typeof e||"number"==typeof e)return e;if("object"==typeof e){if(Array.isArray(e))return e.map(t).join(" ");if(e instanceof Set||e instanceof Map)return Array.from(e).map(t).join(" ");let r="";return Object.entries(e??{}).forEach(([t,e])=>{e&&(r+=(r?" ":"")+t)}),r}return null}(t);return e?String(e):""}function e(...t){let r=new Set,n=[];for(let e of t)if(e)for(let t of e.split(/\s+/))!t||r.has(t)||(r.add(t),n.push(t));return n.join(" ")}exports.defaultComposer=e,exports.defaultSerializer=t,exports.style=function(r){let{serializer:n=t,composer:o=e}=r??{},f=n===t?(...e)=>{for(let r=0;r<e.length;r++){let n=e[r];"string"!=typeof n&&(e[r]=t(n))}return o(...e)}:(...t)=>o(...t.map(t=>n(t)));return{cn:f,vn:function(t){let e=e=>f(t[e]);return e.infer=void 0,e}}};
|
package/dist/es/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function t(t){let e=function t(e){if("string"==typeof e||"number"==typeof e)return e;if("object"==typeof e){if(Array.isArray(e))return e.map(t).join(" ");if(e instanceof Set||e instanceof Map)return Array.from(e).map(t).join(" ");let r="";return Object.entries(e??{}).forEach(([t,e])=>{e&&(r+=(r?" ":"")+t)}),r}return null}(t);return e?String(e):""}function e(...t){let r=new Set,n=[];for(let e of t)if(e)for(let t of e.split(/\s+/))!t||r.has(t)||(r.add(t),n.push(t));return n.join(" ")}function r(r){let{serializer:n=t,composer:f=e}=r??{},o=n===t?(...e)=>{for(let r=0;r<e.length;r++){let n=e[r];"string"!=typeof n&&(e[r]=t(n))}return f(...e)}:(...t)=>f(...t.map(t=>n(t)));return{cn:o,vn:function(t){let e=e=>o(t[e]);return e.infer=void 0,e}}}export{e as defaultComposer,t as defaultSerializer,r as style};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,81 +1,24 @@
|
|
|
1
1
|
type ClassNameNonRecursive = string | string[] | null | undefined | false | true | boolean | 0 | 0n | typeof NaN;
|
|
2
|
-
type ClassesDefinition = Record<string, ClassNameNonRecursive | Record<string, any> | any[]>;
|
|
3
|
-
type Simplify<T> = {
|
|
4
|
-
[K in keyof T]: T[K];
|
|
5
|
-
} & {};
|
|
6
|
-
type KeysWithDefault<V extends readonly VariantAPI<any>[]> = {
|
|
7
|
-
[P in V[number]["params"] as P["name"]]: P["default"] extends undefined ? P["name"] : never;
|
|
8
|
-
}[V[number]["params"]["name"]];
|
|
9
|
-
type KeysWithoutDefault<V extends readonly VariantAPI<any>[]> = {
|
|
10
|
-
[P in V[number]["params"] as P["name"]]: P["default"] extends undefined ? never : P["name"];
|
|
11
|
-
}[V[number]["params"]["name"]];
|
|
12
|
-
type GroupProps<V extends readonly VariantAPI<any>[]> = Simplify<{
|
|
13
|
-
[P in KeysWithDefault<V>]: keyof Extract<V[number]["params"], {
|
|
14
|
-
name: P;
|
|
15
|
-
}>["classes"];
|
|
16
|
-
} & {
|
|
17
|
-
[P in KeysWithoutDefault<V>]?: keyof Extract<V[number]["params"], {
|
|
18
|
-
name: P;
|
|
19
|
-
}>["classes"];
|
|
20
|
-
}>;
|
|
21
|
-
type APIPropsMapper<V extends VariantAPI<any>> = V extends VariantAPI<Variant<infer N, infer VN, infer D, any>> ? D extends NO_DEFAULT ? {
|
|
22
|
-
[K in N]: keyof VN;
|
|
23
|
-
} : {
|
|
24
|
-
[K in N]?: keyof VN;
|
|
25
|
-
} : {};
|
|
26
2
|
|
|
27
3
|
type ClassName = ClassNameNonRecursive | Record<string, ClassNameNonRecursive>;
|
|
28
4
|
type Serializer = (input: ClassName) => string;
|
|
29
5
|
type Composer = (...strings: string[]) => string;
|
|
30
|
-
|
|
31
|
-
type NO_DEFAULT = typeof NO_DEFAULT;
|
|
32
|
-
type Variant<N extends string, V extends ClassesDefinition, D extends keyof V | NO_DEFAULT = NO_DEFAULT, B extends string = ""> = {
|
|
33
|
-
name: N;
|
|
34
|
-
classes: V;
|
|
35
|
-
base?: B;
|
|
36
|
-
default?: D extends NO_DEFAULT ? never : D;
|
|
37
|
-
};
|
|
6
|
+
type VariantsDefinition = Record<string, ClassNameNonRecursive | Record<string, any> | any[]>;
|
|
38
7
|
type StyleConfig = {
|
|
39
8
|
serializer?: Serializer;
|
|
40
9
|
composer?: Composer;
|
|
41
10
|
};
|
|
42
|
-
type
|
|
43
|
-
|
|
44
|
-
css: V extends Variant<any, any, NO_DEFAULT, any> ? (prop: keyof V["classes"]) => string : (prop?: keyof V["classes"]) => string;
|
|
45
|
-
};
|
|
46
|
-
type VariantGroupAPI<V extends readonly VariantAPI<any>[]> = {
|
|
47
|
-
params: Record<V[number]["params"]["name"], VariantAPI<any>>;
|
|
48
|
-
css: (props: GroupProps<V>) => string;
|
|
11
|
+
type Variant<V extends VariantsDefinition> = ((prop: keyof V) => string) & {
|
|
12
|
+
infer: keyof V;
|
|
49
13
|
};
|
|
50
|
-
type StyleFunctions = {
|
|
51
|
-
cn: <const CN extends ClassName[]>(...classes: CN) => string;
|
|
52
|
-
};
|
|
53
|
-
type VariantProps<V> = V extends VariantGroupAPI<infer G> ? GroupProps<G> : V extends VariantAPI<infer VD> ? APIPropsMapper<VariantAPI<VD>> : {};
|
|
54
|
-
|
|
55
|
-
declare function variantGroupBuilder<const V extends readonly VariantAPI<any>[]>(...variants: V): VariantGroupAPI<V>;
|
|
56
14
|
|
|
57
15
|
declare function style(config?: StyleConfig): {
|
|
58
16
|
cn: <const CN extends ClassName[]>(...classes: CN) => string;
|
|
59
|
-
|
|
60
|
-
variant: {
|
|
61
|
-
<N extends string, V extends ClassesDefinition, D extends keyof V & string, B extends string = "">(variantDefinition: {
|
|
62
|
-
name: N;
|
|
63
|
-
classes: V;
|
|
64
|
-
default: D;
|
|
65
|
-
base?: B;
|
|
66
|
-
}): VariantAPI<Variant<N, V, D, B>>;
|
|
67
|
-
<N extends string, V extends ClassesDefinition, B extends string = "">(variantDefinition: {
|
|
68
|
-
name: N;
|
|
69
|
-
classes: V;
|
|
70
|
-
base?: B;
|
|
71
|
-
} & {
|
|
72
|
-
default?: undefined;
|
|
73
|
-
}): VariantAPI<Variant<N, V, NO_DEFAULT, B>>;
|
|
74
|
-
};
|
|
17
|
+
vn: <const V extends VariantsDefinition>(variants: V) => Variant<V>;
|
|
75
18
|
};
|
|
76
19
|
|
|
77
20
|
declare function defaultSerializer(input: ClassName): string;
|
|
78
21
|
|
|
79
22
|
declare function defaultComposer(...strings: string[]): string;
|
|
80
23
|
|
|
81
|
-
export { type ClassName, type Composer,
|
|
24
|
+
export { type ClassName, type Composer, type Serializer, type StyleConfig, type Variant, type VariantsDefinition, defaultComposer, defaultSerializer, style };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hulla/style",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Styling made easy 🎨",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Samuel Hulla",
|
|
@@ -31,16 +31,17 @@
|
|
|
31
31
|
"default": "./dist/es/index.mjs"
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"lint": "eslint . --fix",
|
|
36
|
+
"build": "bunchee -m",
|
|
37
|
+
"test": "vitest run tests/index.test.ts && pnpm run test:types",
|
|
38
|
+
"test:types": "pnpm --dir ../.. exec tsc -p packages/style/tsconfig.types.json --noEmit"
|
|
39
|
+
},
|
|
34
40
|
"devDependencies": {
|
|
35
41
|
"astro": "^4.10.3",
|
|
36
42
|
"bunchee": "^5.2.1",
|
|
37
43
|
"clsx": "^2.1.1",
|
|
38
44
|
"tailwind-merge": "^2.3.0",
|
|
39
45
|
"vitest": "^1.6.0"
|
|
40
|
-
},
|
|
41
|
-
"scripts": {
|
|
42
|
-
"lint": "eslint . --fix",
|
|
43
|
-
"build": "bunchee -m",
|
|
44
|
-
"test": "vitest run"
|
|
45
46
|
}
|
|
46
|
-
}
|
|
47
|
+
}
|