@telus-uds/components-web 4.14.0 → 4.15.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.
package/CHANGELOG.md CHANGED
@@ -1,9 +1,24 @@
1
1
  # Change Log - @telus-uds/components-web
2
2
 
3
- This log was last generated on Wed, 19 Nov 2025 05:51:40 GMT and should not be manually modified.
3
+ This log was last generated on Fri, 12 Dec 2025 05:37:21 GMT and should not be manually modified.
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
+ ## 4.15.0
8
+
9
+ Fri, 12 Dec 2025 05:37:21 GMT
10
+
11
+ ### Minor changes
12
+
13
+ - `Card`: interative mode added for FullBleedContent (35577399+JoshHC@users.noreply.github.com)
14
+ - `Listbox & NavigationBar`: add `secondLevel` variant (sergio.ramirez@telus.com)
15
+ - Bump @telus-uds/components-base to v3.24.0
16
+ - Bump @telus-uds/system-theme-tokens to v4.17.0
17
+
18
+ ### Patches
19
+
20
+ - `Card`: problem with hover when overriding the background color fixed (josue.higueroscalderon@telus.com)
21
+
7
22
  ## 4.14.0
8
23
 
9
24
  Wed, 19 Nov 2025 05:51:40 GMT
@@ -18,6 +18,7 @@ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e;
18
18
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
19
19
  // Passes React Native-oriented system props through UDS Card
20
20
  const [selectProps, selectedSystemPropTypes] = (0, _componentsBase.selectSystemProps)([_componentsBase.a11yProps, _componentsBase.viewProps]);
21
+ const GRID_COLUMNS = 12;
21
22
 
22
23
  /**
23
24
  * A basic card component, unstyled by default.
@@ -49,6 +50,7 @@ const [selectProps, selectedSystemPropTypes] = (0, _componentsBase.selectSystemP
49
50
  * `Card` component accepts all the standard accessibility props.
50
51
  */
51
52
  const DynamicWidthContainer = /*#__PURE__*/_styledComponents.default.div.withConfig({
53
+ shouldForwardProp: prop => !['marginTop', 'marginBottom', 'marginLeft', 'marginRight', 'height', 'alignSelf'].includes(prop),
52
54
  displayName: "Card__DynamicWidthContainer",
53
55
  componentId: "components-web__sc-1elbtwd-0"
54
56
  })(_ref => {
@@ -56,13 +58,21 @@ const DynamicWidthContainer = /*#__PURE__*/_styledComponents.default.div.withCon
56
58
  width,
57
59
  display,
58
60
  borderRadius,
59
- overflow
61
+ overflow,
62
+ marginTop,
63
+ marginBottom,
64
+ marginLeft,
65
+ marginRight
60
66
  } = _ref;
61
67
  return {
62
68
  width,
63
69
  display,
64
70
  borderRadius,
65
- overflow
71
+ overflow,
72
+ marginTop,
73
+ marginBottom,
74
+ marginLeft,
75
+ marginRight
66
76
  };
67
77
  });
