@onewelcome/react-lib-components 6.0.0 → 6.1.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 (194) hide show
  1. package/dist/cjs/Button/Button.module.cjs.js +1 -1
  2. package/dist/cjs/Button/IconButton.module.cjs.js +1 -1
  3. package/dist/cjs/ContextMenu/ContextMenu.cjs.js +1 -1
  4. package/dist/cjs/ContextMenu/ContextMenu.cjs.js.map +1 -1
  5. package/dist/cjs/ContextMenu/ContextMenuItem.module.cjs.js +1 -1
  6. package/dist/cjs/ContextMenu/ContextMenuService.cjs.js +1 -1
  7. package/dist/cjs/ContextMenu/ContextMenuService.cjs.js.map +1 -1
  8. package/dist/cjs/DataGrid/DataGridActions/DataGridColumnsToggle.module.cjs.js +1 -1
  9. package/dist/cjs/Form/Input/Input.module.cjs.js +1 -1
  10. package/dist/cjs/Form/Select/Option.cjs.js +1 -1
  11. package/dist/cjs/Form/Select/Option.cjs.js.map +1 -1
  12. package/dist/cjs/Form/Select/Select.cjs.js +1 -1
  13. package/dist/cjs/Form/Select/Select.cjs.js.map +1 -1
  14. package/dist/cjs/Form/Select/Select.module.cjs.js +1 -1
  15. package/dist/cjs/Form/Select/SelectService.cjs.js +1 -1
  16. package/dist/cjs/Form/Select/SelectService.cjs.js.map +1 -1
  17. package/dist/cjs/Form/Textarea/Textarea.module.cjs.js +1 -1
  18. package/dist/cjs/Link/Link.module.cjs.js +1 -1
  19. package/dist/cjs/Notifications/BaseModal/BaseModal.module.cjs.js +1 -1
  20. package/dist/cjs/Notifications/SlideInModal/SlideInModal.module.cjs.js +1 -1
  21. package/dist/cjs/Notifications/Snackbar/SnackbarItem/SnackbarItem.module.cjs.js +1 -1
  22. package/dist/cjs/Popover/Popover.cjs.js +1 -1
  23. package/dist/cjs/Popover/Popover.cjs.js.map +1 -1
  24. package/dist/cjs/Popover/Popover.module.cjs.js +1 -1
  25. package/dist/cjs/Stepper/Step.cjs.js +1 -1
  26. package/dist/cjs/Stepper/Step.cjs.js.map +1 -1
  27. package/dist/cjs/Stepper/Step.module.cjs.js +1 -1
  28. package/dist/cjs/Stepper/Stepper.cjs.js +1 -1
  29. package/dist/cjs/Stepper/Stepper.cjs.js.map +1 -1
  30. package/dist/cjs/Tabs/TabButton.module.cjs.js +1 -1
  31. package/dist/cjs/TextEllipsis/TextEllipsis.module.cjs.js +1 -1
  32. package/dist/cjs/Tiles/Tile.module.cjs.js +1 -1
  33. package/dist/cjs/Tooltip/Tooltip.module.cjs.js +1 -1
  34. package/dist/cjs/Wizard/BaseWizardSteps/BaseWizardSteps.module.cjs.js +1 -1
  35. package/dist/cjs/src/.scope.d.ts +2 -0
  36. package/dist/cjs/src/components/ContextMenu/ContextMenu.d.ts +1 -1
  37. package/dist/cjs/src/components/ContextMenu/ContextMenuService.d.ts +4 -1
  38. package/dist/cjs/src/components/Form/Select/Option.d.ts +1 -0
  39. package/dist/cjs/src/components/Form/Select/Select.d.ts +20 -0
  40. package/dist/cjs/src/components/Form/Select/Select.interfaces.d.ts +3 -1
  41. package/dist/cjs/src/components/Form/Select/Select.test.d.ts +63 -1
  42. package/dist/cjs/src/components/Form/Select/SelectKeyboardNavigation.test.d.ts +1 -0
  43. package/dist/cjs/src/components/Form/Select/SelectService.d.ts +3 -2
  44. package/dist/cjs/src/components/Stepper/Stepper.d.ts +0 -1
  45. package/dist/cjs/src/hooks/usePosition.cjs.js.map +1 -1
  46. package/dist/cjs/src/hooks/usePosition.d.ts +1 -1
  47. package/dist/esm/Button/Button.module.esm.js +1 -1
  48. package/dist/esm/Button/IconButton.module.esm.js +1 -1
  49. package/dist/esm/ContextMenu/ContextMenu.esm.js +1 -1
  50. package/dist/esm/ContextMenu/ContextMenu.esm.js.map +1 -1
  51. package/dist/esm/ContextMenu/ContextMenuItem.module.esm.js +1 -1
  52. package/dist/esm/ContextMenu/ContextMenuService.esm.js +1 -1
  53. package/dist/esm/ContextMenu/ContextMenuService.esm.js.map +1 -1
  54. package/dist/esm/DataGrid/DataGridActions/DataGridColumnsToggle.module.esm.js +1 -1
  55. package/dist/esm/Form/Input/Input.module.esm.js +1 -1
  56. package/dist/esm/Form/Select/Option.esm.js +1 -1
  57. package/dist/esm/Form/Select/Option.esm.js.map +1 -1
  58. package/dist/esm/Form/Select/Select.esm.js +1 -1
  59. package/dist/esm/Form/Select/Select.esm.js.map +1 -1
  60. package/dist/esm/Form/Select/Select.module.esm.js +1 -1
  61. package/dist/esm/Form/Select/SelectService.esm.js +1 -1
  62. package/dist/esm/Form/Select/SelectService.esm.js.map +1 -1
  63. package/dist/esm/Form/Textarea/Textarea.module.esm.js +1 -1
  64. package/dist/esm/Link/Link.module.esm.js +1 -1
  65. package/dist/esm/Notifications/BaseModal/BaseModal.module.esm.js +1 -1
  66. package/dist/esm/Notifications/SlideInModal/SlideInModal.module.esm.js +1 -1
  67. package/dist/esm/Notifications/Snackbar/SnackbarItem/SnackbarItem.module.esm.js +1 -1
  68. package/dist/esm/Popover/Popover.esm.js +1 -1
  69. package/dist/esm/Popover/Popover.esm.js.map +1 -1
  70. package/dist/esm/Popover/Popover.module.esm.js +1 -1
  71. package/dist/esm/Stepper/Step.esm.js +1 -1
  72. package/dist/esm/Stepper/Step.esm.js.map +1 -1
  73. package/dist/esm/Stepper/Step.module.esm.js +1 -1
  74. package/dist/esm/Stepper/Stepper.esm.js +1 -1
  75. package/dist/esm/Stepper/Stepper.esm.js.map +1 -1
  76. package/dist/esm/Tabs/TabButton.module.esm.js +1 -1
  77. package/dist/esm/TextEllipsis/TextEllipsis.module.esm.js +1 -1
  78. package/dist/esm/Tiles/Tile.module.esm.js +1 -1
  79. package/dist/esm/Tooltip/Tooltip.module.esm.js +1 -1
  80. package/dist/esm/Wizard/BaseWizardSteps/BaseWizardSteps.module.esm.js +1 -1
  81. package/dist/esm/src/.scope.d.ts +2 -0
  82. package/dist/esm/src/components/ContextMenu/ContextMenu.d.ts +1 -1
  83. package/dist/esm/src/components/ContextMenu/ContextMenuService.d.ts +4 -1
  84. package/dist/esm/src/components/Form/Select/Option.d.ts +1 -0
  85. package/dist/esm/src/components/Form/Select/Select.d.ts +20 -0
  86. package/dist/esm/src/components/Form/Select/Select.interfaces.d.ts +3 -1
  87. package/dist/esm/src/components/Form/Select/Select.test.d.ts +63 -1
  88. package/dist/esm/src/components/Form/Select/SelectKeyboardNavigation.test.d.ts +1 -0
  89. package/dist/esm/src/components/Form/Select/SelectService.d.ts +3 -2
  90. package/dist/esm/src/components/Stepper/Stepper.d.ts +0 -1
  91. package/dist/esm/src/hooks/usePosition.d.ts +1 -1
  92. package/dist/esm/src/hooks/usePosition.esm.js.map +1 -1
  93. package/package.json +48 -45
  94. package/src/{hooks/useWrapper.test.ts → .scope.ts} +1 -12
  95. package/src/components/ContextMenu/ContextMenu.tsx +13 -23
  96. package/src/components/ContextMenu/ContextMenuService.ts +47 -1
  97. package/src/components/Form/Select/Option.tsx +3 -1
  98. package/src/components/Form/Select/Select.interfaces.ts +3 -1
  99. package/src/components/Form/Select/Select.module.scss +55 -34
  100. package/src/components/Form/Select/Select.tsx +74 -23
  101. package/src/components/Form/Select/SelectService.ts +26 -10
  102. package/src/components/Link/Link.module.scss +16 -0
  103. package/src/components/Popover/Popover.tsx +19 -2
  104. package/src/components/Stepper/Step.tsx +2 -1
  105. package/src/components/Stepper/Stepper.tsx +0 -2
  106. package/src/components/Tooltip/Tooltip.module.scss +1 -0
  107. package/src/hooks/usePosition.ts +1 -1
  108. package/src/interfaces.ts +2 -2
  109. package/src/mixins.module.scss +2 -4
  110. package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +0 -64
  111. package/src/components/Button/BaseButton.test.tsx +0 -133
  112. package/src/components/Button/Button.test.tsx +0 -150
  113. package/src/components/Button/IconButton.test.tsx +0 -106
  114. package/src/components/ContextMenu/ContextMenu.test.tsx +0 -358
  115. package/src/components/DataGrid/DataGrid.test.tsx +0 -437
  116. package/src/components/DataGrid/DataGridActions/DataGridActions.test.tsx +0 -204
  117. package/src/components/DataGrid/DataGridActions/DataGridColumnsToggle.test.tsx +0 -99
  118. package/src/components/DataGrid/DataGridBody/DataGridBody.test.tsx +0 -140
  119. package/src/components/DataGrid/DataGridBody/DataGridCell.test.tsx +0 -90
  120. package/src/components/DataGrid/DataGridBody/DataGridRow.test.tsx +0 -117
  121. package/src/components/DataGrid/DataGridHeader/DataGridHeader.test.tsx +0 -276
  122. package/src/components/DataGrid/DataGridHeader/DataGridHeaderCell.test.tsx +0 -144
  123. package/src/components/Form/Checkbox/Checkbox.test.tsx +0 -308
  124. package/src/components/Form/Fieldset/Fieldset.test.tsx +0 -127
  125. package/src/components/Form/FileUpload/FileItem/FileItem.test.tsx +0 -103
  126. package/src/components/Form/FileUpload/FileUpload.test.tsx +0 -374
  127. package/src/components/Form/Form.test.tsx +0 -63
  128. package/src/components/Form/FormControl/FormControl.test.tsx +0 -98
  129. package/src/components/Form/FormGroup/FormGroup.test.tsx +0 -127
  130. package/src/components/Form/FormHelperText/FormHelperText.test.tsx +0 -84
  131. package/src/components/Form/FormSelectorWrapper/FormSelectorWrapper.test.tsx +0 -94
  132. package/src/components/Form/Input/Input.test.tsx +0 -267
  133. package/src/components/Form/Label/Label.test.tsx +0 -68
  134. package/src/components/Form/Radio/Radio.test.tsx +0 -130
  135. package/src/components/Form/Select/Option.test.tsx +0 -57
  136. package/src/components/Form/Select/Select.test.tsx +0 -564
  137. package/src/components/Form/Textarea/Textarea.test.tsx +0 -124
  138. package/src/components/Form/Toggle/Toggle.test.tsx +0 -200
  139. package/src/components/Form/Wrapper/CheckboxWrapper/CheckboxWrapper.test.tsx +0 -141
  140. package/src/components/Form/Wrapper/InputWrapper/InputWrapper.test.tsx +0 -211
  141. package/src/components/Form/Wrapper/RadioWrapper/RadioWrapper.test.tsx +0 -117
  142. package/src/components/Form/Wrapper/SelectWrapper/SelectWrapper.test.tsx +0 -186
  143. package/src/components/Form/Wrapper/TextareaWrapper/TextareaWrapper.test.tsx +0 -173
  144. package/src/components/Form/Wrapper/Wrapper/Wrapper.test.tsx +0 -59
  145. package/src/components/Icon/Icon.test.tsx +0 -83
  146. package/src/components/Link/Link.test.tsx +0 -203
  147. package/src/components/Notifications/Banner/Banner.test.tsx +0 -84
  148. package/src/components/Notifications/BaseModal/BaseModal.test.tsx +0 -194
  149. package/src/components/Notifications/BaseModal/BaseModalActions/BaseModalActions.test.tsx +0 -74
  150. package/src/components/Notifications/BaseModal/BaseModalContent/BaseModalContent.test.tsx +0 -71
  151. package/src/components/Notifications/BaseModal/BaseModalHeader/BaseModalHeader.test.tsx +0 -74
  152. package/src/components/Notifications/Dialog/Dialog.test.tsx +0 -118
  153. package/src/components/Notifications/Dialog/DialogActions/DialogActions.test.tsx +0 -61
  154. package/src/components/Notifications/Dialog/DialogTitle/DialogTitle.test.tsx +0 -87
  155. package/src/components/Notifications/DiscardChangesModal/DiscardChangesDialog/DiscardChangesDialog.test.tsx +0 -111
  156. package/src/components/Notifications/DiscardChangesModal/DiscardChangesModal.test.tsx +0 -175
  157. package/src/components/Notifications/Modal/Modal.test.tsx +0 -18
  158. package/src/components/Notifications/NotificationProvider/NotificationContext.test.tsx +0 -449
  159. package/src/components/Notifications/SlideInModal/SlideInModal.test.tsx +0 -90
  160. package/src/components/Notifications/Snackbar/SnackbarContainer/SnackbarContainer.test.tsx +0 -53
  161. package/src/components/Notifications/Snackbar/SnackbarItem/SnackbarItem.test.tsx +0 -77
  162. package/src/components/Notifications/Snackbar/SnackbarProvider/SnackbarProvider.test.tsx +0 -219
  163. package/src/components/Notifications/Snackbar/useSnackbar.test.tsx +0 -136
  164. package/src/components/Pagination/Pagination.test.tsx +0 -183
  165. package/src/components/Popover/Popover.test.tsx +0 -103
  166. package/src/components/ProgressBar/ProgressBar.test.tsx +0 -91
  167. package/src/components/Skeleton/Skeleton.test.tsx +0 -112
  168. package/src/components/StatusIndicator/StatusIndicator.test.tsx +0 -143
  169. package/src/components/Stepper/Stepper.test.tsx +0 -83
  170. package/src/components/Tabs/Tab.test.tsx +0 -49
  171. package/src/components/Tabs/TabButton.test.tsx +0 -65
  172. package/src/components/Tabs/Tabs.test.tsx +0 -291
  173. package/src/components/Tag/Tag.test.tsx +0 -89
  174. package/src/components/TextEllipsis/TextEllipsis.test.tsx +0 -96
  175. package/src/components/Tiles/Tile.test.tsx +0 -183
  176. package/src/components/Tiles/Tiles.test.tsx +0 -162
  177. package/src/components/Tooltip/Tooltip.test.tsx +0 -390
  178. package/src/components/Typography/Typography.test.tsx +0 -177
  179. package/src/components/Wizard/BaseWizardSteps/BaseWizardSteps.test.tsx +0 -90
  180. package/src/components/Wizard/Wizard.test.tsx +0 -218
  181. package/src/components/Wizard/WizardActions/WizardActions.test.tsx +0 -187
  182. package/src/components/Wizard/WizardSteps/WizardSteps.test.tsx +0 -125
  183. package/src/components/_BaseStyling_/BaseStyling.test.tsx +0 -55
  184. package/src/hooks/useAnimation.test.tsx +0 -65
  185. package/src/hooks/useBodyClick.test.tsx +0 -55
  186. package/src/hooks/useDebouncedCallback.test.ts +0 -150
  187. package/src/hooks/useDetermineStatusIcon.test.ts +0 -28
  188. package/src/hooks/useFormSelector.test.ts +0 -56
  189. package/src/hooks/usePosition.test.tsx +0 -510
  190. package/src/hooks/useRepeater.test.tsx +0 -156
  191. package/src/hooks/useScroll.test.tsx +0 -36
  192. package/src/hooks/useSpacing.test.ts +0 -86
  193. package/src/hooks/useUploadFile.test.ts +0 -211
  194. package/src/util/helper.test.tsx +0 -403
