sanity-plugin-iconify 2.0.0 → 2.0.2

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.
@@ -1,10 +1,9 @@
1
- import { Combobox } from '@headlessui/react';
2
1
  import { Popover, useToast } from '@sanity/ui';
3
- import type { ChangeEventHandler } from 'react';
2
+ import { useCombobox } from 'downshift';
4
3
  import { useCallback, useEffect, useId, useRef } from 'react';
5
4
  import { match } from 'ts-pattern';
6
5
  import { useSearch } from '../lib/api';
7
- import { ComboboxWrapper, OptionsWrapper } from './iconify-combobox.styles';
6
+ import { OptionsWrapper } from './iconify-combobox.styles';
8
7
  import { SearchInput } from './search-input';
9
8
  import type { SearchResultsProps } from './search-result';
10
9
  import { SearchResults } from './search-result';
@@ -28,20 +27,36 @@ export function IconifyCombobox(props: IconifyComboboxProps) {
28
27
  collections,
29
28
  });
30
29
 
31
- const handleTermChange: ChangeEventHandler<HTMLInputElement> = useCallback(
32
- (event) => setTerm(event.target.value),
33
- [setTerm],
34
- );
35
-
36
- const handleSelect = useCallback(
37
- (icon: string) => {
38
- pushSelection(icon);
39
- setTerm('', true);
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
+ }
40
45
  },
41
- [pushSelection, setTerm],
42
- );
46
+ onSelectedItemChange({ selectedItem }) {
47
+ if (selectedItem) {
48
+ pushSelection(selectedItem);
49
+ setTerm('', true);
50
+ setInputValue('');
51
+ }
52
+ },
53
+ });
43
54
 
44
- const handleUnset = useCallback(() => handleSelect(''), [handleSelect]);
55
+ const handleUnset = useCallback(() => {
56
+ pushSelection('');
57
+ setTerm('', true);
58
+ selectItem('');
59
+ }, []);
45
60
 