68
78
  const Card = /*#__PURE__*/_react.default.forwardRef(function () {
@@ -85,6 +95,14 @@ const Card = /*#__PURE__*/_react.default.forwardRef(function () {
85
95
  }
86
96
  };
87
97
  let ref = arguments.length > 1 ? arguments[1] : undefined;
98
+ const {
99
+ hrefAttrs: cardLevelHrefAttrs,
100
+ rest: restWithoutHrefAttrs
101
+ } = _componentsBase.hrefAttrsProp.bundle(rest);
102
+ const {
103
+ href: cardLevelHref,
104
+ ...restProps
105
+ } = restWithoutHrefAttrs;
88
106
  const {
89
107
  contentStackAlign,
90
108
  contentStackDirection,
@@ -93,18 +111,52 @@ const Card = /*#__PURE__*/_react.default.forwardRef(function () {
93
111
  fullBleedContentChildrenAlign
94
112
  } = (0, _FullBleedContent.useFullBleedContentProps)(fullBleedContent);
95
113
  const {
96
- imgCol
114
+ imgCol,
115
+ interactive: fullBleedInteractive,
116
+ onPress: fullBleedOnPress,
117
+ href: fullBleedHref,
118
+ hrefAttrs: fullBleedHrefAttrs,
119
+ ...fullBleedContentPropsClean
97
120
  } = fullBleedContentProps;
121
+ const effectiveFullBleedOnPress = fullBleedOnPress || onPress;
122
+ const effectiveFullBleedHref = fullBleedHref || cardLevelHref;
123
+ const effectiveFullBleedHrefAttrs = fullBleedHrefAttrs || cardLevelHrefAttrs;
98
124
 
99
125
  // If the card has rounded corners and a full bleed image, we need to apply
100
126
  // those corners on the image as well, but partially
127
+ const allThemeTokens = (0, _componentsBase.useThemeTokens)('Card', tokens, variant);
101
128
  const {
102
129
  borderRadius
103
- } = (0, _componentsBase.useThemeTokens)('Card', tokens, variant);
130
+ } = allThemeTokens;
131
+ const cardBaseBorderWidth = allThemeTokens.borderWidth;
132
+ const pressableBorderWidth = (0, _componentsBase.useThemeTokens)('Card', {}, {
133
+ ...variant,
134
+ interactive: true
135
+ }).borderWidth;
136
+ const marginOffset = `${-(cardBaseBorderWidth + pressableBorderWidth) / 2}px`;
104
137
  const getThemeTokens = (0, _componentsBase.useThemeTokensCallback)('Card', interactiveCard?.tokens, {
105
138
  interactive: true,
106
139
  ...(interactiveCard?.variant || {})
107
140
  });
141
+ const {
142
+ backgroundColor: _,
143
+ ...tokensWithoutBg
144
+ } = tokens;
145
+ const getFullBleedInteractiveTokens = (0, _componentsBase.useThemeTokensCallback)('Card', tokensWithoutBg, {
146
+ ...variant,
147
+ interactive: true
148
+ });
149
+ const getFullBleedInteractiveCardTokens = cardState => ({
150
+ ...getFullBleedInteractiveTokens(cardState),
151
+ paddingTop: 0,
152
+ paddingBottom: 0,
153
+ paddingLeft: 0,
154
+ paddingRight: 0,
155
+ // Suppress gradient if interactiveCard.body exists to avoid border duplication
156
+ ...(interactiveCard?.body ? {
157
+ gradient: undefined
158
+ } : {})
159
+ });
108
160
  const hasFooter = Boolean(footer);
109
161
  const fullBleedBorderRadius = (0, _FullBleedContent.getFullBleedBorderRadius)(borderRadius, fullBleedContentPosition, hasFooter);
110
162
 
@@ -112,32 +164,35 @@ const Card = /*#__PURE__*/_react.default.forwardRef(function () {
112
164
  // card content will adapt to the size of image to add up to 100% width of card width
113
165
  // pass as props to ConditionalWrapper
114
166
  const imgColCurrentViewport = (0, _componentsBase.useResponsiveProp)(imgCol);
115
- const maxCol = 12;
167
+ const maxCol = GRID_COLUMNS;
116
168
  const fullBleedImageWidth = `${imgColCurrentViewport / maxCol * 100}%`;
117
169
  const adaptiveContentWidth = `${(maxCol - imgColCurrentViewport) / maxCol * 100}%`;
118
170
  const isImageWidthAdjustable = imgCol && (fullBleedContentPosition === 'left' || fullBleedContentPosition === 'right');
119
171
  const contentWrapperStyleProps = {
120
172
  width: adaptiveContentWidth,
121
- display: imgColCurrentViewport >= maxCol ? 'none' : undefined
122
- };
123
- const imageWrapperStyleProps = {
124
- width: fullBleedImageWidth,
125
- borderRadius: imgColCurrentViewport >= maxCol ? borderRadius : undefined,
126
- overflow: imgColCurrentViewport >= maxCol ? 'hidden' : undefined,
127
- display: imgColCurrentViewport === 0 ? 'none' : undefined
173
+ ...(imgColCurrentViewport >= maxCol && {
174
+ display: 'none'
175
+ })
128
176
  };
129
177
  const columnFlex = {
130
178
  flexGrow: interactiveCard?.body ? 0 : 1,
131
179
  flexShrink: 1,
132
180
  justifyContent: 'space-between'
133
181
  };
134
- const {
135
- paddingTop,
136
- paddingBottom,
137
- paddingLeft,
138
- paddingRight,
139
- ...cardBaseTokens
140
- } = tokens;
182
+ const cardBaseTokens = Object.fromEntries(Object.entries(tokens).filter(_ref2 => {
183
+ let [key] = _ref2;
184
+ return !['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight', ...(backgroundImage ? ['backgroundColor'] : [])].includes(key);
185
+ }));
186
+ const imageWrapperStyleProps = {
187
+ width: fullBleedImageWidth,
188
+ ...(imgColCurrentViewport >= maxCol && {
189
+ borderRadius,
190
+ overflow: 'hidden'
191
+ }),
192
+ ...(imgColCurrentViewport === 0 && {
193
+ display: 'none'
194
+ })
195
+ };
141
196
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_componentsBase.Card, {
142
197
  ref: ref,
143
198
  variant: {
@@ -146,23 +201,102 @@ const Card = /*#__PURE__*/_react.default.forwardRef(function () {
146
201
  },
147
202
  tokens: cardBaseTokens,
148
203
  backgroundImage: backgroundImage,
149
- onPress: onPress,
204
+ onPress: fullBleedInteractive ? undefined : onPress,
150
205
  ...(interactiveCard?.selectionType && {
151
- interactiveCard
206
+ interactiveCard,
207
+ id: rest.id
152
208
  }),
153
- ...selectProps(rest),
154
- children: [interactiveCard?.body && !interactiveCard.selectionType ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_componentsBase.PressableCardBase, {
155
- ref: ref,
156
- tokens: getThemeTokens,
157
- dataSet: dataSet,
158
- onPress: onPress,
159
- href: interactiveCard?.href,
160
- hrefAttrs: interactiveCard?.hrefAttrs,
161
- ...selectProps(rest),
162
- children: cardState => /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
163
- children: typeof interactiveCard?.body === 'function' ? interactiveCard.body(cardState) : interactiveCard.body
209
+ ...selectProps(restProps),
210
+ children: [interactiveCard?.selectionType && children ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_CardContent.default, {
211
+ tokens: tokens,
212
+ variant: variant,
213
+ withFooter: hasFooter,
214
+ children: children
215
+ }) : null, interactiveCard?.body && !interactiveCard.selectionType ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
216
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_componentsBase.PressableCardBase, {
217
+ ref: ref,
218
+ tokens: getThemeTokens,
219
+ dataSet: dataSet,
220
+ onPress: onPress,
221
+ href: interactiveCard?.href,
222
+ hrefAttrs: interactiveCard?.hrefAttrs,
223
+ ...selectProps(restProps),
224
+ children: cardState => /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
225
+ children: typeof interactiveCard?.body === 'function' ? interactiveCard.body(cardState) : interactiveCard.body
226
+ })
227
+ }), children && fullBleedContentPosition === 'none' && !fullBleedInteractive ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_CardContent.default, {
228
+ tokens: tokens,
229
+ variant: variant,
230
+ withFooter: hasFooter,
231
+ children: children
232
+ }) : null]
233
+ }) : null, fullBleedInteractive ? /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
234
+ style: {
235
+ marginTop: marginOffset,
236
+ marginBottom: marginOffset,
237
+ marginLeft: marginOffset,
238
+ marginRight: marginOffset
239
+ },
240
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_componentsBase.PressableCardBase, {
241
+ ref: ref,
242
+ tokens: getFullBleedInteractiveCardTokens,
243
+ dataSet: dataSet,
244
+ onPress: effectiveFullBleedOnPress,
245
+ href: effectiveFullBleedHref,
246
+ hrefAttrs: effectiveFullBleedHrefAttrs,
247
+ ...selectProps(restProps),
248
+ children: cardState => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_componentsBase.StackView, {
249
+ direction: contentStackDirection,
250
+ tokens: {
251
+ ...columnFlex,
252
+ alignItems: contentStackAlign
253
+ },
254
+ space: 0,
255
+ children: [children ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_ConditionalWrapper.default, {
256
+ WrapperComponent: DynamicWidthContainer,
257
+ wrapperProps: contentWrapperStyleProps,
258
+ condition: isImageWidthAdjustable,
259
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_CardContent.default, {
260
+ tokens: {
261
+ ...tokensWithoutBg,
262
+ ...(backgroundImage ? {} : {
263
+ backgroundColor: 'transparent'
264
+ }),
265
+ ...(fullBleedContentChildrenAlign && {
266
+ alignSelf: fullBleedContentChildrenAlign
267
+ })
268
+ },
269
+ variant: variant,
270
+ withFooter: hasFooter,
271
+ children: children
272
+ })
273
+ }) : null, fullBleedContentPosition !== 'none' && /*#__PURE__*/(0, _jsxRuntime.jsx)(_ConditionalWrapper.default, {
274
+ WrapperComponent: DynamicWidthContainer,
275
+ wrapperProps: imageWrapperStyleProps,
276
+ condition: isImageWidthAdjustable,
277
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_FullBleedContent.default, {
278
+ borderRadius: fullBleedBorderRadius,
279
+ ...fullBleedContentPropsClean,
280
+ position: fullBleedContentPosition,
281
+ cardState: cardState
282
+ })
283
+ })]
284
+ })
164
285
  })