@@ -37,25 +37,41 @@ import { useDetermineStatusIcon } from "../../../hooks/useDetermineStatusIcon";
37
37
 
38
38
  type PartialInputProps = Partial<InputProps>;
39
39
 
40
+ interface SearchProps {
41
+ enabled?: boolean;
42
+ renderThreshold?: number;
43
+ searchPlaceholder?: string;
44
+ searchInputProps?: PartialInputProps & { reset?: boolean };
45
+ }
46
+
40
47
  export interface Props extends ComponentPropsWithRef<"select">, FormElement {
41
48
  children: ReactElement[];
42
49
  name?: string;
43
50
  labeledBy?: string;
44
51
  describedBy?: string;
45
52
  placeholder?: string;
53
+ /**
54
+ * @deprecated
55
+ */
46
56
  searchPlaceholder?: string;
57
+ /**
58
+ * @deprecated
59
+ */
47
60
  searchInputProps?: PartialInputProps & { reset?: boolean };
48
61
  selectButtonProps?: ComponentPropsWithRef<"button">;
62
+ search?: SearchProps;
49
63
  className?: string;
50
64
  value: string;
51
65
  clearLabel?: string;
52
66
  noResultsLabel?: string;
53
67
  onChange?: (event: React.ChangeEvent<HTMLSelectElement>, child?: ReactElement) => void;
68
+ addNew?: {
69
+ label: string;
70
+ onAddNew: (value: string) => void;
71
+ btnProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
72
+ };
54
73
  }
