@equinor/eds-core-react 0.46.0 → 0.47.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.
@@ -7760,9 +7760,9 @@ const MenuItem$1 = /*#__PURE__*/react.forwardRef(function MenuItem({
7760
7760
  onClick: e => {
7761
7761
  if (onClick) {
7762
7762
  onClick(e);
7763
- if (onClose !== null && closeMenuOnClick) {
7764
- onClose(e);
7765
- }
7763
+ }
7764
+ if (onClose !== null && closeMenuOnClick) {
7765
+ onClose(e);
7766
7766
  }
7767
7767
  },
7768
7768
  children: /*#__PURE__*/jsxRuntime.jsx(Content, {
@@ -9500,11 +9500,49 @@ function AutocompleteOptionInner(props, ref) {
9500
9500
  }
9501
9501
  const AutocompleteOption = /*#__PURE__*/react.forwardRef(AutocompleteOptionInner);
9502
9502
 
9503
+ const StyledAddItemIcon = styled__default.default(Icon$1).withConfig({
9504
+ displayName: "AddNewOption__StyledAddItemIcon",
9505
+ componentId: "sc-8xtrxx-0"
9506
+ })(({
9507
+ multiple
9508
+ }) => {
9509
+ return styled.css(["padding:", ";color:", ";"], multiple ? '0.75rem' : '0 0.75rem 0 0', edsTokens.tokens.colors.interactive.primary__resting.hex);
9510
+ });
9511
+ function AddNewOptionInner(props, ref) {
9512
+ const {
9513
+ value,
9514
+ multiline,
9515
+ multiple,
9516
+ highlighted,
9517
+ onClick,
9518
+ ...other
9519
+ } = props;
9520
+ return /*#__PURE__*/jsxRuntime.jsxs(StyledListItem, {
9521
+ ref: ref,
9522
+ $highlighted: highlighted,
9523
+ "aria-label": `Add new option: ${value}`,
9524
+ onClick: e => {
9525
+ onClick(e);
9526
+ },
9527
+ ...other,
9528
+ children: [/*#__PURE__*/jsxRuntime.jsx(StyledAddItemIcon, {
9529
+ multiple: multiple,
9530
+ data: edsIcons.add_box
9531
+ }), /*#__PURE__*/jsxRuntime.jsx(AutocompleteOptionLabel, {
9532
+ $multiline: multiline,
9533
+ children: value
9534
+ })]
9535
+ });
9536
+ }
9537
+ const AddNewOption = /*#__PURE__*/react.forwardRef(AddNewOptionInner);
9538
+
9503
9539
  const Container$2 = styled__default.default.div.withConfig({
9504
9540
  displayName: "Autocomplete__Container",
9505
9541
  componentId: "sc-yvif0e-0"
9506
9542
  })(["position:relative;"]);
9507
9543
  const AllSymbol = Symbol('Select all');
9544
+ const AddSymbol = Symbol('Add new');
9545
+
9508
9546
  // MARK: styled components
9509
9547
  const StyledList = styled__default.default(List$1).withConfig({
9510
9548
  displayName: "Autocomplete__StyledList",
@@ -9638,6 +9676,7 @@ const defaultOptionDisabled = () => false;
9638
9676
  function AutocompleteInner(props, ref) {
9639
9677
  const {
9640
9678
  options = [],
9679
+ totalOptions,
9641
9680
  label,
9642
9681
  meta,
9643
9682
  className,
@@ -9647,6 +9686,7 @@ function AutocompleteInner(props, ref) {
9647
9686
  loading = false,
9648
9687
  hideClearButton = false,
9649
9688
  onOptionsChange,
9689
+ onAddNewOption,
9650
9690
  onInputChange,
9651
9691
  selectedOptions: _selectedOptions,
9652
9692
  multiple,
@@ -9701,8 +9741,9 @@ function AutocompleteInner(props, ref) {
9701
9741
  }, [allowSelectAll, multiple, typedInputValue]);
9702
9742
  const availableItems = react.useMemo(() => {
9703
9743
  if (showSelectAll) return [AllSymbol, ..._availableItems];
9744
+ if (_availableItems.length === 0 && onAddNewOption) return [AddSymbol];
9704
9745
  return _availableItems;
9705
- }, [_availableItems, showSelectAll]);
9746
+ }, [_availableItems, showSelectAll, onAddNewOption]);
9706
9747
 
9707
9748
  //issue 2304, update dataset when options are added dynamically
9708
9749
  react.useEffect(() => {
@@ -9732,7 +9773,7 @@ function AutocompleteInner(props, ref) {
9732
9773
  ...multipleSelectionProps,
9733
9774
  onSelectedItemsChange: changes => {
9734
9775
  if (onOptionsChange) {
9735
- let selectedItems = changes.selectedItems.filter(item => item !== AllSymbol);
9776
+ let selectedItems = changes.selectedItems.filter(item => item !== AllSymbol || item !== AddSymbol);
9736
9777
  if (itemCompare) {
9737
9778
  selectedItems = inputOptions.filter(item => selectedItems.some(compare => itemCompare(item, compare)));
9738
9779
  }
@@ -9878,6 +9919,8 @@ function AutocompleteInner(props, ref) {
9878
9919
  if (selectedItem != null && !optionDisabled(selectedItem)) {
9879
9920
  if (selectedItem === AllSymbol) {
9880
9921
  toggleAllSelected();
9922
+ } else if (selectedItem === AddSymbol) {
9923
+ onAddNewOption?.(typedInputValue);
9881
9924
  } else if (multiple) {
9882
9925
  const shouldRemove = itemCompare ? selectedItems.some(i => itemCompare(selectedItem, i)) : selectedItems.includes(selectedItem);
9883
9926
  if (shouldRemove) {
@@ -9921,11 +9964,29 @@ function AutocompleteInner(props, ref) {
9921
9964
  ...changes,
9922
9965
  isOpen: !(disabled || readOnly)
9923
9966
  };
9967
+ case downshift.useCombobox.stateChangeTypes.InputKeyDownEnter:
9968
+ case downshift.useCombobox.stateChangeTypes.ItemClick:
9969
+ if (changes.selectedItem === AddSymbol) {
9970
+ onAddNewOption(typedInputValue);
9971
+ return {
9972
+ ...changes,
9973
+ inputValue: '',
9974
+ selectedItem: null
9975
+ };
9976
+ }
9977
+ return {
9978
+ ...changes
9979
+ };
9924
9980
  case downshift.useCombobox.stateChangeTypes.InputBlur:
9925
9981
  return {
9926
9982
  ...changes,
9927
9983
  inputValue: changes.selectedItem ? getLabel(changes.selectedItem) : ''
9928
9984
  };
9985
+ case downshift.useCombobox.stateChangeTypes.InputChange:
9986
+ setTypedInputValue(changes.inputValue);
9987
+ return {
9988
+ ...changes
9989
+ };
9929
9990
  case downshift.useCombobox.stateChangeTypes.InputKeyDownArrowDown:
9930
9991
  case downshift.useCombobox.stateChangeTypes.InputKeyDownHome:
9931
9992
  if (readOnly) {
@@ -9979,7 +10040,9 @@ function AutocompleteInner(props, ref) {
9979
10040
  }
9980
10041
  // MARK: multiselect specific
9981
10042
  if (multiple) {
9982
- placeholderText = typeof placeholderText !== 'undefined' ? placeholderText : `${selectedItems.length}/${inputOptions.length} selected`;
10043
+ const showPlaceholder = placeholderText && selectedItems.length === 0;
10044
+ const optionCount = totalOptions || inputOptions.length;
10045
+ placeholderText = showPlaceholder ? placeholderText : `${selectedItems.length}/${optionCount} selected`;
9983
10046
  comboBoxProps = {
9984
10047
  ...comboBoxProps,
9985
10048
  selectedItem: null,
@@ -10196,6 +10259,29 @@ function AutocompleteInner(props, ref) {
10196
10259
  })
10197
10260
  }, 'select-all');
10198
10261
  }
10262
+ if (item === AddSymbol && onAddNewOption) {
10263
+ return /*#__PURE__*/jsxRuntime.jsx(AddNewOption, {
10264
+ "data-index": 0,
10265
+ "data-testid": 'add-item',
10266
+ "aria-setsize": availableItems.length,
10267
+ multiple: multiple,
10268
+ highlighted: highlightedIndex === index && !isDisabled ? 'true' : 'false',
10269
+ multiline: multiline,
10270
+ style: {
10271
+ position: 'sticky',
10272
+ top: 0,
10273
+ zIndex: 99
10274
+ },
10275
+ ...getItemProps({
10276
+ ...(multiline && {
10277
+ ref: rowVirtualizer.measureElement
10278
+ }),
10279
+ item,
10280
+ index: index
10281
+ }),
10282
+ value: `${typedInputValue}`
10283
+ }, 'add-item');
10284
+ }
10199
10285
  return /*#__PURE__*/jsxRuntime.jsx(AutocompleteOption, {
10200
10286
  "data-index": index,
10201
10287
  "aria-setsize": availableItems.length,
@@ -0,0 +1,45 @@
1
+ import { add_box } from '@equinor/eds-icons';
2
+ import { tokens } from '@equinor/eds-tokens';
3
+ import { forwardRef } from 'react';
4
+ import styled, { css } from 'styled-components';
5
+ import { Icon } from '../Icon/index.js';
6
+ import { StyledListItem, AutocompleteOptionLabel } from './Option.js';
7
+ import { jsxs, jsx } from 'react/jsx-runtime';
8
+
9
+ const StyledAddItemIcon = styled(Icon).withConfig({
10
+ displayName: "AddNewOption__StyledAddItemIcon",
11
+ componentId: "sc-8xtrxx-0"
12
+ })(({
13
+ multiple
14
+ }) => {
15
+ return css(["padding:", ";color:", ";"], multiple ? '0.75rem' : '0 0.75rem 0 0', tokens.colors.interactive.primary__resting.hex);
16
+ });
17
+ function AddNewOptionInner(props, ref) {
18
+ const {
19
+ value,
20
+ multiline,
21
+ multiple,
22
+ highlighted,
23
+ onClick,
24
+ ...other
25
+ } = props;
26
+ return /*#__PURE__*/jsxs(StyledListItem, {
27
+ ref: ref,
28
+ $highlighted: highlighted,
29
+ "aria-label": `Add new option: ${value}`,
30
+ onClick: e => {
31
+ onClick(e);
32
+ },
33
+ ...other,
34
+ children: [/*#__PURE__*/jsx(StyledAddItemIcon, {
35
+ multiple: multiple,
36
+ data: add_box
37
+ }), /*#__PURE__*/jsx(AutocompleteOptionLabel, {
38
+ $multiline: multiline,
39
+ children: value
40
+ })]
41
+ });
42
+ }
43
+ const AddNewOption = /*#__PURE__*/forwardRef(AddNewOptionInner);
44
+
45
+ export { AddNewOption };
@@ -11,6 +11,7 @@ import { multiSelect, selectTokens } from './Autocomplete.tokens.js';
11
11
  import { useToken, useIsomorphicLayoutEffect, bordersTemplate } from '@equinor/eds-utils';
12
12
  import { AutocompleteOption } from './Option.js';
13
13
  import { useFloating, offset, flip, size, useInteractions, autoUpdate } from '@floating-ui/react';
14
+ import { AddNewOption } from './AddNewOption.js';
14
15
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
15
16
  import pickBy from '../../node_modules/.pnpm/ramda@0.30.1/node_modules/ramda/es/pickBy.js';
16
17
  import mergeWith from '../../node_modules/.pnpm/ramda@0.30.1/node_modules/ramda/es/mergeWith.js';
@@ -24,6 +25,8 @@ const Container = styled.div.withConfig({
24
25
  componentId: "sc-yvif0e-0"
25
26
  })(["position:relative;"]);
26
27
  const AllSymbol = Symbol('Select all');
28
+ const AddSymbol = Symbol('Add new');
29
+
27
30
  // MARK: styled components
28
31
  const StyledList = styled(List).withConfig({
29
32
  displayName: "Autocomplete__StyledList",
@@ -157,6 +160,7 @@ const defaultOptionDisabled = () => false;
157
160
  function AutocompleteInner(props, ref) {
158
161
  const {
159
162
  options = [],
163
+ totalOptions,
160
164
  label,
161
165
  meta,
162
166
  className,
@@ -166,6 +170,7 @@ function AutocompleteInner(props, ref) {
166
170
  loading = false,
167
171
  hideClearButton = false,
168
172
  onOptionsChange,
173
+ onAddNewOption,
169
174
  onInputChange,
170
175
  selectedOptions: _selectedOptions,
171
176
  multiple,
@@ -220,8 +225,9 @@ function AutocompleteInner(props, ref) {
220
225
  }, [allowSelectAll, multiple, typedInputValue]);
221
226
  const availableItems = useMemo(() => {
222
227
  if (showSelectAll) return [AllSymbol, ..._availableItems];
228
+ if (_availableItems.length === 0 && onAddNewOption) return [AddSymbol];
223
229
  return _availableItems;
224
- }, [_availableItems, showSelectAll]);
230
+ }, [_availableItems, showSelectAll, onAddNewOption]);
225
231
 
226
232
  //issue 2304, update dataset when options are added dynamically
227
233
  useEffect(() => {
@@ -251,7 +257,7 @@ function AutocompleteInner(props, ref) {
251
257
  ...multipleSelectionProps,
252
258
  onSelectedItemsChange: changes => {
253
259
  if (onOptionsChange) {
254
- let selectedItems = changes.selectedItems.filter(item => item !== AllSymbol);
260
+ let selectedItems = changes.selectedItems.filter(item => item !== AllSymbol || item !== AddSymbol);
255
261
  if (itemCompare) {
256
262
  selectedItems = inputOptions.filter(item => selectedItems.some(compare => itemCompare(item, compare)));
257
263
  }
@@ -397,6 +403,8 @@ function AutocompleteInner(props, ref) {
397
403
  if (selectedItem != null && !optionDisabled(selectedItem)) {
398
404
  if (selectedItem === AllSymbol) {
399
405
  toggleAllSelected();
406
+ } else if (selectedItem === AddSymbol) {
407
+ onAddNewOption?.(typedInputValue);
400
408
  } else if (multiple) {
401
409
  const shouldRemove = itemCompare ? selectedItems.some(i => itemCompare(selectedItem, i)) : selectedItems.includes(selectedItem);
402
410
  if (shouldRemove) {
@@ -440,11 +448,29 @@ function AutocompleteInner(props, ref) {
440
448
  ...changes,
441
449
  isOpen: !(disabled || readOnly)
442
450
  };
451
+ case useCombobox.stateChangeTypes.InputKeyDownEnter:
452
+ case useCombobox.stateChangeTypes.ItemClick:
453
+ if (changes.selectedItem === AddSymbol) {
454
+ onAddNewOption(typedInputValue);
455
+ return {
456
+ ...changes,
457
+ inputValue: '',
458
+ selectedItem: null
459
+ };
460
+ }
461
+ return {
462
+ ...changes
463
+ };
443
464
  case useCombobox.stateChangeTypes.InputBlur:
444
465
  return {
445
466
  ...changes,
446
467
  inputValue: changes.selectedItem ? getLabel(changes.selectedItem) : ''
447
468
  };
469
+ case useCombobox.stateChangeTypes.InputChange:
470
+ setTypedInputValue(changes.inputValue);
471
+ return {
472
+ ...changes
473
+ };
448
474
  case useCombobox.stateChangeTypes.InputKeyDownArrowDown:
449
475
  case useCombobox.stateChangeTypes.InputKeyDownHome:
450
476
  if (readOnly) {
@@ -498,7 +524,9 @@ function AutocompleteInner(props, ref) {
498
524
  }
499
525
  // MARK: multiselect specific
500
526
  if (multiple) {
501
- placeholderText = typeof placeholderText !== 'undefined' ? placeholderText : `${selectedItems.length}/${inputOptions.length} selected`;
527
+ const showPlaceholder = placeholderText && selectedItems.length === 0;
528
+ const optionCount = totalOptions || inputOptions.length;
529
+ placeholderText = showPlaceholder ? placeholderText : `${selectedItems.length}/${optionCount} selected`;
502
530
  comboBoxProps = {
503
531
  ...comboBoxProps,
504
532
  selectedItem: null,
@@ -715,6 +743,29 @@ function AutocompleteInner(props, ref) {
715
743
  })
716
744
  }, 'select-all');
717
745
  }
746
+ if (item === AddSymbol && onAddNewOption) {
747
+ return /*#__PURE__*/jsx(AddNewOption, {
748
+ "data-index": 0,
749
+ "data-testid": 'add-item',
750
+ "aria-setsize": availableItems.length,
751
+ multiple: multiple,
752
+ highlighted: highlightedIndex === index && !isDisabled ? 'true' : 'false',
753
+ multiline: multiline,
754
+ style: {
755
+ position: 'sticky',
756
+ top: 0,
757
+ zIndex: 99
758
+ },
759
+ ...getItemProps({
760
+ ...(multiline && {
761
+ ref: rowVirtualizer.measureElement
762
+ }),
763
+ item,
764
+ index: index
765
+ }),
766
+ value: `${typedInputValue}`
767
+ }, 'add-item');
768
+ }
718
769
  return /*#__PURE__*/jsx(AutocompleteOption, {
719
770
  "data-index": index,
720
771
  "aria-setsize": availableItems.length,
@@ -68,4 +68,4 @@ function AutocompleteOptionInner(props, ref) {
68
68
  }
69
69
  const AutocompleteOption = /*#__PURE__*/forwardRef(AutocompleteOptionInner);
70
70
 
71
- export { AutocompleteOption, AutocompleteOptionLabel };
71
+ export { AutocompleteOption, AutocompleteOptionLabel, StyledListItem };
@@ -80,9 +80,9 @@ const MenuItem = /*#__PURE__*/forwardRef(function MenuItem({
80
80
  onClick: e => {
81
81
  if (onClick) {
82
82
  onClick(e);
83
- if (onClose !== null && closeMenuOnClick) {
84
- onClose(e);
85
- }
83
+ }
84
+ if (onClose !== null && closeMenuOnClick) {
85
+ onClose(e);
86
86
  }
87
87
  },
88
88
  children: /*#__PURE__*/jsx(Content, {
@@ -0,0 +1,13 @@
1
+ import { LiHTMLAttributes } from 'react';
2
+ export type AutocompleteOptionProps = {
3
+ value: string;
4
+ multiple: boolean;
5
+ highlighted: string;
6
+ multiline: boolean;
7
+ } & LiHTMLAttributes<HTMLLIElement>;
8
+ declare function AddNewOptionInner(props: AutocompleteOptionProps, ref: React.ForwardedRef<HTMLLIElement>): import("react/jsx-runtime").JSX.Element;
9
+ export declare const AddNewOption: (props: AutocompleteOptionProps & {
10
+ ref?: React.ForwardedRef<HTMLLIElement>;
11
+ displayName?: string | undefined;
12
+ }) => ReturnType<typeof AddNewOptionInner>;
13
+ export {};
@@ -6,6 +6,8 @@ export type AutocompleteChanges<T> = {
6
6
  export type AutocompleteProps<T> = {
7
7
  /** List of options in dropdown */
8
8
  options: readonly T[];
9
+ /** Total number of options */
10
+ totalOptions?: number;
9
11
  /** Label for the select element */
10
12
  label: ReactNode;
11
13
  /** Array of initial selected items
@@ -53,6 +55,8 @@ export type AutocompleteProps<T> = {
53
55
  * Returns input value
54
56
  */
55
57
  onInputChange?: (text: string) => void;
58
+ /** Callback for clicking the add new option button */
59
+ onAddNewOption?: (text: string) => void;
56
60
  /** Enable multiselect */
57
61
  multiple?: boolean;
58
62
  /** Add select-all option. Throws an error if true while multiple = false */
@@ -1,4 +1,10 @@
1
1
  import { LiHTMLAttributes, ReactNode } from 'react';
2
+ type StyledListItemType = {
3
+ $highlighted?: string;
4
+ $active?: string;
5
+ $isdisabled?: string;
6
+ };
7
+ export declare const StyledListItem: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, StyledListItemType>> & string;
2
8
  export declare const AutocompleteOptionLabel: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, {
3
9
  $multiline: boolean;
4
10
  }>> & string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@equinor/eds-core-react",
3
- "version": "0.46.0",
3
+ "version": "0.47.0",
4
4
  "description": "The React implementation of the Equinor Design System",
5
5
  "sideEffects": [
6
6
  "**/*.css"
@@ -67,8 +67,8 @@
67
67
  "rollup-plugin-delete": "^2.2.0",
68
68
  "rollup-plugin-postcss": "^4.0.2",
69
69
  "rollup-preserve-directives": "^1.1.3",
70
- "storybook": "^8.6.12",
71
- "styled-components": "6.1.17",
70
+ "storybook": "^8.6.14",
71
+ "styled-components": "6.1.18",
72
72
  "tsc-watch": "^6.2.1",
73
73
  "typescript": "^5.8.3"
74
74
  },
@@ -78,19 +78,19 @@
78
78
  "styled-components": ">=5.1"
79
79
  },
80
80
  "dependencies": {
81
- "@babel/runtime": "^7.27.1",
81
+ "@babel/runtime": "^7.27.4",
82
82
  "@floating-ui/react": "^0.27.8",
83
83
  "@internationalized/date": "^3.8.0",
84
84
  "@react-aria/utils": "^3.28.2",
85
85
  "@react-stately/calendar": "^3.8.0",
86
86
  "@react-stately/datepicker": "^3.14.0",
87
87
  "@react-types/shared": "^3.29.0",
88
- "@tanstack/react-virtual": "3.13.6",
88
+ "@tanstack/react-virtual": "3.13.8",
89
89
  "downshift": "9.0.8",
90
90
  "react-aria": "^3.39.0",
91
- "@equinor/eds-icons": "^0.22.0",
92
91
  "@equinor/eds-tokens": "0.9.2",
93
- "@equinor/eds-utils": "0.8.7"
92
+ "@equinor/eds-utils": "0.8.7",
93
+ "@equinor/eds-icons": "^0.22.0"
94
94
  },
95
95
  "scripts": {
96
96
  "build": "rollup -c --bundleConfigAsCjs && tsc -p tsconfig.build.json",