@rescui/select 0.1.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.
Files changed (41) hide show
  1. package/LICENSE.txt +202 -0
  2. package/README.md +3 -0
  3. package/lib/common-prop-types.d.ts +25 -0
  4. package/lib/common-prop-types.js +18 -0
  5. package/lib/index.css +742 -0
  6. package/lib/index.d.ts +3 -0
  7. package/lib/index.js +4 -0
  8. package/lib/key-codes.d.ts +7 -0
  9. package/lib/key-codes.js +8 -0
  10. package/lib/parts/common-select.module.pcss.js +4 -0
  11. package/lib/parts/foldable.d.ts +34 -0
  12. package/lib/parts/foldable.js +297 -0
  13. package/lib/parts/form-trigger-wrapper.d.ts +13 -0
  14. package/lib/parts/form-trigger-wrapper.js +87 -0
  15. package/lib/parts/form-trigger.module.pcss.js +9 -0
  16. package/lib/parts/multiselect.d.ts +80 -0
  17. package/lib/parts/multiselect.js +399 -0
  18. package/lib/parts/native-select.d.ts +29 -0
  19. package/lib/parts/native-select.js +282 -0
  20. package/lib/parts/native-select.module.pcss.js +9 -0
  21. package/lib/parts/trigger.d.ts +37 -0
  22. package/lib/parts/trigger.js +307 -0
  23. package/lib/parts/trigger.module.pcss.js +30 -0
  24. package/lib/select.d.ts +86 -0
  25. package/lib/select.js +391 -0
  26. package/lib/state/multiselect/actions.d.ts +36 -0
  27. package/lib/state/multiselect/actions.js +45 -0
  28. package/lib/state/multiselect/reducer.d.ts +21 -0
  29. package/lib/state/multiselect/reducer.js +203 -0
  30. package/lib/state/multiselect/selectors.d.ts +6 -0
  31. package/lib/state/multiselect/selectors.js +39 -0
  32. package/lib/state/select/actions.d.ts +81 -0
  33. package/lib/state/select/actions.js +87 -0
  34. package/lib/state/select/reducer.d.ts +22 -0
  35. package/lib/state/select/reducer.js +199 -0
  36. package/lib/state/select/selectors.d.ts +20 -0
  37. package/lib/state/select/selectors.js +114 -0
  38. package/lib/types.d.ts +51 -0
  39. package/lib/utils.d.ts +8 -0
  40. package/lib/utils.js +102 -0
  41. package/package.json +46 -0
