@sipesistemas/polaris 1.2.4 → 1.3.0

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.
Files changed (34) hide show
  1. package/build/cjs/components/ActionList/ActionList.js +1 -1
  2. package/build/cjs/components/Filters/components/FiltersBar/FiltersBar.js +11 -5
  3. package/build/cjs/components/Pagination/Pagination.js +6 -3
  4. package/build/cjs/components/SearchableSelect/SearchableSelect.css.js +9 -0
  5. package/build/cjs/components/SearchableSelect/SearchableSelect.js +243 -0
  6. package/build/cjs/index.js +2 -0
  7. package/build/esm/components/ActionList/ActionList.js +1 -1
  8. package/build/esm/components/Filters/components/FiltersBar/FiltersBar.js +11 -5
  9. package/build/esm/components/Pagination/Pagination.js +7 -4
  10. package/build/esm/components/SearchableSelect/SearchableSelect.css.js +5 -0
  11. package/build/esm/components/SearchableSelect/SearchableSelect.js +241 -0
  12. package/build/esm/index.js +1 -0
  13. package/build/esm/styles.css +14 -1
  14. package/build/esnext/components/ActionList/ActionList.esnext +1 -1
  15. package/build/esnext/components/AppProvider/global.out.css +1 -1
  16. package/build/esnext/components/Filters/components/FiltersBar/FiltersBar.esnext +11 -5
  17. package/build/esnext/components/Pagination/Pagination.esnext +7 -4
  18. package/build/esnext/components/SearchableSelect/SearchableSelect.css.esnext +7 -0
  19. package/build/esnext/components/SearchableSelect/SearchableSelect.esnext +241 -0
  20. package/build/esnext/components/SearchableSelect/SearchableSelect.out.css +11 -0
  21. package/build/esnext/index.esnext +1 -0
  22. package/build/ts/.storybook/main.d.ts.map +1 -1
  23. package/build/ts/src/components/Filters/components/FiltersBar/FiltersBar.d.ts.map +1 -1
  24. package/build/ts/src/components/Pagination/Pagination.d.ts +3 -1
  25. package/build/ts/src/components/Pagination/Pagination.d.ts.map +1 -1
  26. package/build/ts/src/components/SearchableSelect/SearchableSelect.d.ts +60 -0
  27. package/build/ts/src/components/SearchableSelect/SearchableSelect.d.ts.map +1 -0
  28. package/build/ts/src/components/SearchableSelect/index.d.ts +2 -0
  29. package/build/ts/src/components/SearchableSelect/index.d.ts.map +1 -0
  30. package/build/ts/src/index.d.ts +2 -0
  31. package/build/ts/src/index.d.ts.map +1 -1
  32. package/locales/en.json +7 -0
  33. package/locales/pt-BR.json +7 -0
  34. package/package.json +8 -5
@@ -52,7 +52,7 @@ function ActionList({
52
52
  actionRole: actionRole,
53
53
  onActionAnyItem: onActionAnyItem,
54
54
  isFirst: index === 0
55
- }, typeof section.title === 'string' ? section.title : index) : null;
55
+ }, typeof section.title === 'string' && section.title ? section.title : index) : null;
56
56
  });
57
57
  const handleFocusPreviousItem = evt => {
58
58
  evt.preventDefault();
@@ -75,11 +75,17 @@ function FiltersBar({
75
75
  togglePopoverActive();
76
76
  }, 0);
77
77
  };
78
- const filterToActionItem = filter => ({
79
- ...filter,
80
- content: filter.label,
81
- onAction: onFilterClick(filter)
82
- });
78
+ const filterToActionItem = filter => {
79
+ const {
80
+ key: _filterKey,
81
+ ...rest
82
+ } = filter;
83
+ return {
84
+ ...rest,
85
+ content: filter.label,
86
+ onAction: onFilterClick(filter)
87
+ };
88
+ };
83
89
  const unpinnedFilters = filters.filter(filter => !pinnedFilters.some(({
84
90
  key
85
91
  }) => key === filter.key));