165
- }) : null, children || fullBleedContentPosition !== 'none' ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_componentsBase.StackView, {
286
+ }) : null, !fullBleedInteractive && !interactiveCard?.body && fullBleedContentPosition === 'none' && children ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_CardContent.default, {
287
+ tokens: {
288
+ ...tokens,
289
+ ...(backgroundImage ? {
290
+ backgroundColor: 'transparent'
291
+ } : {}),
292
+ ...(fullBleedContentChildrenAlign && {
293
+ alignSelf: fullBleedContentChildrenAlign
294
+ })
295
+ },
296
+ variant: variant,
297
+ withFooter: hasFooter,
298
+ children: children
299
+ }) : null, !fullBleedInteractive && fullBleedContentPosition !== 'none' ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_componentsBase.StackView, {
166
300
  direction: contentStackDirection,
167
301
  tokens: {
168
302
  ...columnFlex,
@@ -184,14 +318,15 @@ const Card = /*#__PURE__*/_react.default.forwardRef(function () {
184
318
  withFooter: hasFooter,
185
319
  children: children
186
320
  })
187
- }) : null, fullBleedContentPosition !== 'none' && /*#__PURE__*/(0, _jsxRuntime.jsx)(_ConditionalWrapper.default, {
321
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_ConditionalWrapper.default, {
188
322
  WrapperComponent: DynamicWidthContainer,
189
323
  wrapperProps: imageWrapperStyleProps,
190
324
  condition: isImageWidthAdjustable,
191
325
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_FullBleedContent.default, {
192
326
  borderRadius: fullBleedBorderRadius,
193
- ...fullBleedContentProps,
194
- position: fullBleedContentPosition
327
+ ...fullBleedContentPropsClean,
328
+ position: fullBleedContentPosition,
329
+ cardState: undefined
195
330
  })
196
331
  })]
