@telus-uds/components-web 4.18.2 → 4.19.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 +15 -1
- package/lib/cjs/Card/Card.js +91 -13
- package/lib/cjs/Spinner/Spinner.js +50 -7
- package/lib/cjs/shared/FullBleedContent/index.js +9 -1
- package/lib/cjs/shared/FullBleedContent/useFullBleedContentProps.js +22 -2
- package/lib/esm/Card/Card.js +92 -14
- package/lib/esm/Spinner/Spinner.js +50 -7
- package/lib/esm/shared/FullBleedContent/index.js +1 -1
- package/lib/esm/shared/FullBleedContent/useFullBleedContentProps.js +21 -1
- package/package.json +2 -2
- package/src/Card/Card.jsx +95 -14
- package/src/Spinner/Spinner.jsx +43 -6
- package/src/shared/FullBleedContent/index.js +4 -1
- package/src/shared/FullBleedContent/useFullBleedContentProps.js +21 -1
- package/types/Autocomplete.d.ts +2 -0
- package/types/Spinner.d.ts +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
# Change Log - @telus-uds/components-web
|
|
2
2
|
|
|
3
|
-
This log was last generated on
|
|
3
|
+
This log was last generated on Tue, 03 Mar 2026 23:02:55 GMT and should not be manually modified.
|
|
4
4
|
|
|
5
5
|
<!-- Start content -->
|
|
6
6
|
|
|
7
|
+
## 4.19.0
|
|
8
|
+
|
|
9
|
+
Tue, 03 Mar 2026 23:02:55 GMT
|
|
10
|
+
|
|
11
|
+
### Minor changes
|
|
12
|
+
|
|
13
|
+
- `Card`: add padding feature (sergio.ramirez@telus.com)
|
|
14
|
+
- Spinner: Added a new prop called `persistChildrenState`, to be used only when overlaying children, ensuring the children's state is kept after the Spinner is triggered. (kevin.ruiz@telus.com)
|
|
15
|
+
- Bump @telus-uds/components-base to v3.28.2
|
|
16
|
+
|
|
17
|
+
### Patches
|
|
18
|
+
|
|
19
|
+
- `Autocomplete`: add the ability to show all values and filter them (josue.higueroscalderon@telus.com)
|
|
20
|
+
|
|
7
21
|
## 4.18.2
|
|
8
22
|
|
|
9
23
|
Fri, 20 Feb 2026 23:53:11 GMT
|
package/lib/cjs/Card/Card.js
CHANGED
|
@@ -185,6 +185,52 @@ const FocusBorder = /*#__PURE__*/_styledComponents.default.div.withConfig({
|
|
|
185
185
|
zIndex: 2
|
|
186
186
|
};
|
|
187
187
|
});
|
|
188
|
+
const FullBleedPaddingWrapper = /*#__PURE__*/_styledComponents.default.div.withConfig({
|
|
189
|
+
displayName: "Card__FullBleedPaddingWrapper",
|
|
190
|
+
componentId: "components-web__sc-1elbtwd-4"
|
|
191
|
+
})(_ref4 => {
|
|
192
|
+
let {
|
|
193
|
+
paddingTop,
|
|
194
|
+
paddingBottom,
|
|
195
|
+
paddingLeft,
|
|
196
|
+
paddingRight
|
|
197
|
+
} = _ref4;
|
|
198
|
+
return {
|
|
199
|
+
paddingTop,
|
|
200
|
+
paddingBottom,
|
|
201
|
+
paddingLeft,
|
|
202
|
+
paddingRight,
|
|
203
|
+
display: 'flex',
|
|
204
|
+
flexDirection: 'column'
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Calculates the padding values for full bleed content.
|
|
210
|
+
* Inherits the card's theme padding (minus borderWidth to visually match card body padding)
|
|
211
|
+
* and allows custom overrides via the fullBleedPadding prop.
|
|
212
|
+
*
|
|
213
|
+
* @param {object} themeTokens - The card's resolved theme tokens (contains padding values and borderWidth)
|
|
214
|
+
* @param {object|boolean} fullBleedPadding - true to inherit card padding, or an object with overrides
|
|
215
|
+
* @param {object} paddingSides - Boolean flags for which sides to apply padding
|
|
216
|
+
* @returns {object} Padding values for each side in px
|
|
217
|
+
*/
|
|
218
|
+
const calculateFullBleedPadding = (themeTokens, fullBleedPadding, paddingSides) => {
|
|
219
|
+
const customTokens = typeof fullBleedPadding === 'object' ? fullBleedPadding : undefined;
|
|
220
|
+
const borderWidth = themeTokens.borderWidth || 0;
|
|
221
|
+
const applyPadding = side => {
|
|
222
|
+
const sideKey = `applyPadding${side}`;
|
|
223
|
+
if (!paddingSides[sideKey]) return 0;
|
|
224
|
+
const tokenKey = `padding${side}`;
|
|
225
|
+
return customTokens?.[tokenKey] ?? themeTokens[tokenKey] - borderWidth ?? 0;
|
|
226
|
+
};
|
|
227
|
+
return {
|
|
228
|
+
paddingTop: applyPadding('Top'),
|
|
229
|
+
paddingBottom: applyPadding('Bottom'),
|
|
230
|
+
paddingLeft: applyPadding('Left'),
|
|
231
|
+
paddingRight: applyPadding('Right')
|
|
232
|
+
};
|
|
233
|
+
};
|
|
188
234
|
const Card = /*#__PURE__*/_react.default.forwardRef(function () {
|
|
189
235
|
let {
|
|
190
236
|
children,
|
|
@@ -192,6 +238,7 @@ const Card = /*#__PURE__*/_react.default.forwardRef(function () {
|
|
|
192
238
|
footerPadding,
|
|
193
239
|
fullBleedImage,
|
|
194
240
|
fullBleedContent = fullBleedImage,
|
|
241
|
+
fullBleedPadding,
|
|
195
242
|
tokens = {},
|
|
196
243
|
variant,
|
|
197
244
|
interactiveCard,
|
|
@@ -218,7 +265,8 @@ const Card = /*#__PURE__*/_react.default.forwardRef(function () {
|
|
|
218
265
|
contentStackDirection,
|
|
219
266
|
fullBleedContentPosition,
|
|
220
267
|
fullBleedContentProps,
|
|
221
|
-
fullBleedContentChildrenAlign
|
|
268
|
+
fullBleedContentChildrenAlign,
|
|
269
|
+
fullBleedPaddingSides
|
|
222
270
|
} = (0, _FullBleedContent.useFullBleedContentProps)(fullBleedContent);
|
|
223
271
|
const {
|
|
224
272
|
imgCol,
|
|
@@ -243,6 +291,12 @@ const Card = /*#__PURE__*/_react.default.forwardRef(function () {
|
|
|
243
291
|
borderRadius
|
|
244
292
|
} = allThemeTokens;
|
|
245
293
|
|
|
294
|
+
// Get viewport-aware tokens for responsive full bleed padding
|
|
295
|
+
// useThemeTokens (above) doesn't pass viewport state, so it resolves to base/mobile tokens.
|
|
296
|
+
// useAllViewportTokens resolves tokens for ALL viewports + the current one (reactive to resize).
|
|
297
|
+
// This ensures fullBleedPadding matches CardContent's padding at every breakpoint.
|
|
298
|
+
const allViewportTokens = (0, _componentsBase.useAllViewportTokens)('Card', tokens, variant);
|
|
299
|
+
|
|
246
300
|
// Interactive cards: merge variants for CardBase (outer container)
|
|
247
301
|
// The outer variant takes priority over interactiveCard.variant for the style property
|
|
248
302
|
// This ensures the gradient is only applied to CardBase, not PressableCardBase and avoid duplication
|
|
@@ -318,6 +372,13 @@ const Card = /*#__PURE__*/_react.default.forwardRef(function () {
|
|
|
318
372
|
const hasFooter = Boolean(footer);
|
|
319
373
|
const fullBleedBorderRadius = (0, _FullBleedContent.getFullBleedBorderRadius)(borderRadius, fullBleedContentPosition, hasFooter);
|
|
320
374
|
|
|
375
|
+
// Calculate full bleed content padding values
|
|
376
|
+
// When fullBleedPadding is truthy, apply padding around full bleed content
|
|
377
|
+
// Uses viewport-aware tokens (allViewportTokens.current) so padding matches
|
|
378
|
+
// CardContent's responsive padding at every breakpoint
|
|
379
|
+
const hasFullBleedPadding = Boolean(fullBleedPadding);
|
|
380
|
+
const fullBleedPaddingValues = hasFullBleedPadding ? calculateFullBleedPadding(allViewportTokens.current, fullBleedPadding, fullBleedPaddingSides) : null;
|
|
381
|
+
|
|
321
382
|
// takes imgCol from fullBleedContent if present, to dynamically set width of image
|
|
322
383
|
// card content will adapt to the size of image to add up to 100% width of card width
|
|
323
384
|
// pass as props to ConditionalWrapper
|
|
@@ -343,8 +404,8 @@ const Card = /*#__PURE__*/_react.default.forwardRef(function () {
|
|
|
343
404
|
flexShrink: 1,
|
|
344
405
|
justifyContent: 'space-between'
|
|
345
406
|
};
|
|
346
|
-
const cardBaseTokens = Object.fromEntries(Object.entries(tokens).filter(
|
|
347
|
-
let [key] =
|
|
407
|
+
const cardBaseTokens = Object.fromEntries(Object.entries(tokens).filter(_ref5 => {
|
|
408
|
+
let [key] = _ref5;
|
|
348
409
|
return !['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight', ...(backgroundImage ? ['backgroundColor', 'gradient', 'backgroundGradient'] : [])].includes(key);
|
|
349
410
|
}));
|
|
350
411
|
const cardBaseVariant = backgroundImage ? {
|
|
@@ -456,11 +517,16 @@ const Card = /*#__PURE__*/_react.default.forwardRef(function () {
|
|
|
456
517
|
WrapperComponent: DynamicWidthContainer,
|
|
457
518
|
wrapperProps: imageWrapperStyleProps,
|
|
458
519
|
condition: isImageWidthAdjustable || isHorizontalFullBleed || isVerticalFullBleed,
|
|
459
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
520
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_ConditionalWrapper.default, {
|
|
521
|
+
WrapperComponent: FullBleedPaddingWrapper,
|
|
522
|
+
wrapperProps: fullBleedPaddingValues,
|
|
523
|
+
condition: hasFullBleedPadding,
|
|
524
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_FullBleedContent.default, {
|
|
525
|
+
borderRadius: fullBleedBorderRadius,
|
|
526
|
+
...fullBleedContentPropsClean,
|
|
527
|
+
position: fullBleedContentPosition,
|
|
528
|
+
cardState: undefined
|
|
529
|
+
})
|
|
464
530
|
})
|
|
465
531
|
})]
|
|
466
532
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(InteractiveOverlay, {
|
|
@@ -504,11 +570,16 @@ const Card = /*#__PURE__*/_react.default.forwardRef(function () {
|
|
|
504
570
|
WrapperComponent: DynamicWidthContainer,
|
|
505
571
|
wrapperProps: imageWrapperStyleProps,
|
|
506
572
|
condition: isImageWidthAdjustable || isHorizontalFullBleed || isVerticalFullBleed,
|
|
507
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
573
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_ConditionalWrapper.default, {
|
|
574
|
+
WrapperComponent: FullBleedPaddingWrapper,
|
|
575
|
+
wrapperProps: fullBleedPaddingValues,
|
|
576
|
+
condition: hasFullBleedPadding,
|
|
577
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_FullBleedContent.default, {
|
|
578
|
+
borderRadius: fullBleedBorderRadius,
|
|
579
|
+
...fullBleedContentPropsClean,
|
|
580
|
+
position: fullBleedContentPosition,
|
|
581
|
+
cardState: undefined
|
|
582
|
+
})
|
|
512
583
|
})
|
|
513
584
|
})]
|
|
514
585
|
}) : null, footer && /*#__PURE__*/(0, _jsxRuntime.jsx)(_CardFooter.default, {
|
|
@@ -563,6 +634,13 @@ Card.propTypes = {
|
|
|
563
634
|
* Custom card footer padding.
|
|
564
635
|
*/
|
|
565
636
|
footerPadding: _componentsBase.paddingProp.propType,
|
|
637
|
+
/**
|
|
638
|
+
* Custom full bleed content padding.
|
|
639
|
+
* When true, inherits the card's own padding (from variant/tokens).
|
|
640
|
+
* When an object, provides custom padding overrides: { paddingTop, paddingBottom, paddingLeft, paddingRight }.
|
|
641
|
+
* Padding is automatically adjusted based on the full bleed content's position.
|
|
642
|
+
*/
|
|
643
|
+
fullBleedPadding: _propTypes.default.oneOfType([_propTypes.default.bool, _componentsBase.paddingProp.propType]),
|
|
566
644
|
/**
|
|
567
645
|
* Full bleed image to be placed on the card, deprecated in favor of `fullBleedContent`.
|
|
568
646
|
*
|
|
@@ -44,8 +44,11 @@ const ContentOverlay = /*#__PURE__*/_styledComponents.default.div.withConfig({
|
|
|
44
44
|
})({
|
|
45
45
|
position: 'absolute',
|
|
46
46
|
width: '100%',
|
|
47
|
+
top: 0,
|
|
48
|
+
left: 0,
|
|
47
49
|
height: '100%',
|
|
48
|
-
zIndex: _constants.BACKDROP_Z_INDEX
|
|
50
|
+
zIndex: _constants.BACKDROP_Z_INDEX,
|
|
51
|
+
pointerEvents: 'none'
|
|
49
52
|
});
|
|
50
53
|
const FullscreenOverlay = /*#__PURE__*/_styledComponents.default.div.withConfig({
|
|
51
54
|
displayName: "Spinner__FullscreenOverlay",
|
|
@@ -67,8 +70,15 @@ const FullscreenOverlay = /*#__PURE__*/_styledComponents.default.div.withConfig(
|
|
|
67
70
|
const OpaqueContainer = /*#__PURE__*/_styledComponents.default.div.withConfig({
|
|
68
71
|
displayName: "Spinner__OpaqueContainer",
|
|
69
72
|
componentId: "components-web__sc-116rqck-3"
|
|
70
|
-
})({
|
|
71
|
-
|
|
73
|
+
})(_ref3 => {
|
|
74
|
+
let {
|
|
75
|
+
show = true
|
|
76
|
+
} = _ref3;
|
|
77
|
+
return {
|
|
78
|
+
...(show && {
|
|
79
|
+
opacity: _constants.BACKDROP_OPACITY
|
|
80
|
+
})
|
|
81
|
+
};
|
|
72
82
|
});
|
|
73
83
|
const recursiveMap = (children, fn) => _react.default.Children.map(children, child => {
|
|
74
84
|
if (! /*#__PURE__*/_react.default.isValidElement(child)) {
|
|
@@ -85,7 +95,7 @@ const recursiveMap = (children, fn) => _react.default.Children.map(children, chi
|
|
|
85
95
|
/**
|
|
86
96
|
* Loading indicator.
|
|
87
97
|
*/
|
|
88
|
-
const Spinner = /*#__PURE__*/_react.default.forwardRef((
|
|
98
|
+
const Spinner = /*#__PURE__*/_react.default.forwardRef((_ref4, ref) => {
|
|
89
99
|
let {
|
|
90
100
|
children,
|
|
91
101
|
fullScreen = false,
|
|
@@ -96,8 +106,9 @@ const Spinner = /*#__PURE__*/_react.default.forwardRef((_ref3, ref) => {
|
|
|
96
106
|
isStatic = false,
|
|
97
107
|
tokens,
|
|
98
108
|
variant = {},
|
|
109
|
+
persistChildrenState = false,
|
|
99
110
|
...rest
|
|
100
|
-
} =
|
|
111
|
+
} = _ref4;
|
|
101
112
|
const {
|
|
102
113
|
fullScreenOverLayBackground,
|
|
103
114
|
size,
|
|
@@ -107,6 +118,32 @@ const Spinner = /*#__PURE__*/_react.default.forwardRef((_ref3, ref) => {
|
|
|
107
118
|
size: sizeVariant = 'large'
|
|
108
119
|
} = variant;
|
|
109
120
|
(0, _componentsBase.useScrollBlocking)([fullScreen, show]);
|
|
121
|
+
|
|
122
|
+
// Overlay spinner with persistChildrenState enabled
|
|
123
|
+
if (children && persistChildrenState) {
|
|
124
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(SpinnerContainer, {
|
|
125
|
+
inline: inline,
|
|
126
|
+
"aria-live": "assertive",
|
|
127
|
+
overlay: true,
|
|
128
|
+
...selectProps(rest),
|
|
129
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(OpaqueContainer, {
|
|
130
|
+
show: show,
|
|
131
|
+
...(show ? {
|
|
132
|
+
inert: 'true',
|
|
133
|
+
'aria-hidden': 'true'
|
|
134
|
+
} : {}),
|
|
135
|
+
children: children
|
|
136
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(ContentOverlay, {}), show && /*#__PURE__*/(0, _jsxRuntime.jsx)(_SpinnerContent.default, {
|
|
137
|
+
label: label,
|
|
138
|
+
labelPosition: labelPosition,
|
|
139
|
+
overlay: true,
|
|
140
|
+
size: size,
|
|
141
|
+
thickness: thickness,
|
|
142
|
+
sizeVariant: sizeVariant,
|
|
143
|
+
isStatic: isStatic
|
|
144
|
+
})]
|
|
145
|
+
});
|
|
146
|
+
}
|
|
110
147
|
if (!show) {
|
|
111
148
|
return children ?? null;
|
|
112
149
|
}
|
|
@@ -136,7 +173,7 @@ const Spinner = /*#__PURE__*/_react.default.forwardRef((_ref3, ref) => {
|
|
|
136
173
|
}
|
|
137
174
|
|
|
138
175
|
// Overlay spinner
|
|
139
|
-
if (children) {
|
|
176
|
+
if (children && !persistChildrenState) {
|
|
140
177
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(SpinnerContainer, {
|
|
141
178
|
inline: inline,
|
|
142
179
|
"aria-live": "assertive",
|
|
@@ -218,6 +255,12 @@ Spinner.propTypes = {
|
|
|
218
255
|
/**
|
|
219
256
|
* Determine where the label of the spinner should be placed, left, right, bottom or top.
|
|
220
257
|
*/
|
|
221
|
-
labelPosition: _propTypes.default.string
|
|
258
|
+
labelPosition: _propTypes.default.string,
|
|
259
|
+
/**
|
|
260
|
+
* When true, preserves the state of overlaid children when `show` toggles.
|
|
261
|
+
* Only applicable when the Spinner is used in overlay mode (with children).
|
|
262
|
+
* Use this when overlaying stateful components such as Micro Frontends (MFEs).
|
|
263
|
+
*/
|
|
264
|
+
persistChildrenState: _propTypes.default.bool
|
|
222
265
|
};
|
|
223
266
|
var _default = exports.default = Spinner;
|
|
@@ -10,6 +10,12 @@ Object.defineProperty(exports, "getFullBleedBorderRadius", {
|
|
|
10
10
|
return _getFullBleedBorderRadius.default;
|
|
11
11
|
}
|
|
12
12
|
});
|
|
13
|
+
Object.defineProperty(exports, "getFullBleedPaddingSides", {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
get: function () {
|
|
16
|
+
return _useFullBleedContentProps.getFullBleedPaddingSides;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
13
19
|
Object.defineProperty(exports, "useFullBleedContentProps", {
|
|
14
20
|
enumerable: true,
|
|
15
21
|
get: function () {
|
|
@@ -18,6 +24,8 @@ Object.defineProperty(exports, "useFullBleedContentProps", {
|
|
|
18
24
|
});
|
|
19
25
|
var _FullBleedContent = _interopRequireDefault(require("./FullBleedContent"));
|
|
20
26
|
var _getFullBleedBorderRadius = _interopRequireDefault(require("./getFullBleedBorderRadius"));
|
|
21
|
-
var _useFullBleedContentProps =
|
|
27
|
+
var _useFullBleedContentProps = _interopRequireWildcard(require("./useFullBleedContentProps"));
|
|
28
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
29
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
22
30
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
23
31
|
var _default = exports.default = _FullBleedContent.default;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.default = void 0;
|
|
6
|
+
exports.getFullBleedPaddingSides = exports.default = void 0;
|
|
7
7
|
var _componentsBase = require("@telus-uds/components-base");
|
|
8
8
|
const getContentStackDirection = fullBleedContentPosition => {
|
|
9
9
|
switch (fullBleedContentPosition) {
|
|
@@ -32,6 +32,21 @@ const getContentStackAlign = fullBleedContentAlign => {
|
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Calculates which padding sides should be applied to full bleed content
|
|
37
|
+
* based on its position relative to card content.
|
|
38
|
+
* The side adjacent to card body gets no padding since the body already provides spacing.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} position - 'top' | 'bottom' | 'left' | 'right' | 'none'
|
|
41
|
+
* @returns {object} Boolean flags for which sides to apply padding
|
|
42
|
+
*/
|
|
43
|
+
const getFullBleedPaddingSides = position => ({
|
|
44
|
+
applyPaddingTop: position !== 'bottom',
|
|
45
|
+
applyPaddingBottom: position !== 'top',
|
|
46
|
+
applyPaddingLeft: position !== 'right',
|
|
47
|
+
applyPaddingRight: position !== 'left'
|
|
48
|
+
});
|
|
49
|
+
|
|
35
50
|
/**
|
|
36
51
|
* Resolves a set of `FullBleedContent` props into the variables a container needs to
|
|
37
52
|
* correctly position a `FullBleedContent` component for the current viewport.
|
|
@@ -39,6 +54,7 @@ const getContentStackAlign = fullBleedContentAlign => {
|
|
|
39
54
|
* @param {object} [fullBleedContent] - a set of valid proptypes for FullBleedContent
|
|
40
55
|
* @returns
|
|
41
56
|
*/
|
|
57
|
+
exports.getFullBleedPaddingSides = getFullBleedPaddingSides;
|
|
42
58
|
const useFullBleedContentProps = fullBleedContent => {
|
|
43
59
|
const {
|
|
44
60
|
align: fullBleedContentAlignProp,
|
|
@@ -53,12 +69,16 @@ const useFullBleedContentProps = fullBleedContent => {
|
|
|
53
69
|
const fullBleedContentAlign = (0, _componentsBase.useResponsiveProp)(fullBleedContentAlignProp, 'stretch');
|
|
54
70
|
const contentStackAlign = getContentStackAlign(fullBleedContentAlign);
|
|
55
71
|
const fullBleedContentChildrenAlign = (0, _componentsBase.useResponsiveProp)(fullBleedContentChildrenAlignProp, fullBleedContentAlign);
|
|
72
|
+
|
|
73
|
+
// Get padding sides based on position
|
|
74
|
+
const fullBleedPaddingSides = getFullBleedPaddingSides(fullBleedContentPosition);
|
|
56
75
|
return {
|
|
57
76
|
contentStackAlign,
|
|
58
77
|
contentStackDirection,
|
|
59
78
|
fullBleedContentPosition,
|
|
60
79
|
fullBleedContentProps,
|
|
61
|
-
fullBleedContentChildrenAlign
|
|
80
|
+
fullBleedContentChildrenAlign,
|
|
81
|
+
fullBleedPaddingSides
|
|
62
82
|
};
|
|
63
83
|
};
|
|
64
84
|
var _default = exports.default = useFullBleedContentProps;
|
package/lib/esm/Card/Card.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import { Card as CardBase, getTokensPropType, paddingProp, responsiveProps, selectSystemProps, StackView, useThemeTokens, useThemeTokensCallback, variantProp, a11yProps, viewProps, PressableCardBase, useResponsiveProp, hrefAttrsProp } from '@telus-uds/components-base';
|
|
3
|
+
import { Card as CardBase, getTokensPropType, paddingProp, responsiveProps, selectSystemProps, StackView, useThemeTokens, useThemeTokensCallback, variantProp, a11yProps, viewProps, PressableCardBase, useResponsiveProp, hrefAttrsProp, useAllViewportTokens } from '@telus-uds/components-base';
|
|
4
4
|
import styled from 'styled-components';
|
|
5
5
|
import CardContent from './CardContent';
|
|
6
6
|
import CardFooter from './CardFooter';
|
|
@@ -177,6 +177,52 @@ const FocusBorder = /*#__PURE__*/styled.div.withConfig({
|
|
|
177
177
|
zIndex: 2
|
|
178
178
|
};
|
|
179
179
|
});
|
|
180
|
+
const FullBleedPaddingWrapper = /*#__PURE__*/styled.div.withConfig({
|
|
181
|
+
displayName: "Card__FullBleedPaddingWrapper",
|
|
182
|
+
componentId: "components-web__sc-1elbtwd-4"
|
|
183
|
+
})(_ref4 => {
|
|
184
|
+
let {
|
|
185
|
+
paddingTop,
|
|
186
|
+
paddingBottom,
|
|
187
|
+
paddingLeft,
|
|
188
|
+
paddingRight
|
|
189
|
+
} = _ref4;
|
|
190
|
+
return {
|
|
191
|
+
paddingTop,
|
|
192
|
+
paddingBottom,
|
|
193
|
+
paddingLeft,
|
|
194
|
+
paddingRight,
|
|
195
|
+
display: 'flex',
|
|
196
|
+
flexDirection: 'column'
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Calculates the padding values for full bleed content.
|
|
202
|
+
* Inherits the card's theme padding (minus borderWidth to visually match card body padding)
|
|
203
|
+
* and allows custom overrides via the fullBleedPadding prop.
|
|
204
|
+
*
|
|
205
|
+
* @param {object} themeTokens - The card's resolved theme tokens (contains padding values and borderWidth)
|
|
206
|
+
* @param {object|boolean} fullBleedPadding - true to inherit card padding, or an object with overrides
|
|
207
|
+
* @param {object} paddingSides - Boolean flags for which sides to apply padding
|
|
208
|
+
* @returns {object} Padding values for each side in px
|
|
209
|
+
*/
|
|
210
|
+
const calculateFullBleedPadding = (themeTokens, fullBleedPadding, paddingSides) => {
|
|
211
|
+
const customTokens = typeof fullBleedPadding === 'object' ? fullBleedPadding : undefined;
|
|
212
|
+
const borderWidth = themeTokens.borderWidth || 0;
|
|
213
|
+
const applyPadding = side => {
|
|
214
|
+
const sideKey = `applyPadding${side}`;
|
|
215
|
+
if (!paddingSides[sideKey]) return 0;
|
|
216
|
+
const tokenKey = `padding${side}`;
|
|
217
|
+
return customTokens?.[tokenKey] ?? themeTokens[tokenKey] - borderWidth ?? 0;
|
|
218
|
+
};
|
|
219
|
+
return {
|
|
220
|
+
paddingTop: applyPadding('Top'),
|
|
221
|
+
paddingBottom: applyPadding('Bottom'),
|
|
222
|
+
paddingLeft: applyPadding('Left'),
|
|
223
|
+
paddingRight: applyPadding('Right')
|
|
224
|
+
};
|
|
225
|
+
};
|
|
180
226
|
const Card = /*#__PURE__*/React.forwardRef(function () {
|
|
181
227
|
let {
|
|
182
228
|
children,
|
|
@@ -184,6 +230,7 @@ const Card = /*#__PURE__*/React.forwardRef(function () {
|
|
|
184
230
|
footerPadding,
|
|
185
231
|
fullBleedImage,
|
|
186
232
|
fullBleedContent = fullBleedImage,
|
|
233
|
+
fullBleedPadding,
|
|
187
234
|
tokens = {},
|
|
188
235
|
variant,
|
|
189
236
|
interactiveCard,
|
|
@@ -210,7 +257,8 @@ const Card = /*#__PURE__*/React.forwardRef(function () {
|
|
|
210
257
|
contentStackDirection,
|
|
211
258
|
fullBleedContentPosition,
|
|
212
259
|
fullBleedContentProps,
|
|
213
|
-
fullBleedContentChildrenAlign
|
|
260
|
+
fullBleedContentChildrenAlign,
|
|
261
|
+
fullBleedPaddingSides
|
|
214
262
|
} = useFullBleedContentProps(fullBleedContent);
|
|
215
263
|
const {
|
|
216
264
|
imgCol,
|
|
@@ -235,6 +283,12 @@ const Card = /*#__PURE__*/React.forwardRef(function () {
|
|
|
235
283
|
borderRadius
|
|
236
284
|
} = allThemeTokens;
|
|
237
285
|
|
|
286
|
+
// Get viewport-aware tokens for responsive full bleed padding
|
|
287
|
+
// useThemeTokens (above) doesn't pass viewport state, so it resolves to base/mobile tokens.
|
|
288
|
+
// useAllViewportTokens resolves tokens for ALL viewports + the current one (reactive to resize).
|
|
289
|
+
// This ensures fullBleedPadding matches CardContent's padding at every breakpoint.
|
|
290
|
+
const allViewportTokens = useAllViewportTokens('Card', tokens, variant);
|
|
291
|
+
|
|
238
292
|
// Interactive cards: merge variants for CardBase (outer container)
|
|
239
293
|
// The outer variant takes priority over interactiveCard.variant for the style property
|
|
240
294
|
// This ensures the gradient is only applied to CardBase, not PressableCardBase and avoid duplication
|
|
@@ -310,6 +364,13 @@ const Card = /*#__PURE__*/React.forwardRef(function () {
|
|
|
310
364
|
const hasFooter = Boolean(footer);
|
|
311
365
|
const fullBleedBorderRadius = getFullBleedBorderRadius(borderRadius, fullBleedContentPosition, hasFooter);
|
|
312
366
|
|
|
367
|
+
// Calculate full bleed content padding values
|
|
368
|
+
// When fullBleedPadding is truthy, apply padding around full bleed content
|
|
369
|
+
// Uses viewport-aware tokens (allViewportTokens.current) so padding matches
|
|
370
|
+
// CardContent's responsive padding at every breakpoint
|
|
371
|
+
const hasFullBleedPadding = Boolean(fullBleedPadding);
|
|
372
|
+
const fullBleedPaddingValues = hasFullBleedPadding ? calculateFullBleedPadding(allViewportTokens.current, fullBleedPadding, fullBleedPaddingSides) : null;
|
|
373
|
+
|
|
313
374
|
// takes imgCol from fullBleedContent if present, to dynamically set width of image
|
|
314
375
|
// card content will adapt to the size of image to add up to 100% width of card width
|
|
315
376
|
// pass as props to ConditionalWrapper
|
|
@@ -335,8 +396,8 @@ const Card = /*#__PURE__*/React.forwardRef(function () {
|
|
|
335
396
|
flexShrink: 1,
|
|
336
397
|
justifyContent: 'space-between'
|
|
337
398
|
};
|
|
338
|
-
const cardBaseTokens = Object.fromEntries(Object.entries(tokens).filter(
|
|
339
|
-
let [key] =
|
|
399
|
+
const cardBaseTokens = Object.fromEntries(Object.entries(tokens).filter(_ref5 => {
|
|
400
|
+
let [key] = _ref5;
|
|
340
401
|
return !['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight', ...(backgroundImage ? ['backgroundColor', 'gradient', 'backgroundGradient'] : [])].includes(key);
|
|
341
402
|
}));
|
|
342
403
|
const cardBaseVariant = backgroundImage ? {
|
|
@@ -448,11 +509,16 @@ const Card = /*#__PURE__*/React.forwardRef(function () {
|
|
|
448
509
|
WrapperComponent: DynamicWidthContainer,
|
|
449
510
|
wrapperProps: imageWrapperStyleProps,
|
|
450
511
|
condition: isImageWidthAdjustable || isHorizontalFullBleed || isVerticalFullBleed,
|
|
451
|
-
children: /*#__PURE__*/_jsx(
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
512
|
+
children: /*#__PURE__*/_jsx(ConditionalWrapper, {
|
|
513
|
+
WrapperComponent: FullBleedPaddingWrapper,
|
|
514
|
+
wrapperProps: fullBleedPaddingValues,
|
|
515
|
+
condition: hasFullBleedPadding,
|
|
516
|
+
children: /*#__PURE__*/_jsx(FullBleedContent, {
|
|
517
|
+
borderRadius: fullBleedBorderRadius,
|
|
518
|
+
...fullBleedContentPropsClean,
|
|
519
|
+
position: fullBleedContentPosition,
|
|
520
|
+
cardState: undefined
|
|
521
|
+
})
|
|
456
522
|
})
|
|
457
523
|
})]
|
|
458
524
|
}), /*#__PURE__*/_jsx(InteractiveOverlay, {
|
|
@@ -496,11 +562,16 @@ const Card = /*#__PURE__*/React.forwardRef(function () {
|
|
|
496
562
|
WrapperComponent: DynamicWidthContainer,
|
|
497
563
|
wrapperProps: imageWrapperStyleProps,
|
|
498
564
|
condition: isImageWidthAdjustable || isHorizontalFullBleed || isVerticalFullBleed,
|
|
499
|
-
children: /*#__PURE__*/_jsx(
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
565
|
+
children: /*#__PURE__*/_jsx(ConditionalWrapper, {
|
|
566
|
+
WrapperComponent: FullBleedPaddingWrapper,
|
|
567
|
+
wrapperProps: fullBleedPaddingValues,
|
|
568
|
+
condition: hasFullBleedPadding,
|
|
569
|
+
children: /*#__PURE__*/_jsx(FullBleedContent, {
|
|
570
|
+
borderRadius: fullBleedBorderRadius,
|
|
571
|
+
...fullBleedContentPropsClean,
|
|
572
|
+
position: fullBleedContentPosition,
|
|
573
|
+
cardState: undefined
|
|
574
|
+
})
|
|
504
575
|
})
|
|
505
576
|
})]
|
|
506
577
|
}) : null, footer && /*#__PURE__*/_jsx(CardFooter, {
|
|
@@ -555,6 +626,13 @@ Card.propTypes = {
|
|
|
555
626
|
* Custom card footer padding.
|
|
556
627
|
*/
|
|
557
628
|
footerPadding: paddingProp.propType,
|
|
629
|
+
/**
|
|
630
|
+
* Custom full bleed content padding.
|
|
631
|
+
* When true, inherits the card's own padding (from variant/tokens).
|
|
632
|
+
* When an object, provides custom padding overrides: { paddingTop, paddingBottom, paddingLeft, paddingRight }.
|
|
633
|
+
* Padding is automatically adjusted based on the full bleed content's position.
|
|
634
|
+
*/
|
|
635
|
+
fullBleedPadding: PropTypes.oneOfType([PropTypes.bool, paddingProp.propType]),
|
|
558
636
|
/**
|
|
559
637
|
* Full bleed image to be placed on the card, deprecated in favor of `fullBleedContent`.
|
|
560
638
|
*
|
|
@@ -37,8 +37,11 @@ const ContentOverlay = /*#__PURE__*/styled.div.withConfig({
|
|
|
37
37
|
})({
|
|
38
38
|
position: 'absolute',
|
|
39
39
|
width: '100%',
|
|
40
|
+
top: 0,
|
|
41
|
+
left: 0,
|
|
40
42
|
height: '100%',
|
|
41
|
-
zIndex: BACKDROP_Z_INDEX
|
|
43
|
+
zIndex: BACKDROP_Z_INDEX,
|
|
44
|
+
pointerEvents: 'none'
|
|
42
45
|
});
|
|
43
46
|
const FullscreenOverlay = /*#__PURE__*/styled.div.withConfig({
|
|
44
47
|
displayName: "Spinner__FullscreenOverlay",
|
|
@@ -60,8 +63,15 @@ const FullscreenOverlay = /*#__PURE__*/styled.div.withConfig({
|
|
|
60
63
|
const OpaqueContainer = /*#__PURE__*/styled.div.withConfig({
|
|
61
64
|
displayName: "Spinner__OpaqueContainer",
|
|
62
65
|
componentId: "components-web__sc-116rqck-3"
|
|
63
|
-
})({
|
|
64
|
-
|
|
66
|
+
})(_ref3 => {
|
|
67
|
+
let {
|
|
68
|
+
show = true
|
|
69
|
+
} = _ref3;
|
|
70
|
+
return {
|
|
71
|
+
...(show && {
|
|
72
|
+
opacity: BACKDROP_OPACITY
|
|
73
|
+
})
|
|
74
|
+
};
|
|
65
75
|
});
|
|
66
76
|
const recursiveMap = (children, fn) => React.Children.map(children, child => {
|
|
67
77
|
if (! /*#__PURE__*/React.isValidElement(child)) {
|
|
@@ -78,7 +88,7 @@ const recursiveMap = (children, fn) => React.Children.map(children, child => {
|
|
|
78
88
|
/**
|
|
79
89
|
* Loading indicator.
|
|
80
90
|
*/
|
|
81
|
-
const Spinner = /*#__PURE__*/React.forwardRef((
|
|
91
|
+
const Spinner = /*#__PURE__*/React.forwardRef((_ref4, ref) => {
|
|
82
92
|
let {
|
|
83
93
|
children,
|
|
84
94
|
fullScreen = false,
|
|
@@ -89,8 +99,9 @@ const Spinner = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
|
89
99
|
isStatic = false,
|
|
90
100
|
tokens,
|
|
91
101
|
variant = {},
|
|
102
|
+
persistChildrenState = false,
|
|
92
103
|
...rest
|
|
93
|
-
} =
|
|
104
|
+
} = _ref4;
|
|
94
105
|
const {
|
|
95
106
|
fullScreenOverLayBackground,
|
|
96
107
|
size,
|
|
@@ -100,6 +111,32 @@ const Spinner = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
|
100
111
|
size: sizeVariant = 'large'
|
|
101
112
|
} = variant;
|
|
102
113
|
useScrollBlocking([fullScreen, show]);
|
|
114
|
+
|
|
115
|
+
// Overlay spinner with persistChildrenState enabled
|
|
116
|
+
if (children && persistChildrenState) {
|
|
117
|
+
return /*#__PURE__*/_jsxs(SpinnerContainer, {
|
|
118
|
+
inline: inline,
|
|
119
|
+
"aria-live": "assertive",
|
|
120
|
+
overlay: true,
|
|
121
|
+
...selectProps(rest),
|
|
122
|
+
children: [/*#__PURE__*/_jsx(OpaqueContainer, {
|
|
123
|
+
show: show,
|
|
124
|
+
...(show ? {
|
|
125
|
+
inert: 'true',
|
|
126
|
+
'aria-hidden': 'true'
|
|
127
|
+
} : {}),
|
|
128
|
+
children: children
|
|
129
|
+
}), /*#__PURE__*/_jsx(ContentOverlay, {}), show && /*#__PURE__*/_jsx(SpinnerContent, {
|
|
130
|
+
label: label,
|
|
131
|
+
labelPosition: labelPosition,
|
|
132
|
+
overlay: true,
|
|
133
|
+
size: size,
|
|
134
|
+
thickness: thickness,
|
|
135
|
+
sizeVariant: sizeVariant,
|
|
136
|
+
isStatic: isStatic
|
|
137
|
+
})]
|
|
138
|
+
});
|
|
139
|
+
}
|
|
103
140
|
if (!show) {
|
|
104
141
|
return children ?? null;
|
|
105
142
|
}
|
|
@@ -129,7 +166,7 @@ const Spinner = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
|
129
166
|
}
|
|
130
167
|
|
|
131
168
|
// Overlay spinner
|
|
132
|
-
if (children) {
|
|
169
|
+
if (children && !persistChildrenState) {
|
|
133
170
|
return /*#__PURE__*/_jsxs(SpinnerContainer, {
|
|
134
171
|
inline: inline,
|
|
135
172
|
"aria-live": "assertive",
|
|
@@ -211,6 +248,12 @@ Spinner.propTypes = {
|
|
|
211
248
|
/**
|
|
212
249
|
* Determine where the label of the spinner should be placed, left, right, bottom or top.
|
|
213
250
|
*/
|
|
214
|
-
labelPosition: PropTypes.string
|
|
251
|
+
labelPosition: PropTypes.string,
|
|
252
|
+
/**
|
|
253
|
+
* When true, preserves the state of overlaid children when `show` toggles.
|
|
254
|
+
* Only applicable when the Spinner is used in overlay mode (with children).
|
|
255
|
+
* Use this when overlaying stateful components such as Micro Frontends (MFEs).
|
|
256
|
+
*/
|
|
257
|
+
persistChildrenState: PropTypes.bool
|
|
215
258
|
};
|
|
216
259
|
export default Spinner;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import FullBleedContent from './FullBleedContent';
|
|
2
2
|
export default FullBleedContent;
|
|
3
3
|
export { default as getFullBleedBorderRadius } from './getFullBleedBorderRadius';
|
|
4
|
-
export { default as useFullBleedContentProps } from './useFullBleedContentProps';
|
|
4
|
+
export { default as useFullBleedContentProps, getFullBleedPaddingSides } from './useFullBleedContentProps';
|
|
@@ -26,6 +26,21 @@ const getContentStackAlign = fullBleedContentAlign => {
|
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Calculates which padding sides should be applied to full bleed content
|
|
31
|
+
* based on its position relative to card content.
|
|
32
|
+
* The side adjacent to card body gets no padding since the body already provides spacing.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} position - 'top' | 'bottom' | 'left' | 'right' | 'none'
|
|
35
|
+
* @returns {object} Boolean flags for which sides to apply padding
|
|
36
|
+
*/
|
|
37
|
+
const getFullBleedPaddingSides = position => ({
|
|
38
|
+
applyPaddingTop: position !== 'bottom',
|
|
39
|
+
applyPaddingBottom: position !== 'top',
|
|
40
|
+
applyPaddingLeft: position !== 'right',
|
|
41
|
+
applyPaddingRight: position !== 'left'
|
|
42
|
+
});
|
|
43
|
+
|
|
29
44
|
/**
|
|
30
45
|
* Resolves a set of `FullBleedContent` props into the variables a container needs to
|
|
31
46
|
* correctly position a `FullBleedContent` component for the current viewport.
|
|
@@ -47,12 +62,17 @@ const useFullBleedContentProps = fullBleedContent => {
|
|
|
47
62
|
const fullBleedContentAlign = useResponsiveProp(fullBleedContentAlignProp, 'stretch');
|
|
48
63
|
const contentStackAlign = getContentStackAlign(fullBleedContentAlign);
|
|
49
64
|
const fullBleedContentChildrenAlign = useResponsiveProp(fullBleedContentChildrenAlignProp, fullBleedContentAlign);
|
|
65
|
+
|
|
66
|
+
// Get padding sides based on position
|
|
67
|
+
const fullBleedPaddingSides = getFullBleedPaddingSides(fullBleedContentPosition);
|
|
50
68
|
return {
|
|
51
69
|
contentStackAlign,
|
|
52
70
|
contentStackDirection,
|
|
53
71
|
fullBleedContentPosition,
|
|
54
72
|
fullBleedContentProps,
|
|
55
|
-
fullBleedContentChildrenAlign
|
|
73
|
+
fullBleedContentChildrenAlign,
|
|
74
|
+
fullBleedPaddingSides
|
|
56
75
|
};
|
|
57
76
|
};
|
|
77
|
+
export { getFullBleedPaddingSides };
|
|
58
78
|
export default useFullBleedContentProps;
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
],
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@gorhom/portal": "^1.0.14",
|
|
8
|
-
"@telus-uds/components-base": "^3.28.
|
|
8
|
+
"@telus-uds/components-base": "^3.28.2",
|
|
9
9
|
"@telus-uds/system-constants": "^3.0.0",
|
|
10
10
|
"@telus-uds/system-theme-tokens": "^4.20.0",
|
|
11
11
|
"fscreen": "^1.2.0",
|
|
@@ -82,5 +82,5 @@
|
|
|
82
82
|
"skip": true
|
|
83
83
|
},
|
|
84
84
|
"types": "types/index.d.ts",
|
|
85
|
-
"version": "4.
|
|
85
|
+
"version": "4.19.0"
|
|
86
86
|
}
|
package/src/Card/Card.jsx
CHANGED
|
@@ -14,7 +14,8 @@ import {
|
|
|
14
14
|
viewProps,
|
|
15
15
|
PressableCardBase,
|
|
16
16
|
useResponsiveProp,
|
|
17
|
-
hrefAttrsProp
|
|
17
|
+
hrefAttrsProp,
|
|
18
|
+
useAllViewportTokens
|
|
18
19
|
} from '@telus-uds/components-base'
|
|
19
20
|
import styled from 'styled-components'
|
|
20
21
|
import CardContent from './CardContent'
|
|
@@ -170,6 +171,46 @@ const FocusBorder = styled.div(({ borderWidth, borderColor, borderRadius }) => (
|
|
|
170
171
|
zIndex: 2
|
|
171
172
|
}))
|
|
172
173
|
|
|
174
|
+
const FullBleedPaddingWrapper = styled.div(
|
|
175
|
+
({ paddingTop, paddingBottom, paddingLeft, paddingRight }) => ({
|
|
176
|
+
paddingTop,
|
|
177
|
+
paddingBottom,
|
|
178
|
+
paddingLeft,
|
|
179
|
+
paddingRight,
|
|
180
|
+
display: 'flex',
|
|
181
|
+
flexDirection: 'column'
|
|
182
|
+
})
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Calculates the padding values for full bleed content.
|
|
187
|
+
* Inherits the card's theme padding (minus borderWidth to visually match card body padding)
|
|
188
|
+
* and allows custom overrides via the fullBleedPadding prop.
|
|
189
|
+
*
|
|
190
|
+
* @param {object} themeTokens - The card's resolved theme tokens (contains padding values and borderWidth)
|
|
191
|
+
* @param {object|boolean} fullBleedPadding - true to inherit card padding, or an object with overrides
|
|
192
|
+
* @param {object} paddingSides - Boolean flags for which sides to apply padding
|
|
193
|
+
* @returns {object} Padding values for each side in px
|
|
194
|
+
*/
|
|
195
|
+
const calculateFullBleedPadding = (themeTokens, fullBleedPadding, paddingSides) => {
|
|
196
|
+
const customTokens = typeof fullBleedPadding === 'object' ? fullBleedPadding : undefined
|
|
197
|
+
const borderWidth = themeTokens.borderWidth || 0
|
|
198
|
+
|
|
199
|
+
const applyPadding = (side) => {
|
|
200
|
+
const sideKey = `applyPadding${side}`
|
|
201
|
+
if (!paddingSides[sideKey]) return 0
|
|
202
|
+
const tokenKey = `padding${side}`
|
|
203
|
+
return customTokens?.[tokenKey] ?? themeTokens[tokenKey] - borderWidth ?? 0
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
paddingTop: applyPadding('Top'),
|
|
208
|
+
paddingBottom: applyPadding('Bottom'),
|
|
209
|
+
paddingLeft: applyPadding('Left'),
|
|
210
|
+
paddingRight: applyPadding('Right')
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
173
214
|
const Card = React.forwardRef(
|
|
174
215
|
(
|
|
175
216
|
{
|
|
@@ -178,6 +219,7 @@ const Card = React.forwardRef(
|
|
|
178
219
|
footerPadding,
|
|
179
220
|
fullBleedImage,
|
|
180
221
|
fullBleedContent = fullBleedImage,
|
|
222
|
+
fullBleedPadding,
|
|
181
223
|
tokens = {},
|
|
182
224
|
variant,
|
|
183
225
|
interactiveCard,
|
|
@@ -198,7 +240,8 @@ const Card = React.forwardRef(
|
|
|
198
240
|
contentStackDirection,
|
|
199
241
|
fullBleedContentPosition,
|
|
200
242
|
fullBleedContentProps,
|
|
201
|
-
fullBleedContentChildrenAlign
|
|
243
|
+
fullBleedContentChildrenAlign,
|
|
244
|
+
fullBleedPaddingSides
|
|
202
245
|
} = useFullBleedContentProps(fullBleedContent)
|
|
203
246
|
|
|
204
247
|
const {
|
|
@@ -220,6 +263,12 @@ const Card = React.forwardRef(
|
|
|
220
263
|
const allThemeTokens = useThemeTokens('Card', tokens, variantForTokens)
|
|
221
264
|
const { borderRadius } = allThemeTokens
|
|
222
265
|
|
|
266
|
+
// Get viewport-aware tokens for responsive full bleed padding
|
|
267
|
+
// useThemeTokens (above) doesn't pass viewport state, so it resolves to base/mobile tokens.
|
|
268
|
+
// useAllViewportTokens resolves tokens for ALL viewports + the current one (reactive to resize).
|
|
269
|
+
// This ensures fullBleedPadding matches CardContent's padding at every breakpoint.
|
|
270
|
+
const allViewportTokens = useAllViewportTokens('Card', tokens, variant)
|
|
271
|
+
|
|
223
272
|
// Interactive cards: merge variants for CardBase (outer container)
|
|
224
273
|
// The outer variant takes priority over interactiveCard.variant for the style property
|
|
225
274
|
// This ensures the gradient is only applied to CardBase, not PressableCardBase and avoid duplication
|
|
@@ -312,6 +361,19 @@ const Card = React.forwardRef(
|
|
|
312
361
|
hasFooter
|
|
313
362
|
)
|
|
314
363
|
|
|
364
|
+
// Calculate full bleed content padding values
|
|
365
|
+
// When fullBleedPadding is truthy, apply padding around full bleed content
|
|
366
|
+
// Uses viewport-aware tokens (allViewportTokens.current) so padding matches
|
|
367
|
+
// CardContent's responsive padding at every breakpoint
|
|
368
|
+
const hasFullBleedPadding = Boolean(fullBleedPadding)
|
|
369
|
+
const fullBleedPaddingValues = hasFullBleedPadding
|
|
370
|
+
? calculateFullBleedPadding(
|
|
371
|
+
allViewportTokens.current,
|
|
372
|
+
fullBleedPadding,
|
|
373
|
+
fullBleedPaddingSides
|
|
374
|
+
)
|
|
375
|
+
: null
|
|
376
|
+
|
|
315
377
|
// takes imgCol from fullBleedContent if present, to dynamically set width of image
|
|
316
378
|
// card content will adapt to the size of image to add up to 100% width of card width
|
|
317
379
|
// pass as props to ConditionalWrapper
|
|
@@ -503,12 +565,18 @@ const Card = React.forwardRef(
|
|
|
503
565
|
isImageWidthAdjustable || isHorizontalFullBleed || isVerticalFullBleed
|
|
504
566
|
}
|
|
505
567
|
>
|
|
506
|
-
<
|
|
507
|
-
|
|
508
|
-
{
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
568
|
+
<ConditionalWrapper
|
|
569
|
+
WrapperComponent={FullBleedPaddingWrapper}
|
|
570
|
+
wrapperProps={fullBleedPaddingValues}
|
|
571
|
+
condition={hasFullBleedPadding}
|
|
572
|
+
>
|
|
573
|
+
<FullBleedContent
|
|
574
|
+
borderRadius={fullBleedBorderRadius}
|
|
575
|
+
{...fullBleedContentPropsClean}
|
|
576
|
+
position={fullBleedContentPosition}
|
|
577
|
+
cardState={undefined}
|
|
578
|
+
/>
|
|
579
|
+
</ConditionalWrapper>
|
|
512
580
|
</ConditionalWrapper>
|
|
513
581
|
)}
|
|
514
582
|
</StackView>
|
|
@@ -571,12 +639,18 @@ const Card = React.forwardRef(
|
|
|
571
639
|
wrapperProps={imageWrapperStyleProps}
|
|
572
640
|
condition={isImageWidthAdjustable || isHorizontalFullBleed || isVerticalFullBleed}
|
|
573
641
|
>
|
|
574
|
-
<
|
|
575
|
-
|
|
576
|
-
{
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
642
|
+
<ConditionalWrapper
|
|
643
|
+
WrapperComponent={FullBleedPaddingWrapper}
|
|
644
|
+
wrapperProps={fullBleedPaddingValues}
|
|
645
|
+
condition={hasFullBleedPadding}
|
|
646
|
+
>
|
|
647
|
+
<FullBleedContent
|
|
648
|
+
borderRadius={fullBleedBorderRadius}
|
|
649
|
+
{...fullBleedContentPropsClean}
|
|
650
|
+
position={fullBleedContentPosition}
|
|
651
|
+
cardState={undefined}
|
|
652
|
+
/>
|
|
653
|
+
</ConditionalWrapper>
|
|
580
654
|
</ConditionalWrapper>
|
|
581
655
|
</StackView>
|
|
582
656
|
) : null}
|
|
@@ -636,6 +710,13 @@ Card.propTypes = {
|
|
|
636
710
|
* Custom card footer padding.
|
|
637
711
|
*/
|
|
638
712
|
footerPadding: paddingProp.propType,
|
|
713
|
+
/**
|
|
714
|
+
* Custom full bleed content padding.
|
|
715
|
+
* When true, inherits the card's own padding (from variant/tokens).
|
|
716
|
+
* When an object, provides custom padding overrides: { paddingTop, paddingBottom, paddingLeft, paddingRight }.
|
|
717
|
+
* Padding is automatically adjusted based on the full bleed content's position.
|
|
718
|
+
*/
|
|
719
|
+
fullBleedPadding: PropTypes.oneOfType([PropTypes.bool, paddingProp.propType]),
|
|
639
720
|
/**
|
|
640
721
|
* Full bleed image to be placed on the card, deprecated in favor of `fullBleedContent`.
|
|
641
722
|
*
|
package/src/Spinner/Spinner.jsx
CHANGED
|
@@ -28,8 +28,11 @@ const SpinnerContainer = styled.div(({ inline, fullScreen, overlay }) => ({
|
|
|
28
28
|
const ContentOverlay = styled.div({
|
|
29
29
|
position: 'absolute',
|
|
30
30
|
width: '100%',
|
|
31
|
+
top: 0,
|
|
32
|
+
left: 0,
|
|
31
33
|
height: '100%',
|
|
32
|
-
zIndex: BACKDROP_Z_INDEX
|
|
34
|
+
zIndex: BACKDROP_Z_INDEX,
|
|
35
|
+
pointerEvents: 'none'
|
|
33
36
|
})
|
|
34
37
|
|
|
35
38
|
const FullscreenOverlay = styled.div(({ fullScreenOverLayBackground }) => ({
|
|
@@ -41,9 +44,12 @@ const FullscreenOverlay = styled.div(({ fullScreenOverLayBackground }) => ({
|
|
|
41
44
|
zIndex: BACKDROP_Z_INDEX,
|
|
42
45
|
backgroundColor: fullScreenOverLayBackground
|
|
43
46
|
}))
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
|
|
48
|
+
const OpaqueContainer = styled.div(({ show = true }) => ({
|
|
49
|
+
...(show && {
|
|
50
|
+
opacity: BACKDROP_OPACITY
|
|
51
|
+
})
|
|
52
|
+
}))
|
|
47
53
|
|
|
48
54
|
const recursiveMap = (children, fn) =>
|
|
49
55
|
React.Children.map(children, (child) => {
|
|
@@ -76,6 +82,7 @@ const Spinner = React.forwardRef(
|
|
|
76
82
|
isStatic = false,
|
|
77
83
|
tokens,
|
|
78
84
|
variant = {},
|
|
85
|
+
persistChildrenState = false,
|
|
79
86
|
...rest
|
|
80
87
|
},
|
|
81
88
|
ref
|
|
@@ -90,6 +97,30 @@ const Spinner = React.forwardRef(
|
|
|
90
97
|
|
|
91
98
|
useScrollBlocking([fullScreen, show])
|
|
92
99
|
|
|
100
|
+
// Overlay spinner with persistChildrenState enabled
|
|
101
|
+
if (children && persistChildrenState) {
|
|
102
|
+
return (
|
|
103
|
+
<SpinnerContainer inline={inline} aria-live="assertive" overlay {...selectProps(rest)}>
|
|
104
|
+
{/* Children ALWAYS rendered in same position - OpaqueContainer styles toggle with show */}
|
|
105
|
+
<OpaqueContainer show={show} {...(show ? { inert: 'true', 'aria-hidden': 'true' } : {})}>
|
|
106
|
+
{children}
|
|
107
|
+
</OpaqueContainer>
|
|
108
|
+
<ContentOverlay />
|
|
109
|
+
{show && (
|
|
110
|
+
<SpinnerContent
|
|
111
|
+
label={label}
|
|
112
|
+
labelPosition={labelPosition}
|
|
113
|
+
overlay={true}
|
|
114
|
+
size={size}
|
|
115
|
+
thickness={thickness}
|
|
116
|
+
sizeVariant={sizeVariant}
|
|
117
|
+
isStatic={isStatic}
|
|
118
|
+
/>
|
|
119
|
+
)}
|
|
120
|
+
</SpinnerContainer>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
93
124
|
if (!show) {
|
|
94
125
|
return children ?? null
|
|
95
126
|
}
|
|
@@ -121,7 +152,7 @@ const Spinner = React.forwardRef(
|
|
|
121
152
|
}
|
|
122
153
|
|
|
123
154
|
// Overlay spinner
|
|
124
|
-
if (children) {
|
|
155
|
+
if (children && !persistChildrenState) {
|
|
125
156
|
return (
|
|
126
157
|
<SpinnerContainer inline={inline} aria-live="assertive" overlay {...selectProps(rest)}>
|
|
127
158
|
<SpinnerContent
|
|
@@ -204,7 +235,13 @@ Spinner.propTypes = {
|
|
|
204
235
|
/**
|
|
205
236
|
* Determine where the label of the spinner should be placed, left, right, bottom or top.
|
|
206
237
|
*/
|
|
207
|
-
labelPosition: PropTypes.string
|
|
238
|
+
labelPosition: PropTypes.string,
|
|
239
|
+
/**
|
|
240
|
+
* When true, preserves the state of overlaid children when `show` toggles.
|
|
241
|
+
* Only applicable when the Spinner is used in overlay mode (with children).
|
|
242
|
+
* Use this when overlaying stateful components such as Micro Frontends (MFEs).
|
|
243
|
+
*/
|
|
244
|
+
persistChildrenState: PropTypes.bool
|
|
208
245
|
}
|
|
209
246
|
|
|
210
247
|
export default Spinner
|
|
@@ -3,4 +3,7 @@ import FullBleedContent from './FullBleedContent'
|
|
|
3
3
|
export default FullBleedContent
|
|
4
4
|
|
|
5
5
|
export { default as getFullBleedBorderRadius } from './getFullBleedBorderRadius'
|
|
6
|
-
export {
|
|
6
|
+
export {
|
|
7
|
+
default as useFullBleedContentProps,
|
|
8
|
+
getFullBleedPaddingSides
|
|
9
|
+
} from './useFullBleedContentProps'
|
|
@@ -31,6 +31,21 @@ const getContentStackAlign = (fullBleedContentAlign) => {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Calculates which padding sides should be applied to full bleed content
|
|
36
|
+
* based on its position relative to card content.
|
|
37
|
+
* The side adjacent to card body gets no padding since the body already provides spacing.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} position - 'top' | 'bottom' | 'left' | 'right' | 'none'
|
|
40
|
+
* @returns {object} Boolean flags for which sides to apply padding
|
|
41
|
+
*/
|
|
42
|
+
const getFullBleedPaddingSides = (position) => ({
|
|
43
|
+
applyPaddingTop: position !== 'bottom',
|
|
44
|
+
applyPaddingBottom: position !== 'top',
|
|
45
|
+
applyPaddingLeft: position !== 'right',
|
|
46
|
+
applyPaddingRight: position !== 'left'
|
|
47
|
+
})
|
|
48
|
+
|
|
34
49
|
/**
|
|
35
50
|
* Resolves a set of `FullBleedContent` props into the variables a container needs to
|
|
36
51
|
* correctly position a `FullBleedContent` component for the current viewport.
|
|
@@ -58,13 +73,18 @@ const useFullBleedContentProps = (fullBleedContent) => {
|
|
|
58
73
|
fullBleedContentAlign
|
|
59
74
|
)
|
|
60
75
|
|
|
76
|
+
// Get padding sides based on position
|
|
77
|
+
const fullBleedPaddingSides = getFullBleedPaddingSides(fullBleedContentPosition)
|
|
78
|
+
|
|
61
79
|
return {
|
|
62
80
|
contentStackAlign,
|
|
63
81
|
contentStackDirection,
|
|
64
82
|
fullBleedContentPosition,
|
|
65
83
|
fullBleedContentProps,
|
|
66
|
-
fullBleedContentChildrenAlign
|
|
84
|
+
fullBleedContentChildrenAlign,
|
|
85
|
+
fullBleedPaddingSides
|
|
67
86
|
}
|
|
68
87
|
}
|
|
69
88
|
|
|
89
|
+
export { getFullBleedPaddingSides }
|
|
70
90
|
export default useFullBleedContentProps
|
package/types/Autocomplete.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export interface AutocompleteProps extends AutocompleteCommonProps {
|
|
|
22
22
|
isLoading?: boolean
|
|
23
23
|
items?: AutocompleteItem[]
|
|
24
24
|
loadingLabel?: string
|
|
25
|
+
maxDropdownHeight?: number
|
|
26
|
+
showOptionsOnFocus?: boolean
|
|
25
27
|
minToSuggestion?: number
|
|
26
28
|
maxSuggestions?: number
|
|
27
29
|
noResults?: ReactNode
|
package/types/Spinner.d.ts
CHANGED
|
@@ -9,6 +9,11 @@ export interface SpinnerProps extends HTMLAttrs {
|
|
|
9
9
|
label: string
|
|
10
10
|
show?: boolean
|
|
11
11
|
isStatic?: boolean
|
|
12
|
+
/**
|
|
13
|
+
* When true, preserves the state of overlaid children when `show` toggles.
|
|
14
|
+
* Only applicable when the Spinner is used in overlay mode (with `children`).
|
|
15
|
+
*/
|
|
16
|
+
persistChildrenState?: boolean
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
declare const Spinner: ComponentType<SpinnerProps>
|