eva-css-for-tailwind 1.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/README.md +226 -0
- package/dist/cli.js +400 -0
- package/dist/index.d.mts +121 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.js +369 -0
- package/dist/index.mjs +332 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# eva-css-for-tailwind
|
|
2
|
+
|
|
3
|
+
> Fluid design tokens bridge for Tailwind CSS 4 — converts static arbitrary values (`text-[32px]`, `p-[24px]`) into fluid `clamp()` values using eva-css calculations.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Zero runtime dependencies** — pure CSS string generation
|
|
8
|
+
- **Modular API** — use only what you need, compose freely
|
|
9
|
+
- **Exact match** with eva-css SCSS clamp calculations
|
|
10
|
+
- **4 fluid intensities** — extreme (`__`), strong (`_`), normal, light (`-`)
|
|
11
|
+
- **Per-property intensity** — `p-[32px]__` for extreme padding, `gap-[16px]-` for light gap
|
|
12
|
+
- **Spacing + font-size** — separate phi ratios, separate CSS vars (`--{size}` / `--fs-{size}`)
|
|
13
|
+
- **TW4 @theme support** — native utilities (`p-32`) via `generateTheme()`
|
|
14
|
+
- **CLI + programmatic API**
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install eva-css-for-tailwind
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## API
|
|
23
|
+
|
|
24
|
+
### `generateVars(config)`
|
|
25
|
+
|
|
26
|
+
Generate all CSS custom properties. Always complete — these vars are used by the nocode UI.
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { generateVars } from 'eva-css-for-tailwind';
|
|
30
|
+
|
|
31
|
+
const css = generateVars({
|
|
32
|
+
sizes: [4, 8, 16, 24, 32, 48, 64, 96],
|
|
33
|
+
fontSizes: [12, 14, 16, 18, 24, 32, 48],
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```css
|
|
38
|
+
:root {
|
|
39
|
+
--32__: clamp(0.5rem, 3.16vw - 1.11rem, 2.22rem); /* extreme */
|
|
40
|
+
--32_: clamp(0.69rem, 1.67vw + 0.5rem, 2.22rem); /* strong */
|
|
41
|
+
--32: clamp(1.11rem, 1.11vw + 1rem, 2.22rem); /* normal */
|
|
42
|
+
--32-: clamp(1.8rem, 0.56vw + 1.5rem, 2.22rem); /* light */
|
|
43
|
+
|
|
44
|
+
--fs-32__: clamp(0.85rem, 1.67vw + 0.5rem, 2.22rem);
|
|
45
|
+
--fs-32_: clamp(1.11rem, 1.11vw + 1rem, 2.22rem);
|
|
46
|
+
--fs-32: clamp(1.44rem, 0.56vw + 1.5rem, 2.22rem);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### `generateClassOverrides(classes)`
|
|
51
|
+
|
|
52
|
+
Generate CSS overrides for specific Tailwind arbitrary classes. Each class gets all its intensity variants. Only generates what you need.
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { generateClassOverrides } from 'eva-css-for-tailwind';
|
|
56
|
+
|
|
57
|
+
const css = generateClassOverrides([
|
|
58
|
+
'p-[32px]',
|
|
59
|
+
'text-[48px]',
|
|
60
|
+
'gap-[16px]',
|
|
61
|
+
]);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```css
|
|
65
|
+
/* Default + intensity variants per class */
|
|
66
|
+
.p-\[32px\] { padding: var(--32) }
|
|
67
|
+
.p-\[32px\]__ { padding: var(--32__) }
|
|
68
|
+
.p-\[32px\]_ { padding: var(--32_) }
|
|
69
|
+
.p-\[32px\]- { padding: var(--32-) }
|
|
70
|
+
|
|
71
|
+
.text-\[48px\] { font-size: var(--fs-48) }
|
|
72
|
+
.text-\[48px\]__ { font-size: var(--fs-48__) }
|
|
73
|
+
.text-\[48px\]_ { font-size: var(--fs-48_) }
|
|
74
|
+
|
|
75
|
+
.gap-\[16px\] { gap: var(--16) }
|
|
76
|
+
.gap-\[16px\]__ { gap: var(--16__) }
|
|
77
|
+
.gap-\[16px\]_ { gap: var(--16_) }
|
|
78
|
+
.gap-\[16px\]- { gap: var(--16-) }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Per-property intensity control directly in the class name:
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<section class="pt-[140px]__ pb-[140px] gap-[32px]-">
|
|
85
|
+
<!-- pt = extreme | pb = normal | gap = light -->
|
|
86
|
+
<h1 class="text-[48px]_">Strong fluid font</h1>
|
|
87
|
+
</section>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### `generateTheme(config)`
|
|
91
|
+
|
|
92
|
+
Generate a TW4 `@theme` block mapping native utilities to eva vars. Use this if your project uses named utilities (`p-32`) instead of arbitrary values (`p-[32px]`).
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { generateTheme } from 'eva-css-for-tailwind';
|
|
96
|
+
|
|
97
|
+
const css = generateTheme({
|
|
98
|
+
sizes: [16, 32, 48],
|
|
99
|
+
fontSizes: [16, 32],
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```css
|
|
104
|
+
@theme {
|
|
105
|
+
--spacing-16: var(--16);
|
|
106
|
+
--spacing-32: var(--32);
|
|
107
|
+
--spacing-48: var(--48);
|
|
108
|
+
|
|
109
|
+
--font-size-16: var(--fs-16);
|
|
110
|
+
--font-size-32: var(--fs-32);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### `generateClamp(size, type, intensity)`
|
|
115
|
+
|
|
116
|
+
Generate a single `clamp()` value. Useful for the nocode UI (preview, inspector...).
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { generateClamp } from 'eva-css-for-tailwind';
|
|
120
|
+
|
|
121
|
+
generateClamp(32, 'spacing');
|
|
122
|
+
// → "clamp(1.11rem, 1.11vw + 1rem, 2.22rem)"
|
|
123
|
+
|
|
124
|
+
generateClamp(32, 'spacing', '__');
|
|
125
|
+
// → "clamp(0.5rem, 3.16vw - 1.11rem, 2.22rem)"
|
|
126
|
+
|
|
127
|
+
generateClamp(16, 'font');
|
|
128
|
+
// → "clamp(0.73rem, 0.28vw + 0.75rem, 1.11rem)"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### `parseClass(className)`
|
|
132
|
+
|
|
133
|
+
Parse a Tailwind arbitrary class into its components.
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { parseClass } from 'eva-css-for-tailwind';
|
|
137
|
+
|
|
138
|
+
parseClass('pt-[140px]');
|
|
139
|
+
// → { prefix: 'pt', property: 'padding-top', size: 140, unit: 'px', type: 'spacing' }
|
|
140
|
+
|
|
141
|
+
parseClass('text-[48px]');
|
|
142
|
+
// → { prefix: 'text', property: 'font-size', size: 48, unit: 'px', type: 'font' }
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### `generateBridge(config)`
|
|
146
|
+
|
|
147
|
+
All-in-one shortcut — generates vars + theme + class overrides. Used by the CLI.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { generateBridge } from 'eva-css-for-tailwind';
|
|
151
|
+
|
|
152
|
+
// Full bridge (vars + @theme)
|
|
153
|
+
const css = generateBridge({
|
|
154
|
+
sizes: [4, 8, 16, 24, 32, 48],
|
|
155
|
+
fontSizes: [12, 16, 24, 32, 48],
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// With specific classes (vars + class overrides only)
|
|
159
|
+
const css = generateBridge({
|
|
160
|
+
sizes: [4, 8, 16, 24, 32, 48],
|
|
161
|
+
fontSizes: [12, 16, 24, 32, 48],
|
|
162
|
+
classes: ['p-[32px]', 'text-[48px]', 'gap-[16px]'],
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Configuration
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
// eva-tw.config.cjs
|
|
170
|
+
module.exports = {
|
|
171
|
+
sizes: [4, 8, 12, 16, 24, 32, 48, 64, 96, 128],
|
|
172
|
+
fontSizes: [12, 14, 16, 18, 20, 24, 32, 48],
|
|
173
|
+
screen: 1440,
|
|
174
|
+
defaultIntensity: '',
|
|
175
|
+
};
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
| Option | Default | Description |
|
|
179
|
+
|--------|---------|-------------|
|
|
180
|
+
| `sizes` | — | Spacing sizes in px (from Figma tokens) |
|
|
181
|
+
| `fontSizes` | — | Font sizes in px (from Figma tokens) |
|
|
182
|
+
| `screen` | `1440` | Reference screen width |
|
|
183
|
+
| `phi` | `1.618` | Golden ratio for spacing |
|
|
184
|
+
| `fontPhi` | `1.3` | Ratio for font scaling |
|
|
185
|
+
| `min` | `0.5` | Min fluid factor for spacing |
|
|
186
|
+
| `fontMin` | `0.5` | Min fluid factor for fonts |
|
|
187
|
+
| `max` | `1` | Max fluid factor |
|
|
188
|
+
| `ez` | `142.4` | Extreme intensity factor |
|
|
189
|
+
| `defaultIntensity` | `''` | Default: `__`, `_`, `''`, or `-` |
|
|
190
|
+
|
|
191
|
+
## Intensity Levels
|
|
192
|
+
|
|
193
|
+
| Suffix | Fluid behavior |
|
|
194
|
+
|--------|----------------|
|
|
195
|
+
| `__` | Extreme — most responsive, biggest min/max range |
|
|
196
|
+
| `_` | Strong |
|
|
197
|
+
| *(none)* | Normal — balanced (default) |
|
|
198
|
+
| `-` | Light — near-static, smallest range |
|
|
199
|
+
|
|
200
|
+
Spacing uses all 4 levels. Font-sizes use 3 levels (`__`, `_`, default).
|
|
201
|
+
|
|
202
|
+
## CLI
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
eva-css-for-tailwind init Create eva-tw.config.cjs
|
|
206
|
+
eva-css-for-tailwind generate Write bridge.css
|
|
207
|
+
eva-css-for-tailwind generate --stdout Output to stdout
|
|
208
|
+
eva-css-for-tailwind generate --out=custom.css Custom output path
|
|
209
|
+
eva-css-for-tailwind generate --sizes="4,8,16" Override sizes inline
|
|
210
|
+
eva-css-for-tailwind generate --font-sizes="16" Override font sizes inline
|
|
211
|
+
eva-css-for-tailwind generate --intensity=_ Set default intensity
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
MIT
|
|
217
|
+
|
|
218
|
+
## Author
|
|
219
|
+
|
|
220
|
+
EVA CSS Framework
|
|
221
|
+
|
|
222
|
+
## Links
|
|
223
|
+
|
|
224
|
+
- [eva-css](https://www.npmjs.com/package/eva-css-fluid) — Core fluid framework
|
|
225
|
+
- [eva-colors](https://www.npmjs.com/package/eva-colors) — OKLCH color utilities
|
|
226
|
+
- [eva-purge](https://www.npmjs.com/package/eva-css-purge) — CSS optimization
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// src/cli.ts
|
|
5
|
+
var import_fs = require("fs");
|
|
6
|
+
var import_path = require("path");
|
|
7
|
+
|
|
8
|
+
// src/clamp.ts
|
|
9
|
+
function round2(n) {
|
|
10
|
+
return Math.round(n * 100) / 100;
|
|
11
|
+
}
|
|
12
|
+
function getPercent(size, screen, ratio = 100) {
|
|
13
|
+
return round2(size / screen * ratio);
|
|
14
|
+
}
|
|
15
|
+
function toRem(size) {
|
|
16
|
+
return size / 16;
|
|
17
|
+
}
|
|
18
|
+
function getMinRem(percent, min) {
|
|
19
|
+
return round2(percent * min);
|
|
20
|
+
}
|
|
21
|
+
function getMaxRem(percent, max) {
|
|
22
|
+
return round2(percent * max);
|
|
23
|
+
}
|
|
24
|
+
function getVW(percent) {
|
|
25
|
+
return round2(percent);
|
|
26
|
+
}
|
|
27
|
+
function getFinalMinDiv(size, ratio) {
|
|
28
|
+
return round2(size / ratio);
|
|
29
|
+
}
|
|
30
|
+
function getFinalMinMulti(size, ratio) {
|
|
31
|
+
return round2(size * ratio);
|
|
32
|
+
}
|
|
33
|
+
function vwLight(calcPercent, sizeRem) {
|
|
34
|
+
return {
|
|
35
|
+
vw: round2(getVW(calcPercent) / 4),
|
|
36
|
+
offset: round2(sizeRem / 1.33)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function vwMedium(calcPercent, sizeRem) {
|
|
40
|
+
return {
|
|
41
|
+
vw: round2(getVW(calcPercent) / 2),
|
|
42
|
+
offset: round2(sizeRem / 2)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function vwStrong(calcPercent, sizeRem) {
|
|
46
|
+
return {
|
|
47
|
+
vw: round2(getVW(calcPercent) / 1.33),
|
|
48
|
+
offset: round2(sizeRem / 4)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function vwExtrem(calcPercentEz, remMin) {
|
|
52
|
+
return {
|
|
53
|
+
vw: getVW(calcPercentEz),
|
|
54
|
+
offset: round2(-remMin)
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function formatClamp(min, fluid, max) {
|
|
58
|
+
const minStr = `${round2(min)}rem`;
|
|
59
|
+
const maxStr = `${round2(max)}rem`;
|
|
60
|
+
const sign = fluid.offset >= 0 ? "+" : "-";
|
|
61
|
+
const absOffset = round2(Math.abs(fluid.offset));
|
|
62
|
+
const fluidStr = `${fluid.vw}vw ${sign} ${absOffset}rem`;
|
|
63
|
+
return `clamp(${minStr}, ${fluidStr}, ${maxStr})`;
|
|
64
|
+
}
|
|
65
|
+
function generateSpacingClamp(sizePx, screen, phi, min, max, ez, intensity) {
|
|
66
|
+
const calcPercent = getPercent(sizePx, screen);
|
|
67
|
+
const calcPercentEz = getPercent(sizePx, screen, ez);
|
|
68
|
+
const sizeRem = toRem(sizePx);
|
|
69
|
+
const remMin = getMinRem(calcPercent, min);
|
|
70
|
+
const remMax = getMaxRem(calcPercent, max);
|
|
71
|
+
switch (intensity) {
|
|
72
|
+
// __ = extreme (most fluid)
|
|
73
|
+
case "__": {
|
|
74
|
+
const fluid = vwExtrem(calcPercentEz, remMin);
|
|
75
|
+
return formatClamp(min, fluid, remMax);
|
|
76
|
+
}
|
|
77
|
+
// _ = strong
|
|
78
|
+
case "_": {
|
|
79
|
+
const finalMin = getFinalMinDiv(remMin, phi);
|
|
80
|
+
const fluid = vwStrong(calcPercent, sizeRem);
|
|
81
|
+
return formatClamp(finalMin, fluid, remMax);
|
|
82
|
+
}
|
|
83
|
+
// '' = normal (default)
|
|
84
|
+
case "": {
|
|
85
|
+
const fluid = vwMedium(calcPercent, sizeRem);
|
|
86
|
+
return formatClamp(remMin, fluid, remMax);
|
|
87
|
+
}
|
|
88
|
+
// - = light (least fluid)
|
|
89
|
+
case "-": {
|
|
90
|
+
const finalMin = getFinalMinMulti(remMin, phi);
|
|
91
|
+
const fluid = vwLight(calcPercent, sizeRem);
|
|
92
|
+
return formatClamp(finalMin, fluid, remMax);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function generateFontClamp(sizePx, screen, phi, min, max, intensity) {
|
|
97
|
+
const calcPercent = getPercent(sizePx, screen);
|
|
98
|
+
const sizeRem = toRem(sizePx);
|
|
99
|
+
const remMin = getMinRem(calcPercent, min);
|
|
100
|
+
const remMax = getMaxRem(calcPercent, max);
|
|
101
|
+
switch (intensity) {
|
|
102
|
+
// __ = most fluid (uses vw-strong + min/phi)
|
|
103
|
+
case "__": {
|
|
104
|
+
const finalMin = getFinalMinDiv(remMin, phi);
|
|
105
|
+
const fluid = vwStrong(calcPercent, sizeRem);
|
|
106
|
+
return formatClamp(finalMin, fluid, remMax);
|
|
107
|
+
}
|
|
108
|
+
// _ = medium (uses vw-medium + remMin)
|
|
109
|
+
case "_": {
|
|
110
|
+
const fluid = vwMedium(calcPercent, sizeRem);
|
|
111
|
+
return formatClamp(remMin, fluid, remMax);
|
|
112
|
+
}
|
|
113
|
+
// '' = least fluid (uses vw-light + min*phi)
|
|
114
|
+
case "": {
|
|
115
|
+
const finalMin = getFinalMinMulti(remMin, phi);
|
|
116
|
+
const fluid = vwLight(calcPercent, sizeRem);
|
|
117
|
+
return formatClamp(finalMin, fluid, remMax);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/types.ts
|
|
123
|
+
var PROPERTY_MAP = {
|
|
124
|
+
// Sizing
|
|
125
|
+
h: "height",
|
|
126
|
+
w: "width",
|
|
127
|
+
"min-h": "min-height",
|
|
128
|
+
"min-w": "min-width",
|
|
129
|
+
"max-w": "max-width",
|
|
130
|
+
"max-h": "max-height",
|
|
131
|
+
// Padding
|
|
132
|
+
p: "padding",
|
|
133
|
+
px: "padding-inline",
|
|
134
|
+
py: "padding-block",
|
|
135
|
+
pt: "padding-top",
|
|
136
|
+
pr: "padding-right",
|
|
137
|
+
pb: "padding-bottom",
|
|
138
|
+
pl: "padding-left",
|
|
139
|
+
// Margin
|
|
140
|
+
m: "margin",
|
|
141
|
+
mx: "margin-inline",
|
|
142
|
+
my: "margin-block",
|
|
143
|
+
mt: "margin-top",
|
|
144
|
+
mr: "margin-right",
|
|
145
|
+
mb: "margin-bottom",
|
|
146
|
+
ml: "margin-left",
|
|
147
|
+
// Layout
|
|
148
|
+
gap: "gap",
|
|
149
|
+
// Borders
|
|
150
|
+
rounded: "border-radius"
|
|
151
|
+
};
|
|
152
|
+
var FONT_PROPERTY_MAP = {
|
|
153
|
+
text: "font-size"
|
|
154
|
+
};
|
|
155
|
+
var SPACING_INTENSITIES = ["__", "_", "", "-"];
|
|
156
|
+
var FONT_INTENSITIES = ["__", "_", ""];
|
|
157
|
+
function resolveConfig(config) {
|
|
158
|
+
return {
|
|
159
|
+
sizes: config.sizes,
|
|
160
|
+
fontSizes: config.fontSizes,
|
|
161
|
+
screen: config.screen ?? 1440,
|
|
162
|
+
phi: config.phi ?? 1.61803398875,
|
|
163
|
+
fontPhi: config.fontPhi ?? 1.3,
|
|
164
|
+
min: config.min ?? 0.5,
|
|
165
|
+
fontMin: config.fontMin ?? 0.5,
|
|
166
|
+
max: config.max ?? 1,
|
|
167
|
+
ez: config.ez ?? 142.4,
|
|
168
|
+
defaultIntensity: config.defaultIntensity ?? ""
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/generator.ts
|
|
173
|
+
function parseClass(className) {
|
|
174
|
+
const match = className.match(/^(.+)-\[(\d+(?:\.\d+)?)(px|rem)\]$/);
|
|
175
|
+
if (!match) return null;
|
|
176
|
+
const [, prefix, valueStr, unit] = match;
|
|
177
|
+
const value = parseFloat(valueStr);
|
|
178
|
+
if (prefix in FONT_PROPERTY_MAP) {
|
|
179
|
+
const sizePx = unit === "rem" ? value * 16 : value;
|
|
180
|
+
return {
|
|
181
|
+
raw: className,
|
|
182
|
+
prefix,
|
|
183
|
+
property: FONT_PROPERTY_MAP[prefix],
|
|
184
|
+
size: sizePx,
|
|
185
|
+
unit,
|
|
186
|
+
type: "font"
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (prefix in PROPERTY_MAP) {
|
|
190
|
+
const sizePx = unit === "rem" ? value * 16 : value;
|
|
191
|
+
return {
|
|
192
|
+
raw: className,
|
|
193
|
+
prefix,
|
|
194
|
+
property: PROPERTY_MAP[prefix],
|
|
195
|
+
size: sizePx,
|
|
196
|
+
unit,
|
|
197
|
+
type: "spacing"
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
function generateVars(config) {
|
|
203
|
+
const cfg = resolveConfig(config);
|
|
204
|
+
const lines = [":root {"];
|
|
205
|
+
for (const size of cfg.sizes) {
|
|
206
|
+
lines.push(` /* ---- ${size}px ---- */`);
|
|
207
|
+
for (const intensity of SPACING_INTENSITIES) {
|
|
208
|
+
const clamp = generateSpacingClamp(
|
|
209
|
+
size,
|
|
210
|
+
cfg.screen,
|
|
211
|
+
cfg.phi,
|
|
212
|
+
cfg.min,
|
|
213
|
+
cfg.max,
|
|
214
|
+
cfg.ez,
|
|
215
|
+
intensity
|
|
216
|
+
);
|
|
217
|
+
lines.push(` --${size}${intensity}: ${clamp};`);
|
|
218
|
+
}
|
|
219
|
+
lines.push("");
|
|
220
|
+
}
|
|
221
|
+
for (const size of cfg.fontSizes) {
|
|
222
|
+
lines.push(` /* ---- fs-${size}px ---- */`);
|
|
223
|
+
for (const intensity of FONT_INTENSITIES) {
|
|
224
|
+
const clamp = generateFontClamp(
|
|
225
|
+
size,
|
|
226
|
+
cfg.screen,
|
|
227
|
+
cfg.fontPhi,
|
|
228
|
+
cfg.fontMin,
|
|
229
|
+
cfg.max,
|
|
230
|
+
intensity
|
|
231
|
+
);
|
|
232
|
+
lines.push(` --fs-${size}${intensity}: ${clamp};`);
|
|
233
|
+
}
|
|
234
|
+
lines.push("");
|
|
235
|
+
}
|
|
236
|
+
lines.push("}");
|
|
237
|
+
return lines.join("\n");
|
|
238
|
+
}
|
|
239
|
+
function escapeTwClass(prefix, value, suffix) {
|
|
240
|
+
return `.${prefix}-\\[${value}\\]${suffix}`;
|
|
241
|
+
}
|
|
242
|
+
function generateClassOverrides(classes, config) {
|
|
243
|
+
const cfg = resolveConfig({ sizes: [], fontSizes: [], ...config });
|
|
244
|
+
const lines = [];
|
|
245
|
+
for (const cls of classes) {
|
|
246
|
+
const parsed = parseClass(cls);
|
|
247
|
+
if (!parsed) continue;
|
|
248
|
+
const valueStr = parsed.unit === "rem" ? `${parsed.size / 16}rem` : `${parsed.size}px`;
|
|
249
|
+
if (parsed.type === "font") {
|
|
250
|
+
for (const intensity of FONT_INTENSITIES) {
|
|
251
|
+
const varRef = `var(--fs-${parsed.size}${intensity})`;
|
|
252
|
+
const selector = escapeTwClass(parsed.prefix, valueStr, intensity);
|
|
253
|
+
lines.push(`${selector} { ${parsed.property}: ${varRef} }`);
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
for (const intensity of SPACING_INTENSITIES) {
|
|
257
|
+
const varRef = `var(--${parsed.size}${intensity})`;
|
|
258
|
+
const selector = escapeTwClass(parsed.prefix, valueStr, intensity);
|
|
259
|
+
lines.push(`${selector} { ${parsed.property}: ${varRef} }`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
lines.push("");
|
|
263
|
+
}
|
|
264
|
+
return lines.join("\n");
|
|
265
|
+
}
|
|
266
|
+
function generateTheme(config) {
|
|
267
|
+
const cfg = resolveConfig(config);
|
|
268
|
+
const lines = ["@theme {"];
|
|
269
|
+
for (const size of cfg.sizes) {
|
|
270
|
+
lines.push(` --spacing-${size}: var(--${size}${cfg.defaultIntensity});`);
|
|
271
|
+
}
|
|
272
|
+
if (cfg.sizes.length > 0 && cfg.fontSizes.length > 0) {
|
|
273
|
+
lines.push("");
|
|
274
|
+
}
|
|
275
|
+
const fontSuffix = cfg.defaultIntensity === "-" ? "" : cfg.defaultIntensity;
|
|
276
|
+
for (const size of cfg.fontSizes) {
|
|
277
|
+
lines.push(` --font-size-${size}: var(--fs-${size}${fontSuffix});`);
|
|
278
|
+
}
|
|
279
|
+
lines.push("}");
|
|
280
|
+
return lines.join("\n");
|
|
281
|
+
}
|
|
282
|
+
function generateBridge(config) {
|
|
283
|
+
const sections = [
|
|
284
|
+
"/* =============================================================",
|
|
285
|
+
" EVA CSS \u2014 Tailwind Fluid Bridge",
|
|
286
|
+
" Generated by eva-css-for-tailwind",
|
|
287
|
+
" ============================================================= */",
|
|
288
|
+
"",
|
|
289
|
+
generateVars(config)
|
|
290
|
+
];
|
|
291
|
+
if (!config.classes) {
|
|
292
|
+
sections.push("");
|
|
293
|
+
sections.push(generateTheme(config));
|
|
294
|
+
}
|
|
295
|
+
if (config.classes && config.classes.length > 0) {
|
|
296
|
+
sections.push("");
|
|
297
|
+
sections.push(generateClassOverrides(config.classes, config));
|
|
298
|
+
}
|
|
299
|
+
return sections.join("\n");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/cli.ts
|
|
303
|
+
var DEFAULT_CONFIG = `module.exports = {
|
|
304
|
+
sizes: [4, 8, 12, 16, 24, 32, 48, 64, 96, 128],
|
|
305
|
+
fontSizes: [12, 14, 16, 18, 20, 24, 32, 48],
|
|
306
|
+
screen: 1440,
|
|
307
|
+
defaultIntensity: '',
|
|
308
|
+
};
|
|
309
|
+
`;
|
|
310
|
+
function parseArgs(args) {
|
|
311
|
+
const parsed = {};
|
|
312
|
+
for (const arg of args) {
|
|
313
|
+
if (arg.startsWith("--")) {
|
|
314
|
+
const [key, ...rest] = arg.slice(2).split("=");
|
|
315
|
+
parsed[key] = rest.length > 0 ? rest.join("=") : true;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return parsed;
|
|
319
|
+
}
|
|
320
|
+
function loadConfig(cwd) {
|
|
321
|
+
const candidates = [
|
|
322
|
+
"eva-tw.config.cjs",
|
|
323
|
+
"eva-tw.config.js"
|
|
324
|
+
];
|
|
325
|
+
for (const name of candidates) {
|
|
326
|
+
const configPath = (0, import_path.resolve)(cwd, name);
|
|
327
|
+
if ((0, import_fs.existsSync)(configPath)) {
|
|
328
|
+
return require(configPath);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
function main() {
|
|
334
|
+
const args = process.argv.slice(2);
|
|
335
|
+
const command = args[0];
|
|
336
|
+
const flags = parseArgs(args.slice(1));
|
|
337
|
+
const cwd = process.cwd();
|
|
338
|
+
if (!command || command === "--help" || command === "-h") {
|
|
339
|
+
console.log(`
|
|
340
|
+
eva-css-for-tailwind \u2014 Fluid design tokens bridge for Tailwind CSS 4
|
|
341
|
+
|
|
342
|
+
Commands:
|
|
343
|
+
init Create eva-tw.config.cjs with defaults
|
|
344
|
+
generate Generate bridge.css from config
|
|
345
|
+
|
|
346
|
+
Options (generate):
|
|
347
|
+
--sizes="4,8,16,24,32,48" Override sizes
|
|
348
|
+
--font-sizes="12,14,16,18,24" Override font sizes
|
|
349
|
+
--intensity=<suffix> Default intensity (__,_,,-)
|
|
350
|
+
--stdout Output to stdout instead of file
|
|
351
|
+
--out=<path> Output file path (default: bridge.css)
|
|
352
|
+
`);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (command === "init") {
|
|
356
|
+
const configPath = (0, import_path.join)(cwd, "eva-tw.config.cjs");
|
|
357
|
+
if ((0, import_fs.existsSync)(configPath)) {
|
|
358
|
+
console.log("eva-tw.config.cjs already exists.");
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
(0, import_fs.writeFileSync)(configPath, DEFAULT_CONFIG, "utf-8");
|
|
362
|
+
console.log("Created eva-tw.config.cjs");
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (command === "generate") {
|
|
366
|
+
let config = loadConfig(cwd);
|
|
367
|
+
if (flags["sizes"] && typeof flags["sizes"] === "string") {
|
|
368
|
+
const sizes = flags["sizes"].split(",").map(Number);
|
|
369
|
+
config = { ...config, sizes, fontSizes: config?.fontSizes ?? [] };
|
|
370
|
+
}
|
|
371
|
+
if (flags["font-sizes"] && typeof flags["font-sizes"] === "string") {
|
|
372
|
+
const fontSizes = flags["font-sizes"].split(",").map(Number);
|
|
373
|
+
config = { ...config, sizes: config?.sizes ?? [], fontSizes };
|
|
374
|
+
}
|
|
375
|
+
if (flags["intensity"] !== void 0 && typeof flags["intensity"] === "string") {
|
|
376
|
+
config = {
|
|
377
|
+
...config,
|
|
378
|
+
sizes: config?.sizes ?? [],
|
|
379
|
+
fontSizes: config?.fontSizes ?? [],
|
|
380
|
+
defaultIntensity: flags["intensity"]
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
if (!config) {
|
|
384
|
+
console.error('No config found. Run "eva-css-for-tailwind init" first, or pass --sizes and --font-sizes.');
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
const css = generateBridge(config);
|
|
388
|
+
if (flags["stdout"]) {
|
|
389
|
+
process.stdout.write(css);
|
|
390
|
+
} else {
|
|
391
|
+
const outPath = typeof flags["out"] === "string" ? (0, import_path.resolve)(cwd, flags["out"]) : (0, import_path.join)(cwd, "bridge.css");
|
|
392
|
+
(0, import_fs.writeFileSync)(outPath, css, "utf-8");
|
|
393
|
+
console.log(`Generated ${outPath}`);
|
|
394
|
+
}
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
console.error(`Unknown command: ${command}. Use --help for usage.`);
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
main();
|