@telus-uds/components-base 1.82.0 → 1.84.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.
- package/CHANGELOG.md +33 -2
- package/lib/Button/ButtonGroup.js +9 -0
- package/lib/Carousel/Carousel.js +314 -145
- package/lib/ExpandCollapse/ExpandCollapse.js +18 -9
- package/lib/ExpandCollapse/Panel.js +12 -0
- package/lib/ExpandCollapse/dictionary.js +17 -0
- package/lib/Icon/IconText.js +3 -3
- package/lib/Listbox/Listbox.js +7 -5
- package/lib/Modal/WebModal.js +5 -3
- package/lib/Notification/Notification.js +1 -1
- package/lib/Radio/Radio.js +1 -1
- package/lib-module/Button/ButtonGroup.js +9 -0
- package/lib-module/Carousel/Carousel.js +312 -145
- package/lib-module/ExpandCollapse/ExpandCollapse.js +18 -9
- package/lib-module/ExpandCollapse/Panel.js +13 -1
- package/lib-module/ExpandCollapse/dictionary.js +10 -0
- package/lib-module/Icon/IconText.js +3 -3
- package/lib-module/Listbox/Listbox.js +8 -6
- package/lib-module/Modal/WebModal.js +5 -3
- package/lib-module/Notification/Notification.js +1 -1
- package/lib-module/Radio/Radio.js +1 -1
- package/package.json +2 -2
- package/src/Button/ButtonGroup.jsx +9 -0
- package/src/Carousel/Carousel.jsx +338 -133
- package/src/ExpandCollapse/ExpandCollapse.jsx +13 -5
- package/src/ExpandCollapse/Panel.jsx +27 -5
- package/src/ExpandCollapse/dictionary.js +10 -0
- package/src/Icon/IconText.jsx +5 -3
- package/src/Listbox/Listbox.jsx +112 -105
- package/src/Modal/WebModal.jsx +8 -4
- package/src/Notification/Notification.jsx +1 -1
- package/src/Radio/Radio.jsx +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useState, useRef, useEffect, useCallback, useMemo, forwardRef } from 'react'
|
|
2
2
|
import { View, Animated, PanResponder, StyleSheet, Platform, Dimensions } from 'react-native'
|
|
3
3
|
import PropTypes from 'prop-types'
|
|
4
4
|
import { useThemeTokens } from '../ThemeProvider'
|
|
@@ -26,6 +26,12 @@ import CarouselTabsPanel from './CarouselTabs/CarouselTabsPanel'
|
|
|
26
26
|
import CarouselTabsPanelItem from './CarouselTabs/CarouselTabsPanelItem'
|
|
27
27
|
import dictionary from './dictionary'
|
|
28
28
|
|
|
29
|
+
const TRANSITION_MODES = {
|
|
30
|
+
MANUAL: 'manual',
|
|
31
|
+
AUTOMATIC: 'automatic',
|
|
32
|
+
SWIPE: 'swipe'
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
const staticStyles = StyleSheet.create({
|
|
30
36
|
root: {
|
|
31
37
|
backgroundColor: 'transparent',
|
|
@@ -34,6 +40,12 @@ const staticStyles = StyleSheet.create({
|
|
|
34
40
|
position: 'relative',
|
|
35
41
|
top: 0,
|
|
36
42
|
left: 0
|
|
43
|
+
},
|
|
44
|
+
animationControlButton: {
|
|
45
|
+
position: 'absolute',
|
|
46
|
+
zIndex: 1,
|
|
47
|
+
right: Platform.OS === 'web' ? undefined : 40,
|
|
48
|
+
top: 40
|
|
37
49
|
}
|
|
38
50
|
})
|
|
39
51
|
|
|
@@ -49,6 +61,26 @@ const selectSwipeAreaStyles = (count, width) => ({
|
|
|
49
61
|
flexDirection: 'row'
|
|
50
62
|
})
|
|
51
63
|
|
|
64
|
+
const getDynamicPositionProperty = (areStylesAppliedOnPreviousButton) =>
|
|
65
|
+
areStylesAppliedOnPreviousButton ? 'left' : 'right'
|
|
66
|
+
|
|
67
|
+
const selectControlButtonPositionStyles = ({
|
|
68
|
+
positionVariant,
|
|
69
|
+
buttonWidth,
|
|
70
|
+
positionProperty = getDynamicPositionProperty(),
|
|
71
|
+
spaceBetweenSlideAndButton
|
|
72
|
+
}) => {
|
|
73
|
+
const styles = {}
|
|
74
|
+
if (positionVariant === 'edge') {
|
|
75
|
+
styles[positionProperty] = -1 * (buttonWidth / 2)
|
|
76
|
+
} else if (positionVariant === 'inside') {
|
|
77
|
+
styles[positionProperty] = 0
|
|
78
|
+
} else if (positionVariant === 'outside') {
|
|
79
|
+
styles[positionProperty] = -1 * (spaceBetweenSlideAndButton + buttonWidth)
|
|
80
|
+
}
|
|
81
|
+
return styles
|
|
82
|
+
}
|
|
83
|
+
|
|
52
84
|
const selectPreviousNextNavigationButtonStyles = (
|
|
53
85
|
previousNextNavigationButtonWidth,
|
|
54
86
|
previousNextNavigationPosition,
|
|
@@ -61,7 +93,6 @@ const selectPreviousNextNavigationButtonStyles = (
|
|
|
61
93
|
zIndex: 1,
|
|
62
94
|
position: 'absolute'
|
|
63
95
|
}
|
|
64
|
-
const dynamicPositionProperty = areStylesAppliedOnPreviousButton ? 'left' : 'right'
|
|
65
96
|
if (isFirstSlide) {
|
|
66
97
|
styles.visibility = areStylesAppliedOnPreviousButton ? 'hidden' : 'visible'
|
|
67
98
|
} else if (isLastSlide) {
|
|
@@ -70,15 +101,15 @@ const selectPreviousNextNavigationButtonStyles = (
|
|
|
70
101
|
styles.visibility = 'visible'
|
|
71
102
|
}
|
|
72
103
|
|
|
73
|
-
|
|
74
|
-
styles
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
104
|
+
return {
|
|
105
|
+
...styles,
|
|
106
|
+
...selectControlButtonPositionStyles({
|
|
107
|
+
positionVariant: previousNextNavigationPosition,
|
|
108
|
+
buttonWidth: previousNextNavigationButtonWidth,
|
|
109
|
+
positionProperty: getDynamicPositionProperty(areStylesAppliedOnPreviousButton),
|
|
110
|
+
spaceBetweenSlideAndButton: spaceBetweenSlideAndPreviousNextNavigation
|
|
111
|
+
})
|
|
80
112
|
}
|
|
81
|
-
return styles
|
|
82
113
|
}
|
|
83
114
|
|
|
84
115
|
const selectIconStyles = ({ iconBackgroundColor }) => ({ backgroundColor: iconBackgroundColor })
|
|
@@ -139,7 +170,7 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, vie
|
|
|
139
170
|
- `spaceBetweenSlideAndPreviousNextNavigation` - Horizontal space between slide and previous/next navigational buttons
|
|
140
171
|
- `spaceBetweenSlideAndPanelNavigation` - Vertical space between slide area and panel navigation area
|
|
141
172
|
*/
|
|
142
|
-
const Carousel =
|
|
173
|
+
const Carousel = forwardRef(
|
|
143
174
|
(
|
|
144
175
|
{
|
|
145
176
|
tokens,
|
|
@@ -170,10 +201,21 @@ const Carousel = React.forwardRef(
|
|
|
170
201
|
accessibilityLabel,
|
|
171
202
|
accessibilityLiveRegion = 'polite',
|
|
172
203
|
copy,
|
|
204
|
+
slideDuration = 0,
|
|
205
|
+
transitionDuration = 0,
|
|
206
|
+
autoPlay = false,
|
|
173
207
|
...rest
|
|
174
208
|
},
|
|
175
209
|
ref
|
|
176
210
|
) => {
|
|
211
|
+
let childrenArray = unpackFragment(children)
|
|
212
|
+
const autoPlayFeatureEnabled =
|
|
213
|
+
autoPlay && slideDuration > 0 && transitionDuration > 0 && childrenArray.length > 1
|
|
214
|
+
// if `Carousel` only has one `Carousel.Item`, convert this to a single-item array
|
|
215
|
+
if (!Array.isArray(childrenArray)) {
|
|
216
|
+
childrenArray = [childrenArray]
|
|
217
|
+
}
|
|
218
|
+
const getCopy = useCopy({ dictionary, copy })
|
|
177
219
|
const viewport = useViewport()
|
|
178
220
|
const themeTokens = useThemeTokens('Carousel', tokens, variant, {
|
|
179
221
|
viewport
|
|
@@ -181,22 +223,52 @@ const Carousel = React.forwardRef(
|
|
|
181
223
|
const {
|
|
182
224
|
previousIcon,
|
|
183
225
|
nextIcon,
|
|
226
|
+
playIcon,
|
|
227
|
+
pauseIcon,
|
|
184
228
|
showPreviousNextNavigation,
|
|
185
229
|
showPanelNavigation,
|
|
186
230
|
showPanelTabs,
|
|
187
231
|
spaceBetweenSlideAndPreviousNextNavigation
|
|
188
232
|
} = themeTokens
|
|
189
|
-
const [activeIndex, setActiveIndex] =
|
|
233
|
+
const [activeIndex, setActiveIndex] = useState(0)
|
|
234
|
+
const activeIndexRef = useRef(activeIndex)
|
|
235
|
+
const { reduceMotionEnabled } = useA11yInfo()
|
|
236
|
+
const reduceMotionRef = useRef(reduceMotionEnabled)
|
|
237
|
+
const [containerLayout, setContainerLayout] = React.useState({
|
|
238
|
+
x: 0,
|
|
239
|
+
y: 0,
|
|
240
|
+
width: 0
|
|
241
|
+
})
|
|
242
|
+
const containerLayoutRef = useRef(containerLayout)
|
|
243
|
+
|
|
244
|
+
const [previousNextNavigationButtonWidth, setPreviousNextNavigationButtonWidth] = useState(0)
|
|
245
|
+
const firstFocusRef = useRef(null)
|
|
246
|
+
const pan = useRef(new Animated.ValueXY()).current
|
|
247
|
+
const animatedX = useRef(0)
|
|
248
|
+
const animatedY = useRef(0)
|
|
249
|
+
const [isAnimating, setIsAnimating] = useState(false)
|
|
250
|
+
/**
|
|
251
|
+
* While having the same starting point, `isAutoPlayEnabled` and `isCarouselPlaying` are different states
|
|
252
|
+
*
|
|
253
|
+
* `isAutoPlayEnabled` is a state to determine if the autoplay feature is enabled or disabled
|
|
254
|
+
* `isCarouselPlaying` is a state to determine if the carousel is currently playing or paused
|
|
255
|
+
*/
|
|
256
|
+
const [isAutoPlayEnabled, setIsAutoPlayEnabled] = useState(autoPlayFeatureEnabled)
|
|
257
|
+
const [isCarouselPlaying, setisCarouselPlaying] = useState(autoPlayFeatureEnabled)
|
|
258
|
+
const isSwiping = useRef(false)
|
|
259
|
+
const autoPlayRef = useRef(null)
|
|
190
260
|
|
|
191
|
-
const
|
|
192
|
-
const
|
|
261
|
+
const isFirstSlide = !activeIndex
|
|
262
|
+
const isLastSlide = activeIndex + 1 >= childrenArray.length
|
|
263
|
+
|
|
264
|
+
const handleAnimationStart = useCallback(
|
|
193
265
|
(...args) => {
|
|
194
266
|
if (typeof onAnimationStart === 'function') onAnimationStart(...args)
|
|
195
267
|
setIsAnimating(true)
|
|
196
268
|
},
|
|
197
269
|
[onAnimationStart]
|
|
198
270
|
)
|
|
199
|
-
const handleAnimationEnd =
|
|
271
|
+
const handleAnimationEnd = useCallback(
|
|
200
272
|
(...args) => {
|
|
201
273
|
if (typeof onAnimationEnd === 'function') onAnimationEnd(...args)
|
|
202
274
|
setIsAnimating(false)
|
|
@@ -204,68 +276,30 @@ const Carousel = React.forwardRef(
|
|
|
204
276
|
[onAnimationEnd]
|
|
205
277
|
)
|
|
206
278
|
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
let childrenArray = unpackFragment(children)
|
|
210
|
-
// if `Carousel` only has one `Carousel.Item`, convert this to a single-item array
|
|
211
|
-
if (!Array.isArray(childrenArray)) {
|
|
212
|
-
childrenArray = [childrenArray]
|
|
213
|
-
}
|
|
214
|
-
const systemProps = selectProps({
|
|
215
|
-
...rest,
|
|
216
|
-
accessibilityRole,
|
|
217
|
-
accessibilityLabel,
|
|
218
|
-
accessibilityValue: {
|
|
219
|
-
min: 1,
|
|
220
|
-
max: childrenArray.length,
|
|
221
|
-
now: activeIndex + 1
|
|
222
|
-
}
|
|
223
|
-
})
|
|
224
|
-
const { reduceMotionEnabled } = useA11yInfo()
|
|
225
|
-
const [containerLayout, setContainerLayout] = React.useState({
|
|
226
|
-
x: 0,
|
|
227
|
-
y: 0,
|
|
228
|
-
width: 0
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
const [previousNextNavigationButtonWidth, setPreviousNextNavigationButtonWidth] =
|
|
232
|
-
React.useState(0)
|
|
233
|
-
const firstFocusRef = React.useRef(null)
|
|
234
|
-
const pan = React.useRef(new Animated.ValueXY()).current
|
|
235
|
-
const animatedX = React.useRef(0)
|
|
236
|
-
const animatedY = React.useRef(0)
|
|
237
|
-
const isFirstSlide = !activeIndex
|
|
238
|
-
const isLastSlide = activeIndex + 1 >= children.length
|
|
239
|
-
|
|
240
|
-
const onContainerLayout = ({
|
|
241
|
-
nativeEvent: {
|
|
242
|
-
layout: { x, y, width }
|
|
243
|
-
}
|
|
244
|
-
}) => setContainerLayout((prevState) => ({ ...prevState, x, y, width }))
|
|
245
|
-
|
|
246
|
-
const onPreviousNextNavigationButtonLayout = ({
|
|
247
|
-
nativeEvent: {
|
|
248
|
-
layout: { width }
|
|
249
|
-
}
|
|
250
|
-
}) => setPreviousNextNavigationButtonWidth(width)
|
|
251
|
-
|
|
252
|
-
const updateOffset = React.useCallback(() => {
|
|
253
|
-
animatedX.current = containerLayout.width * activeIndex * -1
|
|
279
|
+
const updateOffset = useCallback(() => {
|
|
280
|
+
animatedX.current = containerLayoutRef.current.width * activeIndexRef.current * -1
|
|
254
281
|
animatedY.current = 0
|
|
255
282
|
pan.setOffset({
|
|
256
283
|
x: animatedX.current,
|
|
257
284
|
y: animatedY.current
|
|
258
285
|
})
|
|
259
286
|
pan.setValue({ x: 0, y: 0 })
|
|
260
|
-
}, [
|
|
287
|
+
}, [pan, animatedX])
|
|
261
288
|
|
|
262
|
-
const animate =
|
|
289
|
+
const animate = useCallback(
|
|
263
290
|
(toValue, toIndex) => {
|
|
264
291
|
const handleAnimationEndToIndex = (...args) => handleAnimationEnd(toIndex, ...args)
|
|
265
|
-
if (
|
|
292
|
+
if (reduceMotionRef.current || isSwiping.current) {
|
|
266
293
|
Animated.timing(pan, { toValue, duration: 1, useNativeDriver: false }).start(
|
|
267
294
|
handleAnimationEndToIndex
|
|
268
295
|
)
|
|
296
|
+
} else if (isAutoPlayEnabled) {
|
|
297
|
+
Animated.timing(pan, {
|
|
298
|
+
...springConfig,
|
|
299
|
+
toValue,
|
|
300
|
+
useNativeDriver: false,
|
|
301
|
+
duration: transitionDuration * 1000
|
|
302
|
+
}).start(handleAnimationEndToIndex)
|
|
269
303
|
} else {
|
|
270
304
|
Animated.spring(pan, {
|
|
271
305
|
...springConfig,
|
|
@@ -274,60 +308,178 @@ const Carousel = React.forwardRef(
|
|
|
274
308
|
}).start(handleAnimationEndToIndex)
|
|
275
309
|
}
|
|
276
310
|
},
|
|
277
|
-
[pan, springConfig,
|
|
311
|
+
[pan, springConfig, handleAnimationEnd, transitionDuration, isAutoPlayEnabled]
|
|
278
312
|
)
|
|
279
313
|
|
|
280
|
-
const
|
|
281
|
-
(
|
|
314
|
+
const stopAutoplay = useCallback(() => {
|
|
315
|
+
if (autoPlayRef?.current) {
|
|
316
|
+
clearTimeout(autoPlayRef?.current)
|
|
317
|
+
}
|
|
318
|
+
}, [])
|
|
319
|
+
|
|
320
|
+
const updateIndex = useCallback(
|
|
321
|
+
(delta = 1, transitionMode) => {
|
|
282
322
|
const toValue = { x: 0, y: 0 }
|
|
283
323
|
let skipChanges = !delta
|
|
284
324
|
let calcDelta = delta
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
calcDelta = -1 * activeIndex + delta - 1
|
|
325
|
+
if (activeIndexRef.current <= 0 && delta < 0) {
|
|
326
|
+
skipChanges = transitionMode !== TRANSITION_MODES.AUTOMATIC
|
|
327
|
+
calcDelta = childrenArray.length + delta
|
|
328
|
+
} else if (activeIndexRef.current + 1 >= childrenArray.length && delta > 0) {
|
|
329
|
+
skipChanges = transitionMode !== TRANSITION_MODES.AUTOMATIC
|
|
330
|
+
calcDelta = -1 * activeIndexRef.current + delta - 1
|
|
292
331
|
}
|
|
293
332
|
|
|
294
|
-
const index =
|
|
295
|
-
|
|
333
|
+
const index = activeIndexRef.current + calcDelta
|
|
296
334
|
if (skipChanges) {
|
|
297
335
|
animate(toValue, index)
|
|
298
336
|
return calcDelta
|
|
299
337
|
}
|
|
300
338
|
|
|
339
|
+
stopAutoplay()
|
|
301
340
|
setActiveIndex(index)
|
|
302
341
|
|
|
303
|
-
toValue.x =
|
|
304
|
-
|
|
342
|
+
toValue.x = containerLayoutRef.current.width * -1 * calcDelta
|
|
305
343
|
animate(toValue, index)
|
|
306
|
-
|
|
344
|
+
if (isCarouselPlaying) {
|
|
345
|
+
stopAutoplay()
|
|
346
|
+
if (
|
|
347
|
+
index === 0 &&
|
|
348
|
+
activeIndexRef.current + 1 === childrenArray.length &&
|
|
349
|
+
transitionMode === TRANSITION_MODES.AUTOMATIC
|
|
350
|
+
) {
|
|
351
|
+
setisCarouselPlaying(false)
|
|
352
|
+
} else if (isAutoPlayEnabled) {
|
|
353
|
+
autoPlayRef.current = setTimeout(() => {
|
|
354
|
+
updateOffset()
|
|
355
|
+
handleAnimationStart(activeIndexRef.current)
|
|
356
|
+
updateIndex(slideDuration < 0 ? -1 : 1, TRANSITION_MODES.AUTOMATIC)
|
|
357
|
+
if (refocus) firstFocusRef.current?.focus()
|
|
358
|
+
}, Math.abs(slideDuration) * 1000)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
307
361
|
if (onIndexChanged) onIndexChanged(calcDelta, index)
|
|
308
362
|
return calcDelta
|
|
309
363
|
},
|
|
310
|
-
[
|
|
364
|
+
[
|
|
365
|
+
handleAnimationStart,
|
|
366
|
+
refocus,
|
|
367
|
+
slideDuration,
|
|
368
|
+
updateOffset,
|
|
369
|
+
animate,
|
|
370
|
+
childrenArray.length,
|
|
371
|
+
onIndexChanged,
|
|
372
|
+
isCarouselPlaying,
|
|
373
|
+
stopAutoplay,
|
|
374
|
+
isAutoPlayEnabled
|
|
375
|
+
]
|
|
311
376
|
)
|
|
312
377
|
|
|
313
|
-
const
|
|
314
|
-
(
|
|
378
|
+
const startAutoplay = useCallback(() => {
|
|
379
|
+
stopAutoplay()
|
|
380
|
+
if (isAutoPlayEnabled) {
|
|
381
|
+
autoPlayRef.current = setTimeout(() => {
|
|
382
|
+
updateOffset()
|
|
383
|
+
handleAnimationStart(activeIndexRef.current)
|
|
384
|
+
updateIndex(slideDuration < 0 ? -1 : 1, TRANSITION_MODES.AUTOMATIC)
|
|
385
|
+
if (refocus && Platform.OS === 'web') firstFocusRef.current?.focus()
|
|
386
|
+
}, Math.abs(slideDuration) * 1000)
|
|
387
|
+
}
|
|
388
|
+
}, [
|
|
389
|
+
handleAnimationStart,
|
|
390
|
+
refocus,
|
|
391
|
+
updateIndex,
|
|
392
|
+
updateOffset,
|
|
393
|
+
slideDuration,
|
|
394
|
+
stopAutoplay,
|
|
395
|
+
isAutoPlayEnabled
|
|
396
|
+
])
|
|
397
|
+
|
|
398
|
+
const fixOffsetAndGo = useCallback(
|
|
399
|
+
(delta, transitionMode) => {
|
|
315
400
|
updateOffset()
|
|
316
|
-
handleAnimationStart(
|
|
317
|
-
updateIndex(delta)
|
|
318
|
-
if (refocus) firstFocusRef.current?.focus()
|
|
401
|
+
handleAnimationStart(activeIndexRef.current)
|
|
402
|
+
updateIndex(delta, transitionMode)
|
|
403
|
+
if (refocus && Platform.OS === 'web') firstFocusRef.current?.focus()
|
|
319
404
|
},
|
|
320
|
-
[updateIndex, updateOffset,
|
|
405
|
+
[updateIndex, updateOffset, handleAnimationStart, refocus]
|
|
321
406
|
)
|
|
322
407
|
|
|
323
|
-
const goToNeighboring =
|
|
324
|
-
(toPrev = false) => {
|
|
325
|
-
fixOffsetAndGo(toPrev ? -1 : 1)
|
|
408
|
+
const goToNeighboring = useCallback(
|
|
409
|
+
(toPrev = false, transitionMode = TRANSITION_MODES.MANUAL) => {
|
|
410
|
+
fixOffsetAndGo(toPrev ? -1 : 1, transitionMode)
|
|
326
411
|
},
|
|
327
412
|
[fixOffsetAndGo]
|
|
328
413
|
)
|
|
329
414
|
|
|
330
|
-
|
|
415
|
+
useEffect(() => {
|
|
416
|
+
activeIndexRef.current = activeIndex
|
|
417
|
+
}, [activeIndex])
|
|
418
|
+
|
|
419
|
+
useEffect(() => {
|
|
420
|
+
reduceMotionRef.current = reduceMotionEnabled
|
|
421
|
+
}, [reduceMotionEnabled])
|
|
422
|
+
|
|
423
|
+
useEffect(() => {
|
|
424
|
+
containerLayoutRef.current = containerLayout
|
|
425
|
+
}, [containerLayout])
|
|
426
|
+
|
|
427
|
+
useEffect(() => {
|
|
428
|
+
pan.x.addListener(({ value }) => {
|
|
429
|
+
animatedX.current = value
|
|
430
|
+
})
|
|
431
|
+
pan.y.addListener(({ value }) => {
|
|
432
|
+
animatedY.current = value
|
|
433
|
+
})
|
|
434
|
+
if (isCarouselPlaying) {
|
|
435
|
+
startAutoplay()
|
|
436
|
+
}
|
|
437
|
+
return () => {
|
|
438
|
+
stopAutoplay()
|
|
439
|
+
pan.x.removeAllListeners()
|
|
440
|
+
pan.y.removeAllListeners()
|
|
441
|
+
}
|
|
442
|
+
}, [pan.x, pan.y, startAutoplay, stopAutoplay, isCarouselPlaying])
|
|
443
|
+
|
|
444
|
+
useEffect(() => {
|
|
445
|
+
const subscription = Dimensions.addEventListener('change', () => {
|
|
446
|
+
updateOffset()
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
return () => {
|
|
450
|
+
if (subscription.remove) {
|
|
451
|
+
subscription.remove()
|
|
452
|
+
} else {
|
|
453
|
+
Dimensions.removeEventListener('change', updateOffset)
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}, [updateOffset])
|
|
457
|
+
|
|
458
|
+
useEffect(() => {
|
|
459
|
+
setIsAutoPlayEnabled(
|
|
460
|
+
autoPlay && slideDuration > 0 && transitionDuration > 0 && childrenArray.length > 1
|
|
461
|
+
)
|
|
462
|
+
}, [autoPlay, slideDuration, transitionDuration, childrenArray.length])
|
|
463
|
+
|
|
464
|
+
useEffect(() => {
|
|
465
|
+
return () => {
|
|
466
|
+
stopAutoplay()
|
|
467
|
+
}
|
|
468
|
+
}, [stopAutoplay])
|
|
469
|
+
|
|
470
|
+
const onContainerLayout = ({
|
|
471
|
+
nativeEvent: {
|
|
472
|
+
layout: { x, y, width }
|
|
473
|
+
}
|
|
474
|
+
}) => setContainerLayout((prevState) => ({ ...prevState, x, y, width }))
|
|
475
|
+
|
|
476
|
+
const onPreviousNextNavigationButtonLayout = ({
|
|
477
|
+
nativeEvent: {
|
|
478
|
+
layout: { width }
|
|
479
|
+
}
|
|
480
|
+
}) => setPreviousNextNavigationButtonWidth(width)
|
|
481
|
+
|
|
482
|
+
const isSwipeAllowed = useCallback(() => {
|
|
331
483
|
if (childrenArray.length === 1) {
|
|
332
484
|
return false
|
|
333
485
|
}
|
|
@@ -337,7 +489,7 @@ const Carousel = React.forwardRef(
|
|
|
337
489
|
return true
|
|
338
490
|
}, [viewport, childrenArray.length])
|
|
339
491
|
|
|
340
|
-
const panResponder =
|
|
492
|
+
const panResponder = useMemo(
|
|
341
493
|
() =>
|
|
342
494
|
PanResponder.create({
|
|
343
495
|
onPanResponderTerminationRequest: () => false,
|
|
@@ -347,76 +499,70 @@ const Carousel = React.forwardRef(
|
|
|
347
499
|
return false
|
|
348
500
|
}
|
|
349
501
|
|
|
350
|
-
handleAnimationStart(
|
|
502
|
+
handleAnimationStart(activeIndexRef.current)
|
|
351
503
|
|
|
352
|
-
|
|
504
|
+
const allow = Math.abs(gestureState.dx) > minDistanceToCapture
|
|
505
|
+
|
|
506
|
+
if (allow) {
|
|
507
|
+
isSwiping.current = true
|
|
508
|
+
stopAutoplay()
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return allow
|
|
512
|
+
},
|
|
513
|
+
onPanResponderGrant: () => {
|
|
514
|
+
updateOffset()
|
|
353
515
|
},
|
|
354
|
-
onPanResponderGrant: () => updateOffset(),
|
|
355
516
|
onPanResponderMove: Animated.event([null, { dx: pan.x }], {
|
|
356
517
|
useNativeDriver: false
|
|
357
518
|
}),
|
|
358
519
|
onPanResponderRelease: (_, gesture) => {
|
|
520
|
+
if (isCarouselPlaying) {
|
|
521
|
+
startAutoplay()
|
|
522
|
+
}
|
|
359
523
|
const correction = gesture.moveX - gesture.x0
|
|
360
524
|
|
|
361
|
-
if (Math.abs(correction) <
|
|
525
|
+
if (Math.abs(correction) < containerLayoutRef.current.width * minDistanceForAction) {
|
|
362
526
|
animate({ x: 0, y: 0 }, 0)
|
|
363
527
|
} else {
|
|
364
528
|
const delta = correction > 0 ? -1 : 1
|
|
365
|
-
updateIndex(delta)
|
|
529
|
+
updateIndex(delta, TRANSITION_MODES.SWIPE)
|
|
366
530
|
}
|
|
531
|
+
|
|
532
|
+
isSwiping.current = false
|
|
367
533
|
}
|
|
368
534
|
}),
|
|
369
535
|
[
|
|
370
|
-
containerLayout.width,
|
|
371
536
|
updateIndex,
|
|
372
537
|
updateOffset,
|
|
373
538
|
animate,
|
|
374
539
|
isSwipeAllowed,
|
|
375
|
-
activeIndex,
|
|
376
540
|
minDistanceForAction,
|
|
377
541
|
handleAnimationStart,
|
|
378
542
|
minDistanceToCapture,
|
|
379
|
-
pan.x
|
|
543
|
+
pan.x,
|
|
544
|
+
startAutoplay,
|
|
545
|
+
stopAutoplay,
|
|
546
|
+
isCarouselPlaying
|
|
380
547
|
]
|
|
381
548
|
)
|
|
382
549
|
|
|
383
|
-
|
|
384
|
-
pan.x.addListener(({ value }) => {
|
|
385
|
-
animatedX.current = value
|
|
386
|
-
})
|
|
387
|
-
pan.y.addListener(({ value }) => {
|
|
388
|
-
animatedY.current = value
|
|
389
|
-
})
|
|
390
|
-
return () => {
|
|
391
|
-
pan.x.removeAllListeners()
|
|
392
|
-
pan.y.removeAllListeners()
|
|
393
|
-
}
|
|
394
|
-
}, [pan.x, pan.y])
|
|
395
|
-
|
|
396
|
-
React.useEffect(() => {
|
|
397
|
-
const subscription = Dimensions.addEventListener('change', () => {
|
|
398
|
-
updateOffset()
|
|
399
|
-
})
|
|
400
|
-
|
|
401
|
-
return () => subscription?.remove()
|
|
402
|
-
})
|
|
403
|
-
|
|
404
|
-
const goToNext = React.useCallback(() => {
|
|
550
|
+
const goToNext = useCallback(() => {
|
|
405
551
|
goToNeighboring()
|
|
406
552
|
}, [goToNeighboring])
|
|
407
553
|
|
|
408
|
-
const goToPrev =
|
|
554
|
+
const goToPrev = useCallback(() => {
|
|
409
555
|
goToNeighboring(true)
|
|
410
556
|
}, [goToNeighboring])
|
|
411
557
|
|
|
412
|
-
const goTo =
|
|
558
|
+
const goTo = useCallback(
|
|
413
559
|
(index = 0) => {
|
|
414
|
-
const delta = index -
|
|
560
|
+
const delta = index - activeIndexRef.current
|
|
415
561
|
if (delta) {
|
|
416
|
-
fixOffsetAndGo(delta)
|
|
562
|
+
fixOffsetAndGo(delta, TRANSITION_MODES.MANUAL)
|
|
417
563
|
}
|
|
418
564
|
},
|
|
419
|
-
[fixOffsetAndGo
|
|
565
|
+
[fixOffsetAndGo]
|
|
420
566
|
)
|
|
421
567
|
|
|
422
568
|
// @TODO: - these are Allium-theme variants and won't have any effect in themes that don't implement them.
|
|
@@ -428,7 +574,7 @@ const Carousel = React.forwardRef(
|
|
|
428
574
|
inverse: variant?.inverse
|
|
429
575
|
}
|
|
430
576
|
|
|
431
|
-
const getCopyWithPlaceholders =
|
|
577
|
+
const getCopyWithPlaceholders = useCallback(
|
|
432
578
|
(copyKey) => {
|
|
433
579
|
const copyText = getCopy(copyKey)
|
|
434
580
|
.replace(/%\{title\}/g, title)
|
|
@@ -459,6 +605,18 @@ const Carousel = React.forwardRef(
|
|
|
459
605
|
}
|
|
460
606
|
}
|
|
461
607
|
}
|
|
608
|
+
|
|
609
|
+
const systemProps = selectProps({
|
|
610
|
+
...rest,
|
|
611
|
+
accessibilityRole,
|
|
612
|
+
accessibilityLabel,
|
|
613
|
+
accessibilityValue: {
|
|
614
|
+
min: 1,
|
|
615
|
+
max: childrenArray.length,
|
|
616
|
+
now: activeIndex + 1
|
|
617
|
+
}
|
|
618
|
+
})
|
|
619
|
+
|
|
462
620
|
// If container isn't used for focus, give it a label of title if none is passed in,
|
|
463
621
|
// otherwise read the current position on focus
|
|
464
622
|
const containerAccessibilityLabel =
|
|
@@ -471,6 +629,15 @@ const Carousel = React.forwardRef(
|
|
|
471
629
|
...(isFirstFocusContainer && { ref: containerRef, focusable: true })
|
|
472
630
|
}
|
|
473
631
|
|
|
632
|
+
const onAnimationControlButtonPress = useCallback(() => {
|
|
633
|
+
if (isCarouselPlaying) {
|
|
634
|
+
stopAutoplay()
|
|
635
|
+
} else {
|
|
636
|
+
startAutoplay()
|
|
637
|
+
}
|
|
638
|
+
setisCarouselPlaying((prevState) => !prevState)
|
|
639
|
+
}, [isCarouselPlaying, stopAutoplay, startAutoplay])
|
|
640
|
+
|
|
474
641
|
return (
|
|
475
642
|
<CarouselProvider
|
|
476
643
|
activeIndex={activeIndex}
|
|
@@ -490,6 +657,25 @@ const Carousel = React.forwardRef(
|
|
|
490
657
|
{...systemProps}
|
|
491
658
|
{...containerProps}
|
|
492
659
|
>
|
|
660
|
+
{isAutoPlayEnabled ? (
|
|
661
|
+
<View
|
|
662
|
+
style={[
|
|
663
|
+
staticStyles.animationControlButton,
|
|
664
|
+
selectControlButtonPositionStyles({
|
|
665
|
+
positionVariant: previousNextNavigationPosition,
|
|
666
|
+
buttonWidth: previousNextNavigationButtonWidth,
|
|
667
|
+
positionProperty: getDynamicPositionProperty(),
|
|
668
|
+
spaceBetweenSlideAndButton: spaceBetweenSlideAndPreviousNextNavigation
|
|
669
|
+
})
|
|
670
|
+
]}
|
|
671
|
+
>
|
|
672
|
+
<IconButton
|
|
673
|
+
icon={isCarouselPlaying ? pauseIcon : playIcon}
|
|
674
|
+
variant={previousNextIconButtonVariants}
|
|
675
|
+
onPress={onAnimationControlButtonPress}
|
|
676
|
+
/>
|
|
677
|
+
</View>
|
|
678
|
+
) : null}
|
|
493
679
|
{showPreviousNextNavigation && childrenArray.length > 1 ? (
|
|
494
680
|
<View
|
|
495
681
|
style={selectPreviousNextNavigationButtonStyles(
|
|
@@ -531,7 +717,7 @@ const Carousel = React.forwardRef(
|
|
|
531
717
|
<View style={selectContainerStyles(containerLayout.width)}>
|
|
532
718
|
<Animated.View
|
|
533
719
|
style={StyleSheet.flatten([
|
|
534
|
-
selectSwipeAreaStyles(
|
|
720
|
+
selectSwipeAreaStyles(childrenArray.length, containerLayout.width),
|
|
535
721
|
{
|
|
536
722
|
transform: [{ translateX: pan.x }, { translateY: pan.y }]
|
|
537
723
|
}
|
|
@@ -641,7 +827,7 @@ Carousel.propTypes = {
|
|
|
641
827
|
* This function is also provided with a parameter indicating changed index (either 1, or -1)
|
|
642
828
|
* Use it as follows:
|
|
643
829
|
* ```js
|
|
644
|
-
* const onIndexChangedCallback =
|
|
830
|
+
* const onIndexChangedCallback = useCallback((changedIndex, currentActiveIndex) => {
|
|
645
831
|
* console.log(changedIndex)
|
|
646
832
|
* }, []) // pass local dependencies as per your component
|
|
647
833
|
* <Carousel
|
|
@@ -692,7 +878,7 @@ Carousel.propTypes = {
|
|
|
692
878
|
* This function is also provided with a parameter indicating the current slide index before animation starts
|
|
693
879
|
* Use it as follows:
|
|
694
880
|
* ```js
|
|
695
|
-
* const onAnimationStartCallback =
|
|
881
|
+
* const onAnimationStartCallback = useCallback((currentIndex) => {
|
|
696
882
|
* console.log(currentIndex)
|
|
697
883
|
* }, []) // pass local dependencies as per your component
|
|
698
884
|
* <Carousel
|
|
@@ -709,7 +895,7 @@ Carousel.propTypes = {
|
|
|
709
895
|
* This function is also provided with a parameter indicating the updated slide index after animation ends
|
|
710
896
|
* Use it as follows:
|
|
711
897
|
* ```js
|
|
712
|
-
* const onAnimationEndCallback =
|
|
898
|
+
* const onAnimationEndCallback = useCallback((changedIndex) => {
|
|
713
899
|
* console.log(changedIndex)
|
|
714
900
|
* }, []) // pass local dependencies as per your component
|
|
715
901
|
* <Carousel
|
|
@@ -743,7 +929,26 @@ Carousel.propTypes = {
|
|
|
743
929
|
* Note that if the immediate Carousel children do not all render as `'li'` elements,
|
|
744
930
|
* this should be changed (e.g. pass tag="div") because only 'li' is a valid child of 'ul'.
|
|
745
931
|
*/
|
|
746
|
-
tag: PropTypes.oneOf(layoutTags)
|
|
932
|
+
tag: PropTypes.oneOf(layoutTags),
|
|
933
|
+
/**
|
|
934
|
+
* If set to `true`, the Carousel will automatically transition between slides
|
|
935
|
+
* and show the play/pause button
|
|
936
|
+
* - Default value is `false`
|
|
937
|
+
* - `slideDuration` and `transitionDuration` are required to be set for this to work
|
|
938
|
+
*/
|
|
939
|
+
autoPlay: PropTypes.bool,
|
|
940
|
+
/**
|
|
941
|
+
* Duration of the time in seconds spent on each slide
|
|
942
|
+
* - Default value is `0`
|
|
943
|
+
* - `autoPlay` and `transitionDuration` are required to be set for this to work
|
|
944
|
+
*/
|
|
945
|
+
slideDuration: PropTypes.number,
|
|
946
|
+
/**
|
|
947
|
+
* Duration of the time in seconds between each slide transition
|
|
948
|
+
* - Default value is `0`
|
|
949
|
+
* - `autoPlay` and `slideDuration` are required to be set for this to work
|
|
950
|
+
*/
|
|
951
|
+
transitionDuration: PropTypes.number
|
|
747
952
|
}
|
|
748
953
|
|
|
749
954
|
Carousel.Item = CarouselItem
|