@liiift-studio/sanity-font-manager 2.3.18 → 2.4.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.
Files changed (42) hide show
  1. package/README.md +437 -437
  2. package/dist/index.js +103 -48
  3. package/dist/index.mjs +103 -48
  4. package/package.json +85 -85
  5. package/src/components/BatchUploadFonts.jsx +640 -639
  6. package/src/components/FontScriptUploaderComponent.jsx +463 -463
  7. package/src/components/GenerateCollectionsPairsComponent.jsx +259 -259
  8. package/src/components/KeyValueInput.jsx +95 -95
  9. package/src/components/KeyValueReferenceInput.jsx +254 -254
  10. package/src/components/NestedObjectArraySelector.jsx +146 -146
  11. package/src/components/PriceInput.jsx +26 -26
  12. package/src/components/PrimaryCollectionGeneratorTypeface.jsx +116 -116
  13. package/src/components/RegenerateSubfamiliesComponent.jsx +185 -185
  14. package/src/components/SetOTF.jsx +87 -87
  15. package/src/components/SingleUploaderTool.jsx +673 -673
  16. package/src/components/StatusDisplay.jsx +26 -26
  17. package/src/components/StyleCountInput.jsx +16 -16
  18. package/src/components/UpdateScriptsComponent.jsx +76 -76
  19. package/src/components/UploadButton.jsx +43 -43
  20. package/src/components/UploadScriptsComponent.jsx +537 -537
  21. package/src/components/VariableInstanceReferencesInput.jsx +190 -190
  22. package/src/hooks/useNestedObjects.js +92 -92
  23. package/src/hooks/useSanityClient.js +9 -9
  24. package/src/index.js +70 -70
  25. package/src/schema/openTypeField.js +1945 -1945
  26. package/src/schema/styleCountField.js +12 -12
  27. package/src/schema/stylesField.js +268 -268
  28. package/src/schema/stylisticSetField.js +301 -301
  29. package/src/utils/generateCssFile.js +205 -205
  30. package/src/utils/generateFontData.js +145 -145
  31. package/src/utils/generateFontFile.js +38 -38
  32. package/src/utils/generateKeywords.js +185 -185
  33. package/src/utils/generateSubset.js +45 -45
  34. package/src/utils/getEmptyFontKit.js +99 -99
  35. package/src/utils/parseVariableFontInstances.js +211 -211
  36. package/src/utils/processFontFiles.js +487 -477
  37. package/src/utils/regenerateFontData.js +146 -146
  38. package/src/utils/sanitizeForSanityId.js +65 -65
  39. package/src/utils/updateFontPrices.js +94 -94
  40. package/src/utils/updateTypefaceDocument.js +149 -160
  41. package/src/utils/uploadFontFiles.js +115 -26
  42. package/src/utils/utils.js +24 -24
