sanity-plugin-iconify 1.1.2 → 2.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 +14 -6
- package/dist/index.cjs +106 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -8
- package/dist/index.d.ts +72 -8
- package/dist/index.js +106 -99
- package/dist/index.js.map +1 -1
- package/package.json +48 -54
- package/src/combobox/iconify-combobox.styles.tsx +9 -9
- package/src/combobox/iconify-combobox.tsx +64 -55
- package/src/combobox/search-input.tsx +7 -14
- package/src/combobox/search-result.tsx +36 -36
- package/src/combobox/unset-button.tsx +1 -1
- package/src/iconify-input.tsx +8 -17
- package/src/iconify-plugin.tsx +3 -3
- package/src/iconify-preview.tsx +3 -3
- package/src/lib/api.ts +2 -3
- package/src/lib/generate-types.js +0 -1
- package/src/lib/icon-types.gen.ts +1 -0
- package/src/lib/query-client.tsx +1 -1
- package/src/lib/types.ts +1 -1
- package/src/lib/use-pretty-icon-name.ts +2 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/lib/api.ts","../src/combobox/iconify-combobox.styles.tsx","../src/combobox/search-input.tsx","../src/combobox/search-result.tsx","../src/combobox/unset-button.tsx","../src/combobox/iconify-combobox.tsx","../src/lib/query-client.tsx","../src/lib/use-pretty-icon-name.ts","../src/iconify-input.tsx","../src/iconify-preview.tsx","../src/iconify-plugin.tsx"],"sourcesContent":["import { IconifyInfo } from '@iconify/types';\nimport { keepPreviousData, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { useCallback, useState } from 'react';\nimport { useDebounce } from 'use-debounce';\n\nimport { IconifySearchResult } from './types';\n\nconst BASE_API_URL = 'https://api.iconify.design';\n\nfunction fetchJson<T>({ url, signal }: { url: string | URL; signal?: AbortSignal }): Promise<T> {\n return fetch(url, { signal })\n .then((response) => {\n if (!response.ok) {\n throw new Error(`Network error: status ${response.status}`);\n }\n\n return response.json();\n })\n .then(\n (result) => result as T,\n (error) => {\n if (error instanceof Error) {\n throw error;\n } else {\n console.error(`Unknown error: ${error}`);\n throw new Error('Something went wrong');\n }\n },\n );\n}\n\nexport function useSearch({ collections }: { collections: string[] | null }) {\n const queryClient = useQueryClient();\n const [term, setTerm] = useState('');\n const [debouncedTerm, setDebouncedTerm] = useDebounce(term, 500);\n\n const updateTerm = useCallback(\n (newTerm: string, updateImmediately = false) => {\n setTerm(newTerm);\n\n if (updateImmediately) {\n setDebouncedTerm(newTerm);\n }\n },\n [setDebouncedTerm],\n );\n\n const { isLoading, isError, error, data, isPlaceholderData } = useQuery<string[], Error>({\n queryKey: ['search', collections, debouncedTerm],\n queryFn: async ({ signal }) => {\n const url = new URL(`/search`, BASE_API_URL);\n\n url.searchParams.append('query', debouncedTerm);\n url.searchParams.append('limit', '60');\n\n if (collections) {\n url.searchParams.append('prefixes', collections.join(','));\n }\n\n const result = debouncedTerm ? await fetchJson<IconifySearchResult>({ url, signal }) : null;\n\n if (result) {\n // Cache the info for each collection\n Object.entries(result.collections).forEach(([prefix, info]) => {\n queryClient.setQueryData<IconifyInfo>(['iconSetInfo', prefix], info);\n });\n }\n\n return result?.icons ?? [];\n },\n enabled: debouncedTerm.length > 0,\n placeholderData: keepPreviousData,\n staleTime: 5 * 60 * 1000, // 5 minutes\n });\n\n return {\n term,\n setTerm: updateTerm,\n debouncedTerm,\n isLoading,\n isError,\n error,\n data,\n isPreviousData: isPlaceholderData,\n };\n}\n\nexport function useIconSetInfo({ prefix }: { prefix?: string | null }) {\n return useQuery<IconifyInfo | null, Error>({\n queryKey: ['iconSetInfo', prefix],\n queryFn: async ({ signal }) => {\n if (!prefix) return null;\n\n const url = new URL('/collection', BASE_API_URL);\n\n url.searchParams.append('prefix', prefix);\n url.searchParams.append('info', 'true');\n\n const result = await fetchJson<{ info: IconifyInfo }>({ url, signal });\n\n return result?.info ?? null;\n },\n staleTime: Infinity,\n });\n}\n","import { Box, Card, Grid, Text } from '@sanity/ui';\nimport { ReactNode } from 'react';\nimport styled from 'styled-components';\n\nexport const ComboboxWrapper = styled(Grid)`\n grid-template-columns: 1fr min-content;\n position: relative;\n`;\n\nexport const OptionsWrapper = styled(Box)`\n box-sizing: border-box;\n padding: 0.5rem;\n\n & [role='listbox'] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(min(100%, 2.5rem), 1fr));\n gap: 0.5rem;\n list-style: none;\n margin: 0;\n padding: 0;\n\n @media (min-width: 650px) {\n grid-template-columns: repeat(10, minmax(min(100%, 2.5rem), 1fr));\n }\n }\n\n & [role='option'] {\n display: grid;\n place-items: center;\n\n & button {\n aspect-ratio: 1;\n cursor: pointer;\n width: 100%;\n\n & > [data-ui='Box'] {\n display: flex;\n }\n }\n }\n`;\n\nexport function MessageWrapper({ children }: { children: ReactNode }) {\n return (\n <Card padding={4}>\n <Text align=\"center\" muted>\n {children}\n </Text>\n </Card>\n );\n}\n","import { Combobox } from '@headlessui/react';\nimport { Icon } from '@iconify/react';\nimport { TextInput } from '@sanity/ui';\nimport { ChangeEventHandler, forwardRef } from 'react';\n\n// ------------ //\n// SEARCH INPUT //\n// ------------ //\n\ninterface SearchInputProps {\n term: string;\n selectedIcon: string | null;\n onChange: ChangeEventHandler<HTMLInputElement>;\n}\n\nexport const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>((props, ref) => {\n const { term, selectedIcon, onChange } = props;\n\n return (\n <Combobox.Input\n as={TextInput}\n ref={ref}\n inputMode=\"search\"\n value={term}\n onChange={onChange}\n icon={selectedIcon ? <Icon icon={selectedIcon} /> : null}\n placeholder={selectedIcon ? 'Search and replace selected icon...' : 'Search for an icon...'}\n />\n );\n});\n\nSearchInput.displayName = 'SearchInput';\n","import { Combobox } from '@headlessui/react';\nimport { Icon } from '@iconify/react';\nimport { Button } from '@sanity/ui';\n\nimport { MessageWrapper } from './iconify-combobox.styles';\n\n// -------------- //\n// SEARCH RESULTS //\n// -------------- //\n\nexport interface SearchResultsProps {\n state: 'initial' | 'loading' | 'error' | 'empty' | 'data' | 'stale';\n data?: string[];\n}\n\nexport function SearchResults(props: SearchResultsProps) {\n const { state, data } = props;\n\n if (state === 'initial') {\n return <MessageWrapper>Search for icons</MessageWrapper>;\n }\n\n if (state === 'loading') {\n return <MessageWrapper>Searching...</MessageWrapper>;\n }\n\n if (state === 'error') {\n return <MessageWrapper>Something went wrong...</MessageWrapper>;\n }\n\n if (state === 'empty') {\n return <MessageWrapper>No icons found</MessageWrapper>;\n }\n\n return (\n <Combobox.Options style={{ opacity: state === 'stale' ? 0.5 : 1 }}>\n {data!.map((icon) => (\n <Combobox.Option key={icon} value={icon}>\n {({ active }) => (\n <Button padding={3} mode=\"bleed\" selected={active}>\n <Icon icon={icon} width=\"100%\" height=\"100%\" />\n </Button>\n )}\n </Combobox.Option>\n ))}\n </Combobox.Options>\n );\n}\n","import { TrashIcon } from '@sanity/icons';\nimport { Button, Card } from '@sanity/ui';\n\n// ------------ //\n// UNSET BUTTON //\n// ------------ //\n\ninterface UnsetButtonProps {\n onUnset: () => void;\n}\n\nexport function UnsetButton(props: UnsetButtonProps) {\n const { onUnset } = props;\n\n return (\n <Card border borderLeft={false} padding={1} display=\"flex\">\n <Button icon={<TrashIcon />} onClick={onUnset} mode=\"bleed\" fontSize={1} padding={2} />\n </Card>\n );\n}\n","import { Combobox } from '@headlessui/react';\nimport { Popover, useToast } from '@sanity/ui';\nimport { ChangeEventHandler, useCallback, useEffect, useId, useRef } from 'react';\nimport { match } from 'ts-pattern';\n\nimport { useSearch } from '../lib/api';\nimport { ComboboxWrapper, OptionsWrapper } from './iconify-combobox.styles';\nimport { SearchInput } from './search-input';\nimport { SearchResults, SearchResultsProps } from './search-result';\nimport { UnsetButton } from './unset-button';\n\nexport interface IconifyComboboxProps {\n selectedIcon: string | null;\n onSelect: (newValue: string) => void;\n collections: string[] | null;\n}\n\nexport function IconifyCombobox(props: IconifyComboboxProps) {\n const { selectedIcon, onSelect: pushSelection, collections } = props;\n\n const id = useId();\n const toast = useToast();\n const inputRef = useRef<HTMLInputElement>(null);\n\n const { term, setTerm, debouncedTerm, isLoading, isError, error, data, isPreviousData } =\n useSearch({\n collections,\n });\n\n const handleTermChange: ChangeEventHandler<HTMLInputElement> = useCallback(\n (event) => setTerm(event.target.value),\n [setTerm],\n );\n\n const handleSelect = useCallback(\n (icon: string) => {\n pushSelection(icon);\n setTerm('', true);\n },\n [pushSelection, setTerm],\n );\n\n const handleUnset = useCallback(() => handleSelect(''), [handleSelect]);\n\n useEffect(() => {\n if (isError) {\n console.error('Iconify input error:', error);\n\n toast.push({\n id,\n status: 'error',\n title: 'Iconify input error',\n description: error?.message,\n });\n }\n }, [error, id, isError, toast]);\n\n return (\n <ComboboxWrapper>\n <Combobox onChange={handleSelect}>\n {({ open }) => (\n <>\n <SearchInput\n ref={inputRef}\n term={term}\n selectedIcon={selectedIcon}\n onChange={handleTermChange}\n />\n\n <Popover\n open={open}\n placement=\"bottom\"\n arrow={false}\n matchReferenceWidth\n portal\n constrainSize\n referenceElement={inputRef.current}\n content={\n <OptionsWrapper>\n <SearchResults\n state={match<boolean>(true)\n .returnType<SearchResultsProps['state']>()\n .with(isLoading, () => 'loading')\n .with(!debouncedTerm, () => 'initial')\n .with(isError, () => 'error')\n .with(!data || data.length === 0, () => 'empty')\n .with(isPreviousData, () => 'stale')\n .otherwise(() => 'data')}\n data={data}\n />\n </OptionsWrapper>\n }\n />\n </>\n )}\n </Combobox>\n\n {selectedIcon ? <UnsetButton onUnset={handleUnset} /> : null}\n </ComboboxWrapper>\n );\n}\n","import {\n QueryClient,\n QueryClientProvider as ReactQueryClientProvider,\n} from '@tanstack/react-query';\nimport { ReactNode } from 'react';\n\nexport const queryClient = new QueryClient();\n\nexport function QueryClientProvider(props: { children: ReactNode }) {\n return <ReactQueryClientProvider client={queryClient}>{props.children}</ReactQueryClientProvider>;\n}\n","import { IconifyIconName, stringToIcon } from '@iconify/utils';\nimport { sentenceCase } from 'change-case';\nimport { useMemo } from 'react';\n\nimport { useIconSetInfo } from './api';\n\ninterface UsePrettyIconNameProps {\n name?: string | null;\n iconMeta?: IconifyIconName | null;\n}\n\nexport function usePrettyIconName(props: UsePrettyIconNameProps) {\n const { name } = props;\n const iconMeta = useMemo(\n () => props.iconMeta ?? (name ? stringToIcon(name) : null),\n [name, props.iconMeta],\n );\n const iconSetInfo = useIconSetInfo({ prefix: iconMeta?.prefix ?? null });\n\n return useMemo(\n () =>\n iconMeta\n ? {\n name: sentenceCase(iconMeta.name),\n collection: iconSetInfo.data?.name ?? iconMeta.prefix,\n }\n : null,\n [iconMeta, iconSetInfo.data?.name],\n );\n}\n","import { Flex, RootTheme, Stack, studioTheme, Text, ThemeProvider, useTheme } from '@sanity/ui';\nimport { useCallback, useMemo } from 'react';\nimport { ObjectInputProps, set, unset } from 'sanity';\n\nimport { IconifyCombobox } from './combobox';\nimport { QueryClientProvider } from './lib/query-client';\nimport { IconifyPluginConfig, IconOptions } from './lib/types';\nimport { usePrettyIconName } from './lib/use-pretty-icon-name';\n\n// -------------- //\n// ICONIFY INPUT //\n// -------------- //\n\ninterface IconifyInputProps extends ObjectInputProps {\n config: IconifyPluginConfig;\n}\n\nexport function IconifyInput(props: IconifyInputProps) {\n const { config, value, onChange: pushChange, schemaType } = props;\n\n const selectedIcon: string | null = value?.name ?? null;\n\n const options: IconOptions = schemaType.options;\n const collections =\n (!!options?.collections?.length && options.collections) ||\n (!!config?.collections?.length && config.collections) ||\n null;\n const showName = options?.showName ?? config?.showName ?? false;\n\n const parentTheme = useTheme();\n const theme: RootTheme = useMemo(\n () => (parentTheme ? { ...parentTheme.sanity, color: studioTheme.color } : studioTheme),\n [parentTheme],\n );\n\n const handleSelect = useCallback(\n (icon: string) => {\n pushChange(icon === '' ? unset() : set(icon, ['name']));\n },\n [pushChange],\n );\n\n return (\n <QueryClientProvider>\n <ThemeProvider theme={theme}>\n <Stack space={2}>\n <IconifyCombobox\n selectedIcon={selectedIcon}\n onSelect={handleSelect}\n collections={collections}\n />\n\n {showName && selectedIcon ? <IconifyNameDisplay name={selectedIcon} /> : null}\n </Stack>\n </ThemeProvider>\n </QueryClientProvider>\n );\n}\n\ninterface IconifyNameDisplayProps {\n name?: string | null;\n}\n\nfunction IconifyNameDisplay(props: IconifyNameDisplayProps) {\n const { name } = props;\n const prettyName = usePrettyIconName({ name });\n\n return (\n <Flex gap={1}>\n <Text size={1} muted>\n Selected:\n </Text>\n\n <Text size={1} weight=\"semibold\">\n {prettyName?.name ?? name}\n </Text>\n\n {prettyName?.collection && (\n <Text size={1} muted style={{ fontStyle: 'italic' }}>\n by {prettyName?.collection}\n </Text>\n )}\n </Flex>\n );\n}\n","import { Icon } from '@iconify/react';\nimport { IconifyIconName, stringToIcon } from '@iconify/utils';\nimport { PreviewProps } from 'sanity';\n\nimport { QueryClientProvider } from './lib/query-client';\nimport { usePrettyIconName } from './lib/use-pretty-icon-name';\n\n// --------------- //\n// ICONIFY PREVIEW //\n// --------------- //\n\nexport function IconifyPreview(props: PreviewProps) {\n const { title } = props;\n const iconName = typeof props.title === 'string' ? stringToIcon(props.title) : null;\n\n // We check this double to avoid the TS error\n if (typeof title === 'string' && iconName) {\n return (\n <QueryClientProvider>\n <IconifyPreviewInner {...props} iconName={title} iconMeta={iconName} />\n </QueryClientProvider>\n );\n }\n\n return props.renderDefault(props);\n}\n\n// --------------------- //\n// ICONIFY PREVIEW INNER //\n// --------------------- //\n\ninterface IconifyPreviewInnerProps extends PreviewProps {\n iconName: string;\n iconMeta: IconifyIconName;\n}\n\nfunction IconifyPreviewInner(props: IconifyPreviewInnerProps) {\n const { iconMeta, iconName, ...previewProps } = props;\n const prettyName = usePrettyIconName({ iconMeta });\n\n return props.renderDefault({\n ...previewProps,\n media: <Icon icon={iconName} />,\n title: prettyName?.name ?? iconName,\n subtitle: prettyName?.collection,\n });\n}\n","import { definePlugin, FieldProps, ObjectInputProps } from 'sanity';\n\nimport { IconifyInput } from './iconify-input';\nimport { IconifyPreview } from './iconify-preview';\nimport { IconifyPluginConfig } from './lib/types';\n\n/**\n * Usage in `sanity.config.ts` (or .js)\n *\n * ```ts\n * import { defineConfig } from 'sanity'\n * import { iconify } from 'sanity-plugin-iconify'\n *\n * export default defineConfig({\n * // ...\n * plugins: [iconify()],\n * })\n * ```\n */\nexport const iconify = definePlugin<IconifyPluginConfig | void>((config = {}) => {\n return {\n name: 'sanity-plugin-iconify',\n schema: {\n types: [\n {\n name: 'icon',\n title: 'Icon',\n type: 'object',\n fields: [\n {\n name: 'name',\n title: 'Name',\n type: 'string',\n },\n ],\n components: {\n input: (props: ObjectInputProps) => <IconifyInput {...props} config={config!} />,\n preview: IconifyPreview,\n\n // This makes sure the input component is not indented\n field: (props: FieldProps) => props.renderDefault({ ...props, level: 0 }),\n },\n },\n ],\n },\n };\n});\n"],"names":["queryClient","ReactQueryClientProvider","_a","_b"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/lib/api.ts","../src/combobox/iconify-combobox.styles.tsx","../src/combobox/search-input.tsx","../src/combobox/search-result.tsx","../src/combobox/unset-button.tsx","../src/combobox/iconify-combobox.tsx","../src/lib/query-client.tsx","../src/lib/use-pretty-icon-name.ts","../src/iconify-input.tsx","../src/iconify-preview.tsx","../src/iconify-plugin.tsx"],"sourcesContent":["import type { IconifyInfo } from '@iconify/types';\nimport { keepPreviousData, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { useCallback, useState } from 'react';\nimport { useDebounce } from 'use-debounce';\nimport type { IconifySearchResult } from './types';\n\nconst BASE_API_URL = 'https://api.iconify.design';\n\nfunction fetchJson<T>({ url, signal }: { url: string | URL; signal?: AbortSignal }): Promise<T> {\n return fetch(url, { signal })\n .then((response) => {\n if (!response.ok) {\n throw new Error(`Network error: status ${response.status}`);\n }\n\n return response.json();\n })\n .then(\n (result) => result as T,\n (error) => {\n if (error instanceof Error) {\n throw error;\n } else {\n console.error(`Unknown error: ${error}`);\n throw new Error('Something went wrong');\n }\n },\n );\n}\n\nexport function useSearch({ collections }: { collections: string[] | null }) {\n const queryClient = useQueryClient();\n const [term, setTerm] = useState('');\n const [debouncedTerm, setDebouncedTerm] = useDebounce(term, 500);\n\n const updateTerm = useCallback(\n (newTerm: string, updateImmediately = false) => {\n setTerm(newTerm);\n\n if (updateImmediately) {\n setDebouncedTerm(newTerm);\n }\n },\n [setDebouncedTerm],\n );\n\n const { isLoading, isError, error, data, isPlaceholderData } = useQuery<string[], Error>({\n queryKey: ['search', collections, debouncedTerm],\n queryFn: async ({ signal }) => {\n const url = new URL(`/search`, BASE_API_URL);\n\n url.searchParams.append('query', debouncedTerm);\n url.searchParams.append('limit', '60');\n\n if (collections) {\n url.searchParams.append('prefixes', collections.join(','));\n }\n\n const result = debouncedTerm ? await fetchJson<IconifySearchResult>({ url, signal }) : null;\n\n if (result) {\n // Cache the info for each collection\n Object.entries(result.collections).forEach(([prefix, info]) => {\n queryClient.setQueryData<IconifyInfo>(['iconSetInfo', prefix], info);\n });\n }\n\n return result?.icons ?? [];\n },\n enabled: debouncedTerm.length > 0,\n placeholderData: keepPreviousData,\n staleTime: 5 * 60 * 1000, // 5 minutes\n });\n\n return {\n term,\n setTerm: updateTerm,\n debouncedTerm,\n isLoading,\n isError,\n error,\n data,\n isPreviousData: isPlaceholderData,\n };\n}\n\nexport function useIconSetInfo({ prefix }: { prefix?: string | null }) {\n return useQuery<IconifyInfo | null, Error>({\n queryKey: ['iconSetInfo', prefix],\n queryFn: async ({ signal }) => {\n if (!prefix) return null;\n\n const url = new URL('/collection', BASE_API_URL);\n\n url.searchParams.append('prefix', prefix);\n url.searchParams.append('info', 'true');\n\n const result = await fetchJson<{ info: IconifyInfo }>({ url, signal });\n\n return result?.info ?? null;\n },\n staleTime: Infinity,\n });\n}\n","import { Box, Card, Grid, Text } from '@sanity/ui';\nimport type { ReactNode } from 'react';\nimport styled from 'styled-components';\n\nexport const ComboboxWrapper = styled(Grid)`\n grid-template-columns: 1fr min-content;\n position: relative;\n`;\n\nexport const OptionsWrapper = styled(Box)`\n box-sizing: border-box;\n padding: 0.5rem;\n\n & [role='listbox'] {\n display: flex;\n flex-wrap: wrap;\n gap: 0.5rem;\n margin: 0;\n padding: 0;\n list-style: none;\n }\n\n & [role='option'] {\n display: grid;\n place-items: center;\n width: clamp(3rem, 10vw, 4rem);\n\n & button {\n cursor: pointer;\n width: 100%;\n\n & > [data-ui='Box'] {\n display: flex;\n }\n\n & svg {\n aspect-ratio: 1;\n }\n }\n }\n`;\n\nexport function MessageWrapper({ children }: { children: ReactNode }) {\n return (\n <Card padding={4}>\n <Text align=\"center\" muted>\n {children}\n </Text>\n </Card>\n );\n}\n","import { Icon } from '@iconify/react';\nimport { TextInput } from '@sanity/ui';\nimport { forwardRef } from 'react';\n\ninterface SearchInputProps extends React.HTMLAttributes<HTMLInputElement> {\n selectedIcon: string | null;\n suffix?: React.ReactNode;\n}\n\nexport const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>((props, ref) => {\n const { selectedIcon, suffix, ...rest } = props;\n\n return (\n <TextInput\n {...rest}\n ref={ref}\n inputMode=\"search\"\n icon={selectedIcon ? <Icon icon={selectedIcon} /> : null}\n placeholder={selectedIcon ? 'Search and replace selected icon...' : 'Search for an icon...'}\n suffix={suffix}\n />\n );\n});\n\nSearchInput.displayName = 'SearchInput';\n","import { Icon } from '@iconify/react';\nimport { Button } from '@sanity/ui';\nimport type { UseComboboxGetItemPropsOptions, UseComboboxGetItemPropsReturnValue } from 'downshift';\nimport { forwardRef } from 'react';\nimport { MessageWrapper } from './iconify-combobox.styles';\n\ntype GetItemProps = (\n options: UseComboboxGetItemPropsOptions<string>,\n) => UseComboboxGetItemPropsReturnValue;\n\nexport interface SearchResultsProps extends React.HTMLAttributes<HTMLUListElement> {\n state: 'initial' | 'loading' | 'error' | 'empty' | 'data' | 'stale';\n data?: string[];\n getItemProps: GetItemProps;\n highlightedIndex: number;\n}\n\nexport const SearchResults = forwardRef<HTMLUListElement, SearchResultsProps>((props, ref) => {\n const { state, data, getItemProps, highlightedIndex, ...rest } = props;\n\n return (\n <>\n {(() => {\n switch (state) {\n case 'initial':\n return <MessageWrapper>Search for icons</MessageWrapper>;\n case 'loading':\n return <MessageWrapper>Searching...</MessageWrapper>;\n case 'error':\n return <MessageWrapper>Something went wrong...</MessageWrapper>;\n case 'empty':\n return <MessageWrapper>No icons found</MessageWrapper>;\n }\n })()}\n\n <ul {...rest} ref={ref} style={{ opacity: state === 'stale' ? 0.5 : 1 }}>\n {(state === 'data' || state === 'stale') &&\n data?.map((icon, index) => (\n <li key={icon} {...getItemProps({ item: icon, index })}>\n <Button padding={3} mode=\"bleed\" selected={index === highlightedIndex}>\n <Icon icon={icon} width=\"100%\" height=\"100%\" />\n </Button>\n </li>\n ))}\n </ul>\n </>\n );\n});\n","import { TrashIcon } from '@sanity/icons';\nimport { Button, Card } from '@sanity/ui';\n\n// ------------ //\n// UNSET BUTTON //\n// ------------ //\n\ninterface UnsetButtonProps {\n onUnset: () => void;\n}\n\nexport function UnsetButton(props: UnsetButtonProps) {\n const { onUnset } = props;\n\n return (\n <Card border borderLeft={false} padding={1} display=\"flex\" radius={2}>\n <Button icon={<TrashIcon />} onClick={onUnset} mode=\"bleed\" fontSize={1} padding={2} />\n </Card>\n );\n}\n","import { Popover, useToast } from '@sanity/ui';\nimport { useCombobox } from 'downshift';\nimport { useCallback, useEffect, useId, useRef } from 'react';\nimport { match } from 'ts-pattern';\nimport { useSearch } from '../lib/api';\nimport { OptionsWrapper } from './iconify-combobox.styles';\nimport { SearchInput } from './search-input';\nimport type { SearchResultsProps } from './search-result';\nimport { SearchResults } from './search-result';\nimport { UnsetButton } from './unset-button';\n\nexport interface IconifyComboboxProps {\n selectedIcon: string | null;\n onSelect: (newValue: string) => void;\n collections: string[] | null;\n}\n\nexport function IconifyCombobox(props: IconifyComboboxProps) {\n const { selectedIcon, onSelect: pushSelection, collections } = props;\n\n const id = useId();\n const toast = useToast();\n const inputRef = useRef<HTMLInputElement>(null);\n\n const { term, setTerm, debouncedTerm, isLoading, isError, error, data, isPreviousData } =\n useSearch({\n collections,\n });\n\n const {\n isOpen,\n getMenuProps,\n getInputProps,\n highlightedIndex,\n getItemProps,\n selectItem,\n setInputValue,\n } = useCombobox({\n items: data ?? [],\n inputValue: term,\n onInputValueChange({ inputValue, selectedItem }) {\n if (inputValue !== selectedItem) {\n setTerm(inputValue);\n }\n },\n onSelectedItemChange({ selectedItem }) {\n pushSelection(selectedItem);\n setTerm('', true);\n setInputValue('');\n },\n });\n\n const handleUnset = useCallback(() => {\n pushSelection('');\n setTerm('', true);\n selectItem('');\n }, []);\n\n useEffect(() => {\n if (isError) {\n console.error('Iconify input error:', error);\n\n toast.push({\n id,\n status: 'error',\n title: 'Iconify input error',\n description: error?.message,\n });\n }\n }, [error, id, isError, toast]);\n\n return (\n <div>\n {/* @ts-expect-error wrong typings */}\n <SearchInput\n {...getInputProps({ ref: inputRef })}\n selectedIcon={selectedIcon}\n suffix={selectedIcon ? <UnsetButton onUnset={handleUnset} /> : null}\n />\n\n <Popover\n open={true}\n style={{ display: isOpen ? 'block' : 'none' }}\n placement=\"bottom\"\n arrow={false}\n matchReferenceWidth\n constrainSize\n referenceElement={inputRef.current}\n content={\n <OptionsWrapper>\n <SearchResults\n {...getMenuProps()}\n state={match<boolean>(true)\n .returnType<SearchResultsProps['state']>()\n .with(isLoading, () => 'loading')\n .with(!debouncedTerm, () => 'initial')\n .with(isError, () => 'error')\n .with(!data || data.length === 0, () => 'empty')\n .with(isPreviousData, () => 'stale')\n .otherwise(() => 'data')}\n data={data}\n getItemProps={getItemProps}\n highlightedIndex={highlightedIndex}\n />\n </OptionsWrapper>\n }\n />\n </div>\n );\n}\n","import {\n QueryClient,\n QueryClientProvider as ReactQueryClientProvider,\n} from '@tanstack/react-query';\nimport type { ReactNode } from 'react';\n\nexport const queryClient = new QueryClient();\n\nexport function QueryClientProvider(props: { children: ReactNode }) {\n return <ReactQueryClientProvider client={queryClient}>{props.children}</ReactQueryClientProvider>;\n}\n","import type { IconifyIconName } from '@iconify/utils';\nimport { stringToIcon } from '@iconify/utils';\nimport { sentenceCase } from 'change-case';\nimport { useMemo } from 'react';\nimport { useIconSetInfo } from './api';\n\ninterface UsePrettyIconNameProps {\n name?: string | null;\n iconMeta?: IconifyIconName | null;\n}\n\nexport function usePrettyIconName(props: UsePrettyIconNameProps) {\n const { name } = props;\n const iconMeta = useMemo(\n () => props.iconMeta ?? (name ? stringToIcon(name) : null),\n [name, props.iconMeta],\n );\n const iconSetInfo = useIconSetInfo({ prefix: iconMeta?.prefix ?? null });\n\n return useMemo(\n () =>\n iconMeta\n ? {\n name: sentenceCase(iconMeta.name),\n collection: iconSetInfo.data?.name ?? iconMeta.prefix,\n }\n : null,\n [iconMeta, iconSetInfo.data?.name],\n );\n}\n","import { Flex, Stack, Text, ThemeProvider } from '@sanity/ui';\nimport { buildTheme } from '@sanity/ui/theme';\nimport { useCallback } from 'react';\nimport type { ObjectInputProps } from 'sanity';\nimport { set, unset } from 'sanity';\nimport { IconifyCombobox } from './combobox';\nimport { QueryClientProvider } from './lib/query-client';\nimport type { IconifyPluginConfig, IconOptions } from './lib/types';\nimport { usePrettyIconName } from './lib/use-pretty-icon-name';\n\ninterface IconifyInputProps extends ObjectInputProps {\n config: IconifyPluginConfig;\n}\n\nexport function IconifyInput(props: IconifyInputProps) {\n const { config, value, onChange: pushChange, schemaType } = props;\n\n const selectedIcon: string | null = value?.name ?? null;\n\n const options: IconOptions = schemaType.options;\n const collections =\n (!!options?.collections?.length && options.collections) ||\n (!!config?.collections?.length && config.collections) ||\n null;\n const showName = options?.showName ?? config?.showName ?? false;\n\n const handleSelect = useCallback(\n (icon: string) => {\n pushChange(icon === '' ? unset() : set(icon, ['name']));\n },\n [pushChange],\n );\n\n return (\n <QueryClientProvider>\n <ThemeProvider theme={buildTheme()}>\n <Stack space={2}>\n <IconifyCombobox\n selectedIcon={selectedIcon}\n onSelect={handleSelect}\n collections={collections}\n />\n\n {showName && selectedIcon ? <IconifyNameDisplay name={selectedIcon} /> : null}\n </Stack>\n </ThemeProvider>\n </QueryClientProvider>\n );\n}\n\ninterface IconifyNameDisplayProps {\n name?: string | null;\n}\n\nexport function IconifyNameDisplay(props: IconifyNameDisplayProps) {\n const { name } = props;\n const prettyName = usePrettyIconName({ name });\n\n return (\n <Flex gap={1}>\n <Text size={1} muted>\n Selected:\n </Text>\n\n <Text size={1} weight=\"semibold\">\n {prettyName?.name ?? name}\n </Text>\n\n {prettyName?.collection && (\n <Text size={1} muted style={{ fontStyle: 'italic' }}>\n by {prettyName?.collection}\n </Text>\n )}\n </Flex>\n );\n}\n","import { Icon } from '@iconify/react';\nimport type { IconifyIconName } from '@iconify/utils';\nimport { stringToIcon } from '@iconify/utils';\nimport type { PreviewProps } from 'sanity';\nimport { QueryClientProvider } from './lib/query-client';\nimport { usePrettyIconName } from './lib/use-pretty-icon-name';\n\n// --------------- //\n// ICONIFY PREVIEW //\n// --------------- //\n\nexport function IconifyPreview(props: PreviewProps) {\n const { title } = props;\n const iconName = typeof props.title === 'string' ? stringToIcon(props.title) : null;\n\n // We check this double to avoid the TS error\n if (typeof title === 'string' && iconName) {\n return (\n <QueryClientProvider>\n <IconifyPreviewInner {...props} iconName={title} iconMeta={iconName} />\n </QueryClientProvider>\n );\n }\n\n return props.renderDefault(props);\n}\n\n// --------------------- //\n// ICONIFY PREVIEW INNER //\n// --------------------- //\n\ninterface IconifyPreviewInnerProps extends PreviewProps {\n iconName: string;\n iconMeta: IconifyIconName;\n}\n\nfunction IconifyPreviewInner(props: IconifyPreviewInnerProps) {\n const { iconMeta, iconName, ...previewProps } = props;\n const prettyName = usePrettyIconName({ iconMeta });\n\n return props.renderDefault({\n ...previewProps,\n media: <Icon icon={iconName} />,\n title: prettyName?.name ?? iconName,\n subtitle: prettyName?.collection,\n });\n}\n","import type { FieldProps, ObjectInputProps } from 'sanity';\nimport { definePlugin } from 'sanity';\nimport { IconifyInput } from './iconify-input';\nimport { IconifyPreview } from './iconify-preview';\nimport type { IconifyPluginConfig } from './lib/types';\n\n/**\n * Usage in `sanity.config.ts` (or .js)\n *\n * ```ts\n * import { defineConfig } from 'sanity'\n * import { iconify } from 'sanity-plugin-iconify'\n *\n * export default defineConfig({\n * // ...\n * plugins: [iconify()],\n * })\n * ```\n */\nexport const iconify = definePlugin<IconifyPluginConfig | void>((config = {}) => {\n return {\n name: 'sanity-plugin-iconify',\n schema: {\n types: [\n {\n name: 'icon',\n title: 'Icon',\n type: 'object',\n fields: [\n {\n name: 'name',\n title: 'Name',\n type: 'string',\n },\n ],\n components: {\n input: (props: ObjectInputProps) => <IconifyInput {...props} config={config!} />,\n preview: IconifyPreview,\n\n // This makes sure the input component is not indented\n field: (props: FieldProps) => props.renderDefault({ ...props, level: 0 }),\n },\n },\n ],\n },\n };\n});\n"],"names":["queryClient","ReactQueryClientProvider"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanity-plugin-iconify",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Icon picker based on Iconify",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
},
|
|
30
30
|
"main": "./dist/index.cjs",
|
|
31
31
|
"module": "./dist/index.js",
|
|
32
|
-
"source": "./src/index.ts",
|
|
33
32
|
"types": "./dist/index.d.ts",
|
|
34
33
|
"files": [
|
|
35
34
|
"dist",
|
|
@@ -38,81 +37,76 @@
|
|
|
38
37
|
"v2-incompatible.js"
|
|
39
38
|
],
|
|
40
39
|
"scripts": {
|
|
41
|
-
"build": "run-s clean
|
|
42
|
-
"
|
|
40
|
+
"build": "run-s build:clean build:verify build:bundle build:check",
|
|
41
|
+
"build:bundle": "pkg-utils build --strict",
|
|
42
|
+
"build:check": "pkg-utils --strict",
|
|
43
|
+
"build:clean": "rimraf dist",
|
|
44
|
+
"build:verify": "plugin-kit verify-package --silent",
|
|
45
|
+
"dev": "tsx dev/vite.ts",
|
|
43
46
|
"format": "prettier --write --cache --ignore-unknown .",
|
|
44
|
-
"link-watch": "pnpm run generate-types && plugin-kit link-watch",
|
|
45
|
-
"lint": "eslint .",
|
|
46
|
-
"prepublish-only": "run-s build",
|
|
47
|
-
"watch": "pkg-utils watch --strict",
|
|
48
47
|
"generate-types": "node src/lib/generate-types",
|
|
49
|
-
"
|
|
48
|
+
"lint": "eslint .",
|
|
49
|
+
"release": "release-it",
|
|
50
|
+
"watch": "pkg-utils watch --strict"
|
|
50
51
|
},
|
|
51
52
|
"browserslist": "extends @sanity/browserslist-config",
|
|
52
53
|
"dependencies": {
|
|
53
|
-
"@
|
|
54
|
-
"@iconify/
|
|
55
|
-
"@
|
|
56
|
-
"@sanity/icons": "^2.11.4",
|
|
54
|
+
"@iconify/react": "^5.0.2",
|
|
55
|
+
"@iconify/utils": "^2.1.32",
|
|
56
|
+
"@sanity/icons": "^3.4.0",
|
|
57
57
|
"@sanity/incompatible-plugin": "^1.0.4",
|
|
58
|
-
"@sanity/ui": "^
|
|
59
|
-
"@tanstack/react-query": "^5.
|
|
60
|
-
"change-case": "^5.4.
|
|
61
|
-
"
|
|
62
|
-
"
|
|
58
|
+
"@sanity/ui": "^2.8.9",
|
|
59
|
+
"@tanstack/react-query": "^5.55.0",
|
|
60
|
+
"change-case": "^5.4.4",
|
|
61
|
+
"downshift": "^9.0.8",
|
|
62
|
+
"ts-pattern": "^5.3.1",
|
|
63
|
+
"use-debounce": "^10.0.3"
|
|
63
64
|
},
|
|
64
65
|
"devDependencies": {
|
|
65
|
-
"@commitlint/cli": "^19.
|
|
66
|
-
"@commitlint/config-conventional": "^19.1
|
|
67
|
-
"@
|
|
68
|
-
"@iconify/json": "^2.2.195",
|
|
66
|
+
"@commitlint/cli": "^19.4.1",
|
|
67
|
+
"@commitlint/config-conventional": "^19.4.1",
|
|
68
|
+
"@iconify/json": "^2.2.245",
|
|
69
69
|
"@iconify/types": "^2.0.0",
|
|
70
|
-
"@
|
|
71
|
-
"@sanity/
|
|
72
|
-
"@sanity/
|
|
73
|
-
"@tanstack/eslint-plugin-query": "^5.
|
|
74
|
-
"@tanstack/react-query-devtools": "^5.
|
|
75
|
-
"@types/react": "^18.
|
|
70
|
+
"@release-it/conventional-changelog": "^8.0.1",
|
|
71
|
+
"@sanity/pkg-utils": "^6.11.0",
|
|
72
|
+
"@sanity/plugin-kit": "^4.0.18",
|
|
73
|
+
"@tanstack/eslint-plugin-query": "^5.53.0",
|
|
74
|
+
"@tanstack/react-query-devtools": "^5.55.0",
|
|
75
|
+
"@types/react": "^18.3.5",
|
|
76
76
|
"@types/styled-components": "^5.1.34",
|
|
77
|
-
"@
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"react-dom": "^18.2.0",
|
|
92
|
-
"react-is": "^18.2.0",
|
|
93
|
-
"rimraf": "^5.0.5",
|
|
94
|
-
"sanity": "^3.35.2",
|
|
95
|
-
"typescript": "^5.4.3"
|
|
77
|
+
"@waspeer/config": "^2.3.2",
|
|
78
|
+
"eslint": "^9.9.1",
|
|
79
|
+
"lefthook": "^1.7.15",
|
|
80
|
+
"npm-run-all2": "^6.2.2",
|
|
81
|
+
"prettier": "^3.3.3",
|
|
82
|
+
"react": "^18.3.1",
|
|
83
|
+
"react-dom": "^18.3.1",
|
|
84
|
+
"react-is": "^18.3.1",
|
|
85
|
+
"release-it": "^17.6.0",
|
|
86
|
+
"rimraf": "^6.0.1",
|
|
87
|
+
"sanity": "^3.57.0",
|
|
88
|
+
"tsx": "^4.19.0",
|
|
89
|
+
"typescript": "^5.5.4",
|
|
90
|
+
"vite": "^5.4.3"
|
|
96
91
|
},
|
|
97
92
|
"peerDependencies": {
|
|
98
93
|
"react": "^18",
|
|
99
94
|
"sanity": "^3",
|
|
100
|
-
"styled-components": "^
|
|
95
|
+
"styled-components": "^6"
|
|
101
96
|
},
|
|
102
97
|
"engines": {
|
|
103
|
-
"node": ">=
|
|
98
|
+
"node": ">=18"
|
|
104
99
|
},
|
|
105
100
|
"pnpm": {
|
|
106
101
|
"overrides": {
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"follow-redirects@<1.15.4": ">=1.15.4",
|
|
110
|
-
"follow-redirects@<=1.15.5": ">=1.15.6"
|
|
102
|
+
"eslint": "^9",
|
|
103
|
+
"micromatch@<4.0.8": ">=4.0.8"
|
|
111
104
|
}
|
|
112
105
|
},
|
|
113
106
|
"sanityPlugin": {
|
|
114
107
|
"verifyPackage": {
|
|
115
|
-
"scripts": false
|
|
108
|
+
"scripts": false,
|
|
109
|
+
"eslintImports": false
|
|
116
110
|
}
|
|
117
111
|
}
|
|
118
112
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Box, Card, Grid, Text } from '@sanity/ui';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
3
|
import styled from 'styled-components';
|
|
4
4
|
|
|
5
5
|
export const ComboboxWrapper = styled(Grid)`
|
|
@@ -12,30 +12,30 @@ export const OptionsWrapper = styled(Box)`
|
|
|
12
12
|
padding: 0.5rem;
|
|
13
13
|
|
|
14
14
|
& [role='listbox'] {
|
|
15
|
-
display:
|
|
16
|
-
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-wrap: wrap;
|
|
17
17
|
gap: 0.5rem;
|
|
18
|
-
list-style: none;
|
|
19
18
|
margin: 0;
|
|
20
19
|
padding: 0;
|
|
21
|
-
|
|
22
|
-
@media (min-width: 650px) {
|
|
23
|
-
grid-template-columns: repeat(10, minmax(min(100%, 2.5rem), 1fr));
|
|
24
|
-
}
|
|
20
|
+
list-style: none;
|
|
25
21
|
}
|
|
26
22
|
|
|
27
23
|
& [role='option'] {
|
|
28
24
|
display: grid;
|
|
29
25
|
place-items: center;
|
|
26
|
+
width: clamp(3rem, 10vw, 4rem);
|
|
30
27
|
|
|
31
28
|
& button {
|
|
32
|
-
aspect-ratio: 1;
|
|
33
29
|
cursor: pointer;
|
|
34
30
|
width: 100%;
|
|
35
31
|
|
|
36
32
|
& > [data-ui='Box'] {
|
|
37
33
|
display: flex;
|
|
38
34
|
}
|
|
35
|
+
|
|
36
|
+
& svg {
|
|
37
|
+
aspect-ratio: 1;
|
|
38
|
+
}
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
`;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { Combobox } from '@headlessui/react';
|
|
2
1
|
import { Popover, useToast } from '@sanity/ui';
|
|
3
|
-
import {
|
|
2
|
+
import { useCombobox } from 'downshift';
|
|
3
|
+
import { useCallback, useEffect, useId, useRef } from 'react';
|
|
4
4
|
import { match } from 'ts-pattern';
|
|
5
|
-
|
|
6
5
|
import { useSearch } from '../lib/api';
|
|
7
|
-
import {
|
|
6
|
+
import { OptionsWrapper } from './iconify-combobox.styles';
|
|
8
7
|
import { SearchInput } from './search-input';
|
|
9
|
-
import {
|
|
8
|
+
import type { SearchResultsProps } from './search-result';
|
|
9
|
+
import { SearchResults } from './search-result';
|
|
10
10
|
import { UnsetButton } from './unset-button';
|
|
11
11
|
|
|
12
12
|
export interface IconifyComboboxProps {
|
|
@@ -27,20 +27,34 @@ export function IconifyCombobox(props: IconifyComboboxProps) {
|
|
|
27
27
|
collections,
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
const {
|
|
31
|
+
isOpen,
|
|
32
|
+
getMenuProps,
|
|
33
|
+
getInputProps,
|
|
34
|
+
highlightedIndex,
|
|
35
|
+
getItemProps,
|
|
36
|
+
selectItem,
|
|
37
|
+
setInputValue,
|
|
38
|
+
} = useCombobox({
|
|
39
|
+
items: data ?? [],
|
|
40
|
+
inputValue: term,
|
|
41
|
+
onInputValueChange({ inputValue, selectedItem }) {
|
|
42
|
+
if (inputValue !== selectedItem) {
|
|
43
|
+
setTerm(inputValue);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
onSelectedItemChange({ selectedItem }) {
|
|
47
|
+
pushSelection(selectedItem);
|
|
38
48
|
setTerm('', true);
|
|
49
|
+
setInputValue('');
|
|
39
50
|
},
|
|
40
|
-
|
|
41
|
-
);
|
|
51
|
+
});
|
|
42
52
|
|
|
43
|
-
const handleUnset = useCallback(() =>
|
|
53
|
+
const handleUnset = useCallback(() => {
|
|
54
|
+
pushSelection('');
|
|
55
|
+
setTerm('', true);
|
|
56
|
+
selectItem('');
|
|
57
|
+
}, []);
|
|
44
58
|
|
|
45
59
|
useEffect(() => {
|
|
46
60
|
if (isError) {
|
|
@@ -56,46 +70,41 @@ export function IconifyCombobox(props: IconifyComboboxProps) {
|
|
|
56
70
|
}, [error, id, isError, toast]);
|
|
57
71
|
|
|
58
72
|
return (
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
selectedIcon={selectedIcon}
|
|
67
|
-
onChange={handleTermChange}
|
|
68
|
-
/>
|
|
73
|
+
<div>
|
|
74
|
+
{/* @ts-expect-error wrong typings */}
|
|
75
|
+
<SearchInput
|
|
76
|
+
{...getInputProps({ ref: inputRef })}
|
|
77
|
+
selectedIcon={selectedIcon}
|
|
78
|
+
suffix={selectedIcon ? <UnsetButton onUnset={handleUnset} /> : null}
|
|
79
|
+
/>
|
|
69
80
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
81
|
+
<Popover
|
|
82
|
+
open={true}
|
|
83
|
+
style={{ display: isOpen ? 'block' : 'none' }}
|
|
84
|
+
placement="bottom"
|
|
85
|
+
arrow={false}
|
|
86
|
+
matchReferenceWidth
|
|
87
|
+
constrainSize
|
|
88
|
+
referenceElement={inputRef.current}
|
|
89
|
+
content={
|
|
90
|
+
<OptionsWrapper>
|
|
91
|
+
<SearchResults
|
|
92
|
+
{...getMenuProps()}
|
|
93
|
+
state={match<boolean>(true)
|
|
94
|
+
.returnType<SearchResultsProps['state']>()
|
|
95
|
+
.with(isLoading, () => 'loading')
|
|
96
|
+
.with(!debouncedTerm, () => 'initial')
|
|
97
|
+
.with(isError, () => 'error')
|
|
98
|
+
.with(!data || data.length === 0, () => 'empty')
|
|
99
|
+
.with(isPreviousData, () => 'stale')
|
|
100
|
+
.otherwise(() => 'data')}
|
|
101
|
+
data={data}
|
|
102
|
+
getItemProps={getItemProps}
|
|
103
|
+
highlightedIndex={highlightedIndex}
|
|
93
104
|
/>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
{selectedIcon ? <UnsetButton onUnset={handleUnset} /> : null}
|
|
99
|
-
</ComboboxWrapper>
|
|
105
|
+
</OptionsWrapper>
|
|
106
|
+
}
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
100
109
|
);
|
|
101
110
|
}
|
|
@@ -1,30 +1,23 @@
|
|
|
1
|
-
import { Combobox } from '@headlessui/react';
|
|
2
1
|
import { Icon } from '@iconify/react';
|
|
3
2
|
import { TextInput } from '@sanity/ui';
|
|
4
|
-
import {
|
|
3
|
+
import { forwardRef } from 'react';
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
// SEARCH INPUT //
|
|
8
|
-
// ------------ //
|
|
9
|
-
|
|
10
|
-
interface SearchInputProps {
|
|
11
|
-
term: string;
|
|
5
|
+
interface SearchInputProps extends React.HTMLAttributes<HTMLInputElement> {
|
|
12
6
|
selectedIcon: string | null;
|
|
13
|
-
|
|
7
|
+
suffix?: React.ReactNode;
|
|
14
8
|
}
|
|
15
9
|
|
|
16
10
|
export const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>((props, ref) => {
|
|
17
|
-
const {
|
|
11
|
+
const { selectedIcon, suffix, ...rest } = props;
|
|
18
12
|
|
|
19
13
|
return (
|
|
20
|
-
<
|
|
21
|
-
|
|
14
|
+
<TextInput
|
|
15
|
+
{...rest}
|
|
22
16
|
ref={ref}
|
|
23
17
|
inputMode="search"
|
|
24
|
-
value={term}
|
|
25
|
-
onChange={onChange}
|
|
26
18
|
icon={selectedIcon ? <Icon icon={selectedIcon} /> : null}
|
|
27
19
|
placeholder={selectedIcon ? 'Search and replace selected icon...' : 'Search for an icon...'}
|
|
20
|
+
suffix={suffix}
|
|
28
21
|
/>
|
|
29
22
|
);
|
|
30
23
|
});
|
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
import { Combobox } from '@headlessui/react';
|
|
2
1
|
import { Icon } from '@iconify/react';
|
|
3
2
|
import { Button } from '@sanity/ui';
|
|
4
|
-
|
|
3
|
+
import type { UseComboboxGetItemPropsOptions, UseComboboxGetItemPropsReturnValue } from 'downshift';
|
|
4
|
+
import { forwardRef } from 'react';
|
|
5
5
|
import { MessageWrapper } from './iconify-combobox.styles';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
type GetItemProps = (
|
|
8
|
+
options: UseComboboxGetItemPropsOptions<string>,
|
|
9
|
+
) => UseComboboxGetItemPropsReturnValue;
|
|
10
10
|
|
|
11
|
-
export interface SearchResultsProps {
|
|
11
|
+
export interface SearchResultsProps extends React.HTMLAttributes<HTMLUListElement> {
|
|
12
12
|
state: 'initial' | 'loading' | 'error' | 'empty' | 'data' | 'stale';
|
|
13
13
|
data?: string[];
|
|
14
|
+
getItemProps: GetItemProps;
|
|
15
|
+
highlightedIndex: number;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
export
|
|
17
|
-
const { state, data } = props;
|
|
18
|
-
|
|
19
|
-
if (state === 'initial') {
|
|
20
|
-
return <MessageWrapper>Search for icons</MessageWrapper>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (state === 'loading') {
|
|
24
|
-
return <MessageWrapper>Searching...</MessageWrapper>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (state === 'error') {
|
|
28
|
-
return <MessageWrapper>Something went wrong...</MessageWrapper>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (state === 'empty') {
|
|
32
|
-
return <MessageWrapper>No icons found</MessageWrapper>;
|
|
33
|
-
}
|
|
18
|
+
export const SearchResults = forwardRef<HTMLUListElement, SearchResultsProps>((props, ref) => {
|
|
19
|
+
const { state, data, getItemProps, highlightedIndex, ...rest } = props;
|
|
34
20
|
|
|
35
21
|
return (
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
22
|
+
<>
|
|
23
|
+
{(() => {
|
|
24
|
+
switch (state) {
|
|
25
|
+
case 'initial':
|
|
26
|
+
return <MessageWrapper>Search for icons</MessageWrapper>;
|
|
27
|
+
case 'loading':
|
|
28
|
+
return <MessageWrapper>Searching...</MessageWrapper>;
|
|
29
|
+
case 'error':
|
|
30
|
+
return <MessageWrapper>Something went wrong...</MessageWrapper>;
|
|
31
|
+
case 'empty':
|
|
32
|
+
return <MessageWrapper>No icons found</MessageWrapper>;
|
|
33
|
+
}
|
|
34
|
+
})()}
|
|
35
|
+
|
|
36
|
+
<ul {...rest} ref={ref} style={{ opacity: state === 'stale' ? 0.5 : 1 }}>
|
|
37
|
+
{(state === 'data' || state === 'stale') &&
|
|
38
|
+
data?.map((icon, index) => (
|
|
39
|
+
<li key={icon} {...getItemProps({ item: icon, index })}>
|
|
40
|
+
<Button padding={3} mode="bleed" selected={index === highlightedIndex}>
|
|
41
|
+
<Icon icon={icon} width="100%" height="100%" />
|
|
42
|
+
</Button>
|
|
43
|
+
</li>
|
|
44
|
+
))}
|
|
45
|
+
</ul>
|
|
46
|
+
</>
|
|
47
47
|
);
|
|
48
|
-
}
|
|
48
|
+
});
|
|
@@ -13,7 +13,7 @@ export function UnsetButton(props: UnsetButtonProps) {
|
|
|
13
13
|
const { onUnset } = props;
|
|
14
14
|
|
|
15
15
|
return (
|
|
16
|
-
<Card border borderLeft={false} padding={1} display="flex">
|
|
16
|
+
<Card border borderLeft={false} padding={1} display="flex" radius={2}>
|
|
17
17
|
<Button icon={<TrashIcon />} onClick={onUnset} mode="bleed" fontSize={1} padding={2} />
|
|
18
18
|
</Card>
|
|
19
19
|
);
|