eva-css-ycode 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 +170 -0
- package/dist/cli.js +443 -0
- package/dist/index.d.mts +56 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +372 -0
- package/dist/index.mjs +342 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# eva-css-ycode
|
|
2
|
+
|
|
3
|
+
> Fluid design tokens bridge for yCode — converts static Tailwind arbitrary values into fluid `clamp()` values using eva-css calculations.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Zero runtime dependencies** — pure CSS string generation
|
|
8
|
+
- **Exact match** with eva-css SCSS clamp calculations
|
|
9
|
+
- **4 fluid intensities** — extreme (`__`), strong (`_`), normal, light (`-`)
|
|
10
|
+
- **Spacing + font-size** — separate phi ratios, separate CSS vars (`--{size}` / `--fs-{size}`)
|
|
11
|
+
- **Tailwind arbitrary class overrides** — `text-[32px]`, `p-[24px]`, `gap-[1rem]`...
|
|
12
|
+
- **Per-section intensity** via `data-eva` attribute
|
|
13
|
+
- **Responsive prefix support** — `max-md:` overrides
|
|
14
|
+
- **CLI + programmatic API**
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install eva-css-ycode
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### CLI
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Create config file with defaults
|
|
28
|
+
npx eva-css-ycode init
|
|
29
|
+
|
|
30
|
+
# Generate bridge.css
|
|
31
|
+
npx eva-css-ycode generate
|
|
32
|
+
|
|
33
|
+
# Generate with inline sizes
|
|
34
|
+
npx eva-css-ycode generate --sizes="4,8,16,24,32,48,64,96" --font-sizes="12,14,16,18,24,32,48" --stdout
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Programmatic
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { generateBridge } from 'eva-css-ycode';
|
|
41
|
+
|
|
42
|
+
const css = generateBridge({
|
|
43
|
+
sizes: [4, 8, 16, 24, 32, 48, 64, 96, 128],
|
|
44
|
+
fontSizes: [12, 14, 16, 18, 20, 24, 32, 48],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// css is a complete CSS string ready to inject or write to file
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
// eva-ycode.config.cjs
|
|
54
|
+
module.exports = {
|
|
55
|
+
sizes: [4, 8, 12, 16, 24, 32, 48, 64, 96, 128],
|
|
56
|
+
fontSizes: [12, 14, 16, 18, 20, 24, 32, 48],
|
|
57
|
+
screen: 1440, // Reference viewport width
|
|
58
|
+
defaultIntensity: '', // '' = normal
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| Option | Default | Description |
|
|
63
|
+
|--------|---------|-------------|
|
|
64
|
+
| `sizes` | — | Spacing sizes in px (from Figma tokens) |
|
|
65
|
+
| `fontSizes` | — | Font sizes in px (from Figma tokens) |
|
|
66
|
+
| `screen` | `1440` | Reference screen width |
|
|
67
|
+
| `phi` | `1.618` | Golden ratio for spacing |
|
|
68
|
+
| `fontPhi` | `1.3` | Ratio for font scaling |
|
|
69
|
+
| `min` | `0.5` | Min fluid factor for spacing |
|
|
70
|
+
| `fontMin` | `0.5` | Min fluid factor for fonts |
|
|
71
|
+
| `max` | `1` | Max fluid factor |
|
|
72
|
+
| `ez` | `142.4` | Extreme intensity factor |
|
|
73
|
+
| `defaultIntensity` | `''` | Default: `__`, `_`, `''`, or `-` |
|
|
74
|
+
|
|
75
|
+
## Generated CSS
|
|
76
|
+
|
|
77
|
+
The bridge CSS has 3 sections:
|
|
78
|
+
|
|
79
|
+
### Section A — CSS Custom Properties
|
|
80
|
+
|
|
81
|
+
```css
|
|
82
|
+
:root {
|
|
83
|
+
/* Spacing: 4 intensities per size */
|
|
84
|
+
--32__: clamp(0.5rem, 3.16vw - 1.11rem, 2.22rem); /* extreme */
|
|
85
|
+
--32_: clamp(0.69rem, 1.67vw + 0.5rem, 2.22rem); /* strong */
|
|
86
|
+
--32: clamp(1.11rem, 1.11vw + 1rem, 2.22rem); /* normal */
|
|
87
|
+
--32-: clamp(1.8rem, 0.56vw + 1.5rem, 2.22rem); /* light */
|
|
88
|
+
|
|
89
|
+
/* Font-sizes: 3 intensities, --fs- prefix */
|
|
90
|
+
--fs-32__: clamp(0.85rem, 1.67vw + 0.5rem, 2.22rem);
|
|
91
|
+
--fs-32_: clamp(1.11rem, 1.11vw + 1rem, 2.22rem);
|
|
92
|
+
--fs-32: clamp(1.44rem, 0.56vw + 1.5rem, 2.22rem);
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Section B — Tailwind Class Overrides
|
|
97
|
+
|
|
98
|
+
```css
|
|
99
|
+
.text-\[32px\] { font-size: var(--fs-32) }
|
|
100
|
+
.p-\[32px\] { padding: var(--32) }
|
|
101
|
+
.gap-\[32px\] { gap: var(--32) }
|
|
102
|
+
.rounded-\[32px\] { border-radius: var(--32) }
|
|
103
|
+
/* ... all 23 spacing properties + font-size */
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Also handles `rem` values: `.gap-\[1rem\] { gap: var(--16) }`
|
|
107
|
+
|
|
108
|
+
### Section C — Per-Section Intensity
|
|
109
|
+
|
|
110
|
+
```html
|
|
111
|
+
<section data-eva="extreme" class="pt-[140px] pb-[140px]">
|
|
112
|
+
<h1 class="text-[56px]">...</h1> <!-- uses --fs-56__ -->
|
|
113
|
+
</section>
|
|
114
|
+
|
|
115
|
+
<section data-eva="light" class="pt-[48px]">
|
|
116
|
+
<p class="text-[16px]">...</p> <!-- uses --fs-16 (light not available for fonts) -->
|
|
117
|
+
</section>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```css
|
|
121
|
+
[data-eva="extreme"] .p-\[32px\] { padding: var(--32__) }
|
|
122
|
+
[data-eva="strong"] .p-\[32px\] { padding: var(--32_) }
|
|
123
|
+
[data-eva="light"] .p-\[32px\] { padding: var(--32-) }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Specificity: `[data-eva] .class` (0,1,1) beats `.class` (0,1,0). No `!important` needed.
|
|
127
|
+
|
|
128
|
+
## Intensity Levels
|
|
129
|
+
|
|
130
|
+
| Suffix | `data-eva` | Fluid behavior |
|
|
131
|
+
|--------|-----------|----------------|
|
|
132
|
+
| `__` | `extreme` | Most responsive — biggest min/max range |
|
|
133
|
+
| `_` | `strong` | Strong fluid |
|
|
134
|
+
| *(none)* | *(default)* | Balanced |
|
|
135
|
+
| `-` | `light` | Near-static — smallest range |
|
|
136
|
+
|
|
137
|
+
Spacing uses all 4 levels. Font-sizes use 3 levels (`__`, `_`, default) — no light level.
|
|
138
|
+
|
|
139
|
+
## yCode Integration
|
|
140
|
+
|
|
141
|
+
1. **Editor canvas** — append bridge CSS as `<style>` after Tailwind processes
|
|
142
|
+
2. **Published pages** — inject as `<style id="eva-bridge">` after Tailwind `<style>`
|
|
143
|
+
3. **Layer panel** — dropdown sets `data-eva` attribute on selected element
|
|
144
|
+
4. **App settings** — user configures sizes, intensity, generates bridge CSS
|
|
145
|
+
|
|
146
|
+
## CLI Reference
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
eva-ycode init Create config file
|
|
150
|
+
eva-ycode generate Write bridge.css
|
|
151
|
+
eva-ycode generate --stdout Output to stdout
|
|
152
|
+
eva-ycode generate --out=custom.css Custom output path
|
|
153
|
+
eva-ycode generate --sizes="4,8,16" Override sizes inline
|
|
154
|
+
eva-ycode generate --font-sizes="16" Override font sizes inline
|
|
155
|
+
eva-ycode generate --intensity=_ Set default intensity
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT
|
|
161
|
+
|
|
162
|
+
## Author
|
|
163
|
+
|
|
164
|
+
EVA CSS Framework
|
|
165
|
+
|
|
166
|
+
## Links
|
|
167
|
+
|
|
168
|
+
- [eva-css](https://www.npmjs.com/package/eva-css-fluid) — Core fluid framework
|
|
169
|
+
- [eva-colors](https://www.npmjs.com/package/@aspect-music/eva-colors) — OKLCH color utilities
|
|
170
|
+
- [eva-purge](https://www.npmjs.com/package/eva-css-purge) — CSS optimization
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
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
|
+
var DATA_EVA_MAP = {
|
|
158
|
+
extreme: "__",
|
|
159
|
+
strong: "_",
|
|
160
|
+
// normal = default (no data-eva needed)
|
|
161
|
+
light: "-"
|
|
162
|
+
};
|
|
163
|
+
var DATA_EVA_FONT_MAP = {
|
|
164
|
+
extreme: "__",
|
|
165
|
+
strong: "_"
|
|
166
|
+
// normal = default (no data-eva needed)
|
|
167
|
+
};
|
|
168
|
+
function resolveConfig(config) {
|
|
169
|
+
return {
|
|
170
|
+
sizes: config.sizes,
|
|
171
|
+
fontSizes: config.fontSizes,
|
|
172
|
+
screen: config.screen ?? 1440,
|
|
173
|
+
phi: config.phi ?? 1.61803398875,
|
|
174
|
+
fontPhi: config.fontPhi ?? 1.3,
|
|
175
|
+
min: config.min ?? 0.5,
|
|
176
|
+
fontMin: config.fontMin ?? 0.5,
|
|
177
|
+
max: config.max ?? 1,
|
|
178
|
+
ez: config.ez ?? 142.4,
|
|
179
|
+
defaultIntensity: config.defaultIntensity ?? ""
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/generator.ts
|
|
184
|
+
function escapeTwClass(prefix, value) {
|
|
185
|
+
return `.${prefix}-\\[${value}\\]`;
|
|
186
|
+
}
|
|
187
|
+
function generateRootVars(cfg) {
|
|
188
|
+
const lines = [":root {"];
|
|
189
|
+
for (const size of cfg.sizes) {
|
|
190
|
+
lines.push(` /* ---- ${size}px ---- */`);
|
|
191
|
+
for (const intensity of SPACING_INTENSITIES) {
|
|
192
|
+
const clamp = generateSpacingClamp(
|
|
193
|
+
size,
|
|
194
|
+
cfg.screen,
|
|
195
|
+
cfg.phi,
|
|
196
|
+
cfg.min,
|
|
197
|
+
cfg.max,
|
|
198
|
+
cfg.ez,
|
|
199
|
+
intensity
|
|
200
|
+
);
|
|
201
|
+
lines.push(` --${size}${intensity}: ${clamp};`);
|
|
202
|
+
}
|
|
203
|
+
lines.push("");
|
|
204
|
+
}
|
|
205
|
+
for (const size of cfg.fontSizes) {
|
|
206
|
+
lines.push(` /* ---- fs-${size}px ---- */`);
|
|
207
|
+
for (const intensity of FONT_INTENSITIES) {
|
|
208
|
+
const clamp = generateFontClamp(
|
|
209
|
+
size,
|
|
210
|
+
cfg.screen,
|
|
211
|
+
cfg.fontPhi,
|
|
212
|
+
cfg.fontMin,
|
|
213
|
+
cfg.max,
|
|
214
|
+
intensity
|
|
215
|
+
);
|
|
216
|
+
lines.push(` --fs-${size}${intensity}: ${clamp};`);
|
|
217
|
+
}
|
|
218
|
+
lines.push("");
|
|
219
|
+
}
|
|
220
|
+
lines.push("}");
|
|
221
|
+
return lines.join("\n");
|
|
222
|
+
}
|
|
223
|
+
function generateDefaultOverrides(cfg) {
|
|
224
|
+
const lines = [];
|
|
225
|
+
const suffix = cfg.defaultIntensity;
|
|
226
|
+
for (const size of cfg.sizes) {
|
|
227
|
+
lines.push(`/* ---- ${size}px ---- */`);
|
|
228
|
+
const varRef = `var(--${size}${suffix})`;
|
|
229
|
+
for (const [twPrefix, cssProp] of Object.entries(PROPERTY_MAP)) {
|
|
230
|
+
lines.push(`${escapeTwClass(twPrefix, `${size}px`)} { ${cssProp}: ${varRef} }`);
|
|
231
|
+
}
|
|
232
|
+
lines.push("");
|
|
233
|
+
}
|
|
234
|
+
const fontSuffix = cfg.defaultIntensity === "-" ? "" : cfg.defaultIntensity;
|
|
235
|
+
for (const size of cfg.fontSizes) {
|
|
236
|
+
const varRef = `var(--fs-${size}${fontSuffix})`;
|
|
237
|
+
for (const [twPrefix, cssProp] of Object.entries(FONT_PROPERTY_MAP)) {
|
|
238
|
+
lines.push(`${escapeTwClass(twPrefix, `${size}px`)} { ${cssProp}: ${varRef} }`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
lines.push("");
|
|
242
|
+
const allSizes = /* @__PURE__ */ new Set([...cfg.sizes]);
|
|
243
|
+
const remEntries = [];
|
|
244
|
+
for (const size of allSizes) {
|
|
245
|
+
const rem = size / 16;
|
|
246
|
+
if (rem === Math.round(rem * 100) / 100 && rem > 0) {
|
|
247
|
+
remEntries.push({ rem, size });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (remEntries.length > 0) {
|
|
251
|
+
lines.push("/* ---- rem mappings ---- */");
|
|
252
|
+
for (const { rem, size } of remEntries) {
|
|
253
|
+
const remStr = rem % 1 === 0 ? `${rem}rem` : `${rem}rem`;
|
|
254
|
+
const varRef = `var(--${size}${suffix})`;
|
|
255
|
+
for (const [twPrefix, cssProp] of Object.entries(PROPERTY_MAP)) {
|
|
256
|
+
lines.push(`${escapeTwClass(twPrefix, remStr)} { ${cssProp}: ${varRef} }`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
lines.push("");
|
|
260
|
+
}
|
|
261
|
+
const fontRemEntries = [];
|
|
262
|
+
for (const size of cfg.fontSizes) {
|
|
263
|
+
const rem = size / 16;
|
|
264
|
+
if (rem === Math.round(rem * 100) / 100 && rem > 0) {
|
|
265
|
+
fontRemEntries.push({ rem, size });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (fontRemEntries.length > 0) {
|
|
269
|
+
for (const { rem, size } of fontRemEntries) {
|
|
270
|
+
const remStr = rem % 1 === 0 ? `${rem}rem` : `${rem}rem`;
|
|
271
|
+
const varRef = `var(--fs-${size}${fontSuffix})`;
|
|
272
|
+
for (const [twPrefix, cssProp] of Object.entries(FONT_PROPERTY_MAP)) {
|
|
273
|
+
lines.push(`${escapeTwClass(twPrefix, remStr)} { ${cssProp}: ${varRef} }`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
lines.push("");
|
|
277
|
+
}
|
|
278
|
+
return lines.join("\n");
|
|
279
|
+
}
|
|
280
|
+
function generateResponsiveOverrides(cfg) {
|
|
281
|
+
const lines = [];
|
|
282
|
+
const suffix = cfg.defaultIntensity;
|
|
283
|
+
const fontSuffix = cfg.defaultIntensity === "-" ? "" : cfg.defaultIntensity;
|
|
284
|
+
lines.push("/* ---- max-md responsive overrides ---- */");
|
|
285
|
+
lines.push("@media (width < 48rem) {");
|
|
286
|
+
for (const size of cfg.sizes) {
|
|
287
|
+
const varRef = `var(--${size}${suffix})`;
|
|
288
|
+
for (const [twPrefix, cssProp] of Object.entries(PROPERTY_MAP)) {
|
|
289
|
+
lines.push(` .max-md\\:${twPrefix}-\\[${size}px\\] { ${cssProp}: ${varRef} }`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
for (const size of cfg.fontSizes) {
|
|
293
|
+
const varRef = `var(--fs-${size}${fontSuffix})`;
|
|
294
|
+
for (const [twPrefix, cssProp] of Object.entries(FONT_PROPERTY_MAP)) {
|
|
295
|
+
lines.push(` .max-md\\:${twPrefix}-\\[${size}px\\] { ${cssProp}: ${varRef} }`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
lines.push("}");
|
|
299
|
+
lines.push("");
|
|
300
|
+
return lines.join("\n");
|
|
301
|
+
}
|
|
302
|
+
function generateIntensitySelectors(cfg) {
|
|
303
|
+
const lines = [];
|
|
304
|
+
for (const [evaValue, intensity] of Object.entries(DATA_EVA_MAP)) {
|
|
305
|
+
lines.push(`/* ---- data-eva="${evaValue}" (${intensity}) ---- */`);
|
|
306
|
+
for (const size of cfg.sizes) {
|
|
307
|
+
const varRef = `var(--${size}${intensity})`;
|
|
308
|
+
for (const [twPrefix, cssProp] of Object.entries(PROPERTY_MAP)) {
|
|
309
|
+
lines.push(`[data-eva="${evaValue}"] ${escapeTwClass(twPrefix, `${size}px`)} { ${cssProp}: ${varRef} }`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
lines.push("");
|
|
313
|
+
}
|
|
314
|
+
for (const [evaValue, intensity] of Object.entries(DATA_EVA_FONT_MAP)) {
|
|
315
|
+
for (const size of cfg.fontSizes) {
|
|
316
|
+
const varRef = `var(--fs-${size}${intensity})`;
|
|
317
|
+
for (const [twPrefix, cssProp] of Object.entries(FONT_PROPERTY_MAP)) {
|
|
318
|
+
lines.push(`[data-eva="${evaValue}"] ${escapeTwClass(twPrefix, `${size}px`)} { ${cssProp}: ${varRef} }`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
lines.push("");
|
|
323
|
+
return lines.join("\n");
|
|
324
|
+
}
|
|
325
|
+
function generateBridge(config) {
|
|
326
|
+
const cfg = resolveConfig(config);
|
|
327
|
+
const sections = [
|
|
328
|
+
"/* =============================================================",
|
|
329
|
+
" EVA CSS \xD7 yCode \u2014 Fluid Bridge",
|
|
330
|
+
" Generated by eva-css-ycode",
|
|
331
|
+
" ============================================================= */",
|
|
332
|
+
"",
|
|
333
|
+
"/* Section A \u2014 CSS Custom Properties */",
|
|
334
|
+
generateRootVars(cfg),
|
|
335
|
+
"",
|
|
336
|
+
"/* Section B \u2014 Tailwind Arbitrary Class Overrides */",
|
|
337
|
+
generateDefaultOverrides(cfg),
|
|
338
|
+
generateResponsiveOverrides(cfg),
|
|
339
|
+
"/* Section C \u2014 Per-Section Intensity Selectors */",
|
|
340
|
+
generateIntensitySelectors(cfg)
|
|
341
|
+
];
|
|
342
|
+
return sections.join("\n");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/cli.ts
|
|
346
|
+
var DEFAULT_CONFIG = `module.exports = {
|
|
347
|
+
sizes: [4, 8, 12, 16, 24, 32, 48, 64, 96, 128],
|
|
348
|
+
fontSizes: [12, 14, 16, 18, 20, 24, 32, 48],
|
|
349
|
+
screen: 1440,
|
|
350
|
+
defaultIntensity: '',
|
|
351
|
+
};
|
|
352
|
+
`;
|
|
353
|
+
function parseArgs(args) {
|
|
354
|
+
const parsed = {};
|
|
355
|
+
for (const arg of args) {
|
|
356
|
+
if (arg.startsWith("--")) {
|
|
357
|
+
const [key, ...rest] = arg.slice(2).split("=");
|
|
358
|
+
parsed[key] = rest.length > 0 ? rest.join("=") : true;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return parsed;
|
|
362
|
+
}
|
|
363
|
+
function loadConfig(cwd) {
|
|
364
|
+
const candidates = [
|
|
365
|
+
"eva-ycode.config.cjs",
|
|
366
|
+
"eva-ycode.config.js"
|
|
367
|
+
];
|
|
368
|
+
for (const name of candidates) {
|
|
369
|
+
const configPath = (0, import_path.resolve)(cwd, name);
|
|
370
|
+
if ((0, import_fs.existsSync)(configPath)) {
|
|
371
|
+
return require(configPath);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
function main() {
|
|
377
|
+
const args = process.argv.slice(2);
|
|
378
|
+
const command = args[0];
|
|
379
|
+
const flags = parseArgs(args.slice(1));
|
|
380
|
+
const cwd = process.cwd();
|
|
381
|
+
if (!command || command === "--help" || command === "-h") {
|
|
382
|
+
console.log(`
|
|
383
|
+
@eva-css/ycode \u2014 Fluid design tokens bridge for yCode
|
|
384
|
+
|
|
385
|
+
Commands:
|
|
386
|
+
init Create eva-ycode.config.cjs with defaults
|
|
387
|
+
generate Generate bridge.css from config
|
|
388
|
+
|
|
389
|
+
Options (generate):
|
|
390
|
+
--sizes="4,8,16,24,32,48" Override sizes
|
|
391
|
+
--font-sizes="12,14,16,18,24" Override font sizes
|
|
392
|
+
--intensity=<suffix> Default intensity (__,_,,-)
|
|
393
|
+
--stdout Output to stdout instead of file
|
|
394
|
+
--out=<path> Output file path (default: bridge.css)
|
|
395
|
+
`);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (command === "init") {
|
|
399
|
+
const configPath = (0, import_path.join)(cwd, "eva-ycode.config.cjs");
|
|
400
|
+
if ((0, import_fs.existsSync)(configPath)) {
|
|
401
|
+
console.log("eva-ycode.config.cjs already exists.");
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
(0, import_fs.writeFileSync)(configPath, DEFAULT_CONFIG, "utf-8");
|
|
405
|
+
console.log("Created eva-ycode.config.cjs");
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (command === "generate") {
|
|
409
|
+
let config = loadConfig(cwd);
|
|
410
|
+
if (flags["sizes"] && typeof flags["sizes"] === "string") {
|
|
411
|
+
const sizes = flags["sizes"].split(",").map(Number);
|
|
412
|
+
config = { ...config, sizes, fontSizes: config?.fontSizes ?? [] };
|
|
413
|
+
}
|
|
414
|
+
if (flags["font-sizes"] && typeof flags["font-sizes"] === "string") {
|
|
415
|
+
const fontSizes = flags["font-sizes"].split(",").map(Number);
|
|
416
|
+
config = { ...config, sizes: config?.sizes ?? [], fontSizes };
|
|
417
|
+
}
|
|
418
|
+
if (flags["intensity"] !== void 0 && typeof flags["intensity"] === "string") {
|
|
419
|
+
config = {
|
|
420
|
+
...config,
|
|
421
|
+
sizes: config?.sizes ?? [],
|
|
422
|
+
fontSizes: config?.fontSizes ?? [],
|
|
423
|
+
defaultIntensity: flags["intensity"]
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
if (!config) {
|
|
427
|
+
console.error('No config found. Run "eva-ycode init" first, or pass --sizes and --font-sizes.');
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
const css = generateBridge(config);
|
|
431
|
+
if (flags["stdout"]) {
|
|
432
|
+
process.stdout.write(css);
|
|
433
|
+
} else {
|
|
434
|
+
const outPath = typeof flags["out"] === "string" ? (0, import_path.resolve)(cwd, flags["out"]) : (0, import_path.join)(cwd, "bridge.css");
|
|
435
|
+
(0, import_fs.writeFileSync)(outPath, css, "utf-8");
|
|
436
|
+
console.log(`Generated ${outPath}`);
|
|
437
|
+
}
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
console.error(`Unknown command: ${command}. Use --help for usage.`);
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
443
|
+
main();
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** Intensity levels matching eva-css SCSS convention */
|
|
2
|
+
type Intensity = '__' | '_' | '' | '-';
|
|
3
|
+
/** Intensity levels for font-size (3 levels only, matching SCSS) */
|
|
4
|
+
type FontIntensity = '__' | '_' | '';
|
|
5
|
+
interface EvaYcodeConfig {
|
|
6
|
+
/** Spacing sizes in px from Figma tokens */
|
|
7
|
+
sizes: number[];
|
|
8
|
+
/** Font sizes in px from Figma tokens */
|
|
9
|
+
fontSizes: number[];
|
|
10
|
+
/** Reference screen width (default: 1440) */
|
|
11
|
+
screen?: number;
|
|
12
|
+
/** Spacing ratio multiplier — golden ratio (default: 1.61803398875) */
|
|
13
|
+
phi?: number;
|
|
14
|
+
/** Font ratio multiplier (default: 1.3) */
|
|
15
|
+
fontPhi?: number;
|
|
16
|
+
/** Min fluid factor for spacing (default: 0.5) */
|
|
17
|
+
min?: number;
|
|
18
|
+
/** Min fluid factor for fonts (default: 0.5 — matches SCSS where getMinRem uses module-level $min) */
|
|
19
|
+
fontMin?: number;
|
|
20
|
+
/** Max fluid factor (default: 1) */
|
|
21
|
+
max?: number;
|
|
22
|
+
/** Extreme intensity factor (default: 142.4) */
|
|
23
|
+
ez?: number;
|
|
24
|
+
/** Default intensity (default: '') */
|
|
25
|
+
defaultIntensity?: Intensity;
|
|
26
|
+
}
|
|
27
|
+
interface ResolvedConfig {
|
|
28
|
+
sizes: number[];
|
|
29
|
+
fontSizes: number[];
|
|
30
|
+
screen: number;
|
|
31
|
+
phi: number;
|
|
32
|
+
fontPhi: number;
|
|
33
|
+
min: number;
|
|
34
|
+
fontMin: number;
|
|
35
|
+
max: number;
|
|
36
|
+
ez: number;
|
|
37
|
+
defaultIntensity: Intensity;
|
|
38
|
+
}
|
|
39
|
+
/** Map of Tailwind prefix → CSS property for spacing/layout */
|
|
40
|
+
declare const PROPERTY_MAP: Record<string, string>;
|
|
41
|
+
/** Font property map — uses --fs-XX vars */
|
|
42
|
+
declare const FONT_PROPERTY_MAP: Record<string, string>;
|
|
43
|
+
declare function resolveConfig(config: EvaYcodeConfig): ResolvedConfig;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* CSS Bridge Generator
|
|
47
|
+
*
|
|
48
|
+
* Generates 3 sections:
|
|
49
|
+
* A) :root CSS custom properties (clamp vars)
|
|
50
|
+
* B) Tailwind arbitrary class overrides (default intensity)
|
|
51
|
+
* C) [data-eva] per-section intensity selectors
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
declare function generateBridge(config: EvaYcodeConfig): string;
|
|
55
|
+
|
|
56
|
+
export { type EvaYcodeConfig, FONT_PROPERTY_MAP, type FontIntensity, type Intensity, PROPERTY_MAP, type ResolvedConfig, generateBridge, resolveConfig };
|