@liiift-studio/sanity-font-manager 2.7.0 → 2.8.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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +695 -437
  3. package/dist/index.js +2330 -7084
  4. package/dist/index.mjs +2331 -7085
  5. package/package.json +84 -83
  6. package/src/components/BatchUploadFonts.jsx +655 -655
  7. package/src/components/BulkActions.jsx +99 -99
  8. package/src/components/ExistingDocumentResolver.jsx +152 -152
  9. package/src/components/FontReviewCard.jsx +455 -455
  10. package/src/components/FontScriptUploaderComponent.jsx +463 -463
  11. package/src/components/GenerateCollectionsPairsComponent.jsx +259 -259
  12. package/src/components/KeyValueInput.jsx +95 -95
  13. package/src/components/KeyValueReferenceInput.jsx +267 -267
  14. package/src/components/NestedObjectArraySelector.jsx +146 -146
  15. package/src/components/PriceInput.jsx +26 -26
  16. package/src/components/PrimaryCollectionGeneratorTypeface.jsx +116 -116
  17. package/src/components/RegenerateSubfamiliesComponent.jsx +185 -185
  18. package/src/components/SetOTF.jsx +87 -87
  19. package/src/components/SingleUploaderTool.jsx +674 -674
  20. package/src/components/StatusDisplay.jsx +26 -26
  21. package/src/components/StyleCountInput.jsx +16 -16
  22. package/src/components/UpdateScriptsComponent.jsx +76 -76
  23. package/src/components/UploadButton.jsx +43 -43
  24. package/src/components/UploadModal.jsx +309 -309
  25. package/src/components/UploadScriptsComponent.jsx +539 -539
  26. package/src/components/UploadStep1Settings.jsx +272 -272
  27. package/src/components/UploadStep2Review.jsx +478 -478
  28. package/src/components/UploadStep3Execute.jsx +234 -234
  29. package/src/components/UploadStep3bInstances.jsx +396 -396
  30. package/src/components/UploadSummary.jsx +196 -196
  31. package/src/components/VariableInstanceReferencesInput.jsx +190 -190
  32. package/src/hooks/useNestedObjects.js +92 -92
  33. package/src/hooks/useSanityClient.js +9 -9
  34. package/src/index.js +120 -120
  35. package/src/schema/openTypeField.js +1995 -1995
  36. package/src/schema/styleCountField.js +12 -12
  37. package/src/schema/stylesField.js +302 -302
  38. package/src/schema/stylisticSetField.js +301 -301
  39. package/src/utils/buildUploadPlan.js +326 -326
  40. package/src/utils/executeUploadPlan.js +430 -430
  41. package/src/utils/executionReducer.js +56 -56
  42. package/src/utils/fontHelpers.js +281 -281
  43. package/src/utils/generateCssFile.js +207 -207
  44. package/src/utils/generateFontData.js +98 -98
  45. package/src/utils/generateFontFile.js +38 -38
  46. package/src/utils/generateKeywords.js +185 -185
  47. package/src/utils/generateSubset.js +45 -45
  48. package/src/utils/getEmptyFontKit.js +101 -101
  49. package/src/utils/parseFont.js +56 -56
  50. package/src/utils/parseVariableFontInstances.js +301 -301
  51. package/src/utils/planReducer.js +531 -531
  52. package/src/utils/planTypes.js +183 -183
  53. package/src/utils/processFontFiles.js +530 -530
  54. package/src/utils/regenerateFontData.js +146 -146
  55. package/src/utils/resolveExistingFont.js +87 -87
  56. package/src/utils/retitleFontEntries.js +154 -154
  57. package/src/utils/sanitizeForSanityId.js +65 -65
  58. package/src/utils/setupDecompressors.js +27 -27
  59. package/src/utils/updateFontPrices.js +94 -94
  60. package/src/utils/updateTypefaceDocument.js +162 -162
  61. package/src/utils/uploadFontFiles.js +405 -405
  62. package/src/utils/utils.js +24 -24