package/README.md CHANGED
@@ -1,437 +1,437 @@
1
- # @liiift-studio/sanity-font-manager
2
-
3
- Full font management suite for Sanity Studio. Handles batch upload, multi-format conversion, metadata extraction, CSS `@font-face` generation, collection and pair generation, and script variant management.
4
-
5
- Compatible with Sanity v3, v4, and v5.
6
-
7
- ---
8
-
9
- ## Installation
10
-
11
- ```bash
12
- npm install @liiift-studio/sanity-font-manager
13
- ```
14
-
15
- ### Peer dependencies
16
-
17
- ```bash
18
- npm install sanity @sanity/ui @sanity/icons react
19
- ```
20
-
21
- | Peer | Required version |
22
- |---|---|
23
- | `sanity` | `>=3` |
24
- | `@sanity/ui` | `>=3` |
25
- | `@sanity/icons` | `>=3` |
26
- | `react` | `>=18` |
27
-
28
- If you hit peer dependency conflicts, add `legacy-peer-deps=true` to your `.npmrc`.
29
-
30
- ---
31
-
32
- ## Components
33
-
34
- ### `BatchUploadFonts`
35
-
36
- Drag-and-drop batch uploader for a typeface document. Accepts TTF/OTF/WOFF/WOFF2 etc., shows a reviewable file list with count, confirm button, elapsed timer, Wake Lock, and `beforeunload` guard for long uploads. Calls `uploadFontFiles` for each batch.
37
-
38
- ```jsx
39
- import { BatchUploadFonts } from '@liiift-studio/sanity-font-manager';
40
-
41
- export const typefaceSchema = {
42
- name: 'typeface',
43
- type: 'document',
44
- fields: [
45
- {
46
- name: 'styles',
47
- type: 'object',
48
- components: { input: BatchUploadFonts },
49
- fields: [ /* see Schema fields below */ ],
50
- },
51
- ],
52
- };
53
- ```
54
-
55
- ### `SingleUploaderTool`
56
-
57
- Per-font file manager inside a font document. Shows TTF/OTF/WOFF/WOFF2/CSS rows always. EOT/SVG/WEB/SUBSET/DATA are hidden behind an advanced toggle (cog icon). Each row has Upload/Build/Delete controls. Handles CSS regeneration, font data extraction, and WEB+SUBSET building via fontWorker.
58
-
59
- ```jsx
60
- import { SingleUploaderTool } from '@liiift-studio/sanity-font-manager';
61
-
62
- {
63
- name: 'fileInput',
64
- type: 'object',
65
- components: { input: SingleUploaderTool },
66
- fields: [ /* format fields — see Schema fields below */ ],
67
- }
68
- ```
69
-
70
- ### `GenerateCollectionsPairsComponent`
71
-
72
- One-click generator for Full Family, Uprights, Italics, and Subfamily collections, plus Regular/Italic pairs matched by weight. Has configurable price inputs for collection-per-font and pair price.
73
-
74
- ```jsx
75
- import { GenerateCollectionsPairsComponent } from '@liiift-studio/sanity-font-manager';
76
- ```
77
-
78
- ### `PrimaryCollectionGeneratorTypeface`
79
-
80
- One-click generator for a single full-family collection that includes all fonts linked to the typeface. Prepends the new collection to the existing `styles.collections` array — non-destructive. Uses `SANITY_STUDIO_DEFAULT_COLLECTION_PRICE` as the default price, falling back to `100`.
81
-
82
- Wire it up on a `string` field in the typeface schema:
83
-
84
- ```jsx
85
- import { PrimaryCollectionGeneratorTypeface } from '@liiift-studio/sanity-font-manager';
86
-
87
- {
88
- name: 'generateCollectionGroup',
89
- type: 'string',
90
- title: 'Generate Full Family Collection',
91
- description: 'Generate a collection that includes all the styles from this typeface.',
92
- components: { input: PrimaryCollectionGeneratorTypeface },
93
- hidden: ({ parent }) => !parent?.styles?.fonts?.length,
94
- }
95
- ```
96
-
97
- ### `FontScriptUploaderComponent`
98
-
99
- Script-aware uploader for per-script font file variants (Latin, Arabic, Hebrew, etc.) stored in `scriptFileInput` on the font document.
100
-
101
- ### `UploadScriptsComponent`
102
-
103
- Batch uploader for script-specific font variants across multiple fonts at once.
104
-
105
- ### `UpdateScriptsComponent`
106
-
107
- Updates and re-links existing script font variant references on font documents — used to fix or reassign script variant assignments.
108
-
109
- ### `RegenerateSubfamiliesComponent`
110
-
111
- Recalculates and patches the `subfamily` field on all fonts linked to a typeface, based on the typeface's defined subfamily groups — without re-uploading any files.
112
-
113
- ### `SetOTF`
114
-
115
- Detects which configured OpenType feature keys are supported by the typeface's first linked font. Reads `opentypeFeatures.chars` from the font document (populated by `generateFontData`) and patches the `features` array on the field. Shows a feature count when features are detected, and clear error messages when font data is missing.
116
-
117
- Wire it up on the `openType` object field in the typeface schema:
118
-
119
- ```jsx
120
- import { SetOTF } from '@liiift-studio/sanity-font-manager';
121
-
122
- {
123
- name: 'openType',
124
- type: 'object',
125
- components: { input: SetOTF },
126
- options: { collapsible: true },
127
- fields: [ /* feature fields — each with a `feature` string e.g. 'liga', 'smcp' */ ],
128
- }
129
- ```
130
-
131
- ### `StyleCountInput`
132
-
133
- Displays the total number of font styles (static + variable) linked to a typeface. Reads `styles.fonts` and `styles.variableFont` arrays from the form context. Useful as a read-only display field in the typeface schema.
134
-
135
- ```jsx
136
- import { StyleCountInput } from '@liiift-studio/sanity-font-manager';
137
-
138
- {
139
- name: 'styleCount',
140
- type: 'number',
141
- readOnly: true,
142
- components: { input: StyleCountInput },
143
- }
144
- ```
145
-
146
- ### `KeyValueInput`
147
-
148
- Generic ordered key-value editor where both keys and values are plain strings. Supports add, remove, and reorder (up/down arrows). Values are stored as an array of `{ key, value }` objects.
149
-
150
- ```jsx
151
- import { KeyValueInput } from '@liiift-studio/sanity-font-manager';
152
-
153
- {
154
- name: 'aliases',
155
- type: 'array',
156
- of: [{ type: 'object', fields: [{ name: 'key', type: 'string' }, { name: 'value', type: 'string' }] }],
157
- components: { input: KeyValueInput },
158
- }
159
- ```
160
-
161
- ### `KeyValueReferenceInput`
162
-
163
- Generic key-value editor where keys are plain strings and values are weak Sanity document references. Supports searching by title via a popover picker, add/remove/reorder, and an optional `topActions` slot for action buttons above the list.
164
-
165
- | Prop | Type | Description |
166
- |---|---|---|
167
- | `fetchReferences` | `async (client, doc) => [{_id, title}]` | Async function that returns candidate references for the picker. Receives the Sanity client and the current document. |
168
- | `topActions` | `ReactNode` | Optional content rendered above the key-value rows (e.g. autofill buttons). |
169
- | `referenceType` | `string` | Document type for the created weak references (default: `'font'`). |
170
-
171
- ```jsx
172
- import { KeyValueReferenceInput } from '@liiift-studio/sanity-font-manager';
173
-
174
- {
175
- name: 'instanceMap',
176
- type: 'array',
177
- of: [{ type: 'object', fields: [{ name: 'key', type: 'string' }, { name: 'value', type: 'reference', weak: true, to: [{ type: 'font' }] }] }],
178
- components: { input: KeyValueReferenceInput },
179
- // Pass props via options or a wrapper component:
180
- options: {
181
- fetchReferences: async (client, doc) => client.fetch('*[_type == "font"]{_id, title}'),
182
- referenceType: 'font',
183
- },
184
- }
185
- ```
186
-
187
- ### `VariableInstanceReferencesInput`
188
-
189
- Font-specific wrapper around `KeyValueReferenceInput` for mapping variable font instance names to their matching static font documents. Provides:
190
-
191
- - A picker filtered to fonts sharing the same `typefaceName`, excluding variable fonts
192
- - **Autofill with Matching** — calls `parseVariableFontInstances` to match instance names to existing font documents by weight/style heuristics
193
- - **Autofill Keys Only** — populates instance name keys from the font's `variableInstances` metadata without resolving references
194
- - Autofill buttons are shown only when the document is a variable font with parsed instance data
195
- - Replace/merge confirmation dialog when pairs already exist
196
-
197
- ```jsx
198
- import { VariableInstanceReferencesInput } from '@liiift-studio/sanity-font-manager';
199
-
200
- {
201
- name: 'variableInstanceReferences',
202
- title: 'Variable Font Instances',
203
- type: 'array',
204
- hidden: ({ parent }) => !parent.variableFont,
205
- of: [
206
- {
207
- type: 'object',
208
- fields: [
209
- { name: 'key', type: 'string', title: 'Instance Name' },
210
- { name: 'value', type: 'reference', weak: true, to: [{ type: 'font' }], title: 'Matching Font' },
211
- ],
212
- },
213
- ],
214
- components: { input: VariableInstanceReferencesInput },
215
- }
216
- ```
217
-
218
- ### `StatusDisplay`
219
-
220
- Shared status bar used by all components. Shows `Status: [message]` in green on success and red on error, with an optional `action` element slot on the far right (used for the advanced toggle in `SingleUploaderTool`).
221
-
222
- ```jsx
223
- import { StatusDisplay } from '@liiift-studio/sanity-font-manager';
224
-
225
- <StatusDisplay status="ready" error={false} action={<Button ... />} />
226
- ```
227
-
228
- ### `PriceInput`
229
-
230
- Reusable `$` + number input for collection and pair price fields.
231
-
232
- ### `UploadButton`
233
-
234
- Label-wrapped button that triggers a hidden file input.
235
-
236
- ---
237
-
238
- ## Schema field definitions
239
-
240
- Pre-built Sanity schema field objects that can be spread directly into a typeface schema's `fields` array. Eliminates hundreds of lines of repeated field definitions across consumer studios.
241
-
242
- ### `openTypeField`
243
-
244
- A complete `openType` object field wired to the `openType` tab group. Includes the `features` checkbox array (all standard OpenType feature keys) plus per-feature sub-objects with `title`, `feature`, and `customText` fields. Uses `SetOTF` internally for auto-detection.
245
-
246
- ```js
247
- import { openTypeField } from '@liiift-studio/sanity-font-manager';
248
-
249
- // In your typeface schema fields array:
250
- openTypeField,
251
- ```
252
-
253
- Requires the `openType` group to be declared in your schema's `groups` array:
254
- ```js
255
- { name: 'openType', title: 'Open Type' }
256
- ```
257
-
258
- ### `styleCountField`
259
-
260
- A read-only `number` field in the `styles` group that displays the total count of static + variable font styles linked to the typeface. Uses `StyleCountInput` internally.
261
-
262
- ```js
263
- import { styleCountField } from '@liiift-studio/sanity-font-manager';
264
-
265
- // In your typeface schema fields array:
266
- styleCountField,
267
- ```
268
-
269
- ### `stylisticSetField`
270
-
271
- A complete `stylisticSet` object field for the `stylisticSets` group. Contains two sub-arrays: `featured` (highlighted words/phrases with per-character backtick syntax, stylistic feature picker, size, and CSS overrides) and `sets` (full catalogue of feature → glyph mappings). Both include the full OpenType feature dropdown (44 named features + all 20 stylistic sets).
272
-
273
- ```js
274
- import { stylisticSetField } from '@liiift-studio/sanity-font-manager';
275
-
276
- // In your typeface schema fields array:
277
- stylisticSetField,
278
- ```
279
-
280
- Requires the `stylisticSets` group to be declared in your schema's `groups` array:
281
- ```js
282
- { name: 'stylisticSets', title: 'Stylistic Sets' }
283
- ```
284
-
285
- ---
286
-
287
- ## Hook
288
-
289
- ### `useSanityClient`
290
-
291
- Returns the Sanity client instance from the studio context. Used internally by all components.
292
-
293
- ```js
294
- import { useSanityClient } from '@liiift-studio/sanity-font-manager';
295
-
296
- const client = useSanityClient();
297
- ```
298
-
299
- ---
300
-
301
- ## Utilities
302
-
303
- ### Font processing
304
-
305
- | Export | Description |
306
- |---|---|
307
- | `processFontFiles` | Reads font files via FileReader, parses with fontkit, and builds the `fontsObjects` map used by `uploadFontFiles` |
308
- | `extractFontMetadata` | Extracts weight name, subfamily, style, and variable font flag from a fontkit instance |
309
- | `extractWeightName` | Reads the weight name from fontkit name records, falling back through `preferredSubfamily → fontSubfamily` |
310
- | `extractWeightFromFullName` | Strips the typeface title from the font's full name to isolate the weight/style suffix |
311
- | `processSubfamilyName` | Strips weight and italic keywords from a subfamily string, preserving non-style words like "Condensed" |
312
- | `formatFontTitle` | Normalises a font filename into a human-readable title — expands abbreviations, title-cases, collapses spaces |
313
- | `addItalicToFontTitle` | Appends the detected italic keyword to a title when the font has a non-zero italic angle |
314
- | `determineWeight` | Maps a weight name to a CSS numeric weight, preferring OS/2 `usWeightClass` when available |
315
- | `sortFontObjects` | Sorts a `fontsObjects` map by ascending weight, placing Regular before Italic at equal weights |
316
- | `createFontObject` | Builds the full font object (id, title, weight, style, files, etc.) for a single font file |
317
- | `uploadFontFiles` | Core batch upload orchestrator — uploads each format to Sanity, generates CSS and metadata, then creates or updates font documents |
318
- | `updateTypefaceDocument` | Patches the parent typeface document's `styles.fonts` array with newly uploaded font references |
319
- | `renameFontDocuments` | Renames font document IDs across a typeface when a typeface slug changes |
320
- | `updateFontPrices` | Bulk-updates the `price` field across all font documents linked to a typeface |
321
- | `sanitizeForSanityId` | Converts arbitrary strings into valid Sanity document IDs (lowercase, hyphens, no special characters) |
322
-
323
- ### CSS and file generation
324
-
325
- | Export | Description |
326
- |---|---|
327
- | `generateCssFile` | Builds a `@font-face` CSS file from a WOFF2 blob — URL or base64 `src`, variable font axis descriptors, and metric-tuned fallback `@font-face` for CLS reduction |
328
- | `buildVFDescriptors` | Pure function — maps fontkit variation axes to CSS descriptors (`font-weight`, `font-stretch`, `font-style`), handling degenerate axes, `slnt`/`ital` priority, and `min > max` clamping |
329
- | `generateFontData` | Fetches a TTF URL, parses with fontkit, and patches the Sanity font document with `metaData`, `metrics`, `glyphCount`, `opentypeFeatures`, `characterSet`, and variable axes/instances |
330
- | `buildFontMetadata` | Pure function — extracts `metaData` and `metrics` from a fontkit instance without any Sanity side effects |
331
- | `generateFontFile` | Fires a POST to the consuming site's `/api/sanity/fontWorker` endpoint with the format codes to convert (otf, woff, woff2, eot, svg, data) |
332
- | `generateSubset` | Requests DS-WEB fingerprinted WOFF2 and display subset generation from an existing WOFF2 via fontWorker |
333
- | `parseVariableFontInstances` | Resolves named variable font instances into Sanity font document references, creating documents for missing instances |
334
- | `getEmptyFontKit` | Returns a zeroed-out fontkit-shaped placeholder used when no font binary is available |
335
-
336
- ### Keyword utilities
337
-
338
- | Export | Description |
339
- |---|---|
340
- | `generateStyleKeywords` | Builds weight and italic keyword lists (including abbreviation expansions like `Bd → Bold`, `Lt → Light`) for parsing font subfamily names |
341
- | `reverseSpellingLookup` | Resolves a font name abbreviation to its canonical weight name |
342
- | `expandAbbreviations` | Expands all known abbreviations in a string to full weight names |
343
- | `removeWeightNames` | Strips weight and italic keywords from a string, leaving only non-style words |
344
-
345
- ### Constants
346
-
347
- | Export | Description |
348
- |---|---|
349
- | `SCRIPTS` | Array of supported script variant names |
350
- | `SCRIPTS_OBJECT` | Map of script names to their display labels |
351
- | `HtmlDescription` | React component rendering the supported script list as formatted HTML |
352
-
353
- ---
354
-
355
- ## Schema fields
356
-
357
- ### Font document (`font`)
358
-
359
- | Field | Type | Description |
360
- |---|---|---|
361
- | `title` | `string` | Full font name (e.g. `MyFont SemiBold Italic`) |
362
- | `slug` | `slug` | Sanitized document ID as a slug (`current` = document `_id`) |
363
- | `typefaceName` | `string` | Name of the parent typeface |
364
- | `style` | `string` | `'Regular'` or `'Italic'` |
365
- | `weight` | `number` | Numeric CSS weight (100–900) |
366
- | `weightName` | `string` | Human-readable weight name (e.g. `'SemiBold'`) |
367
- | `subfamily` | `string` | Subfamily name (e.g. `'Condensed'`) |
368
- | `variableFont` | `boolean` | `true` for variable fonts |
369
- | `normalWeight` | `boolean` | `true` when the weight is the normal/regular weight |
370
- | `fileInput` | `object` | Container for all uploaded format files |
371
- | `fileInput.ttf` | `file` | Uploaded TTF file (Sanity asset reference) |
372
- | `fileInput.otf` | `file` | OTF file (built from TTF or uploaded directly) |
373
- | `fileInput.woff2` | `file` | WOFF2 file (built from TTF or uploaded directly) |
374
- | `fileInput.woff` | `file` | WOFF file |
375
- | `fileInput.eot` | `file` | EOT file (legacy) |
376
- | `fileInput.svg` | `file` | SVG font file (legacy) |
377
- | `fileInput.css` | `file` | Generated `@font-face` CSS file |
378
- | `fileInput.woff2_web` | `file` | DS-WEB fingerprinted WOFF2 for web delivery |
379
- | `fileInput.woff2_subset` | `file` | Display subset WOFF2 (Latin + Latin-1, fingerprinted) |
380
- | `metaData` | `object` | Font metadata — `postscriptName`, `fullName`, `familyName`, `subfamilyName`, `copyright`, `version`, `genDate` |
381
- | `metrics` | `object` | Font metrics — `unitsPerEm`, `ascender`, `descender`, `lineGap`, `capHeight`, `xHeight`, `italicAngle`, etc. |
382
- | `glyphCount` | `number` | Total number of glyphs |
383
- | `opentypeFeatures` | `object` | Available OpenType feature tags |
384
- | `characterSet` | `object` | Array of Unicode code points covered by the font |
385
- | `variableInstanceReferences` | `array<object>` | Maps variable font instance names to static font document references — `[{ key: string, value: reference }]` |
386
-
387
- ### Typeface document (`typeface`)
388
-
389
- | Field | Type | Description |
390
- |---|---|---|
391
- | `styles.fonts` | `array<reference>` | References to regular font documents |
392
- | `styles.variableFont` | `array<reference>` | References to variable font documents |
393
- | `styles.collections` | `array<reference>` | References to generated collection documents |
394
- | `styles.pairs` | `array<reference>` | References to generated pair documents |
395
- | `styles.subfamilies` | `array<object>` | Subfamily groups — each has `title`, `_key`, and `fonts: array<reference>` |
396
- | `preferredStyle` | `reference` | Reference to the preferred regular-weight font document |
397
-
398
- ---
399
-
400
- ## Environment variables
401
-
402
- | Variable | Required | Description |
403
- |---|---|---|
404
- | `SANITY_STUDIO_SITE_URL` | Yes | Base URL of the consuming site. Used by `generateFontFile` and `generateSubset` to call `/api/sanity/fontWorker`. |
405
- | `SANITY_STUDIO_PROJECT_ID` | Yes | Sanity project ID. Used to build CDN file URLs inside the uploaders. |
406
- | `SANITY_STUDIO_DATASET` | Yes | Sanity dataset name. Used alongside `PROJECT_ID` for CDN URLs. |
407
- | `SANITY_STUDIO_SCRIPTS` | No | Comma-separated script variant names (e.g. `latin,greek,arabic`). Controls which script tabs appear. |
408
- | `SANITY_STUDIO_DEFAULT_COLLECTION_PRICE` | No | Default per-font price for generated collections. |
409
- | `SANITY_STUDIO_DEFAULT_PAIR_PRICE` | No | Default price for generated pairs. |
410
-
411
- ---
412
-
413
- ## Local development
414
-
415
- To use the local source instead of the published npm package, symlink it into a foundry repo:
416
-
417
- ```bash
418
- # From the sanity-font-manager directory:
419
- npm run link:darden # symlink into Darden Studio
420
- npm run link:tdf # symlink into The Designers Foundry
421
- npm run link:mckl # symlink into MCKL CMS
422
- npm run link:all # symlink into all three at once
423
- ```
424
-
425
- Then run the watch build so consumers pick up changes live:
426
-
427
- ```bash
428
- npm run dev
429
- ```
430
-
431
- To restore the published package in a consumer repo, run `npm install` inside that repo.
432
-
433
- ---
434
-
435
- ## License
436
-
437
- MIT — [Liiift Studio](https://github.com/Liiift-Studio)
1
+ # @liiift-studio/sanity-font-manager
2
+
3
+ Full font management suite for Sanity Studio. Handles batch upload, multi-format conversion, metadata extraction, CSS `@font-face` generation, collection and pair generation, and script variant management.
4
+
5
+ Compatible with Sanity v3, v4, and v5.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @liiift-studio/sanity-font-manager
13
+ ```
14
+
15
+ ### Peer dependencies
16
+
17
+ ```bash
18
+ npm install sanity @sanity/ui @sanity/icons react
19
+ ```
20
+
21
+ | Peer | Required version |
22
+ |---|---|
23
+ | `sanity` | `>=3` |
24
+ | `@sanity/ui` | `>=3` |
25
+ | `@sanity/icons` | `>=3` |
26
+ | `react` | `>=18` |
27
+
28
+ If you hit peer dependency conflicts, add `legacy-peer-deps=true` to your `.npmrc`.
29
+
30
+ ---
31
+
32
+ ## Components
33
+
34
+ ### `BatchUploadFonts`
35
+
36
+ Drag-and-drop batch uploader for a typeface document. Accepts TTF/OTF/WOFF/WOFF2 etc., shows a reviewable file list with count, confirm button, elapsed timer, Wake Lock, and `beforeunload` guard for long uploads. Calls `uploadFontFiles` for each batch.
37
+
38
+ ```jsx
39
+ import { BatchUploadFonts } from '@liiift-studio/sanity-font-manager';
40
+
41
+ export const typefaceSchema = {
42
+ name: 'typeface',
43
+ type: 'document',
44
+ fields: [
45
+ {
46
+ name: 'styles',
47
+ type: 'object',
48
+ components: { input: BatchUploadFonts },
49
+ fields: [ /* see Schema fields below */ ],
50
+ },
51
+ ],
52
+ };
53
+ ```
54
+
55
+ ### `SingleUploaderTool`
56
+
57
+ Per-font file manager inside a font document. Shows TTF/OTF/WOFF/WOFF2/CSS rows always. EOT/SVG/WEB/SUBSET/DATA are hidden behind an advanced toggle (cog icon). Each row has Upload/Build/Delete controls. Handles CSS regeneration, font data extraction, and WEB+SUBSET building via fontWorker.
58
+
59
+ ```jsx
60
+ import { SingleUploaderTool } from '@liiift-studio/sanity-font-manager';
61
+
62
+ {
63
+ name: 'fileInput',
64
+ type: 'object',
65
+ components: { input: SingleUploaderTool },
66
+ fields: [ /* format fields — see Schema fields below */ ],
67
+ }
68
+ ```
69
+
70
+ ### `GenerateCollectionsPairsComponent`
71
+
72
+ One-click generator for Full Family, Uprights, Italics, and Subfamily collections, plus Regular/Italic pairs matched by weight. Has configurable price inputs for collection-per-font and pair price.
73
+
74
+ ```jsx
75
+ import { GenerateCollectionsPairsComponent } from '@liiift-studio/sanity-font-manager';
76
+ ```
77
+
78
+ ### `PrimaryCollectionGeneratorTypeface`
79
+
80
+ One-click generator for a single full-family collection that includes all fonts linked to the typeface. Prepends the new collection to the existing `styles.collections` array — non-destructive. Uses `SANITY_STUDIO_DEFAULT_COLLECTION_PRICE` as the default price, falling back to `100`.
81
+
82
+ Wire it up on a `string` field in the typeface schema:
83
+
84
+ ```jsx
85
+ import { PrimaryCollectionGeneratorTypeface } from '@liiift-studio/sanity-font-manager';
86
+
87
+ {
88
+ name: 'generateCollectionGroup',
89
+ type: 'string',
90
+ title: 'Generate Full Family Collection',
91
+ description: 'Generate a collection that includes all the styles from this typeface.',
92
+ components: { input: PrimaryCollectionGeneratorTypeface },
93
+ hidden: ({ parent }) => !parent?.styles?.fonts?.length,
94
+ }
95
+ ```
96
+
97
+ ### `FontScriptUploaderComponent`
98
+
99
+ Script-aware uploader for per-script font file variants (Latin, Arabic, Hebrew, etc.) stored in `scriptFileInput` on the font document.
100
+
101
+ ### `UploadScriptsComponent`
102
+
103
+ Batch uploader for script-specific font variants across multiple fonts at once.
104
+
105
+ ### `UpdateScriptsComponent`
106
+
107
+ Updates and re-links existing script font variant references on font documents — used to fix or reassign script variant assignments.
108
+
109
+ ### `RegenerateSubfamiliesComponent`
110
+
111
+ Recalculates and patches the `subfamily` field on all fonts linked to a typeface, based on the typeface's defined subfamily groups — without re-uploading any files.
112
+
113
+ ### `SetOTF`
114
+
115
+ Detects which configured OpenType feature keys are supported by the typeface's first linked font. Reads `opentypeFeatures.chars` from the font document (populated by `generateFontData`) and patches the `features` array on the field. Shows a feature count when features are detected, and clear error messages when font data is missing.
116
+
117
+ Wire it up on the `openType` object field in the typeface schema:
118
+
119
+ ```jsx
120
+ import { SetOTF } from '@liiift-studio/sanity-font-manager';
121
+
122
+ {
123
+ name: 'openType',
124
+ type: 'object',
125
+ components: { input: SetOTF },
126
+ options: { collapsible: true },
127
+ fields: [ /* feature fields — each with a `feature` string e.g. 'liga', 'smcp' */ ],
128
+ }
129
+ ```
130
+
131
+ ### `StyleCountInput`
132
+
133
+ Displays the total number of font styles (static + variable) linked to a typeface. Reads `styles.fonts` and `styles.variableFont` arrays from the form context. Useful as a read-only display field in the typeface schema.
134
+
135
+ ```jsx
136
+ import { StyleCountInput } from '@liiift-studio/sanity-font-manager';
137
+
138
+ {
139
+ name: 'styleCount',
140
+ type: 'number',
141
+ readOnly: true,
142
+ components: { input: StyleCountInput },
143
+ }
144
+ ```
145
+
146
+ ### `KeyValueInput`
147
+
148
+ Generic ordered key-value editor where both keys and values are plain strings. Supports add, remove, and reorder (up/down arrows). Values are stored as an array of `{ key, value }` objects.
149
+
150
+ ```jsx
151
+ import { KeyValueInput } from '@liiift-studio/sanity-font-manager';
152
+
153
+ {
154
+ name: 'aliases',
155
+ type: 'array',
156
+ of: [{ type: 'object', fields: [{ name: 'key', type: 'string' }, { name: 'value', type: 'string' }] }],
157
+ components: { input: KeyValueInput },
158
+ }
159
+ ```
160
+
161
+ ### `KeyValueReferenceInput`
162
+
163
+ Generic key-value editor where keys are plain strings and values are weak Sanity document references. Supports searching by title via a popover picker, add/remove/reorder, and an optional `topActions` slot for action buttons above the list.
164
+
165
+ | Prop | Type | Description |
166
+ |---|---|---|
167
+ | `fetchReferences` | `async (client, doc) => [{_id, title}]` | Async function that returns candidate references for the picker. Receives the Sanity client and the current document. |
168
+ | `topActions` | `ReactNode` | Optional content rendered above the key-value rows (e.g. autofill buttons). |
169
+ | `referenceType` | `string` | Document type for the created weak references (default: `'font'`). |
170
+
171
+ ```jsx
172
+ import { KeyValueReferenceInput } from '@liiift-studio/sanity-font-manager';
173
+
174
+ {
175
+ name: 'instanceMap',
176
+ type: 'array',
177
+ of: [{ type: 'object', fields: [{ name: 'key', type: 'string' }, { name: 'value', type: 'reference', weak: true, to: [{ type: 'font' }] }] }],
178
+ components: { input: KeyValueReferenceInput },
179
+ // Pass props via options or a wrapper component:
180
+ options: {
181
+ fetchReferences: async (client, doc) => client.fetch('*[_type == "font"]{_id, title}'),
182
+ referenceType: 'font',
183
+ },
184
+ }
185
+ ```
186
+
187
+ ### `VariableInstanceReferencesInput`
188
+
189
+ Font-specific wrapper around `KeyValueReferenceInput` for mapping variable font instance names to their matching static font documents. Provides:
190
+
191
+ - A picker filtered to fonts sharing the same `typefaceName`, excluding variable fonts
192
+ - **Autofill with Matching** — calls `parseVariableFontInstances` to match instance names to existing font documents by weight/style heuristics
193
+ - **Autofill Keys Only** — populates instance name keys from the font's `variableInstances` metadata without resolving references
194
+ - Autofill buttons are shown only when the document is a variable font with parsed instance data
195
+ - Replace/merge confirmation dialog when pairs already exist
196
+
197
+ ```jsx
198
+ import { VariableInstanceReferencesInput } from '@liiift-studio/sanity-font-manager';
199
+
200
+ {
201
+ name: 'variableInstanceReferences',
202
+ title: 'Variable Font Instances',
203
+ type: 'array',
204
+ hidden: ({ parent }) => !parent.variableFont,
205
+ of: [
206
+ {
207
+ type: 'object',
208
+ fields: [
209
+ { name: 'key', type: 'string', title: 'Instance Name' },
210
+ { name: 'value', type: 'reference', weak: true, to: [{ type: 'font' }], title: 'Matching Font' },
211
+ ],
212
+ },
213
+ ],
214
+ components: { input: VariableInstanceReferencesInput },
215
+ }
216
+ ```
217
+
218
+ ### `StatusDisplay`
219
+
220
+ Shared status bar used by all components. Shows `Status: [message]` in green on success and red on error, with an optional `action` element slot on the far right (used for the advanced toggle in `SingleUploaderTool`).
221
+
222
+ ```jsx
223
+ import { StatusDisplay } from '@liiift-studio/sanity-font-manager';
224
+
225
+ <StatusDisplay status="ready" error={false} action={<Button ... />} />
226
+ ```
227
+
228
+ ### `PriceInput`
229
+
230
+ Reusable `$` + number input for collection and pair price fields.
231
+
232
+ ### `UploadButton`
233
+
234
+ Label-wrapped button that triggers a hidden file input.
235
+
236
+ ---
237
+
238
+ ## Schema field definitions
239
+
240
+ Pre-built Sanity schema field objects that can be spread directly into a typeface schema's `fields` array. Eliminates hundreds of lines of repeated field definitions across consumer studios.
241
+
242
+ ### `openTypeField`
243
+
244
+ A complete `openType` object field wired to the `openType` tab group. Includes the `features` checkbox array (all standard OpenType feature keys) plus per-feature sub-objects with `title`, `feature`, and `customText` fields. Uses `SetOTF` internally for auto-detection.
245
+
246
+ ```js
247
+ import { openTypeField } from '@liiift-studio/sanity-font-manager';
248
+
249
+ // In your typeface schema fields array:
250
+ openTypeField,
251
+ ```
252
+
253
+ Requires the `openType` group to be declared in your schema's `groups` array:
254
+ ```js
255
+ { name: 'openType', title: 'Open Type' }
256
+ ```
257
+
258
+ ### `styleCountField`
259
+
260
+ A read-only `number` field in the `styles` group that displays the total count of static + variable font styles linked to the typeface. Uses `StyleCountInput` internally.
261
+
262
+ ```js
263
+ import { styleCountField } from '@liiift-studio/sanity-font-manager';
264
+
265
+ // In your typeface schema fields array:
266
+ styleCountField,
267
+ ```
268
+
269
+ ### `stylisticSetField`
270
+
271
+ A complete `stylisticSet` object field for the `stylisticSets` group. Contains two sub-arrays: `featured` (highlighted words/phrases with per-character backtick syntax, stylistic feature picker, size, and CSS overrides) and `sets` (full catalogue of feature → glyph mappings). Both include the full OpenType feature dropdown (44 named features + all 20 stylistic sets).
272
+
273
+ ```js
274
+ import { stylisticSetField } from '@liiift-studio/sanity-font-manager';
275
+
276
+ // In your typeface schema fields array:
277
+ stylisticSetField,
278
+ ```
279
+
280
+ Requires the `stylisticSets` group to be declared in your schema's `groups` array:
281
+ ```js
282
+ { name: 'stylisticSets', title: 'Stylistic Sets' }
283
+ ```
284
+
285
+ ---
286
+
287
+ ## Hook
288
+
289
+ ### `useSanityClient`
290
+
291
+ Returns the Sanity client instance from the studio context. Used internally by all components.
292
+
293
+ ```js
294
+ import { useSanityClient } from '@liiift-studio/sanity-font-manager';
295
+
296
+ const client = useSanityClient();
297
+ ```
298
+
299
+ ---
300
+
301
+ ## Utilities
302
+
303
+ ### Font processing
304
+
305
+ | Export | Description |
306
+ |---|---|
307
+ | `processFontFiles` | Reads font files via FileReader, parses with fontkit, and builds the `fontsObjects` map used by `uploadFontFiles` |
308
+ | `extractFontMetadata` | Extracts weight name, subfamily, style, and variable font flag from a fontkit instance |
309
+ | `extractWeightName` | Reads the weight name from fontkit name records, falling back through `preferredSubfamily → fontSubfamily` |
310
+ | `extractWeightFromFullName` | Strips the typeface title from the font's full name to isolate the weight/style suffix |
311
+ | `processSubfamilyName` | Strips weight and italic keywords from a subfamily string, preserving non-style words like "Condensed" |
312
+ | `formatFontTitle` | Normalises a font filename into a human-readable title — expands abbreviations, title-cases, collapses spaces |
313
+ | `addItalicToFontTitle` | Appends the detected italic keyword to a title when the font has a non-zero italic angle |
314
+ | `determineWeight` | Maps a weight name to a CSS numeric weight, preferring OS/2 `usWeightClass` when available |
315
+ | `sortFontObjects` | Sorts a `fontsObjects` map by ascending weight, placing Regular before Italic at equal weights |
316
+ | `createFontObject` | Builds the full font object (id, title, weight, style, files, etc.) for a single font file |
317
+ | `uploadFontFiles` | Core batch upload orchestrator — uploads each format to Sanity, generates CSS and metadata, then creates or updates font documents |
318
+ | `updateTypefaceDocument` | Patches the parent typeface document's `styles.fonts` array with newly uploaded font references |
319
+ | `renameFontDocuments` | Renames font document IDs across a typeface when a typeface slug changes |
320
+ | `updateFontPrices` | Bulk-updates the `price` field across all font documents linked to a typeface |
321
+ | `sanitizeForSanityId` | Converts arbitrary strings into valid Sanity document IDs (lowercase, hyphens, no special characters) |
322
+
323
+ ### CSS and file generation
324
+
325
+ | Export | Description |
326
+ |---|---|
327
+ | `generateCssFile` | Builds a `@font-face` CSS file from a WOFF2 blob — URL or base64 `src`, variable font axis descriptors, and metric-tuned fallback `@font-face` for CLS reduction |
328
+ | `buildVFDescriptors` | Pure function — maps fontkit variation axes to CSS descriptors (`font-weight`, `font-stretch`, `font-style`), handling degenerate axes, `slnt`/`ital` priority, and `min > max` clamping |
329
+ | `generateFontData` | Fetches a TTF URL, parses with fontkit, and patches the Sanity font document with `metaData`, `metrics`, `glyphCount`, `opentypeFeatures`, `characterSet`, and variable axes/instances |
330
+ | `buildFontMetadata` | Pure function — extracts `metaData` and `metrics` from a fontkit instance without any Sanity side effects |
331
+ | `generateFontFile` | Fires a POST to the consuming site's `/api/sanity/fontWorker` endpoint with the format codes to convert (otf, woff, woff2, eot, svg, data) |
332
+ | `generateSubset` | Requests DS-WEB fingerprinted WOFF2 and display subset generation from an existing WOFF2 via fontWorker |
333
+ | `parseVariableFontInstances` | Resolves named variable font instances into Sanity font document references, creating documents for missing instances |
334
+ | `getEmptyFontKit` | Returns a zeroed-out fontkit-shaped placeholder used when no font binary is available |
335
+
336
+ ### Keyword utilities
337
+
338
+ | Export | Description |
339
+ |---|---|
340
+ | `generateStyleKeywords` | Builds weight and italic keyword lists (including abbreviation expansions like `Bd → Bold`, `Lt → Light`) for parsing font subfamily names |
341
+ | `reverseSpellingLookup` | Resolves a font name abbreviation to its canonical weight name |
342
+ | `expandAbbreviations` | Expands all known abbreviations in a string to full weight names |
343
+ | `removeWeightNames` | Strips weight and italic keywords from a string, leaving only non-style words |
344
+
345
+ ### Constants
346
+
347
+ | Export | Description |
348
+ |---|---|
349
+ | `SCRIPTS` | Array of supported script variant names |
350
+ | `SCRIPTS_OBJECT` | Map of script names to their display labels |
351
+ | `HtmlDescription` | React component rendering the supported script list as formatted HTML |
352
+
353
+ ---
354
+
355
+ ## Schema fields
356
+
357
+ ### Font document (`font`)
358
+
359
+ | Field | Type | Description |
360
+ |---|---|---|
361
+ | `title` | `string` | Full font name (e.g. `MyFont SemiBold Italic`) |
362
+ | `slug` | `slug` | Sanitized document ID as a slug (`current` = document `_id`) |
363
+ | `typefaceName` | `string` | Name of the parent typeface |
364
+ | `style` | `string` | `'Regular'` or `'Italic'` |
365
+ | `weight` | `number` | Numeric CSS weight (100–900) |
366
+ | `weightName` | `string` | Human-readable weight name (e.g. `'SemiBold'`) |
367
+ | `subfamily` | `string` | Subfamily name (e.g. `'Condensed'`) |
368
+ | `variableFont` | `boolean` | `true` for variable fonts |
369
+ | `normalWeight` | `boolean` | `true` when the weight is the normal/regular weight |
370
+ | `fileInput` | `object` | Container for all uploaded format files |
371
+ | `fileInput.ttf` | `file` | Uploaded TTF file (Sanity asset reference) |
372
+ | `fileInput.otf` | `file` | OTF file (built from TTF or uploaded directly) |
373
+ | `fileInput.woff2` | `file` | WOFF2 file (built from TTF or uploaded directly) |
374
+ | `fileInput.woff` | `file` | WOFF file |
375
+ | `fileInput.eot` | `file` | EOT file (legacy) |
376
+ | `fileInput.svg` | `file` | SVG font file (legacy) |
377
+ | `fileInput.css` | `file` | Generated `@font-face` CSS file |
378
+ | `fileInput.woff2_web` | `file` | DS-WEB fingerprinted WOFF2 for web delivery |
379
+ | `fileInput.woff2_subset` | `file` | Display subset WOFF2 (Latin + Latin-1, fingerprinted) |
380
+ | `metaData` | `object` | Font metadata — `postscriptName`, `fullName`, `familyName`, `subfamilyName`, `copyright`, `version`, `genDate` |
381
+ | `metrics` | `object` | Font metrics — `unitsPerEm`, `ascender`, `descender`, `lineGap`, `capHeight`, `xHeight`, `italicAngle`, etc. |
382
+ | `glyphCount` | `number` | Total number of glyphs |
383
+ | `opentypeFeatures` | `object` | Available OpenType feature tags |
384
+ | `characterSet` | `object` | Array of Unicode code points covered by the font |
385
+ | `variableInstanceReferences` | `array<object>` | Maps variable font instance names to static font document references — `[{ key: string, value: reference }]` |
386
+
387
+ ### Typeface document (`typeface`)
388
+
389
+ | Field | Type | Description |
390
+ |---|---|---|
391
+ | `styles.fonts` | `array<reference>` | References to regular font documents |
392
+ | `styles.variableFont` | `array<reference>` | References to variable font documents |
393
+ | `styles.collections` | `array<reference>` | References to generated collection documents |
394
+ | `styles.pairs` | `array<reference>` | References to generated pair documents |
395
+ | `styles.subfamilies` | `array<object>` | Subfamily groups — each has `title`, `_key`, and `fonts: array<reference>` |
396
+ | `preferredStyle` | `reference` | Reference to the preferred regular-weight font document |
397
+
398
+ ---
399
+
400
+ ## Environment variables
401
+
402
+ | Variable | Required | Description |
403
+ |---|---|---|
404
+ | `SANITY_STUDIO_SITE_URL` | Yes | Base URL of the consuming site. Used by `generateFontFile` and `generateSubset` to call `/api/sanity/fontWorker`. |
405
+ | `SANITY_STUDIO_PROJECT_ID` | Yes | Sanity project ID. Used to build CDN file URLs inside the uploaders. |
406
+ | `SANITY_STUDIO_DATASET` | Yes | Sanity dataset name. Used alongside `PROJECT_ID` for CDN URLs. |
407
+ | `SANITY_STUDIO_SCRIPTS` | No | Comma-separated script variant names (e.g. `latin,greek,arabic`). Controls which script tabs appear. |
408
+ | `SANITY_STUDIO_DEFAULT_COLLECTION_PRICE` | No | Default per-font price for generated collections. |
409
+ | `SANITY_STUDIO_DEFAULT_PAIR_PRICE` | No | Default price for generated pairs. |
410
+
411
+ ---
412
+
413
+ ## Local development
414
+
415
+ To use the local source instead of the published npm package, symlink it into a foundry repo:
416
+
417
+ ```bash
418
+ # From the sanity-font-manager directory:
419
+ npm run link:darden # symlink into Darden Studio
420
+ npm run link:tdf # symlink into The Designers Foundry
421
+ npm run link:mckl # symlink into MCKL CMS
422
+ npm run link:all # symlink into all three at once
423
+ ```
424
+
425
+ Then run the watch build so consumers pick up changes live:
426
+
427
+ ```bash
428
+ npm run dev
429
+ ```
430
+
431
+ To restore the published package in a consumer repo, run `npm install` inside that repo.
432
+
433
+ ---
434
+
435
+ ## License
436
+
437
+ MIT — [Liiift Studio](https://github.com/Liiift-Studio)