@telus-uds/components-base 4.0.0-alpha.0 → 4.0.0-alpha.2

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 (82) hide show
  1. package/CHANGELOG.md +62 -6
  2. package/jest.config.cjs +1 -0
  3. package/lib/cjs/ActionCard/ActionCard.js +10 -3
  4. package/lib/cjs/Card/Card.js +3 -3
  5. package/lib/cjs/Carousel/Carousel.js +6 -1
  6. package/lib/cjs/Carousel/CarouselThumbnail.js +125 -33
  7. package/lib/cjs/Carousel/CarouselThumbnailNavigation.js +8 -1
  8. package/lib/cjs/Footnote/FootnoteLink.js +18 -17
  9. package/lib/cjs/InputSupports/InputSupports.js +1 -2
  10. package/lib/cjs/InputSupports/useInputSupports.js +1 -3
  11. package/lib/cjs/Link/ChevronLink.js +1 -2
  12. package/lib/cjs/Notification/Notification.js +4 -12
  13. package/lib/cjs/Progress/Progress.js +30 -5
  14. package/lib/cjs/Progress/ProgressBar.js +3 -3
  15. package/lib/cjs/Progress/ProgressBarBackground.js +2 -3
  16. package/lib/cjs/QuickLinks/QuickLinks.js +7 -0
  17. package/lib/cjs/Scroll/Scroll.js +472 -0
  18. package/lib/cjs/Scroll/ScrollContext.js +55 -0
  19. package/lib/cjs/Scroll/ScrollItem.js +106 -0
  20. package/lib/cjs/Scroll/ScrollProgress.js +100 -0
  21. package/lib/cjs/Scroll/dictionary.js +18 -0
  22. package/lib/cjs/StackView/StackWrapBox.js +9 -1
  23. package/lib/cjs/StackView/StackWrapGap.js +3 -1
  24. package/lib/cjs/StackView/getStackedContent.js +20 -10
  25. package/lib/cjs/index.js +14 -0
  26. package/lib/cjs/utils/animation/useAnimatedValue.js +46 -0
  27. package/lib/cjs/utils/children.js +2 -1
  28. package/lib/esm/ActionCard/ActionCard.js +10 -3
  29. package/lib/esm/Card/Card.js +3 -3
  30. package/lib/esm/Carousel/Carousel.js +6 -1
  31. package/lib/esm/Carousel/CarouselThumbnail.js +126 -34
  32. package/lib/esm/Carousel/CarouselThumbnailNavigation.js +8 -1
  33. package/lib/esm/Footnote/FootnoteLink.js +18 -17
  34. package/lib/esm/InputSupports/InputSupports.js +1 -2
  35. package/lib/esm/InputSupports/useInputSupports.js +1 -3
  36. package/lib/esm/Link/ChevronLink.js +1 -2
  37. package/lib/esm/Notification/Notification.js +4 -12
  38. package/lib/esm/Progress/Progress.js +30 -5
  39. package/lib/esm/Progress/ProgressBar.js +3 -3
  40. package/lib/esm/Progress/ProgressBarBackground.js +2 -3
  41. package/lib/esm/QuickLinks/QuickLinks.js +7 -0
  42. package/lib/esm/Scroll/Scroll.js +465 -0
  43. package/lib/esm/Scroll/ScrollContext.js +46 -0
  44. package/lib/esm/Scroll/ScrollItem.js +100 -0
  45. package/lib/esm/Scroll/ScrollProgress.js +93 -0
  46. package/lib/esm/Scroll/dictionary.js +12 -0
  47. package/lib/esm/StackView/StackWrapBox.js +9 -1
  48. package/lib/esm/StackView/StackWrapGap.js +3 -1
  49. package/lib/esm/StackView/getStackedContent.js +20 -10
  50. package/lib/esm/index.js +2 -0
  51. package/lib/esm/utils/animation/useAnimatedValue.js +38 -0
  52. package/lib/esm/utils/children.js +2 -1
  53. package/lib/package.json +2 -2
  54. package/package.json +2 -2
  55. package/src/ActionCard/ActionCard.jsx +13 -4
  56. package/src/Card/Card.jsx +3 -3
  57. package/src/Carousel/Carousel.jsx +6 -1
  58. package/src/Carousel/CarouselThumbnail.jsx +117 -30
  59. package/src/Carousel/CarouselThumbnailNavigation.jsx +8 -2
  60. package/src/Footnote/FootnoteLink.jsx +17 -12
  61. package/src/InputSupports/InputSupports.jsx +1 -6
  62. package/src/InputSupports/useInputSupports.js +1 -1
  63. package/src/Link/ChevronLink.jsx +1 -2
  64. package/src/Notification/Notification.jsx +5 -21
  65. package/src/Progress/Progress.jsx +22 -3
  66. package/src/Progress/ProgressBar.jsx +3 -3
  67. package/src/Progress/ProgressBarBackground.jsx +2 -3
  68. package/src/QuickLinks/QuickLinks.jsx +8 -0
  69. package/src/Scroll/Scroll.jsx +483 -0
  70. package/src/Scroll/ScrollContext.jsx +39 -0
  71. package/src/Scroll/ScrollItem.jsx +87 -0
  72. package/src/Scroll/ScrollProgress.jsx +74 -0
  73. package/src/Scroll/dictionary.js +12 -0
  74. package/src/StackView/StackWrapBox.jsx +13 -1
  75. package/src/StackView/StackWrapGap.jsx +2 -1
  76. package/src/StackView/getStackedContent.jsx +22 -8
  77. package/src/index.js +2 -0
  78. package/src/utils/animation/useAnimatedValue.js +35 -0
  79. package/src/utils/children.jsx +4 -1
  80. package/types/Link.d.ts +1 -2
  81. package/types/Scroll.d.ts +66 -0
  82. package/types/index.d.ts +9 -0
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ScrollProgress = void 0;
7
+ var _react = _interopRequireDefault(require("react"));
8
+ var _propTypes = _interopRequireDefault(require("prop-types"));
9
+ var _View = _interopRequireDefault(require("react-native-web/dist/cjs/exports/View"));
10
+ var _pressability = require("../utils/pressability");
11
+ var _useAnimatedValue = require("../utils/animation/useAnimatedValue");
12
+ var _Progress = require("../Progress/Progress");
13
+ var _ProgressBar = require("../Progress/ProgressBar");
14
+ var _jsxRuntime = require("react/jsx-runtime");
15
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
16
+ const MAX_PERCENTAGE = 100;
17
+ const BORDER_RADIUS_DIVISOR = 2;
18
+ const staticProgressTokens = {
19
+ borderWidth: 0,
20
+ borderColor: 'transparent'
21
+ };
22
+ const staticProgressBarTokens = {
23
+ gradient: null,
24
+ outlineWidth: 0
25
+ };
26
+ const selectProgressTokens = _ref => {
27
+ let {
28
+ progressTrackHeight,
29
+ progressBarHeight,
30
+ progressTrackColor
31
+ } = _ref;
32
+ return {
33
+ height: progressTrackHeight,
34
+ barHeight: progressBarHeight,
35
+ backgroundColor: progressTrackColor,
36
+ borderRadius: progressTrackHeight / BORDER_RADIUS_DIVISOR
37
+ };
38
+ };
39
+ const selectBarTokens = _ref2 => {
40
+ let {
41
+ progressBarColor,
42
+ progressBarHeight
43
+ } = _ref2;
44
+ return {
45
+ backgroundColor: progressBarColor,
46
+ borderRadius: progressBarHeight / BORDER_RADIUS_DIVISOR
47
+ };
48
+ };
49
+ const ScrollProgress = exports.ScrollProgress = /*#__PURE__*/_react.default.forwardRef((_ref3, ref) => {
50
+ let {
51
+ pressableState,
52
+ getTokens,
53
+ thumbWrapperStyle,
54
+ getCopy
55
+ } = _ref3;
56
+ const state = (0, _pressability.resolvePressableState)(pressableState);
57
+ const themeTokens = getTokens(state);
58
+ const animatedBarHeight = (0, _useAnimatedValue.useAnimatedValue)(themeTokens.progressBarHeight);
59
+ const animatedTokens = {
60
+ ...themeTokens,
61
+ progressBarHeight: animatedBarHeight
62
+ };
63
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Progress.Progress, {
64
+ tokens: {
65
+ ...staticProgressTokens,
66
+ ...selectProgressTokens(animatedTokens)
67
+ },
68
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
69
+ ref: ref,
70
+ style: thumbWrapperStyle,
71
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_ProgressBar.ProgressBar, {
72
+ percentage: MAX_PERCENTAGE,
73
+ tokens: {
74
+ ...staticProgressBarTokens,
75
+ ...selectBarTokens(animatedTokens)
76
+ },
77
+ accessibilityLabel: getCopy('progressBarLabel')
78
+ })
79
+ })
80
+ });
81
+ });
82
+ ScrollProgress.displayName = 'ScrollProgress';
83
+ ScrollProgress.propTypes = {
84
+ /**
85
+ * State object from Pressable's render callback containing hovered, pressed, and focused states
86
+ */
87
+ pressableState: _propTypes.default.object.isRequired,
88
+ /**
89
+ * Theme token resolver callback from useThemeTokensCallback that accepts appearance state
90
+ */
91
+ getTokens: _propTypes.default.func.isRequired,
92
+ /**
93
+ * Style array for the thumb wrapper containing static and dynamic position styles
94
+ */
95
+ thumbWrapperStyle: _propTypes.default.oneOfType([_propTypes.default.object, _propTypes.default.array]).isRequired,
96
+ /**
97
+ * Copy resolver function from useCopy for retrieving localized strings
98
+ */
99
+ getCopy: _propTypes.default.func.isRequired
100
+ };
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.dictionary = void 0;
7
+ const dictionary = exports.dictionary = {
8
+ en: {
9
+ accessibilityLabel: 'Scrollable content',
10
+ instructionText: 'Use Tab, arrow keys, or space bar to navigate',
11
+ progressBarLabel: 'Scroll position'
12
+ },
13
+ fr: {
14
+ accessibilityLabel: 'Contenu défilable',
15
+ instructionText: "Utilisez Tab, les touches fléchées ou la barre d'espace pour naviguer",
16
+ progressBarLabel: 'Position de défilement'
17
+ }
18
+ };
@@ -58,6 +58,7 @@ const StackWrapBox = exports.StackWrapBox = /*#__PURE__*/_react.default.forwardR
58
58
  variant,
