@telus-uds/components-base 3.29.1 → 3.30.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 (50) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/lib/cjs/Carousel/Carousel.js +6 -1
  3. package/lib/cjs/Carousel/CarouselThumbnail.js +123 -31
  4. package/lib/cjs/Carousel/CarouselThumbnailNavigation.js +8 -1
  5. package/lib/cjs/Footnote/FootnoteLink.js +18 -17
  6. package/lib/cjs/Progress/Progress.js +30 -5
  7. package/lib/cjs/Scroll/Scroll.js +466 -0
  8. package/lib/cjs/Scroll/ScrollContext.js +55 -0
  9. package/lib/cjs/Scroll/ScrollItem.js +103 -0
  10. package/lib/cjs/Scroll/ScrollProgress.js +99 -0
  11. package/lib/cjs/Scroll/dictionary.js +18 -0
  12. package/lib/cjs/Scroll/index.js +29 -0
  13. package/lib/cjs/index.js +15 -0
  14. package/lib/cjs/utils/animation/index.js +7 -0
  15. package/lib/cjs/utils/animation/useAnimatedValue.js +46 -0
  16. package/lib/cjs/utils/children.js +2 -1
  17. package/lib/esm/Carousel/Carousel.js +6 -1
  18. package/lib/esm/Carousel/CarouselThumbnail.js +124 -32
  19. package/lib/esm/Carousel/CarouselThumbnailNavigation.js +8 -1
  20. package/lib/esm/Footnote/FootnoteLink.js +18 -17
  21. package/lib/esm/Progress/Progress.js +30 -5
  22. package/lib/esm/Scroll/Scroll.js +459 -0
  23. package/lib/esm/Scroll/ScrollContext.js +47 -0
  24. package/lib/esm/Scroll/ScrollItem.js +96 -0
  25. package/lib/esm/Scroll/ScrollProgress.js +92 -0
  26. package/lib/esm/Scroll/dictionary.js +12 -0
  27. package/lib/esm/Scroll/index.js +4 -0
  28. package/lib/esm/index.js +1 -0
  29. package/lib/esm/utils/animation/index.js +1 -1
  30. package/lib/esm/utils/animation/useAnimatedValue.js +39 -0
  31. package/lib/esm/utils/children.js +2 -1
  32. package/lib/package.json +2 -2
  33. package/package.json +2 -2
  34. package/src/Carousel/Carousel.jsx +6 -1
  35. package/src/Carousel/CarouselThumbnail.jsx +153 -64
  36. package/src/Carousel/CarouselThumbnailNavigation.jsx +8 -2
  37. package/src/Footnote/FootnoteLink.jsx +17 -12
  38. package/src/Progress/Progress.jsx +22 -3
  39. package/src/Scroll/Scroll.jsx +488 -0
  40. package/src/Scroll/ScrollContext.jsx +41 -0
  41. package/src/Scroll/ScrollItem.jsx +89 -0
  42. package/src/Scroll/ScrollProgress.jsx +75 -0
  43. package/src/Scroll/dictionary.js +12 -0
  44. package/src/Scroll/index.js +5 -0
  45. package/src/index.js +1 -0
  46. package/src/utils/animation/index.js +1 -1
  47. package/src/utils/animation/useAnimatedValue.js +37 -0
  48. package/src/utils/children.jsx +4 -1
  49. package/types/Scroll.d.ts +66 -0
  50. package/types/index.d.ts +9 -0
package/CHANGELOG.md CHANGED
@@ -1,9 +1,23 @@
1
1
  # Change Log - @telus-uds/components-base
2
2
 
3
- This log was last generated on Mon, 06 Apr 2026 21:25:55 GMT and should not be manually modified.
3
+ This log was last generated on Thu, 16 Apr 2026 21:04:33 GMT and should not be manually modified.
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
+ ## 3.30.0
8
+
9
+ Thu, 16 Apr 2026 21:04:33 GMT
10
+
11
+ ### Minor changes
12
+
13
+ - `Carousel`: Add `isVideo` prop to thumbnail navigation to display a play icon overlay on video thumbnails (josue.higueros@telus.com)
14
+ - `Scroll`: add component (guillermo.peitzner@telus.com)
15
+ - Bump @telus-uds/system-theme-tokens to v4.21.0
16
+
17
+ ### Patches
18
+
19
+ - `FootnoteLink`: fixed superscript text positioning on native to correctly align with surrounding text (josue.higueroscalderon@telus.com)
20
+
7
21
  ## 3.29.1