@@ -1,12 +1,12 @@
1
- // Sanity schema field definition for displaying the total style count — uses StyleCountInput component
2
- import { StyleCountInput } from '../components/StyleCountInput.jsx';
3
-
4
- export const styleCountField = {
5
- name: 'styleCount',
6
- type: 'number',
7
- group: 'styles',
8
- components: {
9
- input: StyleCountInput,
10
- },
11
- readOnly: true,
12
- };
1
+ // Sanity schema field definition for displaying the total style count — uses StyleCountInput component
2
+ import { StyleCountInput } from '../components/StyleCountInput.jsx';
3
+
4
+ export const styleCountField = {
5
+ name: 'styleCount',
6
+ type: 'number',
7
+ group: 'styles',
8
+ components: {
9
+ input: StyleCountInput,
10
+ },
11
+ readOnly: true,
12
+ };
@@ -1,302 +1,302 @@
1
- // Sanity schema factory function for the Styles object field — call createStylesField(options) to generate the field definition for a typeface document
2
- import React from 'react';
3
- import { AdvancedRefArray } from '@liiift-studio/sanity-advanced-reference-array';
4
- import { RegenerateSubfamiliesComponent } from '../components/RegenerateSubfamiliesComponent.jsx';
5
- import { GenerateCollectionsPairsComponent } from '../components/GenerateCollectionsPairsComponent.jsx';
6
- import { PrimaryCollectionGeneratorTypeface } from '../components/PrimaryCollectionGeneratorTypeface.jsx';
7
- import { StyleCountInput } from '../components/StyleCountInput.jsx';
8
-
9
- // Inline style count field — injected into the styles object when styleCount option is true
10
- const inlineStyleCountField = {
11
- name: 'styleCount',
12
- type: 'number',
13
- components: {
14
- input: StyleCountInput,
15
- },
16
- readOnly: true,
17
- };
18
-
19
- // Returns extra GROQ params scoped to the current typeface document
20
- const typefaceParams = (doc) => ({ typefaceName: doc?.title || '' });
21
-
22
- // AdvancedRefArray wrapper — limits search results to fonts belonging to this typeface
23
- const FontsRefArray = (props) => React.createElement(AdvancedRefArray, {
24
- ...props,
25
- filterGroq: 'lower(typefaceName) == lower($typefaceName)',
26
- filterParams: typefaceParams,
27
- });
28
-
29
- // AdvancedRefArray wrapper — limits search results to variable fonts belonging to this typeface
30
- const VariableFontsRefArray = (props) => React.createElement(AdvancedRefArray, {
31
- ...props,
32
- filterGroq: 'lower(typefaceName) == lower($typefaceName) && variableFont == true',
33
- filterParams: typefaceParams,
34
- });
35
-
36
- // Conditionally includes a field definition in an array
37
- const field = (condition, def) => condition ? [def] : [];
38
-
39
- // GROQ filter — fonts from the same typeface, excluding items already in the array
40
- const fontsFilter = async ({ getClient, document, parent }) => {
41
- const client = getClient({ apiVersion: '2022-11-09' });
42
- const typefaceName = document.title;
43
- const fonts = await client.fetch('*[_type == "font" && lower(typefaceName) == lower($typefaceName)]', { typefaceName });
44
- const relatedItemsFiltered = fonts.map(f => f._id).filter(Boolean);
45
- const existingItems = (parent || []).map(f => f._ref).filter(Boolean);
46
- return {
47
- filter: '!(_id in $existingItems) && (_id in $relatedItemsFiltered)',
48
- params: { existingItems, relatedItemsFiltered },
49
- };
50
- };
51
-
52
- // GROQ filter — variable fonts from the same typeface, excluding items already in the array
53
- const variableFontsFilter = async ({ getClient, document, parent }) => {
54
- const client = getClient({ apiVersion: '2022-11-09' });
55
- const typefaceName = document.title;
56
- const existingItems = (parent || []).map(f => f._ref).filter(Boolean);
57
- const fonts = await client.fetch('*[_type == "font" && typefaceName == $typefaceName && variableFont == true]', { typefaceName });
58
- const relatedItemsFiltered = fonts.map(f => f._id).filter(Boolean);
59
- return {
60
- filter: '!(_id in $existingItems) && (_id in $relatedItemsFiltered)',
61
- params: { existingItems, relatedItemsFiltered },
62
- };
63
- };
64
-
65
- // GROQ filter — non-variable fonts already in the subfamily's fonts array, for preferred style picker
66
- const subfamilyPreferredStyleFilter = async ({ getClient, document, parent }) => {
67
- const client = getClient({ apiVersion: '2022-11-09' });
68
- const typefaceName = document.title;
69
- const fonts = await client.fetch('*[_type == "font" && typefaceName == $typefaceName && variableFont == false]', { typefaceName });
70
- const relatedItemsFiltered = fonts.map(f => f._id).filter(Boolean);
71
- const existingItems = (parent.fonts || []).map(f => f._ref).filter(Boolean);
72
- return {
73
- filter: '(_id in $existingItems) && (_id in $relatedItemsFiltered)',
74
- params: { existingItems, relatedItemsFiltered },
75
- };
76
- };
77
-
78
- /**
79
- * Generates the Styles object field for a typeface document with configurable per-site options.
80
- * @param {Object} [options]
81
- * @param {boolean} [options.free=false] - Include "Free Typeface" boolean
82
- * @param {boolean} [options.displayStyles=true] - Show "Display All Styles" toggle to editors
83
- * @param {boolean} [options.sortHeaviestFirst=false] - Include sort order toggle
84
- * @param {boolean} [options.buySectionColumns=false] - Include multi-column buy section toggle
85
- * @param {boolean} [options.fontSizeMultiplier=false] - Include style grid font size multiplier
86
- * @param {boolean} [options.serif=false] - Include serif/sans classification field
87
- * @param {boolean} [options.regenerateSubfamilies=false] - Include RegenerateSubfamilies action
88
- * @param {boolean} [options.subfamilyFontSizeMultiplier=false] - Include per-subfamily font size multiplier
89
- * @param {boolean} [options.subfamilyListOrder=false] - Include per-subfamily manual order toggle
90
- * @param {boolean} [options.subfamilyPreferredStyle=false] - Include per-subfamily preferred style picker
91
- * @param {boolean} [options.subfamilyFontFilter=false] - Filter subfamily font picker to typeface fonts only
92
- * @param {boolean} [options.subfamilyPreview=false] - Include preview on subfamily array items
93
- * @param {boolean} [options.pairs=true] - Show pairs array to editors
94
- * @param {boolean} [options.styleCount=false] - Include style count display above fonts array
95
- */
96
- export function createStylesField({
97
- free = false,
98
- displayStyles = true,
99
- sortHeaviestFirst = false,
100
- buySectionColumns = false,
101
- fontSizeMultiplier = false,
102
- serif = false,
103
- regenerateSubfamilies = false,
104
- subfamilyFontSizeMultiplier = false,
105
- subfamilyListOrder = false,
106
- subfamilyPreferredStyle = false,
107
- subfamilyFontFilter = false,
108
- subfamilyPreview = false,
109
- pairs = true,
110
- generateCollections = false,
111
- generateFullFamilyCollection = false,
112
- styleCount = false,
113
- } = {}) {
114
-
115
- const subfamilyFields = [
116
- {
117
- title: 'Title',
118
- name: 'title',
119
- type: 'string',
120
- },
121
- ...field(subfamilyFontSizeMultiplier, {
122
- title: 'Subfamily Font Size Multiplier',
123
- name: 'fontSizeMultiplier',
124
- type: 'number',
125
- initialValue: 1,
126
- description: 'Adjust font size for this subfamily in the Family Overview (Design Space). Default is 1.0 (100%). Range: 0.5 to 2.0',
127
- validation: Rule => Rule.min(0.5).max(2.0).precision(2),
128
- }),
129
- ...field(subfamilyListOrder, {
130
- title: 'Use List Order',
131
- name: 'useListOrder',
132
- type: 'boolean',
133
- initialValue: false,
134
- description: 'Display fonts in the manual order listed below, bypassing programmatic weight-based sorting in the Family Overview.',
135
- }),
136
- {
137
- title: 'Fonts',
138
- name: 'fonts',
139
- type: 'array',
140
- components: { input: FontsRefArray },
141
- of: [{ type: 'reference', weak: true, to: [{ type: 'font' }] }],
142
- options: {
143
- sortable: true,
144
- ...(subfamilyFontFilter ? { filter: fontsFilter } : {}),
145
- },
146
- },
147
- ...field(subfamilyPreferredStyle, {
148
- title: 'Subfamily Preferred Style',
149
- name: 'preferredStyle',
150
- type: 'reference',
151
- weak: true,
152
- to: [{ type: 'font' }],
153
- options: { filter: subfamilyPreferredStyleFilter },
154
- }),
155
- ];
156
-
157
- const subfamilyItem = {
158
- type: 'object',
159
- fields: subfamilyFields,
160
- ...(subfamilyPreview ? {
161
- preview: {
162
- select: { title: 'title', fonts: 'fonts' },
163
- prepare({ title, fonts }) {
164
- return { title, subtitle: `${(fonts || []).length} fonts` };
165
- },
166
- },
167
- } : {}),
168
- };
169
-
170
- const fields = [
171
- ...field(free, {
172
- title: 'Free Typeface',
173
- name: 'free',
174
- type: 'boolean',
175
- initialValue: false,
176
- description: 'This typeface is free to download and use. This will alter the "Buy" button and checkout experience.',
177
- }),
178
- {
179
- title: 'Display All Styles',
180
- name: 'displayStyles',
181
- type: 'boolean',
182
- initialValue: true,
183
- hidden: !displayStyles,
184
- description: 'Show all Font Styles below collections in Buy Section',
185
- },
186
- ...field(sortHeaviestFirst, {
187
- title: 'Sort Fonts Heaviest to Lightest',
188
- name: 'sortHeaviestFirst',
189
- type: 'boolean',
190
- initialValue: false,
191
- description: 'Sort fonts by weight from heaviest (900) to lightest (100). Default is lightest to heaviest (industry standard).',
192
- }),
193
- ...field(buySectionColumns, {
194
- title: 'Multi Column Buy Section',
195
- name: 'buySectionColumns',
196
- type: 'boolean',
197
- initialValue: true,
198
- description: 'Choose Single Column or Multi Column for the Buy Section, Default is Multi Column',
199
- }),
200
- ...field(fontSizeMultiplier, {
201
- title: 'Style Grid Font Size Multiplier',
202
- name: 'fontSizeMultiplier',
203
- type: 'number',
204
- initialValue: 1,
205
- description: 'Adjust font size in the buy section style grid. Default is 1.0 (100%). Range: 0.5 to 2.0',
206
- validation: Rule => Rule.min(0.5).max(2.0).precision(2),
207
- }),
208
- ...field(serif, {
209
- title: 'Includes Serifs',
210
- name: 'serif',
211
- type: 'boolean',
212
- initialValue: false,
213
- description: 'Check if this typeface includes serif letterforms. Used for typeface overview serif/sans filters. Frontend automatically treats non-serif typefaces as sans serif.',
214
- }),
215
- ...field(styleCount, inlineStyleCountField),
216
- {
217
- title: 'Fonts',
218
- name: 'fonts',
219
- type: 'array',
220
- components: { input: FontsRefArray },
221
- of: [{
222
- type: 'reference',
223
- weak: true,
224
- to: [{ type: 'font' }],
225
- options: { filter: fontsFilter },
226
- }],
227
- options: { sortable: true },
228
- },
229
- {
230
- title: 'Variable Fonts',
231
- name: 'variableFont',
232
- type: 'array',
233
- components: { input: VariableFontsRefArray },
234
- of: [{
235
- type: 'reference',
236
- weak: true,
237
- to: [{ type: 'font' }],
238
- options: { filter: variableFontsFilter },
239
- }],
240
- description: 'Variable fonts are automatically included as a bonus when customers purchase all non-variable styles of this typeface.',
241
- options: { sortable: true },
242
- },
243
- ...field(regenerateSubfamilies, {
244
- title: 'Regenerate Subfamilies',
245
- name: 'regenerateSubfamilies',
246
- type: 'string',
247
- hidden: ({ parent }) => !parent?.subfamilies?.length || !parent?.fonts?.length,
248
- description: 'Regenerates subfamily groups based on the fonts in this typeface.',
249
- components: { input: RegenerateSubfamiliesComponent },
250
- }),
251
- {
252
- title: 'Subfamilies',
253
- name: 'subfamilies',
254
- type: 'array',
255
- of: [subfamilyItem],
256
- },
257
- ...field(generateCollections, {
258
- title: 'Generate Collections and Pairs',
259
- name: 'generateCollections',
260
- type: 'string',
261
- description: "Generate Collections and Pairs from the typeface's fonts.",
262
- components: { input: GenerateCollectionsPairsComponent },
263
- hidden: ({ parent }) => !parent?.fonts?.length,
264
- }),
265
- ...field(generateFullFamilyCollection, {
266
- title: 'Generate Full Family Collection',
267
- name: 'generateCollectionGroup',
268
- type: 'string',
269
- description: 'Generate a Collection that includes all styles from this typeface.',
270
- components: { input: PrimaryCollectionGeneratorTypeface },
271
- hidden: ({ parent }) => !parent?.fonts?.length,
272
- }),
273
- {
274
- title: 'Collections',
275
- name: 'collections',
276
- type: 'array',
277
- components: { input: AdvancedRefArray },
278
- of: [{ type: 'reference', weak: true, to: [{ type: 'collection' }] }],
279
- options: { sortable: true },
280
- validation: Rule => Rule.unique(),
281
- },
282
- {
283
- title: 'Pairs',
284
- name: 'pairs',
285
- type: 'array',
286
- components: { input: AdvancedRefArray },
287
- of: [{ type: 'reference', weak: true, to: [{ type: 'pair' }] }],
288
- options: { sortable: true },
289
- validation: Rule => Rule.unique(),
290
- hidden: !pairs,
291
- },
292
- ];
293
-
294
- return {
295
- title: 'Styles',
296
- name: 'styles',
297
- type: 'object',
298
- group: 'styles',
299
- fields,
300
- options: { collapsible: true },
301
- };
302
- }
1
+ // Sanity schema factory function for the Styles object field — call createStylesField(options) to generate the field definition for a typeface document
2
+ import React from 'react';
3
+ import { AdvancedRefArray } from '@liiift-studio/sanity-advanced-reference-array';
4
+ import { RegenerateSubfamiliesComponent } from '../components/RegenerateSubfamiliesComponent.jsx';
5
+ import { GenerateCollectionsPairsComponent } from '../components/GenerateCollectionsPairsComponent.jsx';
6
+ import { PrimaryCollectionGeneratorTypeface } from '../components/PrimaryCollectionGeneratorTypeface.jsx';
7
+ import { StyleCountInput } from '../components/StyleCountInput.jsx';
8
+
9
+ // Inline style count field — injected into the styles object when styleCount option is true
10
+ const inlineStyleCountField = {
11
+ name: 'styleCount',
12
+ type: 'number',
13
+ components: {
14
+ input: StyleCountInput,
15
+ },
16
+ readOnly: true,
17
+ };
18
+
19
+ // Returns extra GROQ params scoped to the current typeface document
20
+ const typefaceParams = (doc) => ({ typefaceName: doc?.title || '' });
21
+
22
+ // AdvancedRefArray wrapper — limits search results to fonts belonging to this typeface
23
+ const FontsRefArray = (props) => React.createElement(AdvancedRefArray, {
24
+ ...props,
25
+ filterGroq: 'lower(typefaceName) == lower($typefaceName)',
26
+ filterParams: typefaceParams,
27
+ });
28
+
29
+ // AdvancedRefArray wrapper — limits search results to variable fonts belonging to this typeface
30
+ const VariableFontsRefArray = (props) => React.createElement(AdvancedRefArray, {
31
+ ...props,
32
+ filterGroq: 'lower(typefaceName) == lower($typefaceName) && variableFont == true',
33
+ filterParams: typefaceParams,
34
+ });
35
+
36
+ // Conditionally includes a field definition in an array
37
+ const field = (condition, def) => condition ? [def] : [];
38
+
39
+ // GROQ filter — fonts from the same typeface, excluding items already in the array
40
+ const fontsFilter = async ({ getClient, document, parent }) => {
41
+ const client = getClient({ apiVersion: '2022-11-09' });
42
+ const typefaceName = document.title;
43
+ const fonts = await client.fetch('*[_type == "font" && lower(typefaceName) == lower($typefaceName)]', { typefaceName });
44
+ const relatedItemsFiltered = fonts.map(f => f._id).filter(Boolean);
45
+ const existingItems = (parent || []).map(f => f._ref).filter(Boolean);
46
+ return {
47
+ filter: '!(_id in $existingItems) && (_id in $relatedItemsFiltered)',
48
+ params: { existingItems, relatedItemsFiltered },
49
+ };
50
+ };
51
+
52
+ // GROQ filter — variable fonts from the same typeface, excluding items already in the array
53
+ const variableFontsFilter = async ({ getClient, document, parent }) => {
54
+ const client = getClient({ apiVersion: '2022-11-09' });
55
+ const typefaceName = document.title;
56
+ const existingItems = (parent || []).map(f => f._ref).filter(Boolean);
57
+ const fonts = await client.fetch('*[_type == "font" && typefaceName == $typefaceName && variableFont == true]', { typefaceName });
58
+ const relatedItemsFiltered = fonts.map(f => f._id).filter(Boolean);
59
+ return {
60
+ filter: '!(_id in $existingItems) && (_id in $relatedItemsFiltered)',
61
+ params: { existingItems, relatedItemsFiltered },
62
+ };
63
+ };
64
+
65
+ // GROQ filter — non-variable fonts already in the subfamily's fonts array, for preferred style picker
66
+ const subfamilyPreferredStyleFilter = async ({ getClient, document, parent }) => {
67
+ const client = getClient({ apiVersion: '2022-11-09' });
68
+ const typefaceName = document.title;
69
+ const fonts = await client.fetch('*[_type == "font" && typefaceName == $typefaceName && variableFont == false]', { typefaceName });
70
+ const relatedItemsFiltered = fonts.map(f => f._id).filter(Boolean);
71
+ const existingItems = (parent.fonts || []).map(f => f._ref).filter(Boolean);
72
+ return {
73
+ filter: '(_id in $existingItems) && (_id in $relatedItemsFiltered)',
74
+ params: { existingItems, relatedItemsFiltered },
75
+ };
76
+ };
77
+
78
+ /**
79
+ * Generates the Styles object field for a typeface document with configurable per-site options.
80
+ * @param {Object} [options]
81
+ * @param {boolean} [options.free=false] - Include "Free Typeface" boolean
82
+ * @param {boolean} [options.displayStyles=true] - Show "Display All Styles" toggle to editors
83
+ * @param {boolean} [options.sortHeaviestFirst=false] - Include sort order toggle
84
+ * @param {boolean} [options.buySectionColumns=false] - Include multi-column buy section toggle
85
+ * @param {boolean} [options.fontSizeMultiplier=false] - Include style grid font size multiplier
86
+ * @param {boolean} [options.serif=false] - Include serif/sans classification field
87
+ * @param {boolean} [options.regenerateSubfamilies=false] - Include RegenerateSubfamilies action
88
+ * @param {boolean} [options.subfamilyFontSizeMultiplier=false] - Include per-subfamily font size multiplier
89
+ * @param {boolean} [options.subfamilyListOrder=false] - Include per-subfamily manual order toggle
90
+ * @param {boolean} [options.subfamilyPreferredStyle=false] - Include per-subfamily preferred style picker
91
+ * @param {boolean} [options.subfamilyFontFilter=false] - Filter subfamily font picker to typeface fonts only
92
+ * @param {boolean} [options.subfamilyPreview=false] - Include preview on subfamily array items
93
+ * @param {boolean} [options.pairs=true] - Show pairs array to editors
94
+ * @param {boolean} [options.styleCount=false] - Include style count display above fonts array
95
+ */
96
+ export function createStylesField({
97
+ free = false,
98
+ displayStyles = true,
99
+ sortHeaviestFirst = false,
100
+ buySectionColumns = false,
101
+ fontSizeMultiplier = false,
102
+ serif = false,
103
+ regenerateSubfamilies = false,
104
+ subfamilyFontSizeMultiplier = false,
105
+ subfamilyListOrder = false,
106
+ subfamilyPreferredStyle = false,
107
+ subfamilyFontFilter = false,
108
+ subfamilyPreview = false,
109
+ pairs = true,
110
+ generateCollections = false,
111
+ generateFullFamilyCollection = false,
112
+ styleCount = false,
113
+ } = {}) {
114
+
115
+ const subfamilyFields = [
116
+ {
117
+ title: 'Title',
118
+ name: 'title',
119
+ type: 'string',
120
+ },
121
+ ...field(subfamilyFontSizeMultiplier, {
122
+ title: 'Subfamily Font Size Multiplier',
123
+ name: 'fontSizeMultiplier',
124
+ type: 'number',
125
+ initialValue: 1,
126
+ description: 'Adjust font size for this subfamily in the Family Overview (Design Space). Default is 1.0 (100%). Range: 0.5 to 2.0',
127
+ validation: Rule => Rule.min(0.5).max(2.0).precision(2),
128
+ }),
129
+ ...field(subfamilyListOrder, {
130
+ title: 'Use List Order',
131
+ name: 'useListOrder',
132
+ type: 'boolean',
133
+ initialValue: false,
134
+ description: 'Display fonts in the manual order listed below, bypassing programmatic weight-based sorting in the Family Overview.',
135
+ }),
136
+ {
137
+ title: 'Fonts',
138
+ name: 'fonts',
139
+ type: 'array',
140
+ components: { input: FontsRefArray },
141
+ of: [{ type: 'reference', weak: true, to: [{ type: 'font' }] }],
142
+ options: {
143
+ sortable: true,
144
+ ...(subfamilyFontFilter ? { filter: fontsFilter } : {}),
145
+ },
146
+ },
147
+ ...field(subfamilyPreferredStyle, {
148
+ title: 'Subfamily Preferred Style',
149
+ name: 'preferredStyle',
150
+ type: 'reference',
151
+ weak: true,
152
+ to: [{ type: 'font' }],
153
+ options: { filter: subfamilyPreferredStyleFilter },
154
+ }),
155
+ ];
156
+
157
+ const subfamilyItem = {
158
+ type: 'object',
159
+ fields: subfamilyFields,
160
+ ...(subfamilyPreview ? {
161
+ preview: {
162
+ select: { title: 'title', fonts: 'fonts' },
163
+ prepare({ title, fonts }) {
164
+ return { title, subtitle: `${(fonts || []).length} fonts` };
165
+ },
166
+ },
167
+ } : {}),
168
+ };
169
+
170
+ const fields = [
171
+ ...field(free, {
172
+ title: 'Free Typeface',
173
+ name: 'free',
174
+ type: 'boolean',
175
+ initialValue: false,
176
+ description: 'This typeface is free to download and use. This will alter the "Buy" button and checkout experience.',
177
+ }),
178
+ {
179
+ title: 'Display All Styles',
180
+ name: 'displayStyles',
181
+ type: 'boolean',
182
+ initialValue: true,
183
+ hidden: !displayStyles,
184
+ description: 'Show all Font Styles below collections in Buy Section',
185
+ },
186
+ ...field(sortHeaviestFirst, {
187
+ title: 'Sort Fonts Heaviest to Lightest',
188
+ name: 'sortHeaviestFirst',
189
+ type: 'boolean',
190
+ initialValue: false,
191
+ description: 'Sort fonts by weight from heaviest (900) to lightest (100). Default is lightest to heaviest (industry standard).',
192
+ }),
193
+ ...field(buySectionColumns, {
194
+ title: 'Multi Column Buy Section',
195
+ name: 'buySectionColumns',
196
+ type: 'boolean',
197
+ initialValue: true,
198
+ description: 'Choose Single Column or Multi Column for the Buy Section, Default is Multi Column',
199
+ }),
200
+ ...field(fontSizeMultiplier, {
201
+ title: 'Style Grid Font Size Multiplier',
202
+ name: 'fontSizeMultiplier',
203
+ type: 'number',
204
+ initialValue: 1,
205
+ description: 'Adjust font size in the buy section style grid. Default is 1.0 (100%). Range: 0.5 to 2.0',
206
+ validation: Rule => Rule.min(0.5).max(2.0).precision(2),
207
+ }),
208
+ ...field(serif, {
209
+ title: 'Includes Serifs',
210
+ name: 'serif',
211
+ type: 'boolean',
212
+ initialValue: false,
213
+ description: 'Check if this typeface includes serif letterforms. Used for typeface overview serif/sans filters. Frontend automatically treats non-serif typefaces as sans serif.',
214
+ }),
215
+ ...field(styleCount, inlineStyleCountField),
216
+ {
217
+ title: 'Fonts',
218
+ name: 'fonts',
219
+ type: 'array',
220
+ components: { input: FontsRefArray },
221
+ of: [{
222
+ type: 'reference',
223
+ weak: true,
224
+ to: [{ type: 'font' }],
225
+ options: { filter: fontsFilter },
226
+ }],
227
+ options: { sortable: true },
228
+ },
229
+ {
230
+ title: 'Variable Fonts',
231
+ name: 'variableFont',
232
+ type: 'array',
233
+ components: { input: VariableFontsRefArray },
234
+ of: [{
235
+ type: 'reference',
236
+ weak: true,
237
+ to: [{ type: 'font' }],
238
+ options: { filter: variableFontsFilter },
239
+ }],
240
+ description: 'Variable fonts are automatically included as a bonus when customers purchase all non-variable styles of this typeface.',
241
+ options: { sortable: true },
242
+ },
243
+ ...field(regenerateSubfamilies, {
244
+ title: 'Regenerate Subfamilies',
245
+ name: 'regenerateSubfamilies',
246
+ type: 'string',
247
+ hidden: ({ parent }) => !parent?.subfamilies?.length || !parent?.fonts?.length,
248
+ description: 'Regenerates subfamily groups based on the fonts in this typeface.',
249
+ components: { input: RegenerateSubfamiliesComponent },
250
+ }),
251
+ {
252
+ title: 'Subfamilies',
253
+ name: 'subfamilies',
254
+ type: 'array',
255
+ of: [subfamilyItem],
256
+ },
257
+ ...field(generateCollections, {
258
+ title: 'Generate Collections and Pairs',
259
+ name: 'generateCollections',
260
+ type: 'string',
261
+ description: "Generate Collections and Pairs from the typeface's fonts.",
262
+ components: { input: GenerateCollectionsPairsComponent },
263
+ hidden: ({ parent }) => !parent?.fonts?.length,
264
+ }),
265
+ ...field(generateFullFamilyCollection, {
266
+ title: 'Generate Full Family Collection',
267
+ name: 'generateCollectionGroup',
268
+ type: 'string',
269
+ description: 'Generate a Collection that includes all styles from this typeface.',
270
+ components: { input: PrimaryCollectionGeneratorTypeface },
271
+ hidden: ({ parent }) => !parent?.fonts?.length,
272
+ }),
273
+ {
274
+ title: 'Collections',
275
+ name: 'collections',
276
+ type: 'array',
277
+ components: { input: AdvancedRefArray },
278
+ of: [{ type: 'reference', weak: true, to: [{ type: 'collection' }] }],
279
+ options: { sortable: true },
280
+ validation: Rule => Rule.unique(),
281
+ },
282
+ {
283
+ title: 'Pairs',
284
+ name: 'pairs',
285
+ type: 'array',
286
+ components: { input: AdvancedRefArray },
287
+ of: [{ type: 'reference', weak: true, to: [{ type: 'pair' }] }],
288
+ options: { sortable: true },
289
+ validation: Rule => Rule.unique(),
290
+ hidden: !pairs,
291
+ },
292
+ ];
293
+
294
+ return {
295
+ title: 'Styles',
296
+ name: 'styles',
297
+ type: 'object',
298
+ group: 'styles',
299
+ fields,
300
+ options: { collapsible: true },
301
+ };
302
+ }