@lumx/react 3.8.2-alpha.2 → 3.9.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 (34) hide show
  1. package/index.d.ts +1 -1
  2. package/index.js +53 -39
  3. package/index.js.map +1 -1
  4. package/package.json +4 -5
  5. package/src/components/alert-dialog/AlertDialog.test.tsx +1 -1
  6. package/src/components/alert-dialog/AlertDialog.tsx +3 -2
  7. package/src/components/checkbox/Checkbox.tsx +4 -3
  8. package/src/components/date-picker/DatePickerControlled.tsx +2 -3
  9. package/src/components/message/Message.tsx +1 -1
  10. package/src/components/navigation/NavigationSection.tsx +4 -3
  11. package/src/components/notification/constants.ts +1 -1
  12. package/src/components/radio-button/RadioButton.tsx +4 -3
  13. package/src/components/select/Select.test.tsx +1 -1
  14. package/src/components/select/SelectMultiple.test.tsx +1 -1
  15. package/src/components/select/WithSelectContext.tsx +4 -3
  16. package/src/components/side-navigation/SideNavigationItem.test.tsx +1 -1
  17. package/src/components/side-navigation/SideNavigationItem.tsx +2 -2
  18. package/src/components/slider/Slider.test.tsx +1 -1
  19. package/src/components/slider/Slider.tsx +3 -2
  20. package/src/components/switch/Switch.test.tsx +1 -1
  21. package/src/components/switch/Switch.tsx +4 -3
  22. package/src/components/tabs/state.ts +4 -6
  23. package/src/components/text-field/TextField.tsx +6 -15
  24. package/src/components/tooltip/Tooltip.test.tsx +1 -1
  25. package/src/components/tooltip/Tooltip.tsx +4 -4
  26. package/src/components/uploader/Uploader.tsx +3 -2
  27. package/src/components/user-block/UserBlock.stories.tsx +1 -1
  28. package/src/components/user-block/UserBlock.tsx +2 -2
  29. package/src/hooks/useId.test.tsx +23 -0
  30. package/src/hooks/useId.ts +15 -0
  31. package/src/hooks/useSlideshowControls.ts +7 -4
  32. package/src/stories/generated/UserBlock/Demos.stories.tsx +1 -0
  33. package/src/utils/date/formatDayNumber.test.ts +12 -0
  34. package/src/utils/date/formatDayNumber.ts +5 -0
package/package.json CHANGED
@@ -6,14 +6,13 @@
6
6
  "url": "https://github.com/lumapps/design-system/issues"
7
7
  },
8
8
  "dependencies": {
9
- "@lumx/core": "^3.8.2-alpha.2",
10
- "@lumx/icons": "^3.8.2-alpha.2",
9
+ "@lumx/core": "^3.9.0",
10
+ "@lumx/icons": "^3.9.0",
11
11
  "@popperjs/core": "^2.5.4",
12
12
  "body-scroll-lock": "^3.1.5",
13
13
  "classnames": "^2.3.2",
14
14
  "react-is": ">=16.13.0",
15
- "react-popper": "^2.2.4",
16
- "uid": "^2.0.0"
15
+ "react-popper": "^2.2.4"
17
16
  },
18
17
  "devDependencies": {
19
18
  "@babel/core": "^7.18.13",
@@ -111,5 +110,5 @@
111
110
  "build:storybook": "storybook build"
112
111
  },
113
112
  "sideEffects": false,
114
- "version": "3.8.2-alpha.2"
113
+ "version": "3.9.0"
115
114
  }
@@ -5,7 +5,7 @@ import { queryByClassName } from '@lumx/react/testing/utils/queries';
5
5
  import { render } from '@testing-library/react';
6
6
  import { AlertDialog, AlertDialogProps } from './AlertDialog';
7
7
 
8
- jest.mock('uid', () => ({ uid: () => 'uid' }));
8
+ jest.mock('@lumx/react/hooks/useId', () => ({ useId: () => ':r1:' }));
9
9
 
10
10
  const CLASSNAME = AlertDialog.className as string;
