@onewelcome/react-lib-components 0.1.1-alpha → 0.1.4-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 (180) hide show
  1. package/README.md +16 -1
  2. package/dist/Breadcrumbs/Breadcrumbs.d.ts +3 -3
  3. package/dist/Button/BaseButton.d.ts +3 -4
  4. package/dist/Button/Button.d.ts +3 -4
  5. package/dist/Button/IconButton.d.ts +3 -4
  6. package/dist/ContextMenu/ContextMenu.d.ts +3 -3
  7. package/dist/Form/Checkbox/Checkbox.d.ts +5 -5
  8. package/dist/Form/Fieldset/Fieldset.d.ts +4 -4
  9. package/dist/Form/FormControl/FormControl.d.ts +5 -5
  10. package/dist/Form/FormGroup/FormGroup.d.ts +4 -4
  11. package/dist/Form/FormHelperText/FormHelperText.d.ts +4 -5
  12. package/dist/Form/FormSelectorWrapper/FormSelectorWrapper.d.ts +8 -12
  13. package/dist/Form/Input/Input.d.ts +7 -6
  14. package/dist/Form/Label/Label.d.ts +4 -5
  15. package/dist/Form/Radio/Radio.d.ts +5 -5
  16. package/dist/Form/Select/Option.d.ts +3 -4
  17. package/dist/Form/Select/Select.d.ts +4 -4
  18. package/dist/Form/Textarea/Textarea.d.ts +9 -5
  19. package/dist/Form/Toggle/Toggle.d.ts +3 -3
  20. package/dist/Form/Wrapper/CheckboxWrapper/CheckboxWrapper.d.ts +4 -3
  21. package/dist/Form/Wrapper/InputWrapper/InputWrapper.d.ts +5 -5
  22. package/dist/Form/Wrapper/RadioWrapper/RadioWrapper.d.ts +4 -4
  23. package/dist/Form/Wrapper/SelectWrapper/SelectWrapper.d.ts +7 -4
  24. package/dist/Form/Wrapper/TextareaWrapper/TextareaWrapper.d.ts +3 -3
  25. package/dist/Form/Wrapper/Wrapper/Wrapper.d.ts +6 -6
  26. package/dist/Form/form.interfaces.d.ts +4 -3
  27. package/dist/Icon/Icon.d.ts +4 -4
  28. package/dist/Link/Link.d.ts +4 -6
  29. package/dist/Notifications/BaseModal/BaseModal.d.ts +3 -4
  30. package/dist/Notifications/BaseModal/BaseModalActions/BaseModalActions.d.ts +3 -3
  31. package/dist/Notifications/BaseModal/BaseModalContent/BaseModalContent.d.ts +3 -3
  32. package/dist/Notifications/BaseModal/BaseModalHeader/BaseModalHeader.d.ts +3 -3
  33. package/dist/Notifications/Dialog/Dialog.d.ts +3 -3
  34. package/dist/Notifications/Dialog/DialogActions/DialogActions.d.ts +3 -3
  35. package/dist/Notifications/Dialog/DialogTitle/DialogTitle.d.ts +3 -3
  36. package/dist/Notifications/DiscardChangesModal/DiscardChangesDialog/DiscardChangesDialog.d.ts +5 -4
  37. package/dist/Notifications/DiscardChangesModal/DiscardChangesModal.d.ts +3 -1
  38. package/dist/Pagination/Pagination.d.ts +19 -0
  39. package/dist/Popover/Popover.d.ts +3 -3
  40. package/dist/Tabs/Tab.d.ts +11 -0
  41. package/dist/Tabs/TabButton.d.ts +10 -0
  42. package/dist/Tabs/TabPanel.d.ts +8 -0
  43. package/dist/Tabs/Tabs.d.ts +9 -0
  44. package/dist/TextEllipsis/TextEllipsis.d.ts +6 -0
  45. package/dist/Tiles/Tile.d.ts +3 -3
  46. package/dist/Tiles/Tiles.d.ts +3 -3
  47. package/dist/Tooltip/Tooltip.d.ts +3 -3
  48. package/dist/Typography/Typography.d.ts +6 -4
  49. package/dist/Wizard/BaseWizardSteps/BaseWizardSteps.d.ts +3 -3
  50. package/dist/Wizard/WizardSteps/WizardSteps.d.ts +3 -3
  51. package/dist/_BaseStyling_/BaseStyling.d.ts +9 -0
  52. package/dist/hooks/useRepeater.d.ts +10 -0
  53. package/dist/hooks/useSpacing.d.ts +2 -2
  54. package/dist/hooks/useWrapper.d.ts +1 -1
  55. package/dist/index.d.ts +6 -0
  56. package/dist/interfaces.d.ts +2 -11
  57. package/dist/react-lib-components.cjs.development.js +2395 -1696
  58. package/dist/react-lib-components.cjs.development.js.map +1 -1
  59. package/dist/react-lib-components.cjs.production.min.js +1 -1
  60. package/dist/react-lib-components.cjs.production.min.js.map +1 -1
  61. package/dist/react-lib-components.esm.js +2391 -1698
  62. package/dist/react-lib-components.esm.js.map +1 -1
  63. package/dist/util/helper.d.ts +6 -1
  64. package/package.json +30 -24
  65. package/src/Breadcrumbs/Breadcrumbs.tsx +39 -37
  66. package/src/Button/BaseButton.test.tsx +65 -19
  67. package/src/Button/BaseButton.tsx +2 -3
  68. package/src/Button/Button.test.tsx +63 -17
  69. package/src/Button/Button.tsx +15 -4
  70. package/src/Button/IconButton.test.tsx +57 -22
  71. package/src/Button/IconButton.tsx +14 -9
  72. package/src/ContextMenu/ContextMenu.test.tsx +27 -1
  73. package/src/ContextMenu/ContextMenu.tsx +70 -65
  74. package/src/Form/Checkbox/Checkbox.test.tsx +28 -2
  75. package/src/Form/Checkbox/Checkbox.tsx +132 -122
  76. package/src/Form/Fieldset/Fieldset.test.tsx +28 -2
  77. package/src/Form/Fieldset/Fieldset.tsx +96 -50
  78. package/src/Form/FormControl/FormControl.test.tsx +27 -1
  79. package/src/Form/FormControl/FormControl.tsx +36 -39
  80. package/src/Form/FormGroup/FormGroup.test.tsx +51 -1
  81. package/src/Form/FormGroup/FormGroup.tsx +64 -58
  82. package/src/Form/FormHelperText/FormHelperText.test.tsx +27 -1
  83. package/src/Form/FormHelperText/FormHelperText.tsx +20 -16
  84. package/src/Form/FormSelectorWrapper/FormSelectorWrapper.test.tsx +78 -0
  85. package/src/Form/FormSelectorWrapper/FormSelectorWrapper.tsx +61 -55
  86. package/src/Form/Input/Input.module.scss +34 -15
  87. package/src/Form/Input/Input.test.tsx +27 -1
  88. package/src/Form/Input/Input.tsx +88 -47
  89. package/src/Form/Label/Label.test.tsx +27 -1
  90. package/src/Form/Label/Label.tsx +18 -14
  91. package/src/Form/Radio/Radio.test.tsx +28 -2
  92. package/src/Form/Radio/Radio.tsx +98 -90
  93. package/src/Form/Select/Option.test.tsx +27 -1
  94. package/src/Form/Select/Option.tsx +49 -42
  95. package/src/Form/Select/Select.module.scss +5 -1
  96. package/src/Form/Select/Select.test.tsx +224 -30
  97. package/src/Form/Select/Select.tsx +248 -182
  98. package/src/Form/Textarea/Textarea.module.scss +2 -1
  99. package/src/Form/Textarea/Textarea.test.tsx +28 -2
  100. package/src/Form/Textarea/Textarea.tsx +44 -29
  101. package/src/Form/Toggle/Toggle.module.scss +9 -0
  102. package/src/Form/Toggle/Toggle.test.tsx +27 -1
  103. package/src/Form/Toggle/Toggle.tsx +25 -12
  104. package/src/Form/Wrapper/CheckboxWrapper/CheckboxWrapper.test.tsx +27 -1
  105. package/src/Form/Wrapper/CheckboxWrapper/CheckboxWrapper.tsx +45 -48
  106. package/src/Form/Wrapper/InputWrapper/InputWrapper.module.scss +17 -1
  107. package/src/Form/Wrapper/InputWrapper/InputWrapper.test.tsx +89 -1
  108. package/src/Form/Wrapper/InputWrapper/InputWrapper.tsx +134 -74
  109. package/src/Form/Wrapper/RadioWrapper/RadioWrapper.tsx +64 -59
  110. package/src/Form/Wrapper/SelectWrapper/SelectWrapper.module.scss +1 -1
  111. package/src/Form/Wrapper/SelectWrapper/SelectWrapper.test.tsx +43 -1
  112. package/src/Form/Wrapper/SelectWrapper/SelectWrapper.tsx +54 -44
  113. package/src/Form/Wrapper/TextareaWrapper/TextareaWrapper.module.scss +5 -7
  114. package/src/Form/Wrapper/TextareaWrapper/TextareaWrapper.test.tsx +43 -1
  115. package/src/Form/Wrapper/TextareaWrapper/TextareaWrapper.tsx +100 -85
  116. package/src/Form/Wrapper/Wrapper/Wrapper.module.scss +1 -1
  117. package/src/Form/Wrapper/Wrapper/Wrapper.test.tsx +27 -1
  118. package/src/Form/Wrapper/Wrapper/Wrapper.tsx +76 -71
  119. package/src/Form/form.interfaces.ts +4 -3
  120. package/src/Icon/Icon.module.scss +4 -0
  121. package/src/Icon/Icon.test.tsx +30 -2
  122. package/src/Icon/Icon.tsx +5 -5
  123. package/src/Link/Link.test.tsx +27 -1
  124. package/src/Link/Link.tsx +10 -7
  125. package/src/Notifications/BaseModal/BaseModal.test.tsx +27 -1
  126. package/src/Notifications/BaseModal/BaseModal.tsx +59 -54
  127. package/src/Notifications/BaseModal/BaseModalActions/BaseModalActions.test.tsx +26 -1
  128. package/src/Notifications/BaseModal/BaseModalActions/BaseModalActions.tsx +11 -9
  129. package/src/Notifications/BaseModal/BaseModalContent/BaseModalContent.test.tsx +27 -1
  130. package/src/Notifications/BaseModal/BaseModalContent/BaseModalContent.tsx +27 -26
  131. package/src/Notifications/BaseModal/BaseModalHeader/BaseModalHeader.test.tsx +29 -1
  132. package/src/Notifications/BaseModal/BaseModalHeader/BaseModalHeader.tsx +18 -16
  133. package/src/Notifications/Dialog/Dialog.test.tsx +39 -1
  134. package/src/Notifications/Dialog/Dialog.tsx +84 -78
  135. package/src/Notifications/Dialog/DialogActions/DialogActions.test.tsx +27 -1
  136. package/src/Notifications/Dialog/DialogActions/DialogActions.tsx +15 -12
  137. package/src/Notifications/Dialog/DialogTitle/DialogTitle.test.tsx +28 -2
  138. package/src/Notifications/Dialog/DialogTitle/DialogTitle.tsx +13 -11
  139. package/src/Notifications/DiscardChangesModal/DiscardChangesDialog/DiscardChangesDialog.test.tsx +41 -1
  140. package/src/Notifications/DiscardChangesModal/DiscardChangesDialog/DiscardChangesDialog.tsx +43 -36
  141. package/src/Notifications/DiscardChangesModal/DiscardChangesModal.test.tsx +52 -1
  142. package/src/Notifications/DiscardChangesModal/DiscardChangesModal.tsx +8 -3
  143. package/src/Notifications/Snackbar/SnackbarItem/SnackbarItem.tsx +1 -1
  144. package/src/Pagination/Pagination.module.scss +120 -0
  145. package/src/Pagination/Pagination.test.tsx +176 -0
  146. package/src/Pagination/Pagination.tsx +205 -0
  147. package/src/Popover/Popover.tsx +3 -3
  148. package/src/Tabs/Tab.test.tsx +71 -0
  149. package/src/Tabs/Tab.tsx +17 -0
  150. package/src/Tabs/TabButton.module.scss +36 -0
  151. package/src/Tabs/TabButton.test.tsx +77 -0
  152. package/src/Tabs/TabButton.tsx +58 -0
  153. package/src/Tabs/TabPanel.module.scss +7 -0
  154. package/src/Tabs/TabPanel.test.tsx +76 -0
  155. package/src/Tabs/TabPanel.tsx +27 -0
  156. package/src/Tabs/Tabs.module.scss +41 -0
  157. package/src/Tabs/Tabs.test.tsx +268 -0
  158. package/src/Tabs/Tabs.tsx +149 -0
  159. package/src/TextEllipsis/TextEllipsis.module.scss +18 -0
  160. package/src/TextEllipsis/TextEllipsis.test.tsx +80 -0
  161. package/src/TextEllipsis/TextEllipsis.tsx +55 -0
  162. package/src/Tiles/Tile.test.tsx +27 -1
  163. package/src/Tiles/Tile.tsx +59 -62
  164. package/src/Tiles/Tiles.test.tsx +27 -1
  165. package/src/Tiles/Tiles.tsx +42 -39
  166. package/src/Tooltip/Tooltip.test.tsx +27 -1
  167. package/src/Tooltip/Tooltip.tsx +104 -92
  168. package/src/Typography/Typography.test.tsx +27 -1
  169. package/src/Typography/Typography.tsx +66 -68
  170. package/src/Wizard/BaseWizardSteps/BaseWizardSteps.tsx +67 -62
  171. package/src/Wizard/WizardSteps/WizardSteps.tsx +24 -21
  172. package/src/_BaseStyling_/BaseStyling.tsx +19 -1
  173. package/src/hooks/useRepeater.test.tsx +139 -0
  174. package/src/hooks/useRepeater.ts +34 -0
  175. package/src/hooks/useSpacing.ts +1 -1
  176. package/src/hooks/useWrapper.ts +7 -2
  177. package/src/index.ts +15 -1
  178. package/src/interfaces.ts +2 -12
  179. package/src/util/helper.test.tsx +38 -1
  180. package/src/util/helper.tsx +21 -0
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useEffect, useRef } from 'react';
2
2
  import { DiscardChangesModal, Props } from './DiscardChangesModal';
