postcss-ruler 1.0.1 → 1.1.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 +141 -2
- package/index.js +144 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ module.exports = {
|
|
|
37
37
|
|
|
38
38
|
## Features
|
|
39
39
|
|
|
40
|
-
### 1.
|
|
40
|
+
### 1. Scale Generation: Create Fluid Scales
|
|
41
41
|
|
|
42
42
|
Create multiple CSS custom properties from named size pairs:
|
|
43
43
|
```css
|
|
@@ -106,7 +106,81 @@ For example, if you have `xs: [8, 16]` and `lg: [32, 48]`, a cross pair `xs-lg`
|
|
|
106
106
|
}
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
### 2.
|
|
109
|
+
### 2. Utility Class Generation: Auto-Generate Utility Classes
|
|
110
|
+
|
|
111
|
+
Generate utility classes from your defined scales with complete selector flexibility:
|
|
112
|
+
|
|
113
|
+
```css
|
|
114
|
+
@ruler scale({
|
|
115
|
+
prefix: 'space',
|
|
116
|
+
pairs: {
|
|
117
|
+
"xs": [8, 16],
|
|
118
|
+
"sm": [16, 24],
|
|
119
|
+
"md": [24, 32]
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
/* Basic class selector */
|
|
124
|
+
@ruler utility({
|
|
125
|
+
selector: '.gap',
|
|
126
|
+
property: 'gap',
|
|
127
|
+
scale: 'space'
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
/* Nested selector with & (for PostCSS nesting) */
|
|
131
|
+
@ruler utility({
|
|
132
|
+
selector: '&.active',
|
|
133
|
+
property: 'padding',
|
|
134
|
+
scale: 'space'
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
/* Multiple properties */
|
|
138
|
+
@ruler utility({
|
|
139
|
+
selector: '.p-block',
|
|
140
|
+
property: ['padding-top', 'padding-bottom'],
|
|
141
|
+
scale: 'space'
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Generates:**
|
|
146
|
+
```css
|
|
147
|
+
--space-xs: clamp(0.5rem, 0.5556vw + 0.3889rem, 1rem);
|
|
148
|
+
--space-sm: clamp(1rem, 0.5556vw + 0.8889rem, 1.5rem);
|
|
149
|
+
--space-md: clamp(1.5rem, 0.5556vw + 1.3889rem, 2rem);
|
|
150
|
+
|
|
151
|
+
.gap-xs { gap: clamp(0.5rem, 0.5556vw + 0.3889rem, 1rem) }
|
|
152
|
+
.gap-sm { gap: clamp(1rem, 0.5556vw + 0.8889rem, 1.5rem) }
|
|
153
|
+
.gap-md { gap: clamp(1.5rem, 0.5556vw + 1.3889rem, 2rem) }
|
|
154
|
+
|
|
155
|
+
&.active-xs { padding: clamp(0.5rem, 0.5556vw + 0.3889rem, 1rem) }
|
|
156
|
+
&.active-sm { padding: clamp(1rem, 0.5556vw + 0.8889rem, 1.5rem) }
|
|
157
|
+
&.active-md { padding: clamp(1.5rem, 0.5556vw + 1.3889rem, 2rem) }
|
|
158
|
+
|
|
159
|
+
.p-block-xs {
|
|
160
|
+
padding-top: clamp(0.5rem, 0.5556vw + 0.3889rem, 1rem);
|
|
161
|
+
padding-bottom: clamp(0.5rem, 0.5556vw + 0.3889rem, 1rem)
|
|
162
|
+
}
|
|
163
|
+
/* ... */
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Supported selector patterns:**
|
|
167
|
+
- **Class selectors**: `.gap` → `.gap-xs`, `.gap-sm`, `.gap-md`
|
|
168
|
+
- **Nested with &**: `&.active` → `&.active-xs`, `&.active-sm` (PostCSS nesting)
|
|
169
|
+
- **Multiple classes**: `.container.space` → `.container.space-xs`, etc.
|
|
170
|
+
- **ID selectors**: `#section` → `#section-xs`, `#section-sm`
|
|
171
|
+
- **Element selectors**: `section` → `section-xs`, `section-sm`
|
|
172
|
+
- **Parent context**: `.container &` → `.container &-xs`, etc.
|
|
173
|
+
|
|
174
|
+
**Utility options:**
|
|
175
|
+
|
|
176
|
+
| Option | Type | Required | Description |
|
|
177
|
+
|--------|------|----------|-------------|
|
|
178
|
+
| `selector` | string | Yes | Any valid CSS selector pattern (e.g., `.gap`, `&.active`, `#section`) |
|
|
179
|
+
| `property` | string or array | Yes | CSS property name(s) to apply the scale values to |
|
|
180
|
+
| `scale` | string | Yes | Name of a previously defined scale (the `prefix` value) |
|
|
181
|
+
| `generateAllCrossPairs` | boolean | No | Include/exclude cross-pairs (overrides scale default) |
|
|
182
|
+
|
|
183
|
+
### 3. Inline Mode: Fluid Function
|
|
110
184
|
|
|
111
185
|
Convert individual values directly to `clamp()` functions:
|
|
112
186
|
```css
|
|
@@ -166,6 +240,15 @@ ruler.fluid(minSize, maxSize[, minWidth, maxWidth])
|
|
|
166
240
|
| `minWidth` | number | No | Minimum viewport width (uses config default) |
|
|
167
241
|
| `maxWidth` | number | No | Maximum viewport width (uses config default) |
|
|
168
242
|
|
|
243
|
+
### Utility Options
|
|
244
|
+
|
|
245
|
+
| Option | Type | Default | Description |
|
|
246
|
+
|--------|------|---------|-------------|
|
|
247
|
+
| `selector` | string | required | Any valid CSS selector pattern (e.g., `.gap`, `&.active`, `#section`) |
|
|
248
|
+
| `property` | string or array | required | CSS property name(s) to apply the scale values to |
|
|
249
|
+
| `scale` | string | required | Name of a previously defined scale (the `prefix` value) |
|
|
250
|
+
| `generateAllCrossPairs` | boolean | No | Include/exclude cross-pairs (overrides scale default) |
|
|
251
|
+
|
|
169
252
|
## How It Works
|
|
170
253
|
|
|
171
254
|
The plugin uses linear interpolation to create fluid values that scale smoothly between viewport sizes:
|
|
@@ -189,6 +272,62 @@ At 1760px viewport: `1.5rem` (24px)
|
|
|
189
272
|
|
|
190
273
|
## Use Cases
|
|
191
274
|
|
|
275
|
+
### Utility-First Workflow with Fluid Scales
|
|
276
|
+
|
|
277
|
+
Generate a complete set of utility classes from your design system:
|
|
278
|
+
|
|
279
|
+
```css
|
|
280
|
+
@ruler scale({
|
|
281
|
+
prefix: 'space',
|
|
282
|
+
generateAllCrossPairs: true,
|
|
283
|
+
pairs: {
|
|
284
|
+
"xs": [8, 16],
|
|
285
|
+
"sm": [12, 20],
|
|
286
|
+
"md": [16, 28],
|
|
287
|
+
"lg": [24, 40],
|
|
288
|
+
"xl": [32, 56]
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
/* Gap utilities */
|
|
293
|
+
@ruler utility({
|
|
294
|
+
selector: '.gap',
|
|
295
|
+
property: 'gap',
|
|
296
|
+
scale: 'space'
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
/* Padding utilities */
|
|
300
|
+
@ruler utility({
|
|
301
|
+
selector: '.p',
|
|
302
|
+
property: 'padding',
|
|
303
|
+
scale: 'space'
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
/* Margin utilities */
|
|
307
|
+
@ruler utility({
|
|
308
|
+
selector: '.m',
|
|
309
|
+
property: 'margin',
|
|
310
|
+
scale: 'space'
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
/* Stack spacing (for flow layout) */
|
|
314
|
+
@ruler utility({
|
|
315
|
+
selector: '.stack > * + *',
|
|
316
|
+
property: 'margin-top',
|
|
317
|
+
scale: 'space'
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Use in your HTML:
|
|
322
|
+
```html
|
|
323
|
+
<section class="p-lg gap-md">
|
|
324
|
+
<div class="stack gap-sm">
|
|
325
|
+
<h2>Heading</h2>
|
|
326
|
+
<p>Content that scales smoothly</p>
|
|
327
|
+
</div>
|
|
328
|
+
</section>
|
|
329
|
+
```
|
|
330
|
+
|
|
192
331
|
### Responsive Typography
|
|
193
332
|
|
|
194
333
|
```css
|
package/index.js
CHANGED
|
@@ -11,6 +11,9 @@ module.exports = opts => {
|
|
|
11
11
|
};
|
|
12
12
|
const config = Object.assign(DEFAULTS, opts);
|
|
13
13
|
|
|
14
|
+
// Storage for generated scales
|
|
15
|
+
const scales = {};
|
|
16
|
+
|
|
14
17
|
/**
|
|
15
18
|
* Converts pixels to r]em units
|
|
16
19
|
* @param {number} px - Pixel value to convert
|
|
@@ -113,7 +116,6 @@ module.exports = opts => {
|
|
|
113
116
|
minWidth: config.minWidth,
|
|
114
117
|
maxWidth: config.maxWidth,
|
|
115
118
|
pairs: {},
|
|
116
|
-
relativeTo: 'viewport',
|
|
117
119
|
prefix: 'space',
|
|
118
120
|
generateAllCrossPairs: config.generateAllCrossPairs,
|
|
119
121
|
};
|
|
@@ -132,11 +134,7 @@ module.exports = opts => {
|
|
|
132
134
|
i++;
|
|
133
135
|
break;
|
|
134
136
|
case 'prefix':
|
|
135
|
-
clampsParams.prefix = value.replace(/['
|
|
136
|
-
i++;
|
|
137
|
-
break;
|
|
138
|
-
case 'relativeTo':
|
|
139
|
-
clampsParams.relativeTo = value.replace(/['\"]/g, '');
|
|
137
|
+
clampsParams.prefix = value.replace(/['"]/g, '');
|
|
140
138
|
i++;
|
|
141
139
|
break;
|
|
142
140
|
case 'generateAllCrossPairs':
|
|
@@ -187,6 +185,61 @@ module.exports = opts => {
|
|
|
187
185
|
return pairs;
|
|
188
186
|
};
|
|
189
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Parses parameters from @ruler utility() at-rule
|
|
190
|
+
* @param {Array} params - Parsed parameter nodes
|
|
191
|
+
* @returns {Object} Parsed configuration object
|
|
192
|
+
*/
|
|
193
|
+
const parseUtilityParams = params => {
|
|
194
|
+
const utilityParams = {
|
|
195
|
+
selector: null,
|
|
196
|
+
property: null,
|
|
197
|
+
scale: null,
|
|
198
|
+
generateAllCrossPairs: null,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
for (let i = 0; i < params.length; i++) {
|
|
202
|
+
const param = params[i];
|
|
203
|
+
const nextParam = params[i + 1];
|
|
204
|
+
if (!param || !nextParam) continue;
|
|
205
|
+
const key = param.value;
|
|
206
|
+
let value = nextParam.value.replace(/[:,]/g, '');
|
|
207
|
+
|
|
208
|
+
switch (key) {
|
|
209
|
+
case 'selector':
|
|
210
|
+
case 'scale':
|
|
211
|
+
utilityParams[key] = value.replace(/['"]/g, '');
|
|
212
|
+
i++;
|
|
213
|
+
break;
|
|
214
|
+
case 'property':
|
|
215
|
+
// Check if it's an array (next token is '[')
|
|
216
|
+
if (nextParam.value === '[') {
|
|
217
|
+
// It's an array - collect values until we hit ']'
|
|
218
|
+
const arrayValues = [];
|
|
219
|
+
i += 2; // Skip 'property' and '['
|
|
220
|
+
while (i < params.length && params[i].value !== ']') {
|
|
221
|
+
if (params[i].type === 'string') {
|
|
222
|
+
arrayValues.push(params[i].value);
|
|
223
|
+
}
|
|
224
|
+
i++;
|
|
225
|
+
}
|
|
226
|
+
utilityParams.property = arrayValues;
|
|
227
|
+
} else {
|
|
228
|
+
// Single value
|
|
229
|
+
utilityParams.property = value.replace(/['"]/g, '');
|
|
230
|
+
i++;
|
|
231
|
+
}
|
|
232
|
+
break;
|
|
233
|
+
case 'generateAllCrossPairs':
|
|
234
|
+
utilityParams.generateAllCrossPairs = value === 'true';
|
|
235
|
+
i++;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return utilityParams;
|
|
241
|
+
};
|
|
242
|
+
|
|
190
243
|
/**
|
|
191
244
|
* Processes @fluid at-rule and generates CSS custom properties
|
|
192
245
|
* @param {Object} atRule - PostCSS at-rule node
|
|
@@ -217,11 +270,90 @@ module.exports = opts => {
|
|
|
217
270
|
pairs: clampPairs,
|
|
218
271
|
});
|
|
219
272
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
273
|
+
// Store the scale for later use by utility classes
|
|
274
|
+
scales[clampsParams.prefix] = clampScale;
|
|
275
|
+
|
|
276
|
+
const postcss = require('postcss');
|
|
277
|
+
const root = postcss.root();
|
|
278
|
+
|
|
279
|
+
clampScale.forEach(step => {
|
|
280
|
+
root.append(
|
|
281
|
+
postcss.decl({
|
|
282
|
+
prop: `--${clampsParams.prefix}-${step.label}`,
|
|
283
|
+
value: step.clamp,
|
|
284
|
+
})
|
|
285
|
+
);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
atRule.replaceWith(root.nodes);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Processes @ruler utility() at-rule and generates utility classes
|
|
293
|
+
* @param {Object} atRule - PostCSS at-rule node
|
|
294
|
+
*/
|
|
295
|
+
const processUtilityAtRule = atRule => {
|
|
296
|
+
const postcss = require('postcss');
|
|
297
|
+
const { nodes } = CSSValueParser(atRule.params);
|
|
298
|
+
const params = nodes[0].nodes.filter(
|
|
299
|
+
x =>
|
|
300
|
+
['word', 'string', 'function'].includes(x.type) &&
|
|
301
|
+
x.value !== '{' &&
|
|
302
|
+
x.value !== '}'
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
const utilityParams = parseUtilityParams(params);
|
|
306
|
+
|
|
307
|
+
// Validate required parameters
|
|
308
|
+
if (!utilityParams.selector) {
|
|
309
|
+
throw new Error(
|
|
310
|
+
'[postcss-ruler] @ruler utility() requires a "selector" parameter'
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
if (!utilityParams.property) {
|
|
314
|
+
throw new Error(
|
|
315
|
+
'[postcss-ruler] @ruler utility() requires a "property" parameter'
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
if (!utilityParams.scale) {
|
|
319
|
+
throw new Error(
|
|
320
|
+
'[postcss-ruler] @ruler utility() requires a "scale" parameter'
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Check if scale exists
|
|
325
|
+
const scale = scales[utilityParams.scale];
|
|
326
|
+
if (!scale) {
|
|
327
|
+
throw new Error(
|
|
328
|
+
`[postcss-ruler] Scale "${utilityParams.scale}" not found. Define it with @ruler scale() first.`
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Determine which scale items to use
|
|
333
|
+
let scaleItems = scale;
|
|
334
|
+
if (utilityParams.generateAllCrossPairs === false) {
|
|
335
|
+
// Filter out cross-pairs (items with hyphens in label)
|
|
336
|
+
scaleItems = scale.filter(item => !item.label.includes('-'));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Normalize property to array
|
|
340
|
+
const properties = Array.isArray(utilityParams.property)
|
|
341
|
+
? utilityParams.property
|
|
342
|
+
: [utilityParams.property];
|
|
343
|
+
|
|
344
|
+
// Generate utility classes as PostCSS nodes
|
|
345
|
+
const rules = scaleItems.map(item => {
|
|
346
|
+
const selector = `${utilityParams.selector}-${item.label}`;
|
|
347
|
+
const rule = postcss.rule({ selector });
|
|
348
|
+
|
|
349
|
+
properties.forEach(prop => {
|
|
350
|
+
rule.append(postcss.decl({ prop, value: item.clamp }));
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
return rule;
|
|
354
|
+
});
|
|
223
355
|
|
|
224
|
-
atRule.replaceWith(
|
|
356
|
+
atRule.replaceWith(rules);
|
|
225
357
|
};
|
|
226
358
|
|
|
227
359
|
/**
|
|
@@ -267,6 +399,8 @@ module.exports = opts => {
|
|
|
267
399
|
ruler: atRule => {
|
|
268
400
|
if (atRule.params.startsWith('scale(')) {
|
|
269
401
|
return processFluidAtRule(atRule);
|
|
402
|
+
} else if (atRule.params.startsWith('utility(')) {
|
|
403
|
+
return processUtilityAtRule(atRule);
|
|
270
404
|
}
|
|
271
405
|
},
|
|
272
406
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postcss-ruler",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "PostCSS plugin to generate fluid scales and values.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"eslintConfig": {
|
|
39
39
|
"parserOptions": {
|
|
40
|
-
"ecmaVersion":
|
|
40
|
+
"ecmaVersion": 2018
|
|
41
41
|
},
|
|
42
42
|
"env": {
|
|
43
43
|
"node": true,
|