59
59
  tag,
60
60
  accessibilityRole,
61
+ itemAccessibilityRole,
61
62
  ...rest
62
63
  } = _ref;
63
64
  const viewport = (0, _useViewport.useViewport)();
@@ -84,7 +85,8 @@ const StackWrapBox = exports.StackWrapBox = /*#__PURE__*/_react.default.forwardR
84
85
  const content = (0, _getStackedContent.getStackedContent)(children, {
85
86
  direction,
86
87
  space: 0,
87
- box: boxProps
88
+ box: boxProps,
89
+ itemAccessibilityRole
88
90
  });
89
91
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
90
92
  ref: ref,
@@ -120,6 +122,12 @@ StackWrapBox.propTypes = {
120
122
  * is not defined, the accessibilityRole will default to "heading".
121
123
  */
122
124
  tag: _propTypes.default.oneOf(_semantics.layoutTags),
125
+ /**
126
+ * Optional accessibility role to apply to each item in the stack.
127
+ * On web, items are wrapped (or cloned) with this role, enabling correct list semantics
128
+ * when the container tag is "ul" or "ol".
129
+ */
130
+ itemAccessibilityRole: _propTypes.default.string,
123
131
  /**
124
132
  * A StackWrap may take any children, but will have no effect if it is only passed one child or is passed children
125
133
  * wrapped in a component. If necessary, children may be wrapped in one React Fragment.
@@ -40,6 +40,7 @@ const StackWrapGap = exports.StackWrapGap = /*#__PURE__*/_react.default.forwardR
40
40
  children,
41
41
  tag,
42
42
  accessibilityRole,
43
+ itemAccessibilityRole,
43
44
  ...rest
44
45
  } = _ref;