8
22
 
9
23
  Mon, 06 Apr 2026 21:25:55 GMT
@@ -1177,9 +1177,14 @@ Carousel.propTypes = {
1177
1177
  * An array of objects containing information on the thumbnails to be rendered as navigation panel
1178
1178
  */
1179
1179
  thumbnails: _propTypes.default.arrayOf(_propTypes.default.shape({
1180
+ /** Accessibility label for the thumbnail image, used by assistive technologies. */
1180
1181
  accessibilityLabel: _propTypes.default.string,
1182
+ /** Alternative text for the thumbnail image, displayed when the image cannot be rendered. */
1181
1183
  alt: _propTypes.default.string,
1182
- src: _propTypes.default.string
1184
+ /** When true, renders a play icon overlay on the thumbnail to indicate that the slide contains a video. */
1185
+ isVideo: _propTypes.default.bool,
1186
+ /** URL or path of the thumbnail image. When used with `isVideo`, this should be the video's poster/preview image. */
1187
+ src: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number])
1183
1188
  })),
1184
1189
  /**
1185
1190
  * Minimal part of slide width must be swiped for changing index.
@@ -6,11 +6,14 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  var _react = _interopRequireDefault(require("react"));
8
8
  var _propTypes = _interopRequireDefault(require("prop-types"));
9
+ var _StyleSheet = _interopRequireDefault(require("react-native-web/dist/cjs/exports/StyleSheet"));
9
10
  var _Pressable = _interopRequireDefault(require("react-native-web/dist/cjs/exports/Pressable"));
10
11
  var _Image = _interopRequireDefault(require("react-native-web/dist/cjs/exports/Image"));
12
+ var _View = _interopRequireDefault(require("react-native-web/dist/cjs/exports/View"));
11
13
  var _CarouselContext = require("./CarouselContext");
12
14
  var _ThemeProvider = require("../ThemeProvider");
13
15
  var _ViewportProvider = require("../ViewportProvider");
16
+ var _Icon = _interopRequireDefault(require("../Icon"));
14
17
  var _jsxRuntime = require("react/jsx-runtime");
15
18
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
16
19
  const selectPressableTokens = _ref => {
@@ -30,17 +33,64 @@ const selectPressableTokens = _ref => {
30
33
  };
31
34
  };
32
35
 
36
+ // Play icon overlay appearance is consistent across all brands and is not theme-configurable.
37
+ // The circle occupies 55% of the thumbnail size, and the icon occupies 55% of the circle diameter.
38
+ const PLAY_ICON_RATIO = 0.55;
39
+ const PLAY_ICON_BORDER_RADIUS_DIVISOR = 2;
40
+ const selectImageStyles = size => ({
41
+ width: size,
42
+ height: size
43
+ });
44
+ const selectSelectedStyles = _ref2 => {
45
+ let {
46
+ selectedBorderColor,
47
+ selectedBorderWidth,
48
+ padding,
49
+ margin
50
+ } = _ref2;
51
+ return {
52
+ borderColor: selectedBorderColor,
53
+ borderWidth: selectedBorderWidth,
54
+ padding: padding - selectedBorderWidth,
55
+ marginBottom: margin + selectedBorderWidth
56
+ };
57
+ };
58
+ const selectNonSelectedStyles = _ref3 => {
59
+ let {
60
+ borderWidth,
61
+ padding,
62
+ margin,
63
+ selectedBorderWidth
64
+ } = _ref3;
65
+ return {
66
+ padding: padding - borderWidth,
67
+ marginBottom: margin + selectedBorderWidth
68
+ };
69
+ };
70
+ const selectPlayIconCircleStyles = thumbnailSize => {
71
+ const diameter = thumbnailSize * PLAY_ICON_RATIO;
72
+ return {
73
+ width: diameter,
74
+ height: diameter,
75
+ borderRadius: diameter / PLAY_ICON_BORDER_RADIUS_DIVISOR
76
+ };
77
+ };
78
+ const selectPlayIconTokens = thumbnailSize => ({
79
+ size: thumbnailSize * PLAY_ICON_RATIO * PLAY_ICON_RATIO
80
+ });
81
+
33
82
  /**
34
83
  * `Carousel.Thumbnail` is used to wrap the content of an individual slide and is suppsoed to be the
35
84
  * only top-level component passed to the `Carousel`
36
85
  */
