@lumx/react 3.10.1-alpha.7 → 3.10.1-alpha.8

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 (167) hide show
  1. package/index.d.ts +86 -12
  2. package/index.js +318 -304
  3. package/index.js.map +1 -1
  4. package/package.json +3 -3
  5. package/src/components/alert-dialog/AlertDialog.tsx +6 -3
  6. package/src/components/autocomplete/Autocomplete.stories.tsx +1 -2
  7. package/src/components/autocomplete/Autocomplete.tsx +3 -3
  8. package/src/components/autocomplete/AutocompleteMultiple.stories.tsx +2 -5
  9. package/src/components/autocomplete/AutocompleteMultiple.tsx +2 -2
  10. package/src/components/avatar/Avatar.tsx +3 -3
  11. package/src/components/badge/Badge.tsx +3 -3
  12. package/src/components/badge/BadgeWrapper.tsx +3 -2
  13. package/src/components/button/Button.tsx +9 -8
  14. package/src/components/button/ButtonGroup.tsx +9 -3
  15. package/src/components/button/ButtonRoot.tsx +6 -2
  16. package/src/components/button/IconButton.tsx +2 -2
  17. package/src/components/checkbox/Checkbox.tsx +4 -4
  18. package/src/components/chip/Chip.tsx +15 -9
  19. package/src/components/chip/ChipGroup.tsx +3 -2
  20. package/src/components/comment-block/CommentBlock.stories.tsx +4 -7
  21. package/src/components/comment-block/CommentBlock.tsx +3 -3
  22. package/src/components/date-picker/constants.ts +2 -2
  23. package/src/components/dialog/Dialog.tsx +9 -7
  24. package/src/components/divider/Divider.tsx +3 -3
  25. package/src/components/drag-handle/DragHandle.tsx +3 -3
  26. package/src/components/dropdown/Dropdown.tsx +4 -3
  27. package/src/components/expansion-panel/ExpansionPanel.tsx +26 -20
  28. package/src/components/flag/Flag.tsx +3 -3
  29. package/src/components/flex-box/FlexBox.tsx +4 -4
  30. package/src/components/generic-block/GenericBlock.tsx +10 -8
  31. package/src/components/generic-block/constants.ts +4 -9
  32. package/src/components/grid/Grid.tsx +5 -3
  33. package/src/components/grid/GridItem.tsx +5 -3
  34. package/src/components/grid-column/GridColumn.tsx +5 -3
  35. package/src/components/heading/Heading.tsx +7 -2
  36. package/src/components/icon/Icon.tsx +3 -3
  37. package/src/components/image-block/ImageBlock.tsx +3 -3
  38. package/src/components/image-lightbox/ImageLightbox.tsx +1 -1
  39. package/src/components/image-lightbox/constants.ts +2 -2
  40. package/src/components/image-lightbox/internal/ImageSlide.tsx +1 -1
  41. package/src/components/image-lightbox/internal/ImageSlideshow.tsx +9 -5
  42. package/src/components/image-lightbox/useImageLightbox.tsx +2 -1
  43. package/src/components/inline-list/InlineList.tsx +7 -3
  44. package/src/components/input-helper/InputHelper.tsx +3 -3
  45. package/src/components/input-label/InputLabel.tsx +3 -3
  46. package/src/components/lightbox/Lightbox.tsx +6 -4
  47. package/src/components/link/Link.tsx +11 -7
  48. package/src/components/link-preview/LinkPreview.tsx +3 -3
  49. package/src/components/list/List.tsx +4 -4
  50. package/src/components/list/ListDivider.tsx +3 -2
  51. package/src/components/list/ListItem.tsx +6 -5
  52. package/src/components/list/ListSubheader.tsx +3 -2
  53. package/src/components/list/useInteractiveList.tsx +7 -6
  54. package/src/components/message/Message.tsx +3 -3
  55. package/src/components/mosaic/Mosaic.test.tsx +3 -3
  56. package/src/components/mosaic/Mosaic.tsx +6 -5
  57. package/src/components/navigation/Navigation.stories.tsx +2 -2
  58. package/src/components/navigation/Navigation.tsx +3 -3
  59. package/src/components/navigation/NavigationItem.tsx +3 -9
  60. package/src/components/navigation/NavigationSection.tsx +3 -3
  61. package/src/components/notification/Notification.tsx +9 -5
  62. package/src/components/popover/Popover.stories.tsx +13 -15
  63. package/src/components/popover/Popover.test.tsx +6 -1
  64. package/src/components/popover/Popover.tsx +6 -6
  65. package/src/components/popover/usePopoverStyle.tsx +1 -3
  66. package/src/components/popover/useRestoreFocusOnClose.tsx +1 -1
  67. package/src/components/popover-dialog/PopoverDialog.tsx +3 -2
  68. package/src/components/post-block/PostBlock.tsx +11 -7
  69. package/src/components/progress/Progress.tsx +3 -3
  70. package/src/components/progress/ProgressCircular.tsx +3 -3
  71. package/src/components/progress/ProgressLinear.tsx +3 -3
  72. package/src/components/progress-tracker/ProgressTracker.stories.tsx +11 -11
  73. package/src/components/progress-tracker/ProgressTracker.tsx +4 -3
  74. package/src/components/progress-tracker/ProgressTrackerStep.tsx +3 -3
  75. package/src/components/progress-tracker/ProgressTrackerStepPanel.tsx +2 -0
  76. package/src/components/radio-button/RadioButton.tsx +3 -3
  77. package/src/components/radio-button/RadioGroup.stories.tsx +1 -4
  78. package/src/components/radio-button/RadioGroup.tsx +3 -2
  79. package/src/components/select/Select.stories.tsx +19 -38
  80. package/src/components/select/Select.tsx +5 -5
  81. package/src/components/select/SelectMultiple.stories.tsx +15 -30
  82. package/src/components/select/SelectMultiple.tsx +3 -4
  83. package/src/components/select/WithSelectContext.tsx +3 -4
  84. package/src/components/select/constants.ts +3 -1
  85. package/src/components/side-navigation/SideNavigation.stories.tsx +1 -1
  86. package/src/components/side-navigation/SideNavigation.tsx +4 -3
  87. package/src/components/side-navigation/SideNavigationItem.tsx +7 -7
  88. package/src/components/skeleton/SkeletonCircle.tsx +3 -3
  89. package/src/components/skeleton/SkeletonRectangle.tsx +3 -3
  90. package/src/components/skeleton/SkeletonTypography.stories.tsx +7 -12
  91. package/src/components/skeleton/SkeletonTypography.tsx +3 -3
  92. package/src/components/slider/Slider.tsx +4 -4
  93. package/src/components/slider/index.ts +1 -1
  94. package/src/components/slideshow/Slides.tsx +6 -4
  95. package/src/components/slideshow/Slideshow.stories.tsx +1 -3
  96. package/src/components/slideshow/Slideshow.tsx +3 -1
  97. package/src/components/slideshow/SlideshowControls.stories.tsx +1 -4
  98. package/src/components/slideshow/SlideshowControls.tsx +4 -4
  99. package/src/components/slideshow/SlideshowItem.tsx +5 -2
  100. package/src/components/slideshow/SlideshowItemGroup.tsx +5 -3
  101. package/src/components/switch/Switch.test.tsx +1 -1
  102. package/src/components/switch/Switch.tsx +7 -4
  103. package/src/components/table/Table.tsx +5 -3
  104. package/src/components/table/TableBody.tsx +7 -3
  105. package/src/components/table/TableCell.tsx +5 -3
  106. package/src/components/table/TableHeader.tsx +7 -3
  107. package/src/components/table/TableRow.tsx +5 -3
  108. package/src/components/tabs/TabList.tsx +1 -1
  109. package/src/components/tabs/TabPanel.tsx +2 -0
  110. package/src/components/tabs/Tabs.stories.tsx +12 -11
  111. package/src/components/text/Text.tsx +14 -5
  112. package/src/components/text-field/TextField.test.tsx +2 -2
  113. package/src/components/text-field/TextField.tsx +8 -7
  114. package/src/components/thumbnail/Thumbnail.tsx +4 -4
  115. package/src/components/toolbar/Toolbar.tsx +3 -3
  116. package/src/components/tooltip/Tooltip.tsx +4 -4
  117. package/src/components/tooltip/context.tsx +1 -1
  118. package/src/components/tooltip/useInjectTooltipRef.tsx +1 -1
  119. package/src/components/uploader/Uploader.tsx +3 -3
  120. package/src/components/user-block/UserBlock.tsx +12 -10
  121. package/src/hooks/useCallbackOnEscape.ts +1 -1
  122. package/src/hooks/useClickAway.tsx +2 -1
  123. package/src/hooks/useFocusTrap.ts +1 -1
  124. package/src/hooks/useInterval.tsx +4 -1
  125. package/src/hooks/useKeyboardListNavigation.tsx +4 -2
  126. package/src/hooks/useSizeOnWindowResize.ts +10 -14
  127. package/src/hooks/useStopPropagation.ts +2 -1
  128. package/src/stories/decorators/withCombinations.tsx +1 -1
  129. package/src/testing/utils/commonTestsSuiteRTL.tsx +2 -2
  130. package/src/utils/ClickAwayProvider/ClickAwayProvider.tsx +4 -0
  131. package/src/utils/className/getRootClassName.test.ts +11 -0
  132. package/src/utils/className/getRootClassName.ts +24 -0
  133. package/src/utils/className/index.ts +1 -0
  134. package/src/utils/date/getMonthCalendar.ts +4 -3
  135. package/src/utils/{makeListenerTowerContext.ts → function/makeListenerTowerContext.ts} +2 -2
  136. package/src/utils/{collection/partitionMulti.ts → partitionMulti.ts} +12 -13
  137. package/src/utils/{flattenChildren.ts → react/flattenChildren.ts} +3 -2
  138. package/src/utils/{renderButtonOrLink.tsx → react/renderButtonOrLink.tsx} +1 -1
  139. package/src/utils/{skipRender.tsx → react/skipRender.tsx} +1 -1
  140. package/src/utils/type/index.ts +18 -18
  141. package/src/utils/type/isComponent.ts +33 -0
  142. package/utils/index.d.ts +4 -0
  143. package/utils/index.js +96 -1
  144. package/utils/index.js.map +1 -1
  145. package/_internal/index.js +0 -99
  146. package/_internal/index.js.map +0 -1
  147. package/src/utils/className.test.js +0 -41
  148. package/src/utils/collection/castArray.test.ts +0 -15
  149. package/src/utils/collection/castArray.ts +0 -3
  150. package/src/utils/collection/chunk.test.ts +0 -15
  151. package/src/utils/collection/chunk.ts +0 -6
  152. package/src/utils/collection/isEmpty.test.js +0 -20
  153. package/src/utils/collection/isEmpty.ts +0 -4
  154. package/src/utils/collection/last.ts +0 -2
  155. package/src/utils/collection/partitionMulti.test.ts +0 -35
  156. package/src/utils/collection/pull.test.ts +0 -17
  157. package/src/utils/collection/pull.ts +0 -7
  158. package/src/utils/collection/range.test.js +0 -9
  159. package/src/utils/collection/range.ts +0 -2
  160. package/src/utils/function/memoize.test.ts +0 -36
  161. package/src/utils/function/memoize.ts +0 -13
  162. package/src/utils/type/ComponentClassName.ts +0 -7
  163. package/src/utils/type/KebabCase.ts +0 -6
  164. /package/src/utils/{clamp.ts → number/clamp.ts} +0 -0
  165. /package/src/utils/{OnBeforeUnmount.tsx → react/OnBeforeUnmount.tsx} +0 -0
  166. /package/src/utils/{mergeRefs.ts → react/mergeRefs.ts} +0 -0
  167. /package/src/utils/{renderLink.tsx → react/renderLink.tsx} +0 -0
