tailwind-to-style 2.10.1 → 2.10.3

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 CHANGED
@@ -15,15 +15,14 @@ The library exposes two main functions and a CLI tool:
15
15
  2. **`twsx`**: A more advanced function that allows you to define nested and complex styles similar to SCSS, including support for responsive design, state variants, grouping, and enhanced CSS capabilities.
16
16
  3. **`twsx-cli`**: A command-line tool for generating CSS files from `twsx.*.js` files with watch mode support.
17
17
 
18
- ## ✨ What's New in v2.12.0
18
+ ## ✨ What's New in v2.10.2
19
19
 
20
- - 🎬 **Animation Support**: Full support for `animate-spin`, `animate-pulse`, `animate-bounce`, `animate-ping`!
21
- - 🔄 **Transition Utilities**: Complete transition system with `duration`, `delay`, `ease`, and property control
22
- - ⏱️ **Keyframes**: Built-in keyframes with support for custom animations via `configure()`
23
- - 🎨 **Custom Animations**: Create your own animations through theme extension or plugin API
24
- - 📝 **Examples**: New comprehensive animation examples
25
-
26
- Now you can create smooth transitions and eye-catching animations like Tailwind CSS!
20
+ - **⚛️ React Integration**: Built-in React hooks and provider for seamless integration
21
+ - **🎬 Enhanced Animations**: Complete animation system with custom keyframes support
22
+ - **🔄 Improved Transitions**: Full transition utilities with duration, delay, and easing
23
+ - **🎨 Advanced Theming**: More flexible theme customization and plugin system
24
+ - **⚡ Performance Boost**: Better caching and optimized CSS generation
25
+ - **📱 Responsive Selector Syntax**: New intuitive `'md:.title': 'text-lg'` format
27
26
 
28
27
  ## ✨ What's New in v2.11.0
29
28
 
@@ -58,18 +57,233 @@ All changes are **backward compatible** - your existing code continues to work!
58
57
 
59
58
  ## Installation
60
59
 
61
- To use `tailwind-to-style`, install the library using either npm or yarn:
62
-
63
- ### Using npm
64
-
65
60
  ```bash
66
61
  npm install tailwind-to-style
67
62
  ```
68
63
 
69
- ### Using yarn
64
+ ## React Integration
70
65
 
