@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.
- package/esm/_internal/AlertDialog.js +15 -5
- package/esm/_internal/AlertDialog.js.map +1 -1
- package/esm/_internal/AutocompleteMultiple.js +6 -2
- package/esm/_internal/AutocompleteMultiple.js.map +1 -1
- package/esm/_internal/ButtonRoot.js +1 -0
- package/esm/_internal/ButtonRoot.js.map +1 -1
- package/esm/_internal/ClickAwayProvider.js +31 -30
- package/esm/_internal/ClickAwayProvider.js.map +1 -1
- package/esm/_internal/Dialog2.js +6 -3
- package/esm/_internal/Dialog2.js.map +1 -1
- package/esm/_internal/Dropdown2.js +6 -2
- package/esm/_internal/Dropdown2.js.map +1 -1
- package/esm/_internal/Icon2.js +1 -1
- package/esm/_internal/Icon2.js.map +1 -1
- package/esm/_internal/Lightbox2.js +4 -2
- package/esm/_internal/Lightbox2.js.map +1 -1
- package/esm/_internal/Message2.js +4 -3
- package/esm/_internal/Message2.js.map +1 -1
- package/esm/_internal/Popover2.js +4 -2
- package/esm/_internal/Popover2.js.map +1 -1
- package/esm/_internal/SelectMultiple.js +39 -61
- package/esm/_internal/SelectMultiple.js.map +1 -1
- package/esm/_internal/SlideshowControls.js +93 -44
- package/esm/_internal/SlideshowControls.js.map +1 -1
- package/esm/_internal/Switch2.js +2 -0
- package/esm/_internal/Switch2.js.map +1 -1
- package/esm/_internal/alert-dialog.js +1 -1
- package/esm/_internal/autocomplete.js +1 -2
- package/esm/_internal/autocomplete.js.map +1 -1
- package/esm/_internal/button.js +1 -2
- package/esm/_internal/button.js.map +1 -1
- package/esm/_internal/date-picker.js +1 -2
- package/esm/_internal/date-picker.js.map +1 -1
- package/esm/_internal/dialog.js +1 -1
- package/esm/_internal/dropdown.js +1 -1
- package/esm/_internal/expansion-panel.js +1 -2
- package/esm/_internal/expansion-panel.js.map +1 -1
- package/esm/_internal/getRootClassName.js +78 -68
- package/esm/_internal/getRootClassName.js.map +1 -1
- package/esm/_internal/lightbox.js +1 -2
- package/esm/_internal/lightbox.js.map +1 -1
- package/esm/_internal/popover.js +1 -1
- package/esm/_internal/select.js +1 -2
- package/esm/_internal/select.js.map +1 -1
- package/esm/_internal/side-navigation.js +1 -2
- package/esm/_internal/side-navigation.js.map +1 -1
- package/esm/_internal/slideshow.js +1 -2
- package/esm/_internal/slideshow.js.map +1 -1
- package/esm/_internal/text-field.js +1 -2
- package/esm/_internal/text-field.js.map +1 -1
- package/esm/_internal/tooltip.js +1 -2
- package/esm/_internal/tooltip.js.map +1 -1
- package/esm/_internal/useDelayedVisibility.js +16 -2
- package/esm/_internal/useDelayedVisibility.js.map +1 -1
- package/esm/_internal/useDisableBodyScroll.js +205 -7
- package/esm/_internal/useDisableBodyScroll.js.map +1 -1
- package/esm/index.js +1 -2
- package/esm/index.js.map +1 -1
- package/package.json +6 -6
- package/src/components/alert-dialog/AlertDialog.stories.tsx +29 -0
- package/src/components/alert-dialog/AlertDialog.tsx +29 -9
- package/src/components/alert-dialog/__snapshots__/AlertDialog.test.tsx.snap +91 -0
- package/src/components/autocomplete/Autocomplete.test.tsx +2 -0
- package/src/components/autocomplete/Autocomplete.tsx +7 -0
- package/src/components/autocomplete/AutocompleteMultiple.test.tsx +2 -0
- package/src/components/autocomplete/AutocompleteMultiple.tsx +2 -0
- package/src/components/autocomplete/__snapshots__/Autocomplete.test.tsx.snap +2 -0
- package/src/components/autocomplete/__snapshots__/AutocompleteMultiple.test.tsx.snap +2 -0
- package/src/components/button/Button.stories.tsx +8 -2
- package/src/components/button/ButtonRoot.tsx +1 -1
- package/src/components/comment-block/CommentBlock.stories.tsx +3 -2
- package/src/components/comment-block/__snapshots__/CommentBlock.test.tsx.snap +1 -1
- package/src/components/dialog/Dialog.stories.tsx +62 -22
- package/src/components/dialog/Dialog.tsx +8 -2
- package/src/components/dialog/__snapshots__/Dialog.test.tsx.snap +99 -35
- package/src/components/icon/Icon.tsx +1 -0
- package/src/components/icon/__snapshots__/Icon.test.tsx.snap +2 -2
- package/src/components/lightbox/Lightbox.tsx +4 -1
- package/src/components/lightbox/__snapshots__/Lightbox.test.tsx.snap +7 -5
- package/src/components/message/Message.stories.tsx +8 -0
- package/src/components/message/Message.tsx +4 -2
- package/src/components/popover/Popover.tsx +4 -1
- package/src/components/popover/__snapshots__/Popover.test.tsx.snap +100 -80
- package/src/components/select/WithSelectContext.tsx +3 -60
- package/src/components/slideshow/Slideshow.stories.tsx +47 -7
- package/src/components/slideshow/Slideshow.test.tsx +4 -1
- package/src/components/slideshow/Slideshow.tsx +51 -26
- package/src/components/slideshow/SlideshowControls.stories.tsx +0 -4
- package/src/components/slideshow/SlideshowControls.tsx +23 -8
- package/src/components/slideshow/__snapshots__/Slideshow.test.tsx.snap +0 -5
- package/src/components/slideshow/useKeyNavigate.ts +28 -0
- package/src/components/slideshow/useSwipeNavigate.ts +18 -0
- package/src/components/switch/Switch.tsx +2 -0
- package/src/components/switch/__snapshots__/Switch.test.tsx.snap +8 -0
- package/src/hooks/useClickAway.tsx +6 -5
- package/src/hooks/useDelayedVisibility.tsx +22 -2
- package/src/hooks/useDisableBodyScroll.ts +16 -1
- package/src/hooks/useInfiniteScroll.tsx +14 -3
- package/src/hooks/useListenFocus.tsx +26 -0
- package/src/utils/ClickAwayProvider/ClickAwayProvider.tsx +23 -32
- package/types.d.ts +17 -8
- package/src/components/slideshow/useKeyOrSwipeNavigate.ts +0 -37
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { Ref, useCallback,
|
|
1
|
+
import React, { Ref, useCallback, useMemo, useRef } from 'react';
|
|
2
2
|
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
import { uid } from 'uid';
|
|
@@ -10,7 +10,7 @@ import { Placement } from '@lumx/react/components/popover/Popover';
|
|
|
10
10
|
|
|
11
11
|
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils';
|
|
12
12
|
import { mergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
13
|
-
|
|
13
|
+
import { useListenFocus } from '@lumx/react/hooks/useListenFocus';
|
|
14
14
|
import { CoreSelectProps, SelectVariant } from './constants';
|
|
15
15
|
|
|
16
16
|
/** The display name of the component. */
|
|
@@ -25,57 +25,6 @@ export const DEFAULT_PROPS: Partial<CoreSelectProps> = {
|
|
|
25
25
|
variant: SelectVariant.input,
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
* Listen on element focus to store the focus status.
|
|
30
|
-
*
|
|
31
|
-
* @param element Element to focus.
|
|
32
|
-
* @param setIsFocus Setter used to store the focus status of the element.
|
|
33
|
-
* @param isOpen Is the list opened
|
|
34
|
-
* @param wasBlurred is it blurred
|
|
35
|
-
* @param setWasBlurred set blurred
|
|
36
|
-
* @param onBlur when its blurred
|
|
37
|
-
*/
|
|
38
|
-
function useHandleElementFocus(
|
|
39
|
-
element: HTMLElement | null,
|
|
40
|
-
setIsFocus: (b: boolean) => void,
|
|
41
|
-
isOpen: boolean,
|
|
42
|
-
wasBlurred: boolean,
|
|
43
|
-
setWasBlurred: (b: boolean) => void,
|
|
44
|
-
onBlur?: () => void,
|
|
45
|
-
) {
|
|
46
|
-
useEffect((): VoidFunction | void => {
|
|
47
|
-
if (!element) {
|
|
48
|
-
return undefined;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const setFocus = () => {
|
|
52
|
-
if (!isOpen) {
|
|
53
|
-
setIsFocus(true);
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const setBlur = () => {
|
|
58
|
-
if (!isOpen) {
|
|
59
|
-
setIsFocus(false);
|
|
60
|
-
|
|
61
|
-
if (onBlur) {
|
|
62
|
-
onBlur();
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
setWasBlurred(true);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
element.addEventListener('focus', setFocus);
|
|
70
|
-
element.addEventListener('blur', setBlur);
|
|
71
|
-
|
|
72
|
-
return () => {
|
|
73
|
-
element.removeEventListener('focus', setFocus);
|
|
74
|
-
element.removeEventListener('blur', setBlur);
|
|
75
|
-
};
|
|
76
|
-
}, [element, isOpen, onBlur, setIsFocus, setWasBlurred, wasBlurred]);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
28
|
export const WithSelectContext = (
|
|
80
29
|
SelectElement: React.FC<any>,
|
|
81
30
|
{
|
|
@@ -94,7 +43,6 @@ export const WithSelectContext = (
|
|
|
94
43
|
isRequired,
|
|
95
44
|
isValid,
|
|
96
45
|
label,
|
|
97
|
-
onBlur,
|
|
98
46
|
onClear,
|
|
99
47
|
onDropdownClose,
|
|
100
48
|
onInfiniteScroll,
|
|
@@ -110,10 +58,7 @@ export const WithSelectContext = (
|
|
|
110
58
|
const selectId = useMemo(() => id || `select-${uid()}`, [id]);
|
|
111
59
|
const anchorRef = useRef<HTMLElement>(null);
|
|
112
60
|
const selectRef = useRef<HTMLDivElement>(null);
|
|
113
|
-
const
|
|
114
|
-
const [wasBlurred, setWasBlurred] = useState(false);
|
|
115
|
-
|
|
116
|
-
useHandleElementFocus(anchorRef.current, setIsFocus, Boolean(isOpen), wasBlurred, setWasBlurred, onBlur);
|
|
61
|
+
const isFocus = useListenFocus(anchorRef);
|
|
117
62
|
|
|
118
63
|
const handleKeyboardNav = useCallback(
|
|
119
64
|
(evt: React.KeyboardEvent<HTMLElement>) => {
|
|
@@ -129,8 +74,6 @@ export const WithSelectContext = (
|
|
|
129
74
|
if (onDropdownClose) {
|
|
130
75
|
onDropdownClose();
|
|
131
76
|
}
|
|
132
|
-
|
|
133
|
-
setWasBlurred(false);
|
|
134
77
|
anchorRef?.current?.blur();
|
|
135
78
|
};
|
|
136
79
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { AspectRatio, ImageBlock, Slideshow, SlideshowItem, SlideshowProps } from '@lumx/react';
|
|
2
|
-
import { boolean, number } from '@storybook/addon-knobs';
|
|
3
1
|
import React from 'react';
|
|
2
|
+
import range from 'lodash/range';
|
|
3
|
+
import { AspectRatio, Button, FlexBox, ImageBlock, Slideshow, SlideshowItem } from '@lumx/react';
|
|
4
|
+
import { boolean, number } from '@storybook/addon-knobs';
|
|
4
5
|
import { thumbnailsKnob } from '@lumx/react/stories/knobs/thumbnailsKnob';
|
|
5
6
|
|
|
6
7
|
export default { title: 'LumX components/slideshow/Slideshow' };
|
|
@@ -12,16 +13,15 @@ export const Simple = ({ theme }: any) => {
|
|
|
12
13
|
const autoPlay = boolean('Autoplay', false);
|
|
13
14
|
const interval = number('Autoplay interval (in milliseconds)', 1000);
|
|
14
15
|
|
|
15
|
-
const slideshowControlsProps: SlideshowProps['slideshowControlsProps'] = {
|
|
16
|
-
nextButtonProps: { label: 'Next' },
|
|
17
|
-
previousButtonProps: { label: 'Previous' },
|
|
18
|
-
};
|
|
19
16
|
return (
|
|
20
17
|
<Slideshow
|
|
21
18
|
activeIndex={activeIndex}
|
|
22
19
|
autoPlay={autoPlay}
|
|
23
20
|
interval={interval}
|
|
24
|
-
slideshowControlsProps={
|
|
21
|
+
slideshowControlsProps={{
|
|
22
|
+
nextButtonProps: { label: 'Next' },
|
|
23
|
+
previousButtonProps: { label: 'Previous' },
|
|
24
|
+
}}
|
|
25
25
|
theme={theme}
|
|
26
26
|
groupBy={groupBy}
|
|
27
27
|
style={{ width: '50%' }}
|
|
@@ -39,3 +39,43 @@ export const Simple = ({ theme }: any) => {
|
|
|
39
39
|
</Slideshow>
|
|
40
40
|
);
|
|
41
41
|
};
|
|
42
|
+
|
|
43
|
+
export const ResponsiveSlideShowSwipe = () => {
|
|
44
|
+
const slides = range(3);
|
|
45
|
+
return (
|
|
46
|
+
<>
|
|
47
|
+
In responsive mode
|
|
48
|
+
<ul>
|
|
49
|
+
<li>The slideshow is swipe-able (horizontal left/right)</li>
|
|
50
|
+
<li>The vertical scroll should still be possible</li>
|
|
51
|
+
<li>Clicking on elements inside a slide should work correctly</li>
|
|
52
|
+
</ul>
|
|
53
|
+
<FlexBox vAlign="center">
|
|
54
|
+
<Slideshow
|
|
55
|
+
slideshowControlsProps={{
|
|
56
|
+
nextButtonProps: { label: 'Next' },
|
|
57
|
+
previousButtonProps: { label: 'Previous' },
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
{slides.map((slide) => (
|
|
61
|
+
<SlideshowItem key={`${slide}`}>
|
|
62
|
+
<FlexBox
|
|
63
|
+
style={{ border: '1px solid grey', maxWidth: 300, height: 300 }}
|
|
64
|
+
hAlign="center"
|
|
65
|
+
vAlign="center"
|
|
66
|
+
>
|
|
67
|
+
<Button onClick={() => alert(`Clicked button ${slide}`)}>Button {slide}</Button>
|
|
68
|
+
</FlexBox>
|
|
69
|
+
</SlideshowItem>
|
|
70
|
+
))}
|
|
71
|
+
</Slideshow>
|
|
72
|
+
</FlexBox>
|
|
73
|
+
{
|
|
74
|
+
/* Line padding to test the vertical scroll.*/
|
|
75
|
+
range(100).map((i) => (
|
|
76
|
+
<br key={i} />
|
|
77
|
+
))
|
|
78
|
+
}
|
|
79
|
+
</>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { ReactElement } from 'react';
|
|
2
|
+
import pick from 'lodash/pick';
|
|
2
3
|
|
|
3
4
|
import { mount, shallow } from 'enzyme';
|
|
4
5
|
import 'jest-enzyme';
|
|
@@ -28,7 +29,9 @@ const setup = ({ ...propsOverride }: Partial<SlideshowProps> = {}, shallowRender
|
|
|
28
29
|
describe(`<${Slideshow.displayName}>`, () => {
|
|
29
30
|
// 1. Test render via snapshot.
|
|
30
31
|
describe('Snapshots and structure', () => {
|
|
31
|
-
itShouldRenderStories(stories,
|
|
32
|
+
itShouldRenderStories(pick(stories, ['default', 'Simple']), {
|
|
33
|
+
or: [Slideshow, { path: [Slideshow, SlideshowControls] }],
|
|
34
|
+
});
|
|
32
35
|
});
|
|
33
36
|
|
|
34
37
|
// Common tests suite.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { CSSProperties, forwardRef, useCallback, useEffect,
|
|
1
|
+
import React, { CSSProperties, forwardRef, useCallback, useEffect, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
|
|
@@ -83,7 +83,8 @@ export const Slideshow: Comp<SlideshowProps, HTMLDivElement> = forwardRef((props
|
|
|
83
83
|
...forwardedProps
|
|
84
84
|
} = props;
|
|
85
85
|
const [currentIndex, setCurrentIndex] = useState(activeIndex as number);
|
|
86
|
-
|
|
86
|
+
// Use state instead of a ref to make the slideshow controls update directly when the element is set.
|
|
87
|
+
const [element, setElement] = useState<HTMLDivElement>();
|
|
87
88
|
|
|
88
89
|
// Number of slideshow items.
|
|
89
90
|
const itemsCount = React.Children.count(children);
|
|
@@ -93,22 +94,40 @@ export const Slideshow: Comp<SlideshowProps, HTMLDivElement> = forwardRef((props
|
|
|
93
94
|
const wrapperStyle: CSSProperties = { transform: `translateX(-${FULL_WIDTH_PERCENT * currentIndex}%)` };
|
|
94
95
|
|
|
95
96
|
// Change current index to display next slide.
|
|
96
|
-
const goToNextSlide = useCallback(
|
|
97
|
-
|
|
98
|
-
setCurrentIndex(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
const goToNextSlide = useCallback(
|
|
98
|
+
(loopback = true) => {
|
|
99
|
+
setCurrentIndex((index) => {
|
|
100
|
+
if (loopback && index === slidesCount - 1) {
|
|
101
|
+
// Loopback to the start.
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
if (index < slidesCount - 1) {
|
|
105
|
+
// Next slide.
|
|
106
|
+
return index + 1;
|
|
107
|
+
}
|
|
108
|
+
return index;
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
[slidesCount, setCurrentIndex],
|
|
112
|
+
);
|
|
103
113
|
|
|
104
114
|
// Change current index to display previous slide.
|
|
105
|
-
const goToPreviousSlide = useCallback(
|
|
106
|
-
|
|
107
|
-
setCurrentIndex(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
115
|
+
const goToPreviousSlide = useCallback(
|
|
116
|
+
(loopback = true) => {
|
|
117
|
+
setCurrentIndex((index) => {
|
|
118
|
+
if (loopback && index === 0) {
|
|
119
|
+
// Loopback to the end.
|
|
120
|
+
return slidesCount - 1;
|
|
121
|
+
}
|
|
122
|
+
if (index > 0) {
|
|
123
|
+
// Previous slide.
|
|
124
|
+
return index - 1;
|
|
125
|
+
}
|
|
126
|
+
return index;
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
[slidesCount, setCurrentIndex],
|
|
130
|
+
);
|
|
112
131
|
|
|
113
132
|
// Auto play
|
|
114
133
|
const [isAutoPlaying, setIsAutoPlaying] = useState(Boolean(autoPlay));
|
|
@@ -135,16 +154,22 @@ export const Slideshow: Comp<SlideshowProps, HTMLDivElement> = forwardRef((props
|
|
|
135
154
|
);
|
|
136
155
|
|
|
137
156
|
// Handle click or keyboard event to go to next slide.
|
|
138
|
-
const handleControlNextSlide = useCallback(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
157
|
+
const handleControlNextSlide = useCallback(
|
|
158
|
+
(loopback = true) => {
|
|
159
|
+
setIsAutoPlaying(false);
|
|
160
|
+
goToNextSlide(loopback);
|
|
161
|
+
},
|
|
162
|
+
[goToNextSlide],
|
|
163
|
+
);
|
|
142
164
|
|
|
143
165
|
// Handle click or keyboard event to go to previous slide.
|
|
144
|
-
const handleControlPreviousSlide = useCallback(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
166
|
+
const handleControlPreviousSlide = useCallback(
|
|
167
|
+
(loopback = true) => {
|
|
168
|
+
setIsAutoPlaying(false);
|
|
169
|
+
goToPreviousSlide(loopback);
|
|
170
|
+
},
|
|
171
|
+
[goToPreviousSlide],
|
|
172
|
+
);
|
|
148
173
|
|
|
149
174
|
// If the activeIndex props changes, update the current slide
|
|
150
175
|
useEffect(() => {
|
|
@@ -160,7 +185,7 @@ export const Slideshow: Comp<SlideshowProps, HTMLDivElement> = forwardRef((props
|
|
|
160
185
|
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
|
|
161
186
|
return (
|
|
162
187
|
<div
|
|
163
|
-
ref={mergeRefs(ref,
|
|
188
|
+
ref={mergeRefs(ref, setElement)}
|
|
164
189
|
{...forwardedProps}
|
|
165
190
|
className={classNames(className, handleBasicClasses({ prefix: CLASSNAME, theme }), {
|
|
166
191
|
[`${CLASSNAME}--fill-height`]: fillHeight,
|
|
@@ -183,7 +208,7 @@ export const Slideshow: Comp<SlideshowProps, HTMLDivElement> = forwardRef((props
|
|
|
183
208
|
onNextClick={handleControlNextSlide}
|
|
184
209
|
onPreviousClick={handleControlPreviousSlide}
|
|
185
210
|
slidesCount={slidesCount}
|
|
186
|
-
parentRef={
|
|
211
|
+
parentRef={element}
|
|
187
212
|
theme={theme}
|
|
188
213
|
/>
|
|
189
214
|
</div>
|
|
@@ -6,7 +6,6 @@ import { thumbnailsKnob } from '@lumx/react/stories/knobs/thumbnailsKnob';
|
|
|
6
6
|
export default { title: 'LumX components/slideshow/Slideshow controls' };
|
|
7
7
|
|
|
8
8
|
export const Simple = () => {
|
|
9
|
-
const parentRef = React.useRef(null);
|
|
10
9
|
const slidesCount = 9;
|
|
11
10
|
const [activeIndex, setActiveIndex] = React.useState(0);
|
|
12
11
|
const maxIndex = slidesCount - 1;
|
|
@@ -19,7 +18,6 @@ export const Simple = () => {
|
|
|
19
18
|
<SlideshowControls
|
|
20
19
|
activeIndex={activeIndex}
|
|
21
20
|
slidesCount={slidesCount}
|
|
22
|
-
parentRef={parentRef}
|
|
23
21
|
onNextClick={onNextClick}
|
|
24
22
|
onPreviousClick={onPreviousClick}
|
|
25
23
|
onPaginationClick={onPaginationClick}
|
|
@@ -30,7 +28,6 @@ export const Simple = () => {
|
|
|
30
28
|
};
|
|
31
29
|
|
|
32
30
|
export const ControllingSlideshow = ({ theme }: any) => {
|
|
33
|
-
const parentRef = React.useRef(null);
|
|
34
31
|
const items = thumbnailsKnob(6);
|
|
35
32
|
const [activeIndex, setActiveIndex] = React.useState(0);
|
|
36
33
|
const maxIndex = items.length - 1;
|
|
@@ -67,7 +64,6 @@ export const ControllingSlideshow = ({ theme }: any) => {
|
|
|
67
64
|
<SlideshowControls
|
|
68
65
|
activeIndex={activeIndex}
|
|
69
66
|
slidesCount={items.length}
|
|
70
|
-
parentRef={parentRef}
|
|
71
67
|
onNextClick={onNextClick}
|
|
72
68
|
onPreviousClick={onPreviousClick}
|
|
73
69
|
onPaginationClick={onPaginationClick}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { forwardRef, RefObject, useMemo } from 'react';
|
|
1
|
+
import React, { forwardRef, RefObject, useCallback, useMemo } from 'react';
|
|
2
2
|
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
import range from 'lodash/range';
|
|
@@ -6,8 +6,10 @@ import range from 'lodash/range';
|
|
|
6
6
|
import { mdiChevronLeft, mdiChevronRight } from '@lumx/icons';
|
|
7
7
|
import { Emphasis, IconButton, IconButtonProps, Theme } from '@lumx/react';
|
|
8
8
|
import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';
|
|
9
|
-
import {
|
|
9
|
+
import { WINDOW } from '@lumx/react/constants';
|
|
10
10
|
|
|
11
|
+
import { useSwipeNavigate } from './useSwipeNavigate';
|
|
12
|
+
import { useKeyNavigate } from './useKeyNavigate';
|
|
11
13
|
import { PAGINATION_ITEM_SIZE, PAGINATION_ITEMS_MAX } from './constants';
|
|
12
14
|
import { usePaginationVisibleRange } from './usePaginationVisibleRange';
|
|
13
15
|
|
|
@@ -20,8 +22,8 @@ export interface SlideshowControlsProps extends GenericProps {
|
|
|
20
22
|
/** Props to pass to the next button (minus those already set by the SlideshowControls props). */
|
|
21
23
|
nextButtonProps: Pick<IconButtonProps, 'label'> &
|
|
22
24
|
Omit<IconButtonProps, 'label' | 'onClick' | 'icon' | 'emphasis' | 'color'>;
|
|
23
|
-
/** Reference to the parent element. */
|
|
24
|
-
parentRef
|
|
25
|
+
/** Reference to the parent element on which we want to listen touch swipe. */
|
|
26
|
+
parentRef?: RefObject<HTMLDivElement> | HTMLDivElement;
|
|
25
27
|
/** Props to pass to the previous button (minus those already set by the SlideshowControls props). */
|
|
26
28
|
previousButtonProps: Pick<IconButtonProps, 'label'> &
|
|
27
29
|
Omit<IconButtonProps, 'label' | 'onClick' | 'icon' | 'emphasis' | 'color'>;
|
|
@@ -30,11 +32,11 @@ export interface SlideshowControlsProps extends GenericProps {
|
|
|
30
32
|
/** Theme adapting the component to light or dark background. */
|
|
31
33
|
theme?: Theme;
|
|
32
34
|
/** On next button click callback. */
|
|
33
|
-
onNextClick?(): void;
|
|
35
|
+
onNextClick?(loopback?: boolean): void;
|
|
34
36
|
/** On pagination change callback. */
|
|
35
37
|
onPaginationClick?(index: number): void;
|
|
36
38
|
/** On previous button click callback. */
|
|
37
|
-
onPreviousClick?(): void;
|
|
39
|
+
onPreviousClick?(loopback?: boolean): void;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
/**
|
|
@@ -77,8 +79,21 @@ export const SlideshowControls: Comp<SlideshowControlsProps, HTMLDivElement> = f
|
|
|
77
79
|
...forwardedProps
|
|
78
80
|
} = props;
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
let parent;
|
|
83
|
+
if (WINDOW) {
|
|
84
|
+
// Checking window object to avoid errors in SSR.
|
|
85
|
+
parent = parentRef instanceof HTMLElement ? parentRef : parentRef?.current;
|
|
86
|
+
}
|
|
87
|
+
// Listen to keyboard navigate left & right.
|
|
88
|
+
useKeyNavigate(parent, onNextClick, onPreviousClick);
|
|
89
|
+
// Listen to touch swipe navigate left & right.
|
|
90
|
+
useSwipeNavigate(
|
|
91
|
+
parent,
|
|
92
|
+
// Go next without loopback.
|
|
93
|
+
useCallback(() => onNextClick?.(false), [onNextClick]),
|
|
94
|
+
// Go previous without loopback.
|
|
95
|
+
useCallback(() => onPreviousClick?.(false), [onPreviousClick]),
|
|
96
|
+
);
|
|
82
97
|
|
|
83
98
|
// Pagination "bullet" range.
|
|
84
99
|
const visibleRange = usePaginationVisibleRange(activeIndex as number, slidesCount);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Listen keyboard to navigate left and right.
|
|
5
|
+
*/
|
|
6
|
+
export function useKeyNavigate(element?: HTMLElement | null, onNext?: () => void, onPrevious?: () => void): void {
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!element) return undefined;
|
|
9
|
+
const onKeyNavigate = (evt: KeyboardEvent) => {
|
|
10
|
+
let callback;
|
|
11
|
+
if (evt?.key === 'ArrowRight') {
|
|
12
|
+
callback = onPrevious;
|
|
13
|
+
} else if (evt?.key === 'ArrowLeft') {
|
|
14
|
+
callback = onNext;
|
|
15
|
+
}
|
|
16
|
+
if (!callback) return;
|
|
17
|
+
|
|
18
|
+
callback();
|
|
19
|
+
evt.preventDefault();
|
|
20
|
+
evt.stopPropagation();
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
element.addEventListener('keydown', onKeyNavigate);
|
|
24
|
+
return () => {
|
|
25
|
+
element.removeEventListener('keydown', onKeyNavigate);
|
|
26
|
+
};
|
|
27
|
+
}, [onPrevious, onNext, element]);
|
|
28
|
+
}
|