@lumx/react 2.0.0-alpha.0 → 2.0.1

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 (102) hide show
  1. package/esm/_internal/AlertDialog.js +15 -5
  2. package/esm/_internal/AlertDialog.js.map +1 -1
  3. package/esm/_internal/AutocompleteMultiple.js +6 -2
  4. package/esm/_internal/AutocompleteMultiple.js.map +1 -1
  5. package/esm/_internal/ButtonRoot.js +1 -0
  6. package/esm/_internal/ButtonRoot.js.map +1 -1
  7. package/esm/_internal/ClickAwayProvider.js +31 -30
  8. package/esm/_internal/ClickAwayProvider.js.map +1 -1
  9. package/esm/_internal/Dialog2.js +6 -3
  10. package/esm/_internal/Dialog2.js.map +1 -1
  11. package/esm/_internal/Dropdown2.js +6 -2
  12. package/esm/_internal/Dropdown2.js.map +1 -1
  13. package/esm/_internal/Icon2.js +1 -1
  14. package/esm/_internal/Icon2.js.map +1 -1
  15. package/esm/_internal/Lightbox2.js +4 -2
  16. package/esm/_internal/Lightbox2.js.map +1 -1
  17. package/esm/_internal/Message2.js +4 -3
  18. package/esm/_internal/Message2.js.map +1 -1
  19. package/esm/_internal/Popover2.js +4 -2
  20. package/esm/_internal/Popover2.js.map +1 -1
  21. package/esm/_internal/SelectMultiple.js +39 -61
  22. package/esm/_internal/SelectMultiple.js.map +1 -1
  23. package/esm/_internal/SlideshowControls.js +93 -44
  24. package/esm/_internal/SlideshowControls.js.map +1 -1
  25. package/esm/_internal/Switch2.js +2 -0
  26. package/esm/_internal/Switch2.js.map +1 -1
  27. package/esm/_internal/alert-dialog.js +1 -1
  28. package/esm/_internal/autocomplete.js +1 -2
  29. package/esm/_internal/autocomplete.js.map +1 -1
  30. package/esm/_internal/button.js +1 -2
  31. package/esm/_internal/button.js.map +1 -1
  32. package/esm/_internal/date-picker.js +1 -2
  33. package/esm/_internal/date-picker.js.map +1 -1
  34. package/esm/_internal/dialog.js +1 -1
  35. package/esm/_internal/dropdown.js +1 -1
  36. package/esm/_internal/expansion-panel.js +1 -2
  37. package/esm/_internal/expansion-panel.js.map +1 -1
  38. package/esm/_internal/getRootClassName.js +78 -68
  39. package/esm/_internal/getRootClassName.js.map +1 -1
  40. package/esm/_internal/lightbox.js +1 -2
  41. package/esm/_internal/lightbox.js.map +1 -1
  42. package/esm/_internal/popover.js +1 -1
  43. package/esm/_internal/select.js +1 -2
  44. package/esm/_internal/select.js.map +1 -1
  45. package/esm/_internal/side-navigation.js +1 -2
  46. package/esm/_internal/side-navigation.js.map +1 -1
  47. package/esm/_internal/slideshow.js +1 -2
  48. package/esm/_internal/slideshow.js.map +1 -1
  49. package/esm/_internal/text-field.js +1 -2
  50. package/esm/_internal/text-field.js.map +1 -1
  51. package/esm/_internal/tooltip.js +1 -2
  52. package/esm/_internal/tooltip.js.map +1 -1
  53. package/esm/_internal/useDelayedVisibility.js +16 -2
  54. package/esm/_internal/useDelayedVisibility.js.map +1 -1
  55. package/esm/_internal/useDisableBodyScroll.js +205 -7
  56. package/esm/_internal/useDisableBodyScroll.js.map +1 -1
  57. package/esm/index.js +1 -2
  58. package/esm/index.js.map +1 -1
  59. package/package.json +6 -6
  60. package/src/components/alert-dialog/AlertDialog.stories.tsx +29 -0
  61. package/src/components/alert-dialog/AlertDialog.tsx +29 -9
  62. package/src/components/alert-dialog/__snapshots__/AlertDialog.test.tsx.snap +91 -0
  63. package/src/components/autocomplete/Autocomplete.test.tsx +2 -0
  64. package/src/components/autocomplete/Autocomplete.tsx +7 -0
  65. package/src/components/autocomplete/AutocompleteMultiple.test.tsx +2 -0
  66. package/src/components/autocomplete/AutocompleteMultiple.tsx +2 -0
  67. package/src/components/autocomplete/__snapshots__/Autocomplete.test.tsx.snap +2 -0
  68. package/src/components/autocomplete/__snapshots__/AutocompleteMultiple.test.tsx.snap +2 -0
  69. package/src/components/button/Button.stories.tsx +8 -2
  70. package/src/components/button/ButtonRoot.tsx +1 -1
  71. package/src/components/comment-block/CommentBlock.stories.tsx +3 -2
  72. package/src/components/comment-block/__snapshots__/CommentBlock.test.tsx.snap +1 -1
  73. package/src/components/dialog/Dialog.stories.tsx +62 -22
  74. package/src/components/dialog/Dialog.tsx +8 -2
  75. package/src/components/dialog/__snapshots__/Dialog.test.tsx.snap +99 -35
  76. package/src/components/icon/Icon.tsx +1 -0
  77. package/src/components/icon/__snapshots__/Icon.test.tsx.snap +2 -2
  78. package/src/components/lightbox/Lightbox.tsx +4 -1
  79. package/src/components/lightbox/__snapshots__/Lightbox.test.tsx.snap +7 -5
  80. package/src/components/message/Message.stories.tsx +8 -0
  81. package/src/components/message/Message.tsx +4 -2
  82. package/src/components/popover/Popover.tsx +4 -1
  83. package/src/components/popover/__snapshots__/Popover.test.tsx.snap +100 -80
  84. package/src/components/select/WithSelectContext.tsx +3 -60
  85. package/src/components/slideshow/Slideshow.stories.tsx +47 -7
  86. package/src/components/slideshow/Slideshow.test.tsx +4 -1
  87. package/src/components/slideshow/Slideshow.tsx +51 -26
  88. package/src/components/slideshow/SlideshowControls.stories.tsx +0 -4
  89. package/src/components/slideshow/SlideshowControls.tsx +23 -8
  90. package/src/components/slideshow/__snapshots__/Slideshow.test.tsx.snap +0 -5
  91. package/src/components/slideshow/useKeyNavigate.ts +28 -0
  92. package/src/components/slideshow/useSwipeNavigate.ts +18 -0
  93. package/src/components/switch/Switch.tsx +2 -0
  94. package/src/components/switch/__snapshots__/Switch.test.tsx.snap +8 -0
  95. package/src/hooks/useClickAway.tsx +6 -5
  96. package/src/hooks/useDelayedVisibility.tsx +22 -2
  97. package/src/hooks/useDisableBodyScroll.ts +16 -1
  98. package/src/hooks/useInfiniteScroll.tsx +14 -3
  99. package/src/hooks/useListenFocus.tsx +26 -0
  100. package/src/utils/ClickAwayProvider/ClickAwayProvider.tsx +23 -32
  101. package/types.d.ts +17 -8
  102. package/src/components/slideshow/useKeyOrSwipeNavigate.ts +0 -37