71
- ```bash
72
- yarn add tailwind-to-style
66
+ ### Quick Start with React
67
+
68
+ ```javascript
69
+ import { useTwsx, TwsxProvider } from 'tailwind-to-style'
70
+
71
+ // Theme configuration
72
+ const config = {
73
+ theme: {
74
+ extend: {
75
+ colors: {
76
+ brand: { 500: '#3b82f6', 600: '#2563eb' }
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ function App() {
83
+ return (
84
+ <TwsxProvider config={config}>
85
+ <MyComponent />
86
+ </TwsxProvider>
87
+ )
88
+ }
89
+
90
+ function MyComponent() {
91
+ // Auto-inject CSS into document head
92
+ useTwsx({
93
+ '.card': [
94
+ 'bg-brand-500 text-white p-6 rounded-lg',
95
+ {
96
+ '&:hover': 'bg-brand-600 transform scale-105',
97
+ '.title': 'text-xl font-bold mb-2'
98
+ }
99
+ ]
100
+ })
101
+
102
+ return (
103
+ <div className="card">
104
+ <h2 className="title">Interactive Card</h2>
105
+ <p>Hover me to see the effect!</p>
106
+ </div>
107
+ )
108
+ }
109
+ ```
110
+
111
+ ### Import Options
112
+
113
+ ```javascript
114
+ // Import from main package (recommended)
115
+ import { useTwsx, TwsxProvider } from 'tailwind-to-style'
116
+
117
+ // Or from React subpath
118
+ import { useTwsx, TwsxProvider } from 'tailwind-to-style/react'
119
+ ```
120
+
121
+ ### `useTwsx()` Hook
122
+
123
+ The main React hook for component styling:
124
+
125
+ ```javascript
126
+ import { useTwsx } from 'tailwind-to-style'
127
+
128
+ function MyComponent() {
129
+ // Auto-inject CSS (default behavior)
130
+ useTwsx({
131
+ '.button': [
132
+ 'bg-blue-500 text-white px-6 py-3 rounded-lg font-medium',
133
+ {
134
+ '&:hover': 'bg-blue-600 transform scale-105',
135
+ '&:active': 'bg-blue-700 scale-95',
136
+ '&:disabled': 'bg-gray-400 cursor-not-allowed'
137
+ }
138
+ ]
139
+ })
140
+
141
+ // Get CSS without injection
142
+ const css = useTwsx({
143
+ '.card': 'bg-white p-6 rounded-lg shadow-md'
144
+ }, { inject: false })
145
+
146
+ return (
147
+ <>
148
+ <style>{css}</style>
149
+ <div className="card">
150
+ <button className="button">Click me</button>
151
+ </div>
152
+ </>
153
+ )
154
+ }
155
+ ```
156
+
157
+ ### `TwsxProvider` - Theme Configuration
158
+
159
+ Provide global theme configuration and custom colors:
160
+
161
+ ```javascript
162
+ import { TwsxProvider, useTwsx } from 'tailwind-to-style'
163
+
164
+ const themeConfig = {
165
+ theme: {
166
+ extend: {
167
+ colors: {
168
+ brand: {
169
+ 50: '#eff6ff',
170
+ 500: '#3b82f6',
171
+ 600: '#2563eb',
172
+ 900: '#1e3a8a'
173
+ },
174
+ accent: '#f59e0b'
175
+ },
176
+ spacing: {
177
+ '128': '32rem',
178
+ '144': '36rem'
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ function App() {
185
+ return (
186
+ <TwsxProvider config={themeConfig}>
187
+ <Header />
188
+ <Main />
189
+ <Footer />
190
+ </TwsxProvider>
191
+ )
192
+ }
193
+
194
+ function Header() {
195
+ useTwsx({
196
+ '.header': [
197
+ 'bg-brand-500 text-white p-128', // Uses custom spacing
198
+ {
199
+ '.logo': 'text-accent font-bold text-2xl', // Uses custom color
200
+ '&:hover': 'bg-brand-600'
201
+ }
202
+ ]
203
+ })
204
+
205
+ return (
206
+ <header className="header">
207
+ <div className="logo">My Brand</div>
208
+ </header>
209
+ )
210
+ }
211
+ ```
212
+
213
+ ### Dynamic Styling with State
214
+
215
+ Create dynamic styles that respond to component state:
216
+
217
+ ```javascript
218
+ import { useTwsx } from 'tailwind-to-style'
219
+ import { useState } from 'react'
220
+
221
+ function ThemeToggle() {
222
+ const [theme, setTheme] = useState('light')
223
+
224
+ useTwsx({
225
+ '.theme-container': [
226
+ `bg-${theme === 'dark' ? 'gray-900' : 'white'} p-6 rounded-lg transition-all duration-300`,
227
+ {
228
+ [`&.${theme}`]: theme === 'dark'
229
+ ? 'text-white border-gray-700'
230
+ : 'text-gray-900 border-gray-200',
231
+ '.theme-title': 'text-2xl font-bold mb-4',
232
+ '.theme-button': [
233
+ 'px-4 py-2 rounded-lg font-medium transition-colors',
234
+ theme === 'dark'
235
+ ? 'bg-yellow-500 text-gray-900 hover:bg-yellow-400'
236
+ : 'bg-gray-800 text-white hover:bg-gray-700'
237
+ ]
238
+ }
239
+ ]
240
+ })
241
+
242
+ return (
243
+ <div className={`theme-container ${theme}`}>
244
+ <h2 className="theme-title">🌓 Dynamic Theme</h2>
245
+ <button
246
+ className="theme-button"
247
+ onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
248
+ >
249
+ Switch to {theme === 'light' ? 'Dark' : 'Light'} Mode
250
+ </button>
251
+ </div>
252
+ )
253
+ }
254
+ ```
255
+
256
+ ### Available React Hooks
257
+
258
+ ```javascript
259
+ import {
260
+ useTwsx, // Main hook for styling
261
+ TwsxProvider, // Context provider
262
+ useTwsxContext, // Access provider context
263
+ useTwsxConfig, // Get current config
264
+ useUpdateTwsxConfig // Update config
265
+ } from 'tailwind-to-style'
266
+
267
+ // Example usage
268
+ function ConfigAwareComponent() {
269
+ const { config, isConfigured } = useTwsxConfig()
270
+ const updateConfig = useUpdateTwsxConfig()
271
+
272
+ if (!isConfigured) {
273
+ return <div>Loading theme...</div>
274
+ }
275
+
276
+ return (
277
+ <div>
278
+ <p>Current theme: {config.theme?.extend?.colors?.brand ? 'Custom' : 'Default'}</p>
279
+ <button onClick={() => updateConfig({
280
+ theme: { extend: { colors: { brand: { 500: '#ef4444' } } } }
281
+ })}>
282
+ Change Brand Color
283
+ </button>
284
+ </div>
285
+ )
286
+ }
73
287
  ```
74
288
 
75
289
  ## Core Functions
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tailwind-to-style v2.10.1
2
+ * tailwind-to-style v2.10.3
3
3
  * Convert tailwind classes to inline style
4
4
  *
5
5
  * @author Bigetion
@@ -25,6 +25,8 @@ var tailwindToStyle = (function (exports) {
25
25
  ping: "ping 1s cubic-bezier(0, 0, 0.2, 1) infinite",
26
26
  pulse: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite",
27
27
  bounce: "bounce 1s infinite",
28
+ "fade-in": "fadeIn 2s ease-in-out infinite",
29
+ "slide-up": "slideUp 2s ease-in-out infinite",
28
30
  custom: "custom_value"
29
31
  },
30
32
  aspectRatio: {
@@ -1541,6 +1543,31 @@ var tailwindToStyle = (function (exports) {
1541
1543
  transform: "none",
1542
1544
  animationTimingFunction: "cubic-bezier(0,0,0.2,1)"
1543
1545
  }
1546
+ },
1547
+ fadeIn: {
1548
+ "0%": {
1549
+ opacity: "0"
1550
+ },
1551
+ "50%": {
1552
+ opacity: "1"
1553
+ },
1554
+ "100%": {
1555
+ opacity: "0"
1556
+ }
1557
+ },
1558
+ slideUp: {
1559
+ "0%": {
1560
+ transform: "translateY(20px)",
1561
+ opacity: "0"
1562
+ },
1563
+ "50%": {
1564
+ transform: "translateY(0)",
1565
+ opacity: "1"
1566
+ },
1567
+ "100%": {
1568
+ transform: "translateY(-20px)",
1569
+ opacity: "0"
1570
+ }
1544
1571
  }
1545
1572
  },
1546
1573
  transitionProperty: {
@@ -2446,18 +2473,39 @@ var tailwindToStyle = (function (exports) {
2446
2473
  } = configOptions;
2447
2474
  const prefix = `${globalPrefix}animate`;
2448
2475
  const {
2449
- animation = {}
2476
+ animation = {},
2477
+ keyframes = {}
2450
2478
  } = theme;
2451
2479
  const responsiveCssString = generateCssString$1(_ref => {
2452
2480
  let {
2453
2481
  getCssByOptions
2454
2482
  } = _ref;
2455
- // Merge theme animations with inline animations
2483
+ // Generate keyframes first
2484
+ let keyframesCSS = "";
2485
+ for (const [name, keyframe] of Object.entries(keyframes)) {
2486
+ keyframesCSS += `@keyframes ${name} {\n`;
2487
+ for (const [percentage, styles] of Object.entries(keyframe)) {
2488
+ keyframesCSS += ` ${percentage} {\n`;
2489
+ for (const [prop, value] of Object.entries(styles)) {
2490
+ const cssProp = prop.replace(/([A-Z])/g, "-$1").toLowerCase();
2491
+ keyframesCSS += ` ${cssProp}: ${value};\n`;
2492
+ }
2493
+ keyframesCSS += " }\n";
2494
+ }
2495
+ keyframesCSS += "}\n";
2496
+ }
2497
+
2498
+ // Merge theme animations with inline animations (but skip inline if keyframes exist)
2456
2499
  const allAnimations = {
2457
2500
  ...animation,
2458
- // Add inline animations to the mix
2501
+ // Add inline animations to the mix, but skip if keyframes version exists
2459
2502
  ...Object.keys(INLINE_ANIMATIONS).reduce((acc, key) => {
2460
- acc[key] = `inline-${key}`; // Special marker for inline animations
2503
+ // Check if keyframes version exists (both camelCase and kebab-case)
2504
+ const camelCaseKey = key.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
2505
+ const hasKeyframes = keyframes[key] || keyframes[camelCaseKey];
2506
+ if (!hasKeyframes) {
2507
+ acc[key] = `inline-${key}`; // Special marker for inline animations
2508
+ }
2461
2509
  return acc;
2462
2510
  }, {})
2463
2511
  };
@@ -2503,7 +2551,9 @@ var tailwindToStyle = (function (exports) {
2503
2551
  }
2504
2552
  `;
2505
2553
  });
2506
- return cssString;
2554
+
2555
+ // Combine keyframes and animation classes
2556
+ return keyframesCSS + cssString;
2507
2557
  }, configOptions);
2508
2558
  return responsiveCssString;
2509
2559
  }
@@ -9145,6 +9195,7 @@ var tailwindToStyle = (function (exports) {
9145
9195
  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
9146
9196
  const totalMarker = performanceMonitor.start("twsx:total");
9147
9197
  try {
9198
+ var _options$theme2, _userConfigData$theme2;
9148
9199
  if (!obj || typeof obj !== "object") {
9149
9200
  logger.warn("twsx: Expected an object but received:", obj);
9150
9201
  return "";
@@ -9221,12 +9272,48 @@ var tailwindToStyle = (function (exports) {
9221
9272
  // Generate CSS string
9222
9273
  const cssString = performanceMonitor.measure(() => generateCssString(styles), "twsx:generate");
9223
9274
 
9275
+ // Generate keyframes CSS separately
9276
+ const keyframesMarker = performanceMonitor.start("twsx:keyframes");
9277
+ const userConfigData = getConfig();
9278
+ const mergedOptions = {
9279
+ ...options,
9280
+ theme: {
9281
+ ...options.theme,
9282
+ ...userConfigData.theme,
9283
+ extend: {
9284
+ ...((_options$theme2 = options.theme) === null || _options$theme2 === void 0 ? void 0 : _options$theme2.extend),
9285
+ ...((_userConfigData$theme2 = userConfigData.theme) === null || _userConfigData$theme2 === void 0 ? void 0 : _userConfigData$theme2.extend)
9286
+ }
9287
+ }
9288
+ };
9289
+ const configOptions = getConfigOptions(mergedOptions, Object.keys(plugins));
9290
+ const {
9291
+ keyframes = {}
9292
+ } = configOptions.theme || {};
9293
+ let keyframesCSS = "";
9294
+ for (const [name, keyframe] of Object.entries(keyframes)) {
9295
+ keyframesCSS += `@keyframes ${name} {\n`;
9296
+ for (const [percentage, styles] of Object.entries(keyframe)) {
9297
+ keyframesCSS += ` ${percentage} {\n`;
9298
+ for (const [prop, value] of Object.entries(styles)) {
9299
+ const cssProp = prop.replace(/([A-Z])/g, "-$1").toLowerCase();
9300
+ keyframesCSS += ` ${cssProp}: ${value};\n`;
9301
+ }
9302
+ keyframesCSS += ` }\n`;
9303
+ }
9304
+ keyframesCSS += `}\n`;
9305
+ }
9306
+ performanceMonitor.end(keyframesMarker);
9307
+
9308
+ // Combine keyframes and regular CSS
9309
+ const finalCSS = keyframesCSS + cssString;
9310
+
9224
9311
  // Auto-inject if needed
9225
9312
  if (inject && typeof window !== "undefined" && typeof document !== "undefined") {
9226
- performanceMonitor.measure(() => autoInjectCss(cssString), "twsx:inject");
9313
+ performanceMonitor.measure(() => autoInjectCss(finalCSS), "twsx:inject");
9227
9314
  }
9228
9315
  performanceMonitor.end(totalMarker);
9229
- return cssString;
9316
+ return finalCSS;
9230
9317
  } catch (error) {
9231
9318
  performanceMonitor.end(totalMarker);
9232
9319
  handleError(error, {
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tailwind-to-style v2.10.1
2
+ * tailwind-to-style v2.10.3
3
3
  * Convert tailwind classes to inline style
4
4
  *
5
5
  * @author Bigetion
@@ -26,6 +26,8 @@ const theme = {
26
26
  ping: "ping 1s cubic-bezier(0, 0, 0.2, 1) infinite",
27
27
  pulse: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite",
28
28
  bounce: "bounce 1s infinite",
29
+ "fade-in": "fadeIn 2s ease-in-out infinite",
30
+ "slide-up": "slideUp 2s ease-in-out infinite",
29
31
  custom: "custom_value"
30
32
  },
31
33
  aspectRatio: {
@@ -1542,6 +1544,31 @@ const theme = {
1542
1544
  transform: "none",
1543
1545
  animationTimingFunction: "cubic-bezier(0,0,0.2,1)"
1544
1546
  }
1547
+ },
1548
+ fadeIn: {
1549
+ "0%": {
1550
+ opacity: "0"
1551
+ },
1552
+ "50%": {
1553
+ opacity: "1"
1554
+ },
1555
+ "100%": {
1556
+ opacity: "0"
1557
+ }
1558
+ },
1559
+ slideUp: {
1560
+ "0%": {
1561
+ transform: "translateY(20px)",
1562
+ opacity: "0"
1563
+ },
1564
+ "50%": {
1565
+ transform: "translateY(0)",
1566
+ opacity: "1"
1567
+ },
1568
+ "100%": {
1569
+ transform: "translateY(-20px)",
1570
+ opacity: "0"
1571
+ }
1545
1572
  }
1546
1573
  },
1547
1574
  transitionProperty: {
@@ -2447,18 +2474,39 @@ function generator$2H() {
2447
2474
  } = configOptions;
2448
2475
  const prefix = `${globalPrefix}animate`;
2449
2476
  const {
2450
- animation = {}
2477
+ animation = {},
2478
+ keyframes = {}
2451
2479
  } = theme;
2452
2480
  const responsiveCssString = generateCssString$1(_ref => {
2453
2481
  let {
2454
2482
  getCssByOptions
2455
2483
  } = _ref;
2456
- // Merge theme animations with inline animations
2484
+ // Generate keyframes first
2485
+ let keyframesCSS = "";
2486
+ for (const [name, keyframe] of Object.entries(keyframes)) {
2487
+ keyframesCSS += `@keyframes ${name} {\n`;
2488
+ for (const [percentage, styles] of Object.entries(keyframe)) {
2489
+ keyframesCSS += ` ${percentage} {\n`;
2490
+ for (const [prop, value] of Object.entries(styles)) {
2491
+ const cssProp = prop.replace(/([A-Z])/g, "-$1").toLowerCase();
2492
+ keyframesCSS += ` ${cssProp}: ${value};\n`;
2493
+ }
2494
+ keyframesCSS += " }\n";
2495
+ }
2496
+ keyframesCSS += "}\n";
2497
+ }
2498
+
2499
+ // Merge theme animations with inline animations (but skip inline if keyframes exist)
2457
2500
  const allAnimations = {
2458
2501
  ...animation,
2459
- // Add inline animations to the mix
2502
+ // Add inline animations to the mix, but skip if keyframes version exists
2460
2503
  ...Object.keys(INLINE_ANIMATIONS).reduce((acc, key) => {
2461
- acc[key] = `inline-${key}`; // Special marker for inline animations
2504
+ // Check if keyframes version exists (both camelCase and kebab-case)
2505
+ const camelCaseKey = key.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
2506
+ const hasKeyframes = keyframes[key] || keyframes[camelCaseKey];
2507
+ if (!hasKeyframes) {
2508
+ acc[key] = `inline-${key}`; // Special marker for inline animations
2509
+ }
2462
2510
  return acc;
2463
2511
  }, {})
2464
2512
  };
@@ -2504,7 +2552,9 @@ function generator$2H() {
2504
2552
  }
2505
2553
  `;
2506
2554
  });
2507
- return cssString;
2555
+
2556
+ // Combine keyframes and animation classes
2557
+ return keyframesCSS + cssString;
2508
2558
  }, configOptions);
2509
2559
  return responsiveCssString;
2510
2560
  }
@@ -9146,6 +9196,7 @@ function twsx(obj) {
9146
9196
  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
9147
9197
  const totalMarker = performanceMonitor.start("twsx:total");
9148
9198
  try {
9199
+ var _options$theme2, _userConfigData$theme2;
9149
9200
  if (!obj || typeof obj !== "object") {
9150
9201
  logger.warn("twsx: Expected an object but received:", obj);
9151
9202
  return "";
@@ -9222,12 +9273,48 @@ function twsx(obj) {
9222
9273
  // Generate CSS string
9223
9274
  const cssString = performanceMonitor.measure(() => generateCssString(styles), "twsx:generate");
9224
9275
 
9276
+ // Generate keyframes CSS separately
9277
+ const keyframesMarker = performanceMonitor.start("twsx:keyframes");
9278
+ const userConfigData = getConfig();
9279
+ const mergedOptions = {
9280
+ ...options,
9281
+ theme: {
9282
+ ...options.theme,
9283
+ ...userConfigData.theme,
9284
+ extend: {
9285
+ ...((_options$theme2 = options.theme) === null || _options$theme2 === void 0 ? void 0 : _options$theme2.extend),
9286
+ ...((_userConfigData$theme2 = userConfigData.theme) === null || _userConfigData$theme2 === void 0 ? void 0 : _userConfigData$theme2.extend)
9287
+ }
9288
+ }
9289
+ };
9290
+ const configOptions = getConfigOptions(mergedOptions, Object.keys(plugins));
9291
+ const {
9292
+ keyframes = {}
9293
+ } = configOptions.theme || {};
9294
+ let keyframesCSS = "";
9295
+ for (const [name, keyframe] of Object.entries(keyframes)) {
9296
+ keyframesCSS += `@keyframes ${name} {\n`;
9297
+ for (const [percentage, styles] of Object.entries(keyframe)) {
9298
+ keyframesCSS += ` ${percentage} {\n`;
9299
+ for (const [prop, value] of Object.entries(styles)) {
9300
+ const cssProp = prop.replace(/([A-Z])/g, "-$1").toLowerCase();
9301
+ keyframesCSS += ` ${cssProp}: ${value};\n`;
9302
+ }
9303
+ keyframesCSS += ` }\n`;
9304
+ }
9305
+ keyframesCSS += `}\n`;
9306
+ }
9307
+ performanceMonitor.end(keyframesMarker);
9308
+
9309
+ // Combine keyframes and regular CSS
9310
+ const finalCSS = keyframesCSS + cssString;
9311
+
9225
9312
  // Auto-inject if needed
9226
9313
  if (inject && typeof window !== "undefined" && typeof document !== "undefined") {
9227
- performanceMonitor.measure(() => autoInjectCss(cssString), "twsx:inject");
9314
+ performanceMonitor.measure(() => autoInjectCss(finalCSS), "twsx:inject");
9228
9315
  }
9229
9316
  performanceMonitor.end(totalMarker);
9230
- return cssString;
9317
+ return finalCSS;
9231
9318
  } catch (error) {
9232
9319
  performanceMonitor.end(totalMarker);
9233
9320
  handleError(error, {