@@ -6,6 +6,7 @@ import { getSelectArgType } from '@lumx/react/stories/controls/selectArgType';
6
6
  import { withNestedProps } from '@lumx/react/stories/decorators/withNestedProps';
7
7
  import { toFlattenProps } from '@lumx/react/stories/utils/toFlattenProps';
8
8
  import { withCategory } from '@lumx/react/stories/utils/withCategory';
9
+ import get from 'lodash/get';
9
10
  import times from 'lodash/times';
10
11
  import React, { useState } from 'react';
11
12
 
@@ -17,9 +18,9 @@ export default {
17
18
 
18
19
  /** Default tab behavior with some controllable args */
19
20
  export const Default = {
20
- render: ({ theme, tabProviderProps, tabListProps, tabProps }: any) => (
21
+ render: ({ tabProviderProps, tabListProps, tabProps }: any) => (
21
22
  <TabProvider {...tabProviderProps}>
22
- <TabList theme={theme} aria-label="Tab list" {...tabListProps}>
23
+ <TabList aria-label="Tab list" {...tabListProps}>
23
24
  <Tab {...tabProps[0]} />
24
25
  <Tab {...tabProps[1]} />
25
26
  <Tab {...tabProps[2]} />
@@ -60,15 +61,15 @@ export const Default = {
60
61
 
61
62
  /* Control active tab externally (with activate tab on focus). */
62
63
  export const Controlled = {
63
- render({ theme }: any) {
64
+ render() {
64
65
  const [activeTab, setActiveTab] = useState(1);
65
- const changeActiveTabIndex = (evt: any) => setActiveTab(parseInt(evt.target.value || 0, 10));
66
+ const changeActiveTabIndex = (evt: any) => setActiveTab(parseInt(get(evt, 'target.value', '0'), 10));
66
67
 
67
68
  const [isLazy, setIsLazy] = useState(true);
68
- const changeIsLazy = (evt: any) => setIsLazy(evt.target.checked);
69
+ const changeIsLazy = (evt: any) => setIsLazy(get(evt, 'target.checked'));
69
70
 
70
71
  const [shouldActivateOnFocus, setShouldActivateOnFocus] = useState(true);
71
- const changeShouldActivateOnFocus = (evt: any) => setShouldActivateOnFocus(evt.target.checked);
72
+ const changeShouldActivateOnFocus = (evt: any) => setShouldActivateOnFocus(get(evt, 'target.checked'));
72
73
 
73
74
  return (
74
75
  <>
@@ -92,7 +93,7 @@ export const Controlled = {
92
93
  isLazy={isLazy}
93
94
  shouldActivateOnFocus={shouldActivateOnFocus}
94
95
  >
95
- <TabList theme={theme} aria-label="Tab list">
96
+ <TabList aria-label="Tab list">
96
97
  <Tab label="Tab a" />
97
98
  <Tab label="Tab b" />
98
99
  <Tab label="Tab c" />
@@ -110,7 +111,7 @@ export const Controlled = {
110
111
 
111
112
  /* Display tabs far from their tab panels. */
112
113
  export const SplitTabListAndTabPanels = {
113
- render({ theme }: any) {
114
+ render() {
114
115
  const [isOpen, setOpen] = useState(true);
115
116
  const [activeTabIndex, onChange] = useState(1);
116
117
 
@@ -130,7 +131,7 @@ export const SplitTabListAndTabPanels = {
130
131
  <TabPanel className="lumx-spacing-padding-huge">Tab 3 content</TabPanel>
131
132
 
132
133
  <footer>
133
- <TabList theme={theme} aria-label="Tab list">
134
+ <TabList aria-label="Tab list">
134
135
  <Tab label="Tab 1" />
135
136
  <Tab label="Tab 2" />
136
137
  <Tab label="Tab 3" />
@@ -145,10 +146,10 @@ export const SplitTabListAndTabPanels = {
145
146
 
146
147
  /* Dynamically generate tabs. */
147
148
  export const DynamicTabs = {
148
- render({ theme, tabCount }: any) {
149
+ render({ tabCount }: any) {
149
150
  return (
150
151
  <TabProvider>
151
- <TabList theme={theme} aria-label="Tab list">
152
+ <TabList aria-label="Tab list">
152
153
  {times(tabCount, (tabNumber) => (
153
154
  <Tab key={tabNumber} label={`Tab ${tabNumber}`} />
154
155
  ))}
@@ -3,10 +3,15 @@ import React, { Children, Fragment } from 'react';
3
3
  import classNames from 'classnames';
4
4
 
5
5
  import { Icon, ColorPalette, ColorVariant, Typography, WhiteSpace } from '@lumx/react';
6
- import { type GenericProps, type TextElement, isComponentType, type ComponentClassName } from '@lumx/react/utils/type';
7
- import { getFontColorClassName, handleBasicClasses, getTypographyClassName } from '@lumx/react/utils/className';
6
+ import { GenericProps, TextElement, isComponent } from '@lumx/react/utils/type';
7
+ import {
8
+ getFontColorClassName,
9
+ getRootClassName,
10
+ handleBasicClasses,
11
+ getTypographyClassName,
12
+ } from '@lumx/react/utils/className';
8
13
  import { useOverflowTooltipLabel } from '@lumx/react/hooks/useOverflowTooltipLabel';
9
- import { useMergeRefs } from '@lumx/react/utils/mergeRefs';
14
+ import { useMergeRefs } from '@lumx/react/utils/react/mergeRefs';
10
15
  import { forwardRef } from '@lumx/react/utils/react/forwardRef';
11
16
 
12
17
  /**
@@ -46,6 +51,10 @@ export interface TextProps extends GenericProps {
46
51
  * Ignored when `truncate` is set to true or lines: 1
47
52
  * */
48
53
  whiteSpace?: WhiteSpace;
54
+ /**
55
+ * Children
56
+ */
57
+ children?: React.ReactNode;
49
58
  }
50
59
 
51
60
  /**
@@ -56,7 +65,7 @@ const COMPONENT_NAME = 'Text';
56
65
  /**
57
66
  * Component default class name and class prefix.
58
67
  */
59
- const CLASSNAME: ComponentClassName<typeof COMPONENT_NAME> = 'lumx-text';
68
+ const CLASSNAME = getRootClassName(COMPONENT_NAME);
60
69
 
61
70
  /**
62
71
  * Component default props.
@@ -126,7 +135,7 @@ export const Text = forwardRef<TextProps>((props, ref) => {
126
135
  {children &&
127
136
  Children.toArray(children).map((child, index) => {
128
137
  // Force wrap spaces around icons to make sure they are never stuck against text.
129
- if (isComponentType(Icon)(child)) {
138
+ if (isComponent(Icon)(child)) {
130
139
  return <Fragment key={child.key || index}> {child} </Fragment>;
131
140
  }
132
141
  return child;
@@ -12,10 +12,10 @@ import {
12
12
  queryByClassName,
13
13
  queryByTagName,
14
14
  } from '@lumx/react/testing/utils/queries';
15
+ import partition from 'lodash/partition';
15
16
  import userEvent from '@testing-library/user-event';
16
17
 
17
18
  import { isFocusVisible } from '@lumx/react/utils/browser/isFocusVisible';
18
- import { partitionMulti } from '@lumx/react/utils/collection/partitionMulti';
19
19
  import { TextField, TextFieldProps } from './TextField';
20
20
 
21
21
  const CLASSNAME = TextField.className as string;
@@ -34,7 +34,7 @@ const setup = (propsOverride: Partial<TextFieldProps> = {}, { wrapper }: SetupRe
34
34
  | HTMLTextAreaElement
35
35
  | HTMLInputElement;
36
36
  const helpers = queryAllByClassName(container, 'lumx-text-field__helper');
37
- const [[helper], [error]] = partitionMulti(helpers, [(h) => !h.className.includes('lumx-input-helper--color-red')]);
37
+ const [[helper], [error]] = partition(helpers, (h) => !h.className.includes('lumx-input-helper--color-red'));
38
38
  const clearButton = queryByClassName(container, 'lumx-text-field__input-clear');
39
39
 
40
40
  return {
@@ -1,6 +1,7 @@
1
1
  import React, { ReactNode, Ref, RefObject, SyntheticEvent, useEffect, useRef, useState } from 'react';
2
2
 
3
3
  import classNames from 'classnames';
4
+ import get from 'lodash/get';
4
5
 
5
6
  import { mdiAlertCircle, mdiCheckCircle, mdiCloseCircle } from '@lumx/icons';
6
7
  import {
@@ -15,9 +16,9 @@ import {
15
16
  Size,
16
17
  Theme,
17
18
  } from '@lumx/react';
18
- import type { GenericProps, HasTheme, ComponentClassName } from '@lumx/react/utils/type';
19
- import { handleBasicClasses } from '@lumx/react/utils/className';
20
- import { mergeRefs } from '@lumx/react/utils/mergeRefs';
19
+ import { GenericProps, HasTheme } from '@lumx/react/utils/type';
20
+ import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
21
+ import { mergeRefs } from '@lumx/react/utils/react/mergeRefs';
21
22
  import { useId } from '@lumx/react/hooks/useId';
22
23
  import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
23
24
  import { forwardRef } from '@lumx/react/utils/react/forwardRef';
@@ -27,7 +28,7 @@ import { forwardRef } from '@lumx/react/utils/react/forwardRef';
27
28
  */
28
29
  export interface TextFieldProps extends GenericProps, HasTheme {
29
30
  /** Chip Group to be rendered before the main text input. */
30
- chips?: HTMLElement | ReactNode;
31
+ chips?: ReactNode;
31
32
  /** Props to pass to the clear button (minus those already set by the TextField props). If not specified, the button won't be displayed. */
32
33
  clearButtonProps?: Pick<IconButtonProps, 'label'> &
33
34
  Omit<IconButtonProps, 'label' | 'onClick' | 'icon' | 'emphasis'>;
@@ -91,7 +92,7 @@ const COMPONENT_NAME = 'TextField';
91
92
  /**
92
93
  * Component default class name and class prefix.
93
94
  */
94
- const CLASSNAME: ComponentClassName<typeof COMPONENT_NAME> = 'lumx-text-field';
95
+ const CLASSNAME = getRootClassName(COMPONENT_NAME);
95
96
 
96
97
  /**
97
98
  * Default minimum number of rows in the multiline mode.
@@ -216,8 +217,8 @@ const renderInputNative: React.FC<InputNativeProps> = (props) => {
216
217
  setFocus(false);
217
218
  };
218
219
 
219
- const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
220
- onChange(event.target.value, name, event);
220
+ const handleChange = (event: React.ChangeEvent) => {
221
+ onChange(get(event, 'target.value'), name, event);
221
222
  };
222
223
 
223
224
  const Component = multiline ? 'textarea' : 'input';
@@ -12,10 +12,10 @@ import React, {
12
12
  import classNames from 'classnames';
13
13
 
14
14
  import { AspectRatio, HorizontalAlignment, Icon, Size, Theme, ThumbnailObjectFit } from '@lumx/react';
15
- import type { Falsy, GenericProps, HasTheme, ComponentClassName } from '@lumx/react/utils/type';
16
- import { handleBasicClasses } from '@lumx/react/utils/className';
15
+ import { Falsy, GenericProps, HasTheme } from '@lumx/react/utils/type';
16
+ import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
17
17
  import { mdiImageBroken } from '@lumx/icons';
18
- import { useMergeRefs } from '@lumx/react/utils/mergeRefs';
18
+ import { useMergeRefs } from '@lumx/react/utils/react/mergeRefs';
19
19
  import { useImageLoad } from '@lumx/react/components/thumbnail/useImageLoad';
20
20
  import { useFocusPointStyle } from '@lumx/react/components/thumbnail/useFocusPointStyle';
21
21
  import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
@@ -81,7 +81,7 @@ const COMPONENT_NAME = 'Thumbnail';
81
81
  /**
82
82
  * Component default class name and class prefix.
83
83
  */
84
- const CLASSNAME: ComponentClassName<typeof COMPONENT_NAME> = 'lumx-thumbnail';
84
+ const CLASSNAME = getRootClassName(COMPONENT_NAME);
85
85
 
86
86
  /**
87
87
  * Component default props.
@@ -2,8 +2,8 @@ import React, { ReactNode } from 'react';
2
2
 
3
3
  import classNames from 'classnames';
4
4
 
5
- import { GenericProps, ComponentClassName } from '@lumx/react/utils/type';
6
- import { handleBasicClasses } from '@lumx/react/utils/className';
5
+ import { GenericProps } from '@lumx/react/utils/type';
6
+ import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
7
7
  import { forwardRef } from '@lumx/react/utils/react/forwardRef';
8
8
 
9
9
  /**
@@ -26,7 +26,7 @@ const COMPONENT_NAME = 'Toolbar';
26
26
  /**
27
27
  * Component default class name and class prefix.
28
28
  */
29
- const CLASSNAME: ComponentClassName<typeof COMPONENT_NAME> = 'lumx-toolbar';
29
+ const CLASSNAME = getRootClassName(COMPONENT_NAME);
30
30
 
31
31
  /**
32
32
  * Component default props.
@@ -5,9 +5,9 @@ import { createPortal } from 'react-dom';
5
5
  import classNames from 'classnames';
6
6
 
7
7
  import { DOCUMENT, VISUALLY_HIDDEN } from '@lumx/react/constants';
8
- import type { GenericProps, HasCloseMode, ComponentClassName } from '@lumx/react/utils/type';
9
- import { handleBasicClasses } from '@lumx/react/utils/className';
10
- import { useMergeRefs } from '@lumx/react/utils/mergeRefs';
8
+ import { GenericProps, HasCloseMode } from '@lumx/react/utils/type';
9
+ import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
10
+ import { useMergeRefs } from '@lumx/react/utils/react/mergeRefs';
11
11
  import { Placement } from '@lumx/react/components/popover';
12
12
  import { TooltipContextProvider } from '@lumx/react/components/tooltip/context';
13
13
  import { useId } from '@lumx/react/hooks/useId';
@@ -47,7 +47,7 @@ const COMPONENT_NAME = 'Tooltip';
47
47
  /**
48
48
  * Component default class name and class prefix.
49
49
  */
50
- const CLASSNAME: ComponentClassName<typeof COMPONENT_NAME> = 'lumx-tooltip';
50
+ const CLASSNAME = getRootClassName(COMPONENT_NAME);
51
51
 
52
52
  /**
53
53
  * Component default props.
@@ -10,7 +10,7 @@ const DEFAULT_VALUE = {};
10
10
  */
11
11
  const TooltipContext = React.createContext<TooltipContextValue | undefined>(undefined);
12
12
 
13
- export const TooltipContextProvider: React.FC = ({ children }) => (
13
+ export const TooltipContextProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => (
14
14
  <TooltipContext.Provider value={DEFAULT_VALUE}>{children}</TooltipContext.Provider>
15
15
  );
16
16
 
@@ -1,6 +1,6 @@
1
1
  import React, { cloneElement, ReactNode, useMemo } from 'react';
2
2
 
3
- import { mergeRefs } from '@lumx/react/utils/mergeRefs';
3
+ import { mergeRefs } from '@lumx/react/utils/react/mergeRefs';
4
4
 
5
5
  interface Options {
6
6
  /** Original tooltip anchor */
@@ -3,8 +3,8 @@ import React, { MouseEventHandler } from 'react';
3
3
  import classNames from 'classnames';
4
4
 
5
5
  import { AspectRatio, Icon, Size, Theme } from '@lumx/react';
6
- import type { GenericProps, HasTheme, ValueOf, ComponentClassName } from '@lumx/react/utils/type';
7
- import { handleBasicClasses } from '@lumx/react/utils/className';
6
+ import { GenericProps, HasTheme, ValueOf } from '@lumx/react/utils/type';
7
+ import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
8
8
  import { useBooleanState } from '@lumx/react/hooks/useBooleanState';
9
9
  import { useId } from '@lumx/react/hooks/useId';
10
10
  import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
@@ -60,7 +60,7 @@ const COMPONENT_NAME = 'Uploader';
60
60
  /**
61
61
  * Component default class name and class prefix.
62
62
  */
63
- const CLASSNAME: ComponentClassName<typeof COMPONENT_NAME> = 'lumx-uploader';
63
+ const CLASSNAME = getRootClassName(COMPONENT_NAME);
64
64
 
65
65
  /**
66
66
  * Component default props.
@@ -1,9 +1,12 @@
1
1
  import React, { ReactNode } from 'react';
2
+
2
3
  import classNames from 'classnames';
4
+ import isEmpty from 'lodash/isEmpty';
5
+ import set from 'lodash/set';
3
6
 
4
7
  import { Avatar, ColorPalette, Link, Orientation, Size, Theme } from '@lumx/react';
5
- import type { GenericProps, HasTheme, ComponentClassName } from '@lumx/react/utils/type';
6
- import { handleBasicClasses } from '@lumx/react/utils/className';
8
+ import { GenericProps, HasTheme } from '@lumx/react/utils/type';
9
+ import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
7
10
  import { forwardRef } from '@lumx/react/utils/react/forwardRef';
8
11
 
9
12
  import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
@@ -54,7 +57,7 @@ const COMPONENT_NAME = 'UserBlock';
54
57
  /**
55
58
  * Component default class name and class prefix.
56
59
  */
57
- const CLASSNAME: ComponentClassName<typeof COMPONENT_NAME> = 'lumx-user-block';
60
+ const CLASSNAME = getRootClassName(COMPONENT_NAME);
58
61
 
59
62
  /**
60
63
  * Component default props.
@@ -104,7 +107,7 @@ export const UserBlock = forwardRef<UserBlockProps, HTMLDivElement>((props, ref)
104
107
  const isClickable = !!onClick || isLink;
105
108
 
106
109
  const nameBlock: ReactNode = React.useMemo(() => {
107
- if (!name) {
110
+ if (isEmpty(name)) {
108
111
  return null;
109
112
  }
110
113
  let NameComponent: any = 'span';
@@ -121,8 +124,12 @@ export const UserBlock = forwardRef<UserBlockProps, HTMLDivElement>((props, ref)
121
124
  color: ColorPalette.dark,
122
125
  });
123
126
  }
127
+ // Disable avatar focus since the name block is the same link / same button.
128
+ if (avatarProps) {
129
+ set(avatarProps, ['thumbnailProps', 'tabIndex'], -1);
130
+ }
124
131
  return <NameComponent {...nProps}>{name}</NameComponent>;
125
- }, [isClickable, linkAs, linkProps, name, nameProps, onClick]);
132
+ }, [avatarProps, isClickable, linkAs, linkProps, name, nameProps, onClick]);
126
133
 
127
134
  const fieldsBlock: ReactNode = fields && componentSize !== Size.s && componentSize !== Size.xs && (
128
135
  <div className={`${CLASSNAME}__fields`}>
@@ -155,11 +162,6 @@ export const UserBlock = forwardRef<UserBlockProps, HTMLDivElement>((props, ref)
155
162
  size={componentSize}
156
163
  onClick={onClick}
157
164
  theme={theme}
158
- thumbnailProps={{
159
- ...avatarProps?.thumbnailProps,
160
- // Disable avatar focus since the name block is the same link / same button.
161
- tabIndex: avatarProps ? -1 : undefined,
162
- }}
163
165
  />
164
166
  )}
165
167
  {(fields || name) && (
@@ -2,7 +2,7 @@ import { DOCUMENT } from '@lumx/react/constants';
2
2
  import { Callback } from '@lumx/react/utils/type';
3
3
  import { onEscapePressed } from '@lumx/react/utils/browser/event';
4
4
  import { useEffect } from 'react';
5
- import { Listener, makeListenerTowerContext } from '@lumx/react/utils/makeListenerTowerContext';
5
+ import { Listener, makeListenerTowerContext } from '@lumx/react/utils/function/makeListenerTowerContext';
6
6
 
7
7
  const LISTENERS = makeListenerTowerContext();
8
8
 
@@ -1,7 +1,8 @@
1
1
  import { RefObject, useEffect } from 'react';
2
2
 
3
3
  import { Falsy } from '@lumx/react/utils/type';
4
- import { isEmpty } from '@lumx/react/utils/collection/isEmpty';
4
+
5
+ import isEmpty from 'lodash/isEmpty';
5
6
 
6
7
  const EVENT_TYPES = ['mousedown', 'touchstart'];
7
8
 
@@ -3,7 +3,7 @@ import { useEffect } from 'react';
3
3
  import { DOCUMENT } from '@lumx/react/constants';
4
4
  import { getFirstAndLastFocusable } from '@lumx/react/utils/browser/focus/getFirstAndLastFocusable';
5
5
  import { Falsy } from '@lumx/react/utils/type';
6
- import { Listener, makeListenerTowerContext } from '@lumx/react/utils/makeListenerTowerContext';
6
+ import { Listener, makeListenerTowerContext } from '@lumx/react/utils/function/makeListenerTowerContext';
7
7
 
8
8
  const FOCUS_TRAPS = makeListenerTowerContext();
9
9
 
@@ -1,5 +1,6 @@
1
1
  import { useEffect, useRef } from 'react';
2
2
 
3
+ import isFunction from 'lodash/isFunction';
3
4
  import { Callback } from '@lumx/react/utils/type';
4
5
 
5
6
  /**
@@ -20,7 +21,9 @@ export function useInterval(callback: Callback, delay: number | null): void {
20
21
  if (delay === null) return undefined;
21
22
 
22
23
  function tick() {
23
- savedCallback.current?.();
24
+ if (isFunction(savedCallback.current)) {
25
+ savedCallback.current();
26
+ }
24
27
  }
25
28
  const id = setInterval(tick, delay);
26
29
  return () => clearInterval(id);
@@ -1,5 +1,7 @@
1
1
  import { RefObject, SetStateAction, useEffect, useState } from 'react';
2
2
 
3
+ import get from 'lodash/get';
4
+
3
5
  type Listener = (evt: KeyboardEvent) => void;
4
6
 
5
7
  interface UseKeyboardListNavigationType {
@@ -133,7 +135,7 @@ export const useKeyboardListNavigation: useKeyboardListNavigationType = (
133
135
  onListItemSelected(selectedItem);
134
136
  resetActiveIndex();
135
137
  } else if (activeItemIndex === initialIndex && onEnterPressed) {
136
- const value = (evt.target as any)?.value;
138
+ const value = get(evt, 'target.value');
137
139
  onEnterPressed(value);
138
140
  resetActiveIndex();
139
141
  }
@@ -144,7 +146,7 @@ export const useKeyboardListNavigation: useKeyboardListNavigationType = (
144
146
  * @param evt - key pressed event
145
147
  */
146
148
  const onTabKeyPressed: Listener = (evt) => {
147
- const value = (evt.target as any)?.value;
149
+ const value = get(evt, 'target.value');
148
150
 
149
151
  if (preventTabOnEnteredValue && value && value.length > 0) {
150
152
  preventDefaultAndStopPropagation(evt);
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
 
3
- import type { RectSize } from '@lumx/react/utils/type';
3
+ import throttle from 'lodash/throttle';
4
+ import { RectSize } from '@lumx/react/utils/type';
4
5
 
5
6
  /**
6
7
  * Observe element size (only works if it's size depends on the window size).
@@ -12,19 +13,14 @@ import type { RectSize } from '@lumx/react/utils/type';
12
13
  */
13
14
  export function useSizeOnWindowResize(elementRef: React.RefObject<HTMLElement>): [RectSize | null, () => void] {
14
15
  const [size, setSize] = React.useState<null | RectSize>(null);
15
- const updateSize = React.useMemo(() => {
16
- let prevTimeout: ReturnType<typeof setTimeout> | undefined;
17
- function update() {
18
- const newSize = elementRef.current?.getBoundingClientRect();
19
- if (newSize) setSize(newSize);
20
- prevTimeout = undefined;
21
- }
22
- return () => {
23
- if (!prevTimeout) {
24
- prevTimeout = setTimeout(update, 10);
25
- }
26
- };
27
- }, [elementRef]);
16
+ const updateSize = React.useMemo(
17
+ () =>
18
+ throttle(() => {
19
+ const newSize = elementRef.current?.getBoundingClientRect();
20
+ if (newSize) setSize(newSize);
21
+ }, 10),
22
+ [elementRef],
23
+ );
28
24
  React.useEffect(() => {
29
25
  updateSize();
30
26
  window.addEventListener('resize', updateSize);
@@ -1,3 +1,4 @@
1
+ import isFunction from 'lodash/isFunction';
1
2
  import { MouseEventHandler, useCallback } from 'react';
2
3
 
3
4
  /**
@@ -9,7 +10,7 @@ import { MouseEventHandler, useCallback } from 'react';
9
10
  export function useStopPropagation(handler?: MouseEventHandler): MouseEventHandler {
10
11
  return useCallback(
11
12
  (evt) => {
12
- if (!evt || !handler) {
13
+ if (!evt || !isFunction(handler)) {
13
14
  return;
14
15
  }
15
16
  handler(evt);
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable react/display-name,jsx-a11y/control-has-associated-label,prefer-object-spread */
2
2
  import React from 'react';
3
- import { isEmpty } from '@lumx/react/utils/collection/isEmpty';
3
+ import isEmpty from 'lodash/isEmpty';
4
4
  import { Heading, HeadingLevelProvider } from '@lumx/react';
5
5
 
6
6
  type PropEntry = [key: string, value: unknown];
@@ -1,13 +1,13 @@
1
- import React from 'react';
1
+ import isEmpty from 'lodash/isEmpty';
2
2
 
3
3
  import { GenericProps } from '@lumx/react/utils/type';
4
4
  import { queryByClassName } from '@lumx/react/testing/utils/queries';
5
+ import React from 'react';
5
6
  import { Theme } from '@lumx/react';
6
7
  import { RenderOptions } from '@testing-library/react';
7
8
  import { ThemeProvider } from '@lumx/react/utils/theme/ThemeContext';
8
9
  import castArray from 'lodash/castArray';
9
10
  import { invertTheme } from '@lumx/react/utils/theme/invertTheme';
10
- import { isEmpty } from '@lumx/react/utils/collection/isEmpty';
11
11
 
12
12
  interface CommonSetup {
13
13
  props: GenericProps;
@@ -13,6 +13,10 @@ interface ClickAwayProviderProps extends ClickAwayParameters {
13
13
  * (Optional) Element that should be considered as part of the parent
14
14
  */
15
15
  parentRef?: RefObject<HTMLElement>;
16
+ /**
17
+ * Children
18
+ */
19
+ children?: React.ReactNode;
16
20
  }
17
21
 
18
22
  /**
@@ -0,0 +1,11 @@
1
+ import { getRootClassName } from '@lumx/react/utils/className/getRootClassName';
2
+
3
+ describe(getRootClassName, () => {
4
+ it('should transform the component name into a lumx class', () => {
5
+ expect(getRootClassName('Table')).toBe('lumx-table');
6
+ });
7
+
8
+ it('should transform the sub component name into a lumx class', () => {
9
+ expect(getRootClassName('TableBody', true)).toBe('lumx-table__body');
10
+ });
11
+ });
@@ -0,0 +1,24 @@
1
+ import { CSS_PREFIX } from '@lumx/core/js/constants';
2
+ import kebabCase from 'lodash/kebabCase';
3
+
4
+ // See https://regex101.com/r/YjS1uI/3
5
+ const LAST_PART_CLASSNAME = /^(.*)-(.+)$/gi;
6
+
7
+ /**
8
+ * Get the name of the root CSS class of a component based on its name.
9
+ *
10
+ * @param componentName The name of the component. This name should contains the component prefix and be
11
+ * written in PascalCase.
12
+ * @param subComponent Whether the current component is a sub component, if true, define the class according
13
+ * to BEM standards.
14
+ * @return The name of the root CSS class. This classname include the CSS classname prefix and is written in
15
+ * lower-snake-case.
16
+ */
17
+ export function getRootClassName(componentName: string, subComponent?: boolean): string {
18
+ const formattedClassName = `${CSS_PREFIX}-${kebabCase(componentName)}`;
19
+
20
+ if (subComponent) {
21
+ return formattedClassName.replace(LAST_PART_CLASSNAME, '$1__$2');
22
+ }
23
+ return formattedClassName;
24
+ }
@@ -1,3 +1,4 @@
1
1
  export { getBasicClass, handleBasicClasses } from '@lumx/core/js/utils';
2
+ export { getRootClassName } from './getRootClassName';
2
3
  export { getTypographyClassName } from './getTypographyClassName';
3
4
  export { getFontColorClassName } from './getFontColorClassName';
@@ -1,6 +1,7 @@
1
+ import last from 'lodash/last';
2
+
1
3
  import { getWeekDays, WeekDayInfo } from '@lumx/react/utils/date/getWeekDays';
2
4
  import { Locale } from '@lumx/react/utils/locale/types';
3
- import { last } from '@lumx/react/utils/collection/last';
4
5
 
5
6
  type AnnotatedDay = { date: Date; isOutOfRange?: boolean };
6
7
  type AnnotatedWeek = Partial<Record<number, AnnotatedDay>>;
@@ -28,7 +29,7 @@ export const getMonthCalendar = (
28
29
  iterDate.setDate(1);
29
30
 
30
31
  const weekDays = getWeekDays(locale);
31
- const lastDayOfWeek = last(weekDays);
32
+ const lastDayOfWeek = last(weekDays) as WeekDayInfo;
32
33
 
33
34
  const weeks: Array<AnnotatedWeek> = [];
34
35
  let week: AnnotatedWeek = {};
@@ -46,7 +47,7 @@ export const getMonthCalendar = (
46
47
  week[weekDayNumber] = day;
47
48
  }
48
49
 
49
- if (weekDayNumber === lastDayOfWeek?.number) {
50
+ if (weekDayNumber === lastDayOfWeek.number) {
50
51
  weeks.push(week);
51
52
  rowCount += 1;
52
53
  week = {};
@@ -1,5 +1,5 @@
1
- import { pull } from '@lumx/react/utils/collection/pull';
2
- import { last } from '@lumx/react/utils/collection/last';
1
+ import last from 'lodash/last';
2
+ import pull from 'lodash/pull';
3
3
 
4
4
  export type Listener = { enable(): void; disable(): void };
5
5
 
@@ -1,4 +1,9 @@
1
- import type { Predicate } from '../type';
1
+ import concat from 'lodash/concat';
2
+ import dropRight from 'lodash/dropRight';
3
+ import last from 'lodash/last';
4
+ import partition from 'lodash/partition';
5
+ import reduce from 'lodash/reduce';
6
+ import { Predicate } from './type';
2
7
 
3
8
  /**
4
9
  * Similar to lodash `partition` function but working with multiple predicates.
@@ -14,16 +19,10 @@ import type { Predicate } from '../type';
14
19
  * @return partitioned elements by the given predicates
15
20
  */
16
21
  export function partitionMulti<T>(elements: T[], predicates: Array<Predicate<T>>): T[][] {
17
- const others = [] as T[];
18
- const groups = predicates.map(() => []) as T[][];
19
-
20
- for (const element of elements) {
21
- const index = predicates.findIndex((predicate) => predicate(element));
22
- if (index !== -1) {
23
- groups[index].push(element);
24
- } else {
25
- others.push(element);
26
- }
27
- }
28
- return [...groups, others];
22
+ return reduce(
23
+ predicates,
24
+ (partitioned: T[][], predicate: Predicate<T>) =>
25
+ concat(dropRight(partitioned), partition(last(partitioned), predicate)),
26
+ [elements],
27
+ );
29
28
  }