sanity-plugin-iconify 3.0.0 → 4.0.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 +1 -1
- package/dist/index.cjs +401 -296
- package/dist/index.d.cts +89 -363
- package/dist/index.d.mts +89 -0
- package/dist/index.mjs +434 -0
- package/package.json +75 -49
- package/src/combobox/search-input.tsx +12 -2
- package/src/lib/api.ts +1 -0
- package/src/lib/icon-types.gen.ts +1 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.ts +0 -363
- package/dist/index.js +0 -362
- package/dist/index.js.map +0 -1
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { definePlugin, set, unset } from "sanity";
|
|
2
|
+
import { Box, Button, Card, Flex, Grid, Popover, Stack, Text, TextInput, ThemeProvider, useToast } from "@sanity/ui";
|
|
3
|
+
import { buildTheme } from "@sanity/ui/theme";
|
|
4
|
+
import { forwardRef, memo, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
5
|
+
import { useCombobox } from "downshift";
|
|
6
|
+
import { match } from "ts-pattern";
|
|
7
|
+
import { QueryClient, QueryClientProvider, keepPreviousData, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
8
|
+
import { useDebounce } from "use-debounce";
|
|
9
|
+
import styled from "styled-components";
|
|
10
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { Icon } from "@iconify/react";
|
|
12
|
+
import { TrashIcon } from "@sanity/icons";
|
|
13
|
+
import { stringToIcon } from "@iconify/utils";
|
|
14
|
+
import { sentenceCase } from "change-case";
|
|
15
|
+
//#region src/lib/api.ts
|
|
16
|
+
const BASE_API_URL = "https://api.iconify.design";
|
|
17
|
+
function fetchJson({ url, signal }) {
|
|
18
|
+
return fetch(url, { signal }).then((response) => {
|
|
19
|
+
if (!response.ok) throw new Error(`Network error: status ${response.status}`);
|
|
20
|
+
return response.json();
|
|
21
|
+
}).then((result) => result, (error) => {
|
|
22
|
+
if (error instanceof Error) throw error;
|
|
23
|
+
else {
|
|
24
|
+
console.error(`Unknown error: ${error}`);
|
|
25
|
+
throw new Error("Something went wrong");
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function useSearch({ collections }) {
|
|
30
|
+
const queryClient = useQueryClient();
|
|
31
|
+
const [term, setTerm] = useState("");
|
|
32
|
+
const [debouncedTerm, setDebouncedTerm] = useDebounce(term, 500);
|
|
33
|
+
const updateTerm = useCallback((newTerm, updateImmediately = false) => {
|
|
34
|
+
setTerm(newTerm);
|
|
35
|
+
if (updateImmediately) setDebouncedTerm(newTerm);
|
|
36
|
+
}, [setDebouncedTerm]);
|
|
37
|
+
const { isLoading, isError, error, data, isPlaceholderData } = useQuery({
|
|
38
|
+
queryKey: [
|
|
39
|
+
"search",
|
|
40
|
+
collections,
|
|
41
|
+
debouncedTerm
|
|
42
|
+
],
|
|
43
|
+
queryFn: async ({ signal }) => {
|
|
44
|
+
const url = new URL(`/search`, BASE_API_URL);
|
|
45
|
+
url.searchParams.append("query", debouncedTerm);
|
|
46
|
+
url.searchParams.append("limit", "60");
|
|
47
|
+
if (collections) url.searchParams.append("prefixes", collections.join(","));
|
|
48
|
+
const result = debouncedTerm ? await fetchJson({
|
|
49
|
+
url,
|
|
50
|
+
signal
|
|
51
|
+
}) : null;
|
|
52
|
+
if (result) Object.entries(result.collections).forEach(([prefix, info]) => {
|
|
53
|
+
queryClient.setQueryData(["iconSetInfo", prefix], info);
|
|
54
|
+
});
|
|
55
|
+
return result?.icons ?? [];
|
|
56
|
+
},
|
|
57
|
+
enabled: debouncedTerm.length > 0,
|
|
58
|
+
placeholderData: keepPreviousData,
|
|
59
|
+
staleTime: 300 * 1e3
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
term,
|
|
63
|
+
setTerm: updateTerm,
|
|
64
|
+
debouncedTerm,
|
|
65
|
+
isLoading,
|
|
66
|
+
isError,
|
|
67
|
+
error,
|
|
68
|
+
data,
|
|
69
|
+
isPreviousData: isPlaceholderData
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function useIconSetInfo({ prefix }) {
|
|
73
|
+
return useQuery({
|
|
74
|
+
queryKey: ["iconSetInfo", prefix],
|
|
75
|
+
queryFn: async ({ signal }) => {
|
|
76
|
+
if (!prefix) return null;
|
|
77
|
+
const url = new URL("/collection", BASE_API_URL);
|
|
78
|
+
url.searchParams.append("prefix", prefix);
|
|
79
|
+
url.searchParams.append("info", "true");
|
|
80
|
+
return (await fetchJson({
|
|
81
|
+
url,
|
|
82
|
+
signal
|
|
83
|
+
}))?.info ?? null;
|
|
84
|
+
},
|
|
85
|
+
staleTime: Infinity
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
styled(Grid)`
|
|
89
|
+
grid-template-columns: 1fr min-content;
|
|
90
|
+
position: relative;
|
|
91
|
+
`;
|
|
92
|
+
const OptionsWrapper = styled(Box)`
|
|
93
|
+
box-sizing: border-box;
|
|
94
|
+
padding: 0.5rem;
|
|
95
|
+
|
|
96
|
+
& [role='listbox'] {
|
|
97
|
+
display: flex;
|
|
98
|
+
flex-wrap: wrap;
|
|
99
|
+
gap: 0.5rem;
|
|
100
|
+
margin: 0;
|
|
101
|
+
padding: 0;
|
|
102
|
+
list-style: none;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
& [role='option'] {
|
|
106
|
+
display: grid;
|
|
107
|
+
place-items: center;
|
|
108
|
+
width: clamp(3rem, 10vw, 4rem);
|
|
109
|
+
|
|
110
|
+
& button {
|
|
111
|
+
cursor: pointer;
|
|
112
|
+
width: 100%;
|
|
113
|
+
|
|
114
|
+
& > [data-ui='Box'] {
|
|
115
|
+
display: flex;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
& svg {
|
|
119
|
+
aspect-ratio: 1;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
`;
|
|
124
|
+
function MessageWrapper({ children }) {
|
|
125
|
+
return /* @__PURE__ */ jsx(Card, {
|
|
126
|
+
padding: 4,
|
|
127
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
128
|
+
align: "center",
|
|
129
|
+
muted: true,
|
|
130
|
+
children
|
|
131
|
+
})
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/combobox/search-input.tsx
|
|
136
|
+
const SearchInput = memo(forwardRef((props, ref) => {
|
|
137
|
+
const { selectedIcon, suffix, onChange, ...rest } = props;
|
|
138
|
+
return /* @__PURE__ */ jsx(TextInput, {
|
|
139
|
+
...rest,
|
|
140
|
+
onChange,
|
|
141
|
+
ref,
|
|
142
|
+
inputMode: "search",
|
|
143
|
+
icon: selectedIcon ? /* @__PURE__ */ jsx(Icon, { icon: selectedIcon }) : null,
|
|
144
|
+
placeholder: selectedIcon ? "Search and replace selected icon..." : "Search for an icon...",
|
|
145
|
+
suffix
|
|
146
|
+
});
|
|
147
|
+
}));
|
|
148
|
+
SearchInput.displayName = "SearchInput";
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/combobox/search-result.tsx
|
|
151
|
+
const SearchResults = memo(forwardRef((props, ref) => {
|
|
152
|
+
const { state, data, getItemProps, highlightedIndex, ...rest } = props;
|
|
153
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [(() => {
|
|
154
|
+
switch (state) {
|
|
155
|
+
case "initial": return /* @__PURE__ */ jsx(MessageWrapper, { children: "Search for icons" });
|
|
156
|
+
case "loading": return /* @__PURE__ */ jsx(MessageWrapper, { children: "Searching..." });
|
|
157
|
+
case "error": return /* @__PURE__ */ jsx(MessageWrapper, { children: "Something went wrong..." });
|
|
158
|
+
case "empty": return /* @__PURE__ */ jsx(MessageWrapper, { children: "No icons found" });
|
|
159
|
+
}
|
|
160
|
+
})(), /* @__PURE__ */ jsx("ul", {
|
|
161
|
+
...rest,
|
|
162
|
+
ref,
|
|
163
|
+
"data-testid": "iconify-results",
|
|
164
|
+
style: { opacity: state === "stale" ? .5 : 1 },
|
|
165
|
+
children: (state === "data" || state === "stale") && data?.map((icon, index) => /* @__PURE__ */ jsx("li", {
|
|
166
|
+
...getItemProps({
|
|
167
|
+
item: icon,
|
|
168
|
+
index
|
|
169
|
+
}),
|
|
170
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
171
|
+
padding: 3,
|
|
172
|
+
mode: "bleed",
|
|
173
|
+
selected: index === highlightedIndex,
|
|
174
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
175
|
+
icon,
|
|
176
|
+
width: "100%",
|
|
177
|
+
height: "100%"
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
}, icon))
|
|
181
|
+
})] });
|
|
182
|
+
}));
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region src/combobox/unset-button.tsx
|
|
185
|
+
function UnsetButton(props) {
|
|
186
|
+
const { onUnset } = props;
|
|
187
|
+
return /* @__PURE__ */ jsx(Card, {
|
|
188
|
+
border: true,
|
|
189
|
+
borderLeft: false,
|
|
190
|
+
padding: 1,
|
|
191
|
+
display: "flex",
|
|
192
|
+
radius: 2,
|
|
193
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
194
|
+
"data-testid": "iconify-unset",
|
|
195
|
+
icon: /* @__PURE__ */ jsx(TrashIcon, {}),
|
|
196
|
+
onClick: onUnset,
|
|
197
|
+
mode: "bleed",
|
|
198
|
+
fontSize: 1,
|
|
199
|
+
padding: 2
|
|
200
|
+
})
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/combobox/iconify-combobox.tsx
|
|
205
|
+
const IconifyCombobox = memo(function IconifyCombobox(props) {
|
|
206
|
+
const { selectedIcon, onSelect: pushSelection, collections, studioElementProps, fieldFocused } = props;
|
|
207
|
+
const id = useId();
|
|
208
|
+
const toast = useToast();
|
|
209
|
+
const inputRef = useRef(null);
|
|
210
|
+
const composedInputRef = useCallback((node) => {
|
|
211
|
+
inputRef.current = node;
|
|
212
|
+
if (studioElementProps?.ref) studioElementProps.ref.current = node;
|
|
213
|
+
}, [studioElementProps?.ref]);
|
|
214
|
+
const suppressNextBlur = useRef(false);
|
|
215
|
+
const handleFocus = useCallback((event) => {
|
|
216
|
+
suppressNextBlur.current = true;
|
|
217
|
+
setTimeout(() => {
|
|
218
|
+
suppressNextBlur.current = false;
|
|
219
|
+
}, 0);
|
|
220
|
+
studioElementProps?.onFocus?.(event);
|
|
221
|
+
}, [studioElementProps]);
|
|
222
|
+
const handleBlur = useCallback((event) => {
|
|
223
|
+
if (suppressNextBlur.current) {
|
|
224
|
+
suppressNextBlur.current = false;
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
studioElementProps?.onBlur?.(event);
|
|
228
|
+
}, [studioElementProps]);
|
|
229
|
+
const { term, setTerm, debouncedTerm, isLoading, isError, error, data, isPreviousData } = useSearch({ collections });
|
|
230
|
+
const { isOpen, getMenuProps, getInputProps, highlightedIndex, getItemProps, selectItem, setInputValue, closeMenu } = useCombobox({
|
|
231
|
+
items: data ?? [],
|
|
232
|
+
inputValue: term,
|
|
233
|
+
onInputValueChange({ inputValue, selectedItem }) {
|
|
234
|
+
if (inputValue !== selectedItem) setTerm(inputValue);
|
|
235
|
+
},
|
|
236
|
+
onSelectedItemChange({ selectedItem }) {
|
|
237
|
+
if (selectedItem) {
|
|
238
|
+
pushSelection(selectedItem);
|
|
239
|
+
setTerm("", true);
|
|
240
|
+
setInputValue("");
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
useEffect(() => {
|
|
245
|
+
if (!fieldFocused) closeMenu();
|
|
246
|
+
}, [fieldFocused, closeMenu]);
|
|
247
|
+
const handleUnset = useCallback(() => {
|
|
248
|
+
pushSelection("");
|
|
249
|
+
setTerm("", true);
|
|
250
|
+
selectItem("");
|
|
251
|
+
}, [
|
|
252
|
+
pushSelection,
|
|
253
|
+
setTerm,
|
|
254
|
+
selectItem
|
|
255
|
+
]);
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
if (isError) {
|
|
258
|
+
console.error("Iconify input error:", error);
|
|
259
|
+
toast.push({
|
|
260
|
+
id,
|
|
261
|
+
status: "error",
|
|
262
|
+
title: "Iconify input error",
|
|
263
|
+
description: error?.message
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}, [
|
|
267
|
+
error,
|
|
268
|
+
id,
|
|
269
|
+
isError,
|
|
270
|
+
toast
|
|
271
|
+
]);
|
|
272
|
+
const { onBlur: _downshiftOnBlur, ...inputProps } = getInputProps({
|
|
273
|
+
ref: composedInputRef,
|
|
274
|
+
id: studioElementProps?.id,
|
|
275
|
+
"aria-describedby": studioElementProps?.["aria-describedby"],
|
|
276
|
+
onFocus: handleFocus
|
|
277
|
+
});
|
|
278
|
+
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(SearchInput, {
|
|
279
|
+
...inputProps,
|
|
280
|
+
onBlur: handleBlur,
|
|
281
|
+
selectedIcon,
|
|
282
|
+
suffix: selectedIcon ? /* @__PURE__ */ jsx(UnsetButton, { onUnset: handleUnset }) : null
|
|
283
|
+
}), /* @__PURE__ */ jsx(Popover, {
|
|
284
|
+
open: true,
|
|
285
|
+
style: { display: isOpen ? "block" : "none" },
|
|
286
|
+
placement: "bottom",
|
|
287
|
+
arrow: false,
|
|
288
|
+
matchReferenceWidth: true,
|
|
289
|
+
constrainSize: true,
|
|
290
|
+
referenceElement: inputRef.current,
|
|
291
|
+
content: /* @__PURE__ */ jsx(OptionsWrapper, { children: /* @__PURE__ */ jsx(SearchResults, {
|
|
292
|
+
...getMenuProps(),
|
|
293
|
+
state: match(true).returnType().with(isLoading, () => "loading").with(!debouncedTerm, () => "initial").with(isError, () => "error").with(!data || data.length === 0, () => "empty").with(isPreviousData, () => "stale").otherwise(() => "data"),
|
|
294
|
+
data,
|
|
295
|
+
getItemProps,
|
|
296
|
+
highlightedIndex
|
|
297
|
+
}) })
|
|
298
|
+
})] });
|
|
299
|
+
});
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region src/lib/query-client.tsx
|
|
302
|
+
const queryClient = new QueryClient();
|
|
303
|
+
function QueryClientProvider$1(props) {
|
|
304
|
+
return /* @__PURE__ */ jsx(QueryClientProvider, {
|
|
305
|
+
client: queryClient,
|
|
306
|
+
children: props.children
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region src/lib/use-pretty-icon-name.ts
|
|
311
|
+
function usePrettyIconName(props) {
|
|
312
|
+
const { name } = props;
|
|
313
|
+
const iconMeta = useMemo(() => props.iconMeta ?? (name ? stringToIcon(name) : null), [name, props.iconMeta]);
|
|
314
|
+
const iconSetInfo = useIconSetInfo({ prefix: iconMeta?.prefix ?? null });
|
|
315
|
+
return useMemo(() => iconMeta ? {
|
|
316
|
+
name: sentenceCase(iconMeta.name),
|
|
317
|
+
collection: iconSetInfo.data?.name ?? iconMeta.prefix
|
|
318
|
+
} : null, [iconMeta, iconSetInfo.data?.name]);
|
|
319
|
+
}
|
|
320
|
+
//#endregion
|
|
321
|
+
//#region src/iconify-input.tsx
|
|
322
|
+
const theme = buildTheme();
|
|
323
|
+
const IconifyInput = memo(function IconifyInput(props) {
|
|
324
|
+
const { config, value, onChange: pushChange, schemaType, elementProps, focused } = props;
|
|
325
|
+
const selectedIcon = value?.name ?? null;
|
|
326
|
+
const options = schemaType.options;
|
|
327
|
+
const collections = !!options?.collections?.length && options.collections || !!config?.collections?.length && config.collections || null;
|
|
328
|
+
const showName = options?.showName ?? config?.showName ?? false;
|
|
329
|
+
return /* @__PURE__ */ jsx(QueryClientProvider$1, { children: /* @__PURE__ */ jsx(ThemeProvider, {
|
|
330
|
+
theme,
|
|
331
|
+
children: /* @__PURE__ */ jsxs(Stack, {
|
|
332
|
+
space: 2,
|
|
333
|
+
children: [/* @__PURE__ */ jsx(IconifyCombobox, {
|
|
334
|
+
selectedIcon,
|
|
335
|
+
onSelect: useCallback((icon) => {
|
|
336
|
+
pushChange(icon === "" ? unset() : set(icon, ["name"]));
|
|
337
|
+
}, [pushChange]),
|
|
338
|
+
collections,
|
|
339
|
+
studioElementProps: elementProps,
|
|
340
|
+
fieldFocused: focused
|
|
341
|
+
}), showName && selectedIcon ? /* @__PURE__ */ jsx(IconifyNameDisplay, { name: selectedIcon }) : null]
|
|
342
|
+
})
|
|
343
|
+
}) });
|
|
344
|
+
});
|
|
345
|
+
function IconifyNameDisplay(props) {
|
|
346
|
+
const { name } = props;
|
|
347
|
+
const prettyName = usePrettyIconName({ name });
|
|
348
|
+
return /* @__PURE__ */ jsxs(Flex, {
|
|
349
|
+
gap: 1,
|
|
350
|
+
children: [
|
|
351
|
+
/* @__PURE__ */ jsx(Text, {
|
|
352
|
+
size: 1,
|
|
353
|
+
muted: true,
|
|
354
|
+
children: "Selected:"
|
|
355
|
+
}),
|
|
356
|
+
/* @__PURE__ */ jsx(Text, {
|
|
357
|
+
size: 1,
|
|
358
|
+
weight: "semibold",
|
|
359
|
+
children: prettyName?.name ?? name
|
|
360
|
+
}),
|
|
361
|
+
prettyName?.collection && /* @__PURE__ */ jsxs(Text, {
|
|
362
|
+
size: 1,
|
|
363
|
+
muted: true,
|
|
364
|
+
style: { fontStyle: "italic" },
|
|
365
|
+
children: ["by ", prettyName?.collection]
|
|
366
|
+
})
|
|
367
|
+
]
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
//#endregion
|
|
371
|
+
//#region src/iconify-preview.tsx
|
|
372
|
+
const IconifyPreview = memo(function IconifyPreview(props) {
|
|
373
|
+
const { title } = props;
|
|
374
|
+
const iconName = typeof props.title === "string" ? stringToIcon(props.title) : null;
|
|
375
|
+
if (typeof title === "string" && iconName) return /* @__PURE__ */ jsx(QueryClientProvider$1, { children: /* @__PURE__ */ jsx(IconifyPreviewInner, {
|
|
376
|
+
...props,
|
|
377
|
+
iconName: title,
|
|
378
|
+
iconMeta: iconName
|
|
379
|
+
}) });
|
|
380
|
+
return props.renderDefault(props);
|
|
381
|
+
});
|
|
382
|
+
function IconifyPreviewInner(props) {
|
|
383
|
+
const { iconMeta, iconName, ...previewProps } = props;
|
|
384
|
+
const prettyName = usePrettyIconName({ iconMeta });
|
|
385
|
+
return props.renderDefault({
|
|
386
|
+
...previewProps,
|
|
387
|
+
media: /* @__PURE__ */ jsx(Icon, { icon: iconName }),
|
|
388
|
+
title: prettyName?.name ?? iconName,
|
|
389
|
+
subtitle: prettyName?.collection
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
//#endregion
|
|
393
|
+
//#region src/iconify-plugin.tsx
|
|
394
|
+
/**
|
|
395
|
+
* Usage in `sanity.config.ts` (or .js)
|
|
396
|
+
*
|
|
397
|
+
* ```ts
|
|
398
|
+
* import { defineConfig } from 'sanity'
|
|
399
|
+
* import { iconify } from 'sanity-plugin-iconify'
|
|
400
|
+
*
|
|
401
|
+
* export default defineConfig({
|
|
402
|
+
* // ...
|
|
403
|
+
* plugins: [iconify()],
|
|
404
|
+
* })
|
|
405
|
+
* ```
|
|
406
|
+
*/
|
|
407
|
+
const iconify = definePlugin((config = {}) => {
|
|
408
|
+
return {
|
|
409
|
+
name: "sanity-plugin-iconify",
|
|
410
|
+
schema: { types: [{
|
|
411
|
+
name: "icon",
|
|
412
|
+
title: "Icon",
|
|
413
|
+
type: "object",
|
|
414
|
+
fields: [{
|
|
415
|
+
name: "name",
|
|
416
|
+
title: "Name",
|
|
417
|
+
type: "string"
|
|
418
|
+
}],
|
|
419
|
+
components: {
|
|
420
|
+
input: (props) => /* @__PURE__ */ jsx(IconifyInput, {
|
|
421
|
+
...props,
|
|
422
|
+
config
|
|
423
|
+
}),
|
|
424
|
+
preview: IconifyPreview,
|
|
425
|
+
field: (props) => props.renderDefault({
|
|
426
|
+
...props,
|
|
427
|
+
level: 0
|
|
428
|
+
})
|
|
429
|
+
}
|
|
430
|
+
}] }
|
|
431
|
+
};
|
|
432
|
+
});
|
|
433
|
+
//#endregion
|
|
434
|
+
export { iconify };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanity-plugin-iconify",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"description": "Icon picker based on Iconify",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -21,15 +21,20 @@
|
|
|
21
21
|
"exports": {
|
|
22
22
|
".": {
|
|
23
23
|
"source": "./src/index.ts",
|
|
24
|
-
"import":
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
"import": {
|
|
25
|
+
"types": "./dist/index.d.mts",
|
|
26
|
+
"default": "./dist/index.mjs"
|
|
27
|
+
},
|
|
28
|
+
"require": {
|
|
29
|
+
"types": "./dist/index.d.cts",
|
|
30
|
+
"default": "./dist/index.cjs"
|
|
31
|
+
}
|
|
27
32
|
},
|
|
28
33
|
"./package.json": "./package.json"
|
|
29
34
|
},
|
|
30
35
|
"main": "./dist/index.cjs",
|
|
31
|
-
"module": "./dist/index.
|
|
32
|
-
"types": "./dist/index.d.
|
|
36
|
+
"module": "./dist/index.mjs",
|
|
37
|
+
"types": "./dist/index.d.mts",
|
|
33
38
|
"files": [
|
|
34
39
|
"dist",
|
|
35
40
|
"sanity.json",
|
|
@@ -37,94 +42,115 @@
|
|
|
37
42
|
"v2-incompatible.js"
|
|
38
43
|
],
|
|
39
44
|
"scripts": {
|
|
40
|
-
"build": "run-s build:
|
|
41
|
-
"build:bundle": "
|
|
42
|
-
"build:check": "
|
|
43
|
-
"build:clean": "rimraf dist",
|
|
45
|
+
"build": "run-s build:verify build:bundle build:check",
|
|
46
|
+
"build:bundle": "tsdown",
|
|
47
|
+
"build:check": "node scripts/check-dist.mjs",
|
|
44
48
|
"build:verify": "plugin-kit verify-package --silent",
|
|
45
49
|
"dev": "vite dev --config dev/vite.config.ts",
|
|
50
|
+
"dev:dist": "vite build --config dev/vite.config.ts && vite preview --config dev/vite.config.ts",
|
|
46
51
|
"format": "prettier --write --cache --ignore-unknown .",
|
|
47
52
|
"generate-types": "node src/lib/generate-types",
|
|
48
53
|
"lint": "eslint .",
|
|
54
|
+
"prepublishOnly": "node scripts/check-dist.mjs",
|
|
49
55
|
"release": "release-it",
|
|
50
56
|
"test": "vitest run",
|
|
51
57
|
"test:e2e": "playwright test",
|
|
58
|
+
"test:e2e:dist": "PLUGIN_TARGET=dist playwright test",
|
|
52
59
|
"test:e2e:setup": "tsx playwright/auth-setup.ts",
|
|
53
60
|
"test:e2e:ui": "playwright test --ui",
|
|
54
61
|
"test:ui": "vitest --ui",
|
|
55
62
|
"test:watch": "vitest",
|
|
56
|
-
"watch": "
|
|
63
|
+
"watch": "tsdown --watch"
|
|
57
64
|
},
|
|
58
65
|
"browserslist": "extends @sanity/browserslist-config",
|
|
59
66
|
"dependencies": {
|
|
60
67
|
"@iconify/react": "^6.0.2",
|
|
61
|
-
"@iconify/utils": "^3.1.
|
|
68
|
+
"@iconify/utils": "^3.1.3",
|
|
62
69
|
"@sanity/icons": "^3.7.4",
|
|
63
70
|
"@sanity/incompatible-plugin": "^1.0.5",
|
|
64
|
-
"@sanity/ui": "^3.
|
|
65
|
-
"@tanstack/react-query": "^5.
|
|
71
|
+
"@sanity/ui": "^3.2.0",
|
|
72
|
+
"@tanstack/react-query": "^5.101.0",
|
|
66
73
|
"change-case": "^5.4.4",
|
|
67
|
-
"downshift": "^9.
|
|
74
|
+
"downshift": "^9.3.6",
|
|
68
75
|
"ts-pattern": "^5.9.0",
|
|
69
|
-
"use-debounce": "^10.
|
|
76
|
+
"use-debounce": "^10.1.1"
|
|
70
77
|
},
|
|
71
78
|
"devDependencies": {
|
|
72
|
-
"@commitlint/cli": "^
|
|
73
|
-
"@commitlint/config-conventional": "^
|
|
74
|
-
"@iconify/json": "^2.2.
|
|
79
|
+
"@commitlint/cli": "^21.0.2",
|
|
80
|
+
"@commitlint/config-conventional": "^21.0.2",
|
|
81
|
+
"@iconify/json": "^2.2.486",
|
|
75
82
|
"@iconify/types": "^2.0.0",
|
|
76
|
-
"@playwright/test": "^1.
|
|
77
|
-
"@release-it/conventional-changelog": "^
|
|
78
|
-
"@sanity/pkg-utils": "^10.2.3",
|
|
83
|
+
"@playwright/test": "^1.61.0",
|
|
84
|
+
"@release-it/conventional-changelog": "^11.0.1",
|
|
79
85
|
"@sanity/plugin-kit": "^4.0.20",
|
|
80
|
-
"@tanstack/eslint-plugin-query": "^5.
|
|
81
|
-
"@tanstack/react-query-devtools": "^5.
|
|
82
|
-
"@types/react": "^19.2.
|
|
86
|
+
"@tanstack/eslint-plugin-query": "^5.101.0",
|
|
87
|
+
"@tanstack/react-query-devtools": "^5.101.0",
|
|
88
|
+
"@types/react": "^19.2.17",
|
|
83
89
|
"@types/styled-components": "^5.1.36",
|
|
84
|
-
"@vitejs/plugin-react": "^
|
|
85
|
-
"@vitest/ui": "^4.1.
|
|
86
|
-
"@waspeer/config": "^
|
|
87
|
-
"eslint": "
|
|
88
|
-
"lefthook": "^2.
|
|
89
|
-
"npm-run-all2": "^
|
|
90
|
-
"prettier": "^3.
|
|
91
|
-
"
|
|
92
|
-
"react
|
|
93
|
-
"react-
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
"
|
|
90
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
91
|
+
"@vitest/ui": "^4.1.9",
|
|
92
|
+
"@waspeer/config": "^3.0.0",
|
|
93
|
+
"eslint": "^10.1.0",
|
|
94
|
+
"lefthook": "^2.1.9",
|
|
95
|
+
"npm-run-all2": "^9.0.2",
|
|
96
|
+
"prettier": "^3.8.4",
|
|
97
|
+
"publint": "^0.3.18",
|
|
98
|
+
"react": "^19.2.7",
|
|
99
|
+
"react-dom": "^19.2.7",
|
|
100
|
+
"react-is": "^19.2.7",
|
|
101
|
+
"release-it": "^20.2.0",
|
|
102
|
+
"rimraf": "^6.1.3",
|
|
103
|
+
"sanity": "^6.0.0",
|
|
104
|
+
"tsdown": "^0.22.2",
|
|
105
|
+
"tsx": "^4.22.4",
|
|
106
|
+
"typescript": "^6.0.2",
|
|
107
|
+
"vite": "^8.0.2",
|
|
108
|
+
"vitest": "^4.1.9"
|
|
101
109
|
},
|
|
102
110
|
"peerDependencies": {
|
|
103
111
|
"react": "^19.2",
|
|
104
|
-
"sanity": "^5.0.0-0",
|
|
112
|
+
"sanity": "^5.0.0-0 || ^6.0.0-0",
|
|
105
113
|
"styled-components": "^6"
|
|
106
114
|
},
|
|
107
115
|
"packageManager": "pnpm@10.17.0",
|
|
108
116
|
"engines": {
|
|
109
|
-
"node": ">=
|
|
117
|
+
"node": ">=22.12"
|
|
110
118
|
},
|
|
111
119
|
"pnpm": {
|
|
112
120
|
"onlyBuiltDependencies": [
|
|
113
121
|
"esbuild",
|
|
114
|
-
"lefthook"
|
|
122
|
+
"lefthook",
|
|
123
|
+
"unrs-resolver"
|
|
115
124
|
],
|
|
116
125
|
"overrides": {
|
|
117
|
-
"
|
|
118
|
-
"
|
|
126
|
+
"@babel/core@<7.29.6": ">=7.29.6",
|
|
127
|
+
"@isaacs/brace-expansion@<5.0.1": ">=5.0.1",
|
|
128
|
+
"@typescript-eslint/utils": "^8.61.1",
|
|
129
|
+
"brace-expansion@>=4.0.0 <5.0.6": ">=5.0.6",
|
|
130
|
+
"brace-expansion@<2.0.3": ">=2.0.3",
|
|
131
|
+
"esbuild@<0.28.1": ">=0.28.1",
|
|
132
|
+
"follow-redirects@<1.16.0": ">=1.16.0",
|
|
133
|
+
"lodash@<4.17.23": ">=4.17.23",
|
|
119
134
|
"micromatch@<4.0.8": ">=4.0.8",
|
|
120
|
-
"
|
|
135
|
+
"minimatch@<10.2.3": ">=10.2.3",
|
|
136
|
+
"picomatch@<4.0.4": ">=4.0.4",
|
|
137
|
+
"postcss@<8.5.10": ">=8.5.10",
|
|
138
|
+
"prismjs@<1.30.0": ">=1.30.0",
|
|
139
|
+
"rollup@<4.59.0": ">=4.59.0",
|
|
140
|
+
"serialize-javascript@<7.0.5": ">=7.0.5",
|
|
141
|
+
"shell-quote@<1.8.4": ">=1.8.4",
|
|
142
|
+
"tmp@<0.2.6": ">=0.2.6",
|
|
143
|
+
"uuid@>=11.0.0 <11.1.1": ">=11.1.1"
|
|
121
144
|
}
|
|
122
145
|
},
|
|
123
146
|
"sanityPlugin": {
|
|
124
147
|
"verifyPackage": {
|
|
125
148
|
"scripts": false,
|
|
126
149
|
"eslintImports": false,
|
|
127
|
-
"nodeEngine": false
|
|
150
|
+
"nodeEngine": false,
|
|
151
|
+
"pkg-utils": false,
|
|
152
|
+
"rollupConfig": false,
|
|
153
|
+
"tsconfig": false
|
|
128
154
|
}
|
|
129
155
|
}
|
|
130
156
|
}
|
|
@@ -2,18 +2,28 @@ import { Icon } from '@iconify/react';
|
|
|
2
2
|
import { TextInput } from '@sanity/ui';
|
|
3
3
|
import { forwardRef, memo } from 'react';
|
|
4
4
|
|
|
5
|
-
interface SearchInputProps extends React.HTMLAttributes<HTMLInputElement> {
|
|
5
|
+
interface SearchInputProps extends Omit<React.HTMLAttributes<HTMLInputElement>, 'onChange'> {
|
|
6
|
+
// downshift's getInputProps() returns onChange typed against the generic
|
|
7
|
+
// `Element` (React.ChangeEventHandler with no element parameter), which is
|
|
8
|
+
// wider than HTMLAttributes' onChange. Accept that signature so the props
|
|
9
|
+
// spread from getInputProps() stays type-compatible.
|
|
10
|
+
onChange?: React.ChangeEventHandler;
|
|
6
11
|
selectedIcon: string | null;
|
|
7
12
|
suffix?: React.ReactNode;
|
|
8
13
|
}
|
|
9
14
|
|
|
10
15
|
export const SearchInput = memo(
|
|
11
16
|
forwardRef<HTMLInputElement, SearchInputProps>((props, ref) => {
|
|
12
|
-
const { selectedIcon, suffix, ...rest } = props;
|
|
17
|
+
const { selectedIcon, suffix, onChange, ...rest } = props;
|
|
13
18
|
|
|
14
19
|
return (
|
|
15
20
|
<TextInput
|
|
16
21
|
{...rest}
|
|
22
|
+
// downshift types onChange against the generic `Element`; Sanity UI's
|
|
23
|
+
// TextInput expects an HTMLInputElement handler. The handler is only
|
|
24
|
+
// forwarded to the underlying <input>, so the element-generic
|
|
25
|
+
// difference is purely a type-variance mismatch with no runtime effect.
|
|
26
|
+
onChange={onChange as React.ChangeEventHandler<HTMLInputElement> | undefined}
|
|
17
27
|
ref={ref}
|
|
18
28
|
inputMode="search"
|
|
19
29
|
icon={selectedIcon ? <Icon icon={selectedIcon} /> : null}
|
package/src/lib/api.ts
CHANGED
|
@@ -44,6 +44,7 @@ export function useSearch({ collections }: { collections: string[] | null }) {
|
|
|
44
44
|
[setDebouncedTerm],
|
|
45
45
|
);
|
|
46
46
|
|
|
47
|
+
// eslint-disable-next-line @tanstack/query/exhaustive-deps -- queryClient from useQueryClient() is a stable reference, not a query dependency
|
|
47
48
|
const { isLoading, isError, error, data, isPlaceholderData } = useQuery<string[], Error>({
|
|
48
49
|
queryKey: ['search', collections, debouncedTerm],
|
|
49
50
|
queryFn: async ({ signal }) => {
|