sanity-plugin-iconify 2.0.0 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sanity-plugin-iconify",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Icon picker based on Iconify",
5
5
  "keywords": [
6
6
  "sanity",
@@ -37,54 +37,57 @@
37
37
  "v2-incompatible.js"
38
38
  ],
39
39
  "scripts": {
40
- "build": "run-s clean && plugin-kit verify-package --silent && pkg-utils build --strict && pkg-utils --strict",
41
- "clean": "rimraf dist",
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",
42
46
  "format": "prettier --write --cache --ignore-unknown .",
43
- "link-watch": "pnpm run generate-types && plugin-kit link-watch",
44
- "lint": "eslint .",
45
- "prepublish-only": "run-s build",
46
- "watch": "pkg-utils watch --strict",
47
47
  "generate-types": "node src/lib/generate-types",
48
- "prepare": "husky"
48
+ "lint": "eslint .",
49
+ "release": "release-it",
50
+ "watch": "pkg-utils watch --strict"
49
51
  },
50
52
  "browserslist": "extends @sanity/browserslist-config",
51
53
  "dependencies": {
52
- "@headlessui/react": "^2.0.4",
53
- "@iconify/react": "^5.0.1",
54
- "@iconify/utils": "^2.1.24",
55
- "@sanity/icons": "^3.2.0",
54
+ "@iconify/react": "^5.0.2",
55
+ "@iconify/utils": "^2.1.32",
56
+ "@sanity/icons": "^3.4.0",
56
57
  "@sanity/incompatible-plugin": "^1.0.4",
57
- "@sanity/ui": "^2.3.5",
58
- "@tanstack/react-query": "^5.45.0",
58
+ "@sanity/ui": "^2.8.9",
59
+ "@tanstack/react-query": "^5.55.0",
59
60
  "change-case": "^5.4.4",
60
- "ts-pattern": "^5.2.0",
61
- "use-debounce": "^10.0.1"
61
+ "downshift": "^9.0.8",
62
+ "ts-pattern": "^5.3.1",
63
+ "use-debounce": "^10.0.3"
62
64
  },
63
65
  "devDependencies": {
64
- "@commitlint/cli": "^19.3.0",
65
- "@commitlint/config-conventional": "^19.2.2",
66
- "@commitlint/load": "^19.2.0",
67
- "@iconify/json": "^2.2.218",
66
+ "@commitlint/cli": "^19.4.1",
67
+ "@commitlint/config-conventional": "^19.4.1",
68
+ "@iconify/json": "^2.2.245",
68
69
  "@iconify/types": "^2.0.0",
69
- "@sanity/pkg-utils": "^6.9.3",
70
- "@sanity/plugin-kit": "^4.0.17",
71
- "@sanity/semantic-release-preset": "^5.0.0",
72
- "@tanstack/eslint-plugin-query": "^5.43.1",
73
- "@tanstack/react-query-devtools": "^5.45.0",
74
- "@types/react": "^18.3.3",
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",
75
76
  "@types/styled-components": "^5.1.34",
76
- "@waspeer/config": "^2.2.0",
77
- "eslint": "^9.4.0",
78
- "husky": "^9.0.11",
79
- "lint-staged": "^15.2.7",
80
- "npm-run-all": "^4.1.5",
81
- "prettier": "^3.3.2",
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
82
  "react": "^18.3.1",
83
83
  "react-dom": "^18.3.1",
84
84
  "react-is": "^18.3.1",
85
- "rimraf": "^5.0.7",
86
- "sanity": "^3.46.0",
87
- "typescript": "^5.4.5"
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"
88
91
  },
89
92
  "peerDependencies": {
90
93
  "react": "^18",
@@ -96,11 +99,8 @@
96
99
  },
97
100
  "pnpm": {
98
101
  "overrides": {
99
- "@babel/traverse@<7.23.2": ">=7.23.2",
100
- "conventional-changelog-conventionalcommits": "^8",
101
- "follow-redirects@<1.15.4": ">=1.15.4",
102
- "follow-redirects@<=1.15.5": ">=1.15.6",
103
- "postcss@<8.4.31": ">=8.4.31"
102
+ "eslint": "^9",
103
+ "micromatch@<4.0.8": ">=4.0.8"
104
104
  }
105
105
  },
