@onewelcome/react-lib-components 0.1.7-alpha → 0.1.8-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 (69) hide show
  1. package/dist/DataGrid/DataGrid.d.ts +30 -0
  2. package/dist/DataGrid/DataGridActions/DataGridActions.d.ts +14 -0
  3. package/dist/DataGrid/DataGridActions/DataGridColumnsToggle.d.ts +13 -0
  4. package/dist/DataGrid/DataGridBody/DataGridBody.d.ts +16 -0
  5. package/dist/DataGrid/DataGridBody/DataGridCell.d.ts +6 -0
  6. package/dist/DataGrid/DataGridBody/DataGridRow.d.ts +7 -0
  7. package/dist/DataGrid/DataGridHeader/DataGridHeader.d.ts +10 -0
  8. package/dist/DataGrid/DataGridHeader/DataGridHeaderCell.d.ts +10 -0
  9. package/dist/DataGrid/datagrid.interfaces.d.ts +13 -0
  10. package/dist/Form/Select/Option.d.ts +9 -4
  11. package/dist/Form/Select/Select.d.ts +8 -2
  12. package/dist/Form/Wrapper/SelectWrapper/SelectWrapper.d.ts +1 -1
  13. package/dist/Icon/Icon.d.ts +1 -0
  14. package/dist/_BaseStyling_/BaseStyling.d.ts +4 -0
  15. package/dist/hooks/useSpacing.d.ts +1 -1
  16. package/dist/hooks/useWrapper.d.ts +1 -1
  17. package/dist/index.d.ts +5 -0
  18. package/dist/react-lib-components.cjs.development.js +949 -272
  19. package/dist/react-lib-components.cjs.development.js.map +1 -1
  20. package/dist/react-lib-components.cjs.production.min.js +1 -1
  21. package/dist/react-lib-components.cjs.production.min.js.map +1 -1
  22. package/dist/react-lib-components.esm.js +947 -273
  23. package/dist/react-lib-components.esm.js.map +1 -1
  24. package/dist/util/helper.d.ts +1 -1
  25. package/package.json +11 -11
  26. package/src/ContextMenu/ContextMenu.tsx +16 -1
  27. package/src/DataGrid/DataGrid.module.scss +21 -0
  28. package/src/DataGrid/DataGrid.test.tsx +276 -0
  29. package/src/DataGrid/DataGrid.tsx +101 -0
  30. package/src/DataGrid/DataGridActions/DataGridActions.module.scss +35 -0
  31. package/src/DataGrid/DataGridActions/DataGridActions.test.tsx +184 -0
  32. package/src/DataGrid/DataGridActions/DataGridActions.tsx +109 -0
  33. package/src/DataGrid/DataGridActions/DataGridColumnsToggle.module.scss +41 -0
  34. package/src/DataGrid/DataGridActions/DataGridColumnsToggle.test.tsx +83 -0
  35. package/src/DataGrid/DataGridActions/DataGridColumnsToggle.tsx +88 -0
  36. package/src/DataGrid/DataGridBody/DataGridBody.module.scss +10 -0
  37. package/src/DataGrid/DataGridBody/DataGridBody.test.tsx +123 -0
  38. package/src/DataGrid/DataGridBody/DataGridBody.tsx +64 -0
  39. package/src/DataGrid/DataGridBody/DataGridCell.module.scss +33 -0
  40. package/src/DataGrid/DataGridBody/DataGridCell.test.tsx +74 -0
  41. package/src/DataGrid/DataGridBody/DataGridCell.tsx +25 -0
  42. package/src/DataGrid/DataGridBody/DataGridRow.module.scss +7 -0
  43. package/src/DataGrid/DataGridBody/DataGridRow.test.tsx +101 -0
  44. package/src/DataGrid/DataGridBody/DataGridRow.tsx +27 -0
  45. package/src/DataGrid/DataGridBody/__snapshots__/DataGridBody.test.tsx.snap +258 -0
  46. package/src/DataGrid/DataGridHeader/DataGridHeader.module.scss +26 -0
  47. package/src/DataGrid/DataGridHeader/DataGridHeader.test.tsx +255 -0
  48. package/src/DataGrid/DataGridHeader/DataGridHeader.tsx +80 -0
  49. package/src/DataGrid/DataGridHeader/DataGridHeaderCell.module.scss +68 -0
  50. package/src/DataGrid/DataGridHeader/DataGridHeaderCell.test.tsx +128 -0
  51. package/src/DataGrid/DataGridHeader/DataGridHeaderCell.tsx +72 -0
  52. package/src/DataGrid/datagrid.interfaces.ts +14 -0
  53. package/src/Form/Select/Option.tsx +39 -21
  54. package/src/Form/Select/Select.module.scss +1 -1
  55. package/src/Form/Select/Select.test.tsx +235 -56
  56. package/src/Form/Select/Select.tsx +194 -34
  57. package/src/Form/Toggle/Toggle.module.scss +1 -0
  58. package/src/Form/Wrapper/SelectWrapper/SelectWrapper.test.tsx +44 -0
  59. package/src/Form/Wrapper/SelectWrapper/SelectWrapper.tsx +4 -2
  60. package/src/Icon/Icon.module.scss +4 -0
  61. package/src/Icon/Icon.tsx +1 -0
  62. package/src/Notifications/BaseModal/BaseModal.module.scss +1 -1
  63. package/src/Notifications/BaseModal/BaseModal.test.tsx +2 -1
  64. package/src/Notifications/Dialog/Dialog.module.scss +1 -1
  65. package/src/Notifications/Snackbar/SnackbarContainer/SnackbarContainer.module.scss +1 -1
  66. package/src/Notifications/Snackbar/SnackbarItem/SnackbarItem.module.scss +1 -1
  67. package/src/Pagination/Pagination.module.scss +74 -74
  68. package/src/_BaseStyling_/BaseStyling.tsx +14 -6
  69. package/src/index.ts +6 -0
