@telus-uds/components-base 1.12.0 → 1.14.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.
Files changed (132) hide show
  1. package/CHANGELOG.md +41 -2
  2. package/component-docs.json +933 -55
  3. package/lib/BaseProvider/index.js +7 -2
  4. package/lib/Button/ButtonBase.js +52 -19
  5. package/lib/Button/ButtonGroup.js +7 -0
  6. package/lib/Button/propTypes.js +18 -0
  7. package/lib/Carousel/Carousel.js +83 -58
  8. package/lib/Carousel/CarouselContext.js +22 -8
  9. package/lib/Carousel/CarouselFirstFocus/CarouselFirstFocus.js +73 -0
  10. package/lib/Carousel/CarouselStepTracker/CarouselStepTracker.js +56 -0
  11. package/lib/Carousel/CarouselStepTracker/index.js +13 -0
  12. package/lib/Carousel/CarouselTabs/CarouselTabs.js +70 -0
  13. package/lib/Carousel/CarouselTabs/CarouselTabsPanel.js +95 -0
  14. package/lib/Carousel/CarouselTabs/CarouselTabsPanelItem.js +148 -0
  15. package/lib/Carousel/CarouselTabs/index.js +13 -0
  16. package/lib/Carousel/CarouselThumbnail.js +99 -0
  17. package/lib/Carousel/CarouselThumbnailNavigation.js +87 -0
  18. package/lib/Carousel/dictionary.js +4 -2
  19. package/lib/Carousel/index.js +10 -1
  20. package/lib/Checkbox/Checkbox.js +7 -3
  21. package/lib/Checkbox/CheckboxGroup.js +8 -1
  22. package/lib/Feedback/Feedback.js +18 -10
  23. package/lib/Icon/IconText.js +6 -1
  24. package/lib/InputLabel/InputLabel.js +11 -5
  25. package/lib/Link/InlinePressable.js +1 -8
  26. package/lib/Link/LinkBase.js +13 -10
  27. package/lib/List/ListItem.js +8 -4
  28. package/lib/Notification/Notification.js +44 -24
  29. package/lib/Pagination/Pagination.js +7 -3
  30. package/lib/Radio/RadioGroup.js +8 -0
  31. package/lib/RadioCard/RadioCard.js +6 -1
  32. package/lib/RadioCard/RadioCardGroup.js +7 -0
  33. package/lib/Select/Select.js +7 -3
  34. package/lib/SkipLink/SkipLink.js +216 -0
  35. package/lib/SkipLink/index.js +13 -0
  36. package/lib/StepTracker/Step.js +8 -4
  37. package/lib/StepTracker/StepTracker.js +7 -3
  38. package/lib/Tabs/TabsItem.js +4 -0
  39. package/lib/TextInput/TextInputBase.js +7 -3
  40. package/lib/ThemeProvider/ThemeProvider.js +25 -3
  41. package/lib/ThemeProvider/utils/styles.js +8 -1
  42. package/lib/ThemeProvider/utils/theme-tokens.js +1 -1
  43. package/lib/ToggleSwitch/ToggleSwitchGroup.js +7 -0
  44. package/lib/Typography/Typography.js +6 -2
  45. package/lib/index.js +9 -0
  46. package/lib-module/BaseProvider/index.js +7 -2
  47. package/lib-module/Button/ButtonBase.js +41 -9
  48. package/lib-module/Button/ButtonGroup.js +7 -0
  49. package/lib-module/Button/propTypes.js +17 -0
  50. package/lib-module/Carousel/Carousel.js +80 -57
  51. package/lib-module/Carousel/CarouselContext.js +21 -8
  52. package/lib-module/Carousel/CarouselFirstFocus/CarouselFirstFocus.js +51 -0
  53. package/lib-module/Carousel/CarouselStepTracker/CarouselStepTracker.js +42 -0
  54. package/lib-module/Carousel/CarouselStepTracker/index.js +2 -0
  55. package/lib-module/Carousel/CarouselTabs/CarouselTabs.js +50 -0
  56. package/lib-module/Carousel/CarouselTabs/CarouselTabsPanel.js +76 -0
  57. package/lib-module/Carousel/CarouselTabs/CarouselTabsPanelItem.js +126 -0
  58. package/lib-module/Carousel/CarouselTabs/index.js +2 -0
  59. package/lib-module/Carousel/CarouselThumbnail.js +85 -0
  60. package/lib-module/Carousel/CarouselThumbnailNavigation.js +66 -0
  61. package/lib-module/Carousel/dictionary.js +4 -2
  62. package/lib-module/Carousel/index.js +2 -1
  63. package/lib-module/Checkbox/Checkbox.js +8 -4
  64. package/lib-module/Checkbox/CheckboxGroup.js +8 -1
  65. package/lib-module/Feedback/Feedback.js +19 -11
  66. package/lib-module/Icon/IconText.js +6 -1
  67. package/lib-module/InputLabel/InputLabel.js +12 -6
  68. package/lib-module/Link/InlinePressable.js +1 -8
  69. package/lib-module/Link/LinkBase.js +14 -11
  70. package/lib-module/List/ListItem.js +9 -5
  71. package/lib-module/Notification/Notification.js +46 -26
  72. package/lib-module/Pagination/Pagination.js +8 -4
  73. package/lib-module/Radio/RadioGroup.js +8 -0
  74. package/lib-module/RadioCard/RadioCard.js +7 -2
  75. package/lib-module/RadioCard/RadioCardGroup.js +7 -0
  76. package/lib-module/Select/Select.js +8 -4
  77. package/lib-module/SkipLink/SkipLink.js +188 -0
  78. package/lib-module/SkipLink/index.js +2 -0
  79. package/lib-module/StepTracker/Step.js +9 -5
  80. package/lib-module/StepTracker/StepTracker.js +8 -4
  81. package/lib-module/Tabs/TabsItem.js +5 -1
  82. package/lib-module/TextInput/TextInputBase.js +8 -4
  83. package/lib-module/ThemeProvider/ThemeProvider.js +24 -3
  84. package/lib-module/ThemeProvider/utils/styles.js +8 -1
  85. package/lib-module/ThemeProvider/utils/theme-tokens.js +1 -1
  86. package/lib-module/ToggleSwitch/ToggleSwitchGroup.js +7 -0
  87. package/lib-module/Typography/Typography.js +7 -3
  88. package/lib-module/index.js +1 -0
  89. package/package.json +46 -47
  90. package/src/BaseProvider/index.jsx +6 -3
  91. package/src/Button/ButtonBase.jsx +36 -12
  92. package/src/Button/ButtonGroup.jsx +6 -0
  93. package/src/Button/propTypes.js +14 -0
  94. package/src/Carousel/Carousel.jsx +91 -64
  95. package/src/Carousel/CarouselContext.jsx +29 -5
  96. package/src/Carousel/CarouselFirstFocus/CarouselFirstFocus.jsx +49 -0
  97. package/src/Carousel/CarouselStepTracker/CarouselStepTracker.jsx +36 -0
  98. package/src/Carousel/CarouselStepTracker/index.js +3 -0
  99. package/src/Carousel/CarouselTabs/CarouselTabs.jsx +37 -0
  100. package/src/Carousel/CarouselTabs/CarouselTabsPanel.jsx +69 -0
  101. package/src/Carousel/CarouselTabs/CarouselTabsPanelItem.jsx +119 -0
  102. package/src/Carousel/CarouselTabs/index.js +3 -0
  103. package/src/Carousel/CarouselThumbnail.jsx +77 -0
  104. package/src/Carousel/CarouselThumbnailNavigation.jsx +53 -0
  105. package/src/Carousel/dictionary.js +4 -2
  106. package/src/Carousel/index.js +1 -0
  107. package/src/Checkbox/Checkbox.jsx +14 -11
  108. package/src/Checkbox/CheckboxGroup.jsx +8 -1
  109. package/src/Feedback/Feedback.jsx +14 -7
  110. package/src/Icon/IconText.jsx +3 -1
  111. package/src/InputLabel/InputLabel.jsx +13 -12
  112. package/src/Link/InlinePressable.jsx +2 -8
  113. package/src/Link/LinkBase.jsx +18 -21
  114. package/src/List/ListItem.jsx +10 -5
  115. package/src/Notification/Notification.jsx +40 -23
  116. package/src/Pagination/Pagination.jsx +6 -4
  117. package/src/Radio/RadioGroup.jsx +7 -0
  118. package/src/RadioCard/RadioCard.jsx +3 -2
  119. package/src/RadioCard/RadioCardGroup.jsx +6 -0
  120. package/src/Select/Select.jsx +12 -3
  121. package/src/SkipLink/SkipLink.jsx +179 -0
  122. package/src/SkipLink/index.js +3 -0
  123. package/src/StepTracker/Step.jsx +12 -4
  124. package/src/StepTracker/StepTracker.jsx +11 -10
  125. package/src/Tabs/TabsItem.jsx +3 -2
  126. package/src/TextInput/TextInputBase.jsx +11 -3
  127. package/src/ThemeProvider/ThemeProvider.jsx +22 -3
  128. package/src/ThemeProvider/utils/styles.js +9 -1
  129. package/src/ThemeProvider/utils/theme-tokens.js +1 -1
  130. package/src/ToggleSwitch/ToggleSwitchGroup.jsx +6 -0
  131. package/src/Typography/Typography.jsx +11 -12
  132. package/src/index.js +1 -0
