vite-plugin-capsize-radix 0.1.1 → 0.1.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
@@ -31,6 +31,7 @@ if you're interested on collaborating on one.
31
31
  ### Add to vite.config.ts
32
32
 
33
33
  A sample React config:
34
+
34
35
  ```ts
35
36
  import { defineConfig } from "vite"
36
37
  import react from "@vitejs/plugin-react-swc"
@@ -51,11 +52,12 @@ export default defineConfig({
51
52
  headingFontStack: [merriweatherSans, arial],
52
53
  codingFontStack: [sourceCodePro, arial],
53
54
  }),
54
- ]
55
+ ],
55
56
  })
56
57
  ```
57
58
 
58
59
  ### Using with Radix UI
60
+
59
61
  This plugin overrides all typography related CSS for Radix so you can simply
60
62
  use their typography components as normal (e.g. `<Heading>`, `<Text>`, `<Strong>`, etc)
61
63
 
@@ -87,26 +89,27 @@ Capsize has precalculated metrics for system and many open source fonts. If you'
87
89
  using a custom font, you can calculate the font metrics using their [@capsizecss/unpack](https://github.com/seek-oss/capsize?tab=readme-ov-file#unpack) package.
88
90
 
89
91
  ## Responsive styles
92
+
90
93
  This plugin automatically generates responsive styles. Typically you want your mobile font-size
91
94
  to be slightly smaller than on desktop. We do that by shifting the text a size smaller on mobile.
92
95
 
93
96
  ## Parameters:
94
97
 
95
- * __outputPath (string): Required__. The file path where the generated CSS or design tokens should be saved.
96
- * __textStyles (TextStyle[]): Optional__. An array of objects, each representing a text style. Each object contains two properties: `fontSize` and `lineHeight`, which are numerical values, pixels for `fontSize` and pixels or relative value for `lineHeight`. Defaults to:
97
- ```ts
98
- [
99
- { fontSize: 9, lineHeight: 21 },
100
- { fontSize: 11, lineHeight: 23 },
101
- { fontSize: 12, lineHeight: 25 },
102
- { fontSize: 14, lineHeight: 27 },
103
- { fontSize: 18, lineHeight: 29 },
104
- { fontSize: 24, lineHeight: 36 },
105
- { fontSize: 36, lineHeight: 44 },
106
- { fontSize: 48, lineHeight: 52 },
107
- { fontSize: 64, lineHeight: 64 }
108
- ]
109
- ```
110
- * __defaultFontStack (FontMetrics[]): Optional__. An array of `FontMetrics` objects. Defaults to a System Font stack.
111
- * __headingFontStack (FontMetrics[]): Optional__. An array of `FontMetrics` objects. Defaults to your `defaultFontStack`.
112
- * __codingFontStack (FontMetrics[]): Optional__. An array of `FontMetrics` objects. Defaults to your `defaultFontStack`.
98
+ - **outputPath (string): Required**. The file path where the generated CSS or design tokens should be saved.
99
+ - **textStyles (TextStyle[]): Optional**. An array of objects, each representing a text style. Each object contains two properties: `fontSize` and `lineHeight`, which are numerical values, pixels for `fontSize` and pixels or relative value for `lineHeight`. Defaults to:
100
+ ```ts
101
+ ;[
102
+ { fontSize: 9, lineHeight: 19 },
103
+ { fontSize: 11, lineHeight: 23 },
104
+ { fontSize: 12, lineHeight: 25 },
105
+ { fontSize: 14, lineHeight: 28 },
106
+ { fontSize: 18, lineHeight: 30 },
107
+ { fontSize: 24, lineHeight: 36 },
108
+ { fontSize: 36, lineHeight: 44 },
109
+ { fontSize: 48, lineHeight: 52 },
110
+ { fontSize: 64, lineHeight: 64 },
111
+ ]
112
+ ```
113
+ - **defaultFontStack (FontMetrics[]): Optional**. An array of `FontMetrics` objects. Defaults to a System Font stack.
114
+ - **headingFontStack (FontMetrics[]): Optional**. An array of `FontMetrics` objects. Defaults to your `defaultFontStack`.
115
+ - **codingFontStack (FontMetrics[]): Optional**. An array of `FontMetrics` objects. Defaults to your `defaultFontStack`.
package/dist/index.js CHANGED
@@ -36,11 +36,15 @@ module.exports = __toCommonJS(index_exports);
36
36
  var import_fs = __toESM(require("fs"));
37
37
  var import_core = require("@capsizecss/core");
38
38
  var import_mustache = __toESM(require("mustache"));
39
+ var import_path = __toESM(require("path"));
39
40
  var import_segoeUI = __toESM(require("@capsizecss/metrics/segoeUI"));
40
41
  var import_appleSystem = __toESM(require("@capsizecss/metrics/appleSystem"));
41
42
  var import_roboto = __toESM(require("@capsizecss/metrics/roboto"));
42
43
  var import_ubuntu = __toESM(require("@capsizecss/metrics/ubuntu"));
43
44
  var import_notoSans = __toESM(require("@capsizecss/metrics/notoSans"));
45
+ var TEXT_ELEMENTS = `.rt-Text, .rt-Heading, .rt-Em, .rt-Quote, .rt-Link`;
46
+ var TEXT_SELECTOR_PLACEHOLDER = `__CAPSIZE_TEXT__`;
47
+ var TEXT_SELECTOR_REPLACEMENT = `:where(${TEXT_ELEMENTS})`;
44
48
  var template = `/* Auto-generated by vite-plugin-capsize-radix */
45
49
 
46
50
  /* Override Radix variables */
@@ -109,7 +113,7 @@ var template = `/* Auto-generated by vite-plugin-capsize-radix */
109
113
  display: inline-block;
110
114
  }
