@driftime/sanity-plugin-font 0.0.0-stage → 0.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 +459 -2
- package/dist/catalog-BVw1r_SG.js +183 -0
- package/dist/catalog-BVw1r_SG.js.map +1 -0
- package/dist/catalog-KAqAIjzw.js +96 -0
- package/dist/catalog-KAqAIjzw.js.map +1 -0
- package/dist/catalog.d.ts +29 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +10 -0
- package/dist/catalog.js.map +1 -0
- package/dist/family-BAGnEjZg.js +36 -0
- package/dist/family-BAGnEjZg.js.map +1 -0
- package/dist/font-styles.d.ts +21 -0
- package/dist/font-styles.d.ts.map +1 -0
- package/dist/font-styles.js +43 -0
- package/dist/font-styles.js.map +1 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2364 -0
- package/dist/index.js.map +1 -0
- package/dist/names.d.ts +8 -0
- package/dist/names.d.ts.map +1 -0
- package/dist/names.js +0 -0
- package/dist/next-loader.d.ts +19 -0
- package/dist/next-loader.d.ts.map +1 -0
- package/dist/next-loader.js +28 -0
- package/dist/next-loader.js.map +1 -0
- package/dist/next.d.ts +42 -0
- package/dist/next.d.ts.map +1 -0
- package/dist/next.js +48 -0
- package/dist/next.js.map +1 -0
- package/dist/render.d.ts +12 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +183 -0
- package/dist/render.js.map +1 -0
- package/dist/resolve-Bkp0dEmr.d.ts +81 -0
- package/dist/resolve-Bkp0dEmr.d.ts.map +1 -0
- package/dist/types-DZl90ROd.d.ts +106 -0
- package/dist/types-DZl90ROd.d.ts.map +1 -0
- package/dist/types-DheP5tax.js +27 -0
- package/dist/types-DheP5tax.js.map +1 -0
- package/dist/utils-BV38iODp.js +29 -0
- package/dist/utils-BV38iODp.js.map +1 -0
- package/dist/vite.d.ts +61 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +53 -0
- package/dist/vite.js.map +1 -0
- package/package.json +120 -4
package/README.md
CHANGED
|
@@ -1,3 +1,460 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/font/assets/icon-dark.svg" />
|
|
4
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/font/assets/icon-light.svg" />
|
|
5
|
+
<img src="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/font/assets/icon-light.svg" alt="Font plugin logo" width="48" />
|
|
6
|
+
</picture>
|
|
7
|
+
<h1>Font</h1>
|
|
8
|
+
<p><strong>A Sanity Studio plugin by Driftime®</strong></p>
|
|
9
|
+
<p>Fonts for Sanity Studio, from the Google Fonts catalog or a brand's own files.</p>
|
|
10
|
+
<p>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@driftime/sanity-plugin-font"><img src="https://img.shields.io/npm/v/@driftime/sanity-plugin-font?style=flat-square&labelColor=1a1a1a&color=666666" alt="npm version" /></a>
|
|
12
|
+
<a href="https://github.com/driftime/sanity-plugins/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@driftime/sanity-plugin-font?style=flat-square&labelColor=1a1a1a&color=666666" alt="License: MIT" /></a>
|
|
13
|
+
</p>
|
|
14
|
+
</div>
|
|
2
15
|
|
|
3
|
-
|
|
16
|
+
<br />
|
|
17
|
+
|
|
18
|
+
## Overview
|
|
19
|
+
|
|
20
|
+
Font brings typography into the content model. Authors can browse the whole Google Fonts catalog, with every family shown in its own typeface, and choose one as they would any other value. When a brand has its own typeface, they upload the files instead, and the plugin reads the weight and style from each one, so there's nothing to fill in by hand.
|
|
21
|
+
|
|
22
|
+
The design still has the final say. A field can require the weights, styles, and languages the site depends on, and only fonts that have all of them are offered, so whatever an author picks, the headings still get a real bold.
|
|
23
|
+
|
|
24
|
+
On the site, a font chosen in Sanity is used much like one from `next/font`, whether it came from Google or an upload, and whether it's set for the whole site, a single page, or one block.
|
|
25
|
+
|
|
26
|
+
<br />
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
Font is built for Sanity Studio 6.10 and React 19 and declares both as peer dependencies, so the Studio needs to be on those versions already. Node 20.19 or later is required, or 22.12 or later on Node 22.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bun add -E @driftime/sanity-plugin-font
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
<br />
|
|
37
|
+
|
|
38
|
+
## Studio
|
|
39
|
+
|
|
40
|
+
In the Studio, the plugin registers a `font` type, and a step added to the Studio's build brings in the Google Fonts catalog.
|
|
41
|
+
|
|
42
|
+
<br />
|
|
43
|
+
|
|
44
|
+
### Adding the Build Step
|
|
45
|
+
|
|
46
|
+
The build step brings Google's list of font families into the Studio as it builds. It's added to the configuration of whichever bundler builds the Studio.
|
|
47
|
+
|
|
48
|
+
<br />
|
|
49
|
+
|
|
50
|
+
#### Standalone Studio
|
|
51
|
+
|
|
52
|
+
A standalone Studio is built with Vite, and `withFonts` extends the `vite` option in its `sanity.cli.ts`.
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { withFonts } from "@driftime/sanity-plugin-font/vite";
|
|
56
|
+
import { defineCliConfig } from "sanity/cli";
|
|
57
|
+
|
|
58
|
+
export default defineCliConfig({
|
|
59
|
+
// ...
|
|
60
|
+
vite: withFonts(),
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
<br />
|
|
65
|
+
|
|
66
|
+
#### Next.js
|
|
67
|
+
|
|
68
|
+
For a Studio embedded in a Next.js app, `withFonts` wraps the app's configuration in `next.config.ts` and works with both Turbopack and webpack.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { withFonts } from "@driftime/sanity-plugin-font/next";
|
|
72
|
+
import type { NextConfig } from "next";
|
|
73
|
+
|
|
74
|
+
const config: NextConfig = {
|
|
75
|
+
// ...
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export default withFonts()(config);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`withFonts` returns an async configuration function rather than an object, so when other plugins also wrap the configuration, it needs to be applied last.
|
|
82
|
+
|
|
83
|
+
<br />
|
|
84
|
+
|
|
85
|
+
#### Other Vite Frameworks
|
|
86
|
+
|
|
87
|
+
A Studio embedded in another Vite-based framework, such as React Router, TanStack Start, or Astro, adds `googleFonts` to that framework's Vite plugins.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { googleFonts } from "@driftime/sanity-plugin-font/vite";
|
|
91
|
+
|
|
92
|
+
export default defineConfig({
|
|
93
|
+
// ...
|
|
94
|
+
plugins: [
|
|
95
|
+
// ...
|
|
96
|
+
googleFonts(),
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
<br />
|
|
102
|
+
|
|
103
|
+
### Registering the Plugin
|
|
104
|
+
|
|
105
|
+
The plugin works without any configuration, and the options it's given apply to every font field that doesn't set its own.
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { fontPlugin } from "@driftime/sanity-plugin-font";
|
|
109
|
+
import { defineConfig } from "sanity";
|
|
110
|
+
|
|
111
|
+
export default defineConfig({
|
|
112
|
+
// ...
|
|
113
|
+
plugins: [
|
|
114
|
+
// ...
|
|
115
|
+
fontPlugin(),
|
|
116
|
+
],
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
| Option | Type | Default | Purpose |
|
|
121
|
+
| ------------ | ---------------------- | ---------------------- | --------------------------------------------------------------------------------------------------- |
|
|
122
|
+
| `sources` | `SanityFontSource[]` | `["google", "upload"]` | Where fonts come from. See [Choosing the Sources](#choosing-the-sources). |
|
|
123
|
+
| `categories` | `SanityFontCategory[]` | every kind | Kinds of typeface a font can be. See [Restricting the Fonts](#restricting-the-fonts). |
|
|
124
|
+
| `families` | `SanityFontFamily[]` | every family | Google families on offer, in the order listed. See [Restricting the Fonts](#restricting-the-fonts). |
|
|
125
|
+
| `weights` | `SanityFontWeight[]` | `[400, 600]` | Weights a font must have. See [Setting the Requirements](#setting-the-requirements). |
|
|
126
|
+
| `styles` | `SanityFontStyle[]` | `["normal"]` | Styles a font must have in every weight. See [Setting the Requirements](#setting-the-requirements). |
|
|
127
|
+
| `subsets` | `string[]` | `["latin"]` | Character sets a Google font must cover. See [Setting the Requirements](#setting-the-requirements). |
|
|
128
|
+
| `preview` | `SanityFontPreview` | `"Aa"` | Text used to preview a chosen font. See [Previewing the Font](#previewing-the-font). |
|
|
129
|
+
|
|
130
|
+
<br />
|
|
131
|
+
|
|
132
|
+
### Adding a Font Field
|
|
133
|
+
|
|
134
|
+
A field of type `font` is defined like any other, and takes the same options as the plugin. Each option a field sets replaces the plugin's.
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
defineField({
|
|
138
|
+
name: "headingFont",
|
|
139
|
+
type: "font",
|
|
140
|
+
description: "Typeface for headings.",
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The plugin registers types named `font` and `fontFile`, so a Studio that already has a type by either name needs to rename it before installing.
|
|
145
|
+
|
|
146
|
+
<br />
|
|
147
|
+
|
|
148
|
+
### Setting the Requirements
|
|
149
|
+
|
|
150
|
+
A design is usually set in a handful of weights and styles, and every font an author picks needs to have them. A field lists them, and the picker only offers Google families that have every one, in every character set listed in `subsets`.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
defineField({
|
|
154
|
+
name: "bodyFont",
|
|
155
|
+
type: "font",
|
|
156
|
+
description: "Typeface for body text.",
|
|
157
|
+
options: { weights: [400, 700], styles: ["normal", "italic"], subsets: ["latin", "latin-ext"] },
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Every weight is required in every style, so this field asks for regular, italic, bold, and bold italic. Subsets take Google's own names, such as `cyrillic` or `greek`. Uploaded fonts are held to the same requirements.
|
|
162
|
+
|
|
163
|
+
<br />
|
|
164
|
+
|
|
165
|
+
### Restricting the Fonts
|
|
166
|
+
|
|
167
|
+
`categories` limits the kinds of typeface on offer, and `families` names the exact Google families a field offers, in the order they're listed.
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
defineField({
|
|
171
|
+
name: "headingFont",
|
|
172
|
+
type: "font",
|
|
173
|
+
description: "Typeface for headings.",
|
|
174
|
+
options: { categories: ["serif"], families: ["Playfair Display", "Lora", "Fraunces"] },
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Family names are checked against the Google Fonts catalog once the build step has run, so the editor autocompletes them and a misspelling is a type error. A list shared between fields is typed with `SanityFontFamily`.
|
|
179
|
+
|
|
180
|
+
<br />
|
|
181
|
+
|
|
182
|
+
### Choosing the Sources
|
|
183
|
+
|
|
184
|
+
A field offers Google Fonts and uploads, each in its own tab. Either can be left out, for example for a logo that's always set in the brand's own typeface.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
defineField({
|
|
188
|
+
name: "brandFont",
|
|
189
|
+
type: "font",
|
|
190
|
+
description: "Typeface for the logo.",
|
|
191
|
+
options: { sources: ["upload"] },
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
<br />
|
|
196
|
+
|
|
197
|
+
### Uploading Fonts
|
|
198
|
+
|
|
199
|
+
An uploaded font is a set of WOFF2 or WOFF files, either one for each weight and style or a single variable font. Authors just add the files, and the plugin works out what each one holds.
|
|
200
|
+
|
|
201
|
+
Uploaded files are served from the Sanity CDN, which only sends fonts to the origins in the project's CORS settings. **Every domain the site runs on needs to be added to the project's CORS origins** at [sanity.io/manage](https://www.sanity.io/manage), or the site shows the fallback instead. Uploaded files are public, like every Sanity asset, so the font's license is worth checking before it's uploaded.
|
|
202
|
+
|
|
203
|
+
<br />
|
|
204
|
+
|
|
205
|
+
### Previewing the Font
|
|
206
|
+
|
|
207
|
+
Once a font is chosen, the field shows "Aa" in each of its weights and styles, and `preview.text` replaces it with the site's own text.
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
defineField({
|
|
211
|
+
name: "headingFont",
|
|
212
|
+
type: "font",
|
|
213
|
+
description: "Typeface for headings.",
|
|
214
|
+
options: { preview: { text: "Welcome to Acme Inc." } },
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
<br />
|
|
219
|
+
|
|
220
|
+
### Document Previews
|
|
221
|
+
|
|
222
|
+
`createFontPreview` shows a chosen font in a document's preview, in place of the icon its type was given in the schema. It's called in `prepare` with the stored value.
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import { createFontPreview } from "@driftime/sanity-plugin-font";
|
|
226
|
+
|
|
227
|
+
defineType({
|
|
228
|
+
// ...
|
|
229
|
+
preview: {
|
|
230
|
+
select: { title: "title", font: "headingFont" },
|
|
231
|
+
prepare({ title, font }) {
|
|
232
|
+
return { title, media: createFontPreview(font) };
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
When no font is stored, it returns `undefined` and the preview keeps the type's icon.
|
|
239
|
+
|
|
240
|
+
<br />
|
|
241
|
+
|
|
242
|
+
## Site
|
|
243
|
+
|
|
244
|
+
On the site, a font from Sanity is used much like one from `next/font`. `resolveFont` turns a stored font into classes and a style, `FontStyles` adds the rules behind them to the page, and `fontFragment` expands an uploaded font's files in a query. All three come from `@driftime/sanity-plugin-font/render`, which contains no Studio code.
|
|
245
|
+
|
|
246
|
+
<br />
|
|
247
|
+
|
|
248
|
+
### Querying
|
|
249
|
+
|
|
250
|
+
An uploaded font's files are references to assets, so a query needs to expand them. `fontFragment` does that, and goes in the font field's projection wherever the field is.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
import { fontFragment } from "@driftime/sanity-plugin-font/render";
|
|
254
|
+
|
|
255
|
+
const settingsQuery = `*[_type == "settings"][0] {
|
|
256
|
+
headingFont { ${fontFragment} }
|
|
257
|
+
}`;
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
It leaves a Google font unchanged, so it's safe to use on every font field.
|
|
261
|
+
|
|
262
|
+
<br />
|
|
263
|
+
|
|
264
|
+
### Resolving Fonts
|
|
265
|
+
|
|
266
|
+
`resolveFont` takes a stored font and returns the same three things a `next/font` font does: a `className` that sets it, a `style` that does the same inline, and, when it's given a CSS variable to define, a `variable` class.
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
import { resolveFont } from "@driftime/sanity-plugin-font/render";
|
|
270
|
+
|
|
271
|
+
const heading = resolveFont(settings.headingFont, { variable: "--font-heading" });
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
| Option | Type | Default | Purpose |
|
|
275
|
+
| ---------- | ------------------- | ------------------------ | ---------------------------------------------------------------------------- |
|
|
276
|
+
| `display` | `SanityFontDisplay` | `"swap"` | How the font is shown while it loads, as CSS's `font-display` names it. |
|
|
277
|
+
| `preload` | `boolean` | `true` | Whether the page hints that the font's files should start downloading early. |
|
|
278
|
+
| `fallback` | `string[]` | one for the kind of font | Fonts shown in its place while it loads, such as `["Geist", "sans-serif"]`. |
|
|
279
|
+
| `variable` | `string` | none | The CSS variable the `variable` class defines, such as `--font-heading`. |
|
|
280
|
+
|
|
281
|
+
| Value | Type | Purpose |
|
|
282
|
+
| ----------- | ------------------------ | ---------------------------------------------------------- |
|
|
283
|
+
| `className` | `string` | A class that sets the font. |
|
|
284
|
+
| `style` | `{ fontFamily: string }` | An inline style that sets the font. |
|
|
285
|
+
| `variable` | `string \| undefined` | A class that defines the CSS variable, when one was named. |
|
|
286
|
+
|
|
287
|
+
It returns `undefined` when no font is stored.
|
|
288
|
+
|
|
289
|
+
<br />
|
|
290
|
+
|
|
291
|
+
### Adding the Styles
|
|
292
|
+
|
|
293
|
+
`next/font` writes its rules at build time, but a font from Sanity isn't known until the page renders, so `FontStyles` adds its rules then. It takes one resolved font or several.
|
|
294
|
+
|
|
295
|
+
```tsx
|
|
296
|
+
<FontStyles font={heading} />
|
|
297
|
+
<FontStyles font={[heading, body]} />
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
It can go in a layout, a page, or the component that uses the font, because React 19 moves its links and styles into the document's head. A font added twice is only added once.
|
|
301
|
+
|
|
302
|
+
<br />
|
|
303
|
+
|
|
304
|
+
### Applying Fonts
|
|
305
|
+
|
|
306
|
+
A resolved font is applied with its classes. The `variable` class defines the variable for the element it's on and everything inside it, so on the root element it covers the whole site, and the site's own CSS can use it with a default for when no font is chosen.
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
import { FontStyles, resolveFont } from "@driftime/sanity-plugin-font/render";
|
|
310
|
+
|
|
311
|
+
export default async function Layout({ children }: LayoutProps) {
|
|
312
|
+
const settings = await getSettings();
|
|
313
|
+
const heading = resolveFont(settings.headingFont, { variable: "--font-heading" });
|
|
314
|
+
|
|
315
|
+
return (
|
|
316
|
+
<html lang="en" className={heading?.variable}>
|
|
317
|
+
<body>
|
|
318
|
+
<FontStyles font={heading} />
|
|
319
|
+
{children}
|
|
320
|
+
</body>
|
|
321
|
+
</html>
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
```css
|
|
327
|
+
h1,
|
|
328
|
+
h2 {
|
|
329
|
+
font-family: var(--font-heading, ui-serif, serif);
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
A font stored on a page or a block goes on the element that should use it, through its `variable` class or simply its `className`, so it only affects that part of the page.
|
|
334
|
+
|
|
335
|
+
```tsx
|
|
336
|
+
export function Hero({ font, heading }: HeroProps) {
|
|
337
|
+
const resolved = resolveFont(font);
|
|
338
|
+
|
|
339
|
+
return (
|
|
340
|
+
<section className={resolved?.className}>
|
|
341
|
+
<FontStyles font={resolved} />
|
|
342
|
+
<h1>{heading}</h1>
|
|
343
|
+
</section>
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
A site that doesn't use React adds the font to its head itself. Each resolved font also carries `stylesheet` and `css` for that, along with `preconnect` and `preload` hints.
|
|
349
|
+
|
|
350
|
+
<br />
|
|
351
|
+
|
|
352
|
+
## Reference
|
|
353
|
+
|
|
354
|
+
The rest of this document covers the shape of a stored font and everything the package exports.
|
|
355
|
+
|
|
356
|
+
<br />
|
|
357
|
+
|
|
358
|
+
### Stored Fonts
|
|
359
|
+
|
|
360
|
+
`SanityFont` is the type of a stored font. A Google font holds its family and the weights and styles to load.
|
|
361
|
+
|
|
362
|
+
```json
|
|
363
|
+
{
|
|
364
|
+
"_type": "font",
|
|
365
|
+
"source": "google",
|
|
366
|
+
"family": "Playfair Display",
|
|
367
|
+
"category": "serif",
|
|
368
|
+
"weights": [400, 700],
|
|
369
|
+
"styles": ["normal", "italic"]
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
| Field | Type | Purpose |
|
|
374
|
+
| ---------- | -------------------- | -------------------------------------------------------------------------------------- |
|
|
375
|
+
| `_type` | `string` | Always `font`. |
|
|
376
|
+
| `source` | `SanityFontSource` | Where the font comes from: `google` or `upload`. |
|
|
377
|
+
| `family` | `string` | The name the font is used under. |
|
|
378
|
+
| `category` | `SanityFontCategory` | The kind of typeface: `sans-serif`, `serif`, `display`, `handwriting`, or `monospace`. |
|
|
379
|
+
| `weights` | `SanityFontWeight[]` | The weights to load, for a Google font. |
|
|
380
|
+
| `styles` | `SanityFontStyle[]` | The styles to load, for a Google font. |
|
|
381
|
+
| `files` | `SanityFontFile[]` | The files, for an uploaded font. |
|
|
382
|
+
|
|
383
|
+
An uploaded font holds `files` in place of `weights` and `styles`, each with its asset and the weight and style it holds.
|
|
384
|
+
|
|
385
|
+
<br />
|
|
386
|
+
|
|
387
|
+
### API
|
|
388
|
+
|
|
389
|
+
| Export | Import from | Purpose |
|
|
390
|
+
| ------------------------------ | ------------------------------------- | -------------------------------------------------------- |
|
|
391
|
+
| `fontPlugin(config?)` | `@driftime/sanity-plugin-font` | Registers the `font` type. |
|
|
392
|
+
| `createFontPreview(value)` | `@driftime/sanity-plugin-font` | Builds preview media from a stored font. |
|
|
393
|
+
| `withFonts()` | `@driftime/sanity-plugin-font/vite` | Adds the build step to the `vite` option of a Studio. |
|
|
394
|
+
| `googleFonts()` | `@driftime/sanity-plugin-font/vite` | The build step as a Vite plugin, for a framework's list. |
|
|
395
|
+
| `withFonts()` | `@driftime/sanity-plugin-font/next` | Adds the build step to a Next.js configuration. |
|
|
396
|
+
| `resolveFont(value, options?)` | `@driftime/sanity-plugin-font/render` | Resolves a stored font into classes and a style. |
|
|
397
|
+
| `FontStyles` | `@driftime/sanity-plugin-font/render` | Adds resolved fonts to the page. |
|
|
398
|
+
| `fontFragment` | `@driftime/sanity-plugin-font/render` | GROQ projection that expands an uploaded font's files. |
|
|
399
|
+
|
|
400
|
+
<br />
|
|
401
|
+
|
|
402
|
+
### Types
|
|
403
|
+
|
|
404
|
+
| Type | Import from | Purpose |
|
|
405
|
+
| ----------------------------- | ------------------------------------- | --------------------------------------------------------------- |
|
|
406
|
+
| `SanityFontConfig` | `@driftime/sanity-plugin-font` | Everything `fontPlugin` accepts. |
|
|
407
|
+
| `SanityFontPreview` | `@driftime/sanity-plugin-font` | Settings for the preview below a font field. |
|
|
408
|
+
| `SanityFontOptions` | `@driftime/sanity-plugin-font` | The `options` a font field accepts. |
|
|
409
|
+
| `SanityFontDefinition` | `@driftime/sanity-plugin-font` | A field or array member of type `font`. |
|
|
410
|
+
| `SanityFontFamily` | `@driftime/sanity-plugin-font` | The name of a Google Fonts family, checked against the catalog. |
|
|
411
|
+
| `SanityFontVitePlugin` | `@driftime/sanity-plugin-font/vite` | What `googleFonts` returns. |
|
|
412
|
+
| `SanityFontNextConfig` | `@driftime/sanity-plugin-font/next` | The parts of a Next.js configuration the build step extends. |
|
|
413
|
+
| `SanityFontWebpackConfig` | `@driftime/sanity-plugin-font/next` | The parts of a webpack configuration the build step extends. |
|
|
414
|
+
| `SanityFontWebpackCustomizer` | `@driftime/sanity-plugin-font/next` | A Next.js `webpack` option the build step runs first. |
|
|
415
|
+
| `SanityFont` | `@driftime/sanity-plugin-font/render` | A stored font, from Google Fonts or uploaded. |
|
|
416
|
+
| `SanityFontFile` | `@driftime/sanity-plugin-font/render` | One uploaded file, with the weight and style it holds. |
|
|
417
|
+
| `SanityFontAsset` | `@driftime/sanity-plugin-font/render` | A file's asset reference, or the asset with its `url`. |
|
|
418
|
+
| `SanityFontSource` | `@driftime/sanity-plugin-font/render` | Where a font comes from: `google` or `upload`. |
|
|
419
|
+
| `SanityFontCategory` | `@driftime/sanity-plugin-font/render` | A kind of typeface. |
|
|
420
|
+
| `SanityFontDisplay` | `@driftime/sanity-plugin-font/render` | How a font is shown while it loads. |
|
|
421
|
+
| `SanityFontWeight` | `@driftime/sanity-plugin-font/render` | A weight from 100 to 900. |
|
|
422
|
+
| `SanityFontStyle` | `@driftime/sanity-plugin-font/render` | A style: `normal` or `italic`. |
|
|
423
|
+
| `SanityResolveFontOptions` | `@driftime/sanity-plugin-font/render` | Everything `resolveFont` accepts besides the stored font. |
|
|
424
|
+
| `SanityResolvedFont` | `@driftime/sanity-plugin-font/render` | What `resolveFont` returns. |
|
|
425
|
+
| `SanityFontPreconnect` | `@driftime/sanity-plugin-font/render` | An origin to connect to early. |
|
|
426
|
+
| `SanityFontPreload` | `@driftime/sanity-plugin-font/render` | A file to preload. |
|
|
427
|
+
| `SanityFontStylesProps` | `@driftime/sanity-plugin-font/render` | Everything `FontStyles` accepts. |
|
|
428
|
+
|
|
429
|
+
<br />
|
|
430
|
+
|
|
431
|
+
## License
|
|
432
|
+
|
|
433
|
+
MIT © [Driftime®](https://driftime.com). See [LICENSE](https://github.com/driftime/sanity-plugins/blob/main/LICENSE).
|
|
434
|
+
|
|
435
|
+
<br />
|
|
436
|
+
|
|
437
|
+
## Acknowledgements
|
|
438
|
+
|
|
439
|
+
The plugin ships no fonts. Families chosen from [Google Fonts](https://fonts.google.com) are served by Google under their own open licenses, mostly the SIL Open Font License, and uploaded fonts under whatever license the project holds. WOFF2 files are read with the help of [brotli.js](https://github.com/devongovett/brotli.js), under the MIT License. Icons for the Studio's own controls come from `@sanity/icons`, so they match the rest of the Studio. The plugin's own Studio icon is based on [Lucide](https://lucide.dev), distributed under the [ISC License](https://github.com/lucide-icons/lucide/blob/main/LICENSE), and its logo comes from [Central Icon System](https://iconists.co/central), used under its license.
|
|
440
|
+
|
|
441
|
+
<br />
|
|
442
|
+
<br />
|
|
443
|
+
|
|
444
|
+
<div align="center">
|
|
445
|
+
<p><strong>Built alongside <a href="https://cairn.driftime.com">Cairn</a>, a starting point for responsible web experiences.</strong></p>
|
|
446
|
+
<p>Part of a suite of Sanity Studio plugins by Driftime®</p>
|
|
447
|
+
<p><a href="https://github.com/driftime/sanity-plugins#readme">Sanity Plugins</a> · <a href="https://github.com/driftime/sanity-plugins/tree/main/packages/handbook#readme">Handbook</a> · <a href="https://github.com/driftime/sanity-plugins/tree/main/packages/icon#readme">Icon</a> · <a href="https://github.com/driftime/sanity-plugins/tree/main/packages/color#readme">Color</a> · <a href="https://github.com/driftime/sanity-plugins/tree/main/packages/link#readme">Link</a> · <a href="https://github.com/driftime/sanity-plugins/tree/main/packages/font#readme">Font</a></p>
|
|
448
|
+
</div>
|
|
449
|
+
|
|
450
|
+
<br />
|
|
451
|
+
|
|
452
|
+
<div align="center">
|
|
453
|
+
<a href="https://driftime.com">
|
|
454
|
+
<picture>
|
|
455
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://driftime.com/driftime-github-logo-dark.svg" />
|
|
456
|
+
<source media="(prefers-color-scheme: light)" srcset="https://driftime.com/driftime-github-logo.svg" />
|
|
457
|
+
<img src="https://driftime.com/driftime-github-logo.svg" alt="Driftime® Logo" width="100" />
|
|
458
|
+
</picture>
|
|
459
|
+
</a>
|
|
460
|
+
</div>
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { n as isRecord, t as isDefined } from "./utils-BV38iODp.js";
|
|
2
|
+
import { c as logger, l as pluginName, n as parseWeights, t as parseCatalog } from "./catalog-KAqAIjzw.js";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { realpathSync } from "node:fs";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
/**
|
|
9
|
+
* Resolves symbolic links in a path, so a package linked into `node_modules` matches its real location.
|
|
10
|
+
*
|
|
11
|
+
* @param file - The path.
|
|
12
|
+
* @returns The real path, or the path unchanged when it doesn't exist.
|
|
13
|
+
*/
|
|
14
|
+
function resolveRealPath(file) {
|
|
15
|
+
try {
|
|
16
|
+
return realpathSync(file);
|
|
17
|
+
} catch {
|
|
18
|
+
return file;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Path of the placeholder catalog module the Studio imports, which the build step replaces. The build step's own code
|
|
23
|
+
* is published next to it, so the path is found from this file's location.
|
|
24
|
+
*/
|
|
25
|
+
const catalogModulePath = resolveRealPath(fileURLToPath(new URL("catalog.js", import.meta.url)));
|
|
26
|
+
/**
|
|
27
|
+
* Checks whether a module the bundler is loading is the placeholder catalog.
|
|
28
|
+
*
|
|
29
|
+
* @param id - The module's path, possibly followed by a query.
|
|
30
|
+
* @returns Whether it's the placeholder.
|
|
31
|
+
*/
|
|
32
|
+
function isCatalogModule(id) {
|
|
33
|
+
let [file] = id.split("?");
|
|
34
|
+
return isDefined(file) && resolveRealPath(file) === catalogModulePath;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Writes the module that replaces the placeholder catalog, which imports the catalog only when a picker opens.
|
|
38
|
+
*
|
|
39
|
+
* @param specifier - What the bundler imports the catalog from, or undefined when there's no catalog to import.
|
|
40
|
+
* @returns The module's code.
|
|
41
|
+
*/
|
|
42
|
+
function createCatalogModule(specifier) {
|
|
43
|
+
return isDefined(specifier) ? `export const fontCatalog = { ready: true, load: () => import(${JSON.stringify(specifier)}) };` : "export const fontCatalog = { ready: true };";
|
|
44
|
+
}
|
|
45
|
+
/** Types file the plugin reads family names from, published next to the placeholder catalog. */
|
|
46
|
+
const namesFile = path.join(path.dirname(catalogModulePath), "names.d.ts");
|
|
47
|
+
/**
|
|
48
|
+
* Writes the catalog's family names into the plugin's own types, so options that list families are checked against
|
|
49
|
+
* them. The file is replaced rather than edited, because pnpm shares a package's files between projects.
|
|
50
|
+
*
|
|
51
|
+
* @param families - Every family name in the catalog.
|
|
52
|
+
*/
|
|
53
|
+
async function writeFontNames(families) {
|
|
54
|
+
let union = [...new Set(families)].toSorted().map((name) => JSON.stringify(name)), content = [
|
|
55
|
+
`// Written by ${pluginName}'s build step from the Google Fonts catalog.`,
|
|
56
|
+
`export type SanityFontNames = ${isDefined(union) ? union.join(" | ") : "never"};`,
|
|
57
|
+
""
|
|
58
|
+
].join("\n");
|
|
59
|
+
if (await readFile(namesFile, "utf8").catch(() => "") === content) return;
|
|
60
|
+
let temporary = `${namesFile}.${process.pid}.tmp`;
|
|
61
|
+
await writeFile(temporary, content), await rename(temporary, namesFile);
|
|
62
|
+
}
|
|
63
|
+
/** Google Fonts' own list of families, which only Node can read because it sends no CORS header. */
|
|
64
|
+
const categories = {
|
|
65
|
+
"Sans Serif": "sans-serif",
|
|
66
|
+
Serif: "serif",
|
|
67
|
+
Display: "display",
|
|
68
|
+
Handwriting: "handwriting",
|
|
69
|
+
Monospace: "monospace"
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Converts one family from Google's list, keeping only what the picker filters and previews by.
|
|
73
|
+
*
|
|
74
|
+
* @param value - The family from Google's list.
|
|
75
|
+
* @returns The family and its rank in popularity, or undefined when it can't be read or has no weight a font field can
|
|
76
|
+
* load.
|
|
77
|
+
*/
|
|
78
|
+
function convertFamily(value) {
|
|
79
|
+
if (!isRecord(value)) return;
|
|
80
|
+
let { family, category, fonts, axes, subsets, trending, dateAdded, popularity } = value, mapped = typeof category == "string" ? categories[category] : void 0;
|
|
81
|
+
if (typeof family != "string" || !isDefined(mapped) || !isRecord(fonts) || !Array.isArray(subsets)) return;
|
|
82
|
+
let variants = Object.keys(fonts), weights = parseWeights(variants.filter((key) => !key.endsWith("i")).map(Number)), italics = parseWeights(variants.filter((key) => key.endsWith("i")).map((key) => Number(key.slice(0, -1))));
|
|
83
|
+
if (isDefined(weights) || isDefined(italics)) return {
|
|
84
|
+
converted: {
|
|
85
|
+
family,
|
|
86
|
+
category: mapped,
|
|
87
|
+
weights,
|
|
88
|
+
italics,
|
|
89
|
+
variable: Array.isArray(axes) && isDefined(axes),
|
|
90
|
+
subsets: subsets.filter((subset) => typeof subset == "string" && subset !== "menu"),
|
|
91
|
+
trending: typeof trending == "number" ? trending : 2 ** 53 - 1,
|
|
92
|
+
added: typeof dateAdded == "string" ? dateAdded : ""
|
|
93
|
+
},
|
|
94
|
+
popularity: typeof popularity == "number" ? popularity : Infinity
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Downloads Google's list of families and converts it.
|
|
99
|
+
*
|
|
100
|
+
* @returns Every family, most popular first, or undefined when Google couldn't be reached or the list couldn't be read.
|
|
101
|
+
*/
|
|
102
|
+
async function downloadCatalog() {
|
|
103
|
+
try {
|
|
104
|
+
let response = await fetch("https://fonts.google.com/metadata/fonts", { signal: AbortSignal.timeout(2e4) });
|
|
105
|
+
if (!response.ok) return;
|
|
106
|
+
let metadata = await response.json();
|
|
107
|
+
return !isRecord(metadata) || !Array.isArray(metadata.familyMetadataList) ? void 0 : metadata.familyMetadataList.flatMap((entry) => {
|
|
108
|
+
let family = convertFamily(entry);
|
|
109
|
+
return isDefined(family) ? [family] : [];
|
|
110
|
+
}).toSorted((first, second) => first.popularity - second.popularity).map(({ converted }) => converted);
|
|
111
|
+
} catch {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Reads the catalog an earlier build wrote.
|
|
117
|
+
*
|
|
118
|
+
* @param file - Path of the file.
|
|
119
|
+
* @returns The families and how long ago they were written, or undefined when there's no usable file.
|
|
120
|
+
*/
|
|
121
|
+
async function readCachedCatalog(file) {
|
|
122
|
+
try {
|
|
123
|
+
let [content, details] = await Promise.all([readFile(file, "utf8"), stat(file)]), families = parseCatalog(JSON.parse(content));
|
|
124
|
+
return isDefined(families) ? {
|
|
125
|
+
families,
|
|
126
|
+
written: details.mtime
|
|
127
|
+
} : void 0;
|
|
128
|
+
} catch {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Downloads the catalog unless a recent one is cached, and writes it where the bundler can import it. If Google can't
|
|
134
|
+
* be reached, the last download is used instead.
|
|
135
|
+
*
|
|
136
|
+
* @param root - The project folder.
|
|
137
|
+
* @returns The catalog, or undefined when there's neither a download nor an earlier one to use.
|
|
138
|
+
*/
|
|
139
|
+
async function writeCatalogOnce(root) {
|
|
140
|
+
let file = path.join(root, "node_modules", ".cache", "driftime-font", "catalog.json"), cached = await readCachedCatalog(file);
|
|
141
|
+
if (isDefined(cached) && Date.now() - cached.written.getTime() < 864e5) return {
|
|
142
|
+
file,
|
|
143
|
+
families: cached.families
|
|
144
|
+
};
|
|
145
|
+
let families = await downloadCatalog();
|
|
146
|
+
if (isDefined(families)) {
|
|
147
|
+
let temporary = `${file}.${randomUUID()}.tmp`;
|
|
148
|
+
return await mkdir(path.dirname(file), { recursive: !0 }), await writeFile(temporary, JSON.stringify(families)), await rename(temporary, file), logger.info(`Downloaded ${families.length.toLocaleString("en-US")} Google Fonts families.`), {
|
|
149
|
+
file,
|
|
150
|
+
families
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (isDefined(cached)) return logger.warn(`Couldn't reach Google Fonts, so the picker lists the families downloaded on ${cached.written.toLocaleDateString("en-US", { dateStyle: "long" })}.`), {
|
|
154
|
+
file,
|
|
155
|
+
families: cached.families
|
|
156
|
+
};
|
|
157
|
+
logger.error("Couldn't reach Google Fonts, and there's no earlier download to use. Restart once it can be reached.");
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Writes the catalog, then the family names the plugin's types check options against.
|
|
161
|
+
*
|
|
162
|
+
* @param root - The project folder.
|
|
163
|
+
* @returns The catalog, or undefined when there's neither a download nor an earlier one to use.
|
|
164
|
+
*/
|
|
165
|
+
async function prepareCatalog(root) {
|
|
166
|
+
let catalog = await writeCatalogOnce(root);
|
|
167
|
+
return isDefined(catalog) && await writeFontNames(catalog.families.map(({ family }) => family)), catalog;
|
|
168
|
+
}
|
|
169
|
+
/** Catalog writes in progress, keyed by project folder, so one process downloads the catalog once. */
|
|
170
|
+
const catalogWrites = /* @__PURE__ */ new Map();
|
|
171
|
+
/**
|
|
172
|
+
* Writes the catalog for a project, reusing a write already in progress.
|
|
173
|
+
*
|
|
174
|
+
* @param root - The project folder.
|
|
175
|
+
* @returns The catalog, or undefined when there's neither a download nor an earlier one to use.
|
|
176
|
+
*/
|
|
177
|
+
async function writeFontCatalog(root) {
|
|
178
|
+
let write = catalogWrites.get(root);
|
|
179
|
+
return isDefined(write) || (write = prepareCatalog(root), catalogWrites.set(root, write)), await write;
|
|
180
|
+
}
|
|
181
|
+
export { isCatalogModule as i, catalogModulePath as n, createCatalogModule as r, writeFontCatalog as t };
|
|
182
|
+
|
|
183
|
+
//# sourceMappingURL=catalog-BVw1r_SG.js.map
|