197
332
  }) : null, footer && /*#__PURE__*/(0, _jsxRuntime.jsx)(_CardFooter.default, {
@@ -208,6 +343,26 @@ const PositionedFullBleedContentPropType = _propTypes.default.shape({
208
343
  position: _componentsBase.responsiveProps.getTypeOptionallyByViewport(_propTypes.default.oneOf(positionValues)).isRequired,
209
344
  align: _componentsBase.responsiveProps.getTypeOptionallyByViewport(_propTypes.default.oneOf(alignValues)),
210
345
  contentAlign: _componentsBase.responsiveProps.getTypeOptionallyByViewport(_propTypes.default.oneOf(alignValues)),
346
+ /**
347
+ * Make the full bleed content interactive.
348
+ * When true, the entire card (including the full bleed content) becomes interactive.
349
+ */
350
+ interactive: _propTypes.default.bool,
351
+ /**
352
+ * Function to call when the full bleed content is pressed.
353
+ * If not provided, falls back to the Card's onPress prop for backward compatibility.
354
+ */
355
+ onPress: _propTypes.default.func,
356
+ /**
357
+ * URL to navigate to when the full bleed content is pressed.
358
+ * If not provided, falls back to the Card's href prop for backward compatibility.
359
+ */
360
+ href: _propTypes.default.string,
361
+ /**
362
+ * Additional attributes for the href link.
363
+ * If not provided, falls back to the Card's hrefAttrs prop for backward compatibility.
364
+ */
365
+ hrefAttrs: _propTypes.default.shape(_componentsBase.hrefAttrsProp.types),
211
366
  // eslint-disable-next-line react/forbid-foreign-prop-types
212
367
  ..._FullBleedContent.default.propTypes
213
368
  });
@@ -28,7 +28,8 @@ const CardContentContainer = /*#__PURE__*/_styledComponents.default.div.withConf
28
28
  contentFlexShrink: flexShrink,
29
29
  contentJustifyContent: justifyContent,
30
30
  borderWidth,
31
- alignSelf
31
+ alignSelf,
32
+ backgroundColor
32
33
  } = _ref;
33
34
  return {
34
35
  // We need to make sure to have sharp corners on the bottom
@@ -47,7 +48,8 @@ const CardContentContainer = /*#__PURE__*/_styledComponents.default.div.withConf
47
48
  flexGrow,
48
49
  flexShrink,
49
50
  justifyContent,
50
- alignSelf
51
+ alignSelf,
52
+ backgroundColor
51
53
  };
52
54
  });
