@telus-uds/components-base 1.12.1 → 1.13.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 (69) hide show
  1. package/CHANGELOG.md +20 -2
  2. package/component-docs.json +740 -33
  3. package/lib/Button/ButtonGroup.js +7 -0
  4. package/lib/Carousel/Carousel.js +69 -12
  5. package/lib/Carousel/CarouselContext.js +17 -11
  6. package/lib/Carousel/CarouselFirstFocus/CarouselFirstFocus.js +73 -0
  7. package/lib/Carousel/CarouselTabs/CarouselTabs.js +70 -0
  8. package/lib/Carousel/CarouselTabs/CarouselTabsPanel.js +95 -0
  9. package/lib/Carousel/CarouselTabs/CarouselTabsPanelItem.js +148 -0
  10. package/lib/Carousel/CarouselTabs/index.js +13 -0
  11. package/lib/Carousel/CarouselThumbnail.js +99 -0
  12. package/lib/Carousel/CarouselThumbnailNavigation.js +87 -0
  13. package/lib/Carousel/dictionary.js +4 -2
  14. package/lib/Carousel/index.js +10 -1
  15. package/lib/Checkbox/CheckboxGroup.js +7 -0
  16. package/lib/Link/InlinePressable.js +1 -8
  17. package/lib/Link/LinkBase.js +5 -6
  18. package/lib/Radio/RadioGroup.js +8 -0
  19. package/lib/RadioCard/RadioCardGroup.js +7 -0
  20. package/lib/SkipLink/SkipLink.js +216 -0
  21. package/lib/SkipLink/index.js +13 -0
  22. package/lib/ThemeProvider/ThemeProvider.js +6 -1
  23. package/lib/ToggleSwitch/ToggleSwitchGroup.js +7 -0
  24. package/lib/index.js +9 -0
  25. package/lib-module/Button/ButtonGroup.js +7 -0
  26. package/lib-module/Carousel/Carousel.js +66 -11
  27. package/lib-module/Carousel/CarouselContext.js +17 -11
  28. package/lib-module/Carousel/CarouselFirstFocus/CarouselFirstFocus.js +51 -0
  29. package/lib-module/Carousel/CarouselTabs/CarouselTabs.js +50 -0
  30. package/lib-module/Carousel/CarouselTabs/CarouselTabsPanel.js +76 -0
  31. package/lib-module/Carousel/CarouselTabs/CarouselTabsPanelItem.js +126 -0
  32. package/lib-module/Carousel/CarouselTabs/index.js +2 -0
  33. package/lib-module/Carousel/CarouselThumbnail.js +85 -0
  34. package/lib-module/Carousel/CarouselThumbnailNavigation.js +66 -0
  35. package/lib-module/Carousel/dictionary.js +4 -2
  36. package/lib-module/Carousel/index.js +2 -1
  37. package/lib-module/Checkbox/CheckboxGroup.js +7 -0
  38. package/lib-module/Link/InlinePressable.js +1 -8
  39. package/lib-module/Link/LinkBase.js +5 -6
  40. package/lib-module/Radio/RadioGroup.js +8 -0
  41. package/lib-module/RadioCard/RadioCardGroup.js +7 -0
  42. package/lib-module/SkipLink/SkipLink.js +188 -0
  43. package/lib-module/SkipLink/index.js +2 -0
  44. package/lib-module/ThemeProvider/ThemeProvider.js +5 -1
  45. package/lib-module/ToggleSwitch/ToggleSwitchGroup.js +7 -0
  46. package/lib-module/index.js +1 -0
  47. package/package.json +46 -47
  48. package/src/Button/ButtonGroup.jsx +6 -0
  49. package/src/Carousel/Carousel.jsx +68 -10
  50. package/src/Carousel/CarouselContext.jsx +22 -9
  51. package/src/Carousel/CarouselFirstFocus/CarouselFirstFocus.jsx +49 -0
  52. package/src/Carousel/CarouselTabs/CarouselTabs.jsx +37 -0
  53. package/src/Carousel/CarouselTabs/CarouselTabsPanel.jsx +69 -0
  54. package/src/Carousel/CarouselTabs/CarouselTabsPanelItem.jsx +119 -0
  55. package/src/Carousel/CarouselTabs/index.js +3 -0
  56. package/src/Carousel/CarouselThumbnail.jsx +77 -0
  57. package/src/Carousel/CarouselThumbnailNavigation.jsx +53 -0
  58. package/src/Carousel/dictionary.js +4 -2
  59. package/src/Carousel/index.js +1 -0
  60. package/src/Checkbox/CheckboxGroup.jsx +7 -0
  61. package/src/Link/InlinePressable.jsx +2 -8
  62. package/src/Link/LinkBase.jsx +7 -16
  63. package/src/Radio/RadioGroup.jsx +7 -0
  64. package/src/RadioCard/RadioCardGroup.jsx +6 -0
  65. package/src/SkipLink/SkipLink.jsx +179 -0
  66. package/src/SkipLink/index.js +3 -0
  67. package/src/ThemeProvider/ThemeProvider.jsx +7 -1
  68. package/src/ToggleSwitch/ToggleSwitchGroup.jsx +6 -0
  69. package/src/index.js +1 -0
