@lumx/react 2.2.8 → 2.2.9

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.
@@ -1,7 +1,8 @@
1
1
  import React from 'react';
2
2
 
3
- import { AspectRatio, ImageBlock, Slideshow, SlideshowControls, SlideshowItem } from '@lumx/react';
3
+ import { AspectRatio, ImageBlock, Slides, SlideshowControls, SlideshowItem } from '@lumx/react';
4
4
  import { thumbnailsKnob } from '@lumx/react/stories/knobs/thumbnailsKnob';
5
+ import { useFocusWithin } from '@lumx/react/hooks/useFocusWithin';
5
6
 
6
7
  export default { title: 'LumX components/slideshow/Slideshow controls' };
7
8
 
@@ -30,53 +31,88 @@ export const Simple = () => {
30
31
  };
31
32
 
32
33
  export const ControllingSlideshow = ({ theme }: any) => {
33
- const items = thumbnailsKnob(6);
34
- const slideshowStyle = {
35
- width: '50%',
36
- };
34
+ const images = thumbnailsKnob(6);
37
35
 
38
36
  const {
39
- activeIndex,
40
- setActiveIndex,
37
+ activeIndex: currentIndex,
38
+ slideshowId,
39
+ setSlideshow,
40
+ isAutoPlaying,
41
+ slideshowSlidesId,
42
+ setIsAutoPlaying,
43
+ startIndexVisible,
44
+ endIndexVisible,
45
+ slidesCount,
41
46
  onNextClick,
42
- onPreviousClick,
43
47
  onPaginationClick,
48
+ onPreviousClick,
49
+ slideshow,
50
+ isForcePaused,
51
+ setIsForcePaused,
52
+ stopAutoPlay,
53
+ startAutoPlay,
44
54
  } = SlideshowControls.useSlideshowControls({
55
+ activeIndex: 0,
56
+ defaultActiveIndex: 0,
57
+ autoPlay: true,
45
58
  itemsCount: 6,
59
+ groupBy: 1,
60
+ });
61
+
62
+ useFocusWithin({
63
+ element: slideshow,
64
+ onFocusIn: stopAutoPlay,
65
+ onFocusOut: startAutoPlay,
46
66
  });
47
67
 
68
+ /* eslint-disable jsx-a11y/no-noninteractive-tabindex */
48
69
  return (
49
- <div style={{ height: 400, width: 500, position: 'relative' }}>
50
- <Slideshow
51
- activeIndex={activeIndex}
52
- theme={theme}
53
- autoPlay
54
- groupBy={1}
55
- style={slideshowStyle}
56
- onChange={setActiveIndex}
57
- >
58
- {items.map(({ image, alt }) => (
59
- <SlideshowItem key={image}>
60
- <ImageBlock
61
- image={image}
62
- alt={alt}
63
- thumbnailProps={{ aspectRatio: AspectRatio.vertical }}
64
- theme={theme}
65
- />
66
- </SlideshowItem>
67
- ))}
68
- </Slideshow>
69
- <div style={{ position: 'absolute', bottom: 0, right: -24 }}>
70
- <SlideshowControls
71
- activeIndex={activeIndex}
72
- slidesCount={items.length}
73
- onNextClick={onNextClick}
74
- onPreviousClick={onPreviousClick}
75
- onPaginationClick={onPaginationClick}
76
- nextButtonProps={{ label: 'Next' }}
77
- previousButtonProps={{ label: 'Previous' }}
78
- />
79
- </div>
80
- </div>
70
+ <Slides
71
+ activeIndex={currentIndex}
72
+ id={slideshowId}
73
+ ref={setSlideshow}
74
+ theme={theme}
75
+ isAutoPlaying={isAutoPlaying}
76
+ autoPlay
77
+ slidesId={slideshowSlidesId}
78
+ setIsAutoPlaying={setIsAutoPlaying}
79
+ startIndexVisible={startIndexVisible}
80
+ endIndexVisible={endIndexVisible}
81
+ groupBy={1}
82
+ style={{ width: '50%' }}
83
+ afterSlides={
84
+ <div className={`${Slides.className}__controls`}>
85
+ <SlideshowControls
86
+ activeIndex={currentIndex}
87
+ onPaginationClick={onPaginationClick}
88
+ onNextClick={onNextClick}
89
+ onPreviousClick={onPreviousClick}
90
+ slidesCount={slidesCount}
91
+ parentRef={slideshow}
92
+ theme={theme}
93
+ isAutoPlaying={isAutoPlaying}
94
+ nextButtonProps={{ label: 'Next', 'aria-controls': slideshowSlidesId }}
95
+ previousButtonProps={{ label: 'Previous', 'aria-controls': slideshowSlidesId }}
96
+ playButtonProps={{
97
+ label: 'Play/Pause',
98
+ 'aria-controls': slideshowSlidesId,
99
+ onClick: () => setIsForcePaused(!isForcePaused),
100
+ }}
101
+ paginationItemLabel={(index) => `Slide ${index}`}
102
+ />
103
+ </div>
104
+ }
105
+ >
106
+ {images.map(({ image, alt }, index) => (
107
+ <SlideshowItem key={`${image}-${index}`}>
108
+ <ImageBlock
109
+ thumbnailProps={{ aspectRatio: AspectRatio.horizontal, loading: 'eager' }}
110
+ image={image}
111
+ alt={alt}
112
+ theme={theme}
113
+ />
114
+ </SlideshowItem>
115
+ ))}
116
+ </Slides>
81
117
  );
82
118
  };
