@liiift-studio/sanity-font-manager 2.2.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -0
- package/dist/index.js +393 -1
- package/dist/index.mjs +390 -1
- package/package.json +1 -1
- package/src/components/KeyValueInput.jsx +95 -0
- package/src/components/KeyValueReferenceInput.jsx +254 -0
- package/src/components/SingleUploaderTool.jsx +1 -1
- package/src/components/VariableInstanceReferencesInput.jsx +190 -0
- package/src/index.js +3 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// Generic key-value pair editor where values are weak Sanity document references — add, remove, reorder, and searchable picker
|
|
2
|
+
|
|
3
|
+
import React, { useState, useCallback, useEffect } from 'react';
|
|
4
|
+
import { Button, Stack, TextInput, Box, Card, Flex, Text, Dialog, Menu, MenuButton, MenuItem, Autocomplete } from '@sanity/ui';
|
|
5
|
+
import { AddIcon, ArrowDownIcon, ArrowUpIcon, TrashIcon, SyncIcon, EllipsisHorizontalIcon } from '@sanity/icons';
|
|
6
|
+
import { set, useFormValue } from 'sanity';
|
|
7
|
+
import { useSanityClient } from '../hooks/useSanityClient.js';
|
|
8
|
+
import { nanoid } from 'nanoid';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generic key-value pair editor where values are weak references to Sanity documents.
|
|
12
|
+
* Handles add/remove/reorder, a searchable reference picker dialog, and cached title display.
|
|
13
|
+
* @param {Array} value - Current array of { _key, key, value } pairs
|
|
14
|
+
* @param {Function} onChange - Sanity onChange callback
|
|
15
|
+
* @param {string} referenceType - Display label for the referenced document type (e.g. 'font')
|
|
16
|
+
* @param {Function} fetchReferences - async (client, doc) => [{ _id, title }] — populates the picker
|
|
17
|
+
* @param {ReactNode} topActions - Optional slot rendered above the pairs list (e.g. autofill buttons)
|
|
18
|
+
* @param {Object} schemaType - Sanity schemaType passed automatically by the Studio
|
|
19
|
+
*/
|
|
20
|
+
export function KeyValueReferenceInput(props) {
|
|
21
|
+
const { value = [], onChange, schemaType, referenceType, fetchReferences, topActions } = props;
|
|
22
|
+
|
|
23
|
+
const [pairs, setPairs] = useState(value);
|
|
24
|
+
const [referenceData, setReferenceData] = useState({});
|
|
25
|
+
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
26
|
+
const [editingIndex, setEditingIndex] = useState(null);
|
|
27
|
+
const [availableReferences, setAvailableReferences] = useState([]);
|
|
28
|
+
|
|
29
|
+
const sanityClient = useSanityClient();
|
|
30
|
+
const formDocument = useFormValue([]);
|
|
31
|
+
|
|
32
|
+
/** Fetches and caches display titles for all referenced documents whenever pairs change */
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const refIds = pairs.filter(p => p.value?._ref).map(p => p.value._ref);
|
|
35
|
+
if (refIds.length === 0) return;
|
|
36
|
+
|
|
37
|
+
if (!sanityClient) {
|
|
38
|
+
const fallback = {};
|
|
39
|
+
refIds.forEach(id => { fallback[id] = `Reference (${id.substring(0, 6)}...)`; });
|
|
40
|
+
setReferenceData(fallback);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
sanityClient.fetch(`*[_id in $ids]{_id, title}`, { ids: refIds })
|
|
45
|
+
.then(result => {
|
|
46
|
+
const map = {};
|
|
47
|
+
result.forEach(item => { map[item._id] = item.title; });
|
|
48
|
+
setReferenceData(map);
|
|
49
|
+
})
|
|
50
|
+
.catch(err => {
|
|
51
|
+
console.error('Error fetching reference data:', err);
|
|
52
|
+
const fallback = {};
|
|
53
|
+
refIds.forEach(id => { fallback[id] = `Reference (${id.substring(0, 6)}...)`; });
|
|
54
|
+
setReferenceData(fallback);
|
|
55
|
+
});
|
|
56
|
+
}, [pairs, sanityClient]);
|
|
57
|
+
|
|
58
|
+
/** Updates a field on a pair at the given index and syncs to Sanity */
|
|
59
|
+
const handlePairChange = useCallback((index, field, fieldValue) => {
|
|
60
|
+
const updatedPairs = pairs.map((pair, idx) => idx === index ? { ...pair, [field]: fieldValue } : pair);
|
|
61
|
+
setPairs(updatedPairs);
|
|
62
|
+
onChange(set(updatedPairs));
|
|
63
|
+
}, [pairs, onChange]);
|
|
64
|
+
|
|
65
|
+
/** Appends a new empty pair */
|
|
66
|
+
const handleAddPair = useCallback(() => {
|
|
67
|
+
const updatedPairs = [...pairs, { key: '', value: null, _key: nanoid() }];
|
|
68
|
+
setPairs(updatedPairs);
|
|
69
|
+
onChange(set(updatedPairs));
|
|
70
|
+
}, [pairs, onChange]);
|
|
71
|
+
|
|
72
|
+
/** Removes the pair at the given index */
|
|
73
|
+
const handleRemovePair = useCallback((index) => {
|
|
74
|
+
const updatedPairs = pairs.filter((_, idx) => idx !== index);
|
|
75
|
+
setPairs(updatedPairs);
|
|
76
|
+
onChange(set(updatedPairs));
|
|
77
|
+
}, [pairs, onChange]);
|
|
78
|
+
|
|
79
|
+
/** Swaps a pair with the one above it */
|
|
80
|
+
const handleMoveUp = useCallback((index) => {
|
|
81
|
+
if (index === 0) return;
|
|
82
|
+
const updatedPairs = [...pairs];
|
|
83
|
+
[updatedPairs[index], updatedPairs[index - 1]] = [updatedPairs[index - 1], updatedPairs[index]];
|
|
84
|
+
setPairs(updatedPairs);
|
|
85
|
+
onChange(set(updatedPairs));
|
|
86
|
+
}, [pairs, onChange]);
|
|
87
|
+
|
|
88
|
+
/** Swaps a pair with the one below it */
|
|
89
|
+
const handleMoveDown = useCallback((index) => {
|
|
90
|
+
if (index === pairs.length - 1) return;
|
|
91
|
+
const updatedPairs = [...pairs];
|
|
92
|
+
[updatedPairs[index], updatedPairs[index + 1]] = [updatedPairs[index + 1], updatedPairs[index]];
|
|
93
|
+
setPairs(updatedPairs);
|
|
94
|
+
onChange(set(updatedPairs));
|
|
95
|
+
}, [pairs, onChange]);
|
|
96
|
+
|
|
97
|
+
/** Opens the reference picker, calling fetchReferences to populate the list */
|
|
98
|
+
const openReferenceSelector = useCallback(async (index) => {
|
|
99
|
+
setEditingIndex(index);
|
|
100
|
+
if (!sanityClient) {
|
|
101
|
+
console.error('KeyValueReferenceInput: Sanity client not available');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
let refs;
|
|
106
|
+
if (fetchReferences) {
|
|
107
|
+
refs = await fetchReferences(sanityClient, formDocument);
|
|
108
|
+
} else if (referenceType) {
|
|
109
|
+
refs = await sanityClient.fetch(`*[_type == $type]{_id, title}`, { type: referenceType });
|
|
110
|
+
} else {
|
|
111
|
+
console.warn('KeyValueReferenceInput: provide a fetchReferences prop or referenceType');
|
|
112
|
+
refs = [];
|
|
113
|
+
}
|
|
114
|
+
setAvailableReferences(refs);
|
|
115
|
+
setIsDialogOpen(true);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.error('Error fetching available references:', err);
|
|
118
|
+
}
|
|
119
|
+
}, [sanityClient, fetchReferences, referenceType, formDocument]);
|
|
120
|
+
|
|
121
|
+
/** Closes the picker dialog and clears editing state */
|
|
122
|
+
const closeDialog = useCallback(() => {
|
|
123
|
+
setIsDialogOpen(false);
|
|
124
|
+
setEditingIndex(null);
|
|
125
|
+
}, []);
|
|
126
|
+
|
|
127
|
+
/** Writes the selected item as a weak reference and closes the dialog */
|
|
128
|
+
const handleReferenceSelect = useCallback((reference) => {
|
|
129
|
+
if (editingIndex === null) return;
|
|
130
|
+
handlePairChange(editingIndex, 'value', { _type: 'reference', _ref: reference._id, _weak: true });
|
|
131
|
+
closeDialog();
|
|
132
|
+
}, [editingIndex, handlePairChange, closeDialog]);
|
|
133
|
+
|
|
134
|
+
const referenceOptions = availableReferences.map(ref => ({ value: ref._id, title: ref.title }));
|
|
135
|
+
|
|
136
|
+
// Infer labels from schemaType if available
|
|
137
|
+
const keyField = schemaType?.options?.of?.[0]?.fields?.find(f => f.name === 'key');
|
|
138
|
+
const valueField = schemaType?.options?.of?.[0]?.fields?.find(f => f.name === 'value');
|
|
139
|
+
const keyTitle = keyField?.title || 'Key';
|
|
140
|
+
const valueTitle = valueField?.title || 'Value';
|
|
141
|
+
const keyPlaceholder = keyField?.placeholder || 'Enter key';
|
|
142
|
+
const pickerLabel = referenceType || valueTitle.toLowerCase();
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<Stack space={3}>
|
|
146
|
+
{topActions && <Box paddingBottom={2}>{topActions}</Box>}
|
|
147
|
+
|
|
148
|
+
<Box>
|
|
149
|
+
<Stack space={2}>
|
|
150
|
+
{pairs.map((pair, index) => (
|
|
151
|
+
<Box key={index} style={{ position: 'relative' }}>
|
|
152
|
+
{/* Reorder buttons */}
|
|
153
|
+
<div style={{ position: 'absolute', height: '100%', top: '0', left: '-5px', width: 'min-content', transform: 'translate(-100%, 0%)' }}>
|
|
154
|
+
<button className="manualButton manualButtonUp" style={{ fontSize: '15px', height: '50%' }} onClick={() => handleMoveUp(index)}>
|
|
155
|
+
<ArrowUpIcon />
|
|
156
|
+
</button>
|
|
157
|
+
<button className="manualButton manualButtonDown" style={{ fontSize: '15px', height: '50%' }} onClick={() => handleMoveDown(index)}>
|
|
158
|
+
<ArrowDownIcon />
|
|
159
|
+
</button>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<Flex gap={2} align="flex-start">
|
|
163
|
+
{/* Key input */}
|
|
164
|
+
<Box flex={1}>
|
|
165
|
+
<TextInput
|
|
166
|
+
value={pair.key}
|
|
167
|
+
onChange={(e) => handlePairChange(index, 'key', e.target.value)}
|
|
168
|
+
placeholder={keyPlaceholder}
|
|
169
|
+
/>
|
|
170
|
+
</Box>
|
|
171
|
+
|
|
172
|
+
{/* Reference display or empty-state picker trigger */}
|
|
173
|
+
<Box flex={1} style={{ minHeight: '100%' }}>
|
|
174
|
+
{pair.value?._ref ? (
|
|
175
|
+
<Card className="referenceCard" radius={2} tone="primary" style={{ paddingLeft: '1rem', height: 'fit-content' }}>
|
|
176
|
+
<Flex align="center" justify="space-between">
|
|
177
|
+
<Text
|
|
178
|
+
size={2}
|
|
179
|
+
style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: '90%' }}
|
|
180
|
+
>
|
|
181
|
+
{referenceData[pair.value._ref] || 'Loading...'}
|
|
182
|
+
</Text>
|
|
183
|
+
<MenuButton
|
|
184
|
+
button={<Button icon={EllipsisHorizontalIcon} mode="bleed" title="Options" />}
|
|
185
|
+
id={`ref-options-${index}`}
|
|
186
|
+
menu={
|
|
187
|
+
<Menu>
|
|
188
|
+
<MenuItem tone="critical" icon={TrashIcon} text="Remove" onClick={() => handlePairChange(index, 'value', null)} />
|
|
189
|
+
<MenuItem icon={SyncIcon} text="Replace" onClick={() => openReferenceSelector(index)} />
|
|
190
|
+
</Menu>
|
|
191
|
+
}
|
|
192
|
+
popover={{ portal: true, tone: 'default', placement: 'left' }}
|
|
193
|
+
/>
|
|
194
|
+
</Flex>
|
|
195
|
+
</Card>
|
|
196
|
+
) : (
|
|
197
|
+
<Box
|
|
198
|
+
padding={2}
|
|
199
|
+
style={{ minHeight: '100%', border: '1px dashed #ccc', borderRadius: '4px', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }}
|
|
200
|
+
onClick={() => openReferenceSelector(index)}
|
|
201
|
+
>
|
|
202
|
+
<Text muted size={2}>Click to select a {pickerLabel}</Text>
|
|
203
|
+
</Box>
|
|
204
|
+
)}
|
|
205
|
+
</Box>
|
|
206
|
+
</Flex>
|
|
207
|
+
|
|
208
|
+
{/* Remove button */}
|
|
209
|
+
<button
|
|
210
|
+
className="manualButton"
|
|
211
|
+
onClick={() => handleRemovePair(index)}
|
|
212
|
+
style={{ position: 'absolute', top: '0', right: '-7px', transform: 'translate(100%, 0%)' }}
|
|
213
|
+
>
|
|
214
|
+
<TrashIcon />
|
|
215
|
+
</button>
|
|
216
|
+
</Box>
|
|
217
|
+
))}
|
|
218
|
+
</Stack>
|
|
219
|
+
</Box>
|
|
220
|
+
|
|
221
|
+
<Button tone="primary" mode="ghost" onClick={handleAddPair} icon={AddIcon} text={`Add ${keyTitle}`} />
|
|
222
|
+
|
|
223
|
+
{/* Reference picker dialog */}
|
|
224
|
+
{isDialogOpen && (
|
|
225
|
+
<Dialog
|
|
226
|
+
header={`Select a ${pickerLabel}`}
|
|
227
|
+
id="reference-selector-dialog"
|
|
228
|
+
onClose={closeDialog}
|
|
229
|
+
width={1}
|
|
230
|
+
>
|
|
231
|
+
<Box padding={4}>
|
|
232
|
+
<Autocomplete
|
|
233
|
+
id="reference-autocomplete"
|
|
234
|
+
options={referenceOptions}
|
|
235
|
+
placeholder={`Search ${pickerLabel}s...`}
|
|
236
|
+
renderOption={(option) => (
|
|
237
|
+
<Card key={option.value} padding={3} radius={2} tone="default" style={{ cursor: 'pointer' }}>
|
|
238
|
+
<Text size={2}>{option.title}</Text>
|
|
239
|
+
</Card>
|
|
240
|
+
)}
|
|
241
|
+
renderValue={(val) => referenceOptions.find(o => o.value === val)?.title || ''}
|
|
242
|
+
onChange={(newValue) => {
|
|
243
|
+
const selected = availableReferences.find(r => r._id === newValue);
|
|
244
|
+
if (selected) handleReferenceSelect(selected);
|
|
245
|
+
}}
|
|
246
|
+
openButton
|
|
247
|
+
fontSize={2}
|
|
248
|
+
/>
|
|
249
|
+
</Box>
|
|
250
|
+
</Dialog>
|
|
251
|
+
)}
|
|
252
|
+
</Stack>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// Font-specific wrapper around KeyValueReferenceInput — maps variable font instances to static font documents with autofill
|
|
2
|
+
|
|
3
|
+
import React, { useState, useCallback } from 'react';
|
|
4
|
+
import { Button, Flex, Dialog, Box, Stack, Text } from '@sanity/ui';
|
|
5
|
+
import { SyncIcon, DocumentTextIcon } from '@sanity/icons';
|
|
6
|
+
import { set, useFormValue } from 'sanity';
|
|
7
|
+
import { KeyValueReferenceInput } from './KeyValueReferenceInput.jsx';
|
|
8
|
+
import { useSanityClient } from '../hooks/useSanityClient.js';
|
|
9
|
+
import { parseVariableFontInstances } from '../utils/parseVariableFontInstances.js';
|
|
10
|
+
import { nanoid } from 'nanoid';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Wraps KeyValueReferenceInput with font-specific autofill for variable font instance mapping.
|
|
14
|
+
* Autofill with Matching calls parseVariableFontInstances to resolve instance names to static fonts.
|
|
15
|
+
* Autofill Keys Only populates keys from variableInstances JSON without reference matching.
|
|
16
|
+
* @param {Array} value - Current array of { _key, key, value } pairs
|
|
17
|
+
* @param {Function} onChange - Sanity onChange callback
|
|
18
|
+
* @param {Object} props - Remaining props forwarded to KeyValueReferenceInput
|
|
19
|
+
*/
|
|
20
|
+
export function VariableInstanceReferencesInput(props) {
|
|
21
|
+
const { value = [], onChange } = props;
|
|
22
|
+
|
|
23
|
+
const [isAutofilling, setIsAutofilling] = useState(false);
|
|
24
|
+
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
|
25
|
+
const [pendingAction, setPendingAction] = useState(null);
|
|
26
|
+
|
|
27
|
+
const formDocument = useFormValue([]);
|
|
28
|
+
const sanityClient = useSanityClient();
|
|
29
|
+
|
|
30
|
+
/** Fetches static (non-variable) fonts scoped to the same typeface for the reference picker */
|
|
31
|
+
const fetchReferences = useCallback(async (client, doc) => {
|
|
32
|
+
const typefaceName = doc?.typefaceName;
|
|
33
|
+
if (!typefaceName) {
|
|
34
|
+
return client.fetch(`*[_type == 'font' && variableFont != true]{_id, title}`, {});
|
|
35
|
+
}
|
|
36
|
+
return client.fetch(
|
|
37
|
+
`*[_type == 'font' && typefaceName == $typefaceName && variableFont != true]{_id, title}`,
|
|
38
|
+
{ typefaceName }
|
|
39
|
+
);
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
/** Runs parseVariableFontInstances to match instance names to static font documents */
|
|
43
|
+
const performAutofillWithMatching = useCallback(async (mode) => {
|
|
44
|
+
setIsAutofilling(true);
|
|
45
|
+
try {
|
|
46
|
+
if (!formDocument?.variableInstances) {
|
|
47
|
+
console.warn('Cannot autofill: no variableInstances data on this document');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const mappings = await parseVariableFontInstances(formDocument, sanityClient);
|
|
51
|
+
if (mappings.length === 0) {
|
|
52
|
+
console.warn('No variable instances could be parsed from this font');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const updatedPairs = mode === 'replace'
|
|
56
|
+
? mappings
|
|
57
|
+
: [...value, ...mappings.filter(m => !value.some(p => p.key === m.key))];
|
|
58
|
+
onChange(set(updatedPairs));
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.error('Error during autofill with matching:', err);
|
|
61
|
+
} finally {
|
|
62
|
+
setIsAutofilling(false);
|
|
63
|
+
}
|
|
64
|
+
}, [formDocument, sanityClient, value, onChange]);
|
|
65
|
+
|
|
66
|
+
/** Populates key names from variableInstances JSON without reference matching */
|
|
67
|
+
const performAutofillKeysOnly = useCallback(async (mode) => {
|
|
68
|
+
setIsAutofilling(true);
|
|
69
|
+
try {
|
|
70
|
+
if (!formDocument?.variableInstances) {
|
|
71
|
+
console.warn('Cannot autofill: no variableInstances data on this document');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
let instances;
|
|
75
|
+
try {
|
|
76
|
+
instances = JSON.parse(formDocument.variableInstances);
|
|
77
|
+
} catch {
|
|
78
|
+
console.error('Invalid variableInstances JSON on this document');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const keys = Object.keys(instances);
|
|
82
|
+
if (keys.length === 0) {
|
|
83
|
+
console.warn('No variable instances found in JSON');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const keyOnlyPairs = keys.map(key => ({ key, value: null, _key: nanoid() }));
|
|
87
|
+
const updatedPairs = mode === 'replace'
|
|
88
|
+
? keyOnlyPairs
|
|
89
|
+
: [...value, ...keyOnlyPairs.filter(p => !value.some(existing => existing.key === p.key))];
|
|
90
|
+
onChange(set(updatedPairs));
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.error('Error during keys-only autofill:', err);
|
|
93
|
+
} finally {
|
|
94
|
+
setIsAutofilling(false);
|
|
95
|
+
}
|
|
96
|
+
}, [formDocument, value, onChange]);
|
|
97
|
+
|
|
98
|
+
/** Triggers autofill with matching — prompts confirmation if pairs already exist */
|
|
99
|
+
const handleAutofillWithMatching = useCallback(() => {
|
|
100
|
+
if (value.length > 0) {
|
|
101
|
+
setPendingAction('matching');
|
|
102
|
+
setShowConfirmDialog(true);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
performAutofillWithMatching('replace');
|
|
106
|
+
}, [value, performAutofillWithMatching]);
|
|
107
|
+
|
|
108
|
+
/** Triggers keys-only autofill — prompts confirmation if pairs already exist */
|
|
109
|
+
const handleAutofillKeysOnly = useCallback(() => {
|
|
110
|
+
if (value.length > 0) {
|
|
111
|
+
setPendingAction('keysOnly');
|
|
112
|
+
setShowConfirmDialog(true);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
performAutofillKeysOnly('replace');
|
|
116
|
+
}, [value, performAutofillKeysOnly]);
|
|
117
|
+
|
|
118
|
+
/** Handles the replace/merge choice from the confirmation dialog */
|
|
119
|
+
const handleConfirmChoice = useCallback(async (choice) => {
|
|
120
|
+
setShowConfirmDialog(false);
|
|
121
|
+
if (pendingAction === 'matching') await performAutofillWithMatching(choice);
|
|
122
|
+
else if (pendingAction === 'keysOnly') await performAutofillKeysOnly(choice);
|
|
123
|
+
setPendingAction(null);
|
|
124
|
+
}, [pendingAction, performAutofillWithMatching, performAutofillKeysOnly]);
|
|
125
|
+
|
|
126
|
+
/** Cancels the confirmation dialog */
|
|
127
|
+
const handleConfirmCancel = useCallback(() => {
|
|
128
|
+
setShowConfirmDialog(false);
|
|
129
|
+
setPendingAction(null);
|
|
130
|
+
}, []);
|
|
131
|
+
|
|
132
|
+
// Only show autofill buttons when the document is a variable font with parsed instance data
|
|
133
|
+
const showAutofill = !!(formDocument?.variableFont && formDocument?.variableInstances);
|
|
134
|
+
|
|
135
|
+
const topActions = showAutofill ? (
|
|
136
|
+
<Flex gap={2}>
|
|
137
|
+
<Button
|
|
138
|
+
tone="primary"
|
|
139
|
+
mode="ghost"
|
|
140
|
+
onClick={handleAutofillWithMatching}
|
|
141
|
+
icon={SyncIcon}
|
|
142
|
+
text="Autofill with Matching"
|
|
143
|
+
disabled={isAutofilling}
|
|
144
|
+
loading={isAutofilling}
|
|
145
|
+
/>
|
|
146
|
+
<Button
|
|
147
|
+
tone="default"
|
|
148
|
+
mode="ghost"
|
|
149
|
+
onClick={handleAutofillKeysOnly}
|
|
150
|
+
icon={DocumentTextIcon}
|
|
151
|
+
text="Autofill Keys Only"
|
|
152
|
+
disabled={isAutofilling}
|
|
153
|
+
loading={isAutofilling}
|
|
154
|
+
/>
|
|
155
|
+
</Flex>
|
|
156
|
+
) : null;
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<>
|
|
160
|
+
<KeyValueReferenceInput
|
|
161
|
+
{...props}
|
|
162
|
+
referenceType="font"
|
|
163
|
+
fetchReferences={fetchReferences}
|
|
164
|
+
topActions={topActions}
|
|
165
|
+
/>
|
|
166
|
+
|
|
167
|
+
{showConfirmDialog && (
|
|
168
|
+
<Dialog
|
|
169
|
+
header="Existing entries found"
|
|
170
|
+
id="autofill-confirm-dialog"
|
|
171
|
+
onClose={handleConfirmCancel}
|
|
172
|
+
width={1}
|
|
173
|
+
>
|
|
174
|
+
<Box padding={4}>
|
|
175
|
+
<Stack space={4}>
|
|
176
|
+
<Text>
|
|
177
|
+
You already have {value.length} {value.length === 1 ? 'entry' : 'entries'}. How would you like to proceed?
|
|
178
|
+
</Text>
|
|
179
|
+
<Flex gap={2} justify="flex-end">
|
|
180
|
+
<Button text="Cancel" mode="ghost" onClick={handleConfirmCancel} />
|
|
181
|
+
<Button text="Merge (Add New)" tone="primary" mode="ghost" onClick={() => handleConfirmChoice('merge')} />
|
|
182
|
+
<Button text="Replace All" tone="critical" onClick={() => handleConfirmChoice('replace')} />
|
|
183
|
+
</Flex>
|
|
184
|
+
</Stack>
|
|
185
|
+
</Box>
|
|
186
|
+
</Dialog>
|
|
187
|
+
)}
|
|
188
|
+
</>
|
|
189
|
+
);
|
|
190
|
+
}
|
package/src/index.js
CHANGED
|
@@ -11,6 +11,9 @@ export { FontScriptUploaderComponent } from './components/FontScriptUploaderComp
|
|
|
11
11
|
export { default as StatusDisplay } from './components/StatusDisplay.jsx';
|
|
12
12
|
export { default as PriceInput } from './components/PriceInput.jsx';
|
|
13
13
|
export { default as UploadButton } from './components/UploadButton.jsx';
|
|
14
|
+
export { KeyValueInput } from './components/KeyValueInput.jsx';
|
|
15
|
+
export { KeyValueReferenceInput } from './components/KeyValueReferenceInput.jsx';
|
|
16
|
+
export { VariableInstanceReferencesInput } from './components/VariableInstanceReferencesInput.jsx';
|
|
14
17
|
|
|
15
18
|
// Hooks
|
|
16
19
|
export { useSanityClient } from './hooks/useSanityClient.js';
|