package/lib/index.js CHANGED
@@ -34,6 +34,7 @@ var _exportNames = {
34
34
  Select: true,
35
35
  SideNav: true,
36
36
  Skeleton: true,
37
+ SkipLink: true,
37
38
  Spacer: true,
38
39
  StackView: true,
39
40
  StepTracker: true,
@@ -249,6 +250,12 @@ Object.defineProperty(exports, "Skeleton", {
249
250
  return _Skeleton.default;
250
251
  }
251
252
  });
253
+ Object.defineProperty(exports, "SkipLink", {
254
+ enumerable: true,
255
+ get: function () {
256
+ return _SkipLink.default;
257
+ }
258
+ });
252
259
  Object.defineProperty(exports, "Spacer", {
253
260
  enumerable: true,
254
261
  get: function () {
@@ -524,6 +531,8 @@ var _SideNav = _interopRequireDefault(require("./SideNav"));
524
531
 
525
532
  var _Skeleton = _interopRequireDefault(require("./Skeleton"));
526
533
 
534
+ var _SkipLink = _interopRequireDefault(require("./SkipLink"));
535
+
527
536
  var _Spacer = _interopRequireDefault(require("./Spacer"));
528
537
 
529
538
  var _StackView = _interopRequireWildcard(require("./StackView"));
@@ -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
  */
@@ -7,12 +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
14
  import IconButton from '../IconButton';
15
- import CarouselStepTracker from './CarouselStepTracker/CarouselStepTracker';
15
+ import SkipLink from '../SkipLink';
16
+ import A11yText from '../A11yText';
17
+ import CarouselStepTracker from './CarouselStepTracker';
18
+ import CarouselThumbnailNavigation from './CarouselThumbnailNavigation';
16
19
  import dictionary from './dictionary';
17
20
  import { jsx as _jsx } from "react/jsx-runtime";
18
21
  import { jsxs as _jsxs } from "react/jsx-runtime";
@@ -134,11 +137,18 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
134
137
  onAnimationStart,
135
138
  onAnimationEnd,
136
139
  onIndexChanged,
140
+ skipLinkHref,
141
+ refocus,
142
+ title = 'carousel',
137
143
  springConfig = undefined,
138
- panelNavigation = /*#__PURE__*/_jsx(CarouselStepTracker, {}),
144
+ thumbnails = undefined,
145
+ panelNavigation = thumbnails ? /*#__PURE__*/_jsx(CarouselThumbnailNavigation, {
146
+ thumbnails: thumbnails
147
+ }) : /*#__PURE__*/_jsx(CarouselStepTracker, {}),
139
148
  tag = 'ul',
140
- accessibilityRole = 'adjustable',
141
- accessibilityLabel = 'carousel',
149
+ accessibilityRole,
150
+ accessibilityLabel = title,
151
+ accessibilityLiveRegion = 'polite',
142
152
  copy,
143
153
  ...rest
144
154
  } = _ref;
@@ -167,7 +177,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
167
177
  dictionary,
168
178
  copy
169
179
  });
170
- const childrenArray = React.Children.toArray(children);
180
+ const childrenArray = unpackFragment(children);
171
181
  const systemProps = selectProps({ ...rest,
172
182
  accessibilityRole,
173
183
  accessibilityLabel,
@@ -186,6 +196,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
186
196
  width: 0
187
197
  });
188
198
  const [previousNextNavigationButtonWidth, setPreviousNextNavigationButtonWidth] = React.useState(0);
199
+ const firstFocusRef = React.useRef(null);
189
200
  const pan = React.useRef(new Animated.ValueXY()).current;
190
201
  const animatedX = React.useRef(0);
191
202
  const animatedY = React.useRef(0);
@@ -285,10 +296,13 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
285
296
  return calcDelta;
286
297
  }, [containerLayout.width, activeIndex, animate, children.length, onIndexChanged]);