45
46
  const viewport = (0, _useViewport.useViewport)();
@@ -59,7 +60,8 @@ const StackWrapGap = exports.StackWrapGap = /*#__PURE__*/_react.default.forwardR
59
60
  };
60
61
  const content = (0, _getStackedContent.getStackedContent)(children, {
61
62
  direction,
62
- space: 0
63
+ space: 0,
64
+ itemAccessibilityRole
63
65
  });
64
66
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
65
67
  ref: ref,
@@ -42,7 +42,8 @@ const getStackedContent = (children, _ref) => {
42
42
  space,
43
43
  direction = 'column',
44
44
  box,
45
- preserveFragments = false
45
+ preserveFragments = false,
46
+ itemAccessibilityRole
46
47
  } = _ref;
47
48
  const boxProps = box && typeof box === 'object' ? box : {
48
49
  space
@@ -54,15 +55,24 @@ const getStackedContent = (children, _ref) => {
54
55
  const validChildren = _react.default.Children.toArray(topLevelChildren).filter(Boolean);
55
56
  const content = validChildren.reduce((newChildren, child, index) => {
56
57
  const boxID = `Stack-Box-${index}`;
57
- const item = box ?
58
- /*#__PURE__*/
59
- // If wrapped in Box, that Box needs a key.
60
- // If possible, use an existing content key; use an index-based key only if necessary.
61
- (0, _react.createElement)(_Box.Box, {
62
- ...boxProps,
63
- key: child.key || boxID,
64
- testID: boxID
65
- }, child) : child;
58
+ let item;
59
+ if (box) {
60
+ // If wrapped in Box, that Box needs a key.
61
+ // If possible, use an existing content key; use an index-based key only if necessary.
62
+ item = /*#__PURE__*/(0, _react.createElement)(_Box.Box, {
63
+ ...boxProps,
64
+ accessibilityRole: itemAccessibilityRole,
65
+ key: child.key || boxID,
66
+ testID: boxID
67
+ }, child);
68
+ } else if (itemAccessibilityRole) {
69
+ item = /*#__PURE__*/_react.default.cloneElement(child, {
70
+ accessibilityRole: itemAccessibilityRole,
71
+ key: child.key || boxID
72
+ });
73
+ } else {
74
+ item = child;
75
+ }
66
76
  if (!index || !space && !divider) return [...newChildren, item];
67
77
  const testID = `Stack-${divider ? 'Divider' : 'Spacer'}-${index}`;
68
78
  const commonProps = {
package/lib/cjs/index.js CHANGED
@@ -471,6 +471,18 @@ Object.defineProperty(exports, "Responsive", {
471
471
  return _Responsive.Responsive;
472
472
  }
473
473
  });
474
+ Object.defineProperty(exports, "Scroll", {
475
+ enumerable: true,
476
+ get: function () {
477
+ return _Scroll.Scroll;
478
+ }
479
+ });
480
+ Object.defineProperty(exports, "ScrollItem", {
481
+ enumerable: true,
482
+ get: function () {
483
+ return _ScrollItem.ScrollItem;
484
+ }
485
+ });
474
486
  Object.defineProperty(exports, "Search", {
475
487
  enumerable: true,
476
488
  get: function () {
@@ -1272,6 +1284,8 @@ var _RadioGroup = require("./Radio/RadioGroup");
1272
1284
  var _RadioCard = require("./RadioCard/RadioCard");
1273
1285
  var _RadioCardGroup = require("./RadioCard/RadioCardGroup");
1274
1286
  var _Responsive = require("./Responsive/Responsive");
1287
+ var _Scroll = require("./Scroll/Scroll");
1288
+ var _ScrollItem = require("./Scroll/ScrollItem");
1275
1289
  var _Search = require("./Search/Search");
1276
1290
  var _Select = require("./Select/Select");
1277
1291
  var _SelectItem = require("./Select/SelectItem");
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useAnimatedValue = void 0;
7
+ var _react = _interopRequireDefault(require("react"));
8
+ var _Animated = _interopRequireDefault(require("react-native-web/dist/cjs/exports/Animated"));
9
+ var _Easing = _interopRequireDefault(require("react-native-web/dist/cjs/exports/Easing"));
10
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
+ const ANIMATION_DURATION = 200;
12
+
13
+ /**
14
+ * Smoothly animates a numeric value to a new target using a quadratic ease-in-out
15
+ * curve. Returns the current interpolated value as a plain number so
16
+ * consumers can use it in non-Animated style props.
17
+ *
18
+ * @param {number} targetValue - The value to animate towards.
19
+ * @param {number} [duration=ANIMATION_DURATION] - Animation duration in ms.
20
+ */
21
+ const useAnimatedValue = function (targetValue) {
22
+ let duration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ANIMATION_DURATION;
23
+ const [currentValue, setCurrentValue] = _react.default.useState(targetValue);
24
+ const animatedValue = _react.default.useRef(new _Animated.default.Value(targetValue)).current;
25
+ _react.default.useEffect(() => {
26
+ const id = animatedValue.addListener(_ref => {
27
+ let {
28
+ value
29
+ } = _ref;
30
+ return setCurrentValue(value);
31
+ });
32
+ return () => animatedValue.removeListener(id);
33
+ }, [animatedValue]);
34
+ _react.default.useEffect(() => {
35
+ const animation = _Animated.default.timing(animatedValue, {
36
+ toValue: targetValue,
37
+ duration,
38
+ easing: _Easing.default.inOut(_Easing.default.quad),
39
+ useNativeDriver: false
40
+ });
41
+ animation.start();
42
+ return () => animation.stop();
43
+ }, [targetValue, animatedValue, duration]);
44
+ return currentValue;
45
+ };
46
+ exports.useAnimatedValue = useAnimatedValue;
@@ -54,7 +54,8 @@ const unpackFragment = child => {
54
54
  exports.unpackFragment = unpackFragment;
55
55
  const isStringOrNumber = child => typeof child === 'string' || typeof child === 'number';
56
56
  // Wrap an A11yText with neighouring text strings so it doesn't split them into multiple <Text>s
57
- const isWrapable = child => isStringOrNumber(child) || child.type === _A11yText.A11yText || child.type?.name === 'FootnoteLink';
57
+ // Check displayName first as it is explicitly set and reliable regardless of export style (named vs default)
58
+ const isWrapable = child => isStringOrNumber(child) || child.type === _A11yText.A11yText || child.type?.__UDS_COMPONENT_NAME__ === 'FootnoteLink';
58
59
  const combineKeys = childrenArray => childrenArray.reduce((newKey, child) => `${newKey}${child.key || ''}`, '');
59
60
 
60
61
  // Group wrappable children for one `<Text>` parent, merging adjacent text nodes
@@ -2,7 +2,6 @@ import React from 'react';
2
2
  import Animated from "react-native-web/dist/exports/Animated";
3
3
  import View from "react-native-web/dist/exports/View";
4
4
  import StyleSheet from "react-native-web/dist/exports/StyleSheet";
5
- import Text from "react-native-web/dist/exports/Text";
6
5
  import PropTypes from 'prop-types';
7
6
  import { a11yProps } from '../utils/props/a11yProps';
8
7
  import { getTokensPropType } from '../utils/props/tokens';
@@ -253,9 +252,10 @@ export const ActionCard = /*#__PURE__*/React.forwardRef((_ref1, ref) => {
253
252
  style: staticStyles.header,
254
253
  children: [/*#__PURE__*/_jsx(View, {
255
254
  style: [selectTitleStyles(themeTokens), staticStyles.title],
256
- children: /*#__PURE__*/_jsxs(Text, {
255
+ children: /*#__PURE__*/_jsxs(View, {
256
+ style: staticStyles.titleRow,
257
257
  children: [wrapStringsInText(title, {
258
- style: selectTitleTextStyles(themeTokens)
258
+ style: [selectTitleTextStyles(themeTokens), staticStyles.titleText]
259
259
  }), themeTokens.statusIcon && /*#__PURE__*/_jsx(View, {
260
260
  style: staticStyles.statusIcon,
261
261
  children: /*#__PURE__*/_jsx(Icon, {
@@ -344,6 +344,13 @@ const staticStyles = StyleSheet.create({
344
344
  title: {
345
345
  flexShrink: 1
346
346
  },
347
+ titleRow: {
348
+ flexDirection: 'row',
349
+ alignItems: 'center'
350
+ },
351
+ titleText: {
352
+ flexShrink: 1
353
+ },
347
354
  statusIcon: {
348
355
  marginLeft: 4
349
356
  }
@@ -80,10 +80,10 @@ const getInputProps = _ref3 => {
80
80
  *
81
81
  * ## Component API
82
82
  *
83
- * ### Background
83
+ * ### Style
84
84
  *
85
- * In order to control the background of a card, the following tokens are currently
86
- * supported:
85
+ * In order to control the style of a card, use the `style` variant.
86
+ * The following tokens are supported:
87
87
  *
88
88
  * - `backgroundColor`
89
89
  *
@@ -1180,9 +1180,14 @@ Carousel.propTypes = {
1180
1180
  * An array of objects containing information on the thumbnails to be rendered as navigation panel
1181
1181
  */
1182
1182
  thumbnails: PropTypes.arrayOf(PropTypes.shape({
1183
+ /** Accessibility label for the thumbnail image, used by assistive technologies. */
1183
1184
  accessibilityLabel: PropTypes.string,
1185
+ /** Alternative text for the thumbnail image, displayed when the image cannot be rendered. */
1184
1186
  alt: PropTypes.string,
1185
- src: PropTypes.string
1187
+ /** When true, renders a play icon overlay on the thumbnail to indicate that the slide contains a video. */
1188
+ isVideo: PropTypes.bool,
1189
+ /** URL or path of the thumbnail image. When used with `isVideo`, this should be the video's poster/preview image. */
1190
+ src: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
1186
1191
  })),
1187
1192
  /**
1188
1193
  * Minimal part of slide width must be swiped for changing index.
@@ -1,11 +1,14 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
+ import StyleSheet from "react-native-web/dist/exports/StyleSheet";
3
4
  import Pressable from "react-native-web/dist/exports/Pressable";
4
5
  import Image from "react-native-web/dist/exports/Image";
6
+ import View from "react-native-web/dist/exports/View";
5
7
  import { useCarousel } from './CarouselContext';
6
8
  import { useThemeTokensCallback } from '../ThemeProvider/useThemeTokens';
7
9
  import { useViewport } from '../ViewportProvider/useViewport';
8
- import { jsx as _jsx } from "react/jsx-runtime";
10
+ import { Icon } from '../Icon/Icon';
11
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
12
  const selectPressableTokens = _ref => {
10
13
  let {
11
14
  borderColor,
@@ -23,17 +26,64 @@ const selectPressableTokens = _ref => {
23
26
  };
24
27
  };
25
28
 
29
+ // Play icon overlay appearance is consistent across all brands and is not theme-configurable.
30
+ // The circle occupies 55% of the thumbnail size, and the icon occupies 55% of the circle diameter.
31
+ const PLAY_ICON_RATIO = 0.55;
32
+ const PLAY_ICON_BORDER_RADIUS_DIVISOR = 2;
33
+ const selectImageStyles = size => ({
34
+ width: size,
35
+ height: size
36
+ });
37
+ const selectSelectedStyles = _ref2 => {
38
+ let {
39
+ selectedBorderColor,
40
+ selectedBorderWidth,
41
+ padding,
42
+ margin
43
+ } = _ref2;
44
+ return {
45
+ borderColor: selectedBorderColor,
46
+ borderWidth: selectedBorderWidth,
47
+ padding: padding - selectedBorderWidth,
48
+ marginBottom: margin + selectedBorderWidth
49
+ };
50
+ };
51
+ const selectNonSelectedStyles = _ref3 => {
52
+ let {
53
+ borderWidth,
54
+ padding,
55
+ margin,
56
+ selectedBorderWidth
57
+ } = _ref3;
58
+ return {
59
+ padding: padding - borderWidth,
60
+ marginBottom: margin + selectedBorderWidth
61
+ };
62
+ };
63
+ const selectPlayIconCircleStyles = thumbnailSize => {
64
+ const diameter = thumbnailSize * PLAY_ICON_RATIO;
65
+ return {
66
+ width: diameter,
67
+ height: diameter,
68
+ borderRadius: diameter / PLAY_ICON_BORDER_RADIUS_DIVISOR
69
+ };
70
+ };
71
+ const selectPlayIconTokens = thumbnailSize => ({
72
+ size: thumbnailSize * PLAY_ICON_RATIO * PLAY_ICON_RATIO
73
+ });
74
+
26
75
  /**
27
- * `Carousel.Thumbnail` is used to wrap the content of an individual slide and is suppsoed to be the
76
+ * `CarouselThumbnail` is used to wrap the content of an individual slide and is suppsoed to be the
28
77
  * only top-level component passed to the `Carousel`
29
78
  */
30
- export const CarouselThumbnail = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
79
+ export const CarouselThumbnail = /*#__PURE__*/React.forwardRef((_ref4, ref) => {
31
80
  let {
32
81
  accessibilityLabel,
33
82
  alt,
34
83
  index,
84
+ isVideo,
35
85
  src
36
- } = _ref2;
86
+ } = _ref4;
37
87
  const {
38
88
  activeIndex,
39
89
  itemLabel,
@@ -55,58 +105,100 @@ export const CarouselThumbnail = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
55
105
  selectedBorderColor,
56
106
  selectedBorderWidth,
57
107
  size,
58
- margin
108
+ margin,
109
+ playIcon
59
110
  } = getThumbnailTokens({
60
111
  viewport
61
112
  });
62
- const styles = {
63
- image: {
64
- height: size,
65
- width: size
66
- },
67
- selected: {
68
- borderColor: selectedBorderColor,
69
- borderWidth: selectedBorderWidth,
70
- padding: padding - selectedBorderWidth,
71
- marginBottom: margin + selectedBorderWidth
72
- },
73
- nonSelected: {
74
- padding: padding - borderWidth,
75
- marginBottom: margin + selectedBorderWidth
76
- }
77
- };
78
113
  return /*#__PURE__*/_jsx(Pressable, {
79
114
  onKeyDown: handleKeyDown,
80
115
  onPress: handlePress,
81
- style: _ref3 => {
116
+ style: _ref5 => {
82
117
  let {
83
118
  hovered,
84
119
  pressed,
85
120
  focused
86
- } = _ref3;
121
+ } = _ref5;
87
122
  const pressableStyles = selectPressableTokens(getThumbnailTokens({
88
123
  hover: hovered,
89
124
  pressed,
90
125
  focus: focused
91
126
  }));
92
- return [pressableStyles, index === activeIndex ? [styles.selected, {
93
- outline: 'none'
94
- }] : styles.nonSelected];
127
+ return [pressableStyles, index === activeIndex ? [selectSelectedStyles({
128
+ selectedBorderColor,
129
+ selectedBorderWidth,
130
+ padding,
131
+ margin
132
+ }), staticStyles.selectedPressableOutline] : selectNonSelectedStyles({
133
+ borderWidth,
134
+ padding,
135
+ margin,
136
+ selectedBorderWidth
137
+ })];
95
138
  },
96
139
  ref: ref,
97
- children: /*#__PURE__*/_jsx(Image, {
98
- accessibilityIgnoresInvertColors: true,
99
- accessibilityLabel: accessibilityLabel ?? alt,
100
- source: src,
101
- style: styles.image,
102
- title: thumbnailTitle
140
+ children: /*#__PURE__*/_jsxs(View, {
141
+ style: [staticStyles.imageContainer, selectImageStyles(size)],
142
+ children: [/*#__PURE__*/_jsx(Image, {
143
+ accessibilityIgnoresInvertColors: true,
144
+ accessibilityLabel: accessibilityLabel ?? alt,
145
+ source: src,
146
+ style: selectImageStyles(size),
147
+ title: thumbnailTitle
148
+ }), isVideo && playIcon && /*#__PURE__*/_jsx(View, {
149
+ style: staticStyles.playIconOverlayContainer,
150
+ testID: `play-icon-overlay-${index}`,
151
+ children: /*#__PURE__*/_jsx(View, {
152
+ style: [selectPlayIconCircleStyles(size), staticStyles.playIconCircleBackground],
153
+ children: /*#__PURE__*/_jsx(Icon, {
154
+ icon: playIcon,
155
+ tokens: {
156
+ ...selectPlayIconTokens(size),
157
+ color: staticStyles.playIconSymbol.color
158
+ }
159
+ })
160
+ })
161
+ })]
103
162
  })
104
163
  }, src);
105
164
  });
106
165
  CarouselThumbnail.displayName = 'CarouselThumbnail';
107
166
  CarouselThumbnail.propTypes = {
167
+ /** Accessibility label for screen readers, overrides the alt text */
108
168
  accessibilityLabel: PropTypes.string,
169
+ /** Alt text for the thumbnail image */
109
170
  alt: PropTypes.string,
171
+ /** Zero-based index of this thumbnail within the carousel */
110
172
  index: PropTypes.number,
111
- src: PropTypes.string
112
- };
173
+ /**
174
+ * When true, renders a play icon overlay on the thumbnail to indicate that the slide contains a video.
175
+ */
176
+ isVideo: PropTypes.bool,
177
+ /** Image source URI (web) or local asset require() (native) */
178
+ src: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
179
+ };
180
+ const staticStyles = StyleSheet.create({
181
+ imageContainer: {
182
+ position: 'relative'
183
+ },
184
+ playIconOverlayContainer: {
185
+ position: 'absolute',
186
+ top: 0,
187
+ left: 0,
188
+ right: 0,
189
+ bottom: 0,
190
+ justifyContent: 'center',
191
+ alignItems: 'center'
192
+ },
193
+ selectedPressableOutline: {
194
+ outlineStyle: 'none'
195
+ },
196
+ playIconCircleBackground: {
197
+ backgroundColor: 'rgba(0, 0, 0, 0.6)',
198
+ justifyContent: 'center',
199
+ alignItems: 'center'
200
+ },
201
+ playIconSymbol: {
202
+ color: 'rgb(255, 255, 255)'
203
+ }
204
+ });
@@ -45,12 +45,14 @@ export const CarouselThumbnailNavigation = /*#__PURE__*/React.forwardRef((_ref,
45
45
  let {
46
46
  accessibilityLabel,
47
47
  alt,
48
+ isVideo,
48
49
  src
49
50
  } = _ref2;
50
51
  return /*#__PURE__*/_jsx(CarouselThumbnail, {
51
52
  accessibilityLabel: accessibilityLabel,
52
53
  alt: alt,
53
54
  index: index,
55
+ isVideo: isVideo,
54
56
  src: src
55
57
  }, src);
56
58
  })
@@ -63,8 +65,13 @@ CarouselThumbnailNavigation.propTypes = {
63
65
  * An array of objects containing information on the thumbnail images.
64
66
  */
65
67
  thumbnails: PropTypes.arrayOf(PropTypes.shape({
68
+ /** Accessibility label for the thumbnail image, used by assistive technologies. */
66
69
  accessibilityLabel: PropTypes.string,
70
+ /** Alternative text for the thumbnail image, displayed when the image cannot be rendered. */
67
71
  alt: PropTypes.string,
68
- src: PropTypes.string
72
+ /** When true, renders a play icon overlay on the thumbnail to indicate that the slide contains a video. */
73
+ isVideo: PropTypes.bool,
74
+ /** URL or path of the thumbnail image. When used with `isVideo`, this should be the video's poster/preview image. */
75
+ src: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
69
76
  })).isRequired
70
77
  };
@@ -15,28 +15,28 @@ import { applyTextStyles } from '../ThemeProvider/utils/styles';
15
15
  import { useThemeTokens } from '../ThemeProvider/useThemeTokens';
16
16
  import { jsx as _jsx } from "react/jsx-runtime";
17
17
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs, viewProps]);
18
-
19
- // The top property varies between devices due to how devices render the viewport.
18
+ const HALF_FONT_SIZE = 2;
19
+ const QUARTER_FONT_SIZE = 4;
20
+ const DEFAULT_LINE_HEIGHT = 1;
21
+ const LINE_HEIGHT_MULTIPLIER = 2;
20
22
  const selectTextStyle = _ref => {
21
23
  let {
22
24
  fontSize,
23
25
  lineHeight
24
26
  } = _ref;
25
- return {
26
- fontSize,
27
- lineHeight: lineHeight !== 1 ? lineHeight : fontSize * 2,
28
- ...Platform.select({
29
- ios: {
30
- top: -fontSize / 2
31
- },
32
- android: {
33
- top: fontSize / 4
34
- },
35
- web: {
36
- top: -fontSize / 2.8
37
- }
38
- })
39
- };
27
+ return Platform.select({
28
+ native: {
29
+ fontSize: fontSize / HALF_FONT_SIZE,
30
+ lineHeight: fontSize,
31
+ position: 'relative',
32
+ top: -(fontSize * lineHeight) / HALF_FONT_SIZE
33
+ },
34
+ web: {
35
+ fontSize,
36
+ lineHeight: lineHeight !== DEFAULT_LINE_HEIGHT ? lineHeight : fontSize * LINE_HEIGHT_MULTIPLIER,
37
+ top: -fontSize / QUARTER_FONT_SIZE
38
+ }
39
+ });
40
40
  };
41
41
  export const FootnoteLink = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
42
42
  let {
@@ -80,6 +80,7 @@ export const FootnoteLink = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
80
80
  });
81
81
  });
82
82
  FootnoteLink.displayName = 'FootnoteLink';
83
+ FootnoteLink.__UDS_COMPONENT_NAME__ = 'FootnoteLink';
83
84
  const copyShape = PropTypes.shape({
84
85
  a11yLabel: PropTypes.string.isRequired
85
86
  });
@@ -66,8 +66,7 @@ export const InputSupports = /*#__PURE__*/React.forwardRef((_ref, ref) => {
66
66
  }), typeof children === 'function' ? children({
67
67
  inputId,
68
68
  ...a11yProps,
69
- validation: feedbackValidation,
70
- accessibilityDescribedBy: feedbackId
69
+ validation: feedbackValidation
71
70
  }) : children, feedback || maxCharsReachedErrorMessage ? /*#__PURE__*/_jsx(Feedback, {
72
71
  id: feedbackId,
73
72
  title: feedback || maxCharsReachedErrorMessage,