@@ -0,0 +1,80 @@
1
+ import React, { ComponentPropsWithRef, useEffect, useState } from 'react';
2
+ import { ColumnName, Direction, HeaderCell, OnSortFunction, Sort } from '../datagrid.interfaces';
3
+ import { DataGridHeaderCell } from './DataGridHeaderCell';
4
+ import classes from './DataGridHeader.module.scss';
5
+
6
+ export interface Props extends ComponentPropsWithRef<'thead'> {
7
+ headers: HeaderCell[];
8
+ initialSort?: Sort;
9
+ onSort?: OnSortFunction;
10
+ disableContextMenuColumn?: boolean;
11
+ enableMultiSorting?: boolean;
12
+ }
13
+
14
+ const sortingStates = [undefined, 'ASC', 'DESC'] as (Direction | undefined)[];
15
+
16
+ export const DataGridHeader = React.forwardRef<HTMLTableSectionElement, Props>(
17
+ (
18
+ { initialSort, onSort, headers, disableContextMenuColumn, enableMultiSorting, ...rest }: Props,
19
+ ref
20
+ ) => {
21
+ const [sortList, setSortList] = useState(initialSort || []);
22
+
23
+ useEffect(() => {
24
+ setSortList(initialSort || []);
25
+ }, [initialSort]);
26
+
27
+ const calculateNextSortState = (direction?: Direction) => {
28
+ const currentDirectionIdx = sortingStates.findIndex((item) => item === direction);
29
+ return sortingStates[currentDirectionIdx + (1 % sortingStates.length)];
30
+ };
31
+
32
+ /**
33
+ * The sortList contains sorting columns objects. The order of those objects determinates priorities of sorting.
34
+ * Last modified sorting column has the highest priority.
35
+ */
36
+ const updateSortList = (name: ColumnName): Sort => {
37
+ const current = sortList.find((item) => item.name === name);
38
+ const restSortList = enableMultiSorting ? sortList.filter((item) => item.name !== name) : [];
39
+ const newSortDirection = calculateNextSortState(current?.direction);
40
+ return newSortDirection
41
+ ? [{ name, direction: newSortDirection }, ...restSortList]
42
+ : restSortList;
43
+ };
44
+
45
+ const wrapOnSort = (name: ColumnName) => {
46
+ const newSort = updateSortList(name);
47
+ onSort && onSort(newSort);
48
+ setSortList(newSort);
49
+ };
50
+
51
+ const headerCells = headers.map((header) => {
52
+ if (header.hidden) {
53
+ return null;
54
+ }
55
+
56
+ const sort = sortList.find((item) => item.name === header.name);
57
+ return (
58
+ <DataGridHeaderCell
59
+ key={header.name}
60
+ name={header.name}
61
+ headline={header.headline}
62
+ disableSorting={header.disableSorting || !onSort}
63
+ onSort={wrapOnSort}
64
+ activeSortDirection={sort?.direction}
65
+ />
66
+ );
67
+ });
68
+
69
+ return (
70
+ <thead {...rest} ref={ref} className={classes['thead']}>
71
+ <tr className={classes['row']}>
72
+ {headerCells}
73
+ {!disableContextMenuColumn && (
74
+ <td aria-label="context menu" className={classes['context-menu']}></td>
75
+ )}
76
+ </tr>
77
+ </thead>
78
+ );
79
+ }
80
+ );
@@ -0,0 +1,68 @@
1
+ .header-cell {
2
+ font-weight: normal;
3
+ text-align: left;
4
+ padding: 0 0.625rem;
5
+
6
+ &:first-child {
7
+ padding-left: 1rem;
8
+ }
9
+
10
+ &:last-child {
11
+ padding-right: 1rem;
12
+ }
13
+
14
+ & > * {
15
+ display: inline-flex;
16
+ }
17
+
18
+ & > button {
19
+ padding: 0;
20
+ margin: 0;
21
+ border: 0;
22
+ background: none;
23
+ cursor: pointer;
24
+ }
25
+ }
26
+
27
+ .headline {
28
+ font-family: var(--font-family);
29
+ font-size: var(--font-size);
30
+ line-height: 1.5;
31
+ font-weight: 700;
32
+ color: var(--default);
33
+ }
34
+
35
+ .sorting-indicator-container {
36
+ display: flex;
37
+ flex-direction: column;
38
+ padding-left: 0.5rem;
39
+ justify-content: center;
40
+
41
+ & > * {
42
+ font-size: 0.625rem;
43
+ }
44
+
45
+ .indicator {
46
+ color: var(--greyed-out);
47
+
48
+ &.active {
49
+ color: var(--color-primary);
50
+ }
51
+
52
+ &.hidden {
53
+ visibility: hidden;
54
+ }
55
+ }
56
+ }
57
+
58
+ @media only screen and (min-width: 50em) {
59
+ .header-cell {
60
+ &:first-child {
61
+ padding-left: 1.25rem;
62
+ }
63
+
64
+ &:last-child {
65
+ padding-right: 1.25rem;
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,128 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { DataGridHeaderCell, Props } from './DataGridHeaderCell';
3
+ import { render } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+
6
+ const defaultParams: Props = { name: 'name-123', headline: 'Test' };
7
+
8
+ const createDataGridHeaderCell = (params?: (defaultParams: Props) => Props) => {
9
+ let parameters: Props = defaultParams;
10
+ if (params) {
11
+ parameters = params(defaultParams);
12
+ }
13
+ const container = document.createElement('tr');
14
+ const queries = render(<DataGridHeaderCell {...parameters} data-testid="dataGridHeaderCell" />, {
15
+ container,
16
+ });
17
+ const dataGridHeaderCell = queries.getByTestId('dataGridHeaderCell');
18
+
19
+ return {
20
+ ...queries,
21
+ dataGridHeaderCell,
22
+ };
23
+ };
24
+
25
+ describe('DataGridHeaderCell should render', () => {
26
+ it('renders without crashing', () => {
27
+ const { dataGridHeaderCell, queryByRole } = createDataGridHeaderCell();
28
+
29
+ expect(dataGridHeaderCell).toBeDefined();
30
+ expect(dataGridHeaderCell).toHaveTextContent(defaultParams.headline);
31
+ expect(queryByRole('button')).toBeDefined();
32
+ expect(dataGridHeaderCell.querySelectorAll('[data-icon]')).toHaveLength(2);
33
+ });
34
+
35
+ it('renders without sorting indicators (icons)', () => {
36
+ const { dataGridHeaderCell } = createDataGridHeaderCell((params) => ({
37
+ ...params,
38
+ disableSorting: true,
39
+ }));
40
+
41
+ expect(dataGridHeaderCell.querySelector('[data-icon]')).toBeNull();
42
+ });
43
+
44
+ it('renders DESC and ASC sorting indicators (icons) when sorting is enabled and current sorting is not defined', () => {
45
+ const { dataGridHeaderCell } = createDataGridHeaderCell();
46
+
47
+ expect(dataGridHeaderCell).toBeDefined();
48
+ const sortingIcons = dataGridHeaderCell.querySelectorAll('[data-icon]');
49
+ expect(sortingIcons).toHaveLength(2);
50
+ sortingIcons.forEach((icon) => {
51
+ expect(icon).toHaveClass('indicator');
52
+ expect(icon).not.toHaveClass('active');
53
+ expect(icon).not.toHaveClass('hidden');
54
+ });
55
+ });
56
+
57
+ it('renders ASC sorting indicator (icon) when sorting is enabled and current sorting is ASC', () => {
58
+ const { dataGridHeaderCell } = createDataGridHeaderCell((params) => ({
59
+ ...params,
60
+ activeSortDirection: 'ASC',
61
+ }));
62
+
63
+ expect(dataGridHeaderCell).toBeDefined();
64
+ const sortingIcons = dataGridHeaderCell.querySelectorAll('[data-icon]');
65
+ expect(sortingIcons).toHaveLength(2);
66
+ sortingIcons.forEach((icon) => expect(icon).toHaveClass('indicator'));
67
+ expect(sortingIcons[0]).toHaveClass('indicator', 'active', 'icon-triangle-up');
68
+ expect(sortingIcons[0]).not.toHaveClass('hidden');
69
+ expect(sortingIcons[1]).toHaveClass('indicator', 'hidden', 'icon-triangle-down');
70
+ expect(sortingIcons[1]).not.toHaveClass('active');
71
+ });
72
+ });
73
+
74
+ it('renders DESC sorting indicator (icon) when sorting is enabled and current sorting is DESC', () => {
75
+ const { dataGridHeaderCell } = createDataGridHeaderCell((params) => ({
76
+ ...params,
77
+ activeSortDirection: 'DESC',
78
+ }));
79
+
80
+ expect(dataGridHeaderCell).toBeDefined();
81
+ const sortingIcons = dataGridHeaderCell.querySelectorAll('[data-icon]');
82
+ expect(sortingIcons).toHaveLength(2);
83
+ sortingIcons.forEach((icon) => expect(icon).toHaveClass('indicator'));
84
+ expect(sortingIcons[0]).toHaveClass('indicator', 'hidden', 'icon-triangle-up');
85
+ expect(sortingIcons[0]).not.toHaveClass('active');
86
+ expect(sortingIcons[1]).toHaveClass('indicator', 'active', 'icon-triangle-down');
87
+ expect(sortingIcons[1]).not.toHaveClass('hidden');
88
+ });
89
+
90
+ describe('DataGridHeaderCell should be interactive', () => {
91
+ it('clicking on cell calls onSort callback', () => {
92
+ const onSortHandler = jest.fn();
93
+ const { getByRole } = createDataGridHeaderCell((params) => ({
94
+ ...params,
95
+ disableSorting: false,
96
+ onSort: onSortHandler,
97
+ }));
98
+
99
+ userEvent.click(getByRole('button'));
100
+
101
+ expect(onSortHandler).toBeCalledWith(defaultParams.name);
102
+ });
103
+ });
104
+
105
+ describe('ref should work', () => {
106
+ it('should give back the proper data prop, this also checks if the component propagates ...rest properly', () => {
107
+ const ExampleComponent = ({
108
+ propagateRef,
109
+ }: {
110
+ propagateRef: (ref: React.RefObject<HTMLElement>) => void;
111
+ }) => {
112
+ const ref = useRef(null);
113
+
114
+ useEffect(() => {
115
+ propagateRef(ref);
116
+ }, [ref]);
117
+
118
+ return <DataGridHeaderCell {...defaultParams} data-ref="testing" ref={ref} />;
119
+ };
120
+
121
+ const refCheck = (ref: React.RefObject<HTMLElement>) => {
122
+ expect(ref.current).toHaveAttribute('data-ref', 'testing');
123
+ };
124
+
125
+ const container = document.createElement('tr');
126
+ render(<ExampleComponent propagateRef={refCheck} />, { container });
127
+ });
128
+ });
@@ -0,0 +1,72 @@
1
+ import React, { ComponentPropsWithRef, Fragment } from 'react';
2
+ import { Icon, Icons } from '../../Icon/Icon';
3
+ import { ColumnName, Direction } from '../datagrid.interfaces';
4
+ import classes from './DataGridHeaderCell.module.scss';
5
+
6
+ export interface Props extends ComponentPropsWithRef<'th'> {
7
+ headline: string;
8
+ name: ColumnName;
9
+ disableSorting?: boolean;
10
+ activeSortDirection?: Direction;
11
+ onSort?: (name: ColumnName) => void;
12
+ }
13
+
14
+ const ariaSortMapping = {
15
+ ASC: 'ascending',
16
+ DESC: 'descending',
17
+ } as const;
18
+
19
+ export const DataGridHeaderCell = React.forwardRef<HTMLTableCellElement, Props>(
20
+ ({ headline, name, disableSorting, activeSortDirection, onSort, ...rest }: Props, ref) => {
21
+ const onCellClick = () => {
22
+ onSort && onSort(name);
23
+ };
24
+
25
+ const sortingIndicator = () => {
26
+ const getSortingIndicatorClasses = (direction: Direction) => {
27
+ const sortingIndicatorClasses = [classes['indicator']];
28
+ activeSortDirection &&
29
+ sortingIndicatorClasses.push(
30
+ activeSortDirection === direction ? classes['active'] : classes['hidden']
31
+ );
32
+ return sortingIndicatorClasses;
33
+ };
34
+
35
+ return (
36
+ <Fragment>
37
+ <Icon className={getSortingIndicatorClasses('ASC').join(' ')} icon={Icons.TriangleUp} />
38
+ <Icon
39
+ className={getSortingIndicatorClasses('DESC').join(' ')}
40
+ icon={Icons.TriangleDown}
41
+ />
42
+ </Fragment>
43
+ );
44
+ };
45
+
46
+ const innerContent = (
47
+ <Fragment>
48
+ <span className={classes['headline']}>{headline}</span>
49
+ {!disableSorting && (
50
+ <div className={classes['sorting-indicator-container']}>{sortingIndicator()}</div>
51
+ )}
52
+ </Fragment>
53
+ );
54
+
55
+ return (
56
+ <th
57
+ {...rest}
58
+ ref={ref}
59
+ className={classes['header-cell']}
60
+ aria-sort={activeSortDirection && ariaSortMapping[activeSortDirection]}
61
+ >
62
+ {disableSorting ? (
63
+ <div key={name}>{innerContent}</div>
64
+ ) : (
65
+ <button key={name} onClick={onCellClick}>
66
+ {innerContent}
67
+ </button>
68
+ )}
69
+ </th>
70
+ );
71
+ }
72
+ );
@@ -0,0 +1,14 @@
1
+ export type ColumnName = string;
2
+ export type Direction = 'ASC' | 'DESC';
3
+ export type Sort = {
4
+ name: ColumnName;
5
+ direction: Direction;
6
+ }[];
7
+ export type OnSortFunction = (sort: Sort) => void;
8
+
9
+ export interface HeaderCell {
10
+ name: ColumnName;
11
+ headline: string;
12
+ disableSorting?: boolean;
13
+ hidden?: boolean;
14
+ }
@@ -1,14 +1,19 @@
1
- import React, { ComponentPropsWithRef, useEffect, useState } from 'react';
1
+ import React, { ComponentPropsWithRef, createRef, RefObject, useEffect } from 'react';
2
2
  import classes from './Select.module.scss';
3
3
 
4
4
  export interface Props extends ComponentPropsWithRef<'li'> {
5
5
  children: string;
6
6
  value: string;
7
7
  disabled?: boolean;
8
- selected?: boolean;
8
+ isSelected?: boolean;
9
+ selectOpened?: boolean;
10
+ hasFocus?: boolean;
11
+ shouldClick?: boolean;
12
+ isSearching?: boolean;
9
13
  label?: string;
10
- filter?: string;
11
- onOptionSelect?: (event: React.SyntheticEvent<HTMLLIElement>) => void;
14
+ childIndex?: number;
15
+ onOptionSelect?: (ref: React.RefObject<HTMLLIElement>) => void;
16
+ onFocusChange?: (childIndex: number) => void;
12
17
  }
13
18
 
14
19
  export const Option = React.forwardRef<HTMLLIElement, Props>(
@@ -16,44 +21,57 @@ export const Option = React.forwardRef<HTMLLIElement, Props>(
16
21
  {
17
22
  children,
18
23
  className,
19
- selected = false,
24
+ isSelected = false,
25
+ shouldClick,
26
+ hasFocus,
27
+ selectOpened,
28
+ isSearching,
29
+ childIndex,
20
30
  onOptionSelect,
31
+ onFocusChange,
21
32
  disabled,
22
- filter,
23
33
  value,
24
34
  ...rest
25
35
  }: Props,
26
36
  ref
27
37
  ) => {
28
- const [showOption, setShowOption] = useState(true);
38
+ let innerOptionRef = (ref as RefObject<HTMLLIElement>) || createRef<HTMLLIElement>();
29
39
 
30
- const onSelectHandler = (event: React.SyntheticEvent<HTMLLIElement>) => {
31
- if (onOptionSelect) onOptionSelect(event);
32
- };
40
+ useEffect(() => {
41
+ if (isSelected && innerOptionRef.current && shouldClick) {
42
+ innerOptionRef.current.click();
43
+ }
44
+ }, [isSelected, shouldClick]);
33
45
 
34
46
  useEffect(() => {
35
- if (filter) {
36
- setShowOption(children.toLowerCase().match(filter.toLowerCase()) !== null);
37
- } else {
38
- setShowOption(true);
47
+ if (innerOptionRef.current && hasFocus && selectOpened && !isSearching) {
48
+ onFocusChange && childIndex && onFocusChange(childIndex);
49
+ innerOptionRef.current.focus();
39
50
  }
40
- }, [filter]);
51
+ }, [hasFocus, innerOptionRef, selectOpened, isSearching]);
41
52
 
42
- if (!showOption) return null;
53
+ const onSelectHandler = () => {
54
+ if (onOptionSelect) onOptionSelect(innerOptionRef);
55
+ };
43
56
 
44
57
  return (
45
58
  <li
46
59
  {...rest}
47
- ref={ref}
60
+ ref={innerOptionRef}
48
61
  data-value={value}
49
- className={`${selected ? classes['selected-option'] : ''} ${
62
+ className={`${isSelected ? classes['selected-option'] : ''} ${
50
63
  disabled ? classes.disabled : ''
51
64
  } ${className ?? ''}`}
52
65
  onClick={onSelectHandler}
53
- onKeyPress={(e) => {
54
- e.key === 'Enter' && onSelectHandler(e);
66
+ onKeyDownCapture={(event) => {
67
+ if (event.code === 'Enter') {
68
+ event.stopPropagation();
69
+ event.preventDefault();
70
+
71
+ onSelectHandler();
72
+ }
55
73
  }}
56
- aria-selected={selected}
74
+ aria-selected={isSelected}
57
75
  role="option"
58
76
  tabIndex={disabled ? -1 : 0}
59
77
  >
@@ -37,7 +37,7 @@ $listItemHeight: 2.125rem;
37
37
  border-color: var(--default);
38
38
  }
39
39
 
40
- button {
40
+ .custom-select {
41
41
  width: 100%;
42
42
  min-height: 4rem;
43
43
  background-color: transparent;