106
106
  "sanityPlugin": {
@@ -12,30 +12,30 @@ export const OptionsWrapper = styled(Box)`
12
12
  padding: 0.5rem;
13
13
 
14
14
  & [role='listbox'] {
15
- display: grid;
16
- grid-template-columns: repeat(auto-fill, minmax(min(100%, 2.5rem), 1fr));
15
+ display: flex;
16
+ flex-wrap: wrap;
17
17
  gap: 0.5rem;
18
18
  margin: 0;
19
19
  padding: 0;
20
20
  list-style: none;
21
-
22
- @media (min-width: 650px) {
23
- grid-template-columns: repeat(10, minmax(min(100%, 2.5rem), 1fr));
24
- }
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
29
  cursor: pointer;
33
- aspect-ratio: 1;
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,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,34 @@ 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);
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);
39
48
  setTerm('', true);
49
+ setInputValue('');
40
50
  },
41
- [pushSelection, setTerm],
42
- );
51
+ });
43
52
 
44
- const handleUnset = useCallback(() => handleSelect(''), [handleSelect]);
53
+ const handleUnset = useCallback(() => {
54
+ pushSelection('');
55
+ setTerm('', true);
56
+ selectItem('');
57
+ }, []);
45
58
 
46
59
  useEffect(() => {
47
60
  if (isError) {
@@ -57,45 +70,41 @@ export function IconifyCombobox(props: IconifyComboboxProps) {
57
70
  }, [error, id, isError, toast]);
58
71
 
59
72
  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
- />
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
+ />
71
80
 
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
- }
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}
95
104
  />
96
- </>
97
- )}
98
- </Combobox>
99
- </ComboboxWrapper>
105
+ </OptionsWrapper>
106
+ }
107
+ />
108
+ </div>
100
109
  );
101
110
  }
@@ -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' | 'ph' | 'solar' | 'tabler' | 'hugeicons' | 'mingcute' | 'ri' | 'bi' | 'carbon' | 'iconamoon' | 'iconoir' | 'ion' | 'lucide' | 'lucide-lab' | 'uil' | 'tdesign' | 'teenyicons' | 'clarity' | 'bx' | 'bxs' | 'majesticons' | 'ant-design' | 'gg' | 'gravity-ui' | 'octicon' | 'memory' | 'cil' | 'flowbite' | 'mynaui' | 'basil' | 'pixelarticons' | 'akar-icons' | 'ci' | 'system-uicons' | 'typcn' | 'radix-icons' | 'zondicons' | 'ep' | 'circum' | 'mdi-light' | 'fe' | 'eos-icons' | 'bitcoin-icons' | 'charm' | 'prime' | 'humbleicons' | 'uiw' | 'uim' | 'uit' | 'uis' | 'maki' | 'gridicons' | 'mi' | 'weui' | 'quill' | 'gala' | 'lets-icons' | 'f7' | 'mage' | 'marketeq' | 'fluent' | 'icon-park-outline' | 'icon-park-solid' | 'icon-park-twotone' | 'icon-park' | 'jam' | 'heroicons' | 'pajamas' | 'pepicons-pop' | 'pepicons-print' | 'pepicons-pencil' | 'bytesize' | 'ei' | 'streamline' | 'guidance' | 'fa6-solid' | 'fa6-regular' | 'ooui' | 'rivet-icons' | 'nimbus' | 'oui' | 'formkit' | 'line-md' | 'meteocons' | 'svg-spinners' | 'openmoji' | 'twemoji' | 'noto' | 'fluent-emoji' | 'fluent-emoji-flat' | 'fluent-emoji-high-contrast' | 'noto-v1' | 'emojione' | 'emojione-monotone' | 'emojione-v1' | 'fxemoji' | 'streamline-emojis' | 'bxl' | 'logos' | 'simple-icons' | 'cib' | 'fa6-brands' | 'nonicons' | 'arcticons' | 'cbi' | 'brandico' | 'entypo-social' | 'token' | 'token-branded' | 'cryptocurrency' | 'cryptocurrency-color' | 'flag' | 'circle-flags' | 'flagpack' | 'cif' | 'gis' | 'map' | 'geo' | 'vscode-icons' | 'codicon' | 'file-icons' | 'devicon' | 'devicon-plain' | 'catppuccin' | 'skill-icons' | 'unjs' | 'game-icons' | 'fad' | 'academicons' | 'wi' | 'healthicons' | 'medical-icon' | 'covid' | 'la' | 'eva' | 'dashicons' | 'flat-color-icons' | 'entypo' | 'foundation' | 'raphael' | 'icons8' | 'iwwa' | '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';