3
3
  import { findByTestId, getAllByRole, render, waitFor } from '@testing-library/react';
4
4
  import userEvent from '@testing-library/user-event';
@@ -109,3 +109,54 @@ describe('DiscardChangesModal should show DiscardChangesDialog', () => {
109
109
  expect(defaultParams.onClose).toBeCalled();
110
110
  });
111
111
  });
112
+
113
+ describe('ref should work', () => {
114
+ it('should give back the proper data prop, this also checks if the component propagates ...rest properly', () => {
115
+ const ExampleComponent = ({
116
+ propagateRef,
117
+ }: {
118
+ propagateRef?: (
119
+ ref1: React.RefObject<HTMLElement>,
120
+ ref2: React.RefObject<HTMLElement>
121
+ ) => void;
122
+ }) => {
123
+ const modalRef = useRef(null);
124
+ const dialogRef = useRef(null);
125
+
126
+ useEffect(() => {
127
+ if (dialogRef.current && modalRef.current) {
128
+ propagateRef && propagateRef(modalRef, dialogRef);
129
+ }
130
+ }, [modalRef, dialogRef]);
131
+
132
+ return (
133
+ <DiscardChangesModal
134
+ modalRef={modalRef}
135
+ dialogRef={dialogRef}
136
+ data-ref="testing"
137
+ hasUnsavedChanges={jest.fn()}
138
+ onClose={jest.fn()}
139
+ headerProps={{ title: 'test' }}
140
+ open={false}
141
+ children="test"
142
+ discardChangedDialogProps={{
143
+ contentLabel: 'test',
144
+ discardChangesButtonLabel: 'test',
145
+ keepEditingButtonLabel: 'test',
146
+ titleLabel: 'test',
147
+ 'data-ref': 'testing',
148
+ }}
149
+ title="test"
150
+ id="test"
151
+ />
152
+ );
153
+ };
154
+
155
+ const refCheck = (ref1: React.RefObject<HTMLElement>, ref2: React.RefObject<HTMLElement>) => {
156
+ expect(ref1.current).toHaveAttribute('data-ref', 'testing');
157
+ expect(ref2.current).toHaveAttribute('data-ref', 'testing');
158
+ };
159
+
160
+ render(<ExampleComponent propagateRef={refCheck} />);
161
+ });
162
+ });
@@ -14,15 +14,19 @@ export interface Props extends Omit<ModalProps, 'onClose'> {
14
14
  DiscardChangesDialogProps,
15
15
  'open' | 'onKeepEditing' | 'onDiscardChanges'
16
16
  >;
17
+ modalRef?: React.RefObject<HTMLDivElement>;
18
+ dialogRef?: React.RefObject<HTMLDivElement>;
17
19
  }
