@telus-uds/components-base 1.9.0 → 1.10.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.
@@ -0,0 +1,570 @@
1
+ import React from 'react';
2
+ import View from "react-native-web/dist/exports/View";
3
+ import Animated from "react-native-web/dist/exports/Animated";
4
+ import PanResponder from "react-native-web/dist/exports/PanResponder";
5
+ import StyleSheet from "react-native-web/dist/exports/StyleSheet";
6
+ import Platform from "react-native-web/dist/exports/Platform";
7
+ import PropTypes from 'prop-types';
8
+ import { useThemeTokens } from '../ThemeProvider';
9
+ import { useViewport } from '../ViewportProvider';
10
+ import { getTokensPropType, variantProp, selectSystemProps, a11yProps, viewProps } from '../utils';
11
+ import { useA11yInfo } from '../A11yInfoProvider';
12
+ import { CarouselProvider } from './CarouselContext';
13
+ import CarouselItem from './CarouselItem';
14
+ import StepTracker from '../StepTracker';
15
+ import StackView from '../StackView';
16
+ import IconButton from '../IconButton';
17
+ import { jsx as _jsx } from "react/jsx-runtime";
18
+ import { jsxs as _jsxs } from "react/jsx-runtime";
19
+ const staticStyles = StyleSheet.create({
20
+ root: {
21
+ backgroundColor: 'transparent',
22
+ justifyContent: 'center',
23
+ alignItems: 'center',
24
+ position: 'relative',
25
+ top: 0,
26
+ left: 0
27
+ }
28
+ });
29
+ const staticTokens = {
30
+ stackView: {
31
+ justifyContent: 'center'
32
+ },
33
+ stepTracker: {
34
+ showStepLabel: false,
35
+ showStepTrackerLabel: true,
36
+ knobCompletedBackgroundColor: 'none',
37
+ connectorCompletedColor: 'none',
38
+ connectorColor: 'none'
39
+ }
40
+ };
41
+
42
+ const selectContainerStyles = width => ({
43
+ backgroundColor: 'transparent',
44
+ overflow: 'hidden',
45
+ width
46
+ });
47
+
48
+ const selectSwipeAreaStyles = (count, width) => ({
49
+ width: width * count,
50
+ justifyContent: 'space-between',
51
+ flexDirection: 'row'
52
+ });
53
+
54
+ const selectPreviousNextNavigationButtonStyles = (previousNextNavigationButtonWidth, previousNextNavigationPosition, spaceBetweenSlideAndPreviousNextNavigation, isFirstSlide, isLastSlide, areStylesAppliedOnPreviousButton) => {
55
+ const styles = {
56
+ zIndex: 1,
57
+ position: 'absolute'
58
+ };
59
+ const dynamicPositionProperty = areStylesAppliedOnPreviousButton ? 'left' : 'right';
60
+
61
+ if (isFirstSlide) {
62
+ styles.visibility = areStylesAppliedOnPreviousButton ? 'hidden' : 'visible';
63
+ } else if (isLastSlide) {
64
+ styles.visibility = areStylesAppliedOnPreviousButton ? 'visible' : 'hidden';
65
+ } else {
66
+ styles.visibility = 'visible';
67
+ }
68
+
69
+ if (previousNextNavigationPosition === 'edge') {
70
+ styles[dynamicPositionProperty] = -1 * (previousNextNavigationButtonWidth / 2);
71
+ } else if (previousNextNavigationPosition === 'inside') {
72
+ styles[dynamicPositionProperty] = 0;
73
+ } else if (previousNextNavigationPosition === 'outside') {
74
+ styles[dynamicPositionProperty] = -1 * (spaceBetweenSlideAndPreviousNextNavigation + previousNextNavigationButtonWidth);
75
+ }
76
+
77
+ return styles;
78
+ };
79
+
80
+ const defaultPanelNavigationDictionary = {
81
+ en: {
82
+ stepTrackerLabel: 'Showing %{stepNumber} of %{stepCount}'
83
+ },
84
+ fr: {
85
+ stepTrackerLabel: 'Étape %{stepNumber} sur %{stepCount}: %{stepLabel}'
86
+ }
87
+ };
88
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
89
+ /**
90
+ * Carousel is a general-purpose content slider that can be used to render content in terms of slides.
91
+
92
+ ## Usage
93
+ - `Carousel` is a top-level export from `@telus-uds/components-base` which is used to render a Carousel
94
+ - Immediately within `Carousel`, individual slides are wrapped in `Carousel.Item` for the top-level `Carousel` to know how to identify an individual slide
95
+ - You can use any UDS component or other platform-specific component, (based on the platform you're rendering) to achieve any desired layout
96
+ - By default, Carousel takea all the `width` available to it and the `height` is determined based on the content in the slide with more content
97
+ - You may want to wrap Carousel in other layout components like `Box`, `FlexGrid` etc, to achieve a responsive layout of your need
98
+
99
+ ## `useCarousel` custom hook
100
+
101
+ ```jsx
102
+ import { useCarousel } from '@telus-uds/components-base'
103
+
104
+ const SomeComponentWithinCarouselItem = () => {
105
+ const {
106
+ activeIndex,
107
+ totalItems,
108
+ width,
109
+ goTo
110
+ } = useCarousel()
111
+ return <Text>Hi!</Text>
112
+ }
113
+ ```
114
+
115
+ You can use `useCarousel` to hook into internal state of the Carousel component like:
116
+ - `activeIndex`: Index of the current slide
117
+ - `totalItems`: Total number of items/slides passed to the Carousel
118
+ - `width`: Width of the individual carousel slide
119
+ - `goTo`: A function to go to a particular slide by passing the index of that slide, e.g: goTo(0) where `0` is the index of the first slide
120
+
121
+ ## Accessibility
122
+
123
+ - Top-level `Carousel` and `Carousel.Item` can take all possible React Native's `View` and `a11y` props
124
+ - If your slide contains input elements like buttons, you may want to configure them to be only focusable when `activeIndex` is equal to the current slide index in order to avoid tabbing going between slides
125
+
126
+ ## Platform considerations
127
+ The component is available on both native platforms and web.
128
+
129
+ ## Other considerations
130
+ - You may want to use the same kind of layout in all your slides to avoid visual and height differences
131
+ - `previous` and `next` navigation buttons are automatically removed in `sm` and `xs` viewports, as these smaller viewports offers swipe functionality
132
+
133
+ ## Tokens
134
+
135
+ You can override the following tokens in exceptional circumstances:
136
+ - `previousIcon` - Icon of the previous button
137
+ - `nextIcon` - Icon of the next button
138
+ - `showPreviousNextNavigation` - If you want to show/hide the previous/next navigation
139
+ - `showPanelNavigation` - If you want to show/hide the panel navigation
140
+ - `spaceBetweenSlideAndPreviousNextNavigation` - Horizontal space between slide and previous/next navigational buttons
141
+ - `spaceBetweenSlideAndPanelNavigation` - Vertical space between slide area and panel navigation area
142
+ */
143
+
144
+ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
145
+ let {
146
+ tokens,
147
+ variant,
148
+ children,
149
+ previousNextNavigationPosition = 'inside',
150
+ previousNextIconSize = 'default',
151
+ minDistanceToCapture = 5,
152
+ minDistanceForAction = 0.2,
153
+ onAnimationStart,
154
+ onAnimationEnd,
155
+ onIndexChanged,
156
+ springConfig = undefined,
157
+ onRenderPanelNavigation,
158
+ panelNavigationTextDictionary = defaultPanelNavigationDictionary,
159
+ accessibilityRole = 'adjustable',
160
+ accessibilityLabel = 'carousel',
161
+ ...rest
162
+ } = _ref;
163
+ const viewport = useViewport();
164
+ const {
165
+ previousIcon,
166
+ nextIcon,
167
+ showPreviousNextNavigation,
168
+ showPanelNavigation,
169
+ spaceBetweenSlideAndPreviousNextNavigation,
170
+ spaceBetweenSlideAndPanelNavigation
171
+ } = useThemeTokens('Carousel', tokens, variant, {
172
+ viewport
173
+ });
174
+ const [activeIndex, setActiveIndex] = React.useState(0);
175
+ const childrenArray = React.Children.toArray(children);
176
+ const systemProps = selectProps({ ...rest,
177
+ accessibilityRole,
178
+ accessibilityLabel,
179
+ accessibilityValue: {
180
+ min: 1,
181
+ max: childrenArray.length,
182
+ now: activeIndex + 1
183
+ }
184
+ });
185
+ const {
186
+ reduceMotionEnabled
187
+ } = useA11yInfo();
188
+ const [containerLayout, setContainerLayout] = React.useState({
189
+ x: 0,
190
+ y: 0,
191
+ width: 0
192
+ });
193
+ const [previousNextNavigationButtonWidth, setPreviousNextNavigationButtonWidth] = React.useState(0);
194
+ const pan = React.useRef(new Animated.ValueXY()).current;
195
+ const animatedX = React.useRef(0);
196
+ const animatedY = React.useRef(0);
197
+ const isFirstSlide = !activeIndex;
198
+ const isLastSlide = activeIndex + 1 >= children.length;
199
+ const panelNavigationTokens = { ...staticTokens.stepTracker,
200
+ containerPaddingTop: spaceBetweenSlideAndPanelNavigation
201
+ };
202
+
203
+ const onContainerLayout = _ref2 => {
204
+ let {
205
+ nativeEvent: {
206
+ layout: {
207
+ x,
208
+ y,
209
+ width
210
+ }
211
+ }
212
+ } = _ref2;
213
+ return setContainerLayout(prevState => ({ ...prevState,
214
+ x,
215
+ y,
216
+ width
217
+ }));
218
+ };
219
+
220
+ const onPreviousNextNavigationButtonLayout = _ref3 => {
221
+ let {
222
+ nativeEvent: {
223
+ layout: {
224
+ width
225
+ }
226
+ }
227
+ } = _ref3;
228
+ return setPreviousNextNavigationButtonWidth(width);
229
+ };
230
+
231
+ const updateOffset = React.useCallback(() => {
232
+ animatedX.current = containerLayout.width * activeIndex * -1;
233
+ animatedY.current = 0;
234
+ pan.setOffset({
235
+ x: animatedX.current,
236
+ y: animatedY.current
237
+ });
238
+ pan.setValue({
239
+ x: 0,
240
+ y: 0
241
+ });
242
+ }, [activeIndex, containerLayout.width, pan, animatedX]);
243
+ const animate = React.useCallback(toValue => {
244
+ if (reduceMotionEnabled) {
245
+ Animated.timing(pan, {
246
+ toValue,
247
+ duration: 1,
248
+ useNativeDriver: false
249
+ }).start();
250
+ } else {
251
+ Animated.spring(pan, { ...springConfig,
252
+ toValue,
253
+ useNativeDriver: false
254
+ }).start();
255
+ }
256
+ }, [pan, springConfig, reduceMotionEnabled]);
257
+ const updateIndex = React.useCallback(function () {
258
+ let delta = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
259
+ const toValue = {
260
+ x: 0,
261
+ y: 0
262
+ };
263
+ let skipChanges = !delta;
264
+ let calcDelta = delta;
265
+
266
+ if (activeIndex <= 0 && delta < 0) {
267
+ skipChanges = true;
268
+ calcDelta = children.length + delta;
269
+ } else if (activeIndex + 1 >= children.length && delta > 0) {
270
+ skipChanges = true;
271
+ calcDelta = -1 * activeIndex + delta - 1;
272
+ }
273
+
274
+ if (skipChanges) {
275
+ animate(toValue);
276
+ return calcDelta;
277
+ }
278
+
279
+ const index = activeIndex + calcDelta;
280
+ setActiveIndex(index);
281
+ toValue.x = containerLayout.width * -1 * calcDelta;
282
+ animate(toValue);
283
+ if (onIndexChanged) onIndexChanged(calcDelta);
284
+ if (onAnimationEnd) onAnimationEnd(index);
285
+ return calcDelta;
286
+ }, [containerLayout.width, activeIndex, animate, children.length, onIndexChanged, onAnimationEnd]);
287
+ const fixOffsetAndGo = React.useCallback(delta => {
288
+ updateOffset();
289
+ if (onAnimationStart) onAnimationStart(activeIndex);
290
+ updateIndex(delta);
291
+ }, [updateIndex, updateOffset, activeIndex, onAnimationStart]);
292
+ const goToNeighboring = React.useCallback(function () {
293
+ let toPrev = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
294
+ fixOffsetAndGo(toPrev ? -1 : 1);
295
+ }, [fixOffsetAndGo]);
296
+ const isSwipeAllowed = React.useCallback(() => {
297
+ if (Platform.OS === 'web') {
298
+ return !!(viewport === 'xs' || viewport === 'sm');
299
+ }
300
+
301
+ return true;
302
+ }, [viewport]);
303
+ const panResponder = React.useMemo(() => PanResponder.create({
304
+ onPanResponderTerminationRequest: () => false,
305
+ onMoveShouldSetResponderCapture: () => true,
306
+ onMoveShouldSetPanResponderCapture: (_, gestureState) => {
307
+ if (!isSwipeAllowed()) {
308
+ return false;
309
+ }
310
+
311
+ if (onAnimationStart) onAnimationStart(activeIndex);
312
+ return Math.abs(gestureState.dx) > minDistanceToCapture;
313
+ },
314
+ onPanResponderGrant: () => updateOffset(),
315
+ onPanResponderMove: Animated.event([null, {
316
+ dx: pan.x
317
+ }], {
318
+ useNativeDriver: false
319
+ }),
320
+ onPanResponderRelease: (_, gesture) => {
321
+ const correction = gesture.moveX - gesture.x0;
322
+
323
+ if (Math.abs(correction) < containerLayout.width * minDistanceForAction) {
324
+ animate({
325
+ x: 0,
326
+ y: 0
327
+ });
328
+ } else {
329
+ const delta = correction > 0 ? -1 : 1;
330
+ updateIndex(delta);
331
+ }
332
+ }
333
+ }), [containerLayout.width, updateIndex, updateOffset, animate, isSwipeAllowed, activeIndex, minDistanceForAction, onAnimationStart, minDistanceToCapture, pan.x]);
334
+ React.useEffect(() => {
335
+ pan.x.addListener(_ref4 => {
336
+ let {
337
+ value
338
+ } = _ref4;
339
+ animatedX.current = value;
340
+ });
341
+ pan.y.addListener(_ref5 => {
342
+ let {
343
+ value
344
+ } = _ref5;
345
+ animatedY.current = value;
346
+ });
347
+ return () => {
348
+ pan.x.removeAllListeners();
349
+ pan.y.removeAllListeners();
350
+ };
351
+ }, [pan.x, pan.y]);
352
+ const goToNext = React.useCallback(() => {
353
+ goToNeighboring();
354
+ }, [goToNeighboring]);
355
+ const goToPrev = React.useCallback(() => {
356
+ goToNeighboring(true);
357
+ }, [goToNeighboring]);
358
+ const goTo = React.useCallback(function () {
359
+ let index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
360
+ const delta = index - activeIndex;
361
+
362
+ if (delta) {
363
+ fixOffsetAndGo(delta);
364
+ }
365
+ }, [fixOffsetAndGo, activeIndex]); // @TODO: - these are Allium-theme variants and won't have any effect in themes that don't implement them.
366
+ // Normally we avoid setting variants of subcomponents, however this could be re-considered.
367
+ // Related discussion - https://github.com/telus/universal-design-system/issues/1549
368
+
369
+ const previousNextIconButtonVariants = {
370
+ size: previousNextIconSize,
371
+ raised: true
372
+ };
373
+ return /*#__PURE__*/_jsxs(CarouselProvider, {
374
+ activeIndex: activeIndex,
375
+ totalItems: childrenArray.length,
376
+ width: containerLayout.width,
377
+ goTo: goTo,
378
+ children: [/*#__PURE__*/_jsxs(View, {
379
+ style: staticStyles.root,
380
+ onLayout: onContainerLayout,
381
+ ref: ref,
382
+ ...systemProps,
383
+ children: [showPreviousNextNavigation && /*#__PURE__*/_jsx(View, {
384
+ style: selectPreviousNextNavigationButtonStyles(previousNextNavigationButtonWidth, previousNextNavigationPosition, spaceBetweenSlideAndPreviousNextNavigation, isFirstSlide, isLastSlide, true),
385
+ testID: "previous-button-container",
386
+ children: /*#__PURE__*/_jsx(IconButton, {
387
+ onLayout: onPreviousNextNavigationButtonLayout,
388
+ icon: previousIcon,
389
+ onPress: goToPrev,
390
+ variant: previousNextIconButtonVariants,
391
+ accessibilityLabel: "previous-button"
392
+ })
393
+ }), /*#__PURE__*/_jsx(View, {
394
+ style: selectContainerStyles(containerLayout.width),
395
+ children: /*#__PURE__*/_jsx(Animated.View, {
396
+ style: StyleSheet.flatten([selectSwipeAreaStyles(children.length, containerLayout.width), {
397
+ transform: [{
398
+ translateX: pan.x
399
+ }, {
400
+ translateY: pan.y
401
+ }]
402
+ }]),
403
+ ...panResponder.panHandlers,
404
+ children: childrenArray.map((element, index) => {
405
+ const clonedElement = /*#__PURE__*/React.cloneElement(element, {
406
+ elementIndex: index
407
+ });
408
+ return /*#__PURE__*/_jsx(React.Fragment, {
409
+ children: clonedElement
410
+ }, index.toFixed(2));
411
+ })
412
+ })
413
+ }), showPreviousNextNavigation && /*#__PURE__*/_jsx(View, {
414
+ style: selectPreviousNextNavigationButtonStyles(previousNextNavigationButtonWidth, previousNextNavigationPosition, spaceBetweenSlideAndPreviousNextNavigation, isFirstSlide, isLastSlide, false),
415
+ testID: "next-button-container",
416
+ children: /*#__PURE__*/_jsx(IconButton, {
417
+ onLayout: onPreviousNextNavigationButtonLayout,
418
+ icon: nextIcon,
419
+ onPress: goToNext,
420
+ variant: previousNextIconButtonVariants,
421
+ accessibilityLabel: "next-button"
422
+ })
423
+ })]
424
+ }), showPanelNavigation ? /*#__PURE__*/_jsx(StackView, {
425
+ direction: "row",
426
+ tokens: staticTokens.stackView,
427
+ children: onRenderPanelNavigation ? onRenderPanelNavigation({
428
+ activeIndex,
429
+ totalItems: childrenArray.length
430
+ }) : /*#__PURE__*/_jsx(StepTracker, {
431
+ current: activeIndex,
432
+ steps: childrenArray.map((_, index) => String(index)),
433
+ dictionary: panelNavigationTextDictionary,
434
+ tokens: panelNavigationTokens
435
+ })
436
+ }) : null]
437
+ });
438
+ });
439
+ Carousel.propTypes = { ...selectedSystemPropTypes,
440
+ tokens: getTokensPropType('Carousel'),
441
+ variant: variantProp.propType,
442
+
443
+ /**
444
+ * Slides to render in Carousel. Wrap individual slides in `Carousel.Item`
445
+ */
446
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
447
+
448
+ /**
449
+ * `inside` renders the previous and next buttons inside the slide
450
+ * `outside` renders the previous and next buttons outside the slide
451
+ * `edge` renders the previous and next buttons at the edge of the slide
452
+ */
453
+ previousNextNavigationPosition: PropTypes.oneOf(['inside', 'outside', 'edge']),
454
+
455
+ /**
456
+ * Defines the size of the `IconButton` which is being used to render next and previous buttons
457
+ */
458
+ previousNextIconSize: PropTypes.oneOf(['default', 'small', 'large']),
459
+
460
+ /**
461
+ * Carousel uses `Animated.spring` to animate slide changes, use this option to pass custom animation configuration
462
+ */
463
+ springConfig: PropTypes.object,
464
+
465
+ /**
466
+ * Minimal part of slide width must be swiped for changing index.
467
+ * Otherwise animation restore current slide. Default value 0.2 means that 20% must be swiped for change index
468
+ */
469
+ minDistanceForAction: PropTypes.number,
470
+
471
+ /**
472
+ * Initiate animation after swipe this distance.
473
+ */
474
+ minDistanceToCapture: PropTypes.number,
475
+
476
+ /**
477
+ * Called when active index changed
478
+ * This function is also provided with a parameter indicating changed index (either 1, or -1)
479
+ * Use it as follows:
480
+ * ```js
481
+ * const onIndexChangedCallback = React.useCallback((changedIndex) => {
482
+ * console.log(changedIndex)
483
+ * }, []) // pass local dependencies as per your component
484
+ * <Carousel
485
+ * onIndexChanged={onIndexChangedCallback}
486
+ * >
487
+ * <Carousel.Item>First Slide</Carousel.Item>
488
+ * </Carousel>
489
+ * ```
490
+ * Caution: Always consider wrapping your callback for `onIndexChanged` in `useCallback` in order to avoid bugs and performance issues
491
+ */
492
+ onIndexChanged: PropTypes.func,
493
+
494
+ /**
495
+ * Use this to render a custom panel navigation element instead of dots navigation
496
+ * This function is also provided with an object with the following properties
497
+ * activeIndex: index of current slide
498
+ * totalItems: total number of slides
499
+ * Use it as follows:
500
+ * ```js
501
+ * <Carousel
502
+ * onRenderPanelNavigation={({ totalItems, activeIndex }) => <Text>Showing {activeIndex + 1}</Text>}
503
+ * >
504
+ * <Carousel.Item>First Slide</Carousel.Item>
505
+ * </Carousel>
506
+ * ```
507
+ */
508
+ onRenderPanelNavigation: PropTypes.func,
509
+
510
+ /**
511
+ * When slide animation start
512
+ * This function is also provided with a parameter indicating the current slide index before animation starts
513
+ * Use it as follows:
514
+ * ```js
515
+ * const onAnimationStartCallback = React.useCallback((currentIndex) => {
516
+ * console.log(currentIndex)
517
+ * }, []) // pass local dependencies as per your component
518
+ * <Carousel
519
+ * onAnimationStart={onAnimationStartCallback}
520
+ * >
521
+ * <Carousel.Item>First Slide</Carousel.Item>
522
+ * </Carousel>
523
+ * ```
524
+ * Caution: Always consider wrapping your callback for `onAnimationStart` in `useCallback` in order to avoid bugs and performance issues
525
+ */
526
+ onAnimationStart: PropTypes.func,
527
+
528
+ /**
529
+ * When slide animation end with parameter of current index (after animation ends)
530
+ * This function is also provided with a parameter indicating the updated slide index after animation ends
531
+ * Use it as follows:
532
+ * ```js
533
+ * const onAnimationEndCallback = React.useCallback((changedIndex) => {
534
+ * console.log(changedIndex)
535
+ * }, []) // pass local dependencies as per your component
536
+ * <Carousel
537
+ * onAnimationEnd={onAnimationEndCallback}
538
+ * >
539
+ * <Carousel.Item>First Slide</Carousel.Item>
540
+ * </Carousel>
541
+ * ```
542
+ * Caution: Always consider wrapping your callback for `onAnimationEnd` in `useCallback` in order to avoid bugs and performance issues
543
+ */
544
+ onAnimationEnd: PropTypes.func,
545
+
546
+ /**
547
+ * Use this to override the default text for panel navigation
548
+ */
549
+ panelNavigationTextDictionary: PropTypes.shape({
550
+ en: PropTypes.shape({
551
+ stepTrackerLabel: PropTypes.string.isRequired
552
+ }),
553
+ fr: PropTypes.shape({
554
+ stepTrackerLabel: PropTypes.string.isRequired
555
+ })
556
+ }),
557
+
558
+ /**
559
+ * Provide custom accessibilityRole for Carousel container
560
+ */
561
+ accessibilityRole: PropTypes.string,
562
+
563
+ /**
564
+ * Provide custom accessibilityLabel for Carousel container
565
+ */
566
+ accessibilityLabel: PropTypes.string
567
+ };
568
+ Carousel.Item = CarouselItem;
569
+ Carousel.displayName = 'Carousel';
570
+ export default Carousel;
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { jsx as _jsx } from "react/jsx-runtime";
4
+ const CarouselContext = /*#__PURE__*/React.createContext();
5
+
6
+ const CarouselProvider = _ref => {
7
+ let {
8
+ children,
9
+ activeIndex,
10
+ totalItems,
11
+ width,
12
+ goTo
13
+ } = _ref;
14
+ const value = React.useMemo(() => ({
15
+ activeIndex,
16
+ totalItems,
17
+ width,
18
+ goTo
19
+ }), [activeIndex, totalItems, width, goTo]);
20
+ return /*#__PURE__*/_jsx(CarouselContext.Provider, {
21
+ value: value,
22
+ children: children
23
+ });
24
+ };
25
+
26
+ function useCarousel() {
27
+ const context = React.useContext(CarouselContext);
28
+
29
+ if (context === undefined) {
30
+ throw new Error("'useCarousel' must be used within a 'CarouselProvider'");
31
+ }
32
+
33
+ return context;
34
+ }
35
+
36
+ CarouselProvider.propTypes = {
37
+ children: PropTypes.arrayOf(PropTypes.element).isRequired,
38
+ activeIndex: PropTypes.number.isRequired,
39
+ totalItems: PropTypes.number.isRequired,
40
+ width: PropTypes.number.isRequired,
41
+ goTo: PropTypes.func.isRequired
42
+ };
43
+ export { CarouselProvider, useCarousel };
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import View from "react-native-web/dist/exports/View";
4
+ import Platform from "react-native-web/dist/exports/Platform";
5
+ import { selectSystemProps, a11yProps, viewProps } from '../../utils';
6
+ import { useCarousel } from '../CarouselContext';
7
+ import { jsx as _jsx } from "react/jsx-runtime";
8
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
9
+ /**
10
+ * `Carousel.Item` is used to wrap the content of an individual slide and is suppsoed to be the
11
+ * only top-level component passed to the `Carousel`
12
+ */
13
+
14
+ const CarouselItem = _ref => {
15
+ let {
16
+ children,
17
+ elementIndex,
18
+ ...rest
19
+ } = _ref;
20
+ const {
21
+ width,
22
+ activeIndex,
23
+ totalItems
24
+ } = useCarousel();
25
+ const selectedProps = selectProps({ ...rest,
26
+ // `group` role crashes the app on Android so setting it to `none` for Android
27
+ accessibilityRole: Platform.OS === 'android' ? 'none' : 'group',
28
+ accessibilityLabel: "Showing ".concat(elementIndex + 1, " of ").concat(totalItems)
29
+ });
30
+ const focusabilityProps = activeIndex === elementIndex ? {} : a11yProps.nonFocusableProps;
31
+ return /*#__PURE__*/_jsx(View, {
32
+ style: {
33
+ width
34
+ },
35
+ ...selectedProps,
36
+ ...focusabilityProps,
37
+ children: children
38
+ });
39
+ };
40
+
41
+ CarouselItem.propTypes = { ...selectedSystemPropTypes,
42
+
43
+ /**
44
+ * Index of the current slide
45
+ * Don't pass this prop when using `Carousel.Item` as it is already being passed by `Carousel` top-level component
46
+ */
47
+ elementIndex: PropTypes.number,
48
+
49
+ /**
50
+ * Provide custom accessibilityLabelledBy for Carousel slide
51
+ */
52
+ accessibilityLabelledBy: PropTypes.string,
53
+
54
+ /**
55
+ * Content of the slide
56
+ */
57
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired
58
+ };
59
+ CarouselItem.displayName = 'Carousel.Item';
60
+ export default CarouselItem;
@@ -0,0 +1,2 @@
1
+ import CarouselItem from './CarouselItem';
2
+ export default CarouselItem;
@@ -0,0 +1,2 @@
1
+ export * from './CarouselContext';
2
+ export { default as Carousel } from './Carousel';
@@ -109,7 +109,10 @@ const StepTracker = /*#__PURE__*/forwardRef((_ref4, ref) => {
109
109
  dictionary,
110
110
  copy
111
111
  });
112
- const stepTrackerLabel = getCopy('stepTrackerLabel').replace('%{stepNumber}', current < steps.length ? current + 1 : steps.length).replace('%{stepCount}', steps.length).replace('%{stepLabel}', current < steps.length ? steps[current] : steps[steps.length - 1]);
112
+ const stepTrackerLabel = showStepTrackerLabel ? getCopy('stepTrackerLabel').replace('%{stepNumber}', current < steps.length ? current + 1 : steps.length).replace('%{stepCount}', steps.length).replace('%{stepLabel}', current < steps.length ? steps[current] : steps[steps.length - 1]) : '';
113
+
114
+ const getStepLabel = index => themeTokens.showStepLabel ? getCopy('stepLabel').replace('%{stepNumber}', index + 1) : '';
115
+
113
116
  if (!steps.length) return null;
114
117
  const selectedProps = selectProps({
115
118
  accessibilityLabel: stepTrackerLabel,
@@ -135,7 +138,7 @@ const StepTracker = /*#__PURE__*/forwardRef((_ref4, ref) => {
135
138
  return /*#__PURE__*/_jsx(Step, {
136
139
  status: current,
137
140
  label: label,
138
- name: getCopy('stepLabel').replace('%{stepNumber}', index + 1),
141
+ name: getStepLabel(index),
139
142
  stepIndex: index,
140
143
  stepCount: steps.length,
141
144
  tokens: themeTokens