@lumx/react 3.20.1-alpha.8 → 3.21.0

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 (231) hide show
  1. package/_internal/index.js +20 -13
  2. package/_internal/index.js.map +1 -1
  3. package/index.d.ts +60 -33
  4. package/index.js +2937 -3090
  5. package/index.js.map +1 -1
  6. package/package.json +5 -9
  7. package/src/components/alert-dialog/AlertDialog.stories.tsx +1 -1
  8. package/src/components/alert-dialog/AlertDialog.test.tsx +2 -0
  9. package/src/components/autocomplete/Autocomplete.stories.tsx +1 -1
  10. package/src/components/autocomplete/Autocomplete.test.tsx +2 -0
  11. package/src/components/autocomplete/Autocomplete.tsx +1 -1
  12. package/src/components/autocomplete/AutocompleteMultiple.stories.tsx +1 -1
  13. package/src/components/autocomplete/AutocompleteMultiple.test.tsx +2 -0
  14. package/src/components/autocomplete/AutocompleteMultiple.tsx +1 -1
  15. package/src/components/avatar/Avatar.stories.tsx +2 -0
  16. package/src/components/avatar/Avatar.test.tsx +2 -0
  17. package/src/components/avatar/Avatar.tsx +1 -1
  18. package/src/components/badge/Badge.stories.tsx +1 -0
  19. package/src/components/badge/Badge.test.tsx +2 -0
  20. package/src/components/badge/Badge.tsx +1 -1
  21. package/src/components/badge/BadgeWrapper.stories.tsx +1 -0
  22. package/src/components/badge/BadgeWrapper.test.tsx +2 -0
  23. package/src/components/badge/BadgeWrapper.tsx +1 -1
  24. package/src/components/button/Button.stories.tsx +1 -0
  25. package/src/components/button/Button.test.tsx +2 -0
  26. package/src/components/button/Button.tsx +2 -0
  27. package/src/components/button/ButtonGroup.stories.tsx +2 -0
  28. package/src/components/button/ButtonGroup.test.tsx +2 -0
  29. package/src/components/button/ButtonGroup.tsx +2 -0
  30. package/src/components/button/ButtonRoot.tsx +1 -1
  31. package/src/components/button/IconButton.test.tsx +2 -0
  32. package/src/components/button/IconButton.tsx +2 -0
  33. package/src/components/checkbox/Checkbox.test.tsx +2 -0
  34. package/src/components/chip/Chip.stories.tsx +2 -0
  35. package/src/components/chip/Chip.test.tsx +2 -0
  36. package/src/components/chip/Chip.tsx +1 -1
  37. package/src/components/chip/ChipGroup.stories.tsx +2 -0
  38. package/src/components/chip/ChipGroup.test.tsx +2 -0
  39. package/src/components/chip/ChipGroup.tsx +1 -1
  40. package/src/components/comment-block/CommentBlock.stories.tsx +1 -0
  41. package/src/components/comment-block/CommentBlock.test.tsx +1 -0
  42. package/src/components/comment-block/CommentBlock.tsx +1 -1
  43. package/src/components/date-picker/DatePicker.test.tsx +2 -0
  44. package/src/components/date-picker/DatePicker.tsx +1 -1
  45. package/src/components/date-picker/DatePickerControlled.test.tsx +2 -0
  46. package/src/components/date-picker/DatePickerField.test.tsx +2 -0
  47. package/src/components/dialog/Dialog.test.tsx +2 -0
  48. package/src/components/divider/Divider.test.tsx +2 -0
  49. package/src/components/divider/Divider.tsx +2 -0
  50. package/src/components/drag-handle/DragHandle.test.tsx +2 -0
  51. package/src/components/drag-handle/DragHandle.tsx +2 -0
  52. package/src/components/dropdown/Dropdown.stories.tsx +1 -1
  53. package/src/components/dropdown/Dropdown.tsx +1 -1
  54. package/src/components/expansion-panel/ExpansionPanel.test.tsx +2 -0
  55. package/src/components/flag/Flag.test.tsx +2 -0
  56. package/src/components/flag/Flag.tsx +2 -0
  57. package/src/components/flex-box/FlexBox.stories.tsx +2 -0
  58. package/src/components/flex-box/FlexBox.test.tsx +1 -0
  59. package/src/components/flex-box/FlexBox.tsx +1 -1
  60. package/src/components/generic-block/GenericBlock.test.tsx +1 -1
  61. package/src/components/grid/Grid.tsx +2 -0
  62. package/src/components/grid/GridItem.tsx +2 -0
  63. package/src/components/grid-column/GridColumn.stories.tsx +1 -0
  64. package/src/components/grid-column/GridColumn.test.jsx +2 -0
  65. package/src/components/grid-column/GridColumn.tsx +1 -1
  66. package/src/components/heading/Heading.stories.tsx +1 -0
  67. package/src/components/heading/Heading.test.tsx +2 -0
  68. package/src/components/heading/Heading.tsx +2 -0
  69. package/src/components/heading/HeadingLevelProvider.tsx +1 -1
  70. package/src/components/icon/Icon.test.tsx +2 -0
  71. package/src/components/icon/Icon.tsx +120 -7
  72. package/src/components/image-block/ImageBlock.stories.tsx +2 -0
  73. package/src/components/image-block/ImageBlock.test.tsx +1 -0
  74. package/src/components/image-block/ImageBlock.tsx +1 -1
  75. package/src/components/image-block/ImageCaption.tsx +1 -1
  76. package/src/components/image-lightbox/ImageLightbox.stories.tsx +1 -0
  77. package/src/components/image-lightbox/ImageLightbox.test.tsx +2 -0
  78. package/src/components/image-lightbox/types.ts +2 -0
  79. package/src/components/inline-list/InlineList.stories.tsx +1 -0
  80. package/src/components/inline-list/InlineList.test.tsx +2 -0
  81. package/src/components/inline-list/InlineList.tsx +1 -1
  82. package/src/components/input-helper/InputHelper.test.tsx +2 -0
  83. package/src/components/input-helper/InputHelper.tsx +1 -1
  84. package/src/components/input-label/InputLabel.stories.tsx +2 -0
  85. package/src/components/input-label/InputLabel.test.tsx +2 -0
  86. package/src/components/input-label/InputLabel.tsx +1 -1
  87. package/src/components/lightbox/Lightbox.test.tsx +2 -0
  88. package/src/components/lightbox/Lightbox.tsx +1 -1
  89. package/src/components/link/Link.stories.tsx +1 -0
  90. package/src/components/link/Link.test.tsx +2 -0
  91. package/src/components/link/Link.tsx +2 -0
  92. package/src/components/link-preview/LinkPreview.test.tsx +2 -0
  93. package/src/components/link-preview/LinkPreview.tsx +2 -0
  94. package/src/components/list/List.stories.tsx +1 -1
  95. package/src/components/list/List.test.tsx +2 -0
  96. package/src/components/list/List.tsx +1 -1
  97. package/src/components/list/ListDivider.test.tsx +2 -0
  98. package/src/components/list/ListDivider.tsx +2 -0
  99. package/src/components/list/ListItem.test.tsx +2 -0
  100. package/src/components/list/ListItem.tsx +1 -1
  101. package/src/components/list/ListSubheader.test.tsx +2 -0
  102. package/src/components/list/ListSubheader.tsx +1 -1
  103. package/src/components/message/Message.test.tsx +1 -0
  104. package/src/components/message/Message.tsx +1 -1
  105. package/src/components/mosaic/Mosaic.test.tsx +2 -0
  106. package/src/components/mosaic/Mosaic.tsx +1 -1
  107. package/src/components/navigation/Navigation.stories.tsx +2 -0
  108. package/src/components/navigation/Navigation.test.tsx +2 -0
  109. package/src/components/navigation/Navigation.tsx +2 -0
  110. package/src/components/navigation/NavigationItem.test.tsx +2 -0
  111. package/src/components/navigation/NavigationItem.tsx +1 -1
  112. package/src/components/navigation/NavigationSection.test.tsx +2 -0
  113. package/src/components/navigation/NavigationSection.tsx +1 -1
  114. package/src/components/notification/Notification.test.tsx +2 -0
  115. package/src/components/notification/Notification.tsx +1 -1
  116. package/src/components/popover/Popover.test.tsx +2 -0
  117. package/src/components/popover/Popover.tsx +1 -1
  118. package/src/components/popover/usePopoverStyle.tsx +1 -1
  119. package/src/components/popover-dialog/PopoverDialog.test.tsx +1 -0
  120. package/src/components/popover-dialog/PopoverDialog.tsx +2 -0
  121. package/src/components/post-block/PostBlock.test.tsx +2 -0
  122. package/src/components/post-block/PostBlock.tsx +1 -1
  123. package/src/components/progress/Progress.tsx +2 -0
  124. package/src/components/progress/ProgressCircular.stories.tsx +1 -0
  125. package/src/components/progress/ProgressCircular.test.tsx +2 -0
  126. package/src/components/progress/ProgressCircular.tsx +2 -0
  127. package/src/components/progress/ProgressLinear.test.tsx +2 -0
  128. package/src/components/progress/ProgressLinear.tsx +2 -0
  129. package/src/components/progress-tracker/ProgressTracker.stories.tsx +1 -1
  130. package/src/components/progress-tracker/ProgressTracker.test.tsx +2 -0
  131. package/src/components/progress-tracker/ProgressTrackerProvider.test.tsx +2 -0
  132. package/src/components/progress-tracker/ProgressTrackerProvider.tsx +1 -1
  133. package/src/components/progress-tracker/ProgressTrackerStep.test.tsx +2 -0
  134. package/src/components/progress-tracker/ProgressTrackerStep.tsx +1 -1
  135. package/src/components/progress-tracker/ProgressTrackerStepPanel.test.tsx +2 -0
  136. package/src/components/progress-tracker/ProgressTrackerStepPanel.tsx +2 -0
  137. package/src/components/radio-button/RadioButton.test.tsx +2 -0
  138. package/src/components/radio-button/RadioButton.tsx +1 -1
  139. package/src/components/radio-button/RadioGroup.stories.tsx +1 -1
  140. package/src/components/radio-button/RadioGroup.test.tsx +2 -0
  141. package/src/components/radio-button/RadioGroup.tsx +1 -1
  142. package/src/components/select/Select.stories.tsx +1 -1
  143. package/src/components/select/Select.test.tsx +2 -0
  144. package/src/components/select/Select.tsx +1 -1
  145. package/src/components/select/SelectMultiple.stories.tsx +1 -1
  146. package/src/components/select/SelectMultiple.test.tsx +2 -0
  147. package/src/components/select/SelectMultiple.tsx +1 -1
  148. package/src/components/select/WithSelectContext.tsx +1 -1
  149. package/src/components/select/constants.ts +1 -1
  150. package/src/components/side-navigation/SideNavigation.test.tsx +2 -0
  151. package/src/components/side-navigation/SideNavigation.tsx +1 -1
  152. package/src/components/side-navigation/SideNavigationItem.test.tsx +2 -0
  153. package/src/components/side-navigation/SideNavigationItem.tsx +1 -1
  154. package/src/components/skeleton/SkeletonCircle.test.tsx +2 -0
  155. package/src/components/skeleton/SkeletonCircle.tsx +2 -0
  156. package/src/components/skeleton/SkeletonRectangle.test.tsx +2 -0
  157. package/src/components/skeleton/SkeletonRectangle.tsx +2 -0
  158. package/src/components/skeleton/SkeletonTypography.stories.tsx +2 -0
  159. package/src/components/skeleton/SkeletonTypography.test.tsx +2 -0
  160. package/src/components/skeleton/SkeletonTypography.tsx +1 -1
  161. package/src/components/slider/Slider.test.tsx +2 -0
  162. package/src/components/slider/Slider.tsx +1 -1
  163. package/src/components/slideshow/Slideshow.stories.tsx +1 -0
  164. package/src/components/slideshow/Slideshow.test.tsx +2 -0
  165. package/src/components/slideshow/SlideshowControls.stories.tsx +2 -0
  166. package/src/components/slideshow/SlideshowItem.tsx +2 -0
  167. package/src/components/slideshow/useSlideFocusManagement.tsx +1 -1
  168. package/src/components/switch/Switch.test.tsx +2 -0
  169. package/src/components/switch/Switch.tsx +1 -1
  170. package/src/components/table/Table.test.tsx +2 -0
  171. package/src/components/table/Table.tsx +2 -0
  172. package/src/components/table/TableBody.test.tsx +2 -0
  173. package/src/components/table/TableBody.tsx +2 -0
  174. package/src/components/table/TableCell.test.tsx +2 -0
  175. package/src/components/table/TableCell.tsx +2 -0
  176. package/src/components/table/TableHeader.test.tsx +2 -0
  177. package/src/components/table/TableHeader.tsx +2 -0
  178. package/src/components/table/TableRow.test.tsx +2 -0
  179. package/src/components/table/TableRow.tsx +2 -0
  180. package/src/components/tabs/Tab.test.tsx +2 -0
  181. package/src/components/tabs/Tab.tsx +1 -1
  182. package/src/components/tabs/TabList.test.tsx +2 -0
  183. package/src/components/tabs/TabPanel.test.tsx +2 -0
  184. package/src/components/tabs/TabPanel.tsx +2 -0
  185. package/src/components/tabs/TabProvider.test.tsx +2 -0
  186. package/src/components/tabs/TabProvider.tsx +1 -1
  187. package/src/components/tabs/Tabs.stories.tsx +1 -1
  188. package/src/components/text/Text.stories.tsx +1 -1
  189. package/src/components/text/Text.test.tsx +2 -0
  190. package/src/components/text/Text.tsx +2 -0
  191. package/src/components/text-field/RawInputText.stories.tsx +18 -0
  192. package/src/components/text-field/RawInputText.test.tsx +58 -0
  193. package/src/components/text-field/RawInputText.tsx +56 -0
  194. package/src/components/text-field/RawInputTextarea.stories.tsx +22 -0
  195. package/src/components/text-field/RawInputTextarea.test.tsx +63 -0
  196. package/src/components/text-field/RawInputTextarea.tsx +68 -0
  197. package/src/components/text-field/TextField.test.tsx +11 -2
  198. package/src/components/text-field/TextField.tsx +40 -210
  199. package/src/components/text-field/constants.ts +16 -0
  200. package/src/components/text-field/index.ts +3 -1
  201. package/src/components/text-field/useFitRowsToContent.ts +42 -0
  202. package/src/components/thumbnail/Thumbnail.test.tsx +2 -0
  203. package/src/components/toolbar/Toolbar.tsx +1 -1
  204. package/src/components/tooltip/Tooltip.stories.tsx +1 -2
  205. package/src/components/tooltip/Tooltip.test.tsx +1 -1
  206. package/src/components/uploader/Uploader.test.tsx +2 -0
  207. package/src/components/user-block/UserBlock.stories.tsx +2 -0
  208. package/src/components/user-block/UserBlock.test.tsx +2 -0
  209. package/src/hooks/useId.test.tsx +1 -0
  210. package/src/hooks/useInfiniteScroll.tsx +1 -1
  211. package/src/hooks/usePreviousValue.ts +1 -0
  212. package/src/stories/decorators/withChromaticForceScreenSize.tsx +1 -0
  213. package/src/stories/decorators/withNestedProps.tsx +1 -0
  214. package/src/stories/decorators/withThemedBackground.tsx +2 -0
  215. package/src/stories/decorators/withWrapper.tsx +2 -0
  216. package/src/stories/utils/CustomLink.tsx +1 -0
  217. package/src/testing/utils/ThemeSentinel.tsx +1 -0
  218. package/src/utils/ClickAwayProvider/ClickAwayProvider.stories.jsx +1 -1
  219. package/src/utils/ClickAwayProvider/ClickAwayProvider.tsx +1 -1
  220. package/src/utils/MaterialThemeSwitcher/MaterialThemeSwitcher.tsx +1 -1
  221. package/src/utils/Portal/Portal.test.tsx +1 -0
  222. package/src/utils/Portal/PortalProvider.stories.jsx +1 -0
  223. package/src/utils/Portal/PortalProvider.test.tsx +1 -0
  224. package/src/utils/disabled/DisabledStateProvider.stories.tsx +2 -0
  225. package/src/utils/react/OnBeforeUnmount.tsx +1 -1
  226. package/src/utils/react/RawClickable.tsx +1 -1
  227. package/src/utils/react/skipRender.tsx +2 -2
  228. package/src/utils/react/wrapChildrenIconWithSpaces.test.tsx +1 -1
  229. package/src/utils/type/HasPolymorphicAs.ts +2 -0
  230. package/utils/index.d.ts +7 -7
  231. package/utils/index.js +1 -1
