vite-plugin-capsize-radix 0.0.1
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/LICENSE +21 -0
- package/README.md +86 -0
- package/package.json +53 -0
- package/plugin.ts +224 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Kyle Mathews
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# vite-plugin-capsize-radix
|
|
2
|
+
|
|
3
|
+
Generate bulletproof typography css for [@radix-ui/themes](https://www.radix-ui.com/)
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Getting custom fonts to look right is hard on the web — so most peoply
|
|
8
|
+
rely on a few typography frameworks and fonts. But it doesn't have to be that
|
|
9
|
+
way. [Capsize](https://seek-oss.github.io/capsize/) fixes text sizing and layout
|
|
10
|
+
so changing fonts is as easy as changing colors. Your fonts should be as distinctive
|
|
11
|
+
as the rest of your application.
|
|
12
|
+
|
|
13
|
+
https://github.com/KyleAMathews/vite-plugin-capsize-radix-ui/assets/71047/3ec5d6ca-bf00-4b79-8552-4e3da3454f52
|
|
14
|
+
|
|
15
|
+
Capsize makes fonts render the way you expect them to. With Capsize, if you
|
|
16
|
+
want a font to have 16px height, it _actually_ will.
|
|
17
|
+
|
|
18
|
+
This plugin glues Capsize with the fantastic Radix theming components.
|
|
19
|
+
|
|
20
|
+
Similar plugins could be built for other meta-frameworks & component libraries — open an issue
|
|
21
|
+
if you're interested on collaborating on one.
|
|
22
|
+
|
|
23
|
+
## How to use
|
|
24
|
+
|
|
25
|
+
### Install
|
|
26
|
+
|
|
27
|
+
`npm install vite-plugin-capsize-radix`
|
|
28
|
+
|
|
29
|
+
### Add to vite.config.ts
|
|
30
|
+
|
|
31
|
+
A sample React config:
|
|
32
|
+
```ts
|
|
33
|
+
import { defineConfig } from "vite"
|
|
34
|
+
import react from "@vitejs/plugin-react-swc"
|
|
35
|
+
import { capsizeRadixPlugin } from "vite-plugin-capsize-radix"
|
|
36
|
+
import merriweather from "@capsizecss/metrics/merriweather"
|
|
37
|
+
import merriweatherSans from "@capsizecss/metrics/merriweatherSans"
|
|
38
|
+
import arial from "@capsizecss/metrics/arial"
|
|
39
|
+
|
|
40
|
+
export default defineConfig({
|
|
41
|
+
plugins: [
|
|
42
|
+
react(),
|
|
43
|
+
capsizeRadixPlugin({
|
|
44
|
+
// Import this file into your app.
|
|
45
|
+
outputPath: `./public/merriweather.css`,
|
|
46
|
+
// Pass in Capsize font metric objects for generating the right CSS.
|
|
47
|
+
defaultFontStack: [merriweather, arial],
|
|
48
|
+
headingFontStack: [merriweatherSans, arial],
|
|
49
|
+
}),
|
|
50
|
+
]
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Using with Radix UI
|
|
55
|
+
This plugin overrides all typography related CSS for Radix so you can simply
|
|
56
|
+
use their typography components as normal (e.g. `<Heading>`, `<Text>`, `<Strong>`, etc)
|
|
57
|
+
|
|
58
|
+
See e.g. the [documentation for `<Text>`](https://www.radix-ui.com/themes/docs/components/text).
|
|
59
|
+
|
|
60
|
+
They all share a `size` prop from "1" to "10". This corresponds to the optional
|
|
61
|
+
`fontSizes` array passed to the plugin e.g. `size="0"` is the first value of
|
|
62
|
+
the array, etc. `<Text>` defaults to `size="2"` and `<Heading>` defaults to
|
|
63
|
+
`size="6"`.
|
|
64
|
+
|
|
65
|
+
### Fallback fonts
|
|
66
|
+
|
|
67
|
+
Another great freebie Capsize gives you is it automatically generates CSS for
|
|
68
|
+
aligning your fallback font with your main font, which can dramatically improve
|
|
69
|
+
the [Cumulative Layout Shift](https://web.dev/cls/) metric for sites that depend on a web font
|
|
70
|
+
|
|
71
|
+
### Custom fonts
|
|
72
|
+
|
|
73
|
+
Capsize has precalculated metrics for system and many open source fonts. If you're
|
|
74
|
+
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.
|
|
75
|
+
|
|
76
|
+
## Responsive styles
|
|
77
|
+
This plugin automatically generates responsive styles. Typically you want your mobile font-size
|
|
78
|
+
to be slightly smaller than on desktop. We do that by shifting the text a size smaller on mobile.
|
|
79
|
+
|
|
80
|
+
## Parameters:
|
|
81
|
+
|
|
82
|
+
* __outputPath (string): Required__: The file path where the generated CSS or design tokens should be saved.
|
|
83
|
+
* __fontSizes (number[]): Optional__: An array of numerical font size values (presumably in pixels). Defaults to [9, 11, 12, 14, 18, 24, 36, 48, 64].
|
|
84
|
+
* __lineHeights (number[]): Optional__: An array of numerical line height values. Defaults to [21, 24, 26, 27, 29, 36, 44, 52, 64].
|
|
85
|
+
* __defaultFontStack (FontMetrics[])__: Optional. An array of `FontMetrics` objects. Defaults to a System Font stack.
|
|
86
|
+
* __headingFontStack (FontMetrics[])__: Optional. An array of `FontMetrics` objects. Defaults to your `defaultFontStack`.
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-plugin-capsize-radix",
|
|
3
|
+
"description": "Great Typography with Radix & Capsize",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"author": "Kyle Mathews <mathews.kyle@gmail.com>",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@capsizecss/core": "^4.1.0",
|
|
8
|
+
"@capsizecss/metrics": "^2.2.0",
|
|
9
|
+
"@types/mustache": "^4.2.5",
|
|
10
|
+
"mustache": "^4.2.0"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@capsizecss/unpack": "^2.1.0",
|
|
14
|
+
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
|
15
|
+
"@typescript-eslint/parser": "^7.4.0",
|
|
16
|
+
"eslint": "^8.57.0",
|
|
17
|
+
"eslint-config-prettier": "^9.1.0",
|
|
18
|
+
"eslint-config-react": "^1.1.7",
|
|
19
|
+
"eslint-plugin-prettier": "^5.1.3",
|
|
20
|
+
"eslint-plugin-react": "^7.34.1",
|
|
21
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
22
|
+
"eslint-plugin-react-refresh": "^0.4.6",
|
|
23
|
+
"prettier": "^3.2.5",
|
|
24
|
+
"typescript": "^5.4.3",
|
|
25
|
+
"vite": "^5.2.6",
|
|
26
|
+
"vitest": "^1.4.0"
|
|
27
|
+
},
|
|
28
|
+
"directories": {
|
|
29
|
+
"example": "example"
|
|
30
|
+
},
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./plugin.ts",
|
|
34
|
+
"import": "./plugin.ts"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"plugin.ts"
|
|
39
|
+
],
|
|
40
|
+
"keywords": [
|
|
41
|
+
"capsizecss",
|
|
42
|
+
"radix-ui",
|
|
43
|
+
"typography",
|
|
44
|
+
"vite-plugin"
|
|
45
|
+
],
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"main": "index.js",
|
|
48
|
+
"scripts": {
|
|
49
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
50
|
+
},
|
|
51
|
+
"type": "module",
|
|
52
|
+
"types": "./plugin.ts"
|
|
53
|
+
}
|
package/plugin.ts
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { Plugin } from "vite"
|
|
2
|
+
import fs from "fs"
|
|
3
|
+
import {
|
|
4
|
+
createStyleObject,
|
|
5
|
+
createStyleString,
|
|
6
|
+
createFontStack,
|
|
7
|
+
FontMetrics,
|
|
8
|
+
} from "@capsizecss/core"
|
|
9
|
+
import Mustache from "mustache"
|
|
10
|
+
|
|
11
|
+
// Metrics for system fonts
|
|
12
|
+
import segoeUI from "@capsizecss/metrics/segoeUI"
|
|
13
|
+
import appleSystem from "@capsizecss/metrics/appleSystem"
|
|
14
|
+
import roboto from "@capsizecss/metrics/roboto"
|
|
15
|
+
import ubuntu from "@capsizecss/metrics/ubuntu"
|
|
16
|
+
import cantarell from "@capsizecss/metrics/cantarell"
|
|
17
|
+
import notoSans from "@capsizecss/metrics/notoSans"
|
|
18
|
+
|
|
19
|
+
const template = `/* Auto-generated by scripts/generate-typography-styles.ts */
|
|
20
|
+
|
|
21
|
+
/* Override Radix variables */
|
|
22
|
+
.radix-themes {
|
|
23
|
+
--default-font-family: {{{defaultFontStack.fontFamily}}};
|
|
24
|
+
--em-font-family: {{{defaultFontStack.fontFamily}}};
|
|
25
|
+
--quote-font-family: {{{defaultFontStack.fontFamily}}};
|
|
26
|
+
--heading-font-family: {{{headingFontStack.fontFamily}}};
|
|
27
|
+
|
|
28
|
+
/* Mobile */
|
|
29
|
+
{{#mobileFontData}}
|
|
30
|
+
--font-size-{{{i}}}: {{{fontSize}}};
|
|
31
|
+
--line-height-{{{i}}}: {{{lineHeight}}};
|
|
32
|
+
{{/mobileFontData}}
|
|
33
|
+
|
|
34
|
+
/* Larger devices */
|
|
35
|
+
@media (min-width: 768px) {
|
|
36
|
+
{{#fontData}}
|
|
37
|
+
--font-size-{{{i}}}: {{{fontSize}}};
|
|
38
|
+
--line-height-{{{i}}}: {{{lineHeight}}};
|
|
39
|
+
{{/fontData}}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Otherwise links don't flow inline */
|
|
44
|
+
.rt-Link {
|
|
45
|
+
display: inline-block;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Default text styles */
|
|
49
|
+
{{{mobileTextStyles}}}
|
|
50
|
+
@media (min-width: 768px) {
|
|
51
|
+
{{{textStyles}}}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Em text styles */
|
|
55
|
+
{{{mobileEmStyles}}}
|
|
56
|
+
@media (min-width: 768px) {
|
|
57
|
+
{{{emStyles}}}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Quote text styles */
|
|
61
|
+
{{{mobileQuoteStyles}}}
|
|
62
|
+
@media (min-width: 768px) {
|
|
63
|
+
{{{quoteStyles}}}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.rt-Em, .rt-Quote {
|
|
67
|
+
display: inline-block;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Class names for text elements */
|
|
71
|
+
{{#mobileFontData}}
|
|
72
|
+
{{{style}}}
|
|
73
|
+
{{/mobileFontData}}
|
|
74
|
+
@media (min-width: 768px) {
|
|
75
|
+
{{#fontData}}
|
|
76
|
+
{{{style}}}
|
|
77
|
+
{{/fontData}}
|
|
78
|
+
}
|
|
79
|
+
`
|
|
80
|
+
|
|
81
|
+
async function generate(options) {
|
|
82
|
+
let defaultFontStack
|
|
83
|
+
let headingFontStack
|
|
84
|
+
if (options.defaultFontStack) {
|
|
85
|
+
defaultFontStack = createFontStack(options.defaultFontStack)
|
|
86
|
+
} else {
|
|
87
|
+
options.defaultFontStack = [
|
|
88
|
+
appleSystem,
|
|
89
|
+
segoeUI,
|
|
90
|
+
roboto,
|
|
91
|
+
ubuntu,
|
|
92
|
+
cantarell,
|
|
93
|
+
notoSans,
|
|
94
|
+
]
|
|
95
|
+
defaultFontStack = createFontStack(options.defaultFontStack)
|
|
96
|
+
|
|
97
|
+
// We prefer to determanistically pick the font w/ the stack above so then the font trimmings work
|
|
98
|
+
// but we if we can't, we'll fall back to these.
|
|
99
|
+
defaultFontStack.fontFamily += `, ui-sans-serif, system-ui, sans-serif`
|
|
100
|
+
}
|
|
101
|
+
if (options.headingFontStack) {
|
|
102
|
+
headingFontStack = createFontStack(options.headingFontStack)
|
|
103
|
+
} else {
|
|
104
|
+
headingFontStack = defaultFontStack
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const mobileFontData = [options.fontSizes[0], ...options.fontSizes].map(
|
|
108
|
+
(fontSize, i) => {
|
|
109
|
+
const lineGap = options.lineHeights[i - 1] - fontSize
|
|
110
|
+
const style = createStyleString(
|
|
111
|
+
`rt-r-size-${i + 1}:not(.rt-DialogContent)`,
|
|
112
|
+
{
|
|
113
|
+
capHeight: fontSize,
|
|
114
|
+
lineGap,
|
|
115
|
+
fontMetrics: options.defaultFontStack[0],
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
return {
|
|
119
|
+
style,
|
|
120
|
+
...createStyleObject({
|
|
121
|
+
capHeight: fontSize,
|
|
122
|
+
lineGap,
|
|
123
|
+
fontMetrics: options.defaultFontStack[0],
|
|
124
|
+
}),
|
|
125
|
+
i: i + 1,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
const fontData = options.fontSizes.map((fontSize, i) => {
|
|
130
|
+
const lineGap = options.lineHeights[i] - fontSize
|
|
131
|
+
const style = createStyleString(
|
|
132
|
+
`rt-r-size-${i + 1}:not(.rt-DialogContent)`,
|
|
133
|
+
{
|
|
134
|
+
capHeight: fontSize,
|
|
135
|
+
lineGap,
|
|
136
|
+
fontMetrics: options.defaultFontStack[0],
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
return {
|
|
140
|
+
style,
|
|
141
|
+
...createStyleObject({
|
|
142
|
+
capHeight: fontSize,
|
|
143
|
+
lineGap,
|
|
144
|
+
fontMetrics: options.defaultFontStack[0],
|
|
145
|
+
}),
|
|
146
|
+
i: i + 1,
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const mobileTextStyles = createStyleString(`rt-Text`, {
|
|
151
|
+
capHeight: options.fontSizes[1],
|
|
152
|
+
lineGap: options.lineHeights[1] - options.fontSizes[1],
|
|
153
|
+
fontMetrics: options.defaultFontStack[0],
|
|
154
|
+
})
|
|
155
|
+
const mobileEmStyles = createStyleString(`rt-Em`, {
|
|
156
|
+
capHeight: options.fontSizes[1],
|
|
157
|
+
lineGap: options.lineHeights[1] - options.fontSizes[1],
|
|
158
|
+
fontMetrics: options.defaultFontStack[0],
|
|
159
|
+
})
|
|
160
|
+
const mobileQuoteStyles = createStyleString(`rt-Quote`, {
|
|
161
|
+
capHeight: options.fontSizes[1],
|
|
162
|
+
lineGap: options.lineHeights[1] - options.fontSizes[1],
|
|
163
|
+
fontMetrics: options.defaultFontStack[0],
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
const textStyles = createStyleString(`rt-Text`, {
|
|
167
|
+
capHeight: options.fontSizes[2],
|
|
168
|
+
lineGap: options.lineHeights[2] - options.fontSizes[2],
|
|
169
|
+
fontMetrics: options.defaultFontStack[0],
|
|
170
|
+
})
|
|
171
|
+
const emStyles = createStyleString(`rt-Em`, {
|
|
172
|
+
capHeight: options.fontSizes[2],
|
|
173
|
+
lineGap: options.lineHeights[2] - options.fontSizes[2],
|
|
174
|
+
fontMetrics: options.defaultFontStack[0],
|
|
175
|
+
})
|
|
176
|
+
const quoteStyles = createStyleString(`rt-Quote`, {
|
|
177
|
+
capHeight: options.fontSizes[2],
|
|
178
|
+
lineGap: options.lineHeights[2] - options.fontSizes[2],
|
|
179
|
+
fontMetrics: options.defaultFontStack[0],
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
fs.writeFileSync(
|
|
183
|
+
options.outputPath,
|
|
184
|
+
Mustache.render(template, {
|
|
185
|
+
headingFontStack,
|
|
186
|
+
defaultFontStack,
|
|
187
|
+
mobileFontData,
|
|
188
|
+
fontData,
|
|
189
|
+
mobileTextStyles,
|
|
190
|
+
textStyles,
|
|
191
|
+
mobileEmStyles,
|
|
192
|
+
emStyles,
|
|
193
|
+
mobileQuoteStyles,
|
|
194
|
+
quoteStyles,
|
|
195
|
+
})
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function radixCapsizePlugin({
|
|
200
|
+
outputPath,
|
|
201
|
+
fontSizes = [9, 11, 12, 14, 18, 24, 36, 48, 64],
|
|
202
|
+
lineHeights = [21, 24, 26, 27, 29, 36, 44, 52, 64],
|
|
203
|
+
defaultFontStack,
|
|
204
|
+
headingFontStack,
|
|
205
|
+
}: {
|
|
206
|
+
outputPath: string
|
|
207
|
+
fontSizes?: number[]
|
|
208
|
+
lineHeights?: number[]
|
|
209
|
+
defaultFontStack?: FontMetrics[]
|
|
210
|
+
headingFontStack?: FontMetrics[]
|
|
211
|
+
}): Plugin {
|
|
212
|
+
return {
|
|
213
|
+
name: `radix-capsize-plugin`,
|
|
214
|
+
buildStart() {
|
|
215
|
+
generate({
|
|
216
|
+
outputPath,
|
|
217
|
+
fontSizes,
|
|
218
|
+
lineHeights,
|
|
219
|
+
defaultFontStack,
|
|
220
|
+
headingFontStack,
|
|
221
|
+
})
|
|
222
|
+
},
|
|
223
|
+
}
|
|
224
|
+
}
|