@equinor/eds-core-react 0.19.0-dev.202205096 → 0.20.1-dev.20220609

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.
@@ -0,0 +1,481 @@
1
+ import { forwardRef, useRef, useState, useMemo, useCallback, useEffect } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { useMultipleSelection, useCombobox } from 'downshift';
4
+ import styled, { css, ThemeProvider } from 'styled-components';
5
+ import { Button } from '../Button/Button.js';
6
+ import { List } from '../List/index.js';
7
+ import { useEds } from '../EdsProvider/eds.context.js';
8
+ import { Label } from '../Label/Label.js';
9
+ import { Icon } from '../Icon/index.js';
10
+ import { Input } from '../Input/Input.js';
11
+ import { close, arrow_drop_up, arrow_drop_down } from '@equinor/eds-icons';
12
+ import { multiSelect, selectTokens } from './Autocomplete.tokens.js';
13
+ import { bordersTemplate, useIsMounted, useToken, usePopper } from '@equinor/eds-utils';
14
+ import { AutocompleteOption } from './Option.js';
15
+ import { jsx, jsxs } from 'react/jsx-runtime';
16
+
17
+ const Container = styled.div.withConfig({
18
+ displayName: "Autocomplete__Container",
19
+ componentId: "sc-yvif0e-0"
20
+ })(["position:relative;"]);
21
+ const StyledInput = styled(Input).withConfig({
22
+ displayName: "Autocomplete__StyledInput",
23
+ componentId: "sc-yvif0e-1"
24
+ })(_ref => {
25
+ let {
26
+ theme: {
27
+ entities: {
28
+ button
29
+ }
30
+ }
31
+ } = _ref;
32
+ return css(["padding-right:calc( ", " + ", " + (", " * 2) );"], button.spacings.left, button.spacings.right, button.height);
33
+ });
34
+ const StyledList = styled(List).withConfig({
35
+ displayName: "Autocomplete__StyledList",
36
+ componentId: "sc-yvif0e-2"
37
+ })(_ref2 => {
38
+ let {
39
+ theme
40
+ } = _ref2;
41
+ return css(["background-color:", ";box-shadow:", ";", " overflow-y:auto;max-height:300px;padding:0;position:absolute;right:0;left:0;z-index:50;"], theme.background, theme.boxShadow, bordersTemplate(theme.border));
42
+ });
43
+ const StyledButton = styled(Button).withConfig({
44
+ displayName: "Autocomplete__StyledButton",
45
+ componentId: "sc-yvif0e-3"
46
+ })(_ref3 => {
47
+ let {
48
+ theme: {
49
+ entities: {
50
+ button
51
+ }
52
+ }
53
+ } = _ref3;
54
+ return css(["position:absolute;height:", ";width:", ";right:", ";top:", ";"], button.height, button.height, button.spacings.right, button.spacings.top);
55
+ });
56
+
57
+ const findIndex = _ref4 => {
58
+ let {
59
+ calc,
60
+ index,
61
+ optionDisabled,
62
+ availableItems
63
+ } = _ref4;
64
+ const nextItem = availableItems[index];
65
+
66
+ if (optionDisabled(nextItem)) {
67
+ const nextIndex = calc(index);
68
+ return findIndex({
69
+ calc,
70
+ index: nextIndex,
71
+ availableItems,
72
+ optionDisabled
73
+ });
74
+ }
75
+
76
+ return index;
77
+ };
78
+
79
+ const findNextIndex = _ref5 => {
80
+ let {
81
+ index,
82
+ optionDisabled,
83
+ availableItems
84
+ } = _ref5;
85
+ const options = {
86
+ index,
87
+ optionDisabled,
88
+ availableItems,
89
+ calc: num => num + 1
90
+ };
91
+ const nextIndex = findIndex(options);
92
+
93
+ if (nextIndex > availableItems.length - 1) {
94
+ // jump to start of list
95
+ return findIndex({ ...options,
96
+ index: 0
97
+ });
98
+ }
99
+
100
+ return nextIndex;
101
+ };
102
+
103
+ const findPrevIndex = _ref6 => {
104
+ let {
105
+ index,
106
+ optionDisabled,
107
+ availableItems
108
+ } = _ref6;
109
+ const options = {
110
+ index,
111
+ optionDisabled,
112
+ availableItems,
113
+ calc: num => num - 1
114
+ };
115
+ const prevIndex = findIndex(options);
116
+
117
+ if (prevIndex < 0) {
118
+ // jump to end of list
119
+ return findIndex({ ...options,
120
+ index: availableItems.length - 1
121
+ });
122
+ }
123
+
124
+ return prevIndex;
125
+ };
126
+
127
+ function AutocompleteInner(props, ref) {
128
+ const {
129
+ options = [],
130
+ label,
131
+ meta,
132
+ className,
133
+ disabled = false,
134
+ readOnly = false,
135
+ onOptionsChange,
136
+ selectedOptions,
137
+ multiple,
138
+ initialSelectedOptions = [],
139
+ disablePortal,
140
+ optionDisabled = () => false,
141
+ optionsFilter,
142
+ autoWidth,
143
+ placeholder,
144
+ optionLabel,
145
+ ...other
146
+ } = props;
147
+ const anchorRef = useRef();
148
+ const [anchorEl, setAnchorEl] = useState();
149
+ const [containerEl, setContainerEl] = useState();
150
+ const isMounted = useIsMounted();
151
+ const isControlled = Boolean(selectedOptions);
152
+ const [availableItems, setAvailableItems] = useState(options);
153
+ const disabledItems = useMemo(() => options.filter(optionDisabled), [options, optionDisabled]);
154
+ const {
155
+ density
156
+ } = useEds();
157
+ const token = useToken({
158
+ density
159
+ }, multiple ? multiSelect : selectTokens);
160
+ let placeholderText = placeholder;
161
+ let multipleSelectionProps = {
162
+ initialSelectedItems: multiple ? initialSelectedOptions : initialSelectedOptions[0] ? [initialSelectedOptions[0]] : [],
163
+ onSelectedItemsChange: changes => {
164
+ if (onOptionsChange) {
165
+ onOptionsChange(changes);
166
+ }
167
+ }
168
+ };
169
+
170
+ if (isControlled) {
171
+ multipleSelectionProps = { ...multipleSelectionProps,
172
+ selectedItems: selectedOptions
173
+ };
174
+ }
175
+
176
+ const {
177
+ getDropdownProps,
178
+ addSelectedItem,
179
+ removeSelectedItem,
180
+ selectedItems,
181
+ reset: resetSelection,
182
+ setSelectedItems
183
+ } = useMultipleSelection(multipleSelectionProps);
184
+ const getLabel = useCallback(item => {
185
+ if (!item) {
186
+ return '';
187
+ }
188
+
189
+ if (typeof item === 'object') {
190
+ if (optionLabel) {
191
+ return optionLabel(item);
192
+ } else {
193
+ throw new Error('Missing label. When using objects for options make sure to define the `optionLabel` property');
194
+ }
195
+ }
196
+
197
+ if (typeof item === 'string') {
198
+ return item;
199
+ }
200
+
201
+ try {
202
+ return item === null || item === void 0 ? void 0 : item.toString();
203
+ } catch (error) {
204
+ throw new Error('Unable to find label, make sure your are using options as documented');
205
+ }
206
+ }, [optionLabel]);
207
+ let comboBoxProps = {
208
+ items: availableItems,
209
+ initialSelectedItem: initialSelectedOptions[0],
210
+ itemToString: getLabel,
211
+ onInputValueChange: _ref7 => {
212
+ let {
213
+ inputValue
214
+ } = _ref7;
215
+ setAvailableItems(options.filter(item => {
216
+ if (optionsFilter) {
217
+ return optionsFilter(item, inputValue);
218
+ }
219
+
220
+ return getLabel(item).toLowerCase().includes(inputValue.toLowerCase());
221
+ }));
222
+ },
223
+ onIsOpenChange: _ref8 => {
224
+ let {
225
+ selectedItem
226
+ } = _ref8;
227
+
228
+ // Show all options when single select is reopened with a selected item
229
+ if (availableItems.length === 1 && selectedItem === availableItems[0]) {
230
+ setAvailableItems(options);
231
+ }
232
+ },
233
+ onStateChange: _ref9 => {
234
+ let {
235
+ type,
236
+ selectedItem
237
+ } = _ref9;
238
+
239
+ switch (type) {
240
+ case useCombobox.stateChangeTypes.InputChange:
241
+ break;
242
+
243
+ case useCombobox.stateChangeTypes.InputKeyDownEnter:
244
+ case useCombobox.stateChangeTypes.ItemClick:
245
+ case useCombobox.stateChangeTypes.InputBlur:
246
+ if (selectedItem && !optionDisabled(selectedItem)) {
247
+ if (multiple) {
248
+ selectedItems.includes(selectedItem) ? removeSelectedItem(selectedItem) : addSelectedItem(selectedItem);
249
+ } else {
250
+ setSelectedItems([selectedItem]);
251
+ }
252
+ }
253
+
254
+ break;
255
+ }
256
+ },
257
+ stateReducer: (_, actionAndChanges) => {
258
+ const {
259
+ changes,
260
+ type
261
+ } = actionAndChanges;
262
+
263
+ switch (type) {
264
+ case useCombobox.stateChangeTypes.InputKeyDownArrowDown:
265
+ case useCombobox.stateChangeTypes.InputKeyDownHome:
266
+ return { ...changes,
267
+ highlightedIndex: findNextIndex({
268
+ index: changes.highlightedIndex,
269
+ availableItems,
270
+ optionDisabled
271
+ })
272
+ };
273
+
274
+ case useCombobox.stateChangeTypes.InputKeyDownArrowUp:
275
+ case useCombobox.stateChangeTypes.InputKeyDownEnd:
276
+ return { ...changes,
277
+ highlightedIndex: findPrevIndex({
278
+ index: changes.highlightedIndex,
279
+ availableItems,
280
+ optionDisabled
281
+ })
282
+ };
283
+
284
+ default:
285
+ return changes;
286
+ }
287
+ }
288
+ };
289
+
290
+ if (isControlled && !multiple) {
291
+ comboBoxProps = { ...comboBoxProps,
292
+ selectedItem: selectedOptions[0]
293
+ };
294
+ }
295
+
296
+ if (multiple) {
297
+ placeholderText = typeof placeholderText !== 'undefined' ? placeholderText : "".concat(selectedItems.length, "/").concat(options.length - disabledItems.length, " selected");
298
+ comboBoxProps = { ...comboBoxProps,
299
+ selectedItem: null,
300
+ stateReducer: (state, actionAndChanges) => {
301
+ const {
302
+ changes,
303
+ type
304
+ } = actionAndChanges;
305
+
306
+ switch (type) {
307
+ case useCombobox.stateChangeTypes.InputKeyDownArrowDown:
308
+ case useCombobox.stateChangeTypes.InputKeyDownHome:
309
+ return { ...changes,
310
+ highlightedIndex: findNextIndex({
311
+ index: changes.highlightedIndex,
312
+ availableItems,
313
+ optionDisabled
314
+ })
315
+ };
316
+
317
+ case useCombobox.stateChangeTypes.InputKeyDownArrowUp:
318
+ case useCombobox.stateChangeTypes.InputKeyDownEnd:
319
+ return { ...changes,
320
+ highlightedIndex: findPrevIndex({
321
+ index: changes.highlightedIndex,
322
+ availableItems,
323
+ optionDisabled
324
+ })
325
+ };
326
+
327
+ case useCombobox.stateChangeTypes.InputKeyDownEnter:
328
+ case useCombobox.stateChangeTypes.ItemClick:
329
+ return { ...changes,
330
+ isOpen: true,
331
+ // keep menu open after selection.
332
+ highlightedIndex: state.highlightedIndex,
333
+ inputValue: '' // don't add the item string as input value at selection.
334
+
335
+ };
336
+
337
+ case useCombobox.stateChangeTypes.InputBlur:
338
+ return { ...changes,
339
+ inputValue: '' // don't add the item string as input value at selection.
340
+
341
+ };
342
+
343
+ default:
344
+ return changes;
345
+ }
346
+ }
347
+ };
348
+ }
349
+
350
+ const {
351
+ isOpen,
352
+ getToggleButtonProps,
353
+ getLabelProps,
354
+ getMenuProps,
355
+ getInputProps,
356
+ getComboboxProps,
357
+ highlightedIndex,
358
+ getItemProps,
359
+ openMenu,
360
+ inputValue,
361
+ reset: resetCombobox
362
+ } = useCombobox(comboBoxProps);
363
+ useEffect(() => {
364
+ if (anchorRef.current) {
365
+ setAnchorEl(anchorRef.current);
366
+ }
367
+
368
+ if (isControlled) {
369
+ setSelectedItems(selectedOptions);
370
+ }
371
+
372
+ return () => {
373
+ setAnchorEl(null);
374
+ setContainerEl(null);
375
+ };
376
+ }, [anchorRef, isControlled, isOpen, selectedOptions, setSelectedItems]);
377
+ const {
378
+ styles,
379
+ attributes
380
+ } = usePopper({
381
+ anchorEl,
382
+ popperEl: containerEl,
383
+ placement: 'bottom-start',
384
+ offset: 4,
385
+ autoWidth
386
+ });
387
+
388
+ const openSelect = () => {
389
+ if (!isOpen && !(disabled || readOnly)) {
390
+ openMenu();
391
+ }
392
+ };
393
+
394
+ const clear = () => {
395
+ resetCombobox();
396
+ resetSelection();
397
+ };
398
+
399
+ const showClearButton = (selectedItems.length > 0 || inputValue) && !readOnly;
400
+ const selectedItemsLabels = useMemo(() => selectedItems.map(getLabel), [selectedItems, getLabel]);
401
+
402
+ const optionsList = /*#__PURE__*/jsx(StyledList, { ...getMenuProps({
403
+ ref: setContainerEl,
404
+ style: styles.popper,
405
+ 'aria-multiselectable': multiple ? 'true' : null,
406
+ ...attributes.popper
407
+ }, {
408
+ suppressRefError: true
409
+ }),
410
+ children: !isOpen ? null : availableItems.map((item, index) => {
411
+ const label = getLabel(item);
412
+ const isDisabled = optionDisabled(item);
413
+ const isSelected = selectedItemsLabels.includes(label);
414
+ return /*#__PURE__*/jsx(AutocompleteOption, {
415
+ value: label,
416
+ multiple: multiple,
417
+ highlighted: highlightedIndex === index && !isDisabled ? 'true' : 'false',
418
+ isSelected: isSelected,
419
+ isDisabled: isDisabled,
420
+ ...getItemProps({
421
+ item,
422
+ index,
423
+ disabled
424
+ })
425
+ }, label);
426
+ })
427
+ });
428
+
429
+ return /*#__PURE__*/jsx(ThemeProvider, {
430
+ theme: token,
431
+ children: /*#__PURE__*/jsxs(Container, {
432
+ className: className,
433
+ ref: ref,
434
+ children: [/*#__PURE__*/jsx(Label, { ...getLabelProps(),
435
+ label: label,
436
+ meta: meta,
437
+ disabled: disabled
438
+ }), /*#__PURE__*/jsxs(Container, { ...getComboboxProps(),
439
+ children: [/*#__PURE__*/jsx(StyledInput, { ...getInputProps( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
440
+ getDropdownProps({
441
+ preventKeyAction: multiple ? isOpen : undefined,
442
+ disabled,
443
+ ref: anchorRef
444
+ })),
445
+ placeholder: placeholderText,
446
+ readOnly: readOnly,
447
+ onFocus: openSelect,
448
+ onClick: openSelect,
449
+ ...other
450
+ }), showClearButton && /*#__PURE__*/jsx(StyledButton, {
451
+ variant: "ghost_icon",
452
+ disabled: disabled || readOnly,
453
+ "aria-label": 'clear options',
454
+ title: "clear",
455
+ onClick: clear,
456
+ style: {
457
+ right: 32
458
+ },
459
+ children: /*#__PURE__*/jsx(Icon, {
460
+ data: close,
461
+ size: 16
462
+ })
463
+ }), /*#__PURE__*/jsx(StyledButton, {
464
+ variant: "ghost_icon",
465
+ ...getToggleButtonProps({
466
+ disabled: disabled || readOnly
467
+ }),
468
+ "aria-label": 'toggle options',
469
+ title: "open",
470
+ children: !readOnly && /*#__PURE__*/jsx(Icon, {
471
+ data: isOpen ? arrow_drop_up : arrow_drop_down
472
+ })
473
+ })]
474
+ }), disablePortal ? optionsList : !isMounted ? null : /*#__PURE__*/createPortal(optionsList, document.body)]
475
+ })
476
+ });
477
+ }
478
+
479
+ const Autocomplete = /*#__PURE__*/forwardRef(AutocompleteInner);
480
+
481
+ export { Autocomplete };
@@ -0,0 +1,103 @@
1
+ import { tokens } from '@equinor/eds-tokens';
2
+ import mergeDeepRight from '../../node_modules/.pnpm/ramda@0.28.0/node_modules/ramda/es/mergeDeepRight.js';
3
+
4
+ const {
5
+ typography,
6
+ colors,
7
+ shape: {
8
+ straight,
9
+ corners: {
10
+ borderRadius
11
+ }
12
+ },
13
+ spacings: {
14
+ comfortable: {
15
+ small: spacingSmall,
16
+ medium_small: spacingMediumSmall,
17
+ medium: spacingMedium,
18
+ large: spacingLarge,
19
+ xx_small
20
+ }
21
+ },
22
+ elevation: {
23
+ temporary_nav: boxShadow
24
+ }
25
+ } = tokens;
26
+ const selectTokens = {
27
+ background: colors.ui.background__default.rgba,
28
+ boxShadow,
29
+ minHeight: straight.minHeight,
30
+ spacings: {
31
+ top: spacingMedium,
32
+ right: spacingLarge,
33
+ bottom: spacingMedium,
34
+ left: spacingLarge
35
+ },
36
+ typography: { ...typography.navigation.menu_title,
37
+ color: colors.text.static_icons__default.rgba
38
+ },
39
+ border: {
40
+ type: 'border',
41
+ radius: borderRadius
42
+ },
43
+ states: {
44
+ hover: {
45
+ background: colors.ui.background__medium.rgba
46
+ },
47
+ active: {
48
+ background: colors.interactive.primary__selected_highlight.rgba
49
+ },
50
+ disabled: {
51
+ typography: {
52
+ color: colors.interactive.disabled__text.rgba
53
+ }
54
+ }
55
+ },
56
+ entities: {
57
+ button: {
58
+ height: '24px',
59
+ spacings: {
60
+ left: spacingSmall,
61
+ right: spacingSmall,
62
+ top: '6px'
63
+ }
64
+ }
65
+ },
66
+ modes: {
67
+ compact: {
68
+ spacings: {
69
+ left: spacingSmall,
70
+ right: spacingSmall,
71
+ top: spacingSmall,
72
+ bottom: spacingSmall
73
+ },
74
+ entities: {
75
+ button: {
76
+ spacings: {
77
+ left: spacingSmall,
78
+ right: spacingSmall,
79
+ top: '0'
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+ };
86
+ const multiSelect = mergeDeepRight(selectTokens, {
87
+ spacings: {
88
+ top: '0',
89
+ bottom: '0',
90
+ left: spacingMediumSmall,
91
+ right: spacingLarge
92
+ },
93
+ modes: {
94
+ compact: {
95
+ spacings: {
96
+ top: xx_small,
97
+ bottom: '0'
98
+ }
99
+ }
100
+ }
101
+ });
102
+
103
+ export { multiSelect, selectTokens };
@@ -0,0 +1,52 @@
1
+ import { forwardRef } from 'react';
2
+ import styled, { css } from 'styled-components';
3
+ import { List } from '../List/index.js';
4
+ import { Checkbox } from '../Checkbox/Checkbox.js';
5
+ import { typographyTemplate, spacingsTemplate } from '@equinor/eds-utils';
6
+ import { jsxs, jsx } from 'react/jsx-runtime';
7
+
8
+ const StyledListItem = styled(List.Item).withConfig({
9
+ displayName: "Option__StyledListItem",
10
+ componentId: "sc-1ly11zu-0"
11
+ })(_ref => {
12
+ let {
13
+ theme,
14
+ highlighted,
15
+ active,
16
+ isdisabled
17
+ } = _ref;
18
+ const backgroundColor = highlighted === 'true' ? theme.states.hover.background : active === 'true' ? theme.states.active.background : theme.background;
19
+ return css(["display:flex;align-items:center;margin:0;list-style:none;background-color:", ";user-select:none;cursor:", ";", " ", " ", ""], backgroundColor, highlighted === 'true' ? 'pointer' : 'default', typographyTemplate(theme.typography), spacingsTemplate(theme.spacings), isdisabled === 'true' ? css(["color:", ";"], theme.states.disabled.typography.color) : '');
20
+ });
21
+ const AutocompleteOption = /*#__PURE__*/forwardRef(function AutocompleteOption(_ref2, ref) {
22
+ let {
23
+ value,
24
+ multiple,
25
+ isSelected,
26
+ isDisabled,
27
+ onClick,
28
+ 'aria-selected': ariaSelected,
29
+ ...other
30
+ } = _ref2;
31
+ return /*#__PURE__*/jsxs(StyledListItem, {
32
+ ref: ref,
33
+ isdisabled: isDisabled ? 'true' : 'false',
34
+ "aria-hidden": isDisabled,
35
+ active: !multiple && isSelected ? 'true' : 'false',
36
+ onClick: e => !isDisabled && onClick(e),
37
+ "aria-selected": multiple ? isSelected : ariaSelected,
38
+ ...other,
39
+ children: [multiple && /*#__PURE__*/jsx(Checkbox, {
40
+ disabled: isDisabled,
41
+ checked: isSelected,
42
+ value: value,
43
+ onChange: () => {
44
+ return null;
45
+ }
46
+ }), /*#__PURE__*/jsx("span", {
47
+ children: value
48
+ })]
49
+ });
50
+ });
51
+
52
+ export { AutocompleteOption };
@@ -10,7 +10,7 @@ const {
10
10
  const StyledCard = styled.div.withConfig({
11
11
  displayName: "Card__StyledCard",
12
12
  componentId: "sc-bjucjn-0"
13
- })(["height:fit-content;width:100%;position:relative;background-color:", ";box-sizing:border-box;display:grid;grid-gap:16px;grid-auto-columns:auto;align-items:center;align-content:start;cursor:", ";", ";"], _ref => {
13
+ })(["width:100%;position:relative;background-color:", ";box-sizing:border-box;display:flex;flex-direction:column;grid-gap:16px;cursor:", ";", ";"], _ref => {
14
14
  let {
15
15
  background
16
16
  } = _ref;
@@ -10,7 +10,7 @@ const {
10
10
  const StyledCardActions = styled.div.withConfig({
11
11
  displayName: "CardActions__StyledCardActions",
12
12
  componentId: "sc-1d5vskp-0"
13
- })(["display:grid;grid-gap:8px;grid-auto-flow:column;align-items:center;justify-content:", ";padding:0 ", " 0 ", ";:first-child{padding-top:", ";}:last-child{padding-bottom:", ";}"], _ref => {
13
+ })(["display:grid;grid-gap:8px;grid-auto-flow:column;align-items:center;justify-content:", ";padding:0 ", " 0 ", ";margin-top:auto;:first-child{padding-top:", ";}:last-child{padding-bottom:", ";}"], _ref => {
14
14
  let {
15
15
  justifyContent
16
16
  } = _ref;
@@ -59,12 +59,12 @@ const Popover = /*#__PURE__*/forwardRef(function Popover(_ref5, ref) {
59
59
  const [arrowRef, setArrowRef] = useState(null);
60
60
  const popoverRef = useCombinedRefs(ref, setPopperEl);
61
61
  useOutsideClick(popperEl, e => {
62
- if (open && onClose !== null && anchorEl && !anchorEl.contains(e.target)) {
62
+ if (open && onClose && anchorEl && !anchorEl.contains(e.target)) {
63
63
  onClose();
64
64
  }
65
65
  });
66
66
  useGlobalKeyPress('Escape', () => {
67
- if (onClose !== null && open) {
67
+ if (onClose && open) {
68
68
  onClose();
69
69
  }
70
70
  });