18
20
 
19
21
  export const DiscardChangesModal = ({
20
22
  id,
21
- onClose,
22
23
  children,
23
- hasUnsavedChanges,
24
24
  headerProps,
25
25
  discardChangedDialogProps,
26
+ modalRef,
27
+ dialogRef,
28
+ onClose,
29
+ hasUnsavedChanges,
26
30
  ...rest
27
31
  }: Props) => {
28
32
  const [openDiscardChangesDialog, setOpenDiscardChangesDialog] = useState(false);
@@ -41,12 +45,13 @@ export const DiscardChangesModal = ({
41
45
 
42
46
  return (
43
47
  <Fragment>
44
- <Modal id={id} onClose={onCloseWrapper} {...rest}>
48
+ <Modal id={id} ref={modalRef} onClose={onCloseWrapper} {...rest}>
45
49
  <ModalHeader {...headerProps} id={`${id}-label`} onClose={onCloseWrapper} />
46
50
  {children}
47
51
  </Modal>
48
52
  <DiscardChangesDialog
49
53
  {...discardChangedDialogProps}
54
+ ref={dialogRef}
50
55
  open={openDiscardChangesDialog}
51
56
  onKeepEditing={onDialogKeepEditing}
52
57
  onDiscardChanges={onDialogDiscardChanges}
@@ -51,7 +51,7 @@ export const SnackbarItem = ({
51
51
 
52
52
  const getVariantIcon = () => {
53
53
  if (variant === 'error') {
54
- return Icons.TimesCircleAlt;
54
+ return Icons.Error;
55
55
  }
56
56
  return variant === 'success' ? Icons.CheckmarkCircleBreakout : Icons.InfoCircle;
57
57
  };
@@ -0,0 +1,120 @@
1
+ .pagination-wrapper {
2
+ font-family: var(--font-family);
3
+ font-size: var(--font-size);
4
+ color: var(--greyed-out);
5
+ display: flex;
6
+ flex-direction: column;
7
+ }
8
+
9
+ .form-element {
10
+ height: 2.5rem;
11
+
12
+ button {
13
+ min-height: 2.5rem;
14
+ }
15
+ }
16
+
17
+ .total {
18
+ flex: 1 0 auto;
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+
23
+ span > span {
24
+ font-weight: 700;
25
+ font-family: var(--font-family);
26
+ font-size: var(--font-size);
27
+ margin-left: 0.25rem;
28
+ }
29
+ }
30
+
31
+ .pagination {
32
+ flex: 1 0 auto;
33
+ display: flex;
34
+ align-items: center;
35
+ justify-content: center;
36
+ margin-top: 1rem;
37
+
38
+ .previous,
39
+ .next {
40
+ display: flex;
41
+ align-items: center;
42
+
43
+ button {
44
+ margin: 0.25rem;
45
+ }
46
+
47
+ button span:before {
48
+ font-size: 0.75rem;
49
+ }
50
+ }
51
+ }
52
+
53
+ .per-page {
54
+ display: none;
55
+ align-items: center;
56
+ }
57
+
58
+ .page {
59
+ display: flex;
60
+ align-items: center;
61
+ flex-basis: min-content;
62
+ }
63
+
64
+ .page-size-select {
65
+ border-color: #fff;
66
+ margin: 0 0.25rem;
67
+
68
+ button {
69
+ min-width: 55px;
70
+ padding: 0;
71
+
72
+ div[data-display] {
73
+ left: 0.5rem;
74
+ }
75
+
76
+ div:not([data-display]) {
77
+ right: 0.5rem;
78
+ }
79
+ }
80
+ }
81
+
82
+ .current-value-input {
83
+ margin: 0 0.25rem 0 0;
84
+ padding: 0;
85
+
86
+ input {
87
+ border-radius: var(--input-border-radius);
88
+ padding: 0 1rem;
89
+ width: auto;
90
+ text-align: center;
91
+ }
92
+ }
93
+
94
+ @media screen and (min-width: 30em) {
95
+ .pagination-wrapper {
96
+ flex-direction: row;
97
+ }
98
+
99
+ .total {
100
+ justify-content: flex-start;
101
+ }
102
+
103
+ .pagination {
104
+ margin-top: 0;
105
+ justify-content: flex-end;
106
+
107
+ .previous,
108
+ .next {
109
+ button {
110
+ margin: 0.5rem;
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ @media screen and (min-width: 48em) {
117
+ .per-page {
118
+ display: flex;
119
+ }
120
+ }
@@ -0,0 +1,176 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { Pagination, Props } from './Pagination';
3
+ import { render, waitFor } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+
6
+ const defaultParams: Props = {
7
+ currentPage: 1,
8
+ totalElements: 500,
9
+ onPageChange: jest.fn(),
10
+ onPageSizeChange: jest.fn(),
11
+ };
12
+
13
+ const createPagination = (params?: (defaultParams: Props) => Props) => {
14
+ let parameters: Props = defaultParams;
15
+
16
+ if (params) {
17
+ parameters = params(defaultParams);
18
+ }
19
+
20
+ const queries = render(<Pagination data-testid="pagination" {...parameters} />);
21
+
22
+ const pagination = queries.getByTestId('pagination');
23
+
24
+ return {
25
+ ...queries,
26
+ pagination,
27
+ };
28
+ };
29
+
30
+ describe('Pagination should render', () => {
31
+ it('renders without crashing', () => {
32
+ const { pagination } = createPagination();
33
+
34
+ expect(pagination).toBeTruthy();
35
+ });
36
+ });
37
+
38
+ describe('Pagination events', () => {
39
+ it('should give us the correct values', async () => {
40
+ const onPageChange = jest.fn();
41
+
42
+ const onPageSizeChange = jest.fn();
43
+
44
+ const { pagination } = createPagination((defaultParams) => ({
45
+ ...defaultParams,
46
+ currentPage: 10,
47
+ onPageChange: onPageChange,
48
+ onPageSizeChange: onPageSizeChange,
49
+ }));
50
+
51
+ const next = pagination.querySelector('[data-paginate="next"]')!;
52
+ const previous = pagination.querySelector('[data-paginate="previous"]')!;
53
+ const first = pagination.querySelector('[data-paginate="first"]')!;
54
+ const last = pagination.querySelector('[data-paginate="last"]')!;
55
+ const pageSizeSelect = pagination.querySelector('.page-size-select')!;
56
+ const currentPageInput = pagination.querySelector('#current-value-input')!;
57
+
58
+ userEvent.click(next);
59
+
60
+ await waitFor(() => expect(onPageChange).toHaveBeenCalledWith(11));
61
+
62
+ userEvent.click(previous);
63
+ await waitFor(() => expect(onPageChange).toHaveBeenCalledWith(9));
64
+
65
+ userEvent.click(first);
66
+ await waitFor(() => expect(onPageChange).toHaveBeenCalledWith(0));
67
+
68
+ userEvent.click(last);
69
+ await waitFor(() => expect(onPageChange).toHaveBeenCalledWith(50));
70
+
71
+ userEvent.click(pageSizeSelect);
72
+
73
+ const option25 = pageSizeSelect.querySelector('[data-value="25"]')!;
74
+
75
+ userEvent.click(option25);
76
+
77
+ await waitFor(() => expect(onPageSizeChange).toHaveBeenCalledWith(25));
78
+
79
+ (currentPageInput as HTMLInputElement).focus();
80
+
81
+ userEvent.keyboard('{backspace}{backspace}30{enter}');
82
+
83
+ await waitFor(() => expect(onPageChange).toHaveBeenCalledWith(30));
84
+ });
85
+ });
86
+
87
+ describe('different current pages and their effect on what renders', () => {
88
+ it('is on the first page and does not render previous and first', () => {
89
+ const { pagination } = createPagination();
90
+
91
+ expect(pagination.querySelector('.next')).toBeTruthy();
92
+ expect(pagination.querySelector('.previous')).toBeFalsy();
93
+ });
94
+
95
+ it('is on the second page and does not render first', () => {
96
+ const { pagination } = createPagination((defaultParams) => ({
97
+ ...defaultParams,
98
+ currentPage: 2,
99
+ }));
100
+
101
+ expect(pagination.querySelector('[data-paginate="first"]')).toBeFalsy();
102
+ expect((pagination.querySelector('.current-value-input input') as HTMLInputElement).value).toBe(
103
+ '2'
104
+ );
105
+ });
106
+
107
+ it('is on the second to last page and does not render last', () => {
108
+ const { pagination } = createPagination((defaultParams) => ({
109
+ ...defaultParams,
110
+ currentPage: 499,
111
+ }));
112
+
113
+ expect(pagination.querySelector('[data-paginate="last"]')).toBeFalsy();
114
+ });
115
+
116
+ it('is on the last page and does not render next & last', () => {
117
+ const { pagination } = createPagination((defaultParams) => ({
118
+ ...defaultParams,
119
+ currentPage: 500,
120
+ }));
121
+
122
+ expect(pagination.querySelector('[data-paginate="last"]')).toBeFalsy();
123
+ expect(pagination.querySelector('[data-paginate="next"]')).toBeFalsy();
124
+ });
125
+ });
126
+
127
+ describe('omitted attributes still renders correctly', () => {
128
+ it("still renders next if totalItems prop isn't given and we're on the first page", () => {
129
+ const { pagination } = createPagination((defaultParams) => ({
130
+ ...defaultParams,
131
+ totalElements: undefined,
132
+ }));
133
+
134
+ expect(pagination.querySelector('.page')).toBeFalsy();
135
+ expect(pagination.querySelector('.next')).toBeTruthy();
136
+ expect(pagination.querySelector('.previous')).toBeFalsy();
137
+ });
138
+
139
+ it("still renders next if totalItems prop isn't given and we're on the first page", () => {
140
+ const { pagination } = createPagination((defaultParams) => ({
141
+ ...defaultParams,
142
+ currentPage: 4,
143
+ totalElements: undefined,
144
+ }));
145
+
146
+ expect(pagination.querySelector('.page')).toBeFalsy();
147
+ expect(pagination.querySelector('.next')).toBeTruthy();
148
+ expect(pagination.querySelector('.previous')).toBeTruthy();
149
+ });
150
+ });
151
+
152
+ describe('ref should work', () => {
153
+ it('should give back the proper data prop, this also checks if the component propagates ...rest properly', () => {
154
+ const ExampleComponent = ({
155
+ propagateRef,
156
+ }: {
157
+ propagateRef?: (ref: React.RefObject<HTMLElement>) => void;
158
+ }) => {
159
+ const ref = useRef(null);
160
+
161
+ useEffect(() => {
162
+ if (ref.current) {
163
+ propagateRef && propagateRef(ref);
164
+ }
165
+ }, [ref]);
166
+
167
+ return <Pagination {...defaultParams} data-ref="testing" ref={ref} />;
168
+ };
169
+
170
+ const refCheck = (ref: React.RefObject<HTMLElement>) => {
171
+ expect(ref.current).toHaveAttribute('data-ref', 'testing');
172
+ };
173
+
174
+ render(<ExampleComponent propagateRef={refCheck} />);
175
+ });
176
+ });
@@ -0,0 +1,205 @@
1
+ import React, { ComponentPropsWithRef, Fragment, useState } from 'react';
2
+ import classes from './Pagination.module.scss';
3
+ import readyclasses from '../readyclasses.module.scss';
4
+ import { IconButton } from '../Button/IconButton';
5
+ import { Icons, Icon } from '../Icon/Icon';
6
+ import { Input } from '../Form/Input/Input';
7
+ import { Select } from '../Form/Select/Select';
8
+ import { Option } from '../Form/Select/Option';
9
+ import { Label } from '../Form/Label/Label';
10
+
11
+ export type PaginationTranslations = {
12
+ totalItems: string;
13
+ itemsPerPage: string;
14
+ itemsPerPageLabel: string;
15
+ currentPage: string;
16
+ currentPageLabel: string;
17
+ };
18
+
19
+ export type PageChangeLabels = 'next' | 'previous' | 'first' | 'last';
20
+
21
+ enum DefaultTranslations {
22
+ totalItems = 'Total items',
23
+ itemsPerPage = 'Items per page',
24
+ currentPage = 'Page %1 of %2',
25
+ itemsPerPageLabel = 'Select how many items per page you want to see.',
26
+ currentPageLabel = 'What page you are currently on.',
27
+ }
28
+
29
+ export type PageSize = 10 | 25 | 50;
30
+
31
+ export interface Props extends Omit<ComponentPropsWithRef<'div'>, 'translate'> {
32
+ currentPage?: number;
33
+ totalElements?: number;
34
+ pageSize?: PageSize;
35
+ translate?: PaginationTranslations;
36
+ onPageChange: (pageToGoTo: number) => void;
37
+ onPageSizeChange: (pageSize: PageSize) => void;
38
+ }
39
+
40
+ export const Pagination = React.forwardRef<HTMLDivElement, Props>(
41
+ (
42
+ {
43
+ totalElements,
44
+ pageSize = 10,
45
+ translate = DefaultTranslations,
46
+ currentPage,
47
+ className,
48
+ onPageChange,
49
+ onPageSizeChange,
50
+ ...rest
51
+ }: Props,
52
+ ref
53
+ ) => {
54
+ /** We use an internal state variable, because we don't want to fire onCurrentPageChange whenever onChange fires on the input. Rather, only when the Enter key is pressed. */
55
+ const [internalCurrentPage, setInternalCurrentPage] = useState(currentPage?.toString() || '1');
56
+ const calculateAmountOfPages = () => (totalElements ? Math.ceil(totalElements / pageSize) : 0);
57
+
58
+ const onEnterListener = (event: React.KeyboardEvent<HTMLInputElement>) => {
59
+ if (event.code === 'Enter') {
60
+ onPageChange(Number(internalCurrentPage));
61
+ }
62
+ };
63
+
64
+ const renderCurrentPageTranslation = () => {
65
+ const amountOfPages = calculateAmountOfPages();
66
+
67
+ if (amountOfPages) {
68
+ const splitCurrentPageTranslation = translate.currentPage.split(' ');
69
+
70
+ return splitCurrentPageTranslation.map((string) => {
71
+ if (string.includes('%1')) {
72
+ return (
73
+ <Fragment key={string}>
74
+ <Label
75
+ id="current-value-input-label"
76
+ htmlFor="current-value-input"
77
+ className={readyclasses['sr-only']}
78
+ >
79
+ {translate.currentPageLabel}
80
+ </Label>
81
+ <Input
82
+ aria-labelledby="current-value-input-label"
83
+ key="input"
84
+ id="current-value-input"
85
+ type="text"
86
+ size={currentPage?.toString().length}
87
+ max={calculateAmountOfPages()}
88
+ wrapperProps={{ className: classes['current-value-input'] }}
89
+ onKeyUp={onEnterListener}
90
+ onBlur={(event: React.ChangeEvent<HTMLInputElement>) =>
91
+ onPageChange(Number(event.target.value))
92
+ }
93
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
94
+ setInternalCurrentPage(e.target.value)
95
+ }
96
+ name="current-value-input"
97
+ value={internalCurrentPage}
98
+ className={`${classes['form-element']} ${classes['current-page-input']}`}
99
+ />
100
+ </Fragment>
101
+ );
102
+ }
103
+
104
+ if (string.includes('%2')) {
105
+ return <div key={string}>{string.replace('%2', amountOfPages.toString())}&nbsp;</div>;
106
+ }
107
+
108
+ return <div key={string}>{string}&nbsp;</div>;
109
+ });
110
+ }
111
+
112
+ return null;
113
+ };
114
+
115
+ const onPageSizeChangeHandler = (event: React.ChangeEvent<HTMLSelectElement>) => {
116
+ const pageSizeNumber = Number(event.target.value) as PageSize;
117
+ onPageSizeChange(pageSizeNumber);
118
+ };
119
+
120
+ const onPageChangeHandler = (pageToGoTo: number) => {
121
+ onPageChange(pageToGoTo);
122
+ };
123
+
124
+ return (
125
+ <div
126
+ {...rest}
127
+ ref={ref}
128
+ className={`${classes['pagination-wrapper']} ${className ? className : ''}`}
129
+ >
130
+ {totalElements && (
131
+ <div className={classes['total']}>
132
+ <span tabIndex={0}>
133
+ {translate.totalItems}: <span>{totalElements}</span>
134
+ </span>
135
+ </div>
136
+ )}
137
+ <div className={classes['pagination']}>
138
+ {totalElements && pageSize && (
139
+ <div className={classes['per-page']}>
140
+ <Label id="page-size-select-label">{translate.itemsPerPage}</Label>
141
+ <Select
142
+ labeledBy="page-size-select-label"
143
+ className={`${classes['form-element']} ${classes['page-size-select']}`}
144
+ value={pageSize.toString()}
145
+ onChange={onPageSizeChangeHandler}
146
+ >
147
+ <Option value="10">10</Option>
148
+ <Option value="25">25</Option>
149
+ <Option value="50">50</Option>
150
+ </Select>
151
+ </div>
152
+ )}
153
+ <Fragment>
154
+ {((currentPage && currentPage > 2) || (currentPage && currentPage > 1)) && (
155
+ <div className={classes['previous']}>
156
+ {currentPage > 2 && (
157
+ <IconButton
158
+ title="first"
159
+ onClick={() => onPageChangeHandler(0)}
160
+ data-paginate="first"
161
+ >
162
+ <Icon icon={Icons.NavigationFirst} />
163
+ </IconButton>
164
+ )}
165
+ {currentPage > 1 && (
166
+ <IconButton
167
+ title="previous"
168
+ onClick={() => onPageChangeHandler(currentPage - 1)}
169
+ data-paginate="previous"
170
+ >
171
+ <Icon icon={Icons.ChevronLeft} />
172
+ </IconButton>
173
+ )}
174
+ </div>
175
+ )}
176
+ {totalElements && (
177
+ <div className={classes['page']}>{renderCurrentPageTranslation()}</div>
178
+ )}
179
+ <div className={classes['next']}>
180
+ {((currentPage && currentPage < calculateAmountOfPages()) ||
181
+ (currentPage && !totalElements)) && (
182
+ <IconButton
183
+ title="next"
184
+ onClick={() => onPageChangeHandler(currentPage + 1)}
185
+ data-paginate="next"
186
+ >
187
+ <Icon icon={Icons.ChevronRight} />
188
+ </IconButton>
189
+ )}
190
+ {currentPage && totalElements && currentPage < calculateAmountOfPages()! - 1 && (
191
+ <IconButton
192
+ title="last"
193
+ onClick={() => onPageChangeHandler(totalElements / pageSize)}
194
+ data-paginate="last"
195
+ >
196
+ <Icon icon={Icons.NavigationLast} />
197
+ </IconButton>
198
+ )}
199
+ </div>
200
+ </Fragment>
201
+ </div>
202
+ </div>
203
+ );
204
+ }
205
+ );
@@ -1,8 +1,8 @@
1
- import React, { forwardRef, HTMLProps, ReactNode, RefObject, useEffect, useRef } from 'react';
1
+ import React, { ComponentPropsWithRef, ReactNode, RefObject, useEffect, useRef } from 'react';
2
2
  import { usePosition, Offset, Placement } from '../hooks/usePosition';
3
3
  import classes from './Popover.module.scss';
4
4
 
5
- export interface Props extends HTMLProps<HTMLDivElement> {
5
+ export interface Props extends ComponentPropsWithRef<'div'> {
6
6
  children?: ReactNode;
7
7
  show?: boolean;
8
8
  anchorEl?: RefObject<HTMLOrSVGElement>;
@@ -11,7 +11,7 @@ export interface Props extends HTMLProps<HTMLDivElement> {
11
11
  transformOrigin?: Placement;
12
12
  }
13
13
 
14
- export const Popover = forwardRef<HTMLDivElement, Props>(
14
+ export const Popover = React.forwardRef<HTMLDivElement, Props>(
15
15
  ({ children, className, show, placement, offset, transformOrigin, anchorEl, ...rest }, ref) => {
16
16
  const elToBePositioned = useRef<HTMLDivElement>(null);
17
17
 
@@ -0,0 +1,71 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { Tabs } from './Tabs';
3
+ import { Tab, Props } from './Tab';
4
+ import { render } from '@testing-library/react';
5
+
6
+ const defaultParams: Props = {
7
+ title: 'Title of tab',
8
+ };
9
+
10
+ const createTab = (params?: (defaultParams: Props) => Props) => {
11
+ let parameters: Props = defaultParams;
12
+ if (params) {
13
+ parameters = params(defaultParams);
14
+ }
15
+ const queries = render(
16
+ <Tab {...parameters} data-testid="tab">
17
+ tab content
18
+ </Tab>
19
+ );
20
+ const tab = queries.getByTestId('tab');
21
+
22
+ return {
23
+ ...queries,
24
+ tab,
25
+ };
26
+ };
27
+
28
+ describe('Tab should render', () => {
29
+ it('renders without crashing', () => {
30
+ const { tab } = createTab();
31
+
32
+ expect(tab).toBeTruthy();
33
+ });
34
+ });
35
+
36
+ describe('Tab useRef should work for panel and button', () => {
37
+ it('gives us back the proper refs', () => {
38
+ type logRefsFunction = (
39
+ buttonRef: React.RefObject<HTMLButtonElement>,
40
+ panelRef: React.RefObject<HTMLDivElement>
41
+ ) => void;
42
+ const ExampleComponent = ({ logRefs }: { logRefs: logRefsFunction }) => {
43
+ const ref1 = useRef<HTMLButtonElement>(null);
44
+ const ref2 = useRef<HTMLDivElement>(null);
45
+
46
+ useEffect(() => {
47
+ if (ref1.current && ref2.current) {
48
+ logRefs(ref1, ref2);
49
+ }
50
+ }, [ref1, ref2]);
51
+
52
+ return (
53
+ <Tabs>
54
+ <Tab buttonRef={ref1} panelRef={ref2} title="ShouldBeButtonRef">
55
+ <span>Should be panel ref</span>
56
+ </Tab>
57
+ </Tabs>
58
+ );
59
+ };
60
+
61
+ const setCorrectText = (
62
+ buttonRef: React.RefObject<HTMLButtonElement>,
63
+ panelRef: React.RefObject<HTMLDivElement>
64
+ ) => {
65
+ expect(buttonRef.current?.innerHTML).toContain('ShouldBeButtonRef');
66
+ expect(panelRef.current?.innerHTML).toEqual('<span>Should be panel ref</span>');
67
+ };
68
+
69
+ render(<ExampleComponent logRefs={setCorrectText} />);
70
+ });
71
+ });