55
74
 
56
- /** Amount of items to be rendered before a search input is rendered */
57
- const renderSearchCondition = 10;
58
-
59
75
  const SelectComponent: ForwardRefRenderFunction<HTMLSelectElement, Props> = (
60
76
  {
61
77
  children,
@@ -74,6 +90,8 @@ const SelectComponent: ForwardRefRenderFunction<HTMLSelectElement, Props> = (
74
90
  clearLabel = "Clear selection",
75
91
  noResultsLabel = "No results found",
76
92
  onChange,
93
+ addNew,
94
+ search,
77
95
  ...rest
78
96
  }: Props,
79
97
  ref
@@ -85,14 +103,32 @@ const SelectComponent: ForwardRefRenderFunction<HTMLSelectElement, Props> = (
85
103
  const optionListReference = useRef<HTMLDivElement>(null);
86
104
  const [isSearching, setIsSearching] = useState(false);
87
105
  const [focusedSelectItem, setFocusedSelectItem] = useState(-1);
88
- const [shouldClick, setShouldClick] =
89
- useState(
90
- false
91
- ); /** We need this, because whenever we use the arrow keys to select the select item, and we focus the currently selected item it fires the "click" listener in Option component. Instead, we only want this to fire if we press "enter" or "spacebar" so we set this to true whenever that is the case, and back to false when it has been executed. */
106
+ const [shouldClick, setShouldClick] = useState(false);
107
+ /** We need this, because whenever we use the arrow keys to select the select item, and we focus the currently selected item it fires the "click" listener in Option component. Instead, we only want this to fire if we press "enter" or "spacebar" so we set this to true whenever that is the case, and back to false when it has been executed. */
92
108
  const [shouldFocusButtonAfterClose, setShouldFocusButtonAfterClose] = useState(false);
93
109
 
110
+ const DEFAULT_RENDER_THRESHOLD = 10;
111
+
94
112
  const nativeSelect = (ref as React.RefObject<HTMLSelectElement>) || createRef();
95
113
  const searchInputRef = useRef<HTMLInputElement>(null);
114
+ const addBtnRef = useRef<HTMLButtonElement>(null);
115
+
116
+ const addNewLabel = addNew?.label ?? "Create new";
117
+
118
+ const getRenderThreshold = search?.renderThreshold ?? DEFAULT_RENDER_THRESHOLD;
119
+ const hasEnoughChildren = Array.isArray(children) && children.length > getRenderThreshold;
120
+
121
+ const shouldRenderSearch = () => {
122
+ if (search?.enabled) {
123
+ return hasEnoughChildren;
124
+ }
125
+
126
+ if (search) {
127
+ return search.enabled as boolean;
128
+ }
129
+
130
+ return children.length > DEFAULT_RENDER_THRESHOLD;
131
+ };
96
132
 
97
133
  const onOptionChangeHandler = (optionElement: HTMLElement | null) => {
98
134
  if (nativeSelect.current && optionElement) {
@@ -114,11 +150,12 @@ const SelectComponent: ForwardRefRenderFunction<HTMLSelectElement, Props> = (
114
150
  childrenCount: React.Children.count(children),
115
151
  setShouldClick,
116
152
  searchInputRef,
117
- renderSearchCondition
153
+ addBtnRef,
154
+ renderThreshold: getRenderThreshold
118
155
  });
119
156
 
120
157
  const { listPosition, opacity, optionsListMaxHeight, setListPosition, setOpacity } =
121
- useSelectPositionList({ expanded, optionListReference, containerReference });
158
+ useSelectPositionList({ expanded, optionListReference, containerReference, addBtnRef });
122
159
 
123
160
  const syncDisplayValue = (val: string) => {
124
161
  React.Children.forEach(children, child => {
@@ -129,7 +166,7 @@ const SelectComponent: ForwardRefRenderFunction<HTMLSelectElement, Props> = (
129
166
  };
130
167
 
131
168
  /**
132
- * @description We have to modify the children (Option component) to have a additional props that allows us to keep track of which one is selected and focused at all times and if a filter is active.
169
+ * @description We have to modify the children (Option component) to have an additional props that allows us to keep track of which one is selected and focused at all times and if a filter is active.
133
170
  * The `children` prop can be either a single object (1 child) or an array of multiple children.
134
171
  */
135
172
  const renderOptions = () => {
@@ -165,32 +202,32 @@ const SelectComponent: ForwardRefRenderFunction<HTMLSelectElement, Props> = (
165
202
  selectOpened: expanded,
166
203
  childIndex: index,
167
204
  hasFocus: focusedSelectItem === index,
168
- shouldClick: shouldClick
205
+ shouldClick: shouldClick,
206
+ isAddBtnFocused: addBtnRef.current === document.activeElement
169
207
  });
170
208
  });
171
209
  }
172
210
  };
173
211
 
174
- const shouldRenderSearch =
175
- expanded && Array.isArray(children) && children.length > renderSearchCondition;
176
-
177
212
  const renderSearch = () => (
178
213
  <Input
179
- {...searchInputProps}
214
+ {...(search?.searchInputProps ?? searchInputProps)}
180
215
  ref={searchInputRef}
181
216
  onFocus={() => setIsSearching(true)}
182
217
  onBlur={() => setIsSearching(false)}
183
218
  onChange={filterResults}
184
219
  className={classes["select-search"]}
185
220
  wrapperProps={{
186
- className: searchInputProps?.wrapperProps?.className
221
+ className:
222
+ search?.searchInputProps?.wrapperProps?.className ??
223
+ searchInputProps?.wrapperProps?.className
187
224
  }}
188
225
  style={{
189
226
  display: expanded ? "block" : "none"
190
227
  }}
191
228
  type="text"
192
229
  name="search-option"
193
- placeholder={searchPlaceholder}
230
+ placeholder={search?.searchPlaceholder ?? searchPlaceholder}
194
231
  />
195
232
  );
196
233
 
@@ -245,8 +282,8 @@ const SelectComponent: ForwardRefRenderFunction<HTMLSelectElement, Props> = (
245
282
  };
246
283
 
247
284
  useEffect(() => {
248
- searchInputProps?.reset && resetSearchState();
249
- }, [searchInputProps?.reset]);
285
+ (search?.searchInputProps?.reset || searchInputProps?.reset) && resetSearchState();
286
+ }, [searchInputProps?.reset, search?.searchInputProps?.reset]);
250
287
 
251
288
  const additionalClasses = [];
252
289
  expanded && additionalClasses.push(classes.expanded);
@@ -280,7 +317,7 @@ const SelectComponent: ForwardRefRenderFunction<HTMLSelectElement, Props> = (
280
317
  className ?? ""
281
318
  }`}
282
319
  >
283
- {Array.isArray(children) && children.length > renderSearchCondition && renderSearch()}
320
+ {shouldRenderSearch() && renderSearch()}
284
321
  <button
285
322
  {...selectButtonProps}
286
323
  onClick={() => {
@@ -290,7 +327,7 @@ const SelectComponent: ForwardRefRenderFunction<HTMLSelectElement, Props> = (
290
327
  type="button"
291
328
  name={name}
292
329
  className={`${classes["custom-select"]} ${additionalClasses.join(" ")} `}
293
- style={{ display: shouldRenderSearch ? "none" : "initial" }}
330
+ style={{ display: expanded && shouldRenderSearch() ? "none" : "initial" }}
294
331
  disabled={disabled}
295
332
  aria-disabled={disabled}
296
333
  aria-invalid={error}
@@ -317,11 +354,25 @@ const SelectComponent: ForwardRefRenderFunction<HTMLSelectElement, Props> = (
317
354
  ...listPosition
318
355
  }}
319
356
  >
320
- <ul role="listbox">{renderOptions()}</ul>
357
+ <ul className={addNew && classes["has-sibling"]} role="listbox">
358
+ {renderOptions()}
359
+ </ul>
360
+ {addNew && (
361
+ <button
362
+ data-testid={"select-action-button"}
363
+ className={classes["action-button"]}
364
+ onClick={() => addNew.onAddNew(filter)}
365
+ ref={addBtnRef}
366
+ {...addNew.btnProps}
367
+ >
368
+ {!filter && addNewLabel}
369
+ {filter && <span style={{ fontWeight: "700" }}>{`"${filter}"`}</span>}
370
+ {filter && ` (${addNewLabel.toLowerCase()})`}
371
+ </button>
372
+ )}
321
373
  </div>
322
374
  </div>
323
375
  </Fragment>
324
376
  );
325
377
  };
326
-
327
378
  export const Select = React.forwardRef(SelectComponent);
@@ -21,6 +21,7 @@ import {
21
21
  UseSelectPositionListParams
22
22
  } from "./Select.interfaces";
23
23
 
24
+ /** @scope . */
24
25
  export const useArrowNavigation = ({
25
26
  expanded,
26
27
  setExpanded,
@@ -31,7 +32,8 @@ export const useArrowNavigation = ({
31
32
  childrenCount,
32
33
  setShouldClick,
33
34
  searchInputRef,
34
- renderSearchCondition
35
+ addBtnRef,
36
+ renderThreshold
35
37
  }: UseArrowNavigationParams) => {
36
38
  const onArrowNavigation = (event: React.KeyboardEvent) => {
37
39
  const codesToPreventDefault = [
@@ -54,12 +56,16 @@ export const useArrowNavigation = ({
54
56
  "MetaRight"
55
57
  ];
56
58
 
57
- /** If the select is expanded, we also want to control the Tab key */
59
+ const isAddBtnFocused = addBtnRef?.current === document.activeElement;
60
+
58
61
  if (expanded) {
59
62
  codesToPreventDefault.push("Tab");
60
63
  }
61
64
 
62
- /** We will handle the way certain key strokes affect the Select, unless we're searching */
65
+ if (addBtnRef) {
66
+ codesToPreventDefaultWhenSearching.push("Tab");
67
+ }
68
+
63
69
  if (codesToPreventDefault.includes(event.code) && !event.metaKey && !isSearching) {
64
70
  event.preventDefault();
65
71
  }
@@ -80,7 +86,14 @@ export const useArrowNavigation = ({
80
86
  setFocusedSelectItem(childrenCount - 1);
81
87
  return;
82
88
  case "Escape":
89
+ setIsSearching(false);
90
+ setExpanded(false);
91
+ return;
83
92
  case "Tab":
93
+ if (addBtnRef?.current) {
94
+ addBtnRef.current.focus();
95
+ return;
96
+ }
84
97
  setIsSearching(false);
85
98
  setExpanded(false);
86
99
  }
@@ -125,10 +138,13 @@ export const useArrowNavigation = ({
125
138
 
126
139
  return;
127
140
  case "Tab":
128
- if (childrenCount >= renderSearchCondition && expanded) {
141
+ if (childrenCount >= renderThreshold && expanded && !isAddBtnFocused) {
129
142
  setIsSearching(true);
130
143
  searchInputRef.current?.focus();
131
144
  return;
145
+ } else if (addBtnRef?.current && expanded && !isAddBtnFocused) {
146
+ addBtnRef.current.focus();
147
+ return;
132
148
  }
133
149
  setExpanded(false);
134
150
 
@@ -163,10 +179,11 @@ export const useArrowNavigation = ({
163
179
  export const useSelectPositionList = ({
164
180
  expanded,
165
181
  optionListReference,
182
+ addBtnRef,
166
183
  containerReference
167
184
  }: UseSelectPositionListParams) => {
168
185
  const [optionsListMaxHeight, setOptionsListMaxHeight] = useState("none");
169
- const [opacity, setOpacity] = useState(0); // We set opacity because other wise if we calculate the max height you see the list full height for a split second and then it shortens.
186
+ const [opacity, setOpacity] = useState(0); // We set opacity because otherwise if we calculate the max height you see the list full height for a split second and then it shortens.
170
187
  const [listPosition, setListPosition] = useState<Partial<Position>>({});
171
188
 
172
189
  useEffect(() => {
@@ -203,6 +220,7 @@ export const useSelectPositionList = ({
203
220
  const calculateOptionListMaxHeight = (position: Position) => {
204
221
  // Calculate max height if there's more space below the select
205
222
  const listHeight = optionListReference.current?.getBoundingClientRect().height;
223
+ const addNewButtonHeight = addBtnRef.current?.getBoundingClientRect().height ?? 0;
206
224
  const transformOrigin = position.top !== "initial" ? "top" : "bottom";
207
225
 
208
226
  if (!containerReference.current) {
@@ -214,12 +232,10 @@ export const useSelectPositionList = ({
214
232
 
215
233
  const availableSpace =
216
234
  transformOrigin === "top"
217
- ? window.innerHeight -
218
- containerReference.current.getBoundingClientRect()[transformOrigin] -
219
- 16
220
- : containerReference.current.getBoundingClientRect()[transformOrigin] - 16;
235
+ ? window.innerHeight - containerReference.current.getBoundingClientRect().bottom - 16
236
+ : containerReference.current.getBoundingClientRect().top - 16;
221
237
 
222
- if (listHeight && availableSpace < listHeight) {
238
+ if (listHeight && availableSpace < listHeight + addNewButtonHeight) {
223
239
  setOptionsListMaxHeight(`${availableSpace}px`);
224
240
  setOpacity(100);
225
241
  return;
@@ -39,6 +39,10 @@
39
39
  &:focus-visible {
40
40
  outline: 0.5px dashed var(--color-primary);
41
41
  }
42
+
43
+ &:visited {
44
+ color: var(--button-primary-pressed-color);
45
+ }
42
46
  }
43
47
 
44
48
  &.success {
@@ -47,6 +51,10 @@
47
51
  &:focus-visible {
48
52
  outline: 0.5px dashed var(--color-primary);
49
53
  }
54
+
55
+ &:visited {
56
+ color: var(--button-success-pressed-color);
57
+ }
50
58
  }
51
59
 
52
60
  &.danger {
@@ -55,6 +63,10 @@
55
63
  &:focus-visible {
56
64
  outline: 0.5px dashed var(--color-primary);
57
65
  }
66
+
67
+ &:visited {
68
+ color: var(--button-danger-pressed-color);
69
+ }
58
70
  }
59
71
 
60
72
  &.warning {
@@ -63,6 +75,10 @@
63
75
  &:focus-visible {
64
76
  outline: 0.5px dashed var(--color-primary);
65
77
  }
78
+
79
+ &:visited {
80
+ color: var(--button-warning-pressed-color);
81
+ }
66
82
  }
67
83
 
68
84
  &.disabled {
@@ -23,7 +23,7 @@ import React, {
23
23
  useRef,
24
24
  useState
25
25
  } from "react";
26
- import { Offset, Placement, usePosition } from "../../hooks/usePosition";
26
+ import { Offset, Placement, PositionType, usePosition } from "../../hooks/usePosition";
27
27
  import classes from "./Popover.module.scss";
28
28
 
29
29
  export interface Props extends ComponentPropsWithRef<"div"> {
@@ -63,6 +63,18 @@ const PopoverComponent: ForwardRefRenderFunction<HTMLDivElement, Props> = (
63
63
  debounceAmount: debounceAmount ?? undefined
64
64
  });
65
65
 
66
+ const normalizePosition = (value: PositionType) => {
67
+ if (value === "initial") {
68
+ return "initial";
69
+ }
70
+
71
+ if (isNaN(value)) {
72
+ return 0;
73
+ }
74
+
75
+ return Number(value);
76
+ };
77
+
66
78
  useEffect(() => {
67
79
  if (initialCalculationDone) {
68
80
  setShowPopover(!!show);
@@ -114,7 +126,12 @@ const PopoverComponent: ForwardRefRenderFunction<HTMLDivElement, Props> = (
114
126
  <div
115
127
  ref={elToBePositioned}
116
128
  className={`${classes.popover} ${className ?? ""} ${showPopover ? classes.show : ""}`}
117
- style={{ top: top, left: left, right: right, bottom: bottom }}
129
+ style={{
130
+ top: normalizePosition(top),
131
+ left: normalizePosition(left),
132
+ right: normalizePosition(right),
133
+ bottom: normalizePosition(bottom)
134
+ }}
118
135
  >
119
136
  {children}
120
137
  </div>
@@ -17,7 +17,6 @@
17
17
  import React, { CSSProperties, ComponentPropsWithRef, ForwardRefRenderFunction } from "react";
18
18
  import { Icon, Icons } from "../Icon/Icon";
19
19
  import classes from "./Step.module.scss";
20
- import { gapBetweenStepsInRem } from "./Stepper";
21
20
 
22
21
  export type StepStatus = "waiting" | "current" | "done" | "error";
23
22
 
@@ -33,6 +32,8 @@ export interface Props extends Omit<ComponentPropsWithRef<"div">, "onClick"> {
33
32
  onClick?: (event: React.MouseEvent<HTMLButtonElement | HTMLDivElement>) => void;
34
33
  }
35
34
 
35
+ const gapBetweenStepsInRem = 0.5;
36
+
36
37
  const getStepContent = (index: number, status: StepStatus) => {
37
38
  switch (status) {
38
39
  case "waiting":
@@ -24,8 +24,6 @@ export interface Props extends ComponentPropsWithRef<"div"> {
24
24
  textPosition?: "bottom" | "right";
25
25
  }
26
26
 
27
- export const gapBetweenStepsInRem = 0.5;
28
-
29
27
  export const Stepper = ({
30
28
  steps,
31
29
  direction = "horizontal",
@@ -174,6 +174,7 @@
174
174
  transform-origin: center center;
175
175
  pointer-events: none;
176
176
  box-shadow: 0 0.0625rem 0.3125rem 0 rgba(1, 5, 50, 0.3);
177
+ word-break: break-all;
177
178
 
178
179
  @include mixins.transition($transition-property, 0.2s, ease-in-out);
179
180
 
@@ -84,7 +84,7 @@ interface Dimensions {
84
84
  width: number;
85
85
  }
86
86
 
87
- type PositionType = number | "initial";
87
+ export type PositionType = number | "initial";
88
88
 
89
89
  const defaultConfigObject: ConfigObject = {
90
90
  relativeElement: undefined,
package/src/interfaces.ts CHANGED
@@ -32,6 +32,6 @@ export type DeepPartial<T> = {
32
32
  [P in keyof T]?: T[P] extends (infer U)[]
33
33
  ? DeepPartial<U>[]
34
34
  : T[P] extends ReadonlyArray<infer U>
35
- ? ReadonlyArray<DeepPartial<U>>
36
- : DeepPartial<T[P]>;
35
+ ? ReadonlyArray<DeepPartial<U>>
36
+ : DeepPartial<T[P]>;
37
37
  };
@@ -18,12 +18,12 @@
18
18
 
19
19
  @mixin button($variant: "text", $type: "default") {
20
20
  $disabledSelector: if($type == "link", ".disabled", ":disabled");
21
+ font-weight: 500;
21
22
 
22
23
  @if $variant == "text" {
23
24
  border-color: transparent;
24
25
  background-color: transparent;
25
26
  } @else if $variant == "fill" {
26
- font-weight: 500;
27
27
  &:not(#{$disabledSelector}) {
28
28
  color: var(--button-fill-text-color);
29
29
  @include buttonStyles(
@@ -37,7 +37,6 @@
37
37
  }
38
38
  } @else if $variant == "outline" {
39
39
  background-color: var(--white);
40
- font-weight: 500;
41
40
  }
42
41
 
43
42
  &.primary:not(#{$disabledSelector}) {
@@ -145,8 +144,7 @@
145
144
  z-index: 2;
146
145
  }
147
146
 
148
- &:active:not(.disabled),
149
- &:visited:not(.disabled) {
147
+ &:active:not(.disabled) {
150
148
  @if $variant == "fill" {
151
149
  background-color: $pressedColor;
152
150
  border-color: $pressedColor;
@@ -1,64 +0,0 @@
1
- /*
2
- * Copyright 2022 OneWelcome B.V.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
-
17
- import React from "react";
18
- import { Breadcrumbs, Props } from "./Breadcrumbs";
19
- import { render } from "@testing-library/react";
20
- import { Link } from "../Link/Link";
21
-
22
- const defaultParams: Props = {
23
- ariaLabel: "Breadcrumbs",
24
- children: [
25
- <Link key="0" to="#1" data-testid="link">
26
- Test1
27
- </Link>,
28
- <Link key="1" to="#2" data-testid="link">
29
- Test2
30
- </Link>
31
- ]
32
- };
33
-
34
- const createBreadcrumbs = (params?: (defaultParams: Props) => Props) => {
35
- let parameters: Props = defaultParams;
36
- if (params) {
37
- parameters = params(defaultParams);
38
- }
39
- const queries = render(<Breadcrumbs {...parameters} data-testid="breadcrumbs"></Breadcrumbs>);
40
- const breadcrumbs = queries.getByTestId("breadcrumbs");
41
-
42
- return {
43
- ...queries,
44
- breadcrumbs
45
- };
46
- };
47
-
48
- describe("Breadcrumbs should render", () => {
49
- it("renders without crashing", () => {
50
- const { breadcrumbs } = createBreadcrumbs();
51
-
52
- expect(breadcrumbs).toBeDefined();
53
- expect(breadcrumbs.firstChild).not.toHaveAttribute("aria-current");
54
- if (breadcrumbs.firstChild) {
55
- const homeIcon = (breadcrumbs.firstChild as HTMLElement).querySelector(
56
- "[class*='icon-home-filled']"
57
- );
58
- expect(homeIcon).toBeInTheDocument();
59
- }
60
- expect((breadcrumbs.firstChild as HTMLElement).tagName).toEqual("A");
61
- expect(breadcrumbs.lastChild).toHaveAttribute("aria-current", "page");
62
- expect((breadcrumbs.lastChild as HTMLElement).tagName).toEqual("SPAN");
63
- });
64
- });
@@ -1,133 +0,0 @@
1
- /*
2
- * Copyright 2022 OneWelcome B.V.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
-
17
- import React, { useEffect, useRef } from "react";
18
- import { BaseButton, Props } from "./BaseButton";
19
- import { render } from "@testing-library/react";
20
- import userEvent from "@testing-library/user-event";
21
-
22
- const defaultParams: Props = {};
23
-
24
- const createBaseButton = (params?: (defaultParams: Props) => Props) => {
25
- let parameters: Props = defaultParams;
26
- if (params) {
27
- parameters = params(defaultParams);
28
- }
29
- const queries = render(
30
- <BaseButton {...parameters} data-testid="baseButton">
31
- baseButton content
32
- </BaseButton>
33
- );
34
- const baseButton = queries.getByTestId("baseButton");
35
-
36
- return {
37
- ...queries,
38
- baseButton
39
- };
40
- };
41
-
42
- describe("BaseButton should render", () => {
43
- it("renders without crashing", () => {
44
- const { baseButton } = createBaseButton();
45
-
46
- expect(baseButton).toBeDefined();
47
- expect(baseButton).toHaveTextContent("baseButton content");
48
- });
49
- });
50
-
51
- describe("On click handler", () => {
52
- it("executes the onclick handler", async () => {
53
- const onClickHandler = jest.fn();
54
- const { baseButton } = createBaseButton(defaultParams => ({
55
- ...defaultParams,
56
- onClick: onClickHandler
57
- }));
58
-
59
- await userEvent.click(baseButton);
60
-
61
- expect(onClickHandler).toBeCalled();
62
- });
63
- });
64
-
65
- describe("Properties of the button", () => {
66
- it("should be disabled, function should not have been called", async () => {
67
- const onClickHandler = jest.fn();
68
- const { baseButton } = createBaseButton(defaultParams => ({
69
- ...defaultParams,
70
- disabled: true,
71
- onClick: onClickHandler
72
- }));
73
-
74
- await userEvent.click(baseButton);
75
- expect(onClickHandler).toHaveBeenCalledTimes(0);
76
- });
77
-
78
- it("when loading onClick function should not have been called", async () => {
79
- const onClickHandler = jest.fn();
80
- const { baseButton } = createBaseButton(defaultParams => ({
81
- ...defaultParams,
82
- loading: true,
83
- onClick: onClickHandler
84
- }));
85
-
86
- await userEvent.click(baseButton);
87
- expect(onClickHandler).toHaveBeenCalledTimes(0);
88
- });
89
-
90
- it('should have the class "TESTING"', () => {
91
- const { baseButton } = createBaseButton(defaultParams => ({
92
- ...defaultParams,
93
- className: "TESTING"
94
- }));
95
-
96
- expect(baseButton).toHaveClass("TESTING");
97
- });
98
-
99
- it('should have a "name" property with the value "button"', () => {
100
- const { baseButton } = createBaseButton(defaultParams => ({
101
- ...defaultParams,
102
- name: "button"
103
- }));
104
-
105
- expect(baseButton).toHaveAttribute("name", "button");
106
- });
107
- });
108
-
109
- describe("ref should work", () => {
110
- it("should give back the proper data prop, this also checks if the component propagates ...rest properly", () => {
111
- const ExampleComponent = ({
112
- propagateRef
113
- }: {
114
- propagateRef?: (ref: React.RefObject<HTMLElement>) => void;
115
- }) => {
116
- const ref = useRef(null);
117
-
118
- useEffect(() => {
119
- if (ref.current) {
120
- propagateRef && propagateRef(ref);
121
- }
122
- }, [ref]);
123
-
124
- return <BaseButton {...defaultParams} data-ref="testing" ref={ref} />;
125
- };
126
-
127
- const refCheck = (ref: React.RefObject<HTMLElement>) => {
128
- expect(ref.current).toHaveAttribute("data-ref", "testing");
129
- };
130
-
131
- render(<ExampleComponent propagateRef={refCheck} />);
132
- });
133
- });