@@ -0,0 +1,399 @@
1
+ import _slicedToArray from "@babel/runtime-corejs3/helpers/esm/slicedToArray";
2
+ import _objectWithoutProperties from "@babel/runtime-corejs3/helpers/esm/objectWithoutProperties";
3
+ var _excluded = ["options", "size", "mode", "theme", "className", "defaultValue", "value", "onChange", "placeholder", "isSingleLine", "isClearable", "isLoading", "isDisabled", "noOptionsMessage", "placement"];
4
+ import _concatInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/concat";
5
+ import _mapInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/map";
6
+ import React, { useRef, useState, useReducer, useEffect, useCallback } from 'react';
7
+ import PropTypes from 'prop-types';
8
+ import scrollIntoView from 'scroll-into-view-if-needed';
9
+ import { CheckIcon } from '@rescui/icons';
10
+ import Dropdown from '@rescui/dropdown';
11
+ import { Menu, MenuItem, Divider, GroupHeader } from '@rescui/menu';
12
+ import { useTheme } from '@rescui/ui-contexts';
13
+ import { convertToOptionGroupsIfNeeded, reverseTheme, getEmptyMessage, shouldOpenSelect, calculateMaxHeight } from '../utils.js';
14
+ import { optionType, optionsGroupType, noOptionsMessageType } from '../common-prop-types.js';
15
+ import { multiselectReducer, getInitialState } from '../state/multiselect/reducer.js';
16
+ import { setGroups, closeMenu, openMenu, setCursorIndex, cursorMovedDown, cursorMovedUp, clearSelectedAndCloseMenu } from '../state/select/actions.js';
17
+ import { setSelectedValue, setHideSelected, addSelectedIndex, removeSelectedIndex, removeSelectedOption } from '../state/multiselect/actions.js';
18
+ import { withoutGroupsLabel, OptionGroupTypes, getOptionValue, isIndexMatchCursor, getCursorIndex } from '../state/select/selectors.js';
19
+ import { isEmpty, getSelectedArray, getPlainOptions, isSelected, getGroups } from '../state/multiselect/selectors.js';
20
+ import { ESCAPE, BACKSPACE, ARROW_DOWN, ARROW_UP, ENTER, SPACE, TAB } from '../key-codes.js';
21
+ import SelectTrigger from './trigger.js';
22
+ import FormTriggerWrapper from './form-trigger-wrapper.js';
23
+ import styles from './common-select.module.pcss.js';
24
+ /* eslint-disable no-nested-ternary */
25
+ // eslint-disable-next-line complexity
26
+
27
+ var Option = function Option(_ref) {
28
+ var state = _ref.state,
29
+ size = _ref.size,
30
+ item = _ref.item,
31
+ isSingleLine = _ref.isSingleLine,
32
+ isLastItem = _ref.isLastItem,
33
+ highlightedItemRef = _ref.highlightedItemRef,
34
+ handleHighlightIndex = _ref.handleHighlightIndex,
35
+ toggleSelectIndex = _ref.toggleSelectIndex;
36
+ var marginInPx = withoutGroupsLabel(state) ? 0 : {
37
+ l: 12,
38
+ m: 6,
39
+ s: 0
40
+ }[size];
41
+
42
+ if (item.type === OptionGroupTypes.OPTION) {
43
+ var index = item.index,
44
+ label = item.label;
45
+ var value = getOptionValue(item);
46
+ var isItemSelected = isSelected(state, value);
47
+ var isHighlighted = isIndexMatchCursor(state, index);
48
+ var icon = isSingleLine && isItemSelected ? /*#__PURE__*/React.createElement(CheckIcon, {
49
+ size: size
50
+ }) : null;
51
+ return /*#__PURE__*/React.createElement(MenuItem, {
52
+ selected: isItemSelected,
53
+ highlighted: isHighlighted,
54
+ ref: isHighlighted ? highlightedItemRef : null,
55
+ onMouseMove: function onMouseMove() {
56
+ return handleHighlightIndex(index);
57
+ },
58
+ onClick: function onClick() {
59
+ return toggleSelectIndex(index);
60
+ },
61
+ style: {
62
+ marginBottom: isLastItem ? "".concat(marginInPx, "px") : ''
63
+ },
64
+ icon: icon
65
+ }, label);
66
+ }
67
+
68
+ if (item.type === OptionGroupTypes.DIVIDER) {
69
+ return /*#__PURE__*/React.createElement(Divider, {
70
+ style: {
71
+ marginTop: "".concat(marginInPx, "px")
72
+ }
73
+ });
74
+ }
75
+
76
+ if (item.type === OptionGroupTypes.HEADER) {
77
+ var _label = item.label;
78
+ return /*#__PURE__*/React.createElement(GroupHeader, {
79
+ text: _label
80
+ });
81
+ }
82
+
83
+ return null;
84
+ };
85
+
86
+ var getOptionKey = function getOptionKey(item, i) {
87
+ if (item.type === OptionGroupTypes.OPTION) {
88
+ var _context;
89
+
90
+ var value = getOptionValue(item);
91
+ return _concatInstanceProperty(_context = "".concat(value, "_")).call(_context, item.index[0]);
92
+ } else if (item.type === OptionGroupTypes.DIVIDER) {
93
+ return "divider".concat(i);
94
+ } else if (item.type === OptionGroupTypes.HEADER) {
95
+ return "header".concat(i);
96
+ }
97
+
98
+ return null;
99
+ };
100
+
101
+ function callOnChangeIfNeeded(onChange, state, action) {
102
+ if (onChange) {
103
+ onChange(getSelectedArray(multiselectReducer(state, action)));
104
+ }
105
+ }
106
+
107
+ var Multiselect = function Multiselect(_ref2) {
108
+ var _context2;
109
+
110
+ var options = _ref2.options,
111
+ _ref2$size = _ref2.size,
112
+ size = _ref2$size === void 0 ? 'm' : _ref2$size,
113
+ _ref2$mode = _ref2.mode,
114
+ mode = _ref2$mode === void 0 ? 'classic' : _ref2$mode,
115
+ themeFromProps = _ref2.theme,
116
+ _ref2$className = _ref2.className,
117
+ className = _ref2$className === void 0 ? null : _ref2$className,
118
+ _ref2$defaultValue = _ref2.defaultValue,
119
+ defaultValue = _ref2$defaultValue === void 0 ? [] : _ref2$defaultValue,
120
+ _ref2$value = _ref2.value,
121
+ value = _ref2$value === void 0 ? undefined : _ref2$value,
122
+ onChange = _ref2.onChange,
123
+ _ref2$placeholder = _ref2.placeholder,
124
+ placeholder = _ref2$placeholder === void 0 ? 'Placeholder' : _ref2$placeholder,
125
+ _ref2$isSingleLine = _ref2.isSingleLine,
126
+ isSingleLine = _ref2$isSingleLine === void 0 ? false : _ref2$isSingleLine,
127
+ _ref2$isClearable = _ref2.isClearable,
128
+ isClearable = _ref2$isClearable === void 0 ? false : _ref2$isClearable,
129
+ _ref2$isLoading = _ref2.isLoading,
130
+ isLoading = _ref2$isLoading === void 0 ? false : _ref2$isLoading,
131
+ _ref2$isDisabled = _ref2.isDisabled,
132
+ isDisabled = _ref2$isDisabled === void 0 ? false : _ref2$isDisabled,
133
+ noOptionsMessage = _ref2.noOptionsMessage,
134
+ _ref2$placement = _ref2.placement,
135
+ placement = _ref2$placement === void 0 ? 'bottom' : _ref2$placement,
136
+ restProps = _objectWithoutProperties(_ref2, _excluded);
137
+
138
+ var dropdownRef = useRef(null);
139
+ var highlightedItemRef = useRef();
140
+ var theme = useTheme(themeFromProps);
141
+
142
+ var _useState = useState(false),
143
+ _useState2 = _slicedToArray(_useState, 2),
144
+ isKeyboardNavigating = _useState2[0],
145
+ setIsKeyboardNavigating = _useState2[1];
146
+
147
+ var skipScrollingRef = useRef(false);
148
+
149
+ var _useReducer = useReducer(multiselectReducer, getInitialState(convertToOptionGroupsIfNeeded(options), defaultValue, !isSingleLine)),
150
+ _useReducer2 = _slicedToArray(_useReducer, 2),
151
+ state = _useReducer2[0],
152
+ dispatch = _useReducer2[1];
153
+
154
+ useEffect(function () {
155
+ dispatch(setGroups(convertToOptionGroupsIfNeeded(options)));
156
+ }, [options]);
157
+ useEffect(function () {
158
+ if (value !== undefined) {
159
+ dispatch(setSelectedValue(value));
160
+ }
161
+ }, [value]);
162
+ useEffect(function () {
163
+ dispatch(setHideSelected(!isSingleLine));
164
+ }, [isSingleLine]);
165
+ var isEmptyList = isEmpty(state);
166
+ var menuIsOpen = state.menuIsOpen;
167
+
168
+ var toggleSelectIndex = function toggleSelectIndex(index) {
169
+ var selectedOption = getGroups(state)[index[0]].options[index[1]];
170
+ var optionAdded = !isSelected(state, getOptionValue(selectedOption));
171
+ var toggleSelectAction = optionAdded ? addSelectedIndex(index) : removeSelectedIndex(index);
172
+ dispatch(toggleSelectAction);
173
+ setIsKeyboardNavigating(false);
174
+ callOnChangeIfNeeded(onChange, state, toggleSelectAction);
175
+ };
176
+
177
+ useEffect(function () {
178
+ if (dropdownRef.current) {
179
+ dropdownRef.current.scheduleUpdate();
180
+ }
181
+ }, [state.searchString, state.selected]);
182
+ var handleMouseMove = useCallback(function () {
183
+ return setIsKeyboardNavigating(false);
184
+ }, []);
185
+ useEffect(function () {
186
+ if (isKeyboardNavigating) {
187
+ document.addEventListener('mousemove', handleMouseMove);
188
+ }
189
+
190
+ return function () {
191
+ return document.removeEventListener('mousemove', handleMouseMove);
192
+ };
193
+ }, [handleMouseMove, isKeyboardNavigating]);
194
+ useEffect(function () {
195
+ if (isKeyboardNavigating && highlightedItemRef.current) {
196
+ if (skipScrollingRef.current) {
197
+ skipScrollingRef.current = false;
198
+ } else {
199
+ var elementToScrollTo = highlightedItemRef.current;
200
+
201
+ if (state.cursorIndex[0] === 0 && state.cursorIndex[1] === 0) {
202
+ var groupHeader = highlightedItemRef.current.previousSibling;
203
+
204
+ if (groupHeader) {
205
+ elementToScrollTo = groupHeader;
206
+ }
207
+ }
208
+
209
+ scrollIntoView(elementToScrollTo, {
210
+ behavior: 'instant',
211
+ scrollMode: 'if-needed',
212
+ block: 'nearest',
213
+ inline: 'nearest'
214
+ });
215
+ }
216
+ }
217
+ }, [highlightedItemRef, state.cursorIndex, isKeyboardNavigating]);
218
+
219
+ var handleClose = function handleClose() {
220
+ dispatch(closeMenu());
221
+ setIsKeyboardNavigating(false);
222
+ }; // eslint-disable-next-line complexity
223
+
224
+
225
+ var handleKeyNavigation = function handleKeyNavigation(e) {
226
+ if (e.key === ARROW_DOWN || e.key === ARROW_UP) {
227
+ var isDownDirection = e.key === ARROW_DOWN;
228
+ dispatch(isDownDirection ? cursorMovedDown() : cursorMovedUp());
229
+ setIsKeyboardNavigating(true);
230
+ e.preventDefault();
231
+ }
232
+
233
+ if (e.key === ENTER || e.key === SPACE) {
234
+ var cursorIndex = getCursorIndex(state);
235
+
236
+ if (cursorIndex !== null) {
237
+ toggleSelectIndex(cursorIndex);
238
+ } else {
239
+ handleClose();
240
+ }
241
+
242
+ e.preventDefault();
243
+ }
244
+
245
+ if (e.key === TAB) {
246
+ handleClose();
247
+ }
248
+ };
249
+
250
+ var handleClick = function handleClick() {
251
+ if (isDisabled) {
252
+ return;
253
+ }
254
+
255
+ var action = menuIsOpen ? closeMenu() : openMenu();
256
+ dispatch(action);
257
+ };
258
+
259
+ var handleClearClick = function handleClearClick() {
260
+ var action = clearSelectedAndCloseMenu();
261
+ dispatch(action);
262
+ callOnChangeIfNeeded(onChange, state, action);
263
+ }; // eslint-disable-next-line complexity
264
+
265
+
266
+ var handleTriggerKeyDown = function handleTriggerKeyDown(e) {
267
+ if (e.key === ESCAPE) {
268
+ handleClose();
269
+ e.preventDefault();
270
+ }
271
+
272
+ if (isDisabled) {
273
+ return;
274
+ }
275
+
276
+ if (menuIsOpen) {
277
+ handleKeyNavigation(e);
278
+ } else {
279
+ var target = e.target;
280
+ var trigger = target.closest('[data-target="trigger"]');
281
+ var isTriggerTarget = trigger === target;
282
+
283
+ if (shouldOpenSelect(e.key, isTriggerTarget)) {
284
+ dispatch(openMenu());
285
+ setIsKeyboardNavigating(true);
286
+ skipScrollingRef.current = true;
287
+ trigger.focus();
288
+ e.preventDefault();
289
+ }
290
+
291
+ if (e.key === BACKSPACE && isClearable) {
292
+ handleClearClick();
293
+ trigger.focus();
294
+ e.preventDefault();
295
+ }
296
+ }
297
+ };
298
+
299
+ var handleUnselectClick = function handleUnselectClick(option) {
300
+ var action = removeSelectedOption(option);
301
+ dispatch(action);
302
+ callOnChangeIfNeeded(onChange, state, action);
303
+ };
304
+
305
+ var handleHighlightIndex = function handleHighlightIndex(index) {
306
+ if (!isIndexMatchCursor(state, index)) {
307
+ dispatch(setCursorIndex(index));
308
+ }
309
+ };
310
+
311
+ var maxHeight = calculateMaxHeight(size);
312
+ return /*#__PURE__*/React.createElement(Dropdown, {
313
+ theme: theme,
314
+ onRequestClose: handleClose,
315
+ placement: placement,
316
+ mode: mode,
317
+ ref: dropdownRef,
318
+ cancelCloseAnimation: true,
319
+ minWidth: "trigger",
320
+ maxWidth: "trigger",
321
+ maxHeight: maxHeight,
322
+ leastMaxHeight: maxHeight,
323
+ trigger: function trigger(_ref3) {
324
+ var ref = _ref3.ref;
325
+ return /*#__PURE__*/React.createElement(FormTriggerWrapper, {
326
+ className: className,
327
+ theme: theme,
328
+ size: size,
329
+ error: restProps.error,
330
+ label: restProps.label,
331
+ note: restProps.note
332
+ }, /*#__PURE__*/React.createElement(SelectTrigger, {
333
+ theme: theme,
334
+ mode: mode,
335
+ size: size,
336
+ isOpen: menuIsOpen,
337
+ triggerRef: ref,
338
+ onKeyDown: handleTriggerKeyDown,
339
+ selected: value ? value : getSelectedArray(state),
340
+ placeholder: placeholder,
341
+ isClearable: isClearable,
342
+ isLoading: isLoading,
343
+ isDisabled: isDisabled,
344
+ isSingleLine: isSingleLine,
345
+ onClick: handleClick,
346
+ onClearClick: handleClearClick,
347
+ onItemClearClick: handleUnselectClick,
348
+ error: Boolean(restProps.error),
349
+ id: restProps.id,
350
+ "data-target": "trigger"
351
+ }));
352
+ },
353
+ isOpen: menuIsOpen
354
+ }, /*#__PURE__*/React.createElement(Menu, {
355
+ "data-test": "option-list",
356
+ size: size,
357
+ theme: mode === 'rock' ? reverseTheme(theme) : theme,
358
+ disableCursor: isKeyboardNavigating,
359
+ iconPlacement: 'right',
360
+ className: styles.menu
361
+ }, !isEmptyList ? _mapInstanceProperty(_context2 = getPlainOptions(state)).call(_context2, function (item, i, collection) {
362
+ return /*#__PURE__*/React.createElement(Option, {
363
+ key: getOptionKey(item, i),
364
+ state: state,
365
+ size: size,
366
+ item: item,
367
+ isSingleLine: isSingleLine,
368
+ isLastItem: i === collection.length - 1,
369
+ highlightedItemRef: highlightedItemRef,
370
+ toggleSelectIndex: toggleSelectIndex,
371
+ handleHighlightIndex: handleHighlightIndex
372
+ });
373
+ }) : /*#__PURE__*/React.createElement(MenuItem, {
374
+ disabled: true
375
+ }, getEmptyMessage(isLoading, false, noOptionsMessage))));
376
+ };
377
+
378
+ Multiselect.propTypes = {
379
+ className: PropTypes.string,
380
+ options: PropTypes.arrayOf(PropTypes.oneOfType([optionType, optionsGroupType])),
381
+ defaultValue: PropTypes.arrayOf(optionType),
382
+ value: PropTypes.arrayOf(optionType),
383
+ onChange: PropTypes.func,
384
+ size: PropTypes.oneOf(['m', 's', 'l']),
385
+ mode: PropTypes.oneOf(['classic', 'rock']),
386
+ theme: PropTypes.oneOf(['light', 'dark']),
387
+ placeholder: PropTypes.string,
388
+ isSingleLine: PropTypes.bool,
389
+ isClearable: PropTypes.bool,
390
+ isDisabled: PropTypes.bool,
391
+ isLoading: PropTypes.bool,
392
+ error: PropTypes.oneOfType([PropTypes.bool, PropTypes.node]),
393
+ label: PropTypes.node,
394
+ note: PropTypes.node,
395
+ id: PropTypes.string,
396
+ noOptionsMessage: noOptionsMessageType,
397
+ placement: PropTypes.oneOf(['auto-start', 'auto', 'auto-end', 'top-start', 'top', 'top-end', 'right-start', 'right', 'right-end', 'bottom-end', 'bottom', 'bottom-start', 'left-end', 'left', 'left-start'])
398
+ };
399
+ export default Multiselect;
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import { Theme } from '@rescui/ui-contexts';
3
+ import type { SelectOption, SelectSize, SelectOptionOrGroup, NoOptionsMessage } from '../types';
4
+ export declare type TriggerFunc = (props: {
5
+ selectedLabel: string;
6
+ selectedValue: string;
7
+ placeholder: string;
8
+ hovered: boolean;
9
+ }) => React.ReactNode;
10
+ export declare type NativeSelectProps = {
11
+ options?: SelectOptionOrGroup[];
12
+ size?: SelectSize;
13
+ theme?: Theme;
14
+ className?: string;
15
+ style?: object;
16
+ defaultValue?: SelectOption;
17
+ value?: SelectOption;
18
+ onChange?: (newValue: SelectOption) => void;
19
+ placeholder?: string;
20
+ isLoading?: boolean;
21
+ isDisabled?: boolean;
22
+ trigger?: TriggerFunc;
23
+ label?: React.ReactNode;
24
+ note?: React.ReactNode;
25
+ error?: boolean | React.ReactNode;
26
+ noOptionsMessage?: NoOptionsMessage;
27
+ };
28
+ declare const NativeSelect: React.FC<NativeSelectProps>;
29
+ export default NativeSelect;