tailwind-to-style 3.2.1 → 3.3.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 +523 -23
- package/dist/className/index.cjs +11020 -0
- package/dist/className/index.esm.js +11015 -0
- package/dist/className/index.esm.js.map +1 -0
- package/dist/core/tws.cjs +1 -1
- package/dist/core/tws.esm.js +1 -1
- package/dist/core/tws.esm.js.map +1 -1
- package/dist/core/twsx.cjs +621 -370
- package/dist/core/twsx.esm.js +621 -370
- package/dist/core/twsx.esm.js.map +1 -1
- package/dist/core/twsxVariants.cjs +624 -372
- package/dist/core/twsxVariants.esm.js +624 -372
- package/dist/core/twsxVariants.esm.js.map +1 -1
- package/dist/cx.cjs +1 -1
- package/dist/cx.esm.js +1 -1
- package/dist/index.cjs +3749 -615
- package/dist/index.d.ts +989 -0
- package/dist/index.esm.js +3732 -616
- package/dist/index.esm.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/utils/index.cjs +94 -27
- package/dist/utils/index.esm.js +94 -27
- package/dist/utils/index.esm.js.map +1 -1
- package/package.json +6 -1
- package/types/className/index.d.ts +41 -0
- package/types/index.d.ts +989 -0
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# tailwind-to-style
|
|
2
2
|
|
|
3
3
|
[📦 View on npm](https://www.npmjs.com/package/tailwind-to-style)
|
|
4
|
+
| [🌐 Landing Page](https://bigetion.github.io/tailwind-to-style/landing.html)
|
|
5
|
+
| [🛝 Playground](https://bigetion.github.io/tailwind-to-style/sandbox.html)
|
|
4
6
|
|
|
5
7
|
[](https://www.npmjs.com/package/tailwind-to-style)
|
|
6
8
|
[](https://github.com/Bigetion/tailwind-to-style/actions)
|
|
@@ -22,6 +24,8 @@
|
|
|
22
24
|
- [`tws()` — Tailwind to Inline Styles](#tws--tailwind-to-inline-styles)
|
|
23
25
|
- [`twsx()` — CSS-in-JS Engine](#twsx--css-in-js-engine)
|
|
24
26
|
- [`twsxVariants()` — Component Variant System](#twsxvariants--component-variant-system)
|
|
27
|
+
- [`twsxClassName()` — Unified CSS-in-JS](#twsxclassname--unified-css-in-js)
|
|
28
|
+
- [`tw()` — Atomic CSS Classes](#tw--atomic-css-classes)
|
|
25
29
|
- [`cx()` — Conditional Class Builder](#cx--conditional-class-builder)
|
|
26
30
|
- [Configuration & Plugins](#configuration--plugins)
|
|
27
31
|
- [`configure()` — Custom Theme](#configure--custom-theme)
|
|
@@ -50,6 +54,8 @@
|
|
|
50
54
|
| **Full Tailwind Support** | All utilities, responsive, pseudo-states, arbitrary values |
|
|
51
55
|
| **SCSS-like Nesting** | `twsx()` for complex nested selector-based styles |
|
|
52
56
|
| **Variant System** | Type-safe component variants like CVA/tailwind-variants |
|
|
57
|
+
| **Unified CSS-in-JS** | `twsxClassName()` — one API for basic/variants/slots |
|
|
58
|
+
| **Atomic CSS Classes** | `tw()` — reusable classes with hover/responsive support |
|
|
53
59
|
| **Conditional Classes** | Built-in `cx()` utility (like clsx/classnames) |
|
|
54
60
|
| **SSR Support** | Server-side rendering with `startSSR()`/`stopSSR()` |
|
|
55
61
|
| **@css Directive** | Inject raw CSS for vendor-specific or complex properties |
|
|
@@ -179,6 +185,8 @@ tws('p-0.5 m-1.5 gap-2.5')
|
|
|
179
185
|
|
|
180
186
|
Generates real CSS from Tailwind classes with full selector support, SCSS-like nesting, and auto-injects a `<style>` tag into the DOM.
|
|
181
187
|
|
|
188
|
+
> **HMR-safe** — each `twsx()` call owns a stable slot in the injected style tag keyed by its top-level selectors. When you edit styles during development, the old slot is **replaced** (not appended), so changes are reflected immediately without a hard refresh.
|
|
189
|
+
|
|
182
190
|
```javascript
|
|
183
191
|
import { twsx } from 'tailwind-to-style'
|
|
184
192
|
|
|
@@ -385,6 +393,303 @@ btnClass({ 'opacity-50': disabled })
|
|
|
385
393
|
|
|
386
394
|
---
|
|
387
395
|
|
|
396
|
+
### `twsxClassName()` — Unified CSS-in-JS
|
|
397
|
+
|
|
398
|
+
The complete CSS-in-JS solution with automatic mode detection. Generates scoped class names with auto-injected CSS from Tailwind classes.
|
|
399
|
+
|
|
400
|
+
```javascript
|
|
401
|
+
import { twsxClassName, tw } from 'tailwind-to-style'
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**Basic Mode** — Simple className generation:
|
|
405
|
+
|
|
406
|
+
```javascript
|
|
407
|
+
// Returns: "btn-a1b2c3d4" (or "btn" if hash: false)
|
|
408
|
+
const button = twsxClassName({
|
|
409
|
+
name: 'btn',
|
|
410
|
+
_: 'px-4 py-2 bg-blue-500 text-white rounded-lg',
|
|
411
|
+
hover: 'bg-blue-600',
|
|
412
|
+
focus: 'ring-2 ring-blue-500',
|
|
413
|
+
active: 'bg-blue-700',
|
|
414
|
+
dark: 'bg-blue-400',
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
// Usage
|
|
418
|
+
<button class="${button}">Click me</button>
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**Variants Mode** — Component variants like CVA/tailwind-variants:
|
|
422
|
+
|
|
423
|
+
```javascript
|
|
424
|
+
const button = twsxClassName({
|
|
425
|
+
name: 'btn',
|
|
426
|
+
base: 'px-4 py-2 font-medium rounded-lg transition-colors',
|
|
427
|
+
variants: {
|
|
428
|
+
variant: {
|
|
429
|
+
primary: 'bg-blue-500 text-white hover:bg-blue-600',
|
|
430
|
+
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
|
|
431
|
+
outline: 'border-2 border-blue-500 text-blue-500 hover:bg-blue-50',
|
|
432
|
+
},
|
|
433
|
+
size: {
|
|
434
|
+
sm: 'text-sm px-3 py-1.5',
|
|
435
|
+
md: 'text-base px-4 py-2',
|
|
436
|
+
lg: 'text-lg px-6 py-3',
|
|
437
|
+
},
|
|
438
|
+
disabled: {
|
|
439
|
+
true: 'opacity-50 cursor-not-allowed',
|
|
440
|
+
false: 'cursor-pointer',
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
compoundVariants: [
|
|
444
|
+
{ variant: 'primary', size: 'lg', class: 'shadow-lg' },
|
|
445
|
+
],
|
|
446
|
+
defaultVariants: {
|
|
447
|
+
variant: 'primary',
|
|
448
|
+
size: 'md',
|
|
449
|
+
disabled: false,
|
|
450
|
+
},
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
// Usage
|
|
454
|
+
<button class="${button()}">Default</button>
|
|
455
|
+
<button class="${button({ variant: 'secondary', size: 'lg' })}">Large Secondary</button>
|
|
456
|
+
<button class="${button({ disabled: true })}">Disabled</button>
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
**Slots Mode** — Multi-part components:
|
|
460
|
+
|
|
461
|
+
```javascript
|
|
462
|
+
const card = twsxClassName({
|
|
463
|
+
name: 'card',
|
|
464
|
+
slots: {
|
|
465
|
+
root: 'bg-white rounded-xl shadow-lg overflow-hidden',
|
|
466
|
+
header: 'px-6 py-4 border-b border-gray-200',
|
|
467
|
+
title: 'text-xl font-bold text-gray-900',
|
|
468
|
+
body: 'px-6 py-4',
|
|
469
|
+
footer: 'px-6 py-4 border-t border-gray-200',
|
|
470
|
+
},
|
|
471
|
+
variants: {
|
|
472
|
+
variant: {
|
|
473
|
+
elevated: { root: 'shadow-2xl' },
|
|
474
|
+
outlined: { root: 'shadow-none border-2 border-gray-200' },
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
// Usage
|
|
480
|
+
const classes = card({ variant: 'elevated' })
|
|
481
|
+
<div class="${classes.root}">
|
|
482
|
+
<div class="${classes.header}">
|
|
483
|
+
<h2 class="${classes.title}">Card Title</h2>
|
|
484
|
+
</div>
|
|
485
|
+
<div class="${classes.body}">Content here</div>
|
|
486
|
+
<div class="${classes.footer}">Footer</div>
|
|
487
|
+
</div>
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
**Namespace Methods:**
|
|
491
|
+
|
|
492
|
+
| Category | Method | Description |
|
|
493
|
+
|----------|--------|-------------|
|
|
494
|
+
| **Config** | `config(options)` | Set global options (prefix, hash, hashLength) |
|
|
495
|
+
| | `getConfig()` | Get current configuration |
|
|
496
|
+
| **Tokens** | `defineTokens(tokens)` | Define design tokens |
|
|
497
|
+
| | `getTokens()` | Get all defined tokens |
|
|
498
|
+
| | `setToken(path, value)` | Set a single token value |
|
|
499
|
+
| **Themes** | `createTheme(name, tokens)` | Create a named theme |
|
|
500
|
+
| | `setTheme(name)` | Activate a theme |
|
|
501
|
+
| | `getTheme()` | Get active theme name |
|
|
502
|
+
| | `getThemes()` | Get all defined themes |
|
|
503
|
+
| **Extend** | `extend(base, extension)` | Extend an existing config |
|
|
504
|
+
| | `compose(...configs)` | Compose multiple configs |
|
|
505
|
+
| | `merge(...classes)` | Merge class values (like cx) |
|
|
506
|
+
| **Animation** | `defineAnimation(name, config)` | Register custom animation |
|
|
507
|
+
| | `getAnimations()` | Get all registered animations |
|
|
508
|
+
| **SSR** | `getCSS(className)` | Get CSS for a className |
|
|
509
|
+
| | `getAllCSS()` | Get all generated CSS |
|
|
510
|
+
| | `extractCSS()` | Extract as `<style>` tag |
|
|
511
|
+
| **Cache** | `clearCache()` | Clear all caches |
|
|
512
|
+
| | `getCacheStats()` | Get cache statistics |
|
|
513
|
+
| **Utility** | `tw(classes)` | Atomic CSS class generator |
|
|
514
|
+
|
|
515
|
+
```javascript
|
|
516
|
+
// ═══════════════════════════════════════════════════════════════
|
|
517
|
+
// Configuration
|
|
518
|
+
// ═══════════════════════════════════════════════════════════════
|
|
519
|
+
|
|
520
|
+
// Global configuration
|
|
521
|
+
twsxClassName.config({ prefix: 'my-app', hash: false, hashLength: 8 })
|
|
522
|
+
twsxClassName.getConfig() // → { prefix: 'my-app', hash: false, ... }
|
|
523
|
+
|
|
524
|
+
// ═══════════════════════════════════════════════════════════════
|
|
525
|
+
// Design Tokens
|
|
526
|
+
// ═══════════════════════════════════════════════════════════════
|
|
527
|
+
|
|
528
|
+
// Define tokens
|
|
529
|
+
twsxClassName.defineTokens({
|
|
530
|
+
colors: { primary: '#3b82f6', danger: '#ef4444' },
|
|
531
|
+
spacing: { sm: '0.5rem', md: '1rem' },
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
// Get all tokens
|
|
535
|
+
twsxClassName.getTokens() // → { colors: {...}, spacing: {...} }
|
|
536
|
+
|
|
537
|
+
// Set a single token
|
|
538
|
+
twsxClassName.setToken('colors.success', '#10b981')
|
|
539
|
+
|
|
540
|
+
// Use tokens in styles (use $path.to.token syntax)
|
|
541
|
+
const btn = twsxClassName({ _: 'bg-$colors.primary p-$spacing.md' })
|
|
542
|
+
|
|
543
|
+
// ═══════════════════════════════════════════════════════════════
|
|
544
|
+
// Theme System
|
|
545
|
+
// ═══════════════════════════════════════════════════════════════
|
|
546
|
+
|
|
547
|
+
// Create named themes
|
|
548
|
+
twsxClassName.createTheme('dark', { background: '#1f2937', text: '#f9fafb' })
|
|
549
|
+
twsxClassName.createTheme('light', { background: '#ffffff', text: '#111827' })
|
|
550
|
+
|
|
551
|
+
// Switch themes
|
|
552
|
+
twsxClassName.setTheme('dark')
|
|
553
|
+
|
|
554
|
+
// Get current/all themes
|
|
555
|
+
twsxClassName.getTheme() // → 'dark'
|
|
556
|
+
twsxClassName.getThemes() // → { dark: {...}, light: {...} }
|
|
557
|
+
|
|
558
|
+
// ═══════════════════════════════════════════════════════════════
|
|
559
|
+
// Extend & Compose
|
|
560
|
+
// ═══════════════════════════════════════════════════════════════
|
|
561
|
+
|
|
562
|
+
// Extend existing config
|
|
563
|
+
const iconButton = twsxClassName.extend(button, {
|
|
564
|
+
base: 'p-0 aspect-square',
|
|
565
|
+
variants: { size: { sm: 'w-8 h-8', md: 'w-10 h-10' } },
|
|
566
|
+
})
|
|
567
|
+
|
|
568
|
+
// Compose multiple configs
|
|
569
|
+
const combined = twsxClassName.compose(baseStyles, variants, overrides)
|
|
570
|
+
|
|
571
|
+
// Merge classes (like cx)
|
|
572
|
+
twsxClassName.merge('p-4', isActive && 'bg-blue-500', { 'opacity-50': disabled })
|
|
573
|
+
|
|
574
|
+
// ═══════════════════════════════════════════════════════════════
|
|
575
|
+
// Animations
|
|
576
|
+
// ═══════════════════════════════════════════════════════════════
|
|
577
|
+
|
|
578
|
+
// Define custom animation preset
|
|
579
|
+
twsxClassName.defineAnimation('myFade', {
|
|
580
|
+
keyframes: { '0%': { opacity: 0 }, '100%': { opacity: 1 } },
|
|
581
|
+
duration: '300ms',
|
|
582
|
+
timing: 'ease-out',
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
// Get all registered animations
|
|
586
|
+
twsxClassName.getAnimations() // → { myFade: {...}, ... }
|
|
587
|
+
|
|
588
|
+
// Use in config
|
|
589
|
+
const modal = twsxClassName({
|
|
590
|
+
_: 'fixed inset-0',
|
|
591
|
+
animation: 'myFade', // or built-in: 'fadeIn', 'slideUp', etc.
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
// ═══════════════════════════════════════════════════════════════
|
|
595
|
+
// SSR Support
|
|
596
|
+
// ═══════════════════════════════════════════════════════════════
|
|
597
|
+
|
|
598
|
+
// Get CSS for specific className
|
|
599
|
+
twsxClassName.getCSS('btn-a1b2c3d4')
|
|
600
|
+
|
|
601
|
+
// Get all generated CSS
|
|
602
|
+
twsxClassName.getAllCSS()
|
|
603
|
+
|
|
604
|
+
// Extract as style tag (for SSR)
|
|
605
|
+
const styleTag = twsxClassName.extractCSS()
|
|
606
|
+
// → '<style data-twsx-classname>...</style>'
|
|
607
|
+
|
|
608
|
+
// ═══════════════════════════════════════════════════════════════
|
|
609
|
+
// Cache Management
|
|
610
|
+
// ═══════════════════════════════════════════════════════════════
|
|
611
|
+
|
|
612
|
+
// Clear all caches
|
|
613
|
+
twsxClassName.clearCache()
|
|
614
|
+
|
|
615
|
+
// Get cache statistics
|
|
616
|
+
twsxClassName.getCacheStats()
|
|
617
|
+
// → { classNameCacheSize: 42, cssCacheSize: 38, styleRegistrySize: 15 }
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
---
|
|
621
|
+
|
|
622
|
+
### `tw()` — Atomic CSS Classes
|
|
623
|
+
|
|
624
|
+
Generates reusable atomic CSS classes from Tailwind utilities. Unlike `tws()` which returns inline styles, `tw()` returns class names with auto-injected CSS — supporting pseudo-classes and responsive breakpoints.
|
|
625
|
+
|
|
626
|
+
```javascript
|
|
627
|
+
import { tw } from 'tailwind-to-style'
|
|
628
|
+
// or: import { twsxClassName } from '...' → twsxClassName.tw(...)
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
**Basic Usage:**
|
|
632
|
+
|
|
633
|
+
```javascript
|
|
634
|
+
// Returns: "tw-flex tw-gap-3 tw-items-center"
|
|
635
|
+
tw('flex gap-3 items-center')
|
|
636
|
+
|
|
637
|
+
// Usage
|
|
638
|
+
<div class="${tw('flex gap-3 items-center')}">
|
|
639
|
+
<span>Item 1</span>
|
|
640
|
+
<span>Item 2</span>
|
|
641
|
+
</div>
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
**With Pseudo-classes:**
|
|
645
|
+
|
|
646
|
+
```javascript
|
|
647
|
+
// Returns: "tw-bg-gray-100 tw-hover-bg-gray-200 tw-focus-ring-2"
|
|
648
|
+
tw('bg-gray-100 hover:bg-gray-200 focus:ring-2')
|
|
649
|
+
|
|
650
|
+
// Unlike tws(), these actually work!
|
|
651
|
+
<div class="${tw('opacity-0 hover:opacity-100 transition-opacity')}">
|
|
652
|
+
Hover to reveal
|
|
653
|
+
</div>
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
**With Responsive Breakpoints:**
|
|
657
|
+
|
|
658
|
+
```javascript
|
|
659
|
+
// Returns: "tw-flex tw-flex-col tw-md-flex-row tw-lg-gap-8"
|
|
660
|
+
tw('flex flex-col md:flex-row lg:gap-8')
|
|
661
|
+
|
|
662
|
+
<div class="${tw('grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4')}">
|
|
663
|
+
<!-- Responsive grid -->
|
|
664
|
+
</div>
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
**Combining with twsxClassName:**
|
|
668
|
+
|
|
669
|
+
```javascript
|
|
670
|
+
const button = twsxClassName({
|
|
671
|
+
name: 'btn',
|
|
672
|
+
base: 'px-4 py-2 rounded-lg',
|
|
673
|
+
variants: { color: { primary: 'bg-blue-500', danger: 'bg-red-500' } },
|
|
674
|
+
})
|
|
675
|
+
|
|
676
|
+
// Use tw() for layout, twsxClassName for components
|
|
677
|
+
<div class="${tw('flex gap-3 flex-wrap')}">
|
|
678
|
+
<button class="${button({ color: 'primary' })}">Save</button>
|
|
679
|
+
<button class="${button({ color: 'danger' })}">Delete</button>
|
|
680
|
+
</div>
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
**When to use which:**
|
|
684
|
+
|
|
685
|
+
| Function | Use Case | Example |
|
|
686
|
+
|----------|----------|---------|
|
|
687
|
+
| `tws()` | Quick inline styles, no pseudo | `style="${tws('p-4 bg-blue')}"` |
|
|
688
|
+
| `tw()` | Reusable layout utilities | `class="${tw('flex gap-3 hover:...')}"` |
|
|
689
|
+
| `twsxClassName` | Components with variants | `class="${button({ size: 'lg' })}"` |
|
|
690
|
+
|
|
691
|
+
---
|
|
692
|
+
|
|
388
693
|
## Configuration & Plugins
|
|
389
694
|
|
|
390
695
|
### `configure()` — Custom Theme
|
|
@@ -502,12 +807,49 @@ const fullHtml = `
|
|
|
502
807
|
| `IS_BROWSER` | `true` in browser environment |
|
|
503
808
|
| `IS_SERVER` | `true` in Node.js/server environment |
|
|
504
809
|
|
|
810
|
+
### Advanced SSR Collector
|
|
811
|
+
|
|
812
|
+
For more control over SSR CSS collection:
|
|
813
|
+
|
|
814
|
+
```javascript
|
|
815
|
+
import { createSSRCollector } from 'tailwind-to-style'
|
|
816
|
+
|
|
817
|
+
// Create collector with options
|
|
818
|
+
const ssr = createSSRCollector({
|
|
819
|
+
dedupe: true, // Remove duplicate rules
|
|
820
|
+
minify: true, // Minify output CSS
|
|
821
|
+
sort: true, // Sort by specificity
|
|
822
|
+
})
|
|
823
|
+
|
|
824
|
+
// Render app
|
|
825
|
+
const html = renderToString(<App />)
|
|
826
|
+
|
|
827
|
+
// Extract CSS
|
|
828
|
+
const css = ssr.extractRaw() // Raw CSS string
|
|
829
|
+
const styleTag = ssr.extract({ id: 'ssr-css' }) // <style id="ssr-css">...</style>
|
|
830
|
+
|
|
831
|
+
// Or extract critical CSS (above-the-fold)
|
|
832
|
+
const { critical, rest, stats } = ssr.extractCritical({ maxSize: 14000 })
|
|
833
|
+
// critical: CSS under 14KB limit
|
|
834
|
+
// rest: remaining CSS to lazy-load
|
|
835
|
+
// stats: { criticalSize, criticalCount, totalCount }
|
|
836
|
+
|
|
837
|
+
// Utilities
|
|
838
|
+
ssr.peek() // Preview CSS without stopping
|
|
839
|
+
ssr.count // Number of rules collected
|
|
840
|
+
ssr.uniqueCount // Unique rules (after dedupe)
|
|
841
|
+
ssr.clear(true) // Clear and restart collecting
|
|
842
|
+
ssr.getStats() // { ruleCount, uniqueCount, totalSize, minifiedSize }
|
|
843
|
+
```
|
|
844
|
+
|
|
505
845
|
---
|
|
506
846
|
|
|
507
847
|
## Animation System
|
|
508
848
|
|
|
509
849
|
### Built-in CSS Animations
|
|
510
850
|
|
|
851
|
+
Tailwind animation utilities work out of the box:
|
|
852
|
+
|
|
511
853
|
```javascript
|
|
512
854
|
tws('animate-spin') // → animation: spin 1s linear infinite
|
|
513
855
|
tws('animate-bounce') // → animation: bounce 1s infinite
|
|
@@ -515,32 +857,104 @@ tws('animate-pulse') // → animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) i
|
|
|
515
857
|
tws('animate-ping') // → animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite
|
|
516
858
|
```
|
|
517
859
|
|
|
518
|
-
### Web Animations API
|
|
860
|
+
### Programmatic Animations (Web Animations API)
|
|
861
|
+
|
|
862
|
+
For interactive animations with full control:
|
|
863
|
+
|
|
864
|
+
```javascript
|
|
865
|
+
import { animate, chain, stagger, parallel, transition } from 'tailwind-to-style'
|
|
866
|
+
|
|
867
|
+
// Single element animation
|
|
868
|
+
const ctrl = animate(element, 'fadeIn', { duration: 300 })
|
|
869
|
+
ctrl.pause() // Pause
|
|
870
|
+
ctrl.play() // Resume
|
|
871
|
+
ctrl.reverse() // Reverse
|
|
872
|
+
ctrl.cancel() // Cancel
|
|
873
|
+
await ctrl.finished // Wait for completion
|
|
874
|
+
|
|
875
|
+
// Sequential animations
|
|
876
|
+
await chain(element, ['fadeIn', 'slideUp', 'pulse'])
|
|
877
|
+
|
|
878
|
+
// With custom options per step
|
|
879
|
+
await chain(element, [
|
|
880
|
+
'fadeIn',
|
|
881
|
+
{ name: 'slideUp', delay: 200 },
|
|
882
|
+
{ name: 'pulse', options: { iterations: 2 } }
|
|
883
|
+
])
|
|
884
|
+
|
|
885
|
+
// Staggered animations (lists, grids)
|
|
886
|
+
stagger('.card', 'fadeIn', {
|
|
887
|
+
delay: 50, // 50ms between each
|
|
888
|
+
from: 'start', // 'start' | 'end' | 'center' | 'random'
|
|
889
|
+
onAllComplete: () => console.log('Done!')
|
|
890
|
+
})
|
|
891
|
+
|
|
892
|
+
// Parallel animations on multiple elements
|
|
893
|
+
await parallel([el1, el2, el3], 'fadeIn')
|
|
894
|
+
|
|
895
|
+
// Transition between states
|
|
896
|
+
transition(element,
|
|
897
|
+
{ opacity: 0, transform: 'scale(0.9)' }, // from
|
|
898
|
+
{ opacity: 1, transform: 'scale(1)' }, // to
|
|
899
|
+
{ duration: 300, easing: 'ease-out' }
|
|
900
|
+
)
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
### Animation Presets
|
|
519
904
|
|
|
520
905
|
```javascript
|
|
521
|
-
import {
|
|
906
|
+
import { ANIMATION_PRESETS, EASING } from 'tailwind-to-style'
|
|
522
907
|
|
|
523
|
-
//
|
|
524
|
-
|
|
525
|
-
|
|
908
|
+
// Available presets
|
|
909
|
+
// fadeIn, fadeOut, slideUp, slideDown, slideLeft, slideRight,
|
|
910
|
+
// zoomIn, zoomOut, bounce, shake, pulse, spin, flipX, flipY,
|
|
911
|
+
// enterScale, exitScale, wiggle, heartbeat
|
|
912
|
+
|
|
913
|
+
// Easing presets
|
|
914
|
+
// linear, ease, easeIn, easeOut, easeInOut,
|
|
915
|
+
// spring, springLight, springMedium, springHeavy,
|
|
916
|
+
// smooth, smoothIn, smoothOut, bounce, elastic
|
|
526
917
|
```
|
|
527
918
|
|
|
528
|
-
###
|
|
919
|
+
### Custom Keyframes
|
|
529
920
|
|
|
530
921
|
```javascript
|
|
531
|
-
import {
|
|
922
|
+
import { createKeyframes, clearKeyframes, registerPreset } from 'tailwind-to-style'
|
|
923
|
+
|
|
924
|
+
// Create custom keyframes (auto-injects CSS)
|
|
925
|
+
const animationValue = createKeyframes('myFade', {
|
|
926
|
+
'0%': { opacity: 0, transform: 'translateY(-10px)' },
|
|
927
|
+
'100%': { opacity: 1, transform: 'translateY(0)' }
|
|
928
|
+
}, { duration: 400, easing: 'ease-out' })
|
|
929
|
+
// → "myFade 400ms ease-out forwards"
|
|
930
|
+
|
|
931
|
+
// Register as reusable preset
|
|
932
|
+
registerPreset('myFade', {
|
|
933
|
+
keyframes: [{ opacity: 0 }, { opacity: 1 }],
|
|
934
|
+
options: { duration: 400 }
|
|
935
|
+
})
|
|
532
936
|
|
|
533
|
-
//
|
|
534
|
-
|
|
937
|
+
// Use it
|
|
938
|
+
animate(element, 'myFade')
|
|
535
939
|
|
|
536
|
-
//
|
|
537
|
-
|
|
940
|
+
// Cleanup
|
|
941
|
+
clearKeyframes()
|
|
942
|
+
```
|
|
538
943
|
|
|
539
|
-
|
|
540
|
-
chainAnimations(element, ['fadeIn', 'slideUp', 'bounceIn'])
|
|
944
|
+
### Animation Utilities
|
|
541
945
|
|
|
542
|
-
|
|
543
|
-
|
|
946
|
+
```javascript
|
|
947
|
+
import {
|
|
948
|
+
cancelAllAnimations,
|
|
949
|
+
getActiveAnimationCount,
|
|
950
|
+
isAnimationSupported,
|
|
951
|
+
getPresetNames
|
|
952
|
+
} from 'tailwind-to-style'
|
|
953
|
+
|
|
954
|
+
cancelAllAnimations() // Stop all running animations
|
|
955
|
+
getActiveAnimationCount() // Number of active animations
|
|
956
|
+
isAnimationSupported() // Check Web Animations API support
|
|
957
|
+
getPresetNames() // List all available preset names
|
|
544
958
|
```
|
|
545
959
|
|
|
546
960
|
---
|
|
@@ -551,26 +965,81 @@ Import only what you need to reduce bundle size by **50-70%**:
|
|
|
551
965
|
|
|
552
966
|
```javascript
|
|
553
967
|
// Individual imports (recommended for production)
|
|
554
|
-
import { tws } from 'tailwind-to-style/tws'
|
|
555
|
-
import { twsx } from 'tailwind-to-style/twsx'
|
|
556
|
-
import { twsxVariants } from 'tailwind-to-style/twsx-variants'
|
|
557
|
-
import {
|
|
968
|
+
import { tws } from 'tailwind-to-style/tws' // ~3KB
|
|
969
|
+
import { twsx } from 'tailwind-to-style/twsx' // ~6KB
|
|
970
|
+
import { twsxVariants } from 'tailwind-to-style/twsx-variants' // ~6KB
|
|
971
|
+
import { twsxClassName, tw } from 'tailwind-to-style/classname' // ~8KB
|
|
972
|
+
import { cx } from 'tailwind-to-style/cx' // <1KB
|
|
558
973
|
|
|
559
974
|
// Full import (everything)
|
|
560
|
-
import { tws, twsx, twsxVariants, cx } from 'tailwind-to-style' // ~
|
|
975
|
+
import { tws, twsx, twsxVariants, twsxClassName, cx } from 'tailwind-to-style' // ~15KB
|
|
561
976
|
```
|
|
562
977
|
|
|
563
978
|
| Import Path | Includes | Size (minified) |
|
|
564
979
|
|---|---|---|
|
|
565
|
-
| `tailwind-to-style` | Everything | ~
|
|
980
|
+
| `tailwind-to-style` | Everything | ~15KB |
|
|
566
981
|
| `tailwind-to-style/tws` | `tws()` only | ~3KB |
|
|
567
982
|
| `tailwind-to-style/twsx` | `twsx()` | ~6KB |
|
|
568
983
|
| `tailwind-to-style/twsx-variants` | `twsxVariants()` | ~6KB |
|
|
984
|
+
| `tailwind-to-style/classname` | `twsxClassName()`, `tw()` | ~8KB |
|
|
569
985
|
| `tailwind-to-style/cx` | `cx()` | <1KB |
|
|
570
986
|
| `tailwind-to-style/utils` | Logger, LRUCache, error handler | ~2KB |
|
|
571
987
|
|
|
572
988
|
All sub-paths provide ESM + CJS bundles with TypeScript type definitions.
|
|
573
989
|
|
|
990
|
+
### Utility Exports
|
|
991
|
+
|
|
992
|
+
```javascript
|
|
993
|
+
import {
|
|
994
|
+
// Debounced versions (for rapid updates)
|
|
995
|
+
debouncedTws, // Debounced tws() - 50ms delay
|
|
996
|
+
debouncedTwsx, // Debounced twsx() - 100ms delay
|
|
997
|
+
|
|
998
|
+
// Performance utilities
|
|
999
|
+
performanceUtils,
|
|
1000
|
+
|
|
1001
|
+
// Error handling
|
|
1002
|
+
TwsError,
|
|
1003
|
+
onError,
|
|
1004
|
+
handleError,
|
|
1005
|
+
|
|
1006
|
+
// Cache utilities
|
|
1007
|
+
LRUCache,
|
|
1008
|
+
getTailwindCache,
|
|
1009
|
+
resetTailwindCache,
|
|
1010
|
+
|
|
1011
|
+
// Logging
|
|
1012
|
+
logger,
|
|
1013
|
+
Logger,
|
|
1014
|
+
} from 'tailwind-to-style'
|
|
1015
|
+
|
|
1016
|
+
// Debounced functions - useful for user input
|
|
1017
|
+
const handleInput = (value) => {
|
|
1018
|
+
debouncedTws(`w-[${value}px]`, true) // Won't spam during typing
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// Error handling
|
|
1022
|
+
const unsubscribe = onError((error) => {
|
|
1023
|
+
console.error('TWS Error:', error.message, error.context)
|
|
1024
|
+
sendToErrorTracking(error)
|
|
1025
|
+
})
|
|
1026
|
+
|
|
1027
|
+
// Custom error
|
|
1028
|
+
const error = handleError(new Error('Invalid class'), { className: 'invalid' })
|
|
1029
|
+
// → TwsError with context and timestamp
|
|
1030
|
+
|
|
1031
|
+
// LRU Cache (bounded Map)
|
|
1032
|
+
const cache = new LRUCache(1000) // Max 1000 items
|
|
1033
|
+
cache.set('key', 'value')
|
|
1034
|
+
cache.get('key') // → 'value'
|
|
1035
|
+
cache.has('key') // → true
|
|
1036
|
+
cache.size // → 1
|
|
1037
|
+
|
|
1038
|
+
// Logger
|
|
1039
|
+
logger.setLevel('debug') // 'debug' | 'info' | 'warn' | 'error' | 'silent'
|
|
1040
|
+
logger.debug('Processing class:', className)
|
|
1041
|
+
```
|
|
1042
|
+
|
|
574
1043
|
---
|
|
575
1044
|
|
|
576
1045
|
## Preflight CSS
|
|
@@ -621,10 +1090,41 @@ function App() {
|
|
|
621
1090
|
|
|
622
1091
|
### Vue
|
|
623
1092
|
|
|
1093
|
+
```vue
|
|
1094
|
+
<script setup>
|
|
1095
|
+
import 'tailwind-to-style/preflight.css'
|
|
1096
|
+
import { tws, twsx } from 'tailwind-to-style'
|
|
1097
|
+
|
|
1098
|
+
// twsx() at the top level of <script setup> is HMR-safe.
|
|
1099
|
+
// When you edit the classes, Vite's HMR re-runs this block and
|
|
1100
|
+
// the old CSS slot is replaced automatically — no hard refresh needed.
|
|
1101
|
+
twsx({
|
|
1102
|
+
'html': 'bg-gray-100 min-h-screen flex items-center justify-center',
|
|
1103
|
+
'.card': [
|
|
1104
|
+
'bg-white p-5 border border-gray-300 rounded-xl shadow-md',
|
|
1105
|
+
{
|
|
1106
|
+
'&:hover': 'shadow-xl',
|
|
1107
|
+
'> .title': 'text-xl font-bold text-gray-900 mb-3',
|
|
1108
|
+
'> .body': 'text-sm text-gray-700',
|
|
1109
|
+
},
|
|
1110
|
+
],
|
|
1111
|
+
})
|
|
1112
|
+
</script>
|
|
1113
|
+
|
|
1114
|
+
<template>
|
|
1115
|
+
<div class="card">
|
|
1116
|
+
<div class="title">Card Title</div>
|
|
1117
|
+
<p class="body">Styled with twsx — hot reload works out of the box.</p>
|
|
1118
|
+
</div>
|
|
1119
|
+
</template>
|
|
1120
|
+
```
|
|
1121
|
+
|
|
1122
|
+
For simple inline styles, use `tws()` with the reactive system:
|
|
1123
|
+
|
|
624
1124
|
```vue
|
|
625
1125
|
<script setup>
|
|
626
1126
|
import { tws } from 'tailwind-to-style'
|
|
627
|
-
const btnStyle = tws('bg-blue-500 text-white px-4 py-2 rounded-lg
|
|
1127
|
+
const btnStyle = tws('bg-blue-500 text-white px-4 py-2 rounded-lg', true)
|
|
628
1128
|
</script>
|
|
629
1129
|
|
|
630
1130
|
<template>
|
|
@@ -670,7 +1170,7 @@ v3.2.0 includes major performance optimizations:
|
|
|
670
1170
|
- **Pre-compiled regex** — compiled once at module load, reused for every call
|
|
671
1171
|
- **Multi-level LRU caching** — class resolution, CSS generation, config lookups
|
|
672
1172
|
- **Bounded caches** — Maps capped at 5,000 entries, Sets at 10,000 to prevent memory leaks
|
|
673
|
-
-
|
|
1173
|
+
- **Slot-based CSS injection** — each `twsx()` call owns a named slot; updates rebuild the tag instead of appending, preventing HMR accumulation
|
|
674
1174
|
- **FNV-1a hashing** — 100x faster than `JSON.stringify` for cache keys
|
|
675
1175
|
|
|
676
1176
|
```
|