@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.
- package/build/cjs/components/ActionList/ActionList.js +1 -1
- package/build/cjs/components/Filters/components/FiltersBar/FiltersBar.js +11 -5
- package/build/cjs/components/Pagination/Pagination.js +6 -3
- package/build/cjs/components/SearchableSelect/SearchableSelect.css.js +9 -0
- package/build/cjs/components/SearchableSelect/SearchableSelect.js +243 -0
- package/build/cjs/index.js +2 -0
- package/build/esm/components/ActionList/ActionList.js +1 -1
- package/build/esm/components/Filters/components/FiltersBar/FiltersBar.js +11 -5
- package/build/esm/components/Pagination/Pagination.js +7 -4
- package/build/esm/components/SearchableSelect/SearchableSelect.css.js +5 -0
- package/build/esm/components/SearchableSelect/SearchableSelect.js +241 -0
- package/build/esm/index.js +1 -0
- package/build/esm/styles.css +14 -1
- package/build/esnext/components/ActionList/ActionList.esnext +1 -1
- package/build/esnext/components/AppProvider/global.out.css +1 -1
- package/build/esnext/components/Filters/components/FiltersBar/FiltersBar.esnext +11 -5
- package/build/esnext/components/Pagination/Pagination.esnext +7 -4
- package/build/esnext/components/SearchableSelect/SearchableSelect.css.esnext +7 -0
- package/build/esnext/components/SearchableSelect/SearchableSelect.esnext +241 -0
- package/build/esnext/components/SearchableSelect/SearchableSelect.out.css +11 -0
- package/build/esnext/index.esnext +1 -0
- package/build/ts/.storybook/main.d.ts.map +1 -1
- package/build/ts/src/components/Filters/components/FiltersBar/FiltersBar.d.ts.map +1 -1
- package/build/ts/src/components/Pagination/Pagination.d.ts +3 -1
- package/build/ts/src/components/Pagination/Pagination.d.ts.map +1 -1
- package/build/ts/src/components/SearchableSelect/SearchableSelect.d.ts +60 -0
- package/build/ts/src/components/SearchableSelect/SearchableSelect.d.ts.map +1 -0
- package/build/ts/src/components/SearchableSelect/index.d.ts +2 -0
- package/build/ts/src/components/SearchableSelect/index.d.ts.map +1 -0
- package/build/ts/src/index.d.ts +2 -0
- package/build/ts/src/index.d.ts.map +1 -1
- package/locales/en.json +7 -0
- package/locales/pt-BR.json +7 -0
- 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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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:
|
|
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:
|
|
57
|
+
icon: nextIcon,
|
|
55
58
|
accessibilityLabel: nextLabel,
|
|
56
59
|
url: nextURL,
|
|
57
60
|
onClick: onNext,
|
|
@@ -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;
|
package/build/cjs/index.js
CHANGED
|
@@ -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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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:
|
|
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:
|
|
55
|
+
icon: nextIcon,
|
|
53
56
|
accessibilityLabel: nextLabel,
|
|
54
57
|
url: nextURL,
|
|
55
58
|
onClick: onNext,
|
|
@@ -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 };
|
package/build/esm/index.js
CHANGED
|
@@ -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';
|