46
61
  useEffect(() => {
47
62
  if (isError) {
@@ -57,45 +72,41 @@ export function IconifyCombobox(props: IconifyComboboxProps) {
57
72
  }, [error, id, isError, toast]);
58
73
 
59
74
  return (
60
- <ComboboxWrapper>
61
- <Combobox onChange={handleSelect}>
62
- {({ open }) => (
63
- <>
64
- <SearchInput
65
- ref={inputRef}
66
- term={term}
67
- selectedIcon={selectedIcon}
68
- onChange={handleTermChange}
69
- suffix={selectedIcon ? <UnsetButton onUnset={handleUnset} /> : null}
70
- />
75
+ <div>
76
+ {/* @ts-expect-error wrong typings */}
77
+ <SearchInput
78
+ {...getInputProps({ ref: inputRef })}
79
+ selectedIcon={selectedIcon}
80
+ suffix={selectedIcon ? <UnsetButton onUnset={handleUnset} /> : null}
81
+ />
71
82
 
72
- <Popover
73
- open={open}
74
- placement="bottom"
75
- arrow={false}
76
- matchReferenceWidth
77
- portal
78
- constrainSize
79
- referenceElement={inputRef.current}
80
- content={
81
- <OptionsWrapper>
82
- <SearchResults
83
- state={match<boolean>(true)
84
- .returnType<SearchResultsProps['state']>()
85
- .with(isLoading, () => 'loading')
86
- .with(!debouncedTerm, () => 'initial')
87
- .with(isError, () => 'error')
88
- .with(!data || data.length === 0, () => 'empty')
89
- .with(isPreviousData, () => 'stale')
90
- .otherwise(() => 'data')}
91
- data={data}
92
- />
93
- </OptionsWrapper>
94
- }
83
+ <Popover
84
+ open={true}
85
+ style={{ display: isOpen ? 'block' : 'none' }}
86
+ placement="bottom"
87
+ arrow={false}
88
+ matchReferenceWidth
89
+ constrainSize
90
+ referenceElement={inputRef.current}
91
+ content={
92
+ <OptionsWrapper>
93
+ <SearchResults
94
+ {...getMenuProps()}
95
+ state={match<boolean>(true)
96
+ .returnType<SearchResultsProps['state']>()
97
+ .with(isLoading, () => 'loading')
98
+ .with(!debouncedTerm, () => 'initial')
99
+ .with(isError, () => 'error')
100
+ .with(!data || data.length === 0, () => 'empty')
101
+ .with(isPreviousData, () => 'stale')
102
+ .otherwise(() => 'data')}
103
+ data={data}
104
+ getItemProps={getItemProps}
105
+ highlightedIndex={highlightedIndex}
95
106
  />
96
- </>
97
- )}
98
- </Combobox>
99
- </ComboboxWrapper>
107
+ </OptionsWrapper>
108
+ }
109
+ />
110
+ </div>
100
111
  );
101
112
  }
@@ -1,30 +1,20 @@
1
- import { ComboboxInput } from '@headlessui/react';
2
1
  import { Icon } from '@iconify/react';
3
2
  import { TextInput } from '@sanity/ui';
4
- import type { ChangeEventHandler } from 'react';
5
3
  import { forwardRef } from 'react';
6
4
 
7
- // ------------ //
8
- // SEARCH INPUT //
9
- // ------------ //
10
-
11
- interface SearchInputProps {
12
- term: string;
5
+ interface SearchInputProps extends React.HTMLAttributes<HTMLInputElement> {
13
6
  selectedIcon: string | null;
14
- onChange: ChangeEventHandler<HTMLInputElement>;
15
7
  suffix?: React.ReactNode;
16
8
  }
17
9
 
18
10
  export const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>((props, ref) => {
19
- const { term, selectedIcon, onChange, suffix } = props;
11
+ const { selectedIcon, suffix, ...rest } = props;
20
12
 
21
13
  return (
22
- <ComboboxInput
23
- as={TextInput}
14
+ <TextInput
15
+ {...rest}
24
16
  ref={ref}
25
17
  inputMode="search"
26
- value={term}
27
- onChange={onChange}
28
18
  icon={selectedIcon ? <Icon icon={selectedIcon} /> : null}
29
19
  placeholder={selectedIcon ? 'Search and replace selected icon...' : 'Search for an icon...'}
30
20
  suffix={suffix}
@@ -1,47 +1,48 @@
1
- import { ComboboxOption, ComboboxOptions } from '@headlessui/react';
2
1
  import { Icon } from '@iconify/react';
3
2
  import { Button } from '@sanity/ui';
3
+ import type { UseComboboxGetItemPropsOptions, UseComboboxGetItemPropsReturnValue } from 'downshift';
4
+ import { forwardRef } from 'react';
4
5
  import { MessageWrapper } from './iconify-combobox.styles';
5
6
 
6
- // -------------- //
7
- // SEARCH RESULTS //
8
- // -------------- //
7
+ type GetItemProps = (
8
+ options: UseComboboxGetItemPropsOptions<string>,
9
+ ) => UseComboboxGetItemPropsReturnValue;
9
10
 
10
- export interface SearchResultsProps {
11
+ export interface SearchResultsProps extends React.HTMLAttributes<HTMLUListElement> {
11
12
  state: 'initial' | 'loading' | 'error' | 'empty' | 'data' | 'stale';
12
13
  data?: string[];
14
+ getItemProps: GetItemProps;
15
+ highlightedIndex: number;
13
16
  }
14
17
 
15
- export function SearchResults(props: SearchResultsProps) {
16
- const { state, data } = props;
17
-
18
- if (state === 'initial') {
19
- return <MessageWrapper>Search for icons</MessageWrapper>;
20
- }
21
-
22
- if (state === 'loading') {
23
- return <MessageWrapper>Searching...</MessageWrapper>;
24
- }
25
-
26
- if (state === 'error') {
27
- return <MessageWrapper>Something went wrong...</MessageWrapper>;
28
- }
29
-
30
- if (state === 'empty') {
31
- return <MessageWrapper>No icons found</MessageWrapper>;
32
- }
18
+ export const SearchResults = forwardRef<HTMLUListElement, SearchResultsProps>((props, ref) => {
19
+ const { state, data, getItemProps, highlightedIndex, ...rest } = props;
33
20
 
34
21
  return (
35
- <ComboboxOptions style={{ opacity: state === 'stale' ? 0.5 : 1 }}>
36
- {data!.map((icon) => (
37
- <ComboboxOption key={icon} value={icon}>
38
- {({ focus }) => (
39
- <Button padding={3} mode="bleed" selected={focus}>
40
- <Icon icon={icon} width="100%" height="100%" />
41
- </Button>
42
- )}
43
- </ComboboxOption>
44
- ))}
45
- </ComboboxOptions>
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
+ </>
46
47
  );
47
- }
48
+ });
@@ -8,10 +8,6 @@ import { QueryClientProvider } from './lib/query-client';
8
8
  import type { IconifyPluginConfig, IconOptions } from './lib/types';
9
9
  import { usePrettyIconName } from './lib/use-pretty-icon-name';
10
10
 
11
- // -------------- //
12
- // ICONIFY INPUT //
13
- // -------------- //
14
-
15
11
  interface IconifyInputProps extends ObjectInputProps {
16
12
  config: IconifyPluginConfig;
17
13
  }
@@ -28,12 +24,6 @@ export function IconifyInput(props: IconifyInputProps) {
28
24
  null;
29
25
  const showName = options?.showName ?? config?.showName ?? false;
30
26
 
31
- // const parentTheme = useTheme();
32
- // const theme: RootTheme = useMemo(
33
- // () => (parentTheme ? { ...parentTheme.sanity, color: studioTheme.color } : studioTheme),
34
- // [parentTheme],
35
- // );
36
-
37
27
  const handleSelect = useCallback(
38
28
  (icon: string) => {
39
29
  pushChange(icon === '' ? unset() : set(icon, ['name']));
@@ -62,7 +52,7 @@ interface IconifyNameDisplayProps {
62
52
  name?: string | null;
63
53
  }
64
54
 
65
- function IconifyNameDisplay(props: IconifyNameDisplayProps) {
55
+ export function IconifyNameDisplay(props: IconifyNameDisplayProps) {
66
56
  const { name } = props;
67
57
  const prettyName = usePrettyIconName({ name });
68
58
 
@@ -0,0 +1 @@
1
+ export type IconPrefix = 'material-symbols' | 'material-symbols-light' | 'ic' | 'mdi' | 'mdi-light' | 'line-md' | 'solar' | 'tabler' | 'hugeicons' | 'mingcute' | 'ri' | 'mynaui' | 'iconamoon' | 'iconoir' | 'lucide' | 'lucide-lab' | 'uil' | 'tdesign' | 'si' | 'bx' | 'bxs' | 'majesticons' | 'gg' | 'flowbite' | 'basil' | 'pixelarticons' | 'pixel' | 'akar-icons' | 'ci' | 'proicons' | 'typcn' | 'meteor-icons' | 'prime' | 'circum' | 'fe' | 'eos-icons' | 'bitcoin-icons' | 'humbleicons' | 'uim' | 'uit' | 'uis' | 'gridicons' | 'mi' | 'cuida' | 'weui' | 'duo-icons' | 'svg-spinners' | 'lets-icons' | 'mage' | 'stash' | 'lineicons' | 'icon-park-outline' | 'icon-park-solid' | 'icon-park-twotone' | 'jam' | 'guidance' | 'carbon' | 'ion' | 'famicons' | 'ant-design' | 'lsicon' | 'gravity-ui' | 'cil' | 'ep' | 'charm' | 'quill' | 'bytesize' | 'bi' | 'rivet-icons' | 'nimbus' | 'formkit' | 'fluent' | 'ph' | 'teenyicons' | 'clarity' | 'ix' | 'octicon' | 'memory' | 'system-uicons' | 'radix-icons' | 'zondicons' | 'uiw' | 'maki' | 'codex' | 'ei' | 'heroicons' | 'pepicons-pop' | 'pepicons-print' | 'pepicons-pencil' | 'f7' | 'pajamas' | 'garden' | 'streamline' | 'fa6-solid' | 'fa6-regular' | 'ooui' | 'oui' | 'nrk' | 'qlementine-icons' | 'fluent-color' | 'icon-park' | 'marketeq' | 'vscode-icons' | 'codicon' | 'material-icon-theme' | 'file-icons' | 'devicon' | 'devicon-plain' | 'catppuccin' | 'skill-icons' | 'unjs' | 'simple-icons' | 'logos' | 'cib' | 'fa6-brands' | 'bxl' | 'nonicons' | 'arcticons' | 'cbi' | 'brandico' | 'entypo-social' | 'token' | 'token-branded' | 'cryptocurrency' | 'cryptocurrency-color' | 'openmoji' | 'twemoji' | 'noto' | 'fluent-emoji' | 'fluent-emoji-flat' | 'fluent-emoji-high-contrast' | 'noto-v1' | 'emojione' | 'emojione-monotone' | 'emojione-v1' | 'fxemoji' | 'streamline-emojis' | 'circle-flags' | 'flag' | 'flagpack' | 'cif' | 'gis' | 'map' | 'geo' | 'game-icons' | 'fad' | 'academicons' | 'wi' | 'meteocons' | 'healthicons' | 'medical-icon' | 'covid' | 'la' | 'eva' | 'dashicons' | 'flat-color-icons' | 'entypo' | 'foundation' | 'raphael' | 'icons8' | 'iwwa' | 'gala' | 'heroicons-outline' | 'heroicons-solid' | 'fa-solid' | 'fa-regular' | 'fa-brands' | 'fa' | 'fluent-mdl2' | 'fontisto' | 'icomoon-free' | 'subway' | 'oi' | 'wpf' | 'simple-line-icons' | 'et' | 'el' | 'vaadin' | 'grommet-icons' | 'whh' | 'si-glyph' | 'zmdi' | 'ls' | 'bpmn' | 'flat-ui' | 'vs' | 'topcoat' | 'il' | 'websymbol' | 'fontelico' | 'ps' | 'feather' | 'mono-icons' | 'pepicons';