@onewelcome/react-lib-components 0.1.6-alpha → 0.1.9-alpha

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 (102) hide show
  1. package/dist/Button/IconButton.d.ts +2 -1
  2. package/dist/ContextMenu/ContextMenu.d.ts +2 -3
  3. package/dist/ContextMenu/ContextMenuItem.d.ts +10 -3
  4. package/dist/DataGrid/DataGrid.d.ts +32 -0
  5. package/dist/DataGrid/DataGridActions/DataGridActions.d.ts +14 -0
  6. package/dist/DataGrid/DataGridActions/DataGridColumnsToggle.d.ts +13 -0
  7. package/dist/DataGrid/DataGridBody/DataGridBody.d.ts +17 -0
  8. package/dist/DataGrid/DataGridBody/DataGridCell.d.ts +10 -0
  9. package/dist/DataGrid/DataGridBody/DataGridRow.d.ts +9 -0
  10. package/dist/DataGrid/DataGridHeader/DataGridHeader.d.ts +11 -0
  11. package/dist/DataGrid/DataGridHeader/DataGridHeaderCell.d.ts +10 -0
  12. package/dist/DataGrid/datagrid.interfaces.d.ts +13 -0
  13. package/dist/Form/Checkbox/Checkbox.d.ts +2 -2
  14. package/dist/Form/Select/Option.d.ts +9 -4
  15. package/dist/Form/Select/Select.d.ts +8 -2
  16. package/dist/Form/Toggle/Toggle.d.ts +1 -1
  17. package/dist/Form/Wrapper/SelectWrapper/SelectWrapper.d.ts +1 -1
  18. package/dist/Icon/Icon.d.ts +1 -0
  19. package/dist/Link/Link.d.ts +4 -3
  20. package/dist/Notifications/BaseModal/BaseModal.d.ts +4 -2
  21. package/dist/Notifications/SlideInModal/SlideInModal.d.ts +4 -0
  22. package/dist/StatusIndicator/StatusIndicator.d.ts +9 -0
  23. package/dist/_BaseStyling_/BaseStyling.d.ts +4 -0
  24. package/dist/index.d.ts +48 -43
  25. package/dist/react-lib-components.cjs.development.js +3097 -2157
  26. package/dist/react-lib-components.cjs.development.js.map +1 -1
  27. package/dist/react-lib-components.cjs.production.min.js +1 -1
  28. package/dist/react-lib-components.cjs.production.min.js.map +1 -1
  29. package/dist/react-lib-components.esm.js +3094 -2158
  30. package/dist/react-lib-components.esm.js.map +1 -1
  31. package/package.json +11 -13
  32. package/src/Button/BaseButton.module.scss +3 -18
  33. package/src/Button/Button.module.scss +4 -311
  34. package/src/Button/IconButton.module.scss +21 -128
  35. package/src/Button/IconButton.test.tsx +24 -0
  36. package/src/Button/IconButton.tsx +6 -1
  37. package/src/ContextMenu/ContextMenu.test.tsx +121 -6
  38. package/src/ContextMenu/ContextMenu.tsx +99 -6
  39. package/src/ContextMenu/ContextMenuItem.tsx +57 -9
  40. package/src/DataGrid/DataGrid.module.scss +25 -0
  41. package/src/DataGrid/DataGrid.test.tsx +421 -0
  42. package/src/DataGrid/DataGrid.tsx +157 -0
  43. package/src/DataGrid/DataGridActions/DataGridActions.module.scss +35 -0
  44. package/src/DataGrid/DataGridActions/DataGridActions.test.tsx +184 -0
  45. package/src/DataGrid/DataGridActions/DataGridActions.tsx +109 -0
  46. package/src/DataGrid/DataGridActions/DataGridColumnsToggle.module.scss +41 -0
  47. package/src/DataGrid/DataGridActions/DataGridColumnsToggle.test.tsx +83 -0
  48. package/src/DataGrid/DataGridActions/DataGridColumnsToggle.tsx +88 -0
  49. package/src/DataGrid/DataGridBody/DataGridBody.module.scss +10 -0
  50. package/src/DataGrid/DataGridBody/DataGridBody.test.tsx +123 -0
  51. package/src/DataGrid/DataGridBody/DataGridBody.tsx +80 -0
  52. package/src/DataGrid/DataGridBody/DataGridCell.module.scss +33 -0
  53. package/src/DataGrid/DataGridBody/DataGridCell.test.tsx +74 -0
  54. package/src/DataGrid/DataGridBody/DataGridCell.tsx +58 -0
  55. package/src/DataGrid/DataGridBody/DataGridRow.module.scss +7 -0
  56. package/src/DataGrid/DataGridBody/DataGridRow.test.tsx +101 -0
  57. package/src/DataGrid/DataGridBody/DataGridRow.tsx +42 -0
  58. package/src/DataGrid/DataGridBody/__snapshots__/DataGridBody.test.tsx.snap +258 -0
  59. package/src/DataGrid/DataGridHeader/DataGridHeader.module.scss +26 -0
  60. package/src/DataGrid/DataGridHeader/DataGridHeader.test.tsx +255 -0
  61. package/src/DataGrid/DataGridHeader/DataGridHeader.tsx +103 -0
  62. package/src/DataGrid/DataGridHeader/DataGridHeaderCell.module.scss +68 -0
  63. package/src/DataGrid/DataGridHeader/DataGridHeaderCell.test.tsx +128 -0
  64. package/src/DataGrid/DataGridHeader/DataGridHeaderCell.tsx +72 -0
  65. package/src/DataGrid/datagrid.interfaces.ts +14 -0
  66. package/src/Form/Checkbox/Checkbox.test.tsx +144 -8
  67. package/src/Form/Checkbox/Checkbox.tsx +8 -8
  68. package/src/Form/Select/Option.tsx +39 -21
  69. package/src/Form/Select/Select.module.scss +1 -1
  70. package/src/Form/Select/Select.test.tsx +235 -56
  71. package/src/Form/Select/Select.tsx +194 -34
  72. package/src/Form/Toggle/Toggle.module.scss +1 -0
  73. package/src/Form/Toggle/Toggle.tsx +1 -1
  74. package/src/Form/Wrapper/CheckboxWrapper/CheckboxWrapper.test.tsx +1 -1
  75. package/src/Form/Wrapper/SelectWrapper/SelectWrapper.test.tsx +44 -0
  76. package/src/Form/Wrapper/SelectWrapper/SelectWrapper.tsx +4 -2
  77. package/src/Form/Wrapper/Wrapper/Wrapper.module.scss +1 -0
  78. package/src/Icon/Icon.module.scss +4 -0
  79. package/src/Icon/Icon.tsx +1 -0
  80. package/src/Link/Link.module.scss +20 -0
  81. package/src/Link/Link.test.tsx +33 -0
  82. package/src/Link/Link.tsx +8 -2
  83. package/src/Notifications/BaseModal/BaseModal.module.scss +1 -1
  84. package/src/Notifications/BaseModal/BaseModal.test.tsx +77 -12
  85. package/src/Notifications/BaseModal/BaseModal.tsx +27 -6
  86. package/src/Notifications/Dialog/Dialog.module.scss +1 -1
  87. package/src/Notifications/Dialog/Dialog.tsx +1 -1
  88. package/src/Notifications/SlideInModal/SlideInModal.module.scss +36 -0
  89. package/src/Notifications/SlideInModal/SlideInModal.test.tsx +69 -0
  90. package/src/Notifications/SlideInModal/SlideInModal.tsx +31 -0
  91. package/src/Notifications/Snackbar/SnackbarContainer/SnackbarContainer.module.scss +1 -1
  92. package/src/Notifications/Snackbar/SnackbarItem/SnackbarItem.module.scss +1 -1
  93. package/src/Pagination/Pagination.module.scss +74 -74
  94. package/src/StatusIndicator/StatusIndicator.module.scss +27 -0
  95. package/src/StatusIndicator/StatusIndicator.test.tsx +127 -0
  96. package/src/StatusIndicator/StatusIndicator.tsx +25 -0
  97. package/src/Tiles/Tile.module.scss +1 -1
  98. package/src/Tiles/Tile.test.tsx +4 -4
  99. package/src/_BaseStyling_/BaseStyling.tsx +14 -6
  100. package/src/index.ts +85 -48
  101. package/src/mixins.module.scss +171 -0
  102. package/src/readyclasses.module.scss +0 -30