111
115
 
112
- /* Class names for text elements */
116
+ /* Size variants for text elements only */
113
117
  {{#mobileFontData}}
114
118
  {{{style}}}
115
119
  {{/mobileFontData}}
@@ -119,9 +123,39 @@ var template = `/* Auto-generated by vite-plugin-capsize-radix */
119
123
  {{/fontData}}
120
124
  }
121
125
  `;
122
- async function generate(options) {
126
+ function validateOptions(options) {
127
+ if (!options.outputPath) {
128
+ throw new Error(`[capsize-radix] outputPath is required`);
129
+ }
130
+ if (options.textStyles.length < 3) {
131
+ throw new Error(
132
+ `[capsize-radix] textStyles must have at least 3 entries (found ${options.textStyles.length})`
133
+ );
134
+ }
135
+ for (let i = 0; i < options.textStyles.length; i++) {
136
+ const style = options.textStyles[i];
137
+ if (typeof style.fontSize !== `number` || style.fontSize <= 0) {
138
+ throw new Error(
139
+ `[capsize-radix] textStyles[${i}].fontSize must be a positive number`
140
+ );
141
+ }
142
+ if (typeof style.lineHeight !== `number` || style.lineHeight <= 0) {
143
+ throw new Error(
144
+ `[capsize-radix] textStyles[${i}].lineHeight must be a positive number`
145
+ );
146
+ }
147
+ }
148
+ const outputDir = import_path.default.dirname(options.outputPath);
149
+ if (!import_fs.default.existsSync(outputDir)) {
150
+ throw new Error(
151
+ `[capsize-radix] Output directory does not exist: ${outputDir}`
152
+ );
153
+ }
154
+ }
155
+ function generate(options) {
156
+ validateOptions(options);
123
157
  let defaultFontFamily;
124
- let headingFontFamiliy;
158
+ let headingFontFamily;
125
159
  let codingFontFamily;
126
160
  if (!options.defaultFontStack) {
127
161
  options.defaultFontStack = [
@@ -133,13 +167,13 @@ async function generate(options) {
133
167
  ];
134
168
  defaultFontFamily = (0, import_core.createFontStack)(options.defaultFontStack);
135
169
  defaultFontFamily.fontFamily += `, ui-sans-serif, system-ui, sans-serif`;
136
- } else if (options.defaultFontStack) {
170
+ } else {
137
171
  defaultFontFamily = (0, import_core.createFontStack)(options.defaultFontStack);
138
172
  }
139
173
  if (options.headingFontStack) {
140
- headingFontFamiliy = (0, import_core.createFontStack)(options.headingFontStack);
174
+ headingFontFamily = (0, import_core.createFontStack)(options.headingFontStack);
141
175
  } else {
142
- headingFontFamiliy = defaultFontFamily;
176
+ headingFontFamily = defaultFontFamily;
143
177
  }
144
178
  if (options.codingFontStack) {
145
179
  codingFontFamily = (0, import_core.createFontStack)(options.codingFontStack);
@@ -154,7 +188,7 @@ async function generate(options) {
154
188
  ].map(({ fontSize, lineHeight }, i) => {
155
189
  const lineGap = lineHeight - fontSize;
156
190
  const style = (0, import_core.createStyleString)(
157
- `rt-r-size-${i + 1}:not(.rt-DialogContent)`,
191
+ `${TEXT_SELECTOR_PLACEHOLDER}.rt-r-size-${i + 1}`,
158
192
  {
159
193
  capHeight: fontSize,
160
194
  lineGap,
@@ -194,7 +228,7 @@ async function generate(options) {
194
228
  const fontData = options.textStyles.map(({ fontSize, lineHeight }, i) => {
195
229
  const lineGap = lineHeight - fontSize;
196
230
  const style = (0, import_core.createStyleString)(
197
- `rt-r-size-${i + 1}:not(.rt-DialogContent)`,
231
+ `${TEXT_SELECTOR_PLACEHOLDER}.rt-r-size-${i + 1}`,
198
232
  {
199
233
  capHeight: fontSize,
200
234
  lineGap,
@@ -268,26 +302,36 @@ async function generate(options) {
268
302
  lineGap: options.textStyles[2].lineHeight - options.textStyles[2].fontSize,
269
303
  fontMetrics: options.codingFontStack[0]
270
304
  });
271
- import_fs.default.writeFileSync(
272
- options.outputPath,
273
- import_mustache.default.render(template, {
274
- headingFontFamiliy,
275
- defaultFontFamily,
276
- codingFontFamily,
277
- mobileFontData,
278
- fontData,
279
- mobileCodeFontData,
280
- codeFontData,
281
- mobileTextStyles,
282
- textStyles,
283
- mobileEmStyles,
284
- emStyles,
285
- mobileQuoteStyles,
286
- quoteStyles,
287
- mobileCodeStyles,
288
- codeStyles
289
- })
305
+ const output = import_mustache.default.render(template, {
306
+ headingFontFamily,
307
+ defaultFontFamily,
308
+ codingFontFamily,
309
+ mobileFontData,
310
+ fontData,
311
+ mobileCodeFontData,
312
+ codeFontData,
313
+ mobileTextStyles,
314
+ textStyles,
315
+ mobileEmStyles,
316
+ emStyles,
317
+ mobileQuoteStyles,
318
+ quoteStyles,
319
+ mobileCodeStyles,
320
+ codeStyles
321
+ });
322
+ const placeholderRegex = new RegExp(`\\.${TEXT_SELECTOR_PLACEHOLDER}`, `g`);
323
+ const finalOutput = output.replace(
324
+ placeholderRegex,
325
+ TEXT_SELECTOR_REPLACEMENT
290
326
  );
327
+ try {
328
+ import_fs.default.writeFileSync(options.outputPath, finalOutput);
329
+ } catch (error) {
330
+ const message = error instanceof Error ? error.message : String(error);
331
+ throw new Error(
332
+ `[capsize-radix] Failed to write CSS to ${options.outputPath}: ${message}`
333
+ );
334
+ }
291
335
  }
292
336
  function capsizeRadixPlugin({
293
337
  outputPath,
package/dist/index.mjs CHANGED
@@ -6,11 +6,15 @@ import {
6
6
  createFontStack
7
7
  } from "@capsizecss/core";
8
8
  import Mustache from "mustache";
9
+ import path from "path";
9
10
  import segoeUI from "@capsizecss/metrics/segoeUI";
10
11
  import appleSystem from "@capsizecss/metrics/appleSystem";
11
12
  import roboto from "@capsizecss/metrics/roboto";
12
13
  import ubuntu from "@capsizecss/metrics/ubuntu";
13
14
  import notoSans from "@capsizecss/metrics/notoSans";
15
+ var TEXT_ELEMENTS = `.rt-Text, .rt-Heading, .rt-Em, .rt-Quote, .rt-Link`;
16
+ var TEXT_SELECTOR_PLACEHOLDER = `__CAPSIZE_TEXT__`;
17
+ var TEXT_SELECTOR_REPLACEMENT = `:where(${TEXT_ELEMENTS})`;
14
18
  var template = `/* Auto-generated by vite-plugin-capsize-radix */
15
19
 
16
20
  /* Override Radix variables */
@@ -79,7 +83,7 @@ var template = `/* Auto-generated by vite-plugin-capsize-radix */
79
83
  display: inline-block;
80
84
  }
81
85
 
82
- /* Class names for text elements */
86
+ /* Size variants for text elements only */
83
87
  {{#mobileFontData}}
84
88
  {{{style}}}
85
89
  {{/mobileFontData}}
@@ -89,9 +93,39 @@ var template = `/* Auto-generated by vite-plugin-capsize-radix */
89
93
  {{/fontData}}
90
94
  }
91
95
  `;
92
- async function generate(options) {
96
+ function validateOptions(options) {
97
+ if (!options.outputPath) {
98
+ throw new Error(`[capsize-radix] outputPath is required`);
99
+ }
100
+ if (options.textStyles.length < 3) {
101
+ throw new Error(
102
+ `[capsize-radix] textStyles must have at least 3 entries (found ${options.textStyles.length})`
103
+ );
104
+ }
105
+ for (let i = 0; i < options.textStyles.length; i++) {
106
+ const style = options.textStyles[i];
107
+ if (typeof style.fontSize !== `number` || style.fontSize <= 0) {
108
+ throw new Error(
109
+ `[capsize-radix] textStyles[${i}].fontSize must be a positive number`
110
+ );
111
+ }
112
+ if (typeof style.lineHeight !== `number` || style.lineHeight <= 0) {
113
+ throw new Error(
114
+ `[capsize-radix] textStyles[${i}].lineHeight must be a positive number`
115
+ );
116
+ }
117
+ }
118
+ const outputDir = path.dirname(options.outputPath);
119
+ if (!fs.existsSync(outputDir)) {
120
+ throw new Error(
121
+ `[capsize-radix] Output directory does not exist: ${outputDir}`
122
+ );
123
+ }
124
+ }
125
+ function generate(options) {
126
+ validateOptions(options);
93
127
  let defaultFontFamily;
94
- let headingFontFamiliy;
128
+ let headingFontFamily;
95
129
  let codingFontFamily;
96
130
  if (!options.defaultFontStack) {
97
131
  options.defaultFontStack = [
@@ -103,13 +137,13 @@ async function generate(options) {
103
137
  ];
104
138
  defaultFontFamily = createFontStack(options.defaultFontStack);
105
139
  defaultFontFamily.fontFamily += `, ui-sans-serif, system-ui, sans-serif`;
106
- } else if (options.defaultFontStack) {
140
+ } else {
107
141
  defaultFontFamily = createFontStack(options.defaultFontStack);
108
142
  }
109
143
  if (options.headingFontStack) {
110
- headingFontFamiliy = createFontStack(options.headingFontStack);
144
+ headingFontFamily = createFontStack(options.headingFontStack);
111
145
  } else {
112
- headingFontFamiliy = defaultFontFamily;
146
+ headingFontFamily = defaultFontFamily;
113
147
  }
114
148
  if (options.codingFontStack) {
115
149
  codingFontFamily = createFontStack(options.codingFontStack);
@@ -124,7 +158,7 @@ async function generate(options) {
124
158
  ].map(({ fontSize, lineHeight }, i) => {
125
159
  const lineGap = lineHeight - fontSize;
126
160
  const style = createStyleString(
127
- `rt-r-size-${i + 1}:not(.rt-DialogContent)`,
161
+ `${TEXT_SELECTOR_PLACEHOLDER}.rt-r-size-${i + 1}`,
128
162
  {
129
163
  capHeight: fontSize,
130
164
  lineGap,
@@ -164,7 +198,7 @@ async function generate(options) {
164
198
  const fontData = options.textStyles.map(({ fontSize, lineHeight }, i) => {
165
199
  const lineGap = lineHeight - fontSize;
166
200
  const style = createStyleString(
167
- `rt-r-size-${i + 1}:not(.rt-DialogContent)`,
201
+ `${TEXT_SELECTOR_PLACEHOLDER}.rt-r-size-${i + 1}`,
168
202
  {
169
203
  capHeight: fontSize,
170
204
  lineGap,
@@ -238,26 +272,36 @@ async function generate(options) {
238
272
  lineGap: options.textStyles[2].lineHeight - options.textStyles[2].fontSize,
239
273
  fontMetrics: options.codingFontStack[0]
240
274
  });
241
- fs.writeFileSync(
242
- options.outputPath,
243
- Mustache.render(template, {
244
- headingFontFamiliy,
245
- defaultFontFamily,
246
- codingFontFamily,
247
- mobileFontData,
248
- fontData,
249
- mobileCodeFontData,
250
- codeFontData,
251
- mobileTextStyles,
252
- textStyles,
253
- mobileEmStyles,
254
- emStyles,
255
- mobileQuoteStyles,
256
- quoteStyles,
257
- mobileCodeStyles,
258
- codeStyles
259
- })
275
+ const output = Mustache.render(template, {
276
+ headingFontFamily,
277
+ defaultFontFamily,
278
+ codingFontFamily,
279
+ mobileFontData,
280
+ fontData,
281
+ mobileCodeFontData,
282
+ codeFontData,
283
+ mobileTextStyles,
284
+ textStyles,
285
+ mobileEmStyles,
286
+ emStyles,
287
+ mobileQuoteStyles,
288
+ quoteStyles,
289
+ mobileCodeStyles,
290
+ codeStyles
291
+ });
292
+ const placeholderRegex = new RegExp(`\\.${TEXT_SELECTOR_PLACEHOLDER}`, `g`);
293
+ const finalOutput = output.replace(
294
+ placeholderRegex,
295
+ TEXT_SELECTOR_REPLACEMENT
260
296
  );
297
+ try {
298
+ fs.writeFileSync(options.outputPath, finalOutput);
299
+ } catch (error) {
300
+ const message = error instanceof Error ? error.message : String(error);
301
+ throw new Error(
302
+ `[capsize-radix] Failed to write CSS to ${options.outputPath}: ${message}`
303
+ );
304
+ }
261
305
  }
262
306
  function capsizeRadixPlugin({
263
307
  outputPath,
package/package.json CHANGED
@@ -1,31 +1,32 @@
1
1
  {
2
2
  "name": "vite-plugin-capsize-radix",
3
3
  "description": "Great Typography with Radix & Capsize",
4
- "version": "0.1.1",
4
+ "version": "0.1.3",
5
5
  "author": "Kyle Mathews <mathews.kyle@gmail.com>",
6
6
  "dependencies": {
7
- "@capsizecss/core": "^4.1.0",
8
- "@capsizecss/metrics": "^3.4.0",
7
+ "@capsizecss/core": "^4.1.3",
8
+ "@capsizecss/metrics": "^3.6.2",
9
9
  "mustache": "^4.2.0"
10
10
  },
11
11
  "devDependencies": {
12
- "@capsizecss/unpack": "^2.1.0",
13
- "@types/mustache": "^4.2.5",
12
+ "@capsizecss/unpack": "^2.4.0",
13
+ "@types/mustache": "^4.2.6",
14
+ "@types/node": "^25.0.3",
14
15
  "@typescript-eslint/eslint-plugin": "^7.4.0",
15
16
  "@typescript-eslint/parser": "^7.4.0",
16
17
  "eslint": "^8.57.0",
17
- "eslint-config-prettier": "^9.1.0",
18
+ "eslint-config-prettier": "^9.1.2",
18
19
  "eslint-config-react": "^1.1.7",
19
- "eslint-plugin-prettier": "^5.1.3",
20
- "eslint-plugin-react": "^7.34.1",
20
+ "eslint-plugin-prettier": "^5.5.4",
21
+ "eslint-plugin-react": "^7.37.5",
21
22
  "eslint-plugin-react-hooks": "^4.6.0",
22
- "eslint-plugin-react-refresh": "^0.4.6",
23
- "prettier": "^3.2.5",
23
+ "eslint-plugin-react-refresh": "^0.4.26",
24
+ "prettier": "^3.7.4",
24
25
  "shx": "^0.3.4",
25
- "tsup": "^8.0.2",
26
- "typescript": "^5.4.3",
27
- "vite": "^6.2.3",
28
- "vitest": "^3.0.9"
26
+ "tsup": "^8.5.1",
27
+ "typescript": "^5.9.3",
28
+ "vite": "^6.4.1",
29
+ "vitest": "^3.2.4"
29
30
  },
30
31
  "directories": {
31
32
  "example": "example"
@@ -53,13 +54,6 @@
53
54
  "license": "MIT",
54
55
  "main": "./dist/index.js",
55
56
  "module": "./dist/index.mjs",
56
- "scripts": {
57
- "build": "pnpm run clean && tsup --external @capsizecss/core --external mustache --external @capsizecss/metrics",
58
- "check": "tsc",
59
- "clean": "shx rm -rf dist *.d.ts",
60
- "prepublishOnly": "pnpm run build",
61
- "test": "echo \"Error: no test specified\" && exit 1"
62
- },
63
57
  "tsup": {
64
58
  "entry": [
65
59
  "src/index.ts"
@@ -70,5 +64,14 @@
70
64
  ],
71
65
  "dts": true
72
66
  },
73
- "types": "./dist/index.d.ts"
74
- }
67
+ "types": "./dist/index.d.ts",
68
+ "scripts": {
69
+ "build": "pnpm run clean && tsup --external @capsizecss/core --external mustache --external @capsizecss/metrics",
70
+ "check": "tsc",
71
+ "clean": "shx rm -rf dist *.d.ts",
72
+ "lint": "eslint src tests --ext .ts,.tsx",
73
+ "format": "prettier --write .",
74
+ "format:check": "prettier --check .",
75
+ "test": "vitest run"
76
+ }
77
+ }