@onewelcome/react-lib-components 0.1.1-alpha → 0.1.2-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 (179) 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 +3 -5
  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 +4 -0
  56. package/dist/interfaces.d.ts +2 -11
  57. package/dist/react-lib-components.cjs.development.js +2023 -1551
  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 +2021 -1553
  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 +27 -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 +60 -55
  86. package/src/Form/Input/Input.module.scss +33 -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 +11 -0
  107. package/src/Form/Wrapper/InputWrapper/InputWrapper.test.tsx +89 -1
  108. package/src/Form/Wrapper/InputWrapper/InputWrapper.tsx +127 -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 +3 -5
  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.test.tsx +27 -1
  117. package/src/Form/Wrapper/Wrapper/Wrapper.tsx +76 -71
  118. package/src/Form/form.interfaces.ts +4 -3
  119. package/src/Icon/Icon.module.scss +4 -0
  120. package/src/Icon/Icon.test.tsx +30 -2
  121. package/src/Icon/Icon.tsx +5 -5
  122. package/src/Link/Link.test.tsx +27 -1
  123. package/src/Link/Link.tsx +4 -6
  124. package/src/Notifications/BaseModal/BaseModal.test.tsx +27 -1
  125. package/src/Notifications/BaseModal/BaseModal.tsx +59 -54
  126. package/src/Notifications/BaseModal/BaseModalActions/BaseModalActions.test.tsx +26 -1
  127. package/src/Notifications/BaseModal/BaseModalActions/BaseModalActions.tsx +11 -9
  128. package/src/Notifications/BaseModal/BaseModalContent/BaseModalContent.test.tsx +27 -1
  129. package/src/Notifications/BaseModal/BaseModalContent/BaseModalContent.tsx +27 -26
  130. package/src/Notifications/BaseModal/BaseModalHeader/BaseModalHeader.test.tsx +29 -1
  131. package/src/Notifications/BaseModal/BaseModalHeader/BaseModalHeader.tsx +18 -16
  132. package/src/Notifications/Dialog/Dialog.test.tsx +39 -1
  133. package/src/Notifications/Dialog/Dialog.tsx +84 -78
  134. package/src/Notifications/Dialog/DialogActions/DialogActions.test.tsx +27 -1
  135. package/src/Notifications/Dialog/DialogActions/DialogActions.tsx +15 -12
  136. package/src/Notifications/Dialog/DialogTitle/DialogTitle.test.tsx +28 -2
  137. package/src/Notifications/Dialog/DialogTitle/DialogTitle.tsx +13 -11
  138. package/src/Notifications/DiscardChangesModal/DiscardChangesDialog/DiscardChangesDialog.test.tsx +41 -1
  139. package/src/Notifications/DiscardChangesModal/DiscardChangesDialog/DiscardChangesDialog.tsx +43 -36
  140. package/src/Notifications/DiscardChangesModal/DiscardChangesModal.test.tsx +52 -1
  141. package/src/Notifications/DiscardChangesModal/DiscardChangesModal.tsx +8 -3
  142. package/src/Notifications/Snackbar/SnackbarItem/SnackbarItem.tsx +1 -1
  143. package/src/Pagination/Pagination.module.scss +120 -0
  144. package/src/Pagination/Pagination.test.tsx +176 -0
  145. package/src/Pagination/Pagination.tsx +205 -0
  146. package/src/Popover/Popover.tsx +3 -3
  147. package/src/Tabs/Tab.test.tsx +71 -0
  148. package/src/Tabs/Tab.tsx +17 -0
  149. package/src/Tabs/TabButton.module.scss +36 -0
  150. package/src/Tabs/TabButton.test.tsx +77 -0
  151. package/src/Tabs/TabButton.tsx +58 -0
  152. package/src/Tabs/TabPanel.module.scss +7 -0
  153. package/src/Tabs/TabPanel.test.tsx +76 -0
  154. package/src/Tabs/TabPanel.tsx +27 -0
  155. package/src/Tabs/Tabs.module.scss +41 -0
  156. package/src/Tabs/Tabs.test.tsx +268 -0
  157. package/src/Tabs/Tabs.tsx +149 -0
  158. package/src/TextEllipsis/TextEllipsis.module.scss +18 -0
  159. package/src/TextEllipsis/TextEllipsis.test.tsx +80 -0
  160. package/src/TextEllipsis/TextEllipsis.tsx +55 -0
  161. package/src/Tiles/Tile.test.tsx +27 -1
  162. package/src/Tiles/Tile.tsx +59 -62
  163. package/src/Tiles/Tiles.test.tsx +27 -1
  164. package/src/Tiles/Tiles.tsx +42 -39
  165. package/src/Tooltip/Tooltip.test.tsx +27 -1
  166. package/src/Tooltip/Tooltip.tsx +104 -92
  167. package/src/Typography/Typography.test.tsx +27 -1
  168. package/src/Typography/Typography.tsx +66 -68
  169. package/src/Wizard/BaseWizardSteps/BaseWizardSteps.tsx +67 -62
  170. package/src/Wizard/WizardSteps/WizardSteps.tsx +24 -21
  171. package/src/_BaseStyling_/BaseStyling.tsx +19 -1
  172. package/src/hooks/useRepeater.test.tsx +139 -0
  173. package/src/hooks/useRepeater.ts +34 -0
  174. package/src/hooks/useSpacing.ts +1 -1
  175. package/src/hooks/useWrapper.ts +7 -2
  176. package/src/index.ts +12 -1
  177. package/src/interfaces.ts +2 -12
  178. package/src/util/helper.test.tsx +38 -1
  179. package/src/util/helper.tsx +21 -0
@@ -0,0 +1,149 @@
1
+ import React, {
2
+ ComponentPropsWithRef,
3
+ createRef,
4
+ MutableRefObject,
5
+ useEffect,
6
+ useRef,
7
+ useState,
8
+ } from 'react';
9
+ import { generateID } from '../util/helper';
10
+ import { Props as TabProps } from './Tab';
11
+ import { TabButton } from './TabButton';
12
+ import { TabPanel } from './TabPanel';
13
+ import classes from './Tabs.module.scss';
14
+
15
+ export interface Props extends ComponentPropsWithRef<'div'> {
16
+ children: React.ReactElement<TabProps> | React.ReactElement<TabProps>[];
17
+ selected?: number;
18
+ 'aria-label'?: string;
19
+ onTabChange?: (index: number) => void;
20
+ }
21
+
22
+ export const Tabs = ({
23
+ children,
24
+ selected = 0,
25
+ 'aria-label': ariaLabel,
26
+ onTabChange,
27
+ className,
28
+ ...rest
29
+ }: Props) => {
30
+ const indicatorRef = useRef<HTMLDivElement>(null);
31
+ const [indicatorPosition, setIndicatorPosition] = useState({ left: 0, top: 0 });
32
+ const [indicatorWidth, setIndicatorWidth] = useState(0);
33
+
34
+ const [max] = useState(React.Children.count(children) - 1);
35
+ const min = 0;
36
+
37
+ const [selectedTab, setSelectedTab] = useState(Math.min(max, Math.max(min, selected)));
38
+ const [focussedTab, setFocussedTab] = useState(-1);
39
+ const [tabIds] = useState([...Array(max)].map(() => generateID()));
40
+ const [tabPanelIds] = useState([...Array(max)].map(() => generateID()));
41
+
42
+ useEffect(() => {
43
+ setSelectedTab(Math.min(max, Math.max(min, selected)));
44
+ setFocussedTab(-1);
45
+ }, [selected]);
46
+
47
+ useEffect(() => {
48
+ calculateIndicatorPosition();
49
+ }, [selectedTab]);
50
+
51
+ const calculateIndicatorPosition = () => {
52
+ const selectedTabButton = (
53
+ tabButtons[selectedTab].ref as MutableRefObject<HTMLButtonElement | null>
54
+ ).current as HTMLButtonElement;
55
+
56
+ setIndicatorPosition({
57
+ left: selectedTabButton.offsetLeft,
58
+ top:
59
+ selectedTabButton.offsetTop +
60
+ selectedTabButton.offsetHeight -
61
+ indicatorRef.current!.offsetHeight,
62
+ });
63
+ setIndicatorWidth(selectedTabButton.offsetWidth);
64
+ };
65
+
66
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
67
+ // do not show focus unless we came here before
68
+ let currentFocussedTab = focussedTab === -1 ? selectedTab : focussedTab;
69
+
70
+ switch (e.code) {
71
+ case 'ArrowLeft':
72
+ setFocussedTab(currentFocussedTab === min ? max : currentFocussedTab - 1);
73
+ break;
74
+ case 'ArrowRight':
75
+ setFocussedTab(currentFocussedTab === max ? min : currentFocussedTab + 1);
76
+ break;
77
+ case 'Home':
78
+ setFocussedTab(min);
79
+ break;
80
+ case 'End':
81
+ setFocussedTab(max);
82
+ break;
83
+ case 'Space':
84
+ case 'Enter':
85
+ activateTab(currentFocussedTab);
86
+ break;
87
+ }
88
+ };
89
+
90
+ const handleBlur = () => {
91
+ setFocussedTab(selectedTab);
92
+ };
93
+
94
+ const activateTab = (index: number) => {
95
+ setSelectedTab(index);
96
+ setFocussedTab(index);
97
+ onTabChange && onTabChange(index);
98
+ };
99
+
100
+ const tabButtons = React.Children.map(children, (child, index) =>
101
+ React.createElement(TabButton, {
102
+ ref: child.props.buttonRef || createRef<HTMLButtonElement>(),
103
+ key: generateID(),
104
+ tabId: tabIds[index],
105
+ tabPanelId: tabPanelIds[index],
106
+ selected: selectedTab === index,
107
+ focussed: focussedTab === index,
108
+ onTabButtonClick: () => activateTab(index),
109
+ children: child.props.title,
110
+ })
111
+ );
112
+
113
+ const tabPanels = React.Children.map(children, (child, index) =>
114
+ React.createElement(TabPanel, {
115
+ ref: child.props.panelRef || createRef<HTMLDivElement>(),
116
+ key: generateID(),
117
+ selected: selectedTab === index,
118
+ tabId: tabIds[index],
119
+ tabPanelId: tabPanelIds[index],
120
+ children: child.props.children,
121
+ })
122
+ );
123
+
124
+ return (
125
+ <div {...rest} className={`${classes['tabs']} ${className ?? ''}`}>
126
+ <div
127
+ role="tablist"
128
+ onKeyDown={handleKeyDown}
129
+ onBlur={handleBlur}
130
+ aria-label={ariaLabel}
131
+ className={classes['tablist']}
132
+ >
133
+ <div className={classes['tabdivider']} />
134
+ {tabButtons}
135
+ <div
136
+ className={classes['indicator']}
137
+ ref={indicatorRef}
138
+ aria-hidden="true"
139
+ tabIndex={-1}
140
+ style={{
141
+ width: indicatorWidth,
142
+ ...indicatorPosition,
143
+ }}
144
+ />
145
+ </div>
146
+ <div className={classes['tabpanels']}>{tabPanels}</div>
147
+ </div>
148
+ );
149
+ };
@@ -0,0 +1,18 @@
1
+ .text-ellipsis {
2
+ overflow: hidden;
3
+ text-overflow: ellipsis;
4
+ white-space: nowrap;
5
+ }
6
+
7
+ .popover {
8
+ max-width: min(75ch, 90%);
9
+ padding: 0.6875rem 0.6875rem 0.6875rem 0.875rem;
10
+ border-radius: 0.25rem;
11
+
12
+ box-shadow: 0rem 0.1875rem 0.375rem rgba(0, 0, 0, 0.29);
13
+ white-space: pre-wrap;
14
+
15
+ transition-property: opacity;
16
+ transform: scale(1);
17
+ z-index: 1;
18
+ }
@@ -0,0 +1,80 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import user from '@testing-library/user-event';
4
+ import { Props, TextEllipsis } from './TextEllipsis';
5
+
6
+ const defaultParams: Props = {};
7
+
8
+ const createTextEllipsis = (params?: (defaultParams: Props) => Props) => {
9
+ let parameters: Props = defaultParams;
10
+ if (params) {
11
+ parameters = params(defaultParams);
12
+ }
13
+ const queries = render(
14
+ <TextEllipsis {...parameters} data-testid="TextEllipsis">
15
+ content
16
+ </TextEllipsis>
17
+ );
18
+ const textEllipsis = queries.getByTestId('TextEllipsis');
19
+
20
+ return {
21
+ ...queries,
22
+ textEllipsis,
23
+ };
24
+ };
25
+
26
+ describe('TextEllipsis should render', () => {
27
+ it('renders without crashing', () => {
28
+ const { textEllipsis, getAllByText } = createTextEllipsis();
29
+
30
+ expect(textEllipsis).toBeDefined();
31
+ expect(getAllByText('content')).toHaveLength(2);
32
+ });
33
+
34
+ it('does not show popover when full text is visible', () => {
35
+ const { textEllipsis, getByRole } = createTextEllipsis();
36
+
37
+ expect(getByRole('tooltip', { hidden: true })).toHaveAttribute('data-hidden', 'true');
38
+ user.hover(textEllipsis);
39
+ expect(getByRole('tooltip', { hidden: true })).toHaveAttribute('data-hidden', 'true');
40
+ });
41
+
42
+ it('shows popover with full text when base text has ellipsis', () => {
43
+ const { textEllipsis, getByRole } = createTextEllipsis();
44
+
45
+ Object.defineProperty(textEllipsis, 'offsetWidth', { configurable: true, value: 1 });
46
+ Object.defineProperty(textEllipsis, 'scrollWidth', { configurable: true, value: 2 });
47
+
48
+ expect(getByRole('tooltip', { hidden: true })).toHaveAttribute('data-hidden', 'true');
49
+ user.hover(textEllipsis);
50
+ expect(getByRole('tooltip', { hidden: true })).toHaveAttribute('data-hidden', 'false');
51
+ user.unhover(textEllipsis);
52
+ expect(getByRole('tooltip', { hidden: true })).toHaveAttribute('data-hidden', 'true');
53
+ });
54
+ });
55
+
56
+ describe('ref should work', () => {
57
+ it('should give back the proper data prop, this also checks if the component propagates ...rest properly', () => {
58
+ const ExampleComponent = ({
59
+ propagateRef,
60
+ }: {
61
+ propagateRef?: (ref: React.RefObject<HTMLElement>) => void;
62
+ }) => {
63
+ const ref = useRef(null);
64
+
65
+ useEffect(() => {
66
+ if (ref.current) {
67
+ propagateRef && propagateRef(ref);
68
+ }
69
+ }, [ref]);
70
+
71
+ return <TextEllipsis data-ref="testing" ref={ref} />;
72
+ };
73
+
74
+ const refCheck = (ref: React.RefObject<HTMLElement>) => {
75
+ expect(ref.current).toHaveAttribute('data-ref', 'testing');
76
+ };
77
+
78
+ render(<ExampleComponent propagateRef={refCheck} />);
79
+ });
80
+ });
@@ -0,0 +1,55 @@
1
+ import React, { useRef, MouseEventHandler, useState, ComponentPropsWithRef } from 'react';
2
+ import { Popover } from '../Popover/Popover';
3
+ import classes from './TextEllipsis.module.scss';
4
+
5
+ export interface Props extends ComponentPropsWithRef<'div'> {
6
+ children?: string;
7
+ popoverClassName?: string;
8
+ }
9
+
10
+ export const TextEllipsis = React.forwardRef<HTMLDivElement, Props>(
11
+ ({ children, popoverClassName, className, ...rest }: Props, ref) => {
12
+ const [showPopover, setShowPopover] = useState(false);
13
+ const textContainer = useRef<HTMLDivElement>(null);
14
+
15
+ const ellipsisVisible = () => {
16
+ if (
17
+ textContainer.current &&
18
+ textContainer.current.offsetWidth < textContainer.current.scrollWidth
19
+ ) {
20
+ return true;
21
+ }
22
+ return false;
23
+ };
24
+
25
+ const onMouseEnter: MouseEventHandler<HTMLDivElement> = () => {
26
+ ellipsisVisible() && setShowPopover(true);
27
+ };
28
+
29
+ const onMouseLeave: MouseEventHandler<HTMLDivElement> = () => {
30
+ ellipsisVisible() && setShowPopover(false);
31
+ };
32
+
33
+ return (
34
+ <div
35
+ {...rest}
36
+ onMouseEnter={onMouseEnter}
37
+ onMouseLeave={onMouseLeave}
38
+ className={`${classes['text-ellipsis']} ${className ?? ''}`}
39
+ ref={ref || textContainer}
40
+ >
41
+ {children}
42
+ <Popover
43
+ aria-hidden={true}
44
+ data-hidden={!showPopover}
45
+ show={showPopover}
46
+ role="tooltip"
47
+ anchorEl={textContainer}
48
+ className={`${classes.popover} ${popoverClassName ?? ''}`}
49
+ >
50
+ {children}
51
+ </Popover>
52
+ </div>
53
+ );
54
+ }
55
+ );
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useEffect, useRef } from 'react';
2
2
  import { Tile, Props } from './Tile';
3
3
  import { render, getByRole } from '@testing-library/react';
4
4
  import { Icon, Icons } from '../Icon/Icon';
@@ -137,3 +137,29 @@ describe('contextmenu', () => {
137
137
  expect(contextMenuItemOnClick).toHaveBeenCalledTimes(3);
138
138
  });
139
139
  });
140
+
141
+ describe('ref should work', () => {
142
+ it('should give back the proper data prop, this also checks if the component propagates ...rest properly', () => {
143
+ const ExampleComponent = ({
144
+ propagateRef,
145
+ }: {
146
+ propagateRef?: (ref: React.RefObject<HTMLElement>) => void;
147
+ }) => {
148
+ const ref = useRef(null);
149
+
150
+ useEffect(() => {
151
+ if (ref.current) {
152
+ propagateRef && propagateRef(ref);
153
+ }
154
+ }, [ref]);
155
+
156
+ return <Tile {...defaultParams} data-ref="testing" ref={ref} />;
157
+ };
158
+
159
+ const refCheck = (ref: React.RefObject<HTMLElement>) => {
160
+ expect(ref.current).toHaveAttribute('data-ref', 'testing');
161
+ };
162
+
163
+ render(<ExampleComponent propagateRef={refCheck} />);
164
+ });
165
+ });
@@ -1,4 +1,4 @@
1
- import React, { HTMLProps, ReactElement, useState } from 'react';
1
+ import React, { ComponentPropsWithRef, ReactElement, useState } from 'react';
2
2
  import { Icon, Icons } from '../Icon/Icon';
3
3
  import classes from './Tile.module.scss';
4
4
  import readyClasses from '../readyclasses.module.scss';
@@ -11,7 +11,7 @@ interface ImageProps {
11
11
  src: string;
12
12
  }
13
13
 
14
- export interface Props extends Omit<HTMLProps<HTMLDivElement>, 'contextMenu'> {
14
+ export interface Props extends ComponentPropsWithRef<'div'> {
15
15
  title: string;
16
16
  imageProps?: ImageProps;
17
17
  enabled?: boolean;
@@ -19,67 +19,64 @@ export interface Props extends Omit<HTMLProps<HTMLDivElement>, 'contextMenu'> {
19
19
  tileAction?: ReactElement<ContextMenuProps> | ReactElement<IconButtonProps>;
20
20
  }
21
21
 
22
- export const Tile = ({
23
- title,
24
- imageProps,
25
- enabled,
26
- className,
27
- loading,
28
- tileAction,
29
- ...rest
30
- }: Props) => {
31
- const [tileDescriptionID] = useState(generateID(20));
22
+ export const Tile = React.forwardRef<HTMLDivElement, Props>(
23
+ ({ title, imageProps, enabled, className, loading, tileAction, ...rest }: Props, ref) => {
24
+ const [tileDescriptionID] = useState(generateID(20));
32
25
 
33
- if (!title) {
34
- throw new Error('Please make sure to pass a title prop to your Tile component.');
35
- }
36
-
37
- const statusMessage = () => {
38
- if (enabled) {
39
- return 'Status: enabled';
26
+ if (!title) {
27
+ throw new Error('Please make sure to pass a title prop to your Tile component.');
40
28
  }
41
29
 
42
- return 'Status: disabled';
43
- };
30
+ const statusMessage = () => {
31
+ if (enabled) {
32
+ return 'Status: enabled';
33
+ }
44
34
 
45
- return (
46
- <article
47
- tabIndex={0}
48
- aria-labelledby={tileDescriptionID}
49
- {...rest}
50
- className={`${classes['tile']} ${loading ? classes['loading'] : ''}`}
51
- >
52
- <header style={{ justifyContent: enabled === undefined ? 'flex-end' : 'space-between' }}>
53
- {enabled === true && (
54
- <Icon
55
- color="var(--success)"
56
- icon={Icons.Checkmark}
57
- className={`${classes['icon']} ${className ?? ''}`}
58
- />
59
- )}
60
- {enabled === false && (
61
- <Icon
62
- color="var(--greyed-out)"
63
- icon={Icons.Forbidden}
64
- className={`${classes['icon']} ${className ?? ''}`}
65
- />
66
- )}
67
- {enabled !== undefined && (
68
- <span id={tileDescriptionID} className={readyClasses['sr-only']}>
69
- {`${title}. ${statusMessage()}`}
70
- </span>
71
- )}
72
- {tileAction ?? null}
73
- </header>
74
- <div className={classes['content']}>
75
- {imageProps && imageProps.src.length > 0 && (
76
- <figure className={classes['image']}>{!loading && <img {...imageProps} alt="" />}</figure>
77
- )}
78
- {(!imageProps || imageProps.src.length === 0) && (
79
- <Icon className={classes['placeholder']} icon={Icons.Image} />
80
- )}
81
- <span className={classes['title']}>{title}</span>
82
- </div>
83
- </article>
84
- );
85
- };
35
+ return 'Status: disabled';
36
+ };
37
+
38
+ return (
39
+ <article
40
+ {...rest}
41
+ tabIndex={0}
42
+ aria-labelledby={tileDescriptionID}
43
+ ref={ref}
44
+ className={`${classes['tile']} ${loading ? classes['loading'] : ''}`}
45
+ >
46
+ <header style={{ justifyContent: enabled === undefined ? 'flex-end' : 'space-between' }}>
47
+ {enabled === true && (
48
+ <Icon
49
+ color="var(--success)"
50
+ icon={Icons.Checkmark}
51
+ className={`${classes['icon']} ${className ?? ''}`}
52
+ />
53
+ )}
54
+ {enabled === false && (
55
+ <Icon
56
+ color="var(--greyed-out)"
57
+ icon={Icons.Forbidden}
58
+ className={`${classes['icon']} ${className ?? ''}`}
59
+ />
60
+ )}
61
+ {enabled !== undefined && (
62
+ <span id={tileDescriptionID} className={readyClasses['sr-only']}>
63
+ {`${title}. ${statusMessage()}`}
64
+ </span>
65
+ )}
66
+ {tileAction ?? null}
67
+ </header>
68
+ <div className={classes['content']}>
69
+ {imageProps && imageProps.src.length > 0 && (
70
+ <figure className={classes['image']}>
71
+ {!loading && <img {...imageProps} alt="" />}
72
+ </figure>
73
+ )}
74
+ {(!imageProps || imageProps.src.length === 0) && (
75
+ <Icon className={classes['placeholder']} icon={Icons.Image} />
76
+ )}
77
+ <span className={classes['title']}>{title}</span>
78
+ </div>
79
+ </article>
80
+ );
81
+ }
82
+ );
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useEffect, useRef } from 'react';
2
2
  import { Tiles, Props } from './Tiles';
3
3
  import { Tile } from './Tile';
4
4
  import { render } from '@testing-library/react';
@@ -118,3 +118,29 @@ describe('loading state should be handled properly', () => {
118
118
  expect(tiles.querySelectorAll('.tile.loading').length).toBe(3);
119
119
  });
120
120
  });
121
+
122
+ describe('ref should work', () => {
123
+ it('should give back the proper data prop, this also checks if the component propagates ...rest properly', () => {
124
+ const ExampleComponent = ({
125
+ propagateRef,
126
+ }: {
127
+ propagateRef?: (ref: React.RefObject<HTMLElement>) => void;
128
+ }) => {
129
+ const ref = useRef(null);
130
+
131
+ useEffect(() => {
132
+ if (ref.current) {
133
+ propagateRef && propagateRef(ref);
134
+ }
135
+ }, [ref]);
136
+
137
+ return <Tiles {...defaultParams} data-ref="testing" ref={ref} />;
138
+ };
139
+
140
+ const refCheck = (ref: React.RefObject<HTMLElement>) => {
141
+ expect(ref.current).toHaveAttribute('data-ref', 'testing');
142
+ };
143
+
144
+ render(<ExampleComponent propagateRef={refCheck} />);
145
+ });
146
+ });
@@ -1,48 +1,51 @@
1
- import React, { HTMLProps, ReactNode } from 'react';
1
+ import React, { ComponentPropsWithRef, ReactNode } from 'react';
2
2
  import classes from './Tiles.module.scss';
3
3
  import { Tile } from './Tile';
4
4
 
5
- export interface Props extends HTMLProps<HTMLDivElement> {
5
+ export interface Props extends ComponentPropsWithRef<'div'> {
6
6
  children: ReactNode;
7
7
  loading?: boolean;
8
8
  }
9
9
 
10
- export const Tiles = ({ children, className, loading = false, ...rest }: Props) => {
11
- const renderChildren = () => {
12
- if (loading) {
13
- return [
14
- <Tile
15
- key="placeholder1"
16
- title="placeholder"
17
- imageProps={{ src: 'placeholder' }}
18
- loading={true}
19
- />,
20
- <Tile
21
- key="placeholder2"
22
- title="placeholder"
23
- imageProps={{ src: 'placeholder' }}
24
- loading={true}
25
- />,
26
- <Tile
27
- key="placeholder3"
28
- title="placeholder"
29
- imageProps={{ src: 'placeholder' }}
30
- loading={true}
31
- />,
32
- ];
33
- }
10
+ export const Tiles = React.forwardRef<HTMLDivElement, Props>(
11
+ ({ children, className, loading = false, ...rest }: Props, ref) => {
12
+ const renderChildren = () => {
13
+ if (loading) {
14
+ return [
15
+ <Tile
16
+ key="placeholder1"
17
+ title="placeholder"
18
+ imageProps={{ src: 'placeholder' }}
19
+ loading={true}
20
+ />,
21
+ <Tile
22
+ key="placeholder2"
23
+ title="placeholder"
24
+ imageProps={{ src: 'placeholder' }}
25
+ loading={true}
26
+ />,
27
+ <Tile
28
+ key="placeholder3"
29
+ title="placeholder"
30
+ imageProps={{ src: 'placeholder' }}
31
+ loading={true}
32
+ />,
33
+ ];
34
+ }
34
35
 
35
- return children;
36
- };
36
+ return children;
37
+ };
37
38
 
38
- return (
39
- <div
40
- {...rest}
41
- className={`${classes['tiles']} ${className ?? ''}`}
42
- aria-live="polite"
43
- aria-busy={loading}
44
- >
45
- {renderChildren()}
46
- </div>
47
- );
48
- };
39
+ return (
40
+ <div
41
+ {...rest}
42
+ ref={ref}
43
+ className={`${classes['tiles']} ${className ?? ''}`}
44
+ aria-live="polite"
45
+ aria-busy={loading}
46
+ >
47
+ {renderChildren()}
48
+ </div>
49
+ );
50
+ }
51
+ );
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useEffect, useRef } from 'react';
2
2
  import { Tooltip, Props } from './Tooltip';
3
3
  import { render } from '@testing-library/react';
4
4
  import userEvent from '@testing-library/user-event';
@@ -70,3 +70,29 @@ describe('It opens the tooltip', () => {
70
70
  expect(tooltipHover).toHaveAttribute('aria-hidden', 'true');
71
71
  });
72
72
  });
73
+
74
+ describe('ref should work', () => {
75
+ it('should give back the proper data prop, this also checks if the component propagates ...rest properly', () => {
76
+ const ExampleComponent = ({
77
+ propagateRef,
78
+ }: {
79
+ propagateRef?: (ref: React.RefObject<HTMLElement>) => void;
80
+ }) => {
81
+ const ref = useRef(null);
82
+
83
+ useEffect(() => {
84
+ if (ref.current) {
85
+ propagateRef && propagateRef(ref);
86
+ }
87
+ }, [ref]);
88
+
89
+ return <Tooltip {...defaultParams} data-ref="testing" ref={ref} />;
90
+ };
91
+
92
+ const refCheck = (ref: React.RefObject<HTMLElement>) => {
93
+ expect(ref.current).toHaveAttribute('data-ref', 'testing');
94
+ };
95
+
96
+ render(<ExampleComponent propagateRef={refCheck} />);
97
+ });
98
+ });