@@ -0,0 +1,18 @@
1
+ import { useEffect } from 'react';
2
+ import { detectHorizontalSwipe } from '@lumx/core/js/utils';
3
+
4
+ const isTouchDevice = () => 'ontouchstart' in window;
5
+
6
+ /**
7
+ * Listen swipe to navigate left and right.
8
+ */
9
+ export function useSwipeNavigate(element?: HTMLElement | null, onNext?: () => void, onPrevious?: () => void): void {
10
+ useEffect(() => {
11
+ if (!element || !isTouchDevice()) return undefined;
12
+
13
+ return detectHorizontalSwipe(element, (swipe) => {
14
+ const callback = swipe === 'right' ? onPrevious : onNext;
15
+ callback?.();
16
+ });
17
+ }, [onPrevious, onNext, element]);
18
+ }
@@ -100,12 +100,14 @@ export const Switch: Comp<SwitchProps, HTMLDivElement> = forwardRef((props, ref)
100
100
  <div className={`${CLASSNAME}__input-wrapper`}>
101
101
  <input
102
102
  type="checkbox"
103
+ role="switch"
103
104
  id={switchId}
104
105
  className={`${CLASSNAME}__input-native`}
105
106
  name={name}
106
107
  value={value}
107
108
  disabled={isDisabled}
108
109
  checked={isChecked}
110
+ aria-checked={Boolean(isChecked)}
109
111
  onChange={handleChange}
110
112
  />
111
113
 
@@ -8,9 +8,11 @@ exports[`<Switch> Conditions should not display the \`helper\` if no \`label\` i
8
8
  className="lumx-switch__input-wrapper"
9
9
  >
10
10
  <input
11
+ aria-checked={false}
11
12
  className="lumx-switch__input-native"
12
13
  id="switch-uid"
13
14
  onChange={[Function]}
15
+ role="switch"
14
16
  type="checkbox"
15
17
  />
16
18
  <div
@@ -35,9 +37,11 @@ exports[`<Switch> Snapshots and structure should render correctly with a \`label
35
37
  className="lumx-switch__input-wrapper"
36
38
  >
37
39
  <input
40
+ aria-checked={false}
38
41
  className="lumx-switch__input-native"
39
42
  id="switch-uid"
40
43
  onChange={[Function]}
44
+ role="switch"
41
45
  type="checkbox"
42
46
  />
43
47
  <div
@@ -80,9 +84,11 @@ exports[`<Switch> Snapshots and structure should render correctly with only a \`
80
84
  className="lumx-switch__input-wrapper"
81
85
  >
82
86
  <input
87
+ aria-checked={false}
83
88
  className="lumx-switch__input-native"
84
89
  id="switch-uid"
85
90
  onChange={[Function]}
91
+ role="switch"
86
92
  type="checkbox"
87
93
  />
88
94
  <div
@@ -118,9 +124,11 @@ exports[`<Switch> Snapshots and structure should render correctly without any la
118
124
  className="lumx-switch__input-wrapper"
119
125
  >
120
126
  <input
127
+ aria-checked={false}
121
128
  className="lumx-switch__input-native"
122
129
  id="switch-uid"
123
130
  onChange={[Function]}
131
+ role="switch"
124
132
  type="checkbox"
125
133
  />
126
134
  <div
@@ -8,7 +8,7 @@ const EVENT_TYPES = ['mousedown', 'touchstart'];
8
8
 
9
9
  function isClickAway(target: HTMLElement, refs: Array<RefObject<HTMLElement>>): boolean {
10
10
  // The target element is not contained in any of the listed element references.
11
- return !refs.some((e) => e && e.current && e.current.contains(target));
11
+ return !refs.some((e) => e?.current?.contains(target));
12
12
  }
13
13
 
14
14
  export interface ClickAwayParameters {
@@ -19,7 +19,7 @@ export interface ClickAwayParameters {
19
19
  /**
20
20
  * Elements from which we want to detect the click away.
21
21
  */
22
- refs: Array<RefObject<HTMLElement>>;
22
+ refs: RefObject<Array<RefObject<HTMLElement>>>;
23
23
  }
24
24
 
25
25
  /**
@@ -29,12 +29,13 @@ export interface ClickAwayParameters {
29
29
  */
30
30
  export function useClickAway({ callback, refs }: ClickAwayParameters): void {
31
31
  useEffect(() => {
32
- if (!callback || !refs || isEmpty(refs)) {
32
+ const { current: currentRefs } = refs;
33
+ if (!callback || !currentRefs || isEmpty(currentRefs)) {
33
34
  return undefined;
34
35
  }
35
36
  const listener: EventListener = (evt) => {
36
- if (isClickAway(evt.target as HTMLElement, refs)) {
37
- setTimeout(() => callback(evt));
37
+ if (isClickAway(evt.target as HTMLElement, currentRefs)) {
38
+ callback(evt);
38
39
  }
39
40
  };
40
41
 
@@ -1,4 +1,4 @@
1
- import { useEffect, useState } from 'react';
1
+ import { useEffect, useState, useRef } from 'react';
2
2
 
3
3
  /**
4
4
  * Returns true if the component is visible taking into account the component's
@@ -6,9 +6,14 @@ import { useEffect, useState } from 'react';
6
6
  *
7
7
  * @param isComponentVisible Whether the component intends to be visible or not.
8
8
  * @param transitionDuration time in ms that the transition takes for the specific component.
9
+ * @param onVisibilityChange Callback called when the visibility changes.
9
10
  * @return true if the component should be rendered
10
11
  */
11
- export function useDelayedVisibility(isComponentVisible: boolean, transitionDuration: number): boolean {
12
+ export function useDelayedVisibility(
13
+ isComponentVisible: boolean,
14
+ transitionDuration: number,
15
+ onVisibilityChange?: (isVisible: boolean) => void,
16
+ ): boolean {
12
17
  // Delay visibility to account for the 400ms of CSS opacity animation.
13
18
  const [isVisible, setVisible] = useState(isComponentVisible);
14
19
 
@@ -20,5 +25,20 @@ export function useDelayedVisibility(isComponentVisible: boolean, transitionDura
20
25
  }
21
26
  }, [isComponentVisible, transitionDuration]);
22
27
 
28
+ /**
29
+ * Since we don't want onVisibiltyChange function to trigger itself if when it changes,
30
+ * we store the previous visibility and only trigger when visibility is different
31
+ * than previous value.
32
+ */
33
+
34
+ const previousVisibility = useRef(isVisible);
35
+
36
+ useEffect(() => {
37
+ if (onVisibilityChange && previousVisibility.current !== isVisible) {
38
+ onVisibilityChange(isVisible);
39
+ previousVisibility.current = isVisible;
40
+ }
41
+ }, [isVisible, onVisibilityChange]);
42
+
23
43
  return isComponentVisible || isVisible;
24
44
  }
@@ -2,12 +2,27 @@ import { useEffect } from 'react';
2
2
  import { disableBodyScroll, enableBodyScroll } from 'body-scroll-lock';
3
3
  import { Falsy } from '@lumx/react/utils';
4
4
 
5
+ /**
6
+ * Disables the scroll on the body to make it only usable in the current modal element.
7
+ * When the modal element is not provided anymore, the scroll is restored.
8
+ *
9
+ * @param modalElement The modal element.
10
+ */
5
11
  export const useDisableBodyScroll = (modalElement: Element | Falsy): void => {
6
12
  useEffect(() => {
7
13
  if (!modalElement) {
8
14
  return undefined;
9
15
  }
16
+ // Fixing the document overflow style to prevent a bug that scrolls the window to the top.
17
+ const previousDocumentOverflow = document.documentElement.style.overflow;
18
+ document.documentElement.style.overflow = 'visible';
10
19
  disableBodyScroll(modalElement);
11
- return () => enableBodyScroll(modalElement);
20
+ return () => {
21
+ enableBodyScroll(modalElement);
22
+ // Restore the previous overflow style.
23
+ requestAnimationFrame(() => {
24
+ document.documentElement.style.overflow = previousDocumentOverflow;
25
+ });
26
+ };
12
27
  }, [modalElement]);
13
28
  };
@@ -7,6 +7,9 @@ type useInfiniteScrollType = (
7
7
  ) => void;
8
8
  type EventCallback = (evt?: Event) => void;
9
9
 
10
+ // The error margin in px we want to have for triggering infinite scroll
11
+ const SCROLL_TRIGGER_MARGIN = 5;
12
+
10
13
  /**
11
14
  * Listen to clicks away from a given element and callback the passed in function.
12
15
  *
@@ -14,14 +17,22 @@ type EventCallback = (evt?: Event) => void;
14
17
  * @param [callback] A callback function to call when the bottom of the reference element is reached.
15
18
  * @param [callbackOnMount] A callback function to call when the component is mounted.
16
19
  */
17
- export const useInfiniteScroll: useInfiniteScrollType = (ref, callback, callbackOnMount = false) => {
20
+ export const useInfiniteScroll: useInfiniteScrollType = (
21
+ ref,
22
+ callback,
23
+ callbackOnMount = false,
24
+ scrollTriggerMargin = SCROLL_TRIGGER_MARGIN,
25
+ ) => {
18
26
  useEffect(() => {
19
27
  const { current } = ref;
20
28
  if (!callback || !current) {
21
29
  return undefined;
22
30
  }
23
31
 
24
- const isAtBottom = () => Boolean(current && current.scrollTop + current.clientHeight >= current.scrollHeight);
32
+ const isAtBottom = () =>
33
+ Boolean(
34
+ current && current.scrollHeight - (current.scrollTop + current.clientHeight) <= scrollTriggerMargin,
35
+ );
25
36
 
26
37
  const onScroll = (e?: Event): void => {
27
38
  if (isAtBottom()) {
@@ -39,7 +50,7 @@ export const useInfiniteScroll: useInfiniteScrollType = (ref, callback, callback
39
50
  current.removeEventListener('scroll', onScroll);
40
51
  current.removeEventListener('resize', onScroll);
41
52
  };
42
- }, [ref, callback]);
53
+ }, [ref, callback, scrollTriggerMargin]);
43
54
 
44
55
  useEffect(() => {
45
56
  if (callback && callbackOnMount) {
@@ -0,0 +1,26 @@
1
+ import { RefObject, useEffect, useState } from 'react';
2
+
3
+ /**
4
+ * Listen on element focus to store the focus status.
5
+ */
6
+ export function useListenFocus(ref: RefObject<HTMLElement>) {
7
+ const [isFocus, setFocus] = useState(false);
8
+
9
+ useEffect(() => {
10
+ const { current: element } = ref;
11
+ if (!element) {
12
+ return undefined;
13
+ }
14
+
15
+ const onFocus = () => setFocus(true);
16
+ const onBlur = () => setFocus(false);
17
+ element.addEventListener('focus', onFocus);
18
+ element.addEventListener('blur', onBlur);
19
+ return () => {
20
+ element.removeEventListener('focus', onFocus);
21
+ element.removeEventListener('blur', onBlur);
22
+ };
23
+ }, [ref, setFocus]);
24
+
25
+ return isFocus;
26
+ }
@@ -1,42 +1,33 @@
1
- import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
2
-
1
+ import React, { createContext, useContext, useEffect } from 'react';
2
+ import pull from 'lodash/pull';
3
3
  import { ClickAwayParameters, useClickAway } from '@lumx/react/hooks/useClickAway';
4
4
 
5
- import uniq from 'lodash/uniq';
6
-
7
- type appendChildrenRefsType = (refs: ClickAwayParameters['refs']) => void;
8
- const ClickAwayAncestorContext = createContext<appendChildrenRefsType | null>(null);
5
+ const ClickAwayAncestorContext = createContext<ClickAwayParameters['refs'] | null>(null);
9
6
 
10
7
  /**
11
- * Component combining the `useClickAway` hook with a React context to hook into the React
12
- * component tree and make sure we take into account both the DOM tree and the React tree we trying to detect click away.
8
+ * Component combining the `useClickAway` hook with a React context to hook into the React component tree and make sure
9
+ * we take into account both the DOM tree and the React tree we trying to detect click away.
13
10
  *
14
11
  * @return the react component.
15
12
  */
16
13
  export const ClickAwayProvider: React.FC<ClickAwayParameters> = ({ children, callback, refs }) => {
17
- const appendAncestorChildrenRefs = useContext(ClickAwayAncestorContext);
18
- const [childrenRefs, setChildrenRefs] = useState<ClickAwayParameters['refs']>([]);
19
-
20
- const appendChildrenRefs = useCallback(
21
- (newChildrenRefs: ClickAwayParameters['refs']) => {
22
- setTimeout(() => {
23
- setChildrenRefs(uniq([...childrenRefs, ...newChildrenRefs]));
24
- });
25
- },
26
- [childrenRefs, setChildrenRefs],
27
- );
28
-
29
- const clickAwayRefs = useMemo(() => {
30
- // Use current refs and children refs in click away detection.
31
- const concatenatedRefs = [...refs, ...childrenRefs];
32
-
33
- // Forward refs to parent click away context.
34
- appendAncestorChildrenRefs?.(concatenatedRefs);
35
-
36
- return concatenatedRefs;
37
- }, [refs, childrenRefs, appendAncestorChildrenRefs]);
38
-
39
- useClickAway({ callback, refs: clickAwayRefs });
40
- return <ClickAwayAncestorContext.Provider value={appendChildrenRefs}>{children}</ClickAwayAncestorContext.Provider>;
14
+ const ancestorChildrenRefs = useContext(ClickAwayAncestorContext);
15
+
16
+ useEffect(() => {
17
+ const { current: currentRefs } = refs;
18
+ const { current: currentAncestorChildrenRefs } = ancestorChildrenRefs || {};
19
+ if (!currentAncestorChildrenRefs || !currentRefs) {
20
+ return undefined;
21
+ }
22
+ // Push current refs to parent.
23
+ currentAncestorChildrenRefs.push(...currentRefs);
24
+ return () => {
25
+ // Pull current refs from parent.
26
+ pull(currentAncestorChildrenRefs, ...currentRefs);
27
+ };
28
+ }, [ancestorChildrenRefs, refs]);
29
+
30
+ useClickAway({ callback, refs });
31
+ return <ClickAwayAncestorContext.Provider value={refs}>{children}</ClickAwayAncestorContext.Provider>;
41
32
  };
42
33
  ClickAwayProvider.displayName = 'ClickAwayProvider';
package/types.d.ts CHANGED
@@ -152,16 +152,16 @@ export interface AlertDialogProps extends Omit<DialogProps, "header" | "footer">
152
152
  kind?: Kind;
153
153
  /** Dialog title. */
154
154
  title?: string;
155
- /** The props to the confirm button */
156
- confirmProps: {
155
+ /** Props forwarded to the confirm button */
156
+ confirmProps: ButtonProps & {
157
157
  onClick(): void;
158
158
  label: string;
159
159
  };
160
160
  /**
161
- * The props to the cancel button.
161
+ * Props forwarded to the cancel button.
162
162
  * Will not render a cancel button if undefined.
163
163
  */
164
- cancelProps?: {
164
+ cancelProps?: ButtonProps & {
165
165
  onClick(): void;
166
166
  label: string;
167
167
  };
@@ -240,6 +240,11 @@ export interface AutocompleteProps extends GenericProps {
240
240
  * @see {@link TextFieldProps#isDisabled}
241
241
  */
242
242
  isDisabled?: boolean;
243
+ /**
244
+ * Whether the component is required or not.
245
+ * @see {@link TextFieldProps#isRequired}
246
+ */
247
+ isRequired?: boolean;
243
248
  /**
244
249
  * Whether the text field is displayed with valid style or not.
245
250
  * @see {@link TextFieldProps#isValid}
@@ -755,6 +760,8 @@ export interface DialogProps extends GenericProps {
755
760
  dialogProps?: GenericProps;
756
761
  /** On close callback. */
757
762
  onClose?(): void;
763
+ /** Callback called when the open animation starts and the close animation finishes. */
764
+ onVisibilityChange?(isVisible: boolean): void;
758
765
  }
759
766
  export declare type DialogSizes = Extract<Size, "tiny" | "regular" | "big" | "huge">;
760
767
  /**
@@ -1473,6 +1480,8 @@ export interface MessageProps extends GenericProps {
1473
1480
  hasBackground?: boolean;
1474
1481
  /** Message variant. */
1475
1482
  kind?: Kind;
1483
+ /** Message custom icon SVG path. */
1484
+ icon?: string;
1476
1485
  }
1477
1486
  /**
1478
1487
  * Message component.
@@ -2027,8 +2036,8 @@ export interface SlideshowControlsProps extends GenericProps {
2027
2036
  activeIndex?: number;
2028
2037
  /** Props to pass to the next button (minus those already set by the SlideshowControls props). */
2029
2038
  nextButtonProps: Pick<IconButtonProps, "label"> & Omit<IconButtonProps, "label" | "onClick" | "icon" | "emphasis" | "color">;
2030
- /** Reference to the parent element. */
2031
- parentRef: RefObject<HTMLDivElement>;
2039
+ /** Reference to the parent element on which we want to listen touch swipe. */
2040
+ parentRef?: RefObject<HTMLDivElement> | HTMLDivElement;
2032
2041
  /** Props to pass to the previous button (minus those already set by the SlideshowControls props). */
2033
2042
  previousButtonProps: Pick<IconButtonProps, "label"> & Omit<IconButtonProps, "label" | "onClick" | "icon" | "emphasis" | "color">;
2034
2043
  /** Number of slides. */
@@ -2036,11 +2045,11 @@ export interface SlideshowControlsProps extends GenericProps {
2036
2045
  /** Theme adapting the component to light or dark background. */
2037
2046
  theme?: Theme;
2038
2047
  /** On next button click callback. */
2039
- onNextClick?(): void;
2048
+ onNextClick?(loopback?: boolean): void;
2040
2049
  /** On pagination change callback. */
2041
2050
  onPaginationClick?(index: number): void;
2042
2051
  /** On previous button click callback. */
2043
- onPreviousClick?(): void;
2052
+ onPreviousClick?(loopback?: boolean): void;
2044
2053
  }
2045
2054
  /**
2046
2055
  * SlideshowControls component.
@@ -1,37 +0,0 @@
1
- import { useEffect } from 'react';
2
- import { detectSwipe } from '@lumx/core/js/utils';
3
-
4
- /**
5
- * Listen keyboard and swipe to navigate left and right.
6
- */
7
- export function useKeyOrSwipeNavigate(
8
- element?: HTMLElement | null,
9
- onNext?: () => void,
10
- onPrevious?: () => void,
11
- ): void {
12
- useEffect(() => {
13
- if (!element) return undefined;
14
-
15
- const onNavigate = (evt: KeyboardEvent | string) => {
16
- let callback;
17
- if (typeof evt === 'string' ? evt === 'right' : evt?.key === 'ArrowRight') {
18
- callback = onPrevious;
19
- } else if (typeof evt === 'string' ? evt === 'left' : evt?.key === 'ArrowLeft') {
20
- callback = onNext;
21
- }
22
-
23
- callback?.();
24
- if (callback && typeof evt !== 'string') {
25
- evt.preventDefault();
26
- evt.stopPropagation();
27
- }
28
- };
29
-
30
- element.addEventListener('keydown', onNavigate);
31
- const removeSwipeListeners = detectSwipe(element, onNavigate);
32
- return () => {
33
- element.removeEventListener('keydown', onNavigate);
34
- removeSwipeListeners();
35
- };
36
- }, [onPrevious, onNext, element]);
37
- }