11
11
 
@@ -16,9 +16,9 @@ import {
16
16
  } from '@lumx/react';
17
17
 
18
18
  import { mdiAlert, mdiAlertCircle, mdiCheckCircle, mdiInformation } from '@lumx/icons';
19
- import { uid } from 'uid';
20
19
  import { Comp } from '@lumx/react/utils/type';
21
20
  import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
21
+ import { useId } from '@lumx/react/hooks/useId';
22
22
 
23
23
  export interface AlertDialogProps extends Omit<DialogProps, 'header' | 'footer'> {
24
24
  /** Message variant. */
@@ -86,7 +86,8 @@ export const AlertDialog: Comp<AlertDialogProps, HTMLDivElement> = forwardRef((p
86
86
  const { color, icon } = CONFIG[kind as Kind] || {};
87
87
 
88
88
  // Define a unique ID to target title and description for aria attributes.
89
- const uniqueId = React.useMemo(() => id || uid(), [id]);
89
+ const generatedId = useId();
90
+ const uniqueId = id || generatedId;
90
91
  const titleId = `${uniqueId}-title`;
91
92
  const descriptionId = `${uniqueId}-description`;
92
93
 
@@ -1,13 +1,13 @@
1
- import React, { forwardRef, InputHTMLAttributes, ReactNode, SyntheticEvent, useMemo } from 'react';
1
+ import React, { forwardRef, InputHTMLAttributes, ReactNode, SyntheticEvent } from 'react';
2
2
 
3
3
  import classNames from 'classnames';
4
- import { uid } from 'uid';
5
4
 
6
5
  import { mdiCheck, mdiMinus } from '@lumx/icons';
7
6
 
8
7
  import { Icon, InputHelper, InputLabel, Theme } from '@lumx/react';
9
8
  import { Comp, GenericProps, HasTheme } from '@lumx/react/utils/type';
10
9
  import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
10
+ import { useId } from '@lumx/react/hooks/useId';
11
11
  import { useMergeRefs } from '@lumx/react/utils/mergeRefs';
12
12
 
13
13
  /**
@@ -84,7 +84,8 @@ export const Checkbox: Comp<CheckboxProps, HTMLDivElement> = forwardRef((props,
84
84
  ...forwardedProps
85
85
  } = props;
86
86
  const localInputRef = React.useRef<HTMLInputElement>(null);
87
- const inputId = useMemo(() => id || `${CLASSNAME.toLowerCase()}-${uid()}`, [id]);
87
+ const generatedInputId = useId();
88
+ const inputId = id || generatedInputId;
88
89
 
89
90
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
90
91
  if (onChange) {
@@ -12,6 +12,7 @@ import { usePreviousValue } from '@lumx/react/hooks/usePreviousValue';
12
12
  import { getYearDisplayName } from '@lumx/react/utils/date/getYearDisplayName';
13
13
  import { onEnterPressed } from '@lumx/react/utils/event';
14
14
  import { addMonthResetDay } from '@lumx/react/utils/date/addMonthResetDay';
15
+ import { formatDayNumber } from '@lumx/react/utils/date/formatDayNumber';
15
16
  import { CLASSNAME } from './constants';
16
17
 
17
18
  /**
@@ -198,9 +199,7 @@ export const DatePickerControlled: Comp<DatePickerControlledProps, HTMLDivElemen
198
199
  type="button"
199
200
  onClick={() => onChange(date)}
200
201
  >
201
- <span aria-hidden>
202
- {date.toLocaleDateString(locale, { day: 'numeric' })}
203
- </span>
202
+ <span aria-hidden>{formatDayNumber(locale, date)}</span>
204
203
  <span className="visually-hidden">
205
204
  {date.toLocaleDateString(locale, {
206
205
  day: 'numeric',
@@ -45,7 +45,7 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
45
45
  */
46
46
  const CONFIG = {
47
47
  [Kind.error]: { color: ColorPalette.red, icon: mdiAlert },
48
- [Kind.info]: { color: ColorPalette.dark, icon: mdiInformation },
48
+ [Kind.info]: { color: ColorPalette.blue, icon: mdiInformation },
49
49
  [Kind.success]: { color: ColorPalette.green, icon: mdiCheckCircle },
50
50
  [Kind.warning]: { color: ColorPalette.yellow, icon: mdiAlertCircle },
51
51
  };
@@ -1,12 +1,13 @@
1
- import React, { forwardRef, ReactNode, useRef, useState, useMemo, useContext } from 'react';
1
+ import React, { forwardRef, ReactNode, useRef, useState, useContext } from 'react';
2
2
 
3
3
  import { mdiChevronDown, mdiChevronUp } from '@lumx/icons';
4
4
  import { Icon, Size, Text, Orientation, Popover, Placement, Theme } from '@lumx/react';
5
- import uniqueId from 'lodash/uniqueId';
6
5
  import classNames from 'classnames';
7
6
  import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
8
7
  import { HasClassName } from '@lumx/react/utils/type';
9
8
  import { ThemeContext } from '@lumx/react/utils/ThemeContext';
9
+ import { useId } from '@lumx/react/hooks/useId';
10
+
10
11
  import { CLASSNAME as ITEM_CLASSNAME } from './NavigationItem';
11
12
  import { NavigationContext } from './context';
12
13
 
@@ -34,7 +35,7 @@ export const NavigationSection = Object.assign(
34
35
  const { children, className, label, icon, ...forwardedProps } = props;
35
36
  const [isOpen, setIsOpen] = useState(false);
36
37
  const buttonRef = useRef<HTMLButtonElement>(null);
37
- const sectionId = useMemo(() => uniqueId('section'), []);
38
+ const sectionId = useId();
38
39
  const { orientation } = useContext(NavigationContext) || {};
39
40
  const theme = useContext(ThemeContext);
40
41
  const isDropdown = orientation === Orientation.horizontal;
@@ -14,7 +14,7 @@ export const NOTIFICATION_CONFIGURATION = {
14
14
  icon: mdiAlert,
15
15
  },
16
16
  info: {
17
- color: 'dark',
17
+ color: 'blue',
18
18
  icon: mdiInformation,
19
19
  },
20
20
  success: {
@@ -1,12 +1,12 @@
1
- import React, { useMemo, forwardRef, ReactNode, SyntheticEvent, InputHTMLAttributes } from 'react';
1
+ import React, { forwardRef, ReactNode, SyntheticEvent, InputHTMLAttributes } from 'react';
2
2
 
3
3
  import classNames from 'classnames';
4
- import { uid } from 'uid';
5
4
 
6
5
  import { InputHelper, InputLabel, Theme } from '@lumx/react';
7
6
 
8
7
  import { Comp, GenericProps, HasTheme } from '@lumx/react/utils/type';
9
8
  import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
9
+ import { useId } from '@lumx/react/hooks/useId';
10
10
 
11
11
  /**
12
12
  * Defines the props of the component.
@@ -76,7 +76,8 @@ export const RadioButton: Comp<RadioButtonProps, HTMLDivElement> = forwardRef((p
76
76
  inputProps,
77
77
  ...forwardedProps
78
78
  } = props;
79
- const inputId = useMemo(() => id || `${CLASSNAME.toLowerCase()}-${uid()}`, [id]);
79
+ const generatedInputId = useId();
80
+ const inputId = id || generatedInputId;
80
81
 
81
82
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
82
83
  if (onChange) {
@@ -14,7 +14,7 @@ import { Select, SelectProps, SelectVariant } from './Select';
14
14
  const CLASSNAME = Select.className as string;
15
15
 
16
16
  jest.mock('@lumx/react/utils/isFocusVisible');
17
- jest.mock('uid', () => ({ uid: () => 'uid' }));
17
+ jest.mock('@lumx/react/hooks/useId', () => ({ useId: () => ':r1:' }));
18
18
 
19
19
  /**
20
20
  * Mounts the component and returns common DOM elements / data needed in multiple tests further down.
@@ -18,7 +18,7 @@ import { SelectVariant } from './constants';
18
18
 
19
19
  const CLASSNAME = SelectMultiple.className as string;
20
20
 
21
- jest.mock('uid', () => ({ uid: () => 'uid' }));
21
+ jest.mock('@lumx/react/hooks/useId', () => ({ useId: () => ':r1:' }));
22
22
 
23
23
  /**
24
24
  * Mounts the component and returns common DOM elements / data needed in multiple tests further down.
@@ -1,6 +1,5 @@
1
1
  import classNames from 'classnames';
2
- import React, { Ref, useCallback, useMemo, useRef } from 'react';
3
- import { uid } from 'uid';
2
+ import React, { Ref, useCallback, useRef } from 'react';
4
3
 
5
4
  import { Placement } from '@lumx/react';
6
5
  import { Kind, Theme } from '@lumx/react/components';
@@ -11,6 +10,7 @@ import { useListenFocus } from '@lumx/react/hooks/useListenFocus';
11
10
  import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
12
11
  import { mergeRefs } from '@lumx/react/utils/mergeRefs';
13
12
 
13
+ import { useId } from '@lumx/react/hooks/useId';
14
14
  import { CoreSelectProps, SelectVariant } from './constants';
15
15
 
16
16
  /** The display name of the component. */
@@ -56,7 +56,8 @@ export const WithSelectContext = (
56
56
  }: CoreSelectProps,
57
57
  ref: Ref<HTMLDivElement>,
58
58
  ): React.ReactElement => {
59
- const selectId = useMemo(() => id || `select-${uid()}`, [id]);
59
+ const generatedSelectId = useId();
60
+ const selectId = id || generatedSelectId;
60
61
  const anchorRef = useRef<HTMLElement>(null);
61
62
  const selectRef = useRef<HTMLDivElement>(null);
62
63
  const dropdownRef = useRef<HTMLDivElement>(null);
@@ -10,7 +10,7 @@ const CLASSNAME = SideNavigationItem.className as string;
10
10
 
11
11
  const toggleButtonProps = { label: 'Toggle' };
12
12
 
13
- jest.mock('uid', () => ({ uid: () => 'id' }));
13
+ jest.mock('@lumx/react/hooks/useId', () => ({ useId: () => ':r1:' }));
14
14
 
15
15
  /**
16
16
  * Mounts the component and returns common DOM elements / data needed in multiple tests further down.
@@ -2,7 +2,6 @@ import React, { Children, forwardRef, ReactNode } from 'react';
2
2
 
3
3
  import classNames from 'classnames';
4
4
  import isEmpty from 'lodash/isEmpty';
5
- import { uid } from 'uid';
6
5
 
7
6
  import { mdiChevronDown, mdiChevronUp } from '@lumx/icons';
8
7
 
@@ -12,6 +11,7 @@ import { Comp, GenericProps, isComponent } from '@lumx/react/utils/type';
12
11
  import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
13
12
  import { renderLink } from '@lumx/react/utils/renderLink';
14
13
  import { renderButtonOrLink } from '@lumx/react/utils/renderButtonOrLink';
14
+ import { useId } from '@lumx/react/hooks/useId';
15
15
 
16
16
  /**
17
17
  * Defines the props of the component.
@@ -95,7 +95,7 @@ export const SideNavigationItem: Comp<SideNavigationItemProps, HTMLLIElement> =
95
95
  const shouldSplitActions = Boolean(onActionClick);
96
96
  const showChildren = hasContent && isOpen;
97
97
 
98
- const contentId = React.useMemo(uid, []);
98
+ const contentId = useId();
99
99
  const ariaProps: any = {};
100
100
  if (hasContent) {
101
101
  ariaProps['aria-expanded'] = !!showChildren;
@@ -7,7 +7,7 @@ import { Slider, SliderProps } from './Slider';
7
7
 
8
8
  const CLASSNAME = Slider.className as string;
9
9
 
10
- jest.mock('uid', () => ({ uid: () => 'uid' }));
10
+ jest.mock('@lumx/react/hooks/useId', () => ({ useId: () => ':r1:' }));
11
11
 
12
12
  const setup = (props: Partial<SliderProps> = {}) => {
13
13
  render(<Slider {...(props as any)} />);
@@ -9,8 +9,8 @@ import useEventCallback from '@lumx/react/hooks/useEventCallback';
9
9
  import { Comp, GenericProps, HasTheme } from '@lumx/react/utils/type';
10
10
  import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
11
11
 
12
- import { uid } from 'uid';
13
12
  import { clamp } from '@lumx/react/utils/clamp';
13
+ import { useId } from '@lumx/react/hooks/useId';
14
14
 
15
15
  /**
16
16
  * Defines the props of the component.
@@ -111,7 +111,8 @@ export const Slider: Comp<SliderProps, HTMLDivElement> = forwardRef((props, ref)
111
111
  value,
112
112
  ...forwardedProps
113
113
  } = props;
114
- const sliderId = useMemo(() => id || `slider-${uid()}`, [id]);
114
+ const generatedId = useId();
115
+ const sliderId = id || generatedId;
115
116
  const sliderLabelId = useMemo(() => `label-${sliderId}`, [sliderId]);
116
117
  const sliderRef = useRef<HTMLDivElement>(null);
117
118
 
@@ -24,7 +24,7 @@ const setup = (propsOverride: SetupProps = {}) => {
24
24
  return { switchWrapper, input, helper, label, props };
25
25
  };
26
26
 
27
- jest.mock('uid', () => ({ uid: () => 'uid' }));
27
+ jest.mock('@lumx/react/hooks/useId', () => ({ useId: () => ':r1:' }));
28
28
 
29
29
  describe(`<${Switch.displayName}>`, () => {
30
30
  describe('Props', () => {
@@ -1,7 +1,6 @@
1
- import React, { Children, forwardRef, InputHTMLAttributes, SyntheticEvent, useMemo } from 'react';
1
+ import React, { Children, forwardRef, InputHTMLAttributes, SyntheticEvent } from 'react';
2
2
 
3
3
  import classNames from 'classnames';
4
- import { uid } from 'uid';
5
4
 
6
5
  import isEmpty from 'lodash/isEmpty';
7
6
 
@@ -9,6 +8,7 @@ import { Alignment, InputHelper, InputLabel, Theme } from '@lumx/react';
9
8
 
10
9
  import { Comp, GenericProps, HasTheme } from '@lumx/react/utils/type';
11
10
  import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
11
+ import { useId } from '@lumx/react/hooks/useId';
12
12
 
13
13
  /**
14
14
  * Defines the props of the component.
@@ -75,7 +75,8 @@ export const Switch: Comp<SwitchProps, HTMLDivElement> = forwardRef((props, ref)
75
75
  inputProps = {},
76
76
  ...forwardedProps
77
77
  } = props;
78
- const inputId = useMemo(() => id || `switch-${uid()}`, [id]);
78
+ const generatedInputId = useId();
79
+ const inputId = id || generatedInputId;
79
80
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
80
81
  if (onChange) {
81
82
  onChange(!isChecked, value, name, event);
@@ -1,5 +1,5 @@
1
1
  import { Dispatch, createContext, useCallback, useContext, useEffect, useMemo } from 'react';
2
- import { uid } from 'uid';
2
+ import { useId } from '@lumx/react/hooks/useId';
3
3
 
4
4
  type TabType = 'tab' | 'tabPanel';
5
5
 
@@ -76,11 +76,9 @@ export const useTabProviderContext = (type: TabType, originalId?: string): undef
76
76
  const [state, dispatch] = context;
77
77
 
78
78
  // Current tab or tab panel id.
79
- const id = useMemo(
80
- () => originalId || `${type}-${uid()}`,
81
- // eslint-disable-next-line react-hooks/exhaustive-deps
82
- [],
83
- );
79
+ const generatedId = useId();
80
+ const id = originalId || generatedId;
81
+
84
82
  useEffect(
85
83
  () => {
86
84
  // On mount: register tab or tab panel id.
@@ -1,18 +1,7 @@
1
- import React, {
2
- forwardRef,
3
- ReactNode,
4
- Ref,
5
- RefObject,
6
- SyntheticEvent,
7
- useEffect,
8
- useMemo,
9
- useRef,
10
- useState,
11
- } from 'react';
1
+ import React, { forwardRef, ReactNode, Ref, RefObject, SyntheticEvent, useEffect, useRef, useState } from 'react';
12
2
 
13
3
  import classNames from 'classnames';
14
4
  import get from 'lodash/get';
15
- import { uid } from 'uid';
16
5
 
17
6
  import { mdiAlertCircle, mdiCheckCircle, mdiCloseCircle } from '@lumx/icons';
18
7
  import {
@@ -30,6 +19,7 @@ import {
30
19
  import { Comp, GenericProps, HasTheme } from '@lumx/react/utils/type';
31
20
  import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
32
21
  import { mergeRefs } from '@lumx/react/utils/mergeRefs';
22
+ import { useId } from '@lumx/react/hooks/useId';
33
23
 
34
24
  /**
35
25
  * Defines the props of the component.
@@ -296,7 +286,8 @@ export const TextField: Comp<TextFieldProps, HTMLDivElement> = forwardRef((props
296
286
  afterElement,
297
287
  ...forwardedProps
298
288
  } = props;
299
- const textFieldId = useMemo(() => id || `text-field-${uid()}`, [id]);
289
+ const generatedTextFieldId = useId();
290
+ const textFieldId = id || generatedTextFieldId;
300
291
  /** Keep a clean local input ref to manage focus */
301
292
  const localInputRef = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null);
302
293
  /** Merge prop input ref and local input ref */
@@ -307,8 +298,8 @@ export const TextField: Comp<TextFieldProps, HTMLDivElement> = forwardRef((props
307
298
  * we want to first use the most important one, which is the errorId. That way, screen readers will read first
308
299
  * the error and then the helper
309
300
  */
310
- const helperId = helper ? `text-field-helper-${uid()}` : undefined;
311
- const errorId = error ? `text-field-error-${uid()}` : undefined;
301
+ const helperId = helper ? `text-field-helper-${generatedTextFieldId}` : undefined;
302
+ const errorId = error ? `text-field-error-${generatedTextFieldId}` : undefined;
312
303
  const describedByIds = [errorId, helperId, forwardedProps['aria-describedby']].filter(Boolean);
313
304
  const describedById = describedByIds.length === 0 ? undefined : describedByIds.join(' ');
314
305
 
@@ -12,7 +12,7 @@ import { Tooltip, TooltipProps } from './Tooltip';
12
12
  const CLASSNAME = Tooltip.className as string;
13
13
 
14
14
  jest.mock('@lumx/react/utils/isFocusVisible');
15
- jest.mock('uid', () => ({ uid: () => 'uid' }));
15
+ jest.mock('@lumx/react/hooks/useId', () => ({ useId: () => ':r1:' }));
16
16
  // Skip delays
17
17
  jest.mock('@lumx/react/constants', () => ({
18
18
  ...jest.requireActual('@lumx/react/constants'),
@@ -1,8 +1,7 @@
1
1
  /* eslint-disable react-hooks/rules-of-hooks */
2
- import React, { forwardRef, ReactNode, useMemo, useState } from 'react';
2
+ import React, { forwardRef, ReactNode, useState } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
4
  import { usePopper } from 'react-popper';
5
- import { uid } from 'uid';
6
5
 
7
6
  import classNames from 'classnames';
8
7
 
@@ -11,8 +10,9 @@ import { Comp, GenericProps } from '@lumx/react/utils/type';
11
10
  import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
12
11
  import { mergeRefs } from '@lumx/react/utils/mergeRefs';
13
12
  import { Placement } from '@lumx/react/components/popover';
14
-
15
13
  import { TooltipContextProvider } from '@lumx/react/components/tooltip/context';
14
+ import { useId } from '@lumx/react/hooks/useId';
15
+
16
16
  import { useInjectTooltipRef } from './useInjectTooltipRef';
17
17
  import { useTooltipOpen } from './useTooltipOpen';
18
18
 
@@ -71,7 +71,7 @@ export const Tooltip: Comp<TooltipProps, HTMLDivElement> = forwardRef((props, re
71
71
  return <TooltipContextProvider>{children}</TooltipContextProvider>;
72
72
  }
73
73
 
74
- const id = useMemo(() => `tooltip-${uid()}`, []);
74
+ const id = useId();
75
75
 
76
76
  const [popperElement, setPopperElement] = useState<null | HTMLElement>(null);
77
77
  const [anchorElement, setAnchorElement] = useState<null | HTMLElement>(null);
@@ -1,11 +1,11 @@
1
1
  import React, { forwardRef, MouseEventHandler } from 'react';
2
2
  import classNames from 'classnames';
3
- import uniqueId from 'lodash/uniqueId';
4
3
 
5
4
  import { AspectRatio, Icon, Size, Theme } from '@lumx/react';
6
5
  import { Comp, GenericProps, HasTheme, ValueOf } from '@lumx/react/utils/type';
7
6
  import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
8
7
  import { useBooleanState } from '@lumx/react/hooks/useBooleanState';
8
+ import { useId } from '@lumx/react/hooks/useId';
9
9
 
10
10
  /**
11
11
  * Uploader variants.
@@ -81,7 +81,8 @@ export const Uploader: Comp<UploaderProps> = forwardRef((props, ref) => {
81
81
  // Adjust to square aspect ratio when using circle variants.
82
82
  const adjustedAspectRatio = variant === UploaderVariant.circle ? AspectRatio.square : aspectRatio;
83
83
 
84
- const inputId = React.useMemo(() => fileInputProps?.id || uniqueId('uploader-input-'), [fileInputProps?.id]);
84
+ const generatedInputId = useId();
85
+ const inputId = fileInputProps?.id || generatedInputId;
85
86
  const [isDragHovering, unsetDragHovering, setDragHovering] = useBooleanState(false);
86
87
  const wrapper = fileInputProps
87
88
  ? { Component: 'label' as const, props: { htmlFor: inputId } as const }
@@ -9,7 +9,7 @@ import { withCombinations } from '@lumx/react/stories/decorators/withCombination
9
9
  import { getSelectArgType } from '@lumx/react/stories/controls/selectArgType';
10
10
  import { UserBlock } from './UserBlock';
11
11
 
12
- const sizes = [Size.s, Size.m, Size.l];
12
+ const sizes = [Size.xs, Size.s, Size.m, Size.l];
13
13
 
14
14
  export default {
15
15
  title: 'LumX components/user-block/UserBlock',
@@ -12,7 +12,7 @@ import { AvatarProps } from '../avatar/Avatar';
12
12
  /**
13
13
  * User block sizes.
14
14
  */
15
- export type UserBlockSize = Extract<Size, 's' | 'm' | 'l'>;
15
+ export type UserBlockSize = Extract<Size, 'xs' | 's' | 'm' | 'l'>;
16
16
 
17
17
  /**
18
18
  * Defines the props of the component.
@@ -128,7 +128,7 @@ export const UserBlock: Comp<UserBlockProps, HTMLDivElement> = forwardRef((props
128
128
  return <NameComponent {...nProps}>{name}</NameComponent>;
129
129
  }, [avatarProps, isClickable, linkAs, linkProps, name, nameProps, onClick]);
130
130
 
131
- const fieldsBlock: ReactNode = fields && componentSize !== Size.s && (
131
+ const fieldsBlock: ReactNode = fields && componentSize !== Size.s && componentSize !== Size.xs && (
132
132
  <div className={`${CLASSNAME}__fields`}>
133
133
  {fields.map((field: string, idx: number) => (
134
134
  <span key={idx} className={`${CLASSNAME}__field`}>
@@ -0,0 +1,23 @@
1
+ import { useId } from '@lumx/react/hooks/useId';
2
+ import { render } from '@testing-library/react';
3
+ import React from 'react';
4
+
5
+ function setup() {
6
+ const Component = (): any => useId();
7
+ const result = render(<Component />);
8
+ const rerender = () => result.rerender(<Component />);
9
+ return { ...result, rerender };
10
+ }
11
+
12
+ describe(useId, () => {
13
+ it('should render a unique id stable after re-renders', () => {
14
+ const result = setup();
15
+ // Id generated
16
+ const initialId = result.container.textContent;
17
+ expect(initialId).toMatch(/:lumx\d+:/);
18
+
19
+ // Id is stable after re-render
20
+ result.rerender();
21
+ expect(result.container.textContent).toEqual(initialId);
22
+ });
23
+ });
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+
3
+ let i = 0;
4
+
5
+ /**
6
+ * Generate a unique id (for use in a11y or other id based DOM linking).
7
+ *
8
+ * (Tries to emulate React 18 useId hook, to remove once we upgrade React)
9
+ */
10
+ export function useId() {
11
+ return React.useMemo(() => {
12
+ i += 1;
13
+ return `:lumx${i}:`;
14
+ }, []);
15
+ }
@@ -1,8 +1,8 @@
1
- import { useState, useCallback, useEffect, useMemo } from 'react';
1
+ import { useState, useCallback, useEffect } from 'react';
2
2
 
3
3
  import { useInterval } from '@lumx/react/hooks/useInterval';
4
- import uniqueId from 'lodash/uniqueId';
5
4
  import { AUTOPLAY_DEFAULT_INTERVAL } from '@lumx/react/components/slideshow/constants';
5
+ import { useId } from '@lumx/react/hooks/useId';
6
6
 
7
7
  export interface UseSlideshowControlsOptions {
8
8
  /** default active index to be displayed */
@@ -193,8 +193,11 @@ export const useSlideshowControls = ({
193
193
  onChange(currentIndex);
194
194
  }, [currentIndex, onChange]);
195
195
 
196
- const slideshowId = useMemo(() => id || uniqueId('slideshow'), [id]);
197
- const slideshowSlidesId = useMemo(() => slidesId || uniqueId('slideshow-slides'), [slidesId]);
196
+ const generatedSlideshowId = useId();
197
+ const slideshowId = id || generatedSlideshowId;
198
+
199
+ const generatedSlidesId = useId();
200
+ const slideshowSlidesId = slidesId || generatedSlidesId;
198
201
 
199
202
  const toggleAutoPlay = () => {
200
203
  if (isSlideshowAutoPlaying) {
@@ -7,4 +7,5 @@ export { App as Extended } from './extended';
7
7
  export { App as SizeL } from './size-l';
8
8
  export { App as SizeM } from './size-m';
9
9
  export { App as SizeS } from './size-s';
10
+ export { App as SizeXs } from './size-xs';
10
11
  export { App as Vertical } from './vertical';
@@ -0,0 +1,12 @@
1
+ import { formatDayNumber } from '@lumx/react/utils/date/formatDayNumber';
2
+
3
+ describe(formatDayNumber, () => {
4
+ it('should format ', () => {
5
+ // Standard numerical formatting.
6
+ expect(formatDayNumber('en', new Date('2024-05-11'))).toEqual('11');
7
+ // Keep only the numerical part (if any). Raw formatted day number here is '11日'.
8
+ expect(formatDayNumber('ja', new Date('2024-05-11'))).toEqual('11');
9
+ // Else Keep the non-numerical formatting
10
+ expect(formatDayNumber('ar-eg', new Date('2024-05-11'))).toEqual('١١');
11
+ });
12
+ });
@@ -0,0 +1,5 @@
1
+ export function formatDayNumber(locale: string, date: Date): string {
2
+ const formattedDate = date.toLocaleDateString(locale, { day: 'numeric' });
3
+ if (formattedDate.match(/\d/)) return formattedDate.replace(/\D*(\d+)\D*/g, '$1');
4
+ return formattedDate;
5
+ }