287
298
  const fixOffsetAndGo = React.useCallback(delta => {
299
+ var _firstFocusRef$curren;
300
+
288
301
  updateOffset();
289
302
  handleAnimationStart(activeIndex);
290
303
  updateIndex(delta);
291
- }, [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]);
292
306
  const goToNeighboring = React.useCallback(function () {
293
307
  let toPrev = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
294
308
  fixOffsetAndGo(toPrev ? -1 : 1);
@@ -371,17 +385,20 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
371
385
  raised: true
372
386
  };
373
387
  const getCopyWithPlaceholders = React.useCallback(copyKey => {
374
- 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
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
375
389
 
376
390
  return "".concat(copyText[0].toUpperCase()).concat(copyText.slice(1));
377
- }, [activeIndex, childrenArray.length, itemLabel, getCopy]);
391
+ }, [activeIndex, childrenArray.length, itemLabel, getCopy, title]);
378
392
  return /*#__PURE__*/_jsxs(CarouselProvider, {
379
393
  activeIndex: activeIndex,
380
- totalItems: childrenArray.length,
381
- width: containerLayout.width,
382
394
  goTo: goTo,
383
395
  getCopyWithPlaceholders: getCopyWithPlaceholders,
396
+ itemLabel: itemLabel,
397
+ totalItems: childrenArray.length,
384
398
  themeTokens: themeTokens,
399
+ firstFocusRef: firstFocusRef,
400
+ refocus: refocus,
401
+ width: containerLayout.width,
385
402
  children: [/*#__PURE__*/_jsxs(View, {
386
403
  style: staticStyles.root,
387
404
  onLayout: onContainerLayout,
@@ -397,6 +414,17 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
397
414
  variant: previousNextIconButtonVariants,
398
415
  accessibilityLabel: getCopyWithPlaceholders('iconButtonLabel').replace('%{targetStep}', activeIndex)
399
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')
400
428
  }), /*#__PURE__*/_jsx(View, {
401
429
  style: selectContainerStyles(containerLayout.width),
402
430
  children: /*#__PURE__*/_jsx(Animated.View, {
@@ -409,6 +437,9 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
409
437
  }]),
410
438
  ...panResponder.panHandlers,
411
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,
412
443
  children: childrenArray.map((element, index) => {
413
444
  const hidden = !isAnimating && index !== activeIndex;
414
445
  const clonedElement = /*#__PURE__*/React.cloneElement(element, {
@@ -468,6 +499,15 @@ Carousel.propTypes = { ...selectedSystemPropTypes,
468
499
  */
469
500
  springConfig: PropTypes.object,
470
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
+
471
511
  /**
472
512
  * Minimal part of slide width must be swiped for changing index.
473
513
  * Otherwise animation restore current slide. Default value 0.2 means that 20% must be swiped for change index
@@ -497,6 +537,21 @@ Carousel.propTypes = { ...selectedSystemPropTypes,
497
537
  */
498
538
  onIndexChanged: PropTypes.func,
499
539
 
540
+ /**
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
+
500
555
  /**
501
556
  * Use this to render a custom panel navigation element instead of the default StepTracker's based navigation
502
557
  * You can make use of `useCarousel` within your custom panel navigation component to hook into various Carousel states such as:
@@ -6,22 +6,26 @@ const CarouselContext = /*#__PURE__*/React.createContext();
6
6
 
7
7
  const CarouselProvider = _ref => {
8
8
  let {
9
- children,
10
9
  activeIndex,
11
- totalItems,
12
- width,
10
+ children,
13
11
  goTo,
14
12
  getCopyWithPlaceholders,
15
- themeTokens
13
+ itemLabel,
14
+ refocus = false,
15
+ themeTokens,
16
+ totalItems,
17
+ width
16
18
  } = _ref;
17
19
  const value = React.useMemo(() => ({
18
20
  activeIndex,
19
- totalItems,
20
- width,
21
21
  goTo,
22
22
  getCopyWithPlaceholders,
23
- themeTokens
24
- }), [activeIndex, totalItems, width, goTo, getCopyWithPlaceholders, themeTokens]);
23
+ itemLabel,
24
+ refocus,
25
+ themeTokens,
26
+ totalItems,
27
+ width
28
+ }), [activeIndex, goTo, getCopyWithPlaceholders, itemLabel, refocus, totalItems, themeTokens, width]);
25
29
  return /*#__PURE__*/_jsx(CarouselContext.Provider, {
26
30
  value: value,
27
31
  children: children
@@ -41,10 +45,12 @@ function useCarousel() {
41
45
  CarouselProvider.propTypes = {
42
46
  children: PropTypes.arrayOf(PropTypes.element).isRequired,
43
47
  activeIndex: PropTypes.number.isRequired,
44
- totalItems: PropTypes.number.isRequired,
45
- width: PropTypes.number.isRequired,
46
48
  goTo: PropTypes.func.isRequired,
47
49
  getCopyWithPlaceholders: PropTypes.func.isRequired,
48
- themeTokens: getTokensPropType('Carousel')
50
+ itemLabel: PropTypes.string.isRequired,
51
+ refocus: PropTypes.bool,
52
+ themeTokens: getTokensPropType('Carousel'),
53
+ totalItems: PropTypes.number.isRequired,
54
+ width: PropTypes.number.isRequired
49
55
  };
50
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,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;
@@ -0,0 +1,126 @@
1
+ import React, { forwardRef, useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import Pressable from "react-native-web/dist/exports/Pressable";
4
+ import Text from "react-native-web/dist/exports/Text";
5
+ import Platform from "react-native-web/dist/exports/Platform";
6
+ import { applyTextStyles, useThemeTokensCallback } from '../../ThemeProvider';
7
+ import { selectSystemProps, a11yProps, pressProps, viewProps, textProps, getTokensPropType, variantProp, resolvePressableState } from '../../utils';
8
+ import { jsx as _jsx } from "react/jsx-runtime";
9
+ const [selectPressProps, pressPropTypes] = selectSystemProps([a11yProps, pressProps, viewProps]);
10
+ const [selectTextProps, textPropTypes] = selectSystemProps([textProps]);
11
+
12
+ const selectContainerStyles = _ref => {
13
+ let {
14
+ paddingLeft,
15
+ paddingRight,
16
+ paddingTop,
17
+ paddingBottom = 0,
18
+ borderBottomColor,
19
+ borderBottomWidth = 0,
20
+ borderBottomStyle,
21
+ flex,
22
+ alignItems,
23
+ justifyContent
24
+ } = _ref;
25
+ return {
26
+ paddingLeft,
27
+ paddingRight,
28
+ paddingTop,
29
+ paddingBottom: paddingBottom - borderBottomWidth,
30
+ borderBottomColor,
31
+ borderBottomWidth,
32
+ borderBottomStyle,
33
+ flex,
34
+ alignItems,
35
+ justifyContent,
36
+ ...Platform.select({
37
+ // Removes the default browser :focus outline
38
+ web: {
39
+ outline: 'none'
40
+ }
41
+ })
42
+ };
43
+ };
44
+
45
+ const selectTextStyles = _ref2 => {
46
+ let {
47
+ fontSize,
48
+ fontScaleCap,
49
+ lineHeight,
50
+ letterSpacing,
51
+ fontWeight,
52
+ fontName,
53
+ color
54
+ } = _ref2;
55
+ return applyTextStyles({
56
+ fontSize,
57
+ fontScaleCap,
58
+ lineHeight,
59
+ letterSpacing,
60
+ fontWeight,
61
+ fontName,
62
+ color
63
+ });
64
+ };
65
+
66
+ const CarouselTabsPanelItem = /*#__PURE__*/forwardRef((_ref3, ref) => {
67
+ let {
68
+ title,
69
+ selected,
70
+ inactive,
71
+ variant,
72
+ tokens,
73
+ accessibilityRole = 'tab',
74
+ ...rest
75
+ } = _ref3;
76
+ // Workaround for React Native Web https://github.com/necolas/react-native-web/issues/2357
77
+ // Don't allow disabled to be set while focus is true else focus state gets locked `true`
78
+ // (must refocus _after_ calling `goTo`, else focus target content is not up to date)
79
+ const [isFocused, setIsFocused] = useState(false);
80
+ const disabled = (inactive || selected) && !isFocused;
81
+ const getTokens = useThemeTokensCallback('CarouselTabsPanelItem', tokens, variant);
82
+
83
+ const resolveTokens = pressState => getTokens(resolvePressableState(pressState, {
84
+ selected
85
+ }));
86
+
87
+ const getContainerStyle = pressState => selectContainerStyles(resolveTokens(pressState));
88
+
89
+ const getTextStyle = pressState => selectTextStyles(resolveTokens(pressState));
90
+
91
+ const {
92
+ onPress,
93
+ ...selectedPressProps
94
+ } = selectPressProps(rest);
95
+
96
+ const handleKeyDown = event => {
97
+ // Allow using the spacebar for navigation
98
+ if ((event === null || event === void 0 ? void 0 : event.key) === ' ') onPress(event);
99
+ };
100
+
101
+ return /*#__PURE__*/_jsx(Pressable, {
102
+ onPress: onPress,
103
+ style: getContainerStyle,
104
+ accessibilityRole: accessibilityRole,
105
+ ref: ref,
106
+ onKeyDown: handleKeyDown,
107
+ onFocus: () => setIsFocused(true),
108
+ onBlur: () => setIsFocused(false),
109
+ disabled: disabled,
110
+ ...selectedPressProps,
111
+ children: pressState => /*#__PURE__*/_jsx(Text, {
112
+ style: getTextStyle(pressState),
113
+ ...selectTextProps(rest),
114
+ children: title
115
+ })
116
+ });
117
+ });
118
+ CarouselTabsPanelItem.displayName = 'CarouselTabsPanelItem';
119
+ CarouselTabsPanelItem.propTypes = { ...pressPropTypes,
120
+ ...textPropTypes,
121
+ title: PropTypes.string.isRequired,
122
+ selected: PropTypes.bool,
123
+ tokens: getTokensPropType('CarouselTabsPanelItem'),
124
+ variant: variantProp.propType
125
+ };
126
+ export default CarouselTabsPanelItem;
@@ -0,0 +1,2 @@
1
+ import CarouselTabs from './CarouselTabs';
2
+ export default CarouselTabs;
@@ -0,0 +1,85 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import Pressable from "react-native-web/dist/exports/Pressable";
4
+ import Image from "react-native-web/dist/exports/Image";
5
+ import { useCarousel } from './CarouselContext';
6
+ /**
7
+ * `Carousel.Thumbnail` is used to wrap the content of an individual slide and is suppsoed to be the
8
+ * only top-level component passed to the `Carousel`
9
+ */
10
+
11
+ import { jsx as _jsx } from "react/jsx-runtime";
12
+
13
+ const CarouselThumbnail = _ref => {
14
+ let {
15
+ accessibilityLabel,
16
+ alt,
17
+ index,
18
+ src
19
+ } = _ref;
20
+ const {
21
+ activeIndex,
22
+ itemLabel,
23
+ totalItems,
24
+ getCopyWithPlaceholders,
25
+ goTo,
26
+ themeTokens
27
+ } = useCarousel();
28
+ const thumbnailTitle = alt !== null && alt !== void 0 ? alt : getCopyWithPlaceholders('stepTrackerLabel').replace(/%\{itemLabel\}/g, itemLabel).replace(/%\{stepNumber\}/g, index).replace(/%\{stepCount\}/g, totalItems);
29
+
30
+ const handlePress = () => goTo(index);
31
+
32
+ const handleKeyDown = event => {
33
+ // Allow using the spacebar for navigation
34
+ if ((event === null || event === void 0 ? void 0 : event.key) === ' ') goTo(index);
35
+ };
36
+
37
+ const {
38
+ thumbnailBorderColor,
39
+ thumbnailBorderRadius,
40
+ thumbnailBorderWidth,
41
+ thumbnailMargin,
42
+ thumbnailPadding,
43
+ thumbnailSelectedBorderColor,
44
+ thumbnailSelectedBorderWidth,
45
+ thumbnailSize
46
+ } = themeTokens;
47
+ const styles = {
48
+ pressable: {
49
+ borderColor: thumbnailBorderColor,
50
+ borderRadius: thumbnailBorderRadius,
51
+ borderWidth: thumbnailBorderWidth,
52
+ margin: thumbnailMargin,
53
+ padding: thumbnailPadding
54
+ },
55
+ image: {
56
+ height: thumbnailSize,
57
+ width: thumbnailSize
58
+ },
59
+ selected: {
60
+ borderColor: thumbnailSelectedBorderColor,
61
+ borderWidth: thumbnailSelectedBorderWidth,
62
+ padding: thumbnailPadding - thumbnailSelectedBorderWidth + thumbnailBorderWidth
63
+ }
64
+ };
65
+ return /*#__PURE__*/_jsx(Pressable, {
66
+ onKeyDown: handleKeyDown,
67
+ onPress: handlePress,
68
+ style: [styles.pressable, index === activeIndex && styles.selected],
69
+ children: /*#__PURE__*/_jsx(Image, {
70
+ accessibilityIgnoresInvertColors: true,
71
+ accessibilityLabel: accessibilityLabel !== null && accessibilityLabel !== void 0 ? accessibilityLabel : alt,
72
+ source: src,
73
+ style: styles.image,
74
+ title: thumbnailTitle
75
+ })
76
+ }, src);
77
+ };
78
+
79
+ CarouselThumbnail.propTypes = {
80
+ accessibilityLabel: PropTypes.string,
81
+ alt: PropTypes.string,
82
+ index: PropTypes.number,
83
+ src: PropTypes.string
84
+ };
85
+ export default CarouselThumbnail;