37
- const CarouselThumbnail = /*#__PURE__*/_react.default.forwardRef((_ref2, ref) => {
86
+ const CarouselThumbnail = /*#__PURE__*/_react.default.forwardRef((_ref4, ref) => {
38
87
  let {
39
88
  accessibilityLabel,
40
89
  alt,
41
90
  index,
91
+ isVideo,
42
92
  src
43
- } = _ref2;
93
+ } = _ref4;
44
94
  const {
45
95
  activeIndex,
46
96
  itemLabel,
@@ -62,59 +112,101 @@ const CarouselThumbnail = /*#__PURE__*/_react.default.forwardRef((_ref2, ref) =>
62
112
  selectedBorderColor,
63
113
  selectedBorderWidth,
64
114
  size,
65
- margin
115
+ margin,
116
+ playIcon
66
117
  } = getThumbnailTokens({
67
118
  viewport
68
119
  });
69
- const styles = {
70
- image: {
71
- height: size,
72
- width: size
73
- },
74
- selected: {
75
- borderColor: selectedBorderColor,
76
- borderWidth: selectedBorderWidth,
77
- padding: padding - selectedBorderWidth,
78
- marginBottom: margin + selectedBorderWidth
79
- },
80
- nonSelected: {
81
- padding: padding - borderWidth,
82
- marginBottom: margin + selectedBorderWidth
83
- }
84
- };
85
120
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Pressable.default, {
86
121
  onKeyDown: handleKeyDown,
87
122
  onPress: handlePress,
88
- style: _ref3 => {
123
+ style: _ref5 => {
89
124
  let {
90
125
  hovered,
91
126
  pressed,
92
127
  focused
93
- } = _ref3;
128
+ } = _ref5;
94
129
  const pressableStyles = selectPressableTokens(getThumbnailTokens({
95
130
  hover: hovered,
96
131
  pressed,
97
132
  focus: focused
98
133
  }));
99
- return [pressableStyles, index === activeIndex ? [styles.selected, {
100
- outline: 'none'
101
- }] : styles.nonSelected];
134
+ return [pressableStyles, index === activeIndex ? [selectSelectedStyles({
135
+ selectedBorderColor,
136
+ selectedBorderWidth,
137
+ padding,
138
+ margin
139
+ }), staticStyles.selectedPressableOutline] : selectNonSelectedStyles({
140
+ borderWidth,
141
+ padding,
142
+ margin,
143
+ selectedBorderWidth
144
+ })];
102
145
  },
103
146
  ref: ref,
104
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Image.default, {
105
- accessibilityIgnoresInvertColors: true,
106
- accessibilityLabel: accessibilityLabel ?? alt,
107
- source: src,
108
- style: styles.image,
109
- title: thumbnailTitle
147
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_View.default, {
148
+ style: [staticStyles.imageContainer, selectImageStyles(size)],
149
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Image.default, {
150
+ accessibilityIgnoresInvertColors: true,
151
+ accessibilityLabel: accessibilityLabel ?? alt,
152
+ source: src,
153
+ style: selectImageStyles(size),
154
+ title: thumbnailTitle
155
+ }), isVideo && playIcon && /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
156
+ style: staticStyles.playIconOverlayContainer,
157
+ testID: `play-icon-overlay-${index}`,
158
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
159
+ style: [selectPlayIconCircleStyles(size), staticStyles.playIconCircleBackground],
160
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Icon.default, {
161
+ icon: playIcon,
162
+ tokens: {
163
+ ...selectPlayIconTokens(size),
164
+ color: staticStyles.playIconSymbol.color
165
+ }
166
+ })
167
+ })
168
+ })]
110
169
  })
111
170
  }, src);
112
171
  });
113
172
  CarouselThumbnail.displayName = 'CarouselThumbnail';
114
173
  CarouselThumbnail.propTypes = {
174
+ /** Accessibility label for screen readers, overrides the alt text */
115
175
  accessibilityLabel: _propTypes.default.string,
176
+ /** Alt text for the thumbnail image */
116
177
  alt: _propTypes.default.string,
178
+ /** Zero-based index of this thumbnail within the carousel */
117
179
  index: _propTypes.default.number,
118
- src: _propTypes.default.string
180
+ /**
181
+ * When true, renders a play icon overlay on the thumbnail to indicate that the slide contains a video.
182
+ */
183
+ isVideo: _propTypes.default.bool,
184
+ /** Image source URI (web) or local asset require() (native) */
185
+ src: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number])
119
186
  };