@@ -29,15 +29,18 @@ function Pagination({
29
29
  accessibilityLabel,
30
30
  accessibilityLabels,
31
31
  label,
32
- type = 'page'
32
+ type = 'page',
33
+ direction = 'horizontal'
33
34
  }) {
34
35
  const i18n = hooks.useI18n();
35
36
  const node = /*#__PURE__*/react.createRef();
36
37
  const navLabel = accessibilityLabel || i18n.translate('Polaris.Pagination.pagination');
37
38
  const previousLabel = accessibilityLabels?.previous || i18n.translate('Polaris.Pagination.previous');
38
39
  const nextLabel = accessibilityLabels?.next || i18n.translate('Polaris.Pagination.next');
40
+ const previousIcon = direction === 'vertical' ? polarisIcons.ChevronUpIcon : polarisIcons.ChevronLeftIcon;
41
+ const nextIcon = direction === 'vertical' ? polarisIcons.ChevronDownIcon : polarisIcons.ChevronRightIcon;
39
42
  const prev = /*#__PURE__*/jsxRuntime.jsx(Button.Button, {
40
- icon: polarisIcons.ChevronLeftIcon,
43
+ icon: previousIcon,
41
44
  accessibilityLabel: previousLabel,
42
45
  url: previousURL,
43
46
  onClick: onPrevious,
@@ -51,7 +54,7 @@ function Pagination({
51
54
  children: prev
52
55
  }) : prev;
53
56
  const next = /*#__PURE__*/jsxRuntime.jsx(Button.Button, {
54
- icon: polarisIcons.ChevronRightIcon,
57
+ icon: nextIcon,
55
58
  accessibilityLabel: nextLabel,
56
59
  url: nextURL,
57
60
  onClick: onNext,
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var styles = {
6
+ "Activator": "Polaris-SearchableSelect__Activator"
7
+ };
8
+
9
+ exports.default = styles;
@@ -0,0 +1,243 @@
1
+ 'use strict';
2
+
3
+ var polarisIcons = require('@shopify/polaris-icons');
4
+ var react = require('react');
5
+ var SearchableSelect_module = require('./SearchableSelect.css.js');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+ var hooks = require('../../utilities/i18n/hooks.js');
8
+ var TextField = require('../TextField/TextField.js');
9
+ var Icon = require('../Icon/Icon.js');
10
+ var Listbox = require('../Listbox/Listbox.js');
11
+ var EmptySearchResult = require('../EmptySearchResult/EmptySearchResult.js');
12
+ var Popover = require('../Popover/Popover.js');
13
+ var Box = require('../Box/Box.js');
14
+ var Scrollable = require('../Scrollable/Scrollable.js');
15
+
16
+ function getValueByPath(obj, path) {
17
+ return path.split('.').reduce((acc, part) => acc?.[part], obj);
18
+ }
19
+ const SCROLLABLE_HEIGHT = 292;
20
+ function SearchableSelect({
21
+ loading,
22
+ loadingMore,
23
+ options,
24
+ selected,
25
+ onSelect,
26
+ emptyStateTitle = '',
27
+ emptyStateDescription,
28
+ onQueryChange,
29
+ selectedLabelKey,
30
+ label,
31
+ placeholder,
32
+ disabled,
33
+ requiredIndicator,
34
+ helpText,
35
+ error,
36
+ pinSelected = true,
37
+ allowClear,
38
+ onScrolledToBottom
39
+ }) {
40
+ const i18n = hooks.useI18n();
41
+ const [query, setQuery] = react.useState('');
42
+ const [popoverActive, setPopoverActive] = react.useState(false);
43
+ const [activeOptionId, setActiveOptionId] = react.useState();
44
+ // pagination/search can drop the selected option from `options`; the cache keeps its label visible
45
+ const [cachedSelection, setCachedSelection] = react.useState(null);
46
+ // clicking the clear button bubbles to TextField's container, which focuses the input
47
+ const skipNextFocusOpen = react.useRef(false);
48
+ const listboxId = react.useId();
49
+ const togglePopoverActive = react.useCallback(() => {
50
+ setPopoverActive(active => !active);
51
+ setQuery('');
52
+ onQueryChange('');
53
+ }, [onQueryChange]);
54
+ react.useEffect(() => {
55
+ function handleEsc(event) {
56
+ if (event.key === 'Escape') setPopoverActive(false);
57
+ }
58
+ document.addEventListener('keydown', handleEsc);
59
+ return () => document.removeEventListener('keydown', handleEsc);
60
+ }, []);
61
+
62
+ // keep the cache in sync while the selected option is loaded, so its label survives (and stays
63
+ // fresh) if pagination later drops the option from `options`
64
+ react.useEffect(() => {
65
+ if (selected.length < 1) return;
66
+ const loadedOption = options.find(option => option.value === selected[0]);
67
+ const label = getValueByPath(loadedOption?.object, selectedLabelKey);
68
+ if (typeof label === 'string' && (cachedSelection?.value !== selected[0] || cachedSelection.label !== label)) {
69
+ setCachedSelection({
70
+ value: selected[0],
71
+ label
72
+ });
73
+ }
74
+ }, [selected, options, selectedLabelKey, cachedSelection]);
75
+ const textSelectedItem = react.useMemo(() => {
76
+ if (selected.length < 1) return '';
77
+ const selectedItem = options.find(option => option.value === selected[0]);
78
+ const value = getValueByPath(selectedItem?.object, selectedLabelKey);
79
+ if (typeof value === 'string') return value;
80
+ return cachedSelection?.value === selected[0] ? cachedSelection.label : '';
81
+ }, [selected, options, selectedLabelKey, cachedSelection]);
82
+ const handleSelectionClear = react.useCallback(() => {
83
+ setCachedSelection(null);
84
+ onSelect([]);
85
+ if (requiredIndicator) {
86
+ setPopoverActive(true);
87
+ } else {
88
+ skipNextFocusOpen.current = true;
89
+ }
90
+ }, [onSelect, requiredIndicator]);
91
+ const handleActivatorFocus = react.useCallback(() => {
92
+ if (disabled) return;
93
+ if (skipNextFocusOpen.current) {
94
+ skipNextFocusOpen.current = false;
95
+ return;
96
+ }
97
+ setPopoverActive(true);
98
+ }, [disabled]);
99
+ const activator = /*#__PURE__*/jsxRuntime.jsx("div", {
100
+ className: SearchableSelect_module.default.Activator,
101
+ children: /*#__PURE__*/jsxRuntime.jsx(TextField.TextField, {
102
+ label: label,
103
+ placeholder: placeholder,
104
+ autoComplete: "off",
105
+ value: textSelectedItem,
106
+ disabled: disabled,
107
+ requiredIndicator: requiredIndicator,
108
+ helpText: helpText,
109
+ error: error,
110
+ clearButton: allowClear && textSelectedItem !== '',
111
+ onClearButtonClick: handleSelectionClear,
112
+ onFocus: handleActivatorFocus,
113
+ suffix: /*#__PURE__*/jsxRuntime.jsx(Icon.Icon, {
114
+ source: polarisIcons.CaretDownIcon
115
+ })
116
+ })
117
+ });
118
+ const pinnedSelectedOption = react.useMemo(() => {
119
+ if (query || selected.length < 1) return null;
120
+ const loadedOption = options.find(option => option.value === selected[0]);
121
+ if (loadedOption) {
122
+ return pinSelected ? {
123
+ value: loadedOption.value,
124
+ label: loadedOption.label
125
+ } : null;
126
+ }
127
+ return cachedSelection?.value === selected[0] ? cachedSelection : null;
128
+ }, [query, selected, options, cachedSelection, pinSelected]);
129
+ const optionsMarkup = react.useMemo(() => {
130
+ if (loading) return null;
131
+ const listOptions = pinnedSelectedOption ? options.filter(option => option.value !== pinnedSelectedOption.value) : options;
132
+ if (listOptions.length === 0 && !pinnedSelectedOption) return null;
133
+ const pinnedMarkup = pinnedSelectedOption ? /*#__PURE__*/jsxRuntime.jsx(Listbox.Listbox.Option, {
134
+ value: pinnedSelectedOption.value,
135
+ selected: true,
136
+ children: /*#__PURE__*/jsxRuntime.jsx(Listbox.Listbox.TextOption, {
137
+ selected: true,
138
+ children: pinnedSelectedOption.label
139
+ })
140
+ }, pinnedSelectedOption.value) : null;
141
+ return [pinnedMarkup, ...listOptions.map(option => {
142
+ const isSelected = selected.includes(option.value);
143
+ return /*#__PURE__*/jsxRuntime.jsx(Listbox.Listbox.Option, {
144
+ value: option.value,
145
+ selected: isSelected,
146
+ children: /*#__PURE__*/jsxRuntime.jsx(Listbox.Listbox.TextOption, {
147
+ selected: isSelected,
148
+ children: option.label
149
+ })
150
+ }, option.value);
151
+ })];
152
+ }, [loading, options, selected, pinnedSelectedOption]);
153
+ const loadingMarkup = loading ? /*#__PURE__*/jsxRuntime.jsx(Listbox.Listbox.Loading, {
154
+ accessibilityLabel: i18n.translate('Polaris.SearchableSelect.loadingAccessibilityLabel')
155
+ }) : null;
156
+ const loadingMoreMarkup = loadingMore ? /*#__PURE__*/jsxRuntime.jsx(Listbox.Listbox.Loading, {
157
+ accessibilityLabel: i18n.translate('Polaris.SearchableSelect.loadingMoreAccessibilityLabel')
158
+ }) : null;
159
+ const noResultsMarkup = !loading && options.length === 0 && !pinnedSelectedOption ? /*#__PURE__*/jsxRuntime.jsx(EmptySearchResult.EmptySearchResult, {
160
+ title: emptyStateTitle,
161
+ description: emptyStateDescription ?? (query ? i18n.translate('Polaris.SearchableSelect.noResultsFoundFor', {
162
+ query
163
+ }) : i18n.translate('Polaris.SearchableSelect.noResultsFound'))
164
+ }) : null;
165
+ const updateSelection = react.useCallback(newSelection => {
166
+ const option = options.find(({
167
+ value
168
+ }) => value === newSelection);
169
+ const label = getValueByPath(option?.object, selectedLabelKey);
170
+ if (typeof label === 'string') {
171
+ setCachedSelection({
172
+ value: newSelection,
173
+ label
174
+ });
175
+ } else if (cachedSelection?.value !== newSelection) {
176
+ // re-selecting the pinned option (absent from `options`) must keep its cached label
177
+ setCachedSelection(null);
178
+ }
179
+ onSelect([newSelection]);
180
+ setQuery('');
181
+ onQueryChange('');
182
+ setPopoverActive(false);
183
+ }, [options, selectedLabelKey, cachedSelection, onSelect, onQueryChange]);
184
+ const handleActiveOptionChange = react.useCallback((_value, domId) => {
185
+ setActiveOptionId(domId);
186
+ }, []);
187
+ const handleQueryClear = react.useCallback(() => {
188
+ setQuery('');
189
+ onQueryChange('');
190
+ }, [onQueryChange]);
191
+ return /*#__PURE__*/jsxRuntime.jsx(Popover.Popover, {
192
+ activator: activator,
193
+ active: popoverActive,
194
+ onClose: togglePopoverActive,
195
+ fullWidth: true,
196
+ autofocusTarget: "first-node",
197
+ preferInputActivator: false,
198
+ children: /*#__PURE__*/jsxRuntime.jsxs(Popover.Popover.Pane, {
199
+ fixed: true,
200
+ children: [/*#__PURE__*/jsxRuntime.jsx(Box.Box, {
201
+ padding: "300",
202
+ children: /*#__PURE__*/jsxRuntime.jsx(TextField.TextField, {
203
+ prefix: /*#__PURE__*/jsxRuntime.jsx(Icon.Icon, {
204
+ source: polarisIcons.SearchIcon
205
+ }),
206
+ label: "",
207
+ labelHidden: true,
208
+ autoComplete: "off",
209
+ placeholder: i18n.translate('Polaris.SearchableSelect.searchPlaceholder'),
210
+ clearButton: true,
211
+ ariaActiveDescendant: activeOptionId,
212
+ ariaControls: listboxId,
213
+ value: query,
214
+ onChange: value => {
215
+ onQueryChange(value);
216
+ setQuery(value);
217
+ },
218
+ onClearButtonClick: handleQueryClear
219
+ })
220
+ }), /*#__PURE__*/jsxRuntime.jsx(Scrollable.Scrollable, {
221
+ style: {
222
+ position: 'relative',
223
+ maxHeight: SCROLLABLE_HEIGHT,
224
+ padding: '0 0 var(--p-space-200)',
225
+ borderBottomLeftRadius: 'var(--p-border-radius-200)',
226
+ borderBottomRightRadius: 'var(--p-border-radius-200)'
227
+ },
228
+ horizontal: false,
229
+ onScrolledToBottom: onScrolledToBottom,
230
+ children: /*#__PURE__*/jsxRuntime.jsxs(Listbox.Listbox, {
231
+ enableKeyboardControl: true,
232
+ autoSelection: selected.length > 0 ? Listbox.AutoSelection.FirstSelected : Listbox.AutoSelection.None,
233
+ customListId: listboxId,
234
+ onSelect: updateSelection,
235
+ onActiveOptionChange: handleActiveOptionChange,
236
+ children: [optionsMarkup, loadingMarkup, loadingMoreMarkup, noResultsMarkup]
237
+ })
238
+ })]
239
+ })
240
+ });
241
+ }
242
+
243
+ exports.SearchableSelect = SearchableSelect;
@@ -105,6 +105,7 @@ var ResourceItem = require('./components/ResourceItem/ResourceItem.js');
105
105
  var ResourceList = require('./components/ResourceList/ResourceList.js');
106
106
  var Scrollable = require('./components/Scrollable/Scrollable.js');
107
107
  var ScrollLock = require('./components/ScrollLock/ScrollLock.js');
108
+ var SearchableSelect = require('./components/SearchableSelect/SearchableSelect.js');
108
109
  var Select = require('./components/Select/Select.js');
109
110
  var SelectAllActions = require('./components/SelectAllActions/SelectAllActions.js');
110
111
  var SettingToggle = require('./components/SettingToggle/SettingToggle.js');
@@ -261,6 +262,7 @@ exports.ResourceItem = ResourceItem.ResourceItem;
261
262
  exports.ResourceList = ResourceList.ResourceList;
262
263
  exports.Scrollable = Scrollable.Scrollable;
263
264
  exports.ScrollLock = ScrollLock.ScrollLock;
265
+ exports.SearchableSelect = SearchableSelect.SearchableSelect;
264
266
  exports.Select = Select.Select;
265
267
  exports.SelectAllActions = SelectAllActions.SelectAllActions;
266
268
  exports.SettingToggle = SettingToggle.SettingToggle;
@@ -50,7 +50,7 @@ function ActionList({
50
50
  actionRole: actionRole,
51
51
  onActionAnyItem: onActionAnyItem,
52
52
  isFirst: index === 0
53
- }, typeof section.title === 'string' ? section.title : index) : null;
53
+ }, typeof section.title === 'string' && section.title ? section.title : index) : null;
54
54
  });
55
55
  const handleFocusPreviousItem = evt => {
56
56
  evt.preventDefault();
@@ -73,11 +73,17 @@ function FiltersBar({
73
73
  togglePopoverActive();
74
74
  }, 0);
75
75
  };
76
- const filterToActionItem = filter => ({
77
- ...filter,
78
- content: filter.label,
79
- onAction: onFilterClick(filter)
80
- });
76
+ const filterToActionItem = filter => {
77
+ const {
78
+ key: _filterKey,
79
+ ...rest
80
+ } = filter;
81
+ return {
82
+ ...rest,
83
+ content: filter.label,
84
+ onAction: onFilterClick(filter)
85
+ };
86
+ };
81
87
  const unpinnedFilters = filters.filter(filter => !pinnedFilters.some(({
82
88
  key
83
89
  }) => key === filter.key));
@@ -1,4 +1,4 @@
1
- import { ChevronLeftIcon, ChevronRightIcon } from '@shopify/polaris-icons';
1
+ import { ChevronUpIcon, ChevronLeftIcon, ChevronDownIcon, ChevronRightIcon } from '@shopify/polaris-icons';
2
2
  import { createRef } from 'react';
3
3
  import { classNames } from '../../utilities/css.js';
4
4
  import { isInputFocused } from '../../utilities/is-input-focused.js';
@@ -27,15 +27,18 @@ function Pagination({
27
27
  accessibilityLabel,
28
28
  accessibilityLabels,
29
29
  label,
30
- type = 'page'
30
+ type = 'page',
31
+ direction = 'horizontal'
31
32
  }) {
32
33
  const i18n = useI18n();
33
34
  const node = /*#__PURE__*/createRef();
34
35
  const navLabel = accessibilityLabel || i18n.translate('Polaris.Pagination.pagination');
35
36
  const previousLabel = accessibilityLabels?.previous || i18n.translate('Polaris.Pagination.previous');
36
37
  const nextLabel = accessibilityLabels?.next || i18n.translate('Polaris.Pagination.next');
38
+ const previousIcon = direction === 'vertical' ? ChevronUpIcon : ChevronLeftIcon;
39
+ const nextIcon = direction === 'vertical' ? ChevronDownIcon : ChevronRightIcon;
37
40
  const prev = /*#__PURE__*/jsx(Button, {
38
- icon: ChevronLeftIcon,
41
+ icon: previousIcon,
39
42
  accessibilityLabel: previousLabel,
40
43
  url: previousURL,
41
44
  onClick: onPrevious,
@@ -49,7 +52,7 @@ function Pagination({
49
52
  children: prev
50
53
  }) : prev;
51
54
  const next = /*#__PURE__*/jsx(Button, {
52
- icon: ChevronRightIcon,
55
+ icon: nextIcon,
53
56
  accessibilityLabel: nextLabel,
54
57
  url: nextURL,
55
58
  onClick: onNext,
@@ -0,0 +1,5 @@
1
+ var styles = {
2
+ "Activator": "Polaris-SearchableSelect__Activator"
3
+ };
4
+
5
+ export { styles as default };
@@ -0,0 +1,241 @@
1
+ import { CaretDownIcon, SearchIcon } from '@shopify/polaris-icons';
2
+ import { useState, useRef, useId, useCallback, useEffect, useMemo } from 'react';
3
+ import styles from './SearchableSelect.css.js';
4
+ import { jsx, jsxs } from 'react/jsx-runtime';
5
+ import { useI18n } from '../../utilities/i18n/hooks.js';
6
+ import { TextField } from '../TextField/TextField.js';
7
+ import { Icon } from '../Icon/Icon.js';
8
+ import { Listbox, AutoSelection } from '../Listbox/Listbox.js';
9
+ import { EmptySearchResult } from '../EmptySearchResult/EmptySearchResult.js';
10
+ import { Popover } from '../Popover/Popover.js';
11
+ import { Box } from '../Box/Box.js';
12
+ import { Scrollable } from '../Scrollable/Scrollable.js';
13
+
14
+ function getValueByPath(obj, path) {
15
+ return path.split('.').reduce((acc, part) => acc?.[part], obj);
16
+ }
17
+ const SCROLLABLE_HEIGHT = 292;
18
+ function SearchableSelect({
19
+ loading,
20
+ loadingMore,
21
+ options,
22
+ selected,
23
+ onSelect,
24
+ emptyStateTitle = '',
25
+ emptyStateDescription,
26
+ onQueryChange,
27
+ selectedLabelKey,
28
+ label,
29
+ placeholder,
30
+ disabled,
31
+ requiredIndicator,
32
+ helpText,
33
+ error,
34
+ pinSelected = true,
35
+ allowClear,
36
+ onScrolledToBottom
37
+ }) {
38
+ const i18n = useI18n();
39
+ const [query, setQuery] = useState('');
40
+ const [popoverActive, setPopoverActive] = useState(false);
41
+ const [activeOptionId, setActiveOptionId] = useState();
42
+ // pagination/search can drop the selected option from `options`; the cache keeps its label visible
43
+ const [cachedSelection, setCachedSelection] = useState(null);
44
+ // clicking the clear button bubbles to TextField's container, which focuses the input
45
+ const skipNextFocusOpen = useRef(false);
46
+ const listboxId = useId();
47
+ const togglePopoverActive = useCallback(() => {
48
+ setPopoverActive(active => !active);
49
+ setQuery('');
50
+ onQueryChange('');
51
+ }, [onQueryChange]);
52
+ useEffect(() => {
53
+ function handleEsc(event) {
54
+ if (event.key === 'Escape') setPopoverActive(false);
55
+ }
56
+ document.addEventListener('keydown', handleEsc);
57
+ return () => document.removeEventListener('keydown', handleEsc);
58
+ }, []);
59
+
60
+ // keep the cache in sync while the selected option is loaded, so its label survives (and stays
61
+ // fresh) if pagination later drops the option from `options`
62
+ useEffect(() => {
63
+ if (selected.length < 1) return;
64
+ const loadedOption = options.find(option => option.value === selected[0]);
65
+ const label = getValueByPath(loadedOption?.object, selectedLabelKey);
66
+ if (typeof label === 'string' && (cachedSelection?.value !== selected[0] || cachedSelection.label !== label)) {
67
+ setCachedSelection({
68
+ value: selected[0],
69
+ label
70
+ });
71
+ }
72
+ }, [selected, options, selectedLabelKey, cachedSelection]);
73
+ const textSelectedItem = useMemo(() => {
74
+ if (selected.length < 1) return '';
75
+ const selectedItem = options.find(option => option.value === selected[0]);
76
+ const value = getValueByPath(selectedItem?.object, selectedLabelKey);
77
+ if (typeof value === 'string') return value;
78
+ return cachedSelection?.value === selected[0] ? cachedSelection.label : '';
79
+ }, [selected, options, selectedLabelKey, cachedSelection]);
80
+ const handleSelectionClear = useCallback(() => {
81
+ setCachedSelection(null);
82
+ onSelect([]);
83
+ if (requiredIndicator) {
84
+ setPopoverActive(true);
85
+ } else {
86
+ skipNextFocusOpen.current = true;
87
+ }
88
+ }, [onSelect, requiredIndicator]);
89
+ const handleActivatorFocus = useCallback(() => {
90
+ if (disabled) return;
91
+ if (skipNextFocusOpen.current) {
92
+ skipNextFocusOpen.current = false;
93
+ return;
94
+ }
95
+ setPopoverActive(true);
96
+ }, [disabled]);
97
+ const activator = /*#__PURE__*/jsx("div", {
98
+ className: styles.Activator,
99
+ children: /*#__PURE__*/jsx(TextField, {
100
+ label: label,
101
+ placeholder: placeholder,
102
+ autoComplete: "off",
103
+ value: textSelectedItem,
104
+ disabled: disabled,
105
+ requiredIndicator: requiredIndicator,
106
+ helpText: helpText,
107
+ error: error,
108
+ clearButton: allowClear && textSelectedItem !== '',
109
+ onClearButtonClick: handleSelectionClear,
110
+ onFocus: handleActivatorFocus,
111
+ suffix: /*#__PURE__*/jsx(Icon, {
112
+ source: CaretDownIcon
113
+ })
114
+ })
115
+ });
116
+ const pinnedSelectedOption = useMemo(() => {
117
+ if (query || selected.length < 1) return null;
118
+ const loadedOption = options.find(option => option.value === selected[0]);
119
+ if (loadedOption) {
120
+ return pinSelected ? {
121
+ value: loadedOption.value,
122
+ label: loadedOption.label
123
+ } : null;
124
+ }
125
+ return cachedSelection?.value === selected[0] ? cachedSelection : null;
126
+ }, [query, selected, options, cachedSelection, pinSelected]);
127
+ const optionsMarkup = useMemo(() => {
128
+ if (loading) return null;
129
+ const listOptions = pinnedSelectedOption ? options.filter(option => option.value !== pinnedSelectedOption.value) : options;
130
+ if (listOptions.length === 0 && !pinnedSelectedOption) return null;
131
+ const pinnedMarkup = pinnedSelectedOption ? /*#__PURE__*/jsx(Listbox.Option, {
132
+ value: pinnedSelectedOption.value,
133
+ selected: true,
134
+ children: /*#__PURE__*/jsx(Listbox.TextOption, {
135
+ selected: true,
136
+ children: pinnedSelectedOption.label
137
+ })
138
+ }, pinnedSelectedOption.value) : null;
139
+ return [pinnedMarkup, ...listOptions.map(option => {
140
+ const isSelected = selected.includes(option.value);
141
+ return /*#__PURE__*/jsx(Listbox.Option, {
142
+ value: option.value,
143
+ selected: isSelected,
144
+ children: /*#__PURE__*/jsx(Listbox.TextOption, {
145
+ selected: isSelected,
146
+ children: option.label
147
+ })
148
+ }, option.value);
149
+ })];
150
+ }, [loading, options, selected, pinnedSelectedOption]);
151
+ const loadingMarkup = loading ? /*#__PURE__*/jsx(Listbox.Loading, {
152
+ accessibilityLabel: i18n.translate('Polaris.SearchableSelect.loadingAccessibilityLabel')
153
+ }) : null;
154
+ const loadingMoreMarkup = loadingMore ? /*#__PURE__*/jsx(Listbox.Loading, {
155
+ accessibilityLabel: i18n.translate('Polaris.SearchableSelect.loadingMoreAccessibilityLabel')
156
+ }) : null;
157
+ const noResultsMarkup = !loading && options.length === 0 && !pinnedSelectedOption ? /*#__PURE__*/jsx(EmptySearchResult, {
158
+ title: emptyStateTitle,
159
+ description: emptyStateDescription ?? (query ? i18n.translate('Polaris.SearchableSelect.noResultsFoundFor', {
160
+ query
161
+ }) : i18n.translate('Polaris.SearchableSelect.noResultsFound'))
162
+ }) : null;
163
+ const updateSelection = useCallback(newSelection => {
164
+ const option = options.find(({
165
+ value
166
+ }) => value === newSelection);
167
+ const label = getValueByPath(option?.object, selectedLabelKey);
168
+ if (typeof label === 'string') {
169
+ setCachedSelection({
170
+ value: newSelection,
171
+ label
172
+ });
173
+ } else if (cachedSelection?.value !== newSelection) {
174
+ // re-selecting the pinned option (absent from `options`) must keep its cached label
175
+ setCachedSelection(null);
176
+ }
177
+ onSelect([newSelection]);
178
+ setQuery('');
179
+ onQueryChange('');
180
+ setPopoverActive(false);
181
+ }, [options, selectedLabelKey, cachedSelection, onSelect, onQueryChange]);
182
+ const handleActiveOptionChange = useCallback((_value, domId) => {
183
+ setActiveOptionId(domId);
184
+ }, []);
185
+ const handleQueryClear = useCallback(() => {
186
+ setQuery('');
187
+ onQueryChange('');
188
+ }, [onQueryChange]);
189
+ return /*#__PURE__*/jsx(Popover, {
190
+ activator: activator,
191
+ active: popoverActive,
192
+ onClose: togglePopoverActive,
193
+ fullWidth: true,
194
+ autofocusTarget: "first-node",
195
+ preferInputActivator: false,
196
+ children: /*#__PURE__*/jsxs(Popover.Pane, {
197
+ fixed: true,
198
+ children: [/*#__PURE__*/jsx(Box, {
199
+ padding: "300",
200
+ children: /*#__PURE__*/jsx(TextField, {
201
+ prefix: /*#__PURE__*/jsx(Icon, {
202
+ source: SearchIcon
203
+ }),
204
+ label: "",
205
+ labelHidden: true,
206
+ autoComplete: "off",
207
+ placeholder: i18n.translate('Polaris.SearchableSelect.searchPlaceholder'),
208
+ clearButton: true,
209
+ ariaActiveDescendant: activeOptionId,
210
+ ariaControls: listboxId,
211
+ value: query,
212
+ onChange: value => {
213
+ onQueryChange(value);
214
+ setQuery(value);
215
+ },
216
+ onClearButtonClick: handleQueryClear
217
+ })
218
+ }), /*#__PURE__*/jsx(Scrollable, {
219
+ style: {
220
+ position: 'relative',
221
+ maxHeight: SCROLLABLE_HEIGHT,
222
+ padding: '0 0 var(--p-space-200)',
223
+ borderBottomLeftRadius: 'var(--p-border-radius-200)',
224
+ borderBottomRightRadius: 'var(--p-border-radius-200)'
225
+ },
226
+ horizontal: false,
227
+ onScrolledToBottom: onScrolledToBottom,
228
+ children: /*#__PURE__*/jsxs(Listbox, {
229
+ enableKeyboardControl: true,
230
+ autoSelection: selected.length > 0 ? AutoSelection.FirstSelected : AutoSelection.None,
231
+ customListId: listboxId,
232
+ onSelect: updateSelection,
233
+ onActiveOptionChange: handleActiveOptionChange,
234
+ children: [optionsMarkup, loadingMarkup, loadingMoreMarkup, noResultsMarkup]
235
+ })
236
+ })]
237
+ })
238
+ });
239
+ }
240
+
241
+ export { SearchableSelect };
@@ -103,6 +103,7 @@ export { ResourceItem } from './components/ResourceItem/ResourceItem.js';
103
103
  export { ResourceList } from './components/ResourceList/ResourceList.js';
104
104
  export { Scrollable } from './components/Scrollable/Scrollable.js';
105
105
  export { ScrollLock } from './components/ScrollLock/ScrollLock.js';
106
+ export { SearchableSelect } from './components/SearchableSelect/SearchableSelect.js';
106
107
  export { Select } from './components/Select/Select.js';
107
108
  export { SelectAllActions } from './components/SelectAllActions/SelectAllActions.js';
108
109
  export { SettingToggle } from './components/SettingToggle/SettingToggle.js';