scss-variable-extractor 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/src/parser.js ADDED
@@ -0,0 +1,421 @@
1
+ const fs = require('fs');
2
+
3
+ /**
4
+ * Regular expressions for matching various CSS/SCSS value types
5
+ */
6
+ const PATTERNS = {
7
+ // Hex colors: #RGB, #RRGGBB, #RRGGBBAA
8
+ hexColor: /#([0-9a-fA-F]{3,8})\b/g,
9
+
10
+ // RGB/RGBA colors
11
+ rgbColor: /rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)/g,
12
+
13
+ // Named colors (common ones)
14
+ namedColor: /\b(red|blue|green|white|black|gray|grey|yellow|orange|purple|pink|brown|cyan|magenta|transparent)\b/g,
15
+
16
+ // Pixel values
17
+ pixelValue: /\b(\d+)px\b/g,
18
+
19
+ // Percentage values
20
+ percentValue: /\b(\d+)%\b/g,
21
+
22
+ // Font weight values
23
+ fontWeight: /\b(bold|normal|lighter|bolder|[1-9]00)\b/g,
24
+
25
+ // Decimal values (for opacity, line-height)
26
+ decimalValue: /\b(0?\.\d+|1\.\d+|[2-9]\.\d+)\b/g,
27
+
28
+ // Box shadow
29
+ boxShadow: /(-?\d+px\s+){2,4}(rgba?\([^)]+\)|#[0-9a-fA-F]{3,8}|\w+)/g,
30
+
31
+ // Font family
32
+ fontFamily: /(['"][^'"]+['"](?:\s*,\s*(?:sans-serif|serif|monospace|cursive|fantasy))?)/g,
33
+
34
+ // Transition
35
+ transition: /\b(all|none|[\w-]+)\s+[\d.]+m?s\s+(?:ease|linear|ease-in|ease-out|ease-in-out|cubic-bezier\([^)]+\))/g,
36
+
37
+ // Variable declaration (to skip)
38
+ variableDeclaration: /\$[\w-]+\s*:/g,
39
+
40
+ // URL function (to skip)
41
+ urlFunction: /url\([^)]+\)/g,
42
+
43
+ // Content property (to skip string literals)
44
+ contentProperty: /content\s*:\s*[^;]+;/g,
45
+
46
+ // String interpolation (to skip)
47
+ interpolation: /#\{[^}]+\}/g,
48
+
49
+ // @use and @forward statements (to skip)
50
+ useForward: /@(?:use|forward)\s+[^;]+;/g,
51
+
52
+ // Material palette functions (to skip)
53
+ materialFunctions: /mat\.(?:define-palette|get-color-from-palette|define-(?:light|dark)-theme|all-component-themes)\([^)]*\)/g
54
+ };
55
+
56
+ /**
57
+ * Extracts hardcoded values from SCSS content
58
+ * @param {string} content - SCSS file content
59
+ * @param {string} filePath - Path to the file (for context)
60
+ * @returns {Object} - Extracted values categorized by type
61
+ */
62
+ function parseScss(content, filePath) {
63
+ const extracted = {
64
+ colors: [],
65
+ spacing: [],
66
+ fontSizes: [],
67
+ fontWeights: [],
68
+ fontFamilies: [],
69
+ borderRadius: [],
70
+ shadows: [],
71
+ zIndex: [],
72
+ sizing: [],
73
+ lineHeight: [],
74
+ opacity: [],
75
+ transitions: []
76
+ };
77
+
78
+ // Remove comments
79
+ let cleanContent = content.replace(/\/\*[\s\S]*?\*\//g, '');
80
+ cleanContent = cleanContent.replace(/\/\/.*/g, '');
81
+
82
+ // Remove safe zones (values we should not extract)
83
+ const safeZones = [];
84
+
85
+ // Mark variable declarations - entire lines with $variable:
86
+ let match;
87
+ const varDeclPattern = /\$[\w-]+\s*:[^;]+;/g;
88
+ while ((match = varDeclPattern.exec(cleanContent)) !== null) {
89
+ safeZones.push({ start: match.index, end: match.index + match[0].length });
90
+ }
91
+
92
+ // Mark other safe zones
93
+ const markers = [
94
+ PATTERNS.urlFunction,
95
+ PATTERNS.contentProperty,
96
+ PATTERNS.interpolation,
97
+ PATTERNS.useForward,
98
+ PATTERNS.materialFunctions
99
+ ];
100
+
101
+ markers.forEach(pattern => {
102
+ pattern.lastIndex = 0;
103
+ while ((match = pattern.exec(cleanContent)) !== null) {
104
+ safeZones.push({ start: match.index, end: match.index + match[0].length });
105
+ }
106
+ });
107
+
108
+ // Helper to check if position is in safe zone
109
+ const isInSafeZone = (pos) => {
110
+ return safeZones.some(zone => pos >= zone.start && pos < zone.end);
111
+ };
112
+
113
+ // Extract colors (hex)
114
+ PATTERNS.hexColor.lastIndex = 0;
115
+ while ((match = PATTERNS.hexColor.exec(cleanContent)) !== null) {
116
+ if (!isInSafeZone(match.index)) {
117
+ extracted.colors.push({
118
+ value: match[0],
119
+ line: getLineNumber(cleanContent, match.index),
120
+ file: filePath,
121
+ context: getContext(cleanContent, match.index)
122
+ });
123
+ }
124
+ }
125
+
126
+ // Extract colors (rgb/rgba)
127
+ PATTERNS.rgbColor.lastIndex = 0;
128
+ while ((match = PATTERNS.rgbColor.exec(cleanContent)) !== null) {
129
+ if (!isInSafeZone(match.index)) {
130
+ extracted.colors.push({
131
+ value: match[0],
132
+ line: getLineNumber(cleanContent, match.index),
133
+ file: filePath,
134
+ context: getContext(cleanContent, match.index)
135
+ });
136
+ }
137
+ }
138
+
139
+ // Extract named colors
140
+ PATTERNS.namedColor.lastIndex = 0;
141
+ while ((match = PATTERNS.namedColor.exec(cleanContent)) !== null) {
142
+ if (!isInSafeZone(match.index)) {
143
+ const ctx = getContext(cleanContent, match.index);
144
+ // Only extract if it looks like a color value (not in selectors)
145
+ if (ctx.includes(':')) {
146
+ extracted.colors.push({
147
+ value: match[0],
148
+ line: getLineNumber(cleanContent, match.index),
149
+ file: filePath,
150
+ context: ctx
151
+ });
152
+ }
153
+ }
154
+ }
155
+
156
+ // Extract pixel values (categorize by property)
157
+ PATTERNS.pixelValue.lastIndex = 0;
158
+ while ((match = PATTERNS.pixelValue.exec(cleanContent)) !== null) {
159
+ if (!isInSafeZone(match.index)) {
160
+ const ctx = getContext(cleanContent, match.index);
161
+ const property = extractPropertyAtPosition(cleanContent, match.index);
162
+
163
+ if (isFontSizeProperty(property)) {
164
+ extracted.fontSizes.push({
165
+ value: match[0],
166
+ line: getLineNumber(cleanContent, match.index),
167
+ file: filePath,
168
+ context: ctx,
169
+ property
170
+ });
171
+ } else if (isSpacingProperty(property)) {
172
+ extracted.spacing.push({
173
+ value: match[0],
174
+ line: getLineNumber(cleanContent, match.index),
175
+ file: filePath,
176
+ context: ctx,
177
+ property
178
+ });
179
+ } else if (isSizingProperty(property)) {
180
+ extracted.sizing.push({
181
+ value: match[0],
182
+ line: getLineNumber(cleanContent, match.index),
183
+ file: filePath,
184
+ context: ctx,
185
+ property
186
+ });
187
+ } else if (isBorderRadiusProperty(property)) {
188
+ extracted.borderRadius.push({
189
+ value: match[0],
190
+ line: getLineNumber(cleanContent, match.index),
191
+ file: filePath,
192
+ context: ctx,
193
+ property
194
+ });
195
+ } else if (isLineHeightProperty(property)) {
196
+ extracted.lineHeight.push({
197
+ value: match[0],
198
+ line: getLineNumber(cleanContent, match.index),
199
+ file: filePath,
200
+ context: ctx,
201
+ property
202
+ });
203
+ }
204
+ }
205
+ }
206
+
207
+ // Extract percentage values (for border-radius)
208
+ PATTERNS.percentValue.lastIndex = 0;
209
+ while ((match = PATTERNS.percentValue.exec(cleanContent)) !== null) {
210
+ if (!isInSafeZone(match.index)) {
211
+ const ctx = getContext(cleanContent, match.index);
212
+ const property = extractPropertyAtPosition(cleanContent, match.index);
213
+
214
+ if (isBorderRadiusProperty(property)) {
215
+ extracted.borderRadius.push({
216
+ value: match[0],
217
+ line: getLineNumber(cleanContent, match.index),
218
+ file: filePath,
219
+ context: ctx,
220
+ property
221
+ });
222
+ } else if (isOpacityProperty(property)) {
223
+ extracted.opacity.push({
224
+ value: match[0],
225
+ line: getLineNumber(cleanContent, match.index),
226
+ file: filePath,
227
+ context: ctx,
228
+ property
229
+ });
230
+ }
231
+ }
232
+ }
233
+
234
+ // Extract font weights
235
+ PATTERNS.fontWeight.lastIndex = 0;
236
+ while ((match = PATTERNS.fontWeight.exec(cleanContent)) !== null) {
237
+ if (!isInSafeZone(match.index)) {
238
+ const ctx = getContext(cleanContent, match.index);
239
+ const property = extractPropertyAtPosition(cleanContent, match.index);
240
+
241
+ if (property && property.includes('font-weight')) {
242
+ extracted.fontWeights.push({
243
+ value: match[0],
244
+ line: getLineNumber(cleanContent, match.index),
245
+ file: filePath,
246
+ context: ctx,
247
+ property
248
+ });
249
+ }
250
+ }
251
+ }
252
+
253
+ // Extract font families
254
+ PATTERNS.fontFamily.lastIndex = 0;
255
+ while ((match = PATTERNS.fontFamily.exec(cleanContent)) !== null) {
256
+ if (!isInSafeZone(match.index)) {
257
+ const ctx = getContext(cleanContent, match.index);
258
+ const property = extractPropertyAtPosition(cleanContent, match.index);
259
+
260
+ if (property && property.includes('font-family')) {
261
+ extracted.fontFamilies.push({
262
+ value: match[0],
263
+ line: getLineNumber(cleanContent, match.index),
264
+ file: filePath,
265
+ context: ctx,
266
+ property
267
+ });
268
+ }
269
+ }
270
+ }
271
+
272
+ // Extract box shadows
273
+ PATTERNS.boxShadow.lastIndex = 0;
274
+ while ((match = PATTERNS.boxShadow.exec(cleanContent)) !== null) {
275
+ if (!isInSafeZone(match.index)) {
276
+ const ctx = getContext(cleanContent, match.index);
277
+ const property = extractPropertyAtPosition(cleanContent, match.index);
278
+
279
+ if (property && property.includes('box-shadow')) {
280
+ extracted.shadows.push({
281
+ value: match[0],
282
+ line: getLineNumber(cleanContent, match.index),
283
+ file: filePath,
284
+ context: ctx,
285
+ property
286
+ });
287
+ }
288
+ }
289
+ }
290
+
291
+ // Extract decimal values (opacity, line-height)
292
+ PATTERNS.decimalValue.lastIndex = 0;
293
+ while ((match = PATTERNS.decimalValue.exec(cleanContent)) !== null) {
294
+ if (!isInSafeZone(match.index)) {
295
+ const ctx = getContext(cleanContent, match.index);
296
+ const property = extractPropertyAtPosition(cleanContent, match.index);
297
+
298
+ if (isOpacityProperty(property)) {
299
+ extracted.opacity.push({
300
+ value: match[0],
301
+ line: getLineNumber(cleanContent, match.index),
302
+ file: filePath,
303
+ context: ctx,
304
+ property
305
+ });
306
+ } else if (isLineHeightProperty(property)) {
307
+ extracted.lineHeight.push({
308
+ value: match[0],
309
+ line: getLineNumber(cleanContent, match.index),
310
+ file: filePath,
311
+ context: ctx,
312
+ property
313
+ });
314
+ }
315
+ }
316
+ }
317
+
318
+ // Extract transitions
319
+ PATTERNS.transition.lastIndex = 0;
320
+ while ((match = PATTERNS.transition.exec(cleanContent)) !== null) {
321
+ if (!isInSafeZone(match.index)) {
322
+ extracted.transitions.push({
323
+ value: match[0],
324
+ line: getLineNumber(cleanContent, match.index),
325
+ file: filePath,
326
+ context: getContext(cleanContent, match.index)
327
+ });
328
+ }
329
+ }
330
+
331
+ // Extract z-index values (pure integers in z-index context)
332
+ const zIndexRegex = /z-index\s*:\s*(\d+)/g;
333
+ while ((match = zIndexRegex.exec(cleanContent)) !== null) {
334
+ if (!isInSafeZone(match.index)) {
335
+ extracted.zIndex.push({
336
+ value: match[1],
337
+ line: getLineNumber(cleanContent, match.index),
338
+ file: filePath,
339
+ context: getContext(cleanContent, match.index)
340
+ });
341
+ }
342
+ }
343
+
344
+ return extracted;
345
+ }
346
+
347
+ /**
348
+ * Gets line number for a position in text
349
+ */
350
+ function getLineNumber(text, position) {
351
+ return text.substring(0, position).split('\n').length;
352
+ }
353
+
354
+ /**
355
+ * Gets surrounding context for a position
356
+ */
357
+ function getContext(text, position, contextSize = 50) {
358
+ const start = Math.max(0, position - contextSize);
359
+ const end = Math.min(text.length, position + contextSize);
360
+ return text.substring(start, end).trim();
361
+ }
362
+
363
+ /**
364
+ * Extracts property name at a specific position in the content
365
+ * Looks backward from position to find the property name
366
+ */
367
+ function extractPropertyAtPosition(content, position) {
368
+ // Find the start of the current line
369
+ const lineStart = content.lastIndexOf('\n', position) + 1;
370
+ const lineSegment = content.substring(lineStart, position);
371
+
372
+ // Look for property: pattern before the position
373
+ const match = lineSegment.match(/([\w-]+)\s*:\s*[^:;]*$/);
374
+ return match ? match[1] : null;
375
+ }
376
+
377
+ /**
378
+ * Extracts property name from context (legacy, for other uses)
379
+ */
380
+ function extractProperty(context) {
381
+ const match = context.match(/([\w-]+)\s*:/);
382
+ return match ? match[1] : null;
383
+ }
384
+
385
+ /**
386
+ * Property type checkers
387
+ */
388
+ function isSpacingProperty(prop) {
389
+ if (!prop) return false;
390
+ return /^(padding|margin|gap|top|right|bottom|left)/.test(prop);
391
+ }
392
+
393
+ function isFontSizeProperty(prop) {
394
+ if (!prop) return false;
395
+ return prop === 'font-size';
396
+ }
397
+
398
+ function isSizingProperty(prop) {
399
+ if (!prop) return false;
400
+ return /^(width|height|min-width|max-width|min-height|max-height)/.test(prop);
401
+ }
402
+
403
+ function isBorderRadiusProperty(prop) {
404
+ if (!prop) return false;
405
+ return /border-radius/.test(prop);
406
+ }
407
+
408
+ function isLineHeightProperty(prop) {
409
+ if (!prop) return false;
410
+ return prop === 'line-height';
411
+ }
412
+
413
+ function isOpacityProperty(prop) {
414
+ if (!prop) return false;
415
+ return prop === 'opacity';
416
+ }
417
+
418
+ module.exports = {
419
+ parseScss,
420
+ PATTERNS
421
+ };
@@ -0,0 +1,209 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Refactors SCSS files by replacing hardcoded values with variable references
6
+ * @param {Array<string>} scssFiles - Array of SCSS file paths
7
+ * @param {Object} analysis - Analysis results with variable mappings
8
+ * @param {string} variablesFilePath - Path to the generated variables file
9
+ * @param {Object} config - Configuration
10
+ */
11
+ function refactorScssFiles(scssFiles, analysis, variablesFilePath, config) {
12
+ const refactoredFiles = [];
13
+
14
+ scssFiles.forEach(filePath => {
15
+ const content = fs.readFileSync(filePath, 'utf8');
16
+ const refactored = refactorFile(content, analysis, variablesFilePath, filePath, config);
17
+
18
+ if (refactored.modified) {
19
+ fs.writeFileSync(filePath, refactored.content, 'utf8');
20
+ refactoredFiles.push({
21
+ path: filePath,
22
+ changes: refactored.changes
23
+ });
24
+ }
25
+ });
26
+
27
+ return refactoredFiles;
28
+ }
29
+
30
+ /**
31
+ * Refactors a single SCSS file
32
+ */
33
+ function refactorFile(content, analysis, variablesFilePath, currentFilePath, config) {
34
+ let newContent = content;
35
+ let modified = false;
36
+ const changes = [];
37
+
38
+ // Build value-to-variable mapping
39
+ const valueMap = buildValueMap(analysis);
40
+
41
+ // Check if file already has @use import for variables
42
+ const hasVariablesImport = content.includes('@use') && content.includes('variables');
43
+
44
+ // Replace values with variables
45
+ Object.entries(valueMap).forEach(([value, variableName]) => {
46
+ const regex = createReplacementRegex(value);
47
+ const matches = [];
48
+
49
+ let match;
50
+ while ((match = regex.exec(content)) !== null) {
51
+ matches.push({
52
+ index: match.index,
53
+ matched: match[0],
54
+ fullMatch: match
55
+ });
56
+ }
57
+
58
+ // Check if replacements are in safe zones
59
+ matches.forEach(m => {
60
+ if (!isInSafeZone(content, m.index)) {
61
+ // Replace the value
62
+ const before = newContent;
63
+ newContent = replaceAt(newContent, m.matched, variableName, m.index);
64
+
65
+ if (before !== newContent) {
66
+ modified = true;
67
+ changes.push({
68
+ from: value,
69
+ to: variableName
70
+ });
71
+ }
72
+ }
73
+ });
74
+ });
75
+
76
+ // Add @use import if modified and not already present
77
+ if (modified && !hasVariablesImport) {
78
+ const relativePath = getRelativeImportPath(currentFilePath, variablesFilePath);
79
+ const useStatement = `@use '${relativePath}' as *;\n\n`;
80
+ newContent = useStatement + newContent;
81
+ }
82
+
83
+ return {
84
+ content: newContent,
85
+ modified,
86
+ changes
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Builds a map of values to variable names
92
+ */
93
+ function buildValueMap(analysis) {
94
+ const valueMap = {};
95
+
96
+ Object.keys(analysis).forEach(category => {
97
+ analysis[category].forEach(item => {
98
+ // Use normalized value as key for consistent matching
99
+ valueMap[item.value] = item.suggestedName;
100
+ });
101
+ });
102
+
103
+ return valueMap;
104
+ }
105
+
106
+ /**
107
+ * Creates a regex for replacing a specific value
108
+ */
109
+ function createReplacementRegex(value) {
110
+ // Escape special regex characters
111
+ const escaped = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
112
+ // Match the value with word boundaries or specific contexts
113
+ return new RegExp(escaped, 'g');
114
+ }
115
+
116
+ /**
117
+ * Checks if a position in content is in a safe zone (should not be replaced)
118
+ */
119
+ function isInSafeZone(content, position) {
120
+ // Check for variable declarations
121
+ const lineStart = content.lastIndexOf('\n', position);
122
+ const lineEnd = content.indexOf('\n', position);
123
+ const line = content.substring(lineStart, lineEnd);
124
+
125
+ if (line.includes('$') && line.includes(':')) {
126
+ // This is a variable declaration line
127
+ return true;
128
+ }
129
+
130
+ // Check for @use, @forward, @import
131
+ if (/@(?:use|forward|import)/.test(line)) {
132
+ return true;
133
+ }
134
+
135
+ // Check for url()
136
+ const beforeContext = content.substring(Math.max(0, position - 50), position);
137
+ if (/url\s*\(\s*[^)]*$/.test(beforeContext)) {
138
+ return true;
139
+ }
140
+
141
+ // Check for content property
142
+ if (/content\s*:\s*[^;]*$/.test(beforeContext)) {
143
+ return true;
144
+ }
145
+
146
+ // Check for interpolation #{}
147
+ if (/#\{[^}]*$/.test(beforeContext)) {
148
+ return true;
149
+ }
150
+
151
+ // Check for Material functions
152
+ if (/mat\.\w+\([^)]*$/.test(beforeContext)) {
153
+ return true;
154
+ }
155
+
156
+ // Check for comments
157
+ const commentStart = content.lastIndexOf('/*', position);
158
+ const commentEnd = content.lastIndexOf('*/', position);
159
+ if (commentStart > commentEnd) {
160
+ return true;
161
+ }
162
+
163
+ const lineCommentStart = content.lastIndexOf('//', lineStart);
164
+ if (lineCommentStart > lineStart && lineCommentStart < position) {
165
+ return true;
166
+ }
167
+
168
+ return false;
169
+ }
170
+
171
+ /**
172
+ * Replaces value at specific index
173
+ */
174
+ function replaceAt(content, searchValue, replaceValue, startIndex) {
175
+ // Find the exact position and replace
176
+ const before = content.substring(0, startIndex);
177
+ const after = content.substring(startIndex);
178
+
179
+ // Replace first occurrence in 'after'
180
+ const replaced = after.replace(searchValue, replaceValue);
181
+
182
+ return before + replaced;
183
+ }
184
+
185
+ /**
186
+ * Gets relative import path from current file to variables file
187
+ */
188
+ function getRelativeImportPath(fromFile, toFile) {
189
+ const fromDir = path.dirname(fromFile);
190
+ let relativePath = path.relative(fromDir, toFile);
191
+
192
+ // Convert to forward slashes for imports
193
+ relativePath = relativePath.replace(/\\/g, '/');
194
+
195
+ // Remove .scss extension
196
+ relativePath = relativePath.replace(/\.scss$/, '');
197
+
198
+ // Add ./ prefix if not starting with ../
199
+ if (!relativePath.startsWith('..')) {
200
+ relativePath = './' + relativePath;
201
+ }
202
+
203
+ return relativePath;
204
+ }
205
+
206
+ module.exports = {
207
+ refactorScssFiles,
208
+ refactorFile
209
+ };
package/src/scanner.js ADDED
@@ -0,0 +1,29 @@
1
+ const glob = require('glob');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Recursively scans for SCSS files in the given source directory
6
+ * @param {string} srcPath - Source directory to scan
7
+ * @param {Array<string>} ignorePatterns - Patterns to ignore
8
+ * @returns {Promise<Array<string>>} - Array of absolute file paths
9
+ */
10
+ async function scanScssFiles(srcPath, ignorePatterns = []) {
11
+ return new Promise((resolve, reject) => {
12
+ const pattern = path.join(srcPath, '**/*.scss');
13
+
14
+ glob(pattern, {
15
+ ignore: ignorePatterns,
16
+ absolute: true
17
+ }, (err, files) => {
18
+ if (err) {
19
+ reject(err);
20
+ } else {
21
+ resolve(files);
22
+ }
23
+ });
24
+ });
25
+ }
26
+
27
+ module.exports = {
28
+ scanScssFiles
29
+ };
@@ -0,0 +1,56 @@
1
+ //
2
+ // Auto-generated SCSS Variables
3
+ // Generated by scss-variable-extractor v{{version}}
4
+ // Generated at: {{timestamp}}
5
+ //
6
+ // DO NOT EDIT THIS FILE MANUALLY
7
+ // This file is auto-generated. Your changes will be overwritten.
8
+ //
9
+
10
+ // Colors
11
+ // ────────────────────────────────────────
12
+ {{colors}}
13
+
14
+ // Spacing
15
+ // ────────────────────────────────────────
16
+ {{spacing}}
17
+
18
+ // Font Sizes
19
+ // ────────────────────────────────────────
20
+ {{fontSizes}}
21
+
22
+ // Font Weights
23
+ // ────────────────────────────────────────
24
+ {{fontWeights}}
25
+
26
+ // Font Families
27
+ // ────────────────────────────────────────
28
+ {{fontFamilies}}
29
+
30
+ // Border Radius
31
+ // ────────────────────────────────────────
32
+ {{borderRadius}}
33
+
34
+ // Shadows
35
+ // ────────────────────────────────────────
36
+ {{shadows}}
37
+
38
+ // Z-Index
39
+ // ────────────────────────────────────────
40
+ {{zIndex}}
41
+
42
+ // Sizing
43
+ // ────────────────────────────────────────
44
+ {{sizing}}
45
+
46
+ // Line Heights
47
+ // ────────────────────────────────────────
48
+ {{lineHeight}}
49
+
50
+ // Opacity
51
+ // ────────────────────────────────────────
52
+ {{opacity}}
53
+
54
+ // Transitions
55
+ // ────────────────────────────────────────
56
+ {{transitions}}