@@ -3,7 +3,7 @@ import React, { forwardRef, RefObject, useCallback, useMemo } from 'react';
3
3
  import classNames from 'classnames';
4
4
  import range from 'lodash/range';
5
5
 
6
- import { mdiChevronLeft, mdiChevronRight } from '@lumx/icons';
6
+ import { mdiChevronLeft, mdiChevronRight, mdiPlayCircleOutline, mdiPauseCircleOutline } 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
9
  import { WINDOW } from '@lumx/react/constants';
@@ -38,6 +38,13 @@ export interface SlideshowControlsProps extends GenericProps {
38
38
  onPaginationClick?(index: number): void;
39
39
  /** On previous button click callback. */
40
40
  onPreviousClick?(loopback?: boolean): void;
41
+ /** whether the slideshow is currently playing */
42
+ isAutoPlaying?: boolean;
43
+ /** function to be executed in order to retrieve the label for the pagination item */
44
+ paginationItemLabel?: (index: number) => string;
45
+ /** Props to pass to the lay button (minus those already set by the SlideshowControls props). */
46
+ playButtonProps?: Pick<IconButtonProps, 'label'> &
47
+ Omit<IconButtonProps, 'label' | 'onClick' | 'icon' | 'emphasis' | 'color'>;
41
48
  }
42
49
 
43
50
  /**
@@ -77,6 +84,9 @@ const InternalSlideshowControls: Comp<SlideshowControlsProps, HTMLDivElement> =
77
84
  previousButtonProps,
78
85
  slidesCount,
79
86
  theme,
87
+ isAutoPlaying = false,
88
+ playButtonProps,
89
+ paginationItemLabel,
80
90
  ...forwardedProps
81
91
  } = props;
82
92
 
@@ -130,7 +140,6 @@ const InternalSlideshowControls: Comp<SlideshowControlsProps, HTMLDivElement> =
130
140
  const isActive = activeIndex === index;
131
141
  const isOutRange = index < visibleRange.min || index > visibleRange.max;
132
142
  return (
133
- // eslint-disable-next-line jsx-a11y/control-has-associated-label
134
143
  <button
135
144
  className={classNames(
136
145
  handleBasicClasses({
@@ -143,13 +152,36 @@ const InternalSlideshowControls: Comp<SlideshowControlsProps, HTMLDivElement> =
143
152
  key={index}
144
153
  type="button"
145
154
  onClick={() => onPaginationClick?.(index)}
155
+ {...{
156
+ 'aria-label': paginationItemLabel
157
+ ? paginationItemLabel(index)
158
+ : `${index + 1} / ${slidesCount}`,
159
+ }}
146
160
  />
147
161
  );
148
162
  }),
149
- [slidesCount, visibleRange.min, visibleRange.max, activeIndex, onPaginationClick],
163
+ [
164
+ slidesCount,
165
+ visibleRange.min,
166
+ visibleRange.max,
167
+ activeIndex,
168
+ paginationItemLabel,
169
+ onPaginationClick,
170
+ ],
150
171
  )}
151
172
  </div>
152
173
  </div>
174
+
175
+ {playButtonProps ? (
176
+ <IconButton
177
+ {...playButtonProps}
178
+ icon={isAutoPlaying ? mdiPauseCircleOutline : mdiPlayCircleOutline}
179
+ className={`${CLASSNAME}__play`}
180
+ color={theme === Theme.dark ? 'light' : 'dark'}
181
+ emphasis={Emphasis.low}
182
+ />
183
+ ) : null}
184
+
153
185
  <IconButton
154
186
  {...nextButtonProps}
155
187
  icon={mdiChevronRight}
@@ -1,13 +1,22 @@
1
- import React, { forwardRef } from 'react';
1
+ import React, { forwardRef, useState } from 'react';
2
2
 
3
3
  import classNames from 'classnames';
4
4
 
5
5
  import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';
6
6
 
7
+ import { useDelayedVisibility } from '@lumx/react/hooks/useDelayedVisibility';
8
+
9
+ import { SLIDESHOW_TRANSITION_DURATION } from '@lumx/core/js/constants';
10
+
7
11
  /**
8
12
  * Defines the props of the component.
9
13
  */
10
- export type SlideshowItemProps = GenericProps;
14
+ export interface SlideshowItemProps extends GenericProps {
15
+ /** whether the slideshow item is currently visible */
16
+ isCurrentlyVisible?: boolean;
17
+ /** interval in which slides are automatically shown */
18
+ interval?: number;
19
+ }
11
20
 
12
21
  /**
13
22
  * Component display name.
@@ -27,7 +36,12 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
27
36
  * @return React element.
28
37
  */
29
38
  export const SlideshowItem: Comp<SlideshowItemProps, HTMLDivElement> = forwardRef((props, ref) => {
30
- const { className, children, ...forwardedProps } = props;
39
+ const { className, children, isCurrentlyVisible = false, ...forwardedProps } = props;
40
+ const [isVisible, setIsVisible] = useState<boolean>(isCurrentlyVisible);
41
+
42
+ useDelayedVisibility(isCurrentlyVisible, SLIDESHOW_TRANSITION_DURATION, (isNowVisible) => {
43
+ setIsVisible(isNowVisible);
44
+ });
31
45
 
32
46
  return (
33
47
  <div
@@ -41,6 +55,8 @@ export const SlideshowItem: Comp<SlideshowItemProps, HTMLDivElement> = forwardRe
41
55
  aria-roledescription="slide"
42
56
  role="group"
43
57
  {...forwardedProps}
58
+ style={!isVisible ? { visibility: 'hidden', ...(forwardedProps.style || {}) } : forwardedProps.style || {}}
59
+ aria-hidden={!isVisible}
44
60
  >
45
61
  {children}
46
62
  </div>
@@ -2,262 +2,154 @@
2
2
 
3
3
  exports[`<Slideshow> Snapshots and structure should render story 'Simple' 1`] = `
4
4
  Array [
5
- <section
6
- aria-live="polite"
7
- aria-roledescription="carousel"
8
- className="lumx-slideshow lumx-slideshow--theme-light lumx-slideshow--group-by-1"
5
+ <Slideshow
6
+ activeIndex={0}
7
+ afterSlides={
8
+ <div
9
+ className="lumx-slideshow__controls"
10
+ >
11
+ <SlideshowControls
12
+ activeIndex={0}
13
+ isAutoPlaying={false}
14
+ nextButtonProps={
15
+ Object {
16
+ "aria-controls": "slideshow-slides2",
17
+ "label": "Next",
18
+ }
19
+ }
20
+ onNextClick={[Function]}
21
+ onPaginationClick={[Function]}
22
+ onPreviousClick={[Function]}
23
+ previousButtonProps={
24
+ Object {
25
+ "aria-controls": "slideshow-slides2",
26
+ "label": "Previous",
27
+ }
28
+ }
29
+ slidesCount={6}
30
+ theme="light"
31
+ />
32
+ </div>
33
+ }
34
+ autoPlay={false}
35
+ endIndexVisible={1}
36
+ groupBy={1}
9
37
  id="slideshow1"
38
+ interval={1000}
39
+ isAutoPlaying={false}
40
+ setIsAutoPlaying={[Function]}
41
+ slidesId="slideshow-slides2"
42
+ startIndexVisible={0}
10
43
  style={
11
44
  Object {
12
45
  "width": "50%",
13
46
  }
14
47
  }
48
+ theme="light"
15
49
  >
16
- <div
17
- className="lumx-slideshow__slides"
18
- id="slideshow-slides2"
19
- onMouseEnter={[Function]}
20
- onMouseLeave={[Function]}
50
+ <SlideshowItem
51
+ key="/demo-assets/landscape1.jpg-0"
21
52
  >
22
- <div
23
- className="lumx-slideshow__wrapper"
24
- style={
53
+ <ImageBlock
54
+ align="left"
55
+ alt="Image 1"
56
+ captionPosition="below"
57
+ image="/demo-assets/landscape1.jpg"
58
+ theme="light"
59
+ thumbnailProps={
25
60
  Object {
26
- "transform": "translateX(-0%)",
61
+ "aspectRatio": "horizontal",
62
+ "loading": "eager",
27
63
  }
28
64
  }
29
- >
30
- <SlideshowItem
31
- aria-hidden={false}
32
- key=".$/demo-assets/landscape1.jpg-0"
33
- style={Object {}}
34
- >
35
- <ImageBlock
36
- align="left"
37
- alt="Image 1"
38
- captionPosition="below"
39
- image="/demo-assets/landscape1.jpg"
40
- theme="light"
41
- thumbnailProps={
42
- Object {
43
- "aspectRatio": "horizontal",
44
- "loading": "eager",
45
- }
46
- }
47
- />
48
- </SlideshowItem>
49
- <SlideshowItem
50
- aria-hidden={false}
51
- key=".$/demo-assets/landscape1-s200.jpg-1"
52
- style={Object {}}
53
- >
54
- <ImageBlock
55
- align="left"
56
- alt="Image 2"
57
- captionPosition="below"
58
- image="/demo-assets/landscape1-s200.jpg"
59
- theme="light"
60
- thumbnailProps={
61
- Object {
62
- "aspectRatio": "horizontal",
63
- "loading": "eager",
64
- }
65
- }
66
- />
67
- </SlideshowItem>
68
- <SlideshowItem
69
- aria-hidden={true}
70
- key=".$/demo-assets/landscape2.jpg-2"
71
- style={
72
- Object {
73
- "visibility": "hidden",
74
- }
75
- }
76
- >
77
- <ImageBlock
78
- align="left"
79
- alt="Image 3"
80
- captionPosition="below"
81
- image="/demo-assets/landscape2.jpg"
82
- theme="light"
83
- thumbnailProps={
84
- Object {
85
- "aspectRatio": "horizontal",
86
- "loading": "eager",
87
- }
88
- }
89
- />
90
- </SlideshowItem>
91
- <SlideshowItem
92
- aria-hidden={true}
93
- key=".$/demo-assets/landscape3.jpg-3"
94
- style={
95
- Object {
96
- "visibility": "hidden",
97
- }
98
- }
99
- >
100
- <ImageBlock
101
- align="left"
102
- alt="Image 4"
103
- captionPosition="below"
104
- image="/demo-assets/landscape3.jpg"
105
- theme="light"
106
- thumbnailProps={
107
- Object {
108
- "aspectRatio": "horizontal",
109
- "loading": "eager",
110
- }
111
- }
112
- />
113
- </SlideshowItem>
114
- <SlideshowItem
115
- aria-hidden={true}
116
- key=".$/demo-assets/portrait1.jpg-4"
117
- style={
118
- Object {
119
- "visibility": "hidden",
120
- }
121
- }
122
- >
123
- <ImageBlock
124
- align="left"
125
- alt="Image 5"
126
- captionPosition="below"
127
- image="/demo-assets/portrait1.jpg"
128
- theme="light"
129
- thumbnailProps={
130
- Object {
131
- "aspectRatio": "horizontal",
132
- "loading": "eager",
133
- }
134
- }
135
- />
136
- </SlideshowItem>
137
- <SlideshowItem
138
- aria-hidden={true}
139
- key=".$/demo-assets/portrait1-s200.jpg-5"
140
- style={
141
- Object {
142
- "visibility": "hidden",
143
- }
65
+ />
66
+ </SlideshowItem>
67
+ <SlideshowItem
68
+ key="/demo-assets/landscape1-s200.jpg-1"
69
+ >
70
+ <ImageBlock
71
+ align="left"
72
+ alt="Image 2"
73
+ captionPosition="below"
74
+ image="/demo-assets/landscape1-s200.jpg"
75
+ theme="light"
76
+ thumbnailProps={
77
+ Object {
78
+ "aspectRatio": "horizontal",
79
+ "loading": "eager",
144
80
  }
145
- >
146
- <ImageBlock
147
- align="left"
148
- alt="Image 6"
149
- captionPosition="below"
150
- image="/demo-assets/portrait1-s200.jpg"
151
- theme="light"
152
- thumbnailProps={
153
- Object {
154
- "aspectRatio": "horizontal",
155
- "loading": "eager",
156
- }
157
- }
158
- />
159
- </SlideshowItem>
160
- </div>
161
- </div>
162
- <div
163
- className="lumx-slideshow__controls"
81
+ }
82
+ />
83
+ </SlideshowItem>
84
+ <SlideshowItem
85
+ key="/demo-assets/landscape2.jpg-2"
164
86
  >
165
- <SlideshowControls
166
- activeIndex={0}
167
- nextButtonProps={
87
+ <ImageBlock
88
+ align="left"
89
+ alt="Image 3"
90
+ captionPosition="below"
91
+ image="/demo-assets/landscape2.jpg"
92
+ theme="light"
93
+ thumbnailProps={
168
94
  Object {
169
- "aria-controls": "slideshow-slides2",
170
- "label": "Next",
95
+ "aspectRatio": "horizontal",
96
+ "loading": "eager",
171
97
  }
172
98
  }
173
- onNextClick={[Function]}
174
- onPaginationClick={[Function]}
175
- onPreviousClick={[Function]}
176
- previousButtonProps={
99
+ />
100
+ </SlideshowItem>
101
+ <SlideshowItem
102
+ key="/demo-assets/landscape3.jpg-3"
103
+ >
104
+ <ImageBlock
105
+ align="left"
106
+ alt="Image 4"
107
+ captionPosition="below"
108
+ image="/demo-assets/landscape3.jpg"
109
+ theme="light"
110
+ thumbnailProps={
177
111
  Object {
178
- "aria-controls": "slideshow-slides2",
179
- "label": "Previous",
112
+ "aspectRatio": "horizontal",
113
+ "loading": "eager",
180
114
  }
181
115
  }
182
- slidesCount={6}
116
+ />
117
+ </SlideshowItem>
118
+ <SlideshowItem
119
+ key="/demo-assets/portrait1.jpg-4"
120
+ >
121
+ <ImageBlock
122
+ align="left"
123
+ alt="Image 5"
124
+ captionPosition="below"
125
+ image="/demo-assets/portrait1.jpg"
183
126
  theme="light"
127
+ thumbnailProps={
128
+ Object {
129
+ "aspectRatio": "horizontal",
130
+ "loading": "eager",
131
+ }
132
+ }
184
133
  />
185
- </div>
186
- </section>,
187
- <div
188
- className="lumx-slideshow-controls lumx-slideshow-controls--theme-light lumx-slideshow-controls--has-infinite-pagination"
189
- >
190
- <IconButton
191
- aria-controls="slideshow-slides4"
192
- className="lumx-slideshow-controls__navigation"
193
- color="dark"
194
- emphasis="low"
195
- icon="M15.41,16.58L10.83,12L15.41,7.41L14,6L8,12L14,18L15.41,16.58Z"
196
- label="Previous"
197
- onClick={[Function]}
198
- size="m"
199
- theme="light"
200
- />
201
- <div
202
- className="lumx-slideshow-controls__pagination"
134
+ </SlideshowItem>
135
+ <SlideshowItem
136
+ key="/demo-assets/portrait1-s200.jpg-5"
203
137
  >
204
- <div
205
- className="lumx-slideshow-controls__pagination-items"
206
- style={
138
+ <ImageBlock
139
+ align="left"
140
+ alt="Image 6"
141
+ captionPosition="below"
142
+ image="/demo-assets/portrait1-s200.jpg"
143
+ theme="light"
144
+ thumbnailProps={
207
145
  Object {
208
- "transform": "translateX(-0px)",
146
+ "aspectRatio": "horizontal",
147
+ "loading": "eager",
209
148
  }
210
149
  }
211
- >
212
- <button
213
- className="lumx-slideshow-controls__pagination-item lumx-slideshow-controls__pagination-item--is-active"
214
- key="0"
215
- onClick={[Function]}
216
- type="button"
217
- />
218
- <button
219
- className="lumx-slideshow-controls__pagination-item"
220
- key="1"
221
- onClick={[Function]}
222
- type="button"
223
- />
224
- <button
225
- className="lumx-slideshow-controls__pagination-item"
226
- key="2"
227
- onClick={[Function]}
228
- type="button"
229
- />
230
- <button
231
- className="lumx-slideshow-controls__pagination-item"
232
- key="3"
233
- onClick={[Function]}
234
- type="button"
235
- />
236
- <button
237
- className="lumx-slideshow-controls__pagination-item lumx-slideshow-controls__pagination-item--is-on-edge"
238
- key="4"
239
- onClick={[Function]}
240
- type="button"
241
- />
242
- <button
243
- className="lumx-slideshow-controls__pagination-item lumx-slideshow-controls__pagination-item--is-out-range"
244
- key="5"
245
- onClick={[Function]}
246
- type="button"
247
- />
248
- </div>
249
- </div>
250
- <IconButton
251
- aria-controls="slideshow-slides4"
252
- className="lumx-slideshow-controls__navigation"
253
- color="dark"
254
- emphasis="low"
255
- icon="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z"
256
- label="Next"
257
- onClick={[Function]}
258
- size="m"
259
- theme="light"
260
- />
261
- </div>,
150
+ />
151
+ </SlideshowItem>
152
+ </Slideshow>,
153
+ null,
262
154
  ]
263
155
  `;
@@ -1,3 +1,4 @@
1
1
  export * from './Slideshow';
2
2
  export * from './SlideshowItem';
3
3
  export * from './SlideshowControls';
4
+ export * from './Slides';
@@ -26,7 +26,7 @@ export const useFocusWithin = ({ element, onFocusIn, onFocusOut }: UseFocusWithi
26
26
  if (element) {
27
27
  element.removeEventListener('focusin', onFocusIn);
28
28
 
29
- element.addEventListener('focusout', onFocusOut);
29
+ element.removeEventListener('focusout', onFocusOut);
30
30
  }
31
31
  };
32
32
  }, [onFocusIn, element, onFocusOut]);