187
+ const staticStyles = _StyleSheet.default.create({
188
+ imageContainer: {
189
+ position: 'relative'
190
+ },
191
+ playIconOverlayContainer: {
192
+ position: 'absolute',
193
+ top: 0,
194
+ left: 0,
195
+ right: 0,
196
+ bottom: 0,
197
+ justifyContent: 'center',
198
+ alignItems: 'center'
199
+ },
200
+ selectedPressableOutline: {
201
+ outlineStyle: 'none'
202
+ },
203
+ playIconCircleBackground: {
204
+ backgroundColor: 'rgba(0, 0, 0, 0.6)',
205
+ justifyContent: 'center',
206
+ alignItems: 'center'
207
+ },
208
+ playIconSymbol: {
209
+ color: 'rgb(255, 255, 255)'
210
+ }
211
+ });
120
212
  var _default = exports.default = CarouselThumbnail;
@@ -52,12 +52,14 @@ const CarouselThumbnailNavigation = /*#__PURE__*/_react.default.forwardRef((_ref
52
52
  let {
53
53
  accessibilityLabel,
54
54
  alt,
55
+ isVideo,
55
56
  src
56
57
  } = _ref2;
57
58
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_CarouselThumbnail.default, {
58
59
  accessibilityLabel: accessibilityLabel,
59
60
  alt: alt,
60
61
  index: index,
62
+ isVideo: isVideo,
61
63
  src: src
62
64
  }, src);
63
65
  })
@@ -70,9 +72,14 @@ CarouselThumbnailNavigation.propTypes = {
70
72
  * An array of objects containing information on the thumbnail images.
71
73
  */
72
74
  thumbnails: _propTypes.default.arrayOf(_propTypes.default.shape({
75
+ /** Accessibility label for the thumbnail image, used by assistive technologies. */
73
76
  accessibilityLabel: _propTypes.default.string,
77
+ /** Alternative text for the thumbnail image, displayed when the image cannot be rendered. */
74
78
  alt: _propTypes.default.string,
75
- src: _propTypes.default.string
79
+ /** When true, renders a play icon overlay on the thumbnail to indicate that the slide contains a video. */
80
+ isVideo: _propTypes.default.bool,
81
+ /** URL or path of the thumbnail image. When used with `isVideo`, this should be the video's poster/preview image. */
82
+ src: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number])
76
83
  })).isRequired
77
84
  };
78
85
  var _default = exports.default = CarouselThumbnailNavigation;
@@ -16,28 +16,28 @@ var _ThemeProvider = require("../ThemeProvider");
16
16
  var _jsxRuntime = require("react/jsx-runtime");
17
17
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
18
18
  const [selectProps, selectedSystemPropTypes] = (0, _utils.selectSystemProps)([_utils.htmlAttrs, _utils.viewProps]);
19
-
20
- // The top property varies between devices due to how devices render the viewport.
19
+ const HALF_FONT_SIZE = 2;
20
+ const QUARTER_FONT_SIZE = 4;
21
+ const DEFAULT_LINE_HEIGHT = 1;
22
+ const LINE_HEIGHT_MULTIPLIER = 2;
21
23
  const selectTextStyle = _ref => {
22
24
  let {
23
25
  fontSize,
24
26
  lineHeight
25
27
  } = _ref;
26
- return {
27
- fontSize,
28
- lineHeight: lineHeight !== 1 ? lineHeight : fontSize * 2,
29
- ..._Platform.default.select({
30
- ios: {
31
- top: -fontSize / 2
32
- },
33
- android: {
34
- top: fontSize / 4
35
- },
36
- web: {
37
- top: -fontSize / 2.8
38
- }
39
- })
40
- };
28
+ return _Platform.default.select({
29
+ native: {
30
+ fontSize: fontSize / HALF_FONT_SIZE,
31
+ lineHeight: fontSize,
32
+ position: 'relative',
33
+ top: -(fontSize * lineHeight) / HALF_FONT_SIZE
34
+ },
35
+ web: {
36
+ fontSize,
37
+ lineHeight: lineHeight !== DEFAULT_LINE_HEIGHT ? lineHeight : fontSize * LINE_HEIGHT_MULTIPLIER,
38
+ top: -fontSize / QUARTER_FONT_SIZE
39
+ }
40
+ });
41
41
  };