@@ -2,20 +2,22 @@ import classes from './Select.module.scss';
2
2
 
3
3
  import React, {
4
4
  ComponentPropsWithRef,
5
+ createRef,
5
6
  Fragment,
6
7
  ReactElement,
7
- RefObject,
8
8
  useEffect,
9
9
  useRef,
10
10
  useState,
11
11
  } from 'react';
12
- import { Input } from '../Input/Input';
12
+ import { Input, Props as InputProps } from '../Input/Input';
13
13
  import { Icon, Icons } from '../../Icon/Icon';
14
14
  import { FormElement } from '../form.interfaces';
15
15
  import { useBodyClick } from '../../hooks/useBodyClick';
16
16
  import readyclasses from '../../readyclasses.module.scss';
17
17
  import { filterProps } from '../../util/helper';
18
18
 
19
+ type PartialInputProps = Partial<InputProps>;
20
+
19
21
  export interface Props extends ComponentPropsWithRef<'select'>, FormElement {
20
22
  children: ReactElement[];
21
23
  name?: string;
@@ -23,10 +25,13 @@ export interface Props extends ComponentPropsWithRef<'select'>, FormElement {
23
25
  describedBy?: string;
24
26
  placeholder?: string;
25
27
  searchPlaceholder?: string;
28
+ searchInputProps?: PartialInputProps;
29
+ selectButtonProps?: ComponentPropsWithRef<'button'>;
26
30
  className?: string;
27
31
  value: string;
32
+ clearLabel?: string;
28
33
  onChange?: (event: React.ChangeEvent<HTMLSelectElement>, child?: ReactElement) => void;
29
- onClear?: (event: React.MouseEvent<HTMLDivElement>) => void;
34
+ onClear?: (event: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>) => void;
30
35
  }
31
36
 
32
37
  type Position = {
@@ -34,6 +39,9 @@ type Position = {
34
39
  bottom: 0 | 'initial';
35
40
  };
36
41
 
42
+ /** Amount of items to be rendered before a search input is rendered */
43
+ const renderSearchCondition = 10;
44
+
37
45
  export const Select = React.forwardRef<HTMLSelectElement, Props>(
38
46
  (
39
47
  {
@@ -44,9 +52,12 @@ export const Select = React.forwardRef<HTMLSelectElement, Props>(
44
52
  placeholder,
45
53
  describedBy,
46
54
  searchPlaceholder = 'Search item',
55
+ searchInputProps,
56
+ selectButtonProps,
47
57
  className,
48
58
  error = false,
49
59
  value,
60
+ clearLabel = 'Clear selection',
50
61
  onChange,
51
62
  onClear,
52
63
  ...rest
@@ -61,8 +72,115 @@ export const Select = React.forwardRef<HTMLSelectElement, Props>(
61
72
  const [optionsListMaxHeight, setOptionsListMaxHeight] = useState('none');
62
73
  const containerReference = useRef<HTMLDivElement>(null);
63
74
  const optionListReference = useRef<HTMLDivElement>(null);
75
+ const [isSearching, setIsSearching] = useState(false);
76
+ const [focusedSelectItem, setFocusedSelectItem] = useState(-1);
77
+ const [shouldClick, setShouldClick] =
78
+ useState(
79
+ false
80
+ ); /** 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. */
81
+ const [childrenCount] = useState(React.Children.count(children));
82
+
83
+ const nativeSelect = (ref as React.RefObject<HTMLSelectElement>) || createRef();
84
+ const searchInputRef = useRef<HTMLInputElement>(null);
85
+
86
+ const onArrowNavigation = (event: React.KeyboardEvent) => {
87
+ const codesToPreventDefault = [
88
+ 'ArrowDown',
89
+ 'ArrowUp',
90
+ 'ArrowLeft',
91
+ 'ArrowRight',
92
+ 'Space',
93
+ 'Escape',
94
+ 'End',
95
+ 'Home',
96
+ ];
97
+
98
+ const codesToPreventDefaultWhenSearching = ['ArrowDown', 'ArrowUp', 'Enter', 'Escape'];
99
+
100
+ /** If the select is expanded, we also want to control the Tab key */
101
+ if (expanded) {
102
+ codesToPreventDefault.push('Tab');
103
+ }
104
+
105
+ /** We will handle the way certain key strokes affect the Select, unless we're searching */
106
+ if (codesToPreventDefault.includes(event.code) && !isSearching) {
107
+ event.preventDefault();
108
+ }
109
+
110
+ if (isSearching && codesToPreventDefaultWhenSearching.includes(event.code)) {
111
+ event.preventDefault();
112
+ }
113
+
114
+ if (isSearching) {
115
+ switch (event.code) {
116
+ case 'ArrowDown':
117
+ case 'Enter':
118
+ setIsSearching(false);
119
+ setFocusedSelectItem(0);
120
+ return;
121
+ case 'ArrowUp':
122
+ setIsSearching(false);
123
+ setFocusedSelectItem(childrenCount - 1);
124
+ return;
125
+ case 'Escape':
126
+ case 'Tab':
127
+ setIsSearching(false);
128
+ setExpanded(false);
129
+ containerReference.current &&
130
+ containerReference.current.querySelector('button')!.focus();
131
+ }
132
+ } else {
133
+ switch (event.code) {
134
+ case 'ArrowDown':
135
+ if (!expanded) {
136
+ setExpanded(true);
137
+ return;
138
+ }
139
+ setFocusedSelectItem((prevState) => {
140
+ return prevState + 1 > childrenCount - 1 ? 0 : prevState + 1;
141
+ });
142
+ return;
143
+ case 'ArrowUp':
144
+ setFocusedSelectItem((prevState) => {
145
+ return prevState - 1 < 0 ? childrenCount - 1 : prevState - 1;
146
+ });
147
+ return;
148
+ case 'Space':
149
+ if (!expanded) {
150
+ setExpanded(true);
151
+ return;
152
+ }
153
+
154
+ setShouldClick(true);
155
+ setExpanded(false);
156
+ containerReference.current &&
157
+ containerReference.current.querySelector('button')!.focus();
158
+ return;
159
+ case 'Tab':
160
+ if (childrenCount >= renderSearchCondition && expanded) {
161
+ setIsSearching(true);
162
+ searchInputRef.current && searchInputRef.current.focus();
163
+ return;
164
+ }
165
+ setExpanded(false);
64
166
 
65
- const nativeSelect = useRef<HTMLSelectElement>(null);
167
+ return;
168
+ case 'Escape':
169
+ if (expanded) {
170
+ setExpanded(false);
171
+ containerReference.current &&
172
+ containerReference.current.querySelector('button')!.focus();
173
+ }
174
+ return;
175
+ case 'End':
176
+ setFocusedSelectItem(childrenCount - 1);
177
+ return;
178
+ case 'Home':
179
+ setFocusedSelectItem(0);
180
+ return;
181
+ }
182
+ }
183
+ };
66
184
 
67
185
  const syncDisplayValue = (val: string) => {
68
186
  React.Children.forEach(children, (child) => {
@@ -121,39 +239,65 @@ export const Select = React.forwardRef<HTMLSelectElement, Props>(
121
239
  setOpacity(100);
122
240
  };
123
241
 
124
- const onOptionChangeHandler = (event: React.MouseEvent<HTMLLIElement>) => {
125
- // We need to set value and the fire change event. If a custom ref has been given we pass that value, otherwise we use the ref we've created ourselves when the component was instantiated.
126
- if (nativeSelect.current) {
127
- nativeSelect.current.value = event.currentTarget.dataset.value!;
242
+ const onOptionChangeHandler = (optionRef: React.RefObject<HTMLLIElement>) => {
243
+ if (nativeSelect.current && optionRef.current) {
244
+ nativeSelect.current.value = optionRef.current.getAttribute('data-value')!;
128
245
  nativeSelect.current.dispatchEvent(new Event('change', { bubbles: true }));
129
- } else if (ref) {
130
- (ref as RefObject<HTMLSelectElement>).current!.value = event.currentTarget.dataset.value!;
131
- (ref as RefObject<HTMLSelectElement>).current!.dispatchEvent(
132
- new Event('change', { bubbles: true })
133
- );
134
246
  }
247
+
135
248
  setExpanded(false);
249
+
250
+ containerReference.current && containerReference.current.querySelector('button')!.focus();
136
251
  };
137
252
 
138
253
  /**
139
- * @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 at all times and if a filter is active.
254
+ * @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.
140
255
  * The `children` prop can be either a single object (1 child) or an array of multiple children.
141
256
  */
142
- const renderOptions = () =>
143
- React.Children.map(children, (child) =>
144
- React.cloneElement(child, {
145
- onOptionSelect: onOptionChangeHandler,
146
- selected: child.props.value === value,
147
- filter: filter,
148
- })
149
- );
257
+ const renderOptions = () => {
258
+ if (isSearching || filter !== '') {
259
+ const filteredChildren = React.Children.toArray(children).filter(
260
+ (child) =>
261
+ (child as ReactElement).props.children.toLowerCase().match(filter.toLowerCase()) !==
262
+ null
263
+ );
264
+
265
+ return _internalRenderChildren(filteredChildren as ReactElement[]);
266
+ }
267
+
268
+ return _internalRenderChildren(children);
269
+
270
+ function _internalRenderChildren(internalChildren: ReactElement[]) {
271
+ return React.Children.map(internalChildren, (child, index) => {
272
+ return React.cloneElement(child, {
273
+ onFocusChange: (childIndex: number) => setFocusedSelectItem(childIndex),
274
+ onOptionSelect: (optionRef: React.RefObject<HTMLLIElement>) => {
275
+ onOptionChangeHandler(optionRef);
276
+ setShouldClick(false);
277
+ },
278
+ isSelected: child.props.value === value,
279
+ isSearching: isSearching,
280
+ selectOpened: expanded,
281
+ childIndex: index,
282
+ hasFocus: focusedSelectItem === index,
283
+ shouldClick: shouldClick,
284
+ });
285
+ });
286
+ }
287
+ };
150
288
 
151
289
  const renderSearch = () => (
152
290
  <Input
291
+ {...searchInputProps}
153
292
  autoFocus
293
+ ref={searchInputRef}
294
+ onFocus={() => setIsSearching(true)}
295
+ onBlur={() => setIsSearching(false)}
154
296
  onChange={filterResults}
155
297
  className={classes['select-search']}
156
- wrapperProps={{ className: classes['select-search-wrapper'] }}
298
+ wrapperProps={{
299
+ className: `${classes['select-search-wrapper']} ${searchInputProps?.wrapperProps?.className}`,
300
+ }}
157
301
  type="text"
158
302
  name="search-option"
159
303
  placeholder={searchPlaceholder}
@@ -171,16 +315,29 @@ export const Select = React.forwardRef<HTMLSelectElement, Props>(
171
315
 
172
316
  if (value?.length !== 0 && onClear) {
173
317
  return (
174
- <Icon
175
- tag="div"
318
+ <div
319
+ aria-hidden={false}
320
+ role="button"
321
+ tabIndex={0}
176
322
  data-clear
177
- icon={Icons.TimesThin}
178
323
  onClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
179
324
  e.preventDefault();
180
325
  e.stopPropagation();
181
326
  onClear(e);
182
327
  }}
183
- />
328
+ onKeyDown={(e: React.KeyboardEvent<HTMLDivElement>) => {
329
+ if (e.code === 'Enter' || e.code === 'Space') {
330
+ e.preventDefault();
331
+ e.stopPropagation();
332
+ onClear(e);
333
+ containerReference.current &&
334
+ containerReference.current.querySelector('button')!.focus();
335
+ }
336
+ }}
337
+ >
338
+ <span className={readyclasses['sr-only']}>{clearLabel}</span>
339
+ <Icon tag="span" icon={Icons.TimesThin} />
340
+ </div>
184
341
  );
185
342
  }
186
343
  return null;
@@ -221,7 +378,7 @@ export const Select = React.forwardRef<HTMLSelectElement, Props>(
221
378
  {...filterProps(rest, /^data-/, false)}
222
379
  tabIndex={-1}
223
380
  aria-hidden="true"
224
- ref={ref || nativeSelect}
381
+ ref={nativeSelect}
225
382
  name={name}
226
383
  onChange={nativeOnChangeHandler}
227
384
  className={readyclasses['sr-only']}
@@ -234,12 +391,16 @@ export const Select = React.forwardRef<HTMLSelectElement, Props>(
234
391
  <div
235
392
  {...filterProps(rest, /^data-/)}
236
393
  ref={containerReference}
394
+ onKeyDown={onArrowNavigation}
237
395
  className={`custom-select ${classes.select} ${additionalClasses.join(' ')} ${
238
396
  className ?? ''
239
397
  }`}
240
398
  >
241
399
  <button
242
- onClick={() => setExpanded(!expanded)}
400
+ {...selectButtonProps}
401
+ onClick={() => {
402
+ setExpanded(!expanded);
403
+ }}
243
404
  type="button"
244
405
  name={name}
245
406
  disabled={disabled}
@@ -249,12 +410,13 @@ export const Select = React.forwardRef<HTMLSelectElement, Props>(
249
410
  aria-haspopup="listbox"
250
411
  aria-labelledby={labeledBy}
251
412
  aria-describedby={describedBy}
413
+ className={classes['custom-select']}
252
414
  >
253
415
  <div data-display className={classes['selected']}>
254
416
  {!value && placeholder && (
255
417
  <span className={classes['placeholder']}>{placeholder}</span>
256
418
  )}
257
- {value?.length > 0 && <span>{display}</span>}
419
+ {value?.length > 0 && <span data-display-inner>{display}</span>}
258
420
  </div>
259
421
  <div className={classes['status']}>
260
422
  {statusIcon()}
@@ -271,10 +433,8 @@ export const Select = React.forwardRef<HTMLSelectElement, Props>(
271
433
  ...listPosition,
272
434
  }}
273
435
  >
274
- {Array.isArray(children) && children.length > 10 && renderSearch()}
275
- <ul role="listbox" tabIndex={-1}>
276
- {renderOptions()}
277
- </ul>
436
+ {Array.isArray(children) && children.length > renderSearchCondition && renderSearch()}
437
+ <ul role="listbox">{renderOptions()}</ul>
278
438
  </div>
279
439
  </div>
280
440
  </Fragment>
@@ -8,6 +8,7 @@ $borderRadius: 2.5rem;
8
8
 
9
9
  .toggle {
10
10
  width: 2rem;
11
+ min-width: 2rem;
11
12
  height: 1.25rem;
12
13
  background-color: var(--default);
13
14
  border-radius: $borderRadius;
@@ -1,5 +1,5 @@
1
1
  import React, { ComponentPropsWithRef } from 'react';
2
- import { Checkbox, CheckboxProps } from '../Checkbox/Checkbox';
2
+ import { Checkbox, Props as CheckboxProps } from '../Checkbox/Checkbox';
3
3
  import classes from './Toggle.module.scss';
4
4
 
5
5
  export interface Props
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect, useRef } from 'react';
2
2
  import { CheckboxWrapper, Props } from './CheckboxWrapper';
3
- import { Checkbox, CheckboxProps } from '../../Checkbox/Checkbox';
3
+ import { Checkbox, Props as CheckboxProps } from '../../Checkbox/Checkbox';
4
4
  import { render } from '@testing-library/react';
5
5
 
6
6
  const defaultParentParams: CheckboxProps = {
@@ -5,6 +5,7 @@ import { Option } from '../../Select/Option';
5
5
  import userEvent from '@testing-library/user-event';
6
6
 
7
7
  const onChangeHandler = jest.fn();
8
+ const onClearHandler = jest.fn();
8
9
 
9
10
  const defaultParams: Props = {
10
11
  children: [
@@ -29,6 +30,7 @@ const defaultParams: Props = {
29
30
  error: false,
30
31
  value: 'option1',
31
32
  onChange: onChangeHandler,
33
+ onClear: onClearHandler,
32
34
  };
33
35
 
34
36
  const createSelectWrapper = (params?: (defaultParams: Props) => Props) => {
@@ -117,6 +119,32 @@ describe('SelectWrapper & Select have the right attributes', () => {
117
119
  expect(helpertext).toHaveTextContent('helpertext');
118
120
  });
119
121
 
122
+ it('Passes the proper helperProps class', () => {
123
+ const { getByTestId } = createSelectWrapper((defaultParams) => ({
124
+ ...defaultParams,
125
+ helperProps: { ...defaultParams.helperProps, className: 'example-helper-classname' },
126
+ }));
127
+
128
+ const helpertext = getByTestId('helpertext');
129
+
130
+ expect(helpertext.parentElement).toHaveClass('example-helper-classname');
131
+ });
132
+
133
+ it('Passes the proper selectProps class', () => {
134
+ const { getByTestId } = createSelectWrapper((defaultParams) => ({
135
+ ...defaultParams,
136
+ selectProps: {
137
+ ...defaultParams.selectProps,
138
+ 'data-testid': 'select-element',
139
+ className: 'example-select-classname',
140
+ },
141
+ }));
142
+
143
+ const select = getByTestId('select-element');
144
+
145
+ expect(select).toHaveClass('example-select-classname');
146
+ });
147
+
120
148
  it('SelectWrapper has the right errormessage', async () => {
121
149
  const { findByText, select } = createSelectWrapper((defaultParams) => ({
122
150
  ...defaultParams,
@@ -140,4 +168,20 @@ describe('SelectWrapper & Select have the right attributes', () => {
140
168
 
141
169
  expect(onChangeHandler).toHaveBeenCalled();
142
170
  });
171
+
172
+ it('Fires the onClear event', async () => {
173
+ const { select, findByText } = createSelectWrapper();
174
+
175
+ userEvent.click(select as Element);
176
+
177
+ const option3 = await findByText('Option 3');
178
+
179
+ userEvent.click(option3 as Element);
180
+
181
+ const clearButton = select!.querySelector('[data-clear]')!;
182
+
183
+ userEvent.click(clearButton);
184
+
185
+ expect(onClearHandler).toHaveBeenCalled();
186
+ });
143
187
  });
@@ -15,7 +15,7 @@ export interface Props
15
15
  error?: boolean;
16
16
  selectProps?: PartialSelectProps;
17
17
  onChange?: (event: React.ChangeEvent<HTMLSelectElement>) => void;
18
- onClear?: () => void;
18
+ onClear?: (event: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>) => void;
19
19
  }
20
20
 
21
21
  export const SelectWrapper = React.forwardRef<HTMLDivElement, Props>(
@@ -56,7 +56,9 @@ export const SelectWrapper = React.forwardRef<HTMLDivElement, Props>(
56
56
  error={error}
57
57
  describedBy={error ? errorId : helperId}
58
58
  onChange={onChange}
59
- onClear={onClear}
59
+ onClear={(e) => {
60
+ onClear && onClear(e);
61
+ }}
60
62
  placeholder={placeholder}
61
63
  className={`${floatingLabelActive ? classes['floating-label-active'] : ''} ${
62
64
  selectProps?.className ?? ''
@@ -3,6 +3,7 @@
3
3
  }
4
4
 
5
5
  .floating-label {
6
+ font-family: var(--font-family);
6
7
  font-size: 1rem;
7
8
  position: absolute;
8
9
  z-index: 1;
@@ -280,3 +280,7 @@
280
280
  content: '\e93e';
281
281
  @include fontProperties();
282
282
  }
283
+ .icon-table-search:before {
284
+ content: '\e93f';
285
+ @include fontProperties();
286
+ }
package/src/Icon/Icon.tsx CHANGED
@@ -53,6 +53,7 @@ export enum Icons {
53
53
  Square = 'square',
54
54
  Star = 'star',
55
55
  StarAlt = 'star-alt',
56
+ TableSearch = 'table-search',
56
57
  Times = 'times',
57
58
  TimesCircle = 'times-circle',
58
59
  TimesCircleAlt = 'times-circle-alt',
@@ -1,3 +1,5 @@
1
+ @import '../mixins.module.scss';
2
+
1
3
  .link {
2
4
  font-family: var(--font-family);
3
5
  font-size: var(--font-size);
@@ -44,3 +46,21 @@
44
46
  }
45
47
  }
46
48
  }
49
+
50
+ .button {
51
+ @include buttonBase('link');
52
+
53
+ text-decoration: none;
54
+
55
+ &.fill {
56
+ @include button('fill', 'link');
57
+ }
58
+
59
+ &.outline {
60
+ @include button('outline', 'link');
61
+ }
62
+
63
+ &.text {
64
+ @include button('text', 'link');
65
+ }
66
+ }
@@ -99,6 +99,39 @@ describe('Link should render', () => {
99
99
 
100
100
  expect(link).toHaveClass('classname');
101
101
  });
102
+
103
+ it('should render as a filled button ', () => {
104
+ const { link } = createLink((defaultParams) => ({
105
+ ...defaultParams,
106
+ display: 'button',
107
+ buttonVariant: 'fill',
108
+ }));
109
+
110
+ expect(link).toHaveClass('button');
111
+ expect(link).toHaveClass('fill');
112
+ });
113
+
114
+ it('should render as a text button ', () => {
115
+ const { link } = createLink((defaultParams) => ({
116
+ ...defaultParams,
117
+ display: 'button',
118
+ buttonVariant: 'text',
119
+ }));
120
+
121
+ expect(link).toHaveClass('button');
122
+ expect(link).toHaveClass('text');
123
+ });
124
+
125
+ it('should render as an outline button ', () => {
126
+ const { link } = createLink((defaultParams) => ({
127
+ ...defaultParams,
128
+ display: 'button',
129
+ buttonVariant: 'outline',
130
+ }));
131
+
132
+ expect(link).toHaveClass('button');
133
+ expect(link).toHaveClass('outline');
134
+ });
102
135
  });
103
136
 
104
137
  describe('ref should work', () => {
package/src/Link/Link.tsx CHANGED
@@ -7,11 +7,13 @@ import React, {
7
7
  import classes from './Link.module.scss';
8
8
  import { LinkProps } from './types';
9
9
 
10
- type AnchorType = 'external' | 'internal' | 'download';
10
+ export type AnchorType = 'external' | 'internal' | 'download';
11
11
 
12
12
  export interface Props extends ComponentPropsWithRef<'a'> {
13
13
  children?: ReactNode;
14
14
  color?: 'primary' | 'secondary' | 'tertiary';
15
+ display?: 'link' | 'button';
16
+ buttonVariant?: 'outline' | 'text' | 'fill';
15
17
  type?: AnchorType;
16
18
  to: string;
17
19
  disabled?: boolean;
@@ -27,6 +29,8 @@ export const Link = React.forwardRef<HTMLAnchorElement, Props>(
27
29
  to,
28
30
  color = 'primary',
29
31
  type = 'internal',
32
+ display = 'link',
33
+ buttonVariant = 'fill',
30
34
  component,
31
35
  ...rest
32
36
  }: Props,
@@ -44,7 +48,9 @@ export const Link = React.forwardRef<HTMLAnchorElement, Props>(
44
48
  return '';
45
49
  };
46
50
 
47
- const classNames = [classes['link'], classes[color]];
51
+ const classNames = [classes[color]];
52
+ display === 'link' && classNames.push(classes['link']);
53
+ display === 'button' && classNames.push(classes['button'], classes[buttonVariant]);
48
54
  disabled && classNames.push(classes['disabled']);
49
55
  className && classNames.push(className);
50
56
 
@@ -46,7 +46,7 @@ $marginTop: 3.125rem;
46
46
  flex-direction: column;
47
47
  }
48
48
 
49
- @media only screen and (min-width: 50rem) {
49
+ @media only screen and (min-width: 50em) {
50
50
  .container {
51
51
  margin-top: $marginTop;
52
52
  width: 50rem;