53
55
 
@@ -45,6 +45,7 @@ const NavigationBar = /*#__PURE__*/_react.default.forwardRef((_ref, ref) => {
45
45
  LinkRouter,
46
46
  linkRouterProps,
47
47
  tokens,
48
+ variant,
48
49
  ...rest
49
50
  } = _ref;
50
51
  const {
@@ -211,6 +212,7 @@ const NavigationBar = /*#__PURE__*/_react.default.forwardRef((_ref, ref) => {
211
212
  },
212
213
  items: scrollableNestedItems,
213
214
  tokens: tokens,
215
+ variant: variant,
214
216
  selected: itemId === currentValue,
215
217
  itemsContainerRef: itemsRef,
216
218
  ...itemRest,
@@ -279,6 +281,10 @@ NavigationBar.propTypes = {
279
281
  /**
280
282
  * Accesibility role for stackview
281
283
  */
282
- accessibilityRole: _propTypes.default.string
284
+ accessibilityRole: _propTypes.default.string,
285
+ /**
286
+ * Variant configuration
287
+ */
288
+ variant: _componentsBase.variantProp.propType
283
289
  };
284
290
  var _default = exports.default = NavigationBar;
@@ -22,6 +22,7 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
22
22
  id,
23
23
  isOpen = false,
24
24
  tokens = {},
25
+ variant = {},
25
26
  label,
26
27
  onClick,
27
28
  selectedId,
@@ -125,6 +126,7 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
125
126
  isReady: isReady,
126
127
  onLayout: onTargetLayout,
127
128
  ref: openOverlayRef,
129
+ variant: variant,
128
130
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_componentsBase.Listbox, {
129
131
  items: items,
130
132
  firstItemRef: targetRef,
@@ -132,6 +134,8 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
132
134
  selectedId: selectedId,
133
135
  LinkRouter: LinkRouter,
134
136
  linkRouterProps: linkRouterProps,
137
+ variant: variant,
138
+ onClose: onClick,
135
139
  ref: itemsContainerRef || ref
136
140
  })
137
141
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
@@ -159,6 +163,7 @@ NavigationSubMenu.propTypes = {
159
163
  openOverlayRef: _propTypes.default.object,
160
164
  LinkRouter: _propTypes.default.elementType,
161
165
  linkRouterProps: _propTypes.default.object,
162
- itemsContainerRef: _propTypes.default.object
166
+ itemsContainerRef: _propTypes.default.object,
167
+ variant: _propTypes.default.object
163
168
  };
164
169
  var _default = exports.default = NavigationSubMenu;
@@ -46,14 +46,19 @@ const FullBleedContentContainer = /*#__PURE__*/_styledComponents.default.div.wit
46
46
  borderBottomLeftRadius,
47
47
  borderBottomRightRadius,
48
48
  borderTopLeftRadius,
49
- borderTopRightRadius
49
+ borderTopRightRadius,
50
+ opacity,
51
+ transform
50
52
  } = _ref2;
51
53
  return {
52
54
  overflow: 'hidden',
53
55
  borderBottomLeftRadius,
54
56
  borderBottomRightRadius,
55
57
  borderTopLeftRadius,
56
- borderTopRightRadius
58
+ borderTopRightRadius,
59
+ opacity,
60
+ transform,
61
+ transition: 'opacity 0.2s ease, transform 0.2s ease'
57
62
  };
58
63
  });
59
64
 
@@ -66,6 +71,7 @@ const FullBleedContent = _ref3 => {
66
71
  let {
67
72
  borderRadius,
68
73
  content,
74
+ cardState,
69
75
  ...rest
70
76
  } = _ref3;
71
77
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(FullBleedContentContainer, {
@@ -89,6 +95,14 @@ FullBleedContent.propTypes = {
89
95
  * Custom JSX to be used for rendering the content (defaults to `ResponsiveImage` receiving other props).
90
96
  */
91
97
  content: _propTypes.default.node,
98
+ /**
99
+ * Card state object containing interactive states (hovered, pressed, focused).
100
+ */
101
+ cardState: _propTypes.default.shape({
102
+ hovered: _propTypes.default.bool,
103
+ pressed: _propTypes.default.bool,
104
+ focused: _propTypes.default.bool
105
+ }),
92
106
  /**
93
107
  * Image source.
94
108
  */