42
42
  const FootnoteLink = /*#__PURE__*/_react.default.forwardRef((_ref2, ref) => {
43
43
  let {
@@ -81,6 +81,7 @@ const FootnoteLink = /*#__PURE__*/_react.default.forwardRef((_ref2, ref) => {
81
81
  });
82
82
  });
83
83
  FootnoteLink.displayName = 'FootnoteLink';
84
+ FootnoteLink.__UDS_COMPONENT_NAME__ = 'FootnoteLink';
84
85
  const copyShape = _propTypes.default.shape({
85
86
  a11yLabel: _propTypes.default.string.isRequired
86
87
  });
@@ -14,6 +14,8 @@ var _ProgressContext = _interopRequireDefault(require("./ProgressContext"));
14
14
  var _jsxRuntime = require("react/jsx-runtime");
15
15
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
16
16
  const [selectProps, selectedSystemPropTypes] = (0, _utils.selectSystemProps)([_utils.a11yProps, _utils.viewProps]);
17
+ const BORDER_SIDES = 2;
18
+ const CENTER_DIVISOR = 2;
17
19
  const selectProgressStyles = _ref => {
18
20
  let {
19
21
  backgroundColor,
@@ -32,6 +34,21 @@ const selectProgressStyles = _ref => {
32
34
  ...(0, _ThemeProvider.applyShadowToken)(shadow)
33
35
  };
34
36
  };
37
+ const selectBarWrapperStyles = _ref2 => {
38
+ let {
39
+ barHeight,
40
+ borderRadius,
41
+ borderWidth,
42
+ height
43
+ } = _ref2;
44
+ return {
45
+ height: barHeight,
46
+ width: '100%',
47
+ position: 'absolute',
48
+ top: (height - borderWidth * BORDER_SIDES - barHeight) / CENTER_DIVISOR,
49
+ borderRadius
50
+ };
51
+ };
35
52
 
36
53
  /**
37
54
  * The `Progress` is a container for displaying one or several `ProgressBar`s.
@@ -45,10 +62,11 @@ const selectProgressStyles = _ref => {
45
62
  *
46
63
  * - Use the following tokens to customize the appearance:
47
64
  * - `backgroundColor` for the background color of the progress container,
65
+ * - `barHeight` to control the height of the progress bars displayed within the container,
48
66
  * - `borderColor` to control the color of the border,
49
67
  * - `borderRadius` for the rounded corners,
50
68
  * - `borderWidth` to change the border width.
51
- * - `height` to control the height of the progress bars displayed within the progress bar container.
69
+ * - `height` to control the height of the progress bar container.
52
70
  * - `shadow` to apply a box shadow to the progress bar container.
53
71
  *
54
72
  * ## Variants
@@ -69,16 +87,23 @@ const selectProgressStyles = _ref => {
69
87
  * role.
70
88
  *
71
89
  */
72
- const Progress = /*#__PURE__*/_react.default.forwardRef((_ref2, ref) => {
90
+ const Progress = /*#__PURE__*/_react.default.forwardRef((_ref3, ref) => {
73
91
  let {
74
92
  children,
75
93
  tokens,
76
94
  variant,
77
95
  ...rest
78
- } = _ref2;
96
+ } = _ref3;
79
97
  const themeTokens = (0, _ThemeProvider.useThemeTokens)('Progress', tokens, variant);
80
- // Default to false (vertical layout) to preserve existing behavior and avoid breaking changes
81
98
  const layers = variant?.layers ?? false;
99
+ const {
100
+ barHeight,
101
+ height
102
+ } = themeTokens;
103
+ const content = barHeight && barHeight !== height ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
104
+ style: selectBarWrapperStyles(themeTokens),
105
+ children: children
106
+ }) : children;
82
107
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_ProgressContext.default.Provider, {
83
108
  value: {
84
109
  layers
@@ -87,7 +112,7 @@ const Progress = /*#__PURE__*/_react.default.forwardRef((_ref2, ref) => {
87
112
  ref: ref,
88
113
  style: [staticStyles.progressContainer, selectProgressStyles(themeTokens)],
89
114
  ...selectProps(rest),
90
- children: children
115
+ children: content
91
116
  })
92
117
  });
93
118
  });