@liiift-studio/sanity-font-manager 2.3.1 → 2.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -0
- package/dist/index.js +156 -0
- package/dist/index.mjs +241 -88
- package/package.json +1 -1
- package/src/components/PrimaryCollectionGeneratorTypeface.jsx +116 -0
- package/src/components/SetOTF.jsx +87 -0
- package/src/components/StyleCountInput.jsx +16 -0
- package/src/index.js +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liiift-studio/sanity-font-manager",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.3",
|
|
4
4
|
"description": "Sanity Studio plugin — full font management suite with batch upload, format conversion, metadata extraction, CSS generation, collection/pair generation, and script variant support. Supports Sanity v3, v4, and v5.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Liiift Studio",
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Generates a single primary full-family collection from a typeface's linked fonts and prepends it to the existing collections array
|
|
2
|
+
|
|
3
|
+
import React, { useCallback, useState } from 'react';
|
|
4
|
+
import { Stack, Flex, Text, Button, Card, Spinner } from '@sanity/ui';
|
|
5
|
+
import { useFormValue } from 'sanity';
|
|
6
|
+
import { nanoid } from 'nanoid';
|
|
7
|
+
|
|
8
|
+
import { useSanityClient } from '../hooks/useSanityClient';
|
|
9
|
+
import StatusDisplay from './StatusDisplay';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generates a full-family collection document from the typeface's linked fonts
|
|
13
|
+
* and prepends it to the existing styles.collections array.
|
|
14
|
+
*/
|
|
15
|
+
export const PrimaryCollectionGeneratorTypeface = () => {
|
|
16
|
+
const client = useSanityClient();
|
|
17
|
+
|
|
18
|
+
const [status, setStatus] = useState('ready');
|
|
19
|
+
const [ready, setReady] = useState(true);
|
|
20
|
+
const [price, setPrice] = useState(
|
|
21
|
+
process.env.SANITY_STUDIO_DEFAULT_COLLECTION_PRICE || 100
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const fonts = useFormValue(['styles', 'fonts']);
|
|
25
|
+
const title = useFormValue(['title']);
|
|
26
|
+
const preferredStyle = useFormValue(['preferredStyle']);
|
|
27
|
+
const docId = useFormValue(['_id']);
|
|
28
|
+
const styles = useFormValue(['styles']);
|
|
29
|
+
|
|
30
|
+
/** Creates or replaces the full-family collection document and prepends it to the typeface's collections array. */
|
|
31
|
+
const generateCollection = useCallback(async () => {
|
|
32
|
+
setStatus('Generating collection...');
|
|
33
|
+
setReady(false);
|
|
34
|
+
|
|
35
|
+
let id = title.toLowerCase().replace(/\s+/g, '-').slice(0, 200);
|
|
36
|
+
if (!id.includes('collection')) id += '-collection';
|
|
37
|
+
|
|
38
|
+
const colTitle = id.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
|
39
|
+
|
|
40
|
+
const collectionDoc = {
|
|
41
|
+
_key: nanoid(),
|
|
42
|
+
_id: id,
|
|
43
|
+
title: colTitle,
|
|
44
|
+
slug: { _type: 'slug', current: id },
|
|
45
|
+
price: Number(price) || 0,
|
|
46
|
+
fonts: Object.values(fonts),
|
|
47
|
+
preferredStyle: preferredStyle,
|
|
48
|
+
_type: 'collection',
|
|
49
|
+
type: 'collection',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const sanityCollection = await client.createOrReplace(collectionDoc);
|
|
54
|
+
const collections = styles.collections || [];
|
|
55
|
+
|
|
56
|
+
await client.patch(docId)
|
|
57
|
+
.setIfMissing({ styles: {} })
|
|
58
|
+
.set({
|
|
59
|
+
styles: {
|
|
60
|
+
...styles,
|
|
61
|
+
collections: [{
|
|
62
|
+
_type: 'reference',
|
|
63
|
+
_key: nanoid(),
|
|
64
|
+
_ref: sanityCollection._id,
|
|
65
|
+
_weak: true,
|
|
66
|
+
}, ...collections],
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
.commit();
|
|
70
|
+
|
|
71
|
+
setStatus('Collection generated');
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error('Error creating collection:', err.message);
|
|
74
|
+
setStatus('Error generating collection');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
setReady(true);
|
|
78
|
+
}, [docId, fonts, price, preferredStyle, styles, title, client]);
|
|
79
|
+
|
|
80
|
+
if (!title || !fonts) return null;
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<Stack space={2}>
|
|
84
|
+
<StatusDisplay status={status} error={false} />
|
|
85
|
+
<Card border padding={2} shadow={1} radius={2}>
|
|
86
|
+
{ready ? (
|
|
87
|
+
<Stack space={3}>
|
|
88
|
+
<Flex align="center" gap={2} marginTop={1} marginBottom={1}>
|
|
89
|
+
<Text size={1} muted>Price</Text>
|
|
90
|
+
<Text size={1} muted>$</Text>
|
|
91
|
+
<input
|
|
92
|
+
value={price}
|
|
93
|
+
onChange={(e) => setPrice(e.target.value)}
|
|
94
|
+
type="number"
|
|
95
|
+
style={{ textAlign: 'end', padding: '5px', maxWidth: '75px' }}
|
|
96
|
+
/>
|
|
97
|
+
<Text size={1} muted>per full family</Text>
|
|
98
|
+
</Flex>
|
|
99
|
+
<Button
|
|
100
|
+
mode="ghost"
|
|
101
|
+
tone="primary"
|
|
102
|
+
style={{ width: '100%' }}
|
|
103
|
+
onClick={generateCollection}
|
|
104
|
+
text="Generate Full Family Collection"
|
|
105
|
+
/>
|
|
106
|
+
</Stack>
|
|
107
|
+
) : (
|
|
108
|
+
<Flex align="center" justify="center" gap={3} padding={4}>
|
|
109
|
+
<Spinner />
|
|
110
|
+
<Text muted size={1}>{status}</Text>
|
|
111
|
+
</Flex>
|
|
112
|
+
)}
|
|
113
|
+
</Card>
|
|
114
|
+
</Stack>
|
|
115
|
+
);
|
|
116
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Detects and sets active OpenType features on a typeface document from the first linked font's metadata
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { set, useFormValue } from 'sanity';
|
|
5
|
+
import { Stack, Button, Text } from '@sanity/ui';
|
|
6
|
+
import { useSanityClient } from '../hooks/useSanityClient';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Reads the first linked font's opentypeFeatures data and checks which configured
|
|
10
|
+
* feature keys are supported. Patches the field with the detected features array.
|
|
11
|
+
*/
|
|
12
|
+
export const SetOTF = (props) => {
|
|
13
|
+
const { onChange, value = {} } = props;
|
|
14
|
+
const client = useSanityClient();
|
|
15
|
+
const stylesObject = useFormValue(['styles']);
|
|
16
|
+
const [message, setMessage] = useState('');
|
|
17
|
+
|
|
18
|
+
/** Fetches the first font document and matches its OpenType features against the configured keys. */
|
|
19
|
+
const detect = async () => {
|
|
20
|
+
if (!stylesObject?.fonts?.length) {
|
|
21
|
+
setMessage('Error: No fonts found in styles. Please add at least one font first.');
|
|
22
|
+
setTimeout(() => setMessage(''), 5000);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const fontRef = stylesObject.fonts[0]?._ref;
|
|
27
|
+
if (!fontRef) {
|
|
28
|
+
setMessage('Error: Invalid font reference in styles.');
|
|
29
|
+
setTimeout(() => setMessage(''), 5000);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const font = await client.fetch('*[_type == "font" && _id == $id][0]', { id: fontRef });
|
|
35
|
+
|
|
36
|
+
if (!font) {
|
|
37
|
+
setMessage('Error: Could not find the referenced font.');
|
|
38
|
+
setTimeout(() => setMessage(''), 5000);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!font.opentypeFeatures?.chars) {
|
|
43
|
+
setMessage(`Error: No OpenType feature data found in "${font.title || 'this font'}". Generate font data first.`);
|
|
44
|
+
setTimeout(() => setMessage(''), 5000);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const features = [];
|
|
49
|
+
Object.keys(value).forEach(key => {
|
|
50
|
+
if (key !== 'features' && value[key]?.feature) {
|
|
51
|
+
const requiredFeatures = value[key].feature.split(' ');
|
|
52
|
+
const approved = requiredFeatures.every(v => font.opentypeFeatures.chars.includes(v));
|
|
53
|
+
if (approved) features.push(key);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
onChange(set({ ...value, features }));
|
|
58
|
+
setMessage(`Features detected: ${features.length ? features.join(', ') : 'none'}.`);
|
|
59
|
+
setTimeout(() => setMessage(''), 5000);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
setMessage('Error detecting features. Check the console for details.');
|
|
62
|
+
console.error('SetOTF detect error:', err);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Stack className="openType">
|
|
68
|
+
{value?.features?.length > 0 && (
|
|
69
|
+
<Text muted size={1} style={{ marginBottom: '0.5rem' }}>
|
|
70
|
+
Number of features: {value.features.length}
|
|
71
|
+
</Text>
|
|
72
|
+
)}
|
|
73
|
+
{!!stylesObject?.fonts?.length && (
|
|
74
|
+
<Button
|
|
75
|
+
text="Detect OTF"
|
|
76
|
+
mode="ghost"
|
|
77
|
+
onClick={detect}
|
|
78
|
+
style={{ borderRadius: '0 3px 0 0', marginBottom: '1rem' }}
|
|
79
|
+
/>
|
|
80
|
+
)}
|
|
81
|
+
{!!message && (
|
|
82
|
+
<Text muted size={1}><br />{message}<br /><br /></Text>
|
|
83
|
+
)}
|
|
84
|
+
{props.renderDefault(props)}
|
|
85
|
+
</Stack>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Displays the total count of static and variable font styles linked to a typeface document
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { Text } from '@sanity/ui';
|
|
5
|
+
import { useFormValue } from 'sanity';
|
|
6
|
+
|
|
7
|
+
/** Reads styles.fonts and styles.variableFont arrays and displays the combined count. */
|
|
8
|
+
export const StyleCountInput = (props) => {
|
|
9
|
+
const styles = useFormValue(['styles', 'fonts']) || [];
|
|
10
|
+
const vfStyles = useFormValue(['styles', 'variableFont']) || [];
|
|
11
|
+
const count = styles.length + vfStyles.length;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<Text size={1}>{count}</Text>
|
|
15
|
+
);
|
|
16
|
+
};
|
package/src/index.js
CHANGED
|
@@ -14,6 +14,9 @@ export { default as UploadButton } from './components/UploadButton.jsx';
|
|
|
14
14
|
export { KeyValueInput } from './components/KeyValueInput.jsx';
|
|
15
15
|
export { KeyValueReferenceInput } from './components/KeyValueReferenceInput.jsx';
|
|
16
16
|
export { VariableInstanceReferencesInput } from './components/VariableInstanceReferencesInput.jsx';
|
|
17
|
+
export { PrimaryCollectionGeneratorTypeface } from './components/PrimaryCollectionGeneratorTypeface.jsx';
|
|
18
|
+
export { SetOTF } from './components/SetOTF.jsx';
|
|
19
|
+
export { StyleCountInput } from './components/StyleCountInput.jsx';
|
|
17
20
|
|
|
18
21
|
// Hooks
|
|
19
22
|
export { useSanityClient } from './hooks/useSanityClient.js';
|