@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
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useEffect, useRef } from 'react';
2
2
  import { Toggle } from './Toggle';
3
3
  import { render } from '@testing-library/react';
4
4
 
@@ -14,6 +14,32 @@ describe('Toggle should render', () => {
14
14
  });
15
15
  });
16
16
 
17
+ describe('ref should work', () => {
18
+ it('should give back the proper data prop, this also checks if the component propagates ...rest properly', () => {
19
+ const ExampleComponent = ({
20
+ propagateRef,
21
+ }: {
22
+ propagateRef?: (ref: React.RefObject<HTMLElement>) => void;
23
+ }) => {
24
+ const ref = useRef(null);
25
+
26
+ useEffect(() => {
27
+ if (ref.current) {
28
+ propagateRef && propagateRef(ref);
29
+ }
30
+ }, [ref]);
31
+
32
+ return <Toggle name="test" children="test" data-ref="testing" ref={ref} />;
33
+ };
34
+
35
+ const refCheck = (ref: React.RefObject<HTMLElement>) => {
36
+ expect(ref.current).toHaveAttribute('data-ref', 'testing');
37
+ };
38
+
39
+ render(<ExampleComponent propagateRef={refCheck} />);
40
+ });
41
+ });
42
+
17
43
  describe('Toggle attributes', () => {
18
44
  it('should be checked', () => {
19
45
  const { getByTestId } = render(
@@ -1,20 +1,33 @@
1
- import React from 'react';
1
+ import React, { ComponentPropsWithRef } from 'react';
2
2
  import { Checkbox, CheckboxProps } from '../Checkbox/Checkbox';
3
3
  import classes from './Toggle.module.scss';
4
4
 
5
5
  export interface Props
6
- extends Omit<CheckboxProps, 'indeterminate' | 'errorMessage' | 'error' | 'children'> {
6
+ extends ComponentPropsWithRef<'input'>,
7
+ Omit<CheckboxProps, 'indeterminate' | 'errorMessage' | 'error' | 'children' | 'label'> {
7
8
  children: string;
8
9
  }
9
10
 
10
- export const Toggle = ({ children, checked, ...rest }: Props) => (
11
- <div className={classes['toggle-wrapper']}>
12
- <Checkbox {...rest} checked={checked} className={classes.checkbox} label={children}>
13
- <span
14
- data-toggle
15
- aria-hidden="true"
16
- className={`${classes.toggle} ${checked ? classes.checked : ''} `}
17
- ></span>
18
- </Checkbox>
19
- </div>
11
+ export const Toggle = React.forwardRef<HTMLInputElement, Props>(
12
+ ({ children, checked, disabled, ...rest }: Props, ref) => (
13
+ <div className={classes['toggle-wrapper']}>
14
+ <Checkbox
15
+ {...rest}
16
+ ref={ref}
17
+ checked={checked}
18
+ className={classes['checkbox']}
19
+ helperProps={{ className: classes['toggle-helper'] }}
20
+ disabled={disabled}
21
+ label={children}
22
+ >
23
+ <span
24
+ data-toggle
25
+ aria-hidden="true"
26
+ className={`${classes['toggle']} ${checked ? classes['checked'] : ''} ${
27
+ disabled ? classes['disabled'] : ''
28
+ } `}
29
+ ></span>
30
+ </Checkbox>
31
+ </div>
32
+ )
20
33
  );
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useEffect, useRef } from 'react';
2
2
  import { CheckboxWrapper, Props } from './CheckboxWrapper';
3
3
  import { Checkbox, CheckboxProps } from '../../Checkbox/Checkbox';
4
4
  import { render } from '@testing-library/react';
@@ -63,6 +63,32 @@ describe('checkboxwrapper should render', () => {
63
63
  });
64
64
  });
65
65
 
66
+ describe('ref should work', () => {
67
+ it('should give back the proper data prop, this also checks if the component propagates ...rest properly', () => {
68
+ const ExampleComponent = ({
69
+ propagateRef,
70
+ }: {
71
+ propagateRef?: (ref: React.RefObject<HTMLElement>) => void;
72
+ }) => {
73
+ const ref = useRef(null);
74
+
75
+ useEffect(() => {
76
+ if (ref.current) {
77
+ propagateRef && propagateRef(ref);
78
+ }
79
+ }, [ref]);
80
+
81
+ return <CheckboxWrapper {...defaultParams} name="test" data-ref="testing" ref={ref} />;
82
+ };
83
+
84
+ const refCheck = (ref: React.RefObject<HTMLElement>) => {
85
+ expect(ref.current).toHaveAttribute('data-ref', 'testing');
86
+ };
87
+
88
+ render(<ExampleComponent propagateRef={refCheck} />);
89
+ });
90
+ });
91
+
66
92
  describe('CheckboxWrapper should have an error', () => {
67
93
  it('should have an error and the children checkboxes should have aria-describedby of the error message of the group.', () => {
68
94
  const { checkboxwrapper } = createCheckboxWrapper((defaultParams) => ({
@@ -1,62 +1,59 @@
1
- import React, { ReactElement, useEffect } from 'react';
1
+ import React, { ComponentPropsWithRef, ReactElement, useEffect } from 'react';
2
2
  import classes from './CheckboxWrapper.module.scss';
3
3
  import { useWrapper } from '../../../hooks/useWrapper';
4
4
  import { Wrapper, WrapperProps } from '../Wrapper/Wrapper';
5
5
  import { Icons } from '../../../Icon/Icon';
6
6
  import { Fieldset, Props as FieldsetProps } from '../../../Form/Fieldset/Fieldset';
7
7
 
8
- export interface Props extends WrapperProps {
8
+ export interface Props extends ComponentPropsWithRef<'div'>, WrapperProps {
9
9
  children: ReactElement[] | ReactElement;
10
10
  fieldsetProps: FieldsetProps;
11
+ onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
11
12
  }
12
13
 
13
- export const CheckboxWrapper = ({
14
- children,
15
- error = false,
16
- helperText,
17
- helperProps,
18
- fieldsetProps,
19
- ...rest
20
- }: Props) => {
21
- const { errorId, helperId } = useWrapper();
14
+ export const CheckboxWrapper = React.forwardRef<HTMLDivElement, Props>(
15
+ ({ children, error = false, helperText, helperProps, fieldsetProps, ...rest }: Props, ref) => {
16
+ const { errorId, helperId } = useWrapper();
22
17
 
23
- useEffect(() => {
24
- if (fieldsetProps.legend === undefined) {
25
- console.error(
26
- `You should give your Fieldset component a title prop so a legend element is rendered. This error was thrown in CheckboxWrapper. You can add this title prop through the fieldsetProps prop by passing an object (fieldsetProps={{ title: "title here" }})`
18
+ useEffect(() => {
19
+ if (fieldsetProps.legend === undefined) {
20
+ console.error(
21
+ `You should give your Fieldset component a legend prop so a legend element is rendered. This error was thrown in CheckboxWrapper. You can add this legend prop through the fieldsetProps prop by passing an object (fieldsetProps={{ legend: "legend here" }})`
22
+ );
23
+ }
24
+ }, []);
25
+
26
+ const renderChildren = () =>
27
+ React.Children.map(children, (child) =>
28
+ React.cloneElement(child, {
29
+ parentErrorId: errorId,
30
+ error: error,
31
+ parentHelperId: helperText ? helperId : false,
32
+ disabled: rest.disabled,
33
+ })
27
34
  );
28
- }
29
- }, []);
30
35
 
31
- const renderChildren = () =>
32
- React.Children.map(children, (child) =>
33
- React.cloneElement(child, {
34
- parentErrorId: errorId,
35
- error: error,
36
- parentHelperId: helperText ? helperId : false,
37
- disabled: rest.disabled,
38
- })
36
+ return (
37
+ <Fieldset {...fieldsetProps} error={error} required={rest.required}>
38
+ <Wrapper
39
+ {...rest}
40
+ ref={ref}
41
+ label=""
42
+ helperId={helperId}
43
+ helperText={helperText}
44
+ helperProps={{
45
+ ...helperProps,
46
+ className: `${classes['checkbox-wrapper-helper']} ${
47
+ error ? classes['checkbox-wrapper-error'] : ''
48
+ } ${helperProps?.className ?? ''}`,
49
+ }}
50
+ error={error}
51
+ errorMessageIcon={Icons.Error}
52
+ errorId={errorId}
53
+ >
54
+ {renderChildren()}
55
+ </Wrapper>
56
+ </Fieldset>
39
57
  );
40
-
41
- return (
42
- <Fieldset {...fieldsetProps}>
43
- <Wrapper
44
- {...rest}
45
- label=""
46
- helperId={helperId}
47
- helperText={helperText}
48
- helperProps={{
49
- ...helperProps,
50
- className: `${classes['checkbox-wrapper-helper']} ${
51
- error ? classes['checkbox-wrapper-error'] : ''
52
- } ${helperProps?.className ?? ''}`,
53
- }}
54
- error={error}
55
- errorMessageIcon={Icons.Warning}
56
- errorId={errorId}
57
- >
58
- {renderChildren()}
59
- </Wrapper>
60
- </Fieldset>
61
- );
62
- };
58
+ }
59
+ );
@@ -22,3 +22,14 @@ input {
22
22
  transform: translateY(5px);
23
23
  font-size: 1.125rem;
24
24
  }
25
+
26
+ .input-wrapper [data-prefix],
27
+ .input-wrapper [data-suffix] {
28
+ transform: translateY(-0.125rem);
29
+ }
30
+
31
+ .floating-label-active [data-prefix],
32
+ .floating-label-active [data-suffix] {
33
+ padding-top: 0.5rem;
34
+ transform: translateY(0);
35
+ }
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useEffect, useRef } from 'react';
2
2
  import { InputWrapper, Props } from './InputWrapper';
3
3
  import { render } from '@testing-library/react';
4
4
  import userEvent from '@testing-library/user-event';
@@ -60,6 +60,48 @@ describe('InputWrapper should render', () => {
60
60
  });
61
61
  });
62
62
 
63
+ describe('ref should work', () => {
64
+ it('should give back the proper data prop, this also checks if the component propagates ...rest properly', () => {
65
+ const ExampleComponent = ({
66
+ propagateRef,
67
+ }: {
68
+ propagateRef?: (
69
+ ref: React.RefObject<HTMLElement>,
70
+ innerRef: React.RefObject<HTMLInputElement>
71
+ ) => void;
72
+ }) => {
73
+ const ref = useRef(null);
74
+ const innerRef = useRef(null);
75
+
76
+ useEffect(() => {
77
+ if (ref.current) {
78
+ propagateRef && propagateRef(ref, innerRef);
79
+ }
80
+ }, [ref]);
81
+
82
+ return (
83
+ <InputWrapper
84
+ {...defaultParams}
85
+ inputProps={{ ref: innerRef, 'data-ref': 'inner-testing' }}
86
+ name="test"
87
+ data-ref="testing"
88
+ ref={ref}
89
+ />
90
+ );
91
+ };
92
+
93
+ const refCheck = (
94
+ ref: React.RefObject<HTMLElement>,
95
+ innerRef: React.RefObject<HTMLElement>
96
+ ) => {
97
+ expect(ref.current).toHaveAttribute('data-ref', 'testing');
98
+ expect(innerRef.current).toHaveAttribute('data-ref', 'inner-testing');
99
+ };
100
+
101
+ render(<ExampleComponent propagateRef={refCheck} />);
102
+ });
103
+ });
104
+
63
105
  describe('InputWrapper should be interactive', () => {
64
106
  it('executes the eventlisteners', () => {
65
107
  const onFocusHandler = jest.fn();
@@ -91,3 +133,49 @@ describe('InputWrapper should be interactive', () => {
91
133
  expect(onBlurHandler).toHaveBeenCalled();
92
134
  });
93
135
  });
136
+
137
+ describe('InputWrapper should support prefix and suffix', () => {
138
+ const prefix = 'prefix';
139
+ const suffix = 'suffix';
140
+
141
+ const hasPrefixAndSuffixDefined = (container: HTMLElement) => {
142
+ expect(container.querySelector('[data-prefix]')).toHaveTextContent(prefix);
143
+ expect(container.querySelector('[data-suffix]')).toHaveTextContent(suffix);
144
+ };
145
+
146
+ it('renders prefix and sufix when input has focus', () => {
147
+ const { container, getByTestId } = createInputWrapper((defaultParams) => ({
148
+ ...defaultParams,
149
+ value: '',
150
+ inputProps: { 'data-testid': 'input', prefix, suffix },
151
+ }));
152
+
153
+ const input = getByTestId('input');
154
+
155
+ expect(container.querySelector('[data-prefix]')).toBeNull();
156
+ expect(container.querySelector('[data-suffix]')).toBeNull();
157
+
158
+ userEvent.tab();
159
+ expect(input).toHaveFocus();
160
+ hasPrefixAndSuffixDefined(container);
161
+ });
162
+
163
+ it('renders prefix and sufix when input has value', () => {
164
+ const { container, getByTestId } = createInputWrapper((defaultParams) => ({
165
+ ...defaultParams,
166
+ inputProps: { 'data-testid': 'input', prefix, suffix },
167
+ }));
168
+
169
+ const input = getByTestId('input');
170
+
171
+ hasPrefixAndSuffixDefined(container);
172
+
173
+ userEvent.tab();
174
+ expect(input).toHaveFocus();
175
+ hasPrefixAndSuffixDefined(container);
176
+
177
+ userEvent.tab();
178
+ expect(input).not.toHaveFocus();
179
+ hasPrefixAndSuffixDefined(container);
180
+ });
181
+ });
@@ -1,92 +1,145 @@
1
- import React from 'react';
1
+ import React, { ComponentPropsWithRef, useEffect, useRef, useState } from 'react';
2
2
  import { Input, Type, Props as InputProps } from '../../Input/Input';
3
3
  import classes from './InputWrapper.module.scss';
4
4
  import { Wrapper, WrapperProps } from '../Wrapper/Wrapper';
5
5
  import { useWrapper } from '../../../hooks/useWrapper';
6
6
 
7
- interface OptionalInputProps extends Omit<InputProps, 'type'> {}
7
+ interface PartialInputProps extends Partial<InputProps> {}
8
8
 
9
- export interface Props extends WrapperProps {
9
+ export interface Props extends ComponentPropsWithRef<'div'>, WrapperProps {
10
10
  label: string;
11
11
  type: Type;
12
12
  name: string;
13
- inputProps?: OptionalInputProps;
13
+ inputProps?: PartialInputProps;
14
14
  value: string;
15
15
  onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
16
16
  onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
17
17
  onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
18
18
  }
19
19
 
20
- export const InputWrapper = ({
21
- type,
22
- name,
23
- inputProps,
24
- helperText,
25
- helperProps,
26
- value,
27
- error,
28
- onChange,
29
- onBlur,
30
- onFocus,
31
- ...rest
32
- }: Props) => {
33
- const {
34
- errorId,
35
- floatingLabelActive,
36
- setFloatingLabelActive,
37
- hasFocus,
38
- setHasFocus,
39
- helperId,
40
- labelId,
41
- } = useWrapper(value, inputProps?.placeholder, type);
20
+ const useLabelOffset = (
21
+ wrapper: React.RefObject<HTMLDivElement>,
22
+ input: React.RefObject<HTMLInputElement>,
23
+ floatingLabelActive: boolean,
24
+ prefix?: string
25
+ ) => {
26
+ const [labelOffset, setLabelOffset] = useState({});
42
27
 
43
- return (
44
- <Wrapper
45
- {...rest}
46
- name={name}
47
- className={classes['input-wrapper']}
48
- labelProps={{
49
- id: labelId,
50
- className: `${classes['input-label']} ${hasFocus ? classes['focus'] : ''}`,
51
- }}
52
- floatingLabelActive={floatingLabelActive}
53
- errorId={errorId}
54
- error={error}
55
- helperId={helperId}
56
- helperText={helperText}
57
- helperProps={{
58
- ...helperProps,
59
- className: `${classes['input-wrapper-helper']} ${helperProps?.className ?? ''} `,
60
- }}
61
- helperIndent={20}
62
- >
63
- <Input
64
- {...inputProps}
65
- aria-labelledby={labelId}
66
- aria-describedby={error ? errorId : helperId}
67
- onChange={onChange}
68
- onFocus={(e) => {
69
- onFocus && onFocus(e);
70
- setHasFocus(true);
71
- setFloatingLabelActive(true);
72
- }}
73
- onBlur={(e) => {
74
- onBlur && onBlur(e);
75
- setHasFocus(false);
76
- e.target.value ||
77
- inputProps?.placeholder?.length ||
78
- type === 'datetime-local' ||
79
- type === 'time'
80
- ? setFloatingLabelActive(true)
81
- : setFloatingLabelActive(false);
82
- }}
83
- className={floatingLabelActive ? classes['floating-label'] : ''}
28
+ const resetLabelOffset = () => setLabelOffset({ left: undefined });
29
+
30
+ useEffect(() => {
31
+ if (wrapper.current && input.current && prefix) {
32
+ if (floatingLabelActive) {
33
+ resetLabelOffset();
34
+ } else {
35
+ const prefixDifference =
36
+ input.current.getBoundingClientRect().left -
37
+ wrapper.current.getBoundingClientRect().left +
38
+ 4;
39
+
40
+ setLabelOffset({ left: `${prefixDifference}px` });
41
+ }
42
+ }
43
+ }, [wrapper.current, input.current, prefix, floatingLabelActive]);
44
+
45
+ return { labelOffset };
46
+ };
47
+
48
+ export const InputWrapper = React.forwardRef<HTMLDivElement, Props>(
49
+ (
50
+ {
51
+ type,
52
+ name,
53
+ inputProps,
54
+ helperText,
55
+ helperProps,
56
+ value,
57
+ error,
58
+ onChange,
59
+ onBlur,
60
+ onFocus,
61
+ ...rest
62
+ }: Props,
63
+ ref
64
+ ) => {
65
+ const {
66
+ errorId,
67
+ floatingLabelActive,
68
+ setFloatingLabelActive,
69
+ hasFocus,
70
+ setHasFocus,
71
+ helperId,
72
+ labelId,
73
+ } = useWrapper(value, inputProps?.placeholder, type);
74
+ const { prefix, suffix } = inputProps || {};
75
+ const wrapper = useRef<HTMLDivElement>(null);
76
+ const input = useRef<HTMLInputElement>(null);
77
+ const hasValueOrActiveFloatingLabel = !!value || floatingLabelActive;
78
+ const labelClasses = [classes['input-label']];
79
+ const { labelOffset } = useLabelOffset(
80
+ (ref as React.RefObject<HTMLDivElement>) || wrapper,
81
+ (inputProps && (inputProps.ref as React.RefObject<HTMLInputElement>)) || input,
82
+ floatingLabelActive,
83
+ prefix
84
+ );
85
+
86
+ hasFocus && labelClasses.push(classes['focus']);
87
+
88
+ return (
89
+ <Wrapper
90
+ {...rest}
91
+ ref={ref || wrapper}
84
92
  name={name}
93
+ className={classes['input-wrapper']}
94
+ labelProps={{
95
+ id: labelId,
96
+ className: labelClasses.join(' '),
97
+ style: { ...labelOffset },
98
+ }}
99
+ floatingLabelActive={floatingLabelActive}
100
+ errorId={errorId}
85
101
  error={error}
86
- id={name}
87
- value={value}
88
- type={type}
89
- />
90
- </Wrapper>
91
- );
92
- };
102
+ helperId={helperId}
103
+ helperText={helperText}
104
+ helperProps={{
105
+ ...helperProps,
106
+ className: `${classes['input-wrapper-helper']} ${helperProps?.className ?? ''} `,
107
+ }}
108
+ helperIndent={20}
109
+ >
110
+ <Input
111
+ {...inputProps}
112
+ prefix={hasValueOrActiveFloatingLabel ? prefix : ''}
113
+ suffix={hasValueOrActiveFloatingLabel ? suffix : ''}
114
+ wrapperProps={{ className: floatingLabelActive ? classes['floating-label-active'] : '' }}
115
+ ref={(inputProps && inputProps.ref) || input}
116
+ aria-labelledby={labelId}
117
+ aria-describedby={error ? errorId : helperId}
118
+ onChange={onChange}
119
+ onFocus={(e) => {
120
+ onFocus && onFocus(e);
121
+ setHasFocus(true);
122
+ setFloatingLabelActive(true);
123
+ }}
124
+ onBlur={(e) => {
125
+ onBlur && onBlur(e);
126
+ setHasFocus(false);
127
+ e.target.value ||
128
+ e.target.placeholder ||
129
+ inputProps?.placeholder?.length ||
130
+ type === 'datetime-local' ||
131
+ type === 'time'
132
+ ? setFloatingLabelActive(true)
133
+ : setFloatingLabelActive(false);
134
+ }}
135
+ className={floatingLabelActive ? classes['floating-label'] : ''}
136
+ name={name}
137
+ error={error}
138
+ id={name}
139
+ value={value}
140
+ type={type}
141
+ />
142
+ </Wrapper>
143
+ );
144
+ }
145
+ );