@@ -0,0 +1,68 @@
1
+ import React, { ComponentProps, ChangeEventHandler, SyntheticEvent, useRef, useCallback } from 'react';
2
+
3
+ import classNames from 'classnames';
4
+
5
+ import { Theme, useTheme } from '@lumx/react';
6
+ import { forwardRef } from '@lumx/react/utils/react/forwardRef';
7
+ import { useMergeRefs } from '@lumx/react/utils/react/mergeRefs';
8
+ import type { HasClassName, HasTheme } from '@lumx/core/js/types';
9
+
10
+ import { handleBasicClasses } from '@lumx/core/js/utils';
11
+
12
+ import { useFitRowsToContent } from './useFitRowsToContent';
13
+ import { INPUT_NATIVE_CLASSNAME } from './constants';
14
+
15
+ type NativeTextareaProps = ComponentProps<'textarea'>;
16
+
17
+ /**
18
+ * Defines the props of the component.
19
+ */
20
+ export interface RawInputTextareaProps extends Omit<NativeTextareaProps, 'value' | 'onChange'>, HasTheme, HasClassName {
21
+ minimumRows?: number;
22
+ value?: string;
23
+ onChange?: (value: string, name?: string, event?: SyntheticEvent) => void;
24
+ }
25
+
26
+ /**
27
+ * Component default props.
28
+ */
29
+ export const DEFAULT_PROPS: Partial<RawInputTextareaProps> = {
30
+ minimumRows: 2,
31
+ };
32
+
33
+ /**
34
+ * Raw input text area component
35
+ * (textarea element without any decoration)
36
+ */
37
+ export const RawInputTextarea = forwardRef<Omit<RawInputTextareaProps, 'type'>, HTMLTextAreaElement>((props, ref) => {
38
+ const defaultTheme = useTheme() || Theme.light;
39
+ const {
40
+ className,
41
+ theme = defaultTheme,
42
+ minimumRows = DEFAULT_PROPS.minimumRows as number,
43
+ value,
44
+ onChange,
45
+ ...forwardedProps
46
+ } = props;
47
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
48
+
49
+ const rows = useFitRowsToContent(minimumRows, textareaRef, value);
50
+
51
+ const handleChange: ChangeEventHandler<HTMLTextAreaElement> = useCallback(
52
+ (evt) => {
53
+ onChange?.(evt.target.value, evt.target.name, evt);
54
+ },
55
+ [onChange],
56
+ );
57
+
58
+ return (
59
+ <textarea
60
+ className={classNames(className, handleBasicClasses({ prefix: INPUT_NATIVE_CLASSNAME, theme }))}
61
+ ref={useMergeRefs(ref, textareaRef)}
62
+ {...forwardedProps}
63
+ onChange={handleChange}
64
+ value={value}
65
+ rows={rows}
66
+ />
67
+ );
68
+ });
@@ -1,4 +1,6 @@
1
1
  import { Mock } from 'vitest';
2
+ import React from 'react';
3
+
2
4
  import camelCase from 'lodash/camelCase';
3
5
 
4
6
  import { commonTestsSuiteRTL, SetupRenderOptions } from '@lumx/react/testing/utils';
@@ -15,6 +17,7 @@ import partition from 'lodash/partition';
15
17
  import userEvent from '@testing-library/user-event';
16
18
 
17
19
  import { isFocusVisible } from '@lumx/react/utils/browser/isFocusVisible';
20
+ import { createRef } from 'react';
18
21
  import { TextField, TextFieldProps } from './TextField';
19
22
 
20
23
  const CLASSNAME = TextField.className as string;
@@ -52,7 +55,8 @@ describe(`<${TextField.displayName}>`, () => {
52
55
 
53
56
  describe('Render', () => {
54
57
  it('should render defaults', () => {
55
- const { element, inputNative } = setup({ id: 'fixedId' });
58
+ const inputRef = createRef<HTMLInputElement>();
59
+ const { element, inputNative } = setup({ id: 'fixedId', inputRef });
56
60
  expect(element).toBeInTheDocument();
57
61
 
58
62
  expect(element).not.toHaveClass(`${CLASSNAME}--is-valid`);
@@ -65,13 +69,16 @@ describe(`<${TextField.displayName}>`, () => {
65
69
 
66
70
  expect(element).toHaveClass(`${CLASSNAME}--theme-light`);
67
71
  expect(inputNative.tagName).toBe('INPUT');
72
+ expect(inputRef.current).toBe(inputNative);
68
73
  });
69
74
 
70
75
  it('should render textarea', () => {
71
- const { element, inputNative } = setup({ id: 'fixedId', multiline: true });
76
+ const inputRef = createRef<HTMLTextAreaElement>();
77
+ const { element, inputNative } = setup({ id: 'fixedId', multiline: true, inputRef });
72
78
  expect(element).toBeInTheDocument();
73
79
 
74
80
  expect(inputNative.tagName).toBe('TEXTAREA');
81
+ expect(inputRef.current).toBe(inputNative);
75
82
  });
76
83
  });
77
84
 
@@ -174,6 +181,8 @@ describe(`<${TextField.displayName}>`, () => {
174
181
  expect(error).toHaveTextContent('error');
175
182
  expect(helper).toHaveTextContent('helper');
176
183
  expect(inputNative).toHaveAttribute('aria-describedby', expect.stringContaining('aria-description'));
184
+ expect(inputNative).toHaveAttribute('aria-describedby', expect.stringContaining('text-field-error-'));
185
+ expect(inputNative).toHaveAttribute('aria-describedby', expect.stringContaining('text-field-helper-'));
177
186
  });
178
187
  });
179
188
 
@@ -1,7 +1,6 @@
1
- import { ReactNode, Ref, RefObject, SyntheticEvent, useEffect, useRef, useState } from 'react';
1
+ import React, { ReactNode, Ref, RefObject, SyntheticEvent, useRef, useState } from 'react';
2
2
 
3
3
  import classNames from 'classnames';
4
- import get from 'lodash/get';
5
4
 
6
5
  import { mdiAlertCircle, mdiCheckCircle, mdiCloseCircle } from '@lumx/icons';
7
6
  import {
@@ -17,7 +16,7 @@ import {
17
16
  Theme,
18
17
  } from '@lumx/react';
19
18
  import { GenericProps, HasTheme } from '@lumx/react/utils/type';
20
- import { getRootClassName, handleBasicClasses } from '@lumx/core/js/utils/className';
19
+ import { handleBasicClasses } from '@lumx/core/js/utils/className';
21
20
  import { mergeRefs } from '@lumx/react/utils/react/mergeRefs';
22
21
  import { useId } from '@lumx/react/hooks/useId';
23
22
  import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
@@ -25,6 +24,10 @@ import { forwardRef } from '@lumx/react/utils/react/forwardRef';
25
24
  import { useDisableStateProps } from '@lumx/react/utils/disabled/useDisableStateProps';
26
25
  import { HasAriaDisabled } from '@lumx/react/utils/type/HasAriaDisabled';
27
26
 
27
+ import { CLASSNAME, COMPONENT_NAME } from './constants';
28
+ import { RawInputText } from './RawInputText';
29
+ import { RawInputTextarea } from './RawInputTextarea';
30
+
28
31
  /**
29
32
  * Defines the props of the component.
30
33
  */
@@ -86,21 +89,6 @@ export interface TextFieldProps extends GenericProps, HasTheme, HasAriaDisabled
86
89
  onFocus?(event: React.FocusEvent): void;
87
90
  }
88
91
 
89
- /**
90
- * Component display name.
91
- */
92
- const COMPONENT_NAME = 'TextField';
93
-
94
- /**
95
- * Component default class name and class prefix.
96
- */
97
- const CLASSNAME = getRootClassName(COMPONENT_NAME);
98
-
99
- /**
100
- * Default minimum number of rows in the multiline mode.
101
- */
102
- const DEFAULT_MIN_ROWS = 2;
103
-
104
92
  /**
105
93
  * Component default props.
106
94
  */
@@ -108,149 +96,6 @@ const DEFAULT_PROPS: Partial<TextFieldProps> = {
108
96
  type: 'text',
109
97
  };
110
98
 
111
- /**
112
- * Hook that allows to calculate the number of rows needed for a text area.
113
- * @param minimumRows Minimum number of rows that we want to display.
114
- * @return rows to be used and a callback to recalculate
115
- */
116
- const useComputeNumberOfRows = (
117
- minimumRows: number,
118
- ): {
119
- /** number of rows to be used on the text area */
120
- rows: number;
121
- /**
122
- * Callback in order to recalculate the number of rows due to a change on the text area
123
- */
124
- recomputeNumberOfRows(target: Element): void;
125
- } => {
126
- const [rows, setRows] = useState(minimumRows);
127
-
128
- const recompute = (target: Element) => {
129
- /**
130
- * HEAD's UP! This part is a little bit tricky. The idea here is to only
131
- * display the necessary rows on the textarea. In order to dynamically adjust
132
- * the height on that field, we need to:
133
- * 1. Set the current amount of rows to the minimum. That will make the scroll appear.
134
- * 2. With that, we will have the `scrollHeight`, meaning the height of the container adjusted to the current content
135
- * 3. With the scroll height, we can figure out how many rows we need to use by dividing the scroll height
136
- * by the line height.
137
- * 4. With that number, we can readjust the number of rows on the text area. We need to do that here, if we leave that to
138
- * the state change through React, there are some scenarios (resize, hitting ENTER or BACKSPACE which add or remove lines)
139
- * when we will not see the update and the rows will be resized to the minimum.
140
- * 5. In case there is any other update on the component that changes the UI, we need to keep the number of rows
141
- * on the state in order to allow React to re-render. Therefore, we save them using `useState`
142
- */
143
- // eslint-disable-next-line no-param-reassign
144
- (target as HTMLTextAreaElement).rows = minimumRows;
145
- let currentRows = target.scrollHeight / (target.clientHeight / minimumRows);
146
- currentRows = currentRows >= minimumRows ? currentRows : minimumRows;
147
- // eslint-disable-next-line no-param-reassign
148
- (target as HTMLTextAreaElement).rows = currentRows;
149
-
150
- setRows(currentRows);
151
- };
152
-
153
- return {
154
- recomputeNumberOfRows: recompute,
155
- rows,
156
- };
157
- };
158
-
159
- interface InputNativeProps {
160
- id?: string;
161
- inputRef?: TextFieldProps['inputRef'];
162
- isDisabled?: boolean;
163
- 'aria-disabled'?: boolean;
164
- isRequired?: boolean;
165
- readOnly?: boolean;
166
- multiline?: boolean;
167
- maxLength?: number;
168
- placeholder?: string;
169
- rows: number;
170
- type: TextFieldProps['type'];
171
- name?: string;
172
- value?: string;
173
- setFocus(focus: boolean): void;
174
- recomputeNumberOfRows(target: Element): void;
175
- onChange(value: string, name?: string, event?: SyntheticEvent): void;
176
- onFocus?(value: React.FocusEvent): void;
177
- onBlur?(value: React.FocusEvent): void;
178
- hasError?: boolean;
179
- describedById?: string;
180
- }
181
-
182
- const renderInputNative: React.FC<InputNativeProps> = (props) => {
183
- const {
184
- id,
185
- isRequired,
186
- placeholder,
187
- multiline,
188
- value,
189
- setFocus,
190
- onChange,
191
- onFocus,
192
- onBlur,
193
- inputRef,
194
- rows,
195
- recomputeNumberOfRows,
196
- type,
197
- name,
198
- hasError,
199
- describedById,
200
- ...forwardedProps
201
- } = props;
202
- // eslint-disable-next-line react-hooks/rules-of-hooks
203
- const ref = useRef<HTMLElement>(null);
204
-
205
- // eslint-disable-next-line react-hooks/rules-of-hooks
206
- useEffect(() => {
207
- // Recompute the number of rows for the first rendering
208
- if (multiline && ref && ref.current) {
209
- recomputeNumberOfRows(ref.current);
210
- }
211
- }, [ref, multiline, recomputeNumberOfRows, value]);
212
-
213
- const onTextFieldFocus = (event: React.FocusEvent) => {
214
- onFocus?.(event);
215
- setFocus(true);
216
- };
217
-
218
- const onTextFieldBlur = (event: React.FocusEvent) => {
219
- onBlur?.(event);
220
- setFocus(false);
221
- };
222
-
223
- const handleChange = (event: React.ChangeEvent) => {
224
- onChange(get(event, 'target.value'), name, event);
225
- };
226
-
227
- const Component = multiline ? 'textarea' : 'input';
228
- const inputProps: any = {
229
- ...forwardedProps,
230
- id,
231
- className: multiline
232
- ? `${CLASSNAME}__input-native ${CLASSNAME}__input-native--textarea`
233
- : `${CLASSNAME}__input-native ${CLASSNAME}__input-native--text`,
234
- placeholder,
235
- value,
236
- name,
237
- required: isRequired,
238
- onFocus: onTextFieldFocus,
239
- onBlur: onTextFieldBlur,
240
- onChange: handleChange,
241
- 'aria-invalid': hasError ? 'true' : undefined,
242
- 'aria-describedby': describedById,
243
- readOnly: forwardedProps.readOnly || forwardedProps['aria-disabled'],
244
- ref: mergeRefs(inputRef as any, ref) as any,
245
- };
246
- if (multiline) {
247
- inputProps.rows = rows;
248
- } else {
249
- inputProps.type = type;
250
- }
251
- return <Component {...inputProps} />;
252
- };
253
-
254
99
  /**
255
100
  * TextField component.
256
101
  *
@@ -310,7 +155,6 @@ export const TextField = forwardRef<TextFieldProps, HTMLDivElement>((props, ref)
310
155
  const describedById = describedByIds.length === 0 ? undefined : describedByIds.join(' ');
311
156
 
312
157
  const [isFocus, setFocus] = useState(false);
313
- const { rows, recomputeNumberOfRows } = useComputeNumberOfRows(multiline ? minimumRows || DEFAULT_MIN_ROWS : 0);
314
158
  const valueLength = (value || '').length;
315
159
  const isNotEmpty = valueLength > 0;
316
160
 
@@ -339,6 +183,36 @@ export const TextField = forwardRef<TextFieldProps, HTMLDivElement>((props, ref)
339
183
  }
340
184
  };
341
185
 
186
+ const inputProps = {
187
+ id: textFieldId,
188
+ ref: inputRef as any,
189
+ ...disabledStateProps,
190
+ ...forwardedProps,
191
+ required: isRequired,
192
+ maxLength,
193
+ onBlur(evt: React.FocusEvent) {
194
+ setFocus(false);
195
+ onBlur?.(evt);
196
+ },
197
+ onFocus(evt: React.FocusEvent) {
198
+ setFocus(true);
199
+ onFocus?.(evt);
200
+ },
201
+ placeholder,
202
+ value,
203
+ onChange,
204
+ name,
205
+ 'aria-invalid': hasError || undefined,
206
+ 'aria-describedby': describedById,
207
+ readOnly: forwardedProps.readOnly || disabledStateProps['aria-disabled'],
208
+ theme,
209
+ };
210
+ const input = multiline ? (
211
+ <RawInputTextarea {...inputProps} minimumRows={minimumRows} />
212
+ ) : (
213
+ <RawInputText type={type} {...inputProps} />
214
+ );
215
+
342
216
  return (
343
217
  <div
344
218
  ref={ref}
@@ -395,58 +269,14 @@ export const TextField = forwardRef<TextFieldProps, HTMLDivElement>((props, ref)
395
269
  />
396
270
  )}
397
271
 
398
- {chips && (
272
+ {chips ? (
399
273
  <div className={`${CLASSNAME}__chips`}>
400
274
  {chips}
401
275
 
402
- {renderInputNative({
403
- id: textFieldId,
404
- inputRef,
405
- ...disabledStateProps,
406
- isRequired,
407
- maxLength,
408
- multiline,
409
- onBlur,
410
- onChange,
411
- onFocus,
412
- placeholder,
413
- recomputeNumberOfRows,
414
- rows,
415
- setFocus,
416
- type,
417
- value,
418
- name,
419
- hasError,
420
- describedById,
421
- ...forwardedProps,
422
- })}
423
- </div>
424
- )}
425
-
426
- {!chips && (
427
- <div className={`${CLASSNAME}__input-wrapper`}>
428
- {renderInputNative({
429
- id: textFieldId,
430
- inputRef,
431
- ...disabledStateProps,
432
- isRequired,
433
- maxLength,
434
- multiline,
435
- onBlur,
436
- onChange,
437
- onFocus,
438
- placeholder,
439
- recomputeNumberOfRows,
440
- rows,
441
- setFocus,
442
- type,
443
- value,
444
- name,
445
- hasError,
446
- describedById,
447
- ...forwardedProps,
448
- })}
276
+ {input}
449
277
  </div>
278
+ ) : (
279
+ <div className={`${CLASSNAME}__input-wrapper`}>{input}</div>
450
280
  )}
451
281
 
452
282
  {(isValid || hasError) && (
@@ -0,0 +1,16 @@
1
+ import { getRootClassName } from '@lumx/core/js/utils';
2
+
3
+ /**
4
+ * Component display name.
5
+ */
6
+ export const COMPONENT_NAME = 'TextField';
7
+
8
+ /**
9
+ * Component default class name and class prefix.
10
+ */
11
+ export const CLASSNAME = getRootClassName(COMPONENT_NAME);
12
+
13
+ /**
14
+ * Input native element class name.
15
+ */
16
+ export const INPUT_NATIVE_CLASSNAME = `${CLASSNAME}__input-native`;
@@ -1 +1,3 @@
1
- export * from './TextField';
1
+ export { TextField, type TextFieldProps } from './TextField';
2
+ export { RawInputText, type RawInputTextProps } from './RawInputText';
3
+ export { RawInputTextarea, type RawInputTextareaProps } from './RawInputTextarea';
@@ -0,0 +1,42 @@
1
+ import React, { useState } from 'react';
2
+
3
+ /**
4
+ * Fit textarea rows to its content
5
+ */
6
+ export function useFitRowsToContent(
7
+ minimumRows: number,
8
+ textareaRef: React.RefObject<HTMLTextAreaElement>,
9
+ value: string | undefined,
10
+ ) {
11
+ const [rows, setRows] = useState(minimumRows);
12
+ React.useLayoutEffect(() => {
13
+ const { current: textarea } = textareaRef;
14
+ if (!textarea) {
15
+ return;
16
+ }
17
+
18
+ // Save the current number of rows to restore it later and avoid a flicker.
19
+ const previousRows = textarea.rows;
20
+
21
+ // Set the rows to the minimum to get a baseline for row height.
22
+ // This is necessary to get a consistent row height calculation.
23
+ textarea.rows = minimumRows;
24
+ const rowHeight = textarea.clientHeight / minimumRows;
25
+
26
+ // Set rows to 1 to get the smallest possible textarea, which forces
27
+ // scrollHeight to be the actual content height.
28
+ textarea.rows = 1;
29
+ const { scrollHeight } = textarea;
30
+
31
+ // Restore the rows to the previous value.
32
+ textarea.rows = previousRows;
33
+
34
+ // Calculate the number of rows required to display the content.
35
+ // Fallback to `minimumRows` if `rowHeight` is 0.
36
+ const requiredRows = rowHeight > 0 ? Math.ceil(scrollHeight / rowHeight) : minimumRows;
37
+
38
+ // Update the rows state with the new calculated value.
39
+ setRows(Math.max(requiredRows, minimumRows));
40
+ }, [value, minimumRows, textareaRef]);
41
+ return rows;
42
+ }
@@ -1,3 +1,5 @@
1
+ import React from 'react';
2
+
1
3
  import { DisabledStateProvider } from '@lumx/react/utils';
2
4
  import { commonTestsSuiteRTL, SetupRenderOptions } from '@lumx/react/testing/utils';
3
5
  import { queryByClassName } from '@lumx/react/testing/utils/queries';
@@ -1,4 +1,4 @@
1
- import { ReactNode } from 'react';
1
+ import React, { ReactNode } from 'react';
2
2
 
3
3
  import classNames from 'classnames';
4
4
 
@@ -1,6 +1,5 @@
1
- import React, { useState } from 'react';
2
-
3
1
  import { Button, Dialog, Dropdown, Placement, Tooltip } from '@lumx/react';
2
+ import React, { useState } from 'react';
4
3
  import { getSelectArgType } from '@lumx/react/stories/controls/selectArgType';
5
4
  import { withChromaticForceScreenSize } from '@lumx/react/stories/decorators/withChromaticForceScreenSize';
6
5
  import { ARIA_LINK_MODES } from '@lumx/react/components/tooltip/constants';
@@ -1,6 +1,6 @@
1
+ import { MockInstance } from 'vitest';
1
2
  import React from 'react';
2
3
 
3
- import { MockInstance } from 'vitest';
4
4
  import { Button } from '@lumx/react';
5
5
  import { screen, render } from '@testing-library/react';
6
6
  import { queryAllByTagName, queryByClassName } from '@lumx/react/testing/utils/queries';
@@ -1,3 +1,5 @@
1
+ import React from 'react';
2
+
1
3
  import { commonTestsSuiteRTL, SetupRenderOptions } from '@lumx/react/testing/utils';
2
4
 
3
5
  import { render, screen } from '@testing-library/react';
@@ -1,4 +1,6 @@
1
1
  /* eslint-disable jsx-a11y/anchor-is-valid */
2
+ import React from 'react';
3
+
2
4
  import { mdiMenuDown, mdiStar } from '@lumx/icons';
3
5
  import { Badge, ColorPalette, Icon, IconButton, Link, Orientation, Size, Text } from '@lumx/react';
4
6
  import { CustomLink } from '@lumx/react/stories/utils/CustomLink';
@@ -1,3 +1,5 @@
1
+ import React from 'react';
2
+
1
3
  import { commonTestsSuiteRTL, SetupRenderOptions } from '@lumx/react/testing/utils';
2
4
  import { render, within, screen } from '@testing-library/react';
3
5
  import { getByClassName, queryByClassName } from '@lumx/react/testing/utils/queries';
@@ -1,5 +1,6 @@
1
1
  import { useId } from '@lumx/react/hooks/useId';
2
2
  import { render } from '@testing-library/react';
3
+ import React from 'react';
3
4
 
4
5
  function setup() {
5
6
  const Component = (): any => useId();
@@ -1,4 +1,4 @@
1
- import { useEffect } from 'react';
1
+ import React, { useEffect } from 'react';
2
2
 
3
3
  type useInfiniteScrollType = (
4
4
  ref: React.RefObject<HTMLElement>,
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+
2
3
  /**
3
4
  * Returns the value from the previous render.
4
5
  */
@@ -1,3 +1,4 @@
1
+ import React from 'react';
1
2
  import isChromatic from 'chromatic/isChromatic';
2
3
 
3
4
  /** Story decorator used to force a minimum screen size when running in chromatic */
@@ -1,3 +1,4 @@
1
+ import React from 'react';
1
2
  import set from 'lodash/set';
2
3
 
3
4
  /**
@@ -1,3 +1,5 @@
1
+ import React from 'react';
2
+
1
3
  const LIGHT_BACKGROUND_COLOR = '#EEE';
2
4
  const DARK_BACKGROUND_COLOR = '#333';
3
5
 
@@ -1,3 +1,5 @@
1
+ import React from 'react';
2
+
1
3
  /**
2
4
  * SB decorator adding a wrapping div around the story
3
5
  */
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+
2
3
  /**
3
4
  * Example custom link that can be used in linkAs props.
4
5
  */
@@ -1,3 +1,4 @@
1
+ import React from 'react';
1
2
  import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
2
3
 
3
4
  const TEST_ID = 'theme-sentinel';
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable react-hooks/rules-of-hooks */
2
2
  /* eslint-disable @typescript-eslint/no-use-before-define */
3
- import { useCallback, useRef, useState } from 'react';
3
+ import React, { useCallback, useRef, useState } from 'react';
4
4
  import { Button } from '@lumx/react';
5
5
  import { ClickAwayProvider } from '@lumx/react/utils/ClickAwayProvider';
6
6
  import { initDemoShadowDOMPortal } from '@lumx/react/stories/utils/initDemoShadowDOMPortal';
@@ -1,4 +1,4 @@
1
- import { createContext, RefObject, useContext, useEffect, useMemo, useRef } from 'react';
1
+ import React, { createContext, RefObject, useContext, useEffect, useMemo, useRef } from 'react';
2
2
  import { ClickAwayParameters, useClickAway } from '@lumx/react/hooks/useClickAway';
3
3
 
4
4
  interface ContextValue {
@@ -1,4 +1,4 @@
1
- import { useCallback, useState } from 'react';
1
+ import React, { useCallback, useState } from 'react';
2
2
  import { Switch } from '@lumx/react/components/switch';
3
3
  import { Alignment } from '@lumx/react';
4
4
 
@@ -1,3 +1,4 @@
1
+ import React from 'react';
1
2
  import { render, screen } from '@testing-library/react';
2
3
  import { Portal } from './Portal';
3
4
 
@@ -1,3 +1,4 @@
1
+ import React from 'react';
1
2
  import { Button } from '@lumx/react';
2
3
  import { initDemoShadowDOMPortal } from '@lumx/react/stories/utils/initDemoShadowDOMPortal';
3
4
  import { PortalProvider } from './PortalProvider';
@@ -1,3 +1,4 @@
1
+ import React from 'react';
1
2
  import { render, screen } from '@testing-library/react';
2
3
  import { PortalInit, PortalProvider } from './PortalProvider';
3
4
  import { Portal } from './Portal';
@@ -1,3 +1,5 @@
1
+ import React from 'react';
2
+
1
3
  import {
2
4
  Button,
3
5
  Checkbox,
@@ -1,4 +1,4 @@
1
- import { useLayoutEffect } from 'react';
1
+ import React, { useLayoutEffect } from 'react';
2
2
 
3
3
  /**
4
4
  * Helper component using useLayoutEffect to trigger a callback on before unmount.