@@ -26,6 +26,7 @@ const ButtonGroup = /*#__PURE__*/forwardRef((_ref, ref) => {
26
26
  legend,
27
27
  tooltip,
28
28
  hint,
29
+ hintPosition = 'inline',
29
30
  validation,
30
31
  feedback,
31
32
  name: inputGroupName,
@@ -77,6 +78,7 @@ const ButtonGroup = /*#__PURE__*/forwardRef((_ref, ref) => {
77
78
  legend: legend,
78
79
  tooltip: tooltip,
79
80
  hint: hint,
81
+ hintPosition: hintPosition,
80
82
  space: fieldSpace,
81
83
  feedback: feedback,
82
84
  readOnly: readOnly,
@@ -208,6 +210,11 @@ ButtonGroup.propTypes = { ...selectedSystemPropTypes,
208
210
  */
209
211
  hint: PropTypes.string,
210
212
 
213
+ /**
214
+ * Position of the hint relative to label. Use `below` to display a larger hint below the label.
215
+ */
216
+ hintPosition: PropTypes.oneOf(['inline', 'below']),
217
+
211
218
  /**
212
219
  * Optional tooltip text content to include alongside the legend and hint.
213
220
  */
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
2
2
  import ABBPropTypes from 'airbnb-prop-types';
3
3
  import { variantProp, getTokensPropType } from '../utils/props';
4
4
  import A11yText from '../A11yText';
5
+ import { iconComponentPropTypes } from '../Icon';
5
6
  export const textAndA11yText = ABBPropTypes.childrenOf(PropTypes.oneOfType([ABBPropTypes.elementType(A11yText), PropTypes.string]));
6
7
  const buttonPropTypes = {
7
8
  tokens: getTokensPropType('Button'),
@@ -31,6 +32,22 @@ const buttonPropTypes = {
31
32
  * Function called when the button is pressed. Required unless the button has a href.
32
33
  */
33
34
  onPress: PropTypes.func,
35
+
36
+ /**
37
+ * Optional variant that may be passed down to the link's icon if there is one
38
+ */
39
+ iconProps: PropTypes.exact(iconComponentPropTypes),
40
+
41
+ /**
42
+ * When `icon` is provided, use `iconPosition` to place the Icon to the left or right side of the button.
43
+ */
44
+ iconPosition: PropTypes.oneOf(['left', 'right']),
45
+
46
+ /**
47
+ * A function component for an SVG icon to render inside the link. Inherits size and color from
48
+ * the link and any Typography the link is nested inside.
49
+ */
50
+ icon: PropTypes.func,
34
51
  variant: variantProp.propType
35
52
  };
36
53
  export default buttonPropTypes;
@@ -7,13 +7,15 @@ import Platform from "react-native-web/dist/exports/Platform";
7
7
  import PropTypes from 'prop-types';
8
8
  import { useThemeTokens } from '../ThemeProvider';
9
9
  import { useViewport } from '../ViewportProvider';
10
- import { getTokensPropType, getA11yPropsFromHtmlTag, layoutTags, variantProp, selectSystemProps, a11yProps, viewProps, useCopy } from '../utils';
10
+ import { getTokensPropType, getA11yPropsFromHtmlTag, layoutTags, variantProp, selectSystemProps, a11yProps, viewProps, useCopy, unpackFragment } from '../utils';
11
11
  import { useA11yInfo } from '../A11yInfoProvider';
12
12
  import { CarouselProvider } from './CarouselContext';
13
13
  import CarouselItem from './CarouselItem';
14
- import StepTracker from '../StepTracker';
15
- import StackView from '../StackView';
16
14
  import IconButton from '../IconButton';
15
+ import SkipLink from '../SkipLink';
16
+ import A11yText from '../A11yText';
17
+ import CarouselStepTracker from './CarouselStepTracker';
18
+ import CarouselThumbnailNavigation from './CarouselThumbnailNavigation';
17
19
  import dictionary from './dictionary';
18
20
  import { jsx as _jsx } from "react/jsx-runtime";
19
21
  import { jsxs as _jsxs } from "react/jsx-runtime";
@@ -27,18 +29,6 @@ const staticStyles = StyleSheet.create({
27
29
  left: 0
28
30
  }
29
31
  });
30
- const staticTokens = {
31
- stackView: {
32
- justifyContent: 'center'
33
- },
34
- stepTracker: {
35
- showStepLabel: false,
36
- showStepTrackerLabel: true,
37
- knobCompletedBackgroundColor: 'none',
38
- connectorCompletedColor: 'none',
39
- connectorColor: 'none'
40
- }
41
- };
42
32
 
43
33
  const selectContainerStyles = width => ({
44
34
  backgroundColor: 'transparent',
@@ -147,25 +137,32 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
147
137
  onAnimationStart,
148
138
  onAnimationEnd,
149
139
  onIndexChanged,
140
+ skipLinkHref,
141
+ refocus,
142
+ title = 'carousel',
150
143
  springConfig = undefined,
151
- onRenderPanelNavigation,
144
+ thumbnails = undefined,
145
+ panelNavigation = thumbnails ? /*#__PURE__*/_jsx(CarouselThumbnailNavigation, {
146
+ thumbnails: thumbnails
147
+ }) : /*#__PURE__*/_jsx(CarouselStepTracker, {}),
152
148
  tag = 'ul',
153
- accessibilityRole = 'adjustable',
154
- accessibilityLabel = 'carousel',
149
+ accessibilityRole,
150
+ accessibilityLabel = title,
151
+ accessibilityLiveRegion = 'polite',
155
152
  copy,
156
153
  ...rest
157
154
  } = _ref;
158
155
  const viewport = useViewport();
156
+ const themeTokens = useThemeTokens('Carousel', tokens, variant, {
157
+ viewport
158
+ });
159
159
  const {
160
160
  previousIcon,
161
161
  nextIcon,
162
162
  showPreviousNextNavigation,
163
163
  showPanelNavigation,
164
- spaceBetweenSlideAndPreviousNextNavigation,
165
- spaceBetweenSlideAndPanelNavigation
166
- } = useThemeTokens('Carousel', tokens, variant, {
167
- viewport
168
- });
164
+ spaceBetweenSlideAndPreviousNextNavigation
165
+ } = themeTokens;
169
166
  const [activeIndex, setActiveIndex] = React.useState(0);
170
167
  const [isAnimating, setIsAnimating] = React.useState(false);
171
168
  const handleAnimationStart = React.useCallback(function () {
@@ -180,7 +177,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
180
177
  dictionary,
181
178
  copy
182
179
  });
183
- const childrenArray = React.Children.toArray(children);
180
+ const childrenArray = unpackFragment(children);
184
181
  const systemProps = selectProps({ ...rest,
185
182
  accessibilityRole,
186
183
  accessibilityLabel,
@@ -199,14 +196,12 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
199
196
  width: 0
200
197
  });
201
198
  const [previousNextNavigationButtonWidth, setPreviousNextNavigationButtonWidth] = React.useState(0);
199
+ const firstFocusRef = React.useRef(null);
202
200
  const pan = React.useRef(new Animated.ValueXY()).current;
203
201
  const animatedX = React.useRef(0);
204
202
  const animatedY = React.useRef(0);
205
203
  const isFirstSlide = !activeIndex;
206
204
  const isLastSlide = activeIndex + 1 >= children.length;
207
- const panelNavigationTokens = { ...staticTokens.stepTracker,
208
- containerPaddingTop: spaceBetweenSlideAndPanelNavigation
209
- };
210
205
 
211
206
  const onContainerLayout = _ref2 => {
212
207
  let {
@@ -301,10 +296,13 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
301
296
  return calcDelta;
302
297
  }, [containerLayout.width, activeIndex, animate, children.length, onIndexChanged]);
303
298
  const fixOffsetAndGo = React.useCallback(delta => {
299
+ var _firstFocusRef$curren;
300
+
304
301
  updateOffset();
305
302
  handleAnimationStart(activeIndex);
306
303
  updateIndex(delta);
307
- }, [updateIndex, updateOffset, activeIndex, handleAnimationStart]);
304
+ if (refocus) (_firstFocusRef$curren = firstFocusRef.current) === null || _firstFocusRef$curren === void 0 ? void 0 : _firstFocusRef$curren.focus();
305
+ }, [updateIndex, updateOffset, activeIndex, handleAnimationStart, refocus]);
308
306
  const goToNeighboring = React.useCallback(function () {
309
307
  let toPrev = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
310
308
  fixOffsetAndGo(toPrev ? -1 : 1);
@@ -386,18 +384,21 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
386
384
  size: previousNextIconSize,
387
385
  raised: true
388
386
  };
389
-
390
- const getCopyWithPlaceholders = copyKey => {
391
- const copyText = getCopy(copyKey).replace(/%\{itemLabel\}/g, itemLabel).replace(/%\{stepNumber\}/g, activeIndex + 1).replace(/%\{stepCount\}/g, childrenArray.length); // First word might be a lowercase placeholder: capitalize the first letter
387
+ const getCopyWithPlaceholders = React.useCallback(copyKey => {
388
+ const copyText = getCopy(copyKey).replace(/%\{title\}/g, title).replace(/%\{itemLabel\}/g, itemLabel).replace(/%\{stepNumber\}/g, activeIndex + 1).replace(/%\{stepCount\}/g, childrenArray.length); // First word might be a lowercase placeholder: capitalize the first letter
392
389
 
393
390
  return "".concat(copyText[0].toUpperCase()).concat(copyText.slice(1));
394
- };
395
-
391
+ }, [activeIndex, childrenArray.length, itemLabel, getCopy, title]);
396
392
  return /*#__PURE__*/_jsxs(CarouselProvider, {
397
393
  activeIndex: activeIndex,
394
+ goTo: goTo,
395
+ getCopyWithPlaceholders: getCopyWithPlaceholders,
396
+ itemLabel: itemLabel,
398
397
  totalItems: childrenArray.length,
398
+ themeTokens: themeTokens,
399
+ firstFocusRef: firstFocusRef,
400
+ refocus: refocus,
399
401
  width: containerLayout.width,
400
- goTo: goTo,
401
402
  children: [/*#__PURE__*/_jsxs(View, {
402
403
  style: staticStyles.root,
403
404
  onLayout: onContainerLayout,
@@ -413,6 +414,17 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
413
414
  variant: previousNextIconButtonVariants,
414
415
  accessibilityLabel: getCopyWithPlaceholders('iconButtonLabel').replace('%{targetStep}', activeIndex)
415
416
  })
417
+ }), Boolean(skipLinkHref) && /*#__PURE__*/_jsx(SkipLink, {
418
+ ref: firstFocusRef,
419
+ href: skipLinkHref,
420
+ children: getCopyWithPlaceholders('skipLink')
421
+ }), /*#__PURE__*/_jsx(A11yText // Read the current slide position to screen readers on slide.
422
+ // If it's set to refocus and doesn't have a SkipLink to focus to, focus this.
423
+ , {
424
+ ref: !skipLinkHref && refocus ? firstFocusRef : null,
425
+ accessibilityLiveRegion: !skipLinkHref && refocus ? undefined : 'polite',
426
+ focusable: !skipLinkHref && refocus,
427
+ text: getCopyWithPlaceholders('stepTrackerLabel')
416
428
  }), /*#__PURE__*/_jsx(View, {
417
429
  style: selectContainerStyles(containerLayout.width),
418
430
  children: /*#__PURE__*/_jsx(Animated.View, {
@@ -425,6 +437,9 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
425
437
  }]),
426
438
  ...panResponder.panHandlers,
427
439
  ...getA11yPropsFromHtmlTag(tag),
440
+ // In iframes on Mac (e.g. in Storybook), this content may be misread or read twice.
441
+ // This is a known Voiceover bug: https://github.com/phetsims/a11y-research/issues/132
442
+ accessibilityLiveRegion: accessibilityLiveRegion,
428
443
  children: childrenArray.map((element, index) => {
429
444
  const hidden = !isAnimating && index !== activeIndex;
430
445
  const clonedElement = /*#__PURE__*/React.cloneElement(element, {
@@ -447,23 +462,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
447
462
  accessibilityLabel: getCopyWithPlaceholders('iconButtonLabel').replace('%{targetStep}', activeIndex + 2)
448
463
  })
449
464
  })]
450
- }), showPanelNavigation ? /*#__PURE__*/_jsx(StackView, {
451
- direction: "row",
452
- tokens: staticTokens.stackView,
453
- children: onRenderPanelNavigation ? onRenderPanelNavigation({
454
- activeIndex,
455
- totalItems: childrenArray.length
456
- }) : /*#__PURE__*/_jsx(StepTracker, {
457
- current: activeIndex,
458
- steps: childrenArray.map((_, index) => String(index)),
459
- copy: {
460
- // Give StepTracker copy from Carousel's language and dictionary
461
- stepLabel: getCopyWithPlaceholders('stepLabel'),
462
- stepTrackerLabel: getCopyWithPlaceholders('stepTrackerLabel')
463
- },
464
- tokens: panelNavigationTokens
465
- })
466
- }) : null]
465
+ }), showPanelNavigation ? panelNavigation : null]
467
466
  });
468
467
  });
469
468
  Carousel.propTypes = { ...selectedSystemPropTypes,
@@ -500,6 +499,15 @@ Carousel.propTypes = { ...selectedSystemPropTypes,
500
499
  */
501
500
  springConfig: PropTypes.object,
502
501
 
502
+ /**
503
+ * An array of objects containing information on the thumbnails to be rendered as navigation panel
504
+ */
505
+ thumbnails: PropTypes.arrayOf(PropTypes.shape({
506
+ accessibilityLabel: PropTypes.string,
507
+ alt: PropTypes.string,
508
+ src: PropTypes.string
509
+ })),
510
+
503
511
  /**
504
512
  * Minimal part of slide width must be swiped for changing index.
505
513
  * Otherwise animation restore current slide. Default value 0.2 means that 20% must be swiped for change index
@@ -530,20 +538,35 @@ Carousel.propTypes = { ...selectedSystemPropTypes,
530
538
  onIndexChanged: PropTypes.func,
531
539
 
532
540
  /**
533
- * Use this to render a custom panel navigation element instead of dots navigation
534
- * This function is also provided with an object with the following properties
535
- * activeIndex: index of current slide
536
- * totalItems: total number of slides
541
+ * If this is a complex carousel with a lot of focusable content, pass a href for a skip link. Typically, this will be an anchor link
542
+ * with the ID of a focusable element immediately after the Carousel, e.g. `'#section-2-heading'`.
543
+ */
544
+ skipLinkHref: PropTypes.string,
545
+
546
+ /**
547
+ * If true, whenever a new slide comes into view, the focus of the Carousel switches to the start.
548
+ *
549
+ * Pass this as true when using carousel items that contain interactive content, so a user can easily tab into that content.
550
+ *
551
+ * If skipLinkHref is passed, the focus target will be the SkipLink; if not, it'll be an empty element before the slide content.
552
+ */
553
+ refocus: PropTypes.bool,
554
+
555
+ /**
556
+ * Use this to render a custom panel navigation element instead of the default StepTracker's based navigation
557
+ * You can make use of `useCarousel` within your custom panel navigation component to hook into various Carousel states such as:
558
+ * - activeIndex: index of current slide
559
+ * - totalItems: total number of slides
537
560
  * Use it as follows:
538
561
  * ```js
539
562
  * <Carousel
540
- * onRenderPanelNavigation={({ totalItems, activeIndex }) => <Text>Showing {activeIndex + 1}</Text>}
563
+ * panelNavigation={<CustomPanelNavigation />}
541
564
  * >
542
565
  * <Carousel.Item>First Slide</Carousel.Item>
543
566
  * </Carousel>
544
567
  * ```
545
568
  */
546
- onRenderPanelNavigation: PropTypes.func,
569
+ panelNavigation: PropTypes.element,
547
570
 
548
571
  /**
549
572
  * When slide animation start
@@ -1,22 +1,31 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
+ import { getTokensPropType } from '../utils';
3
4
  import { jsx as _jsx } from "react/jsx-runtime";
4
5
  const CarouselContext = /*#__PURE__*/React.createContext();
5
6
 
6
7
  const CarouselProvider = _ref => {
7
8
  let {
8
- children,
9
9
  activeIndex,
10
+ children,
11
+ goTo,
12
+ getCopyWithPlaceholders,
13
+ itemLabel,
14
+ refocus = false,
15
+ themeTokens,
10
16
  totalItems,
11
- width,
12
- goTo
17
+ width
13
18
  } = _ref;
14
19
  const value = React.useMemo(() => ({
15
20
  activeIndex,
21
+ goTo,
22
+ getCopyWithPlaceholders,
23
+ itemLabel,
24
+ refocus,
25
+ themeTokens,
16
26
  totalItems,
17
- width,
18
- goTo
19
- }), [activeIndex, totalItems, width, goTo]);
27
+ width
28
+ }), [activeIndex, goTo, getCopyWithPlaceholders, itemLabel, refocus, totalItems, themeTokens, width]);
20
29
  return /*#__PURE__*/_jsx(CarouselContext.Provider, {
21
30
  value: value,
22
31
  children: children
@@ -36,8 +45,12 @@ function useCarousel() {
36
45
  CarouselProvider.propTypes = {
37
46
  children: PropTypes.arrayOf(PropTypes.element).isRequired,
38
47
  activeIndex: PropTypes.number.isRequired,
48
+ goTo: PropTypes.func.isRequired,
49
+ getCopyWithPlaceholders: PropTypes.func.isRequired,
50
+ itemLabel: PropTypes.string.isRequired,
51
+ refocus: PropTypes.bool,
52
+ themeTokens: getTokensPropType('Carousel'),
39
53
  totalItems: PropTypes.number.isRequired,
40
- width: PropTypes.number.isRequired,
41
- goTo: PropTypes.func.isRequired
54
+ width: PropTypes.number.isRequired
42
55
  };
43
56
  export { CarouselProvider, useCarousel };
@@ -0,0 +1,51 @@
1
+ import React, { forwardRef } from 'react';
2
+ import Pressable from "react-native-web/dist/exports/Pressable";
3
+ import Platform from "react-native-web/dist/exports/Platform";
4
+ import StyleSheet from "react-native-web/dist/exports/StyleSheet";
5
+ import { PropTypes } from 'prop-types';
6
+ import { useCarousel } from '../CarouselContext';
7
+ /**
8
+ * Focus target so that when a new slide is shown, the user can tab into
9
+ * its content using the keyboard.
10
+ *
11
+ * @TODO rework this after integrating with SkipLink when available.
12
+ */
13
+
14
+ import { jsx as _jsx } from "react/jsx-runtime";
15
+ const CarouselFirstFocus = /*#__PURE__*/forwardRef((_ref, ref) => {
16
+ let {
17
+ title
18
+ } = _ref;
19
+ const {
20
+ getCopyWithPlaceholders
21
+ } = useCarousel(); // TODO: integrate skip link description if behaving as skip link.
22
+ // Consider moving this content to aria-live area while only the skip link is focused.
23
+
24
+ const accessibilityLabel = "".concat(title, ", ").concat(getCopyWithPlaceholders('stepTrackerLabel'));
25
+ const accessibilityRole = Platform.select({
26
+ web: 'link',
27
+ // The focused item will ultimately be a skip link.
28
+ default: 'button' // 'link' role usually denotes opening browser on Native.
29
+
30
+ });
31
+ return /*#__PURE__*/_jsx(Pressable // TODO: integrate skip link functionality, jump focus to after Carousel
32
+ , {
33
+ onPress: undefined,
34
+ ref: ref,
35
+ accessibilityLabel: accessibilityLabel,
36
+ accessibilityRole: accessibilityRole,
37
+ style: StyleSheet.absoluteFill,
38
+ focusable: true
39
+ });
40
+ });
41
+ CarouselFirstFocus.displayName = 'CarouselFirstFocus';
42
+ CarouselFirstFocus.propTypes = {
43
+ /**
44
+ * Simple description of this carousel for screenreaders, to be read before
45
+ * "{itemLabel} {index} of {count}.
46
+ *
47
+ * For example, "Summer offers" in "Summer offers, offer 1 of 3"
48
+ */
49
+ title: PropTypes.string
50
+ };
51
+ export default CarouselFirstFocus;
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import { useCarousel } from '../CarouselContext';
3
+ import StepTracker from '../../StepTracker';
4
+ import StackView from '../../StackView';
5
+ import { jsx as _jsx } from "react/jsx-runtime";
6
+
7
+ const CarouselStepTracker = () => {
8
+ const {
9
+ activeIndex,
10
+ totalItems,
11
+ getCopyWithPlaceholders,
12
+ themeTokens
13
+ } = useCarousel();
14
+ const stackViewTokens = {
15
+ justifyContent: 'center'
16
+ };
17
+ const stepTrackerTokens = {
18
+ showStepLabel: false,
19
+ showStepTrackerLabel: true,
20
+ knobCompletedBackgroundColor: 'none',
21
+ connectorCompletedColor: 'none',
22
+ connectorColor: 'none',
23
+ containerPaddingTop: themeTokens.spaceBetweenSlideAndPanelNavigation
24
+ };
25
+ const steps = Array.from(Array(totalItems)).map((_, index) => String(index));
26
+ return /*#__PURE__*/_jsx(StackView, {
27
+ direction: "row",
28
+ tokens: stackViewTokens,
29
+ children: /*#__PURE__*/_jsx(StepTracker, {
30
+ current: activeIndex,
31
+ steps: steps,
32
+ copy: {
33
+ // Give StepTracker copy from Carousel's language and dictionary
34
+ stepLabel: getCopyWithPlaceholders('stepLabel'),
35
+ stepTrackerLabel: getCopyWithPlaceholders('stepTrackerLabel')
36
+ },
37
+ tokens: stepTrackerTokens
38
+ })
39
+ });
40
+ };
41
+
42
+ export default CarouselStepTracker;
@@ -0,0 +1,2 @@
1
+ import CarouselStepTracker from './CarouselStepTracker';
2
+ export default CarouselStepTracker;
@@ -0,0 +1,50 @@
1
+ var _CarouselTabs$propTyp, _CarouselTabs$propTyp2;
2
+
3
+ import React, { forwardRef } from 'react';
4
+ import PropTypes from 'prop-types';
5
+ import { useResponsiveProp } from '../../utils';
6
+ import Carousel from '../Carousel';
7
+ import CarouselTabsPanel from './CarouselTabsPanel';
8
+ import { jsx as _jsx } from "react/jsx-runtime";
9
+ const CarouselTabs = /*#__PURE__*/forwardRef((_ref, ref) => {
10
+ let {
11
+ items,
12
+ refocus = true,
13
+ ...carouselProps
14
+ } = _ref;
15
+ const panelNavigation = useResponsiveProp({
16
+ md: /*#__PURE__*/_jsx(CarouselTabsPanel, {
17
+ items: items
18
+ })
19
+ });
20
+ return /*#__PURE__*/_jsx(Carousel, {
21
+ refocus: refocus,
22
+ ...carouselProps,
23
+ ref: ref,
24
+ panelNavigation: panelNavigation,
25
+ children: items.map(_ref2 => {
26
+ let {
27
+ title,
28
+ content
29
+ } = _ref2;
30
+ return /*#__PURE__*/_jsx(React.Fragment, {
31
+ children: content
32
+ }, title);
33
+ })
34
+ });
35
+ });
36
+ CarouselTabs.displayName = 'CarouselTabs';
37
+ CarouselTabs.propTypes = {
38
+ /**
39
+ * An array of objects where title is the string shown in the slide's tab and content is the JSX for the slide itself
40
+ */
41
+ items: PropTypes.arrayOf(PropTypes.shape({
42
+ title: PropTypes.string.isRequired,
43
+ content: PropTypes.node.isRequired
44
+ })),
45
+ ...Carousel.propTypes
46
+ }; // CarouselTabs doesn't require `children` prop, it uses `items` instead.
47
+ // eslint-disable-next-line react/forbid-foreign-prop-types
48
+
49
+ if ((_CarouselTabs$propTyp = CarouselTabs.propTypes) !== null && _CarouselTabs$propTyp !== void 0 && _CarouselTabs$propTyp.children) (_CarouselTabs$propTyp2 = CarouselTabs.propTypes) === null || _CarouselTabs$propTyp2 === void 0 ? true : delete _CarouselTabs$propTyp2.children;
50
+ export default CarouselTabs;
@@ -0,0 +1,76 @@
1
+ import React, { forwardRef, useRef } from 'react';
2
+ import View from "react-native-web/dist/exports/View";
3
+ import PropTypes from 'prop-types';
4
+ import StackView from '../../StackView';
5
+ import { useCarousel } from '../CarouselContext';
6
+ import CarouselTabsPanelItem from './CarouselTabsPanelItem';
7
+ import { jsx as _jsx } from "react/jsx-runtime";
8
+ import { Fragment as _Fragment } from "react/jsx-runtime";
9
+ import { jsxs as _jsxs } from "react/jsx-runtime";
10
+ const CarouselTabsPanel = /*#__PURE__*/forwardRef((_ref, ref) => {
11
+ let {
12
+ items
13
+ } = _ref;
14
+ const {
15
+ activeIndex,
16
+ goTo
17
+ } = useCarousel();
18
+ const nextFocusRef = useRef();
19
+ const firstTabRef = useRef(); // TODO: figure out a better cross-brand way to specify subcomponent variants.
20
+ // For now, this picks an Allium variant, and does nothing in brands that lack it.
21
+ // See similar comment in Carousel and https://github.com/telus/universal-design-system/issues/1549
22
+
23
+ const dividerVariant = {
24
+ decorative: true
25
+ };
26
+ const lastTabSelected = activeIndex === items.length - 1;
27
+ return /*#__PURE__*/_jsxs(_Fragment, {
28
+ children: [/*#__PURE__*/_jsx(View, {
29
+ focusable: true,
30
+ accessible: true,
31
+ onFocus: event => {
32
+ // When user forward-tabs into this section, focus the next tab; if they backwards-tab
33
+ // (shift-tab) back into the carousel content, don't interfere.
34
+ const previousWebFocus = event.relatedTarget;
35
+ if (previousWebFocus !== firstTabRef.current) nextFocusRef.current.focus();
36
+ }
37
+ }), /*#__PURE__*/_jsx(StackView, {
38
+ direction: "row",
39
+ space: 3,
40
+ divider: {
41
+ variant: dividerVariant
42
+ },
43
+ ref: ref,
44
+ children: items.map((_ref2, index) => {
45
+ let {
46
+ title,
47
+ onPress,
48
+ ...panelItemProps
49
+ } = _ref2;
50
+ const selected = index === activeIndex;
51
+ const isNext = index === activeIndex + 1; // Selected item should be always unfocusable and unpressable
52
+
53
+ const handlePress = selected ? undefined : event => {
54
+ if (typeof onPress === 'function') onPress(event, index);
55
+ goTo(index);
56
+ };
57
+ return /*#__PURE__*/_jsx(CarouselTabsPanelItem, {
58
+ ref: isNext && nextFocusRef || index === 0 && firstTabRef || null,
59
+ title: title,
60
+ selected: selected,
61
+ onPress: handlePress,
62
+ ...panelItemProps
63
+ }, title);
64
+ })
65
+ }), /*#__PURE__*/_jsx(View, {
66
+ focusable: true,
67
+ accessible: true,
68
+ ref: lastTabSelected ? nextFocusRef : null
69
+ })]
70
+ });
71
+ });
72
+ CarouselTabsPanel.displayName = 'CarouselTabsPanel';
73
+ CarouselTabsPanel.propTypes = {
74
+ items: PropTypes.arrayOf(PropTypes.shape(CarouselTabsPanelItem.propTypes || {}))
75
+ };
76
+ export default CarouselTabsPanel;