@telus-uds/components-base 3.19.0 → 3.20.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 +16 -1
- package/lib/cjs/Button/ButtonDropdown.js +1 -0
- package/lib/cjs/ExpandCollapseMini/ExpandCollapseMiniControl.js +8 -21
- package/lib/cjs/Link/LinkBase.js +8 -9
- package/lib/cjs/MultiSelectFilter/MultiSelectFilter.js +8 -8
- package/lib/cjs/Spacer/Spacer.js +65 -5
- package/lib/cjs/utils/index.js +8 -0
- package/lib/cjs/utils/useMediaQuerySpacing.js +121 -0
- package/lib/esm/Button/ButtonDropdown.js +1 -0
- package/lib/esm/ExpandCollapseMini/ExpandCollapseMiniControl.js +8 -21
- package/lib/esm/Link/LinkBase.js +8 -9
- package/lib/esm/MultiSelectFilter/MultiSelectFilter.js +8 -8
- package/lib/esm/Spacer/Spacer.js +66 -6
- package/lib/esm/utils/index.js +1 -0
- package/lib/esm/utils/useMediaQuerySpacing.js +116 -0
- package/lib/package.json +2 -2
- package/package.json +2 -2
- package/src/Button/ButtonDropdown.jsx +1 -0
- package/src/ExpandCollapseMini/ExpandCollapseMiniControl.jsx +9 -16
- package/src/Link/LinkBase.jsx +11 -9
- package/src/MultiSelectFilter/MultiSelectFilter.jsx +9 -8
- package/src/Spacer/Spacer.jsx +54 -7
- package/src/utils/index.js +1 -0
- package/src/utils/useMediaQuerySpacing.js +124 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
# Change Log - @telus-uds/components-base
|
|
2
2
|
|
|
3
|
-
This log was last generated on Fri,
|
|
3
|
+
This log was last generated on Fri, 10 Oct 2025 15:11:05 GMT and should not be manually modified.
|
|
4
4
|
|
|
5
5
|
<!-- Start content -->
|
|
6
6
|
|
|
7
|
+
## 3.20.0
|
|
8
|
+
|
|
9
|
+
Fri, 10 Oct 2025 15:11:05 GMT
|
|
10
|
+
|
|
11
|
+
### Minor changes
|
|
12
|
+
|
|
13
|
+
- `Spacer`: add RNMQ support (guillermo.peitzner@telus.com)
|
|
14
|
+
- Bump @telus-uds/system-theme-tokens to v4.15.1
|
|
15
|
+
|
|
16
|
+
### Patches
|
|
17
|
+
|
|
18
|
+
- `ButtonDropdown`: Small size variant updates. (oscar.palencia@telus.com)
|
|
19
|
+
- `MultiSelectfilter`: rowLimit prop issue causing display problems fixed (35577399+JoshHC@users.noreply.github.com)
|
|
20
|
+
- `ExpandCollapseMini`: extra padding removed (35577399+JoshHC@users.noreply.github.com)
|
|
21
|
+
|
|
7
22
|
## 3.19.0
|
|
8
23
|
|
|
9
24
|
Fri, 03 Oct 2025 20:34:06 GMT
|
|
@@ -64,6 +64,7 @@ const selectDescriptionTextStyles = tokens => ({
|
|
|
64
64
|
fontName: tokens?.descriptionFontName,
|
|
65
65
|
fontSize: tokens?.descriptionFontSize,
|
|
66
66
|
fontWeight: tokens?.descriptionFontWeight,
|
|
67
|
+
lineHeight: tokens?.descriptionLineHeight,
|
|
67
68
|
fontColor: tokens?.color
|
|
68
69
|
}),
|
|
69
70
|
paddingBottom: tokens?.descriptionTextPaddingBottom
|
|
@@ -73,35 +73,21 @@ const ExpandCollapseMiniControl = /*#__PURE__*/_react.default.forwardRef((_ref,
|
|
|
73
73
|
const isHovered = hover || linkHover;
|
|
74
74
|
const iconBaselineOffset = 0;
|
|
75
75
|
const hoverTranslateY = 4;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
const iconBaseline = iconSize / hoverTranslateY; // Quarter of icon size - adjusts for icon's visual center point
|
|
81
|
-
const staticOffset = hoverTranslateY; // Fixed downward adjustment to fine-tune vertical alignment
|
|
82
|
-
const sizeCompensation = -Math.abs(iconSize - fontSize); // Compensates when icon and text sizes differ significantly
|
|
83
|
-
|
|
76
|
+
const fontBaseline = fontSize / hoverTranslateY;
|
|
77
|
+
const iconBaseline = iconSize / hoverTranslateY;
|
|
78
|
+
const staticOffset = hoverTranslateY;
|
|
79
|
+
const sizeCompensation = -Math.abs(iconSize - fontSize);
|
|
84
80
|
const baselineAlignment = fontBaseline + iconBaseline - staticOffset + sizeCompensation;
|
|
85
|
-
|
|
86
|
-
// For native platforms, use baseline alignment with optional offset
|
|
87
|
-
return {
|
|
88
|
-
iconTranslateY: baselineAlignment + iconBaselineOffset
|
|
89
|
-
};
|
|
90
|
-
}
|
|
81
|
+
const mobileAdjustment = _Platform.default.OS !== 'web' ? -2 : 0;
|
|
91
82
|
if (isHovered) {
|
|
92
|
-
// Apply animation offset to the baseline-aligned position
|
|
93
|
-
// When expanded: move icon UP (1.3 the hover distance for clear movement)
|
|
94
|
-
// When collapsed: move icon DOWN (single hover distance)
|
|
95
83
|
const hoverMovementDistance = 1.3;
|
|
96
84
|
const animationOffset = expanded ? -(hoverTranslateY * hoverMovementDistance) : hoverTranslateY;
|
|
97
85
|
return {
|
|
98
|
-
iconTranslateY: baselineAlignment + iconBaselineOffset + animationOffset
|
|
86
|
+
iconTranslateY: baselineAlignment + iconBaselineOffset + animationOffset + mobileAdjustment
|
|
99
87
|
};
|
|
100
88
|
}
|
|
101
|
-
|
|
102
|
-
// Default state uses baseline alignment with optional offset
|
|
103
89
|
return {
|
|
104
|
-
iconTranslateY: baselineAlignment + iconBaselineOffset
|
|
90
|
+
iconTranslateY: baselineAlignment + iconBaselineOffset + mobileAdjustment
|
|
105
91
|
};
|
|
106
92
|
};
|
|
107
93
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Link.Link, {
|
|
@@ -112,6 +98,7 @@ const ExpandCollapseMiniControl = /*#__PURE__*/_react.default.forwardRef((_ref,
|
|
|
112
98
|
...linkTokens,
|
|
113
99
|
...getTokens(linkState),
|
|
114
100
|
iconSize,
|
|
101
|
+
blockFontSize: fontSize,
|
|
115
102
|
blockLineHeight: lineHeight
|
|
116
103
|
}),
|
|
117
104
|
ref: ref,
|
package/lib/cjs/Link/LinkBase.js
CHANGED
|
@@ -187,7 +187,8 @@ const LinkBase = /*#__PURE__*/_react.default.forwardRef((_ref6, ref) => {
|
|
|
187
187
|
const themeTokens = resolveLinkTokens(linkState);
|
|
188
188
|
const outerBorderStyles = selectOuterBorderStyles(themeTokens);
|
|
189
189
|
const decorationStyles = selectDecorationStyles(themeTokens);
|
|
190
|
-
|
|
190
|
+
const mobileCompensation = null;
|
|
191
|
+
return [outerBorderStyles, mobileCompensation, blockLeftStyle, decorationStyles, hasIcon && staticStyles.rowContainer];
|
|
191
192
|
},
|
|
192
193
|
children: linkState => {
|
|
193
194
|
const themeTokens = resolveLinkTokens(linkState);
|
|
@@ -200,10 +201,12 @@ const LinkBase = /*#__PURE__*/_react.default.forwardRef((_ref6, ref) => {
|
|
|
200
201
|
const {
|
|
201
202
|
iconSpace
|
|
202
203
|
} = themeTokens;
|
|
204
|
+
const isTextOnlyLink = !IconComponent && !icon && accessibilityRole === 'link';
|
|
205
|
+
const adjustedIconSpace = _Platform.default.OS !== 'web' && isTextOnlyLink ? 0 : iconSpace;
|
|
203
206
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Icon.IconText, {
|
|
204
207
|
icon: IconComponent,
|
|
205
208
|
iconPosition: iconPosition,
|
|
206
|
-
space:
|
|
209
|
+
space: adjustedIconSpace,
|
|
207
210
|
iconProps: {
|
|
208
211
|
...iconProps,
|
|
209
212
|
tokens: iconTokens,
|
|
@@ -270,15 +273,11 @@ const staticStyles = _StyleSheet.default.create({
|
|
|
270
273
|
}
|
|
271
274
|
})
|
|
272
275
|
},
|
|
273
|
-
|
|
276
|
+
outerBorderCompensation: {
|
|
274
277
|
...(_Platform.default.OS !== 'web' && {
|
|
275
|
-
margin: 0,
|
|
276
278
|
marginHorizontal: 2,
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
...(_Platform.default.OS === 'android' && {
|
|
280
|
-
paddingHorizontal: 2,
|
|
281
|
-
paddingTop: 2
|
|
279
|
+
paddingHorizontal: _Platform.default.OS === 'android' ? 2 : 0,
|
|
280
|
+
paddingTop: _Platform.default.OS === 'android' ? 2 : 0
|
|
282
281
|
})
|
|
283
282
|
}
|
|
284
283
|
});
|
|
@@ -71,7 +71,6 @@ const selectContainerStyle = (windowHeight, windowWidth) => ({
|
|
|
71
71
|
width: windowWidth
|
|
72
72
|
});
|
|
73
73
|
const TOTAL_COLUMNS = 12;
|
|
74
|
-
const MAX_ITEMS_THRESHOLD = 12;
|
|
75
74
|
const MultiSelectFilter = /*#__PURE__*/_react.default.forwardRef((_ref3, ref) => {
|
|
76
75
|
let {
|
|
77
76
|
label,
|
|
@@ -178,12 +177,13 @@ const MultiSelectFilter = /*#__PURE__*/_react.default.forwardRef((_ref3, ref) =>
|
|
|
178
177
|
});
|
|
179
178
|
const colSizeNotMobile = items.length > rowLimit ? 2 : 1;
|
|
180
179
|
const colSize = viewport !== 'xs' ? colSizeNotMobile : 1;
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
let rowLength = items.length;
|
|
181
|
+
if (viewport !== 'xs' && colSize === 2) {
|
|
182
|
+
rowLength = Math.ceil(items.length / 2);
|
|
183
|
+
}
|
|
183
184
|
_react.default.useEffect(() => {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}, [colSize]);
|
|
185
|
+
setMaxWidth(items.length >= rowLimit);
|
|
186
|
+
}, [items.length, rowLimit]);
|
|
187
187
|
_react.default.useEffect(() => setCheckedIds(currentValues ?? []), [currentValues]);
|
|
188
188
|
const uniqueFields = ['id', 'label'];
|
|
189
189
|
if (!(0, _utils.containUniqueFields)(items, uniqueFields)) {
|
|
@@ -430,14 +430,14 @@ const MultiSelectFilter = /*#__PURE__*/_react.default.forwardRef((_ref3, ref) =>
|
|
|
430
430
|
dismissWhenPressedOutside: dismissWhenPressedOutside,
|
|
431
431
|
onClose: onClose,
|
|
432
432
|
overlaidPosition: overlaidPosition,
|
|
433
|
-
maxHeight: items.length
|
|
433
|
+
maxHeight: items.length >= rowLimit ? true : maxHeight,
|
|
434
434
|
maxHeightSize: maxHeightSize,
|
|
435
435
|
maxWidthSize: maxWidthSize,
|
|
436
436
|
minHeight: minHeight,
|
|
437
437
|
minWidth: minWidth,
|
|
438
438
|
tokens: {
|
|
439
439
|
...tokens,
|
|
440
|
-
maxWidth: items.length
|
|
440
|
+
maxWidth: items.length >= rowLimit ? true : maxWidth,
|
|
441
441
|
borderColor: containerBorderColor
|
|
442
442
|
},
|
|
443
443
|
copy: copy,
|
package/lib/cjs/Spacer/Spacer.js
CHANGED
|
@@ -6,9 +6,11 @@ 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"));
|
|
10
9
|
var _View = _interopRequireDefault(require("react-native-web/dist/cjs/exports/View"));
|
|
10
|
+
var _StyleSheet = _interopRequireDefault(require("react-native-web/dist/cjs/exports/StyleSheet"));
|
|
11
11
|
var _utils = require("../utils");
|
|
12
|
+
var _useMediaQuerySpacing = _interopRequireDefault(require("../utils/useMediaQuerySpacing"));
|
|
13
|
+
var _useTheme = _interopRequireDefault(require("../ThemeProvider/useTheme"));
|
|
12
14
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
13
15
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
16
|
/**
|
|
@@ -65,13 +67,66 @@ const Spacer = /*#__PURE__*/_react.default.forwardRef((_ref, ref) => {
|
|
|
65
67
|
let {
|
|
66
68
|
space = 1,
|
|
67
69
|
direction = 'column',
|
|
70
|
+
dataSet,
|
|
68
71
|
...rest
|
|
69
72
|
} = _ref;
|
|
70
|
-
const
|
|
71
|
-
|
|
73
|
+
const {
|
|
74
|
+
themeOptions: {
|
|
75
|
+
enableMediaQueryStyleSheet
|
|
76
|
+
}
|
|
77
|
+
} = (0, _useTheme.default)();
|
|
78
|
+
const {
|
|
79
|
+
sizeByViewport
|
|
80
|
+
} = (0, _useMediaQuerySpacing.default)(space);
|
|
81
|
+
const fallbackSize = (0, _utils.useSpacingScale)(space);
|
|
82
|
+
const sizeStyle = selectSizeStyle(fallbackSize, direction);
|
|
83
|
+
let spacerStyles;
|
|
84
|
+
let dataSetValue = dataSet;
|
|
85
|
+
if (enableMediaQueryStyleSheet) {
|
|
86
|
+
const sizeKey = direction === 'row' ? 'width' : 'height';
|
|
87
|
+
const stylesByViewport = {
|
|
88
|
+
xs: {
|
|
89
|
+
[sizeKey]: sizeByViewport.xs,
|
|
90
|
+
...staticStyles.stretch
|
|
91
|
+
},
|
|
92
|
+
sm: {
|
|
93
|
+
[sizeKey]: sizeByViewport.sm,
|
|
94
|
+
...staticStyles.stretch
|
|
95
|
+
},
|
|
96
|
+
md: {
|
|
97
|
+
[sizeKey]: sizeByViewport.md,
|
|
98
|
+
...staticStyles.stretch
|
|
99
|
+
},
|
|
100
|
+
lg: {
|
|
101
|
+
[sizeKey]: sizeByViewport.lg,
|
|
102
|
+
...staticStyles.stretch
|
|
103
|
+
},
|
|
104
|
+
xl: {
|
|
105
|
+
[sizeKey]: sizeByViewport.xl,
|
|
106
|
+
...staticStyles.stretch
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const mediaQueryStyles = (0, _utils.createMediaQueryStyles)(stylesByViewport);
|
|
110
|
+
const {
|
|
111
|
+
ids,
|
|
112
|
+
styles
|
|
113
|
+
} = _utils.StyleSheet.create({
|
|
114
|
+
spacer: {
|
|
115
|
+
...mediaQueryStyles
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
spacerStyles = styles.spacer;
|
|
119
|
+
dataSetValue = {
|
|
120
|
+
media: ids.spacer,
|
|
121
|
+
...dataSet
|
|
122
|
+
};
|
|
123
|
+
} else {
|
|
124
|
+
spacerStyles = [staticStyles.stretch, sizeStyle];
|
|
125
|
+
}
|
|
72
126
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
|
|
73
127
|
ref: ref,
|
|
74
|
-
style:
|
|
128
|
+
style: spacerStyles,
|
|
129
|
+
dataSet: dataSetValue,
|
|
75
130
|
...selectProps(rest)
|
|
76
131
|
});
|
|
77
132
|
});
|
|
@@ -90,7 +145,12 @@ Spacer.propTypes = {
|
|
|
90
145
|
* - `'column'` (default) applies space vertically; has a fixed height and not width.
|
|
91
146
|
* - `'row'` applies space horizontally; has a fixed width and not height.
|
|
92
147
|
*/
|
|
93
|
-
direction: _propTypes.default.oneOf(['column', 'row'])
|
|
148
|
+
direction: _propTypes.default.oneOf(['column', 'row']),
|
|
149
|
+
/**
|
|
150
|
+
* Data attributes to be applied to the element. When media query stylesheet is enabled,
|
|
151
|
+
* this will include media query IDs for responsive styling.
|
|
152
|
+
*/
|
|
153
|
+
dataSet: _propTypes.default.object
|
|
94
154
|
};
|
|
95
155
|
const staticStyles = _StyleSheet.default.create({
|
|
96
156
|
stretch: {
|
package/lib/cjs/utils/index.js
CHANGED
|
@@ -8,6 +8,7 @@ var _exportNames = {
|
|
|
8
8
|
useCopy: true,
|
|
9
9
|
useHash: true,
|
|
10
10
|
useSpacingScale: true,
|
|
11
|
+
useMediaQuerySpacing: true,
|
|
11
12
|
useResponsiveProp: true,
|
|
12
13
|
useOverlaidPosition: true,
|
|
13
14
|
useSafeLayoutEffect: true,
|
|
@@ -90,6 +91,12 @@ Object.defineProperty(exports, "useHash", {
|
|
|
90
91
|
return _useHash.default;
|
|
91
92
|
}
|
|
92
93
|
});
|
|
94
|
+
Object.defineProperty(exports, "useMediaQuerySpacing", {
|
|
95
|
+
enumerable: true,
|
|
96
|
+
get: function () {
|
|
97
|
+
return _useMediaQuerySpacing.default;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
93
100
|
Object.defineProperty(exports, "useOverlaidPosition", {
|
|
94
101
|
enumerable: true,
|
|
95
102
|
get: function () {
|
|
@@ -226,6 +233,7 @@ var _info = _interopRequireDefault(require("./info"));
|
|
|
226
233
|
var _useCopy = _interopRequireDefault(require("./useCopy"));
|
|
227
234
|
var _useHash = _interopRequireDefault(require("./useHash"));
|
|
228
235
|
var _useSpacingScale = _interopRequireDefault(require("./useSpacingScale"));
|
|
236
|
+
var _useMediaQuerySpacing = _interopRequireDefault(require("./useMediaQuerySpacing"));
|
|
229
237
|
var _useResponsiveProp = _interopRequireWildcard(require("./useResponsiveProp"));
|
|
230
238
|
Object.keys(_useResponsiveProp).forEach(function (key) {
|
|
231
239
|
if (key === "default" || key === "__esModule") return;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _ThemeProvider = require("../ThemeProvider");
|
|
8
|
+
var _useResponsiveProp = require("./useResponsiveProp");
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {import('@telus-uds/system-constants/viewports').Viewport} Viewport
|
|
11
|
+
* @typedef {import('./props/spacingProps.js').SpacingValue} SpacingValue
|
|
12
|
+
* @typedef {import('./props/spacingProps.js').SpacingIndex} SpacingIndex
|
|
13
|
+
* @typedef {import('./props/spacingProps.js').SpacingObject} SpacingObject
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A utility hook that simplifies implementing media query-based responsive spacing.
|
|
18
|
+
*
|
|
19
|
+
* This hook handles the complexity of:
|
|
20
|
+
* - Detecting if a space value is responsive (has viewport keys)
|
|
21
|
+
* - Fetching theme tokens for each viewport
|
|
22
|
+
* - Resolving the correct space index for each viewport
|
|
23
|
+
* - Extracting actual pixel values from theme tokens
|
|
24
|
+
*
|
|
25
|
+
* ## Usage
|
|
26
|
+
*
|
|
27
|
+
* ```jsx
|
|
28
|
+
* const { sizeByViewport } = useMediaQuerySpacing(space, 'spacingScale')
|
|
29
|
+
*
|
|
30
|
+
* // Use sizeByViewport to create media query styles
|
|
31
|
+
* const stylesByViewport = {
|
|
32
|
+
* xs: { padding: sizeByViewport.xs },
|
|
33
|
+
* sm: { padding: sizeByViewport.sm },
|
|
34
|
+
* md: { padding: sizeByViewport.md },
|
|
35
|
+
* lg: { padding: sizeByViewport.lg },
|
|
36
|
+
* xl: { padding: sizeByViewport.xl }
|
|
37
|
+
* }
|
|
38
|
+
* const mediaQueryStyles = createMediaQueryStyles(stylesByViewport)
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* ## Parameters
|
|
42
|
+
*
|
|
43
|
+
* @param {SpacingValue} spaceValue - A spacing value (number or responsive object with viewport keys)
|
|
44
|
+
* @param {string} tokenKey - The theme token key to use (e.g., 'spacingScale', 'Typography')
|
|
45
|
+
* @param {object} [tokens={}] - Additional tokens to pass to useThemeTokens
|
|
46
|
+
* @param {object} [variant={}] - Variant to pass to useThemeTokens
|
|
47
|
+
*
|
|
48
|
+
* ## Returns
|
|
49
|
+
*
|
|
50
|
+
* @returns {{
|
|
51
|
+
* spaceIndexByViewport: { xs: number, sm: number, md: number, lg: number, xl: number },
|
|
52
|
+
* sizeByViewport: { xs: number, sm: number, md: number, lg: number, xl: number },
|
|
53
|
+
* tokensByViewport: { xs: object, sm: object, md: object, lg: object, xl: object }
|
|
54
|
+
* }}
|
|
55
|
+
*
|
|
56
|
+
* - `spaceIndexByViewport`: The resolved space index for each viewport
|
|
57
|
+
* - `sizeByViewport`: The actual pixel/number values for each viewport
|
|
58
|
+
* - `tokensByViewport`: The full theme tokens for each viewport (for advanced use cases)
|
|
59
|
+
*/
|
|
60
|
+
const useMediaQuerySpacing = function (spaceValue) {
|
|
61
|
+
let tokenKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'spacingScale';
|
|
62
|
+
let tokens = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
63
|
+
let variant = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
|
|
64
|
+
const isResponsive = typeof spaceValue === 'object' && spaceValue !== null && !spaceValue.space && !spaceValue.options;
|
|
65
|
+
const getSpaceIndex = viewport => {
|
|
66
|
+
if (isResponsive) {
|
|
67
|
+
return (0, _useResponsiveProp.resolveResponsiveProp)(spaceValue, viewport);
|
|
68
|
+
}
|
|
69
|
+
if (typeof spaceValue === 'number') {
|
|
70
|
+
return spaceValue;
|
|
71
|
+
}
|
|
72
|
+
return spaceValue?.space ?? 1;
|
|
73
|
+
};
|
|
74
|
+
const spaceIndexByViewport = {
|
|
75
|
+
xs: getSpaceIndex('xs'),
|
|
76
|
+
sm: getSpaceIndex('sm'),
|
|
77
|
+
md: getSpaceIndex('md'),
|
|
78
|
+
lg: getSpaceIndex('lg'),
|
|
79
|
+
xl: getSpaceIndex('xl')
|
|
80
|
+
};
|
|
81
|
+
const tokensXs = (0, _ThemeProvider.useThemeTokens)(tokenKey, tokens, variant, {
|
|
82
|
+
space: spaceIndexByViewport.xs,
|
|
83
|
+
viewport: 'xs'
|
|
84
|
+
});
|
|
85
|
+
const tokensSm = (0, _ThemeProvider.useThemeTokens)(tokenKey, tokens, variant, {
|
|
86
|
+
space: spaceIndexByViewport.sm,
|
|
87
|
+
viewport: 'sm'
|
|
88
|
+
});
|
|
89
|
+
const tokensMd = (0, _ThemeProvider.useThemeTokens)(tokenKey, tokens, variant, {
|
|
90
|
+
space: spaceIndexByViewport.md,
|
|
91
|
+
viewport: 'md'
|
|
92
|
+
});
|
|
93
|
+
const tokensLg = (0, _ThemeProvider.useThemeTokens)(tokenKey, tokens, variant, {
|
|
94
|
+
space: spaceIndexByViewport.lg,
|
|
95
|
+
viewport: 'lg'
|
|
96
|
+
});
|
|
97
|
+
const tokensXl = (0, _ThemeProvider.useThemeTokens)(tokenKey, tokens, variant, {
|
|
98
|
+
space: spaceIndexByViewport.xl,
|
|
99
|
+
viewport: 'xl'
|
|
100
|
+
});
|
|
101
|
+
const sizeByViewport = {
|
|
102
|
+
xs: tokensXs.size ?? 0,
|
|
103
|
+
sm: tokensSm.size ?? 0,
|
|
104
|
+
md: tokensMd.size ?? 0,
|
|
105
|
+
lg: tokensLg.size ?? 0,
|
|
106
|
+
xl: tokensXl.size ?? 0
|
|
107
|
+
};
|
|
108
|
+
const tokensByViewport = {
|
|
109
|
+
xs: tokensXs,
|
|
110
|
+
sm: tokensSm,
|
|
111
|
+
md: tokensMd,
|
|
112
|
+
lg: tokensLg,
|
|
113
|
+
xl: tokensXl
|
|
114
|
+
};
|
|
115
|
+
return {
|
|
116
|
+
spaceIndexByViewport,
|
|
117
|
+
sizeByViewport,
|
|
118
|
+
tokensByViewport
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
var _default = exports.default = useMediaQuerySpacing;
|
|
@@ -55,6 +55,7 @@ const selectDescriptionTextStyles = tokens => ({
|
|
|
55
55
|
fontName: tokens?.descriptionFontName,
|
|
56
56
|
fontSize: tokens?.descriptionFontSize,
|
|
57
57
|
fontWeight: tokens?.descriptionFontWeight,
|
|
58
|
+
lineHeight: tokens?.descriptionLineHeight,
|
|
58
59
|
fontColor: tokens?.color
|
|
59
60
|
}),
|
|
60
61
|
paddingBottom: tokens?.descriptionTextPaddingBottom
|
|
@@ -66,35 +66,21 @@ const ExpandCollapseMiniControl = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
66
66
|
const isHovered = hover || linkHover;
|
|
67
67
|
const iconBaselineOffset = 0;
|
|
68
68
|
const hoverTranslateY = 4;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
const iconBaseline = iconSize / hoverTranslateY; // Quarter of icon size - adjusts for icon's visual center point
|
|
74
|
-
const staticOffset = hoverTranslateY; // Fixed downward adjustment to fine-tune vertical alignment
|
|
75
|
-
const sizeCompensation = -Math.abs(iconSize - fontSize); // Compensates when icon and text sizes differ significantly
|
|
76
|
-
|
|
69
|
+
const fontBaseline = fontSize / hoverTranslateY;
|
|
70
|
+
const iconBaseline = iconSize / hoverTranslateY;
|
|
71
|
+
const staticOffset = hoverTranslateY;
|
|
72
|
+
const sizeCompensation = -Math.abs(iconSize - fontSize);
|
|
77
73
|
const baselineAlignment = fontBaseline + iconBaseline - staticOffset + sizeCompensation;
|
|
78
|
-
|
|
79
|
-
// For native platforms, use baseline alignment with optional offset
|
|
80
|
-
return {
|
|
81
|
-
iconTranslateY: baselineAlignment + iconBaselineOffset
|
|
82
|
-
};
|
|
83
|
-
}
|
|
74
|
+
const mobileAdjustment = Platform.OS !== 'web' ? -2 : 0;
|
|
84
75
|
if (isHovered) {
|
|
85
|
-
// Apply animation offset to the baseline-aligned position
|
|
86
|
-
// When expanded: move icon UP (1.3 the hover distance for clear movement)
|
|
87
|
-
// When collapsed: move icon DOWN (single hover distance)
|
|
88
76
|
const hoverMovementDistance = 1.3;
|
|
89
77
|
const animationOffset = expanded ? -(hoverTranslateY * hoverMovementDistance) : hoverTranslateY;
|
|
90
78
|
return {
|
|
91
|
-
iconTranslateY: baselineAlignment + iconBaselineOffset + animationOffset
|
|
79
|
+
iconTranslateY: baselineAlignment + iconBaselineOffset + animationOffset + mobileAdjustment
|
|
92
80
|
};
|
|
93
81
|
}
|
|
94
|
-
|
|
95
|
-
// Default state uses baseline alignment with optional offset
|
|
96
82
|
return {
|
|
97
|
-
iconTranslateY: baselineAlignment + iconBaselineOffset
|
|
83
|
+
iconTranslateY: baselineAlignment + iconBaselineOffset + mobileAdjustment
|
|
98
84
|
};
|
|
99
85
|
};
|
|
100
86
|
return /*#__PURE__*/_jsx(Link, {
|
|
@@ -105,6 +91,7 @@ const ExpandCollapseMiniControl = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
105
91
|
...linkTokens,
|
|
106
92
|
...getTokens(linkState),
|
|
107
93
|
iconSize,
|
|
94
|
+
blockFontSize: fontSize,
|
|
108
95
|
blockLineHeight: lineHeight
|
|
109
96
|
}),
|
|
110
97
|
ref: ref,
|
package/lib/esm/Link/LinkBase.js
CHANGED
|
@@ -180,7 +180,8 @@ const LinkBase = /*#__PURE__*/React.forwardRef((_ref6, ref) => {
|
|
|
180
180
|
const themeTokens = resolveLinkTokens(linkState);
|
|
181
181
|
const outerBorderStyles = selectOuterBorderStyles(themeTokens);
|
|
182
182
|
const decorationStyles = selectDecorationStyles(themeTokens);
|
|
183
|
-
|
|
183
|
+
const mobileCompensation = null;
|
|
184
|
+
return [outerBorderStyles, mobileCompensation, blockLeftStyle, decorationStyles, hasIcon && staticStyles.rowContainer];
|
|
184
185
|
},
|
|
185
186
|
children: linkState => {
|
|
186
187
|
const themeTokens = resolveLinkTokens(linkState);
|
|
@@ -193,10 +194,12 @@ const LinkBase = /*#__PURE__*/React.forwardRef((_ref6, ref) => {
|
|
|
193
194
|
const {
|
|
194
195
|
iconSpace
|
|
195
196
|
} = themeTokens;
|
|
197
|
+
const isTextOnlyLink = !IconComponent && !icon && accessibilityRole === 'link';
|
|
198
|
+
const adjustedIconSpace = Platform.OS !== 'web' && isTextOnlyLink ? 0 : iconSpace;
|
|
196
199
|
return /*#__PURE__*/_jsx(IconText, {
|
|
197
200
|
icon: IconComponent,
|
|
198
201
|
iconPosition: iconPosition,
|
|
199
|
-
space:
|
|
202
|
+
space: adjustedIconSpace,
|
|
200
203
|
iconProps: {
|
|
201
204
|
...iconProps,
|
|
202
205
|
tokens: iconTokens,
|
|
@@ -263,15 +266,11 @@ const staticStyles = StyleSheet.create({
|
|
|
263
266
|
}
|
|
264
267
|
})
|
|
265
268
|
},
|
|
266
|
-
|
|
269
|
+
outerBorderCompensation: {
|
|
267
270
|
...(Platform.OS !== 'web' && {
|
|
268
|
-
margin: 0,
|
|
269
271
|
marginHorizontal: 2,
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
...(Platform.OS === 'android' && {
|
|
273
|
-
paddingHorizontal: 2,
|
|
274
|
-
paddingTop: 2
|
|
272
|
+
paddingHorizontal: Platform.OS === 'android' ? 2 : 0,
|
|
273
|
+
paddingTop: Platform.OS === 'android' ? 2 : 0
|
|
275
274
|
})
|
|
276
275
|
}
|
|
277
276
|
});
|
|
@@ -64,7 +64,6 @@ const selectContainerStyle = (windowHeight, windowWidth) => ({
|
|
|
64
64
|
width: windowWidth
|
|
65
65
|
});
|
|
66
66
|
const TOTAL_COLUMNS = 12;
|
|
67
|
-
const MAX_ITEMS_THRESHOLD = 12;
|
|
68
67
|
const MultiSelectFilter = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
69
68
|
let {
|
|
70
69
|
label,
|
|
@@ -171,12 +170,13 @@ const MultiSelectFilter = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
|
171
170
|
});
|
|
172
171
|
const colSizeNotMobile = items.length > rowLimit ? 2 : 1;
|
|
173
172
|
const colSize = viewport !== 'xs' ? colSizeNotMobile : 1;
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
let rowLength = items.length;
|
|
174
|
+
if (viewport !== 'xs' && colSize === 2) {
|
|
175
|
+
rowLength = Math.ceil(items.length / 2);
|
|
176
|
+
}
|
|
176
177
|
React.useEffect(() => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}, [colSize]);
|
|
178
|
+
setMaxWidth(items.length >= rowLimit);
|
|
179
|
+
}, [items.length, rowLimit]);
|
|
180
180
|
React.useEffect(() => setCheckedIds(currentValues ?? []), [currentValues]);
|
|
181
181
|
const uniqueFields = ['id', 'label'];
|
|
182
182
|
if (!containUniqueFields(items, uniqueFields)) {
|
|
@@ -423,14 +423,14 @@ const MultiSelectFilter = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
|
423
423
|
dismissWhenPressedOutside: dismissWhenPressedOutside,
|
|
424
424
|
onClose: onClose,
|
|
425
425
|
overlaidPosition: overlaidPosition,
|
|
426
|
-
maxHeight: items.length
|
|
426
|
+
maxHeight: items.length >= rowLimit ? true : maxHeight,
|
|
427
427
|
maxHeightSize: maxHeightSize,
|
|
428
428
|
maxWidthSize: maxWidthSize,
|
|
429
429
|
minHeight: minHeight,
|
|
430
430
|
minWidth: minWidth,
|
|
431
431
|
tokens: {
|
|
432
432
|
...tokens,
|
|
433
|
-
maxWidth: items.length
|
|
433
|
+
maxWidth: items.length >= rowLimit ? true : maxWidth,
|
|
434
434
|
borderColor: containerBorderColor
|
|
435
435
|
},
|
|
436
436
|
copy: copy,
|
package/lib/esm/Spacer/Spacer.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import StyleSheet from "react-native-web/dist/exports/StyleSheet";
|
|
4
3
|
import View from "react-native-web/dist/exports/View";
|
|
5
|
-
import
|
|
4
|
+
import StyleSheet from "react-native-web/dist/exports/StyleSheet";
|
|
5
|
+
import { a11yProps, selectSystemProps, spacingProps, useSpacingScale, viewProps, StyleSheet as StyleSheetUtils, createMediaQueryStyles } from '../utils';
|
|
6
|
+
import useMediaQuerySpacing from '../utils/useMediaQuerySpacing';
|
|
7
|
+
import useTheme from '../ThemeProvider/useTheme';
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* @typedef {import('../utils/props/spacingProps.js').SpacingValue} SpacingValue
|
|
@@ -60,13 +62,66 @@ const Spacer = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
60
62
|
let {
|
|
61
63
|
space = 1,
|
|
62
64
|
direction = 'column',
|
|
65
|
+
dataSet,
|
|
63
66
|
...rest
|
|
64
67
|
} = _ref;
|
|
65
|
-
const
|
|
66
|
-
|
|
68
|
+
const {
|
|
69
|
+
themeOptions: {
|
|
70
|
+
enableMediaQueryStyleSheet
|
|
71
|
+
}
|
|
72
|
+
} = useTheme();
|
|
73
|
+
const {
|
|
74
|
+
sizeByViewport
|
|
75
|
+
} = useMediaQuerySpacing(space);
|
|
76
|
+
const fallbackSize = useSpacingScale(space);
|
|
77
|
+
const sizeStyle = selectSizeStyle(fallbackSize, direction);
|
|
78
|
+
let spacerStyles;
|
|
79
|
+
let dataSetValue = dataSet;
|
|
80
|
+
if (enableMediaQueryStyleSheet) {
|
|
81
|
+
const sizeKey = direction === 'row' ? 'width' : 'height';
|
|
82
|
+
const stylesByViewport = {
|
|
83
|
+
xs: {
|
|
84
|
+
[sizeKey]: sizeByViewport.xs,
|
|
85
|
+
...staticStyles.stretch
|
|
86
|
+
},
|
|
87
|
+
sm: {
|
|
88
|
+
[sizeKey]: sizeByViewport.sm,
|
|
89
|
+
...staticStyles.stretch
|
|
90
|
+
},
|
|
91
|
+
md: {
|
|
92
|
+
[sizeKey]: sizeByViewport.md,
|
|
93
|
+
...staticStyles.stretch
|
|
94
|
+
},
|
|
95
|
+
lg: {
|
|
96
|
+
[sizeKey]: sizeByViewport.lg,
|
|
97
|
+
...staticStyles.stretch
|
|
98
|
+
},
|
|
99
|
+
xl: {
|
|
100
|
+
[sizeKey]: sizeByViewport.xl,
|
|
101
|
+
...staticStyles.stretch
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const mediaQueryStyles = createMediaQueryStyles(stylesByViewport);
|
|
105
|
+
const {
|
|
106
|
+
ids,
|
|
107
|
+
styles
|
|
108
|
+
} = StyleSheetUtils.create({
|
|
109
|
+
spacer: {
|
|
110
|
+
...mediaQueryStyles
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
spacerStyles = styles.spacer;
|
|
114
|
+
dataSetValue = {
|
|
115
|
+
media: ids.spacer,
|
|
116
|
+
...dataSet
|
|
117
|
+
};
|
|
118
|
+
} else {
|
|
119
|
+
spacerStyles = [staticStyles.stretch, sizeStyle];
|
|
120
|
+
}
|
|
67
121
|
return /*#__PURE__*/_jsx(View, {
|
|
68
122
|
ref: ref,
|
|
69
|
-
style:
|
|
123
|
+
style: spacerStyles,
|
|
124
|
+
dataSet: dataSetValue,
|
|
70
125
|
...selectProps(rest)
|
|
71
126
|
});
|
|
72
127
|
});
|
|
@@ -85,7 +140,12 @@ Spacer.propTypes = {
|
|
|
85
140
|
* - `'column'` (default) applies space vertically; has a fixed height and not width.
|
|
86
141
|
* - `'row'` applies space horizontally; has a fixed width and not height.
|
|
87
142
|
*/
|
|
88
|
-
direction: PropTypes.oneOf(['column', 'row'])
|
|
143
|
+
direction: PropTypes.oneOf(['column', 'row']),
|
|
144
|
+
/**
|
|
145
|
+
* Data attributes to be applied to the element. When media query stylesheet is enabled,
|
|
146
|
+
* this will include media query IDs for responsive styling.
|
|
147
|
+
*/
|
|
148
|
+
dataSet: PropTypes.object
|
|
89
149
|
};
|
|
90
150
|
const staticStyles = StyleSheet.create({
|
|
91
151
|
stretch: {
|
package/lib/esm/utils/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export { default as info } from './info';
|
|
|
9
9
|
export { default as useCopy } from './useCopy';
|
|
10
10
|
export { default as useHash } from './useHash';
|
|
11
11
|
export { default as useSpacingScale } from './useSpacingScale';
|
|
12
|
+
export { default as useMediaQuerySpacing } from './useMediaQuerySpacing';
|
|
12
13
|
export { default as useResponsiveProp } from './useResponsiveProp';
|
|
13
14
|
export { default as useOverlaidPosition } from './useOverlaidPosition';
|
|
14
15
|
export { default as useSafeLayoutEffect } from './useSafeLayoutEffect';
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { useThemeTokens } from '../ThemeProvider';
|
|
2
|
+
import { resolveResponsiveProp } from './useResponsiveProp';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import('@telus-uds/system-constants/viewports').Viewport} Viewport
|
|
6
|
+
* @typedef {import('./props/spacingProps.js').SpacingValue} SpacingValue
|
|
7
|
+
* @typedef {import('./props/spacingProps.js').SpacingIndex} SpacingIndex
|
|
8
|
+
* @typedef {import('./props/spacingProps.js').SpacingObject} SpacingObject
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A utility hook that simplifies implementing media query-based responsive spacing.
|
|
13
|
+
*
|
|
14
|
+
* This hook handles the complexity of:
|
|
15
|
+
* - Detecting if a space value is responsive (has viewport keys)
|
|
16
|
+
* - Fetching theme tokens for each viewport
|
|
17
|
+
* - Resolving the correct space index for each viewport
|
|
18
|
+
* - Extracting actual pixel values from theme tokens
|
|
19
|
+
*
|
|
20
|
+
* ## Usage
|
|
21
|
+
*
|
|
22
|
+
* ```jsx
|
|
23
|
+
* const { sizeByViewport } = useMediaQuerySpacing(space, 'spacingScale')
|
|
24
|
+
*
|
|
25
|
+
* // Use sizeByViewport to create media query styles
|
|
26
|
+
* const stylesByViewport = {
|
|
27
|
+
* xs: { padding: sizeByViewport.xs },
|
|
28
|
+
* sm: { padding: sizeByViewport.sm },
|
|
29
|
+
* md: { padding: sizeByViewport.md },
|
|
30
|
+
* lg: { padding: sizeByViewport.lg },
|
|
31
|
+
* xl: { padding: sizeByViewport.xl }
|
|
32
|
+
* }
|
|
33
|
+
* const mediaQueryStyles = createMediaQueryStyles(stylesByViewport)
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* ## Parameters
|
|
37
|
+
*
|
|
38
|
+
* @param {SpacingValue} spaceValue - A spacing value (number or responsive object with viewport keys)
|
|
39
|
+
* @param {string} tokenKey - The theme token key to use (e.g., 'spacingScale', 'Typography')
|
|
40
|
+
* @param {object} [tokens={}] - Additional tokens to pass to useThemeTokens
|
|
41
|
+
* @param {object} [variant={}] - Variant to pass to useThemeTokens
|
|
42
|
+
*
|
|
43
|
+
* ## Returns
|
|
44
|
+
*
|
|
45
|
+
* @returns {{
|
|
46
|
+
* spaceIndexByViewport: { xs: number, sm: number, md: number, lg: number, xl: number },
|
|
47
|
+
* sizeByViewport: { xs: number, sm: number, md: number, lg: number, xl: number },
|
|
48
|
+
* tokensByViewport: { xs: object, sm: object, md: object, lg: object, xl: object }
|
|
49
|
+
* }}
|
|
50
|
+
*
|
|
51
|
+
* - `spaceIndexByViewport`: The resolved space index for each viewport
|
|
52
|
+
* - `sizeByViewport`: The actual pixel/number values for each viewport
|
|
53
|
+
* - `tokensByViewport`: The full theme tokens for each viewport (for advanced use cases)
|
|
54
|
+
*/
|
|
55
|
+
const useMediaQuerySpacing = function (spaceValue) {
|
|
56
|
+
let tokenKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'spacingScale';
|
|
57
|
+
let tokens = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
58
|
+
let variant = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
|
|
59
|
+
const isResponsive = typeof spaceValue === 'object' && spaceValue !== null && !spaceValue.space && !spaceValue.options;
|
|
60
|
+
const getSpaceIndex = viewport => {
|
|
61
|
+
if (isResponsive) {
|
|
62
|
+
return resolveResponsiveProp(spaceValue, viewport);
|
|
63
|
+
}
|
|
64
|
+
if (typeof spaceValue === 'number') {
|
|
65
|
+
return spaceValue;
|
|
66
|
+
}
|
|
67
|
+
return spaceValue?.space ?? 1;
|
|
68
|
+
};
|
|
69
|
+
const spaceIndexByViewport = {
|
|
70
|
+
xs: getSpaceIndex('xs'),
|
|
71
|
+
sm: getSpaceIndex('sm'),
|
|
72
|
+
md: getSpaceIndex('md'),
|
|
73
|
+
lg: getSpaceIndex('lg'),
|
|
74
|
+
xl: getSpaceIndex('xl')
|
|
75
|
+
};
|
|
76
|
+
const tokensXs = useThemeTokens(tokenKey, tokens, variant, {
|
|
77
|
+
space: spaceIndexByViewport.xs,
|
|
78
|
+
viewport: 'xs'
|
|
79
|
+
});
|
|
80
|
+
const tokensSm = useThemeTokens(tokenKey, tokens, variant, {
|
|
81
|
+
space: spaceIndexByViewport.sm,
|
|
82
|
+
viewport: 'sm'
|
|
83
|
+
});
|
|
84
|
+
const tokensMd = useThemeTokens(tokenKey, tokens, variant, {
|
|
85
|
+
space: spaceIndexByViewport.md,
|
|
86
|
+
viewport: 'md'
|
|
87
|
+
});
|
|
88
|
+
const tokensLg = useThemeTokens(tokenKey, tokens, variant, {
|
|
89
|
+
space: spaceIndexByViewport.lg,
|
|
90
|
+
viewport: 'lg'
|
|
91
|
+
});
|
|
92
|
+
const tokensXl = useThemeTokens(tokenKey, tokens, variant, {
|
|
93
|
+
space: spaceIndexByViewport.xl,
|
|
94
|
+
viewport: 'xl'
|
|
95
|
+
});
|
|
96
|
+
const sizeByViewport = {
|
|
97
|
+
xs: tokensXs.size ?? 0,
|
|
98
|
+
sm: tokensSm.size ?? 0,
|
|
99
|
+
md: tokensMd.size ?? 0,
|
|
100
|
+
lg: tokensLg.size ?? 0,
|
|
101
|
+
xl: tokensXl.size ?? 0
|
|
102
|
+
};
|
|
103
|
+
const tokensByViewport = {
|
|
104
|
+
xs: tokensXs,
|
|
105
|
+
sm: tokensSm,
|
|
106
|
+
md: tokensMd,
|
|
107
|
+
lg: tokensLg,
|
|
108
|
+
xl: tokensXl
|
|
109
|
+
};
|
|
110
|
+
return {
|
|
111
|
+
spaceIndexByViewport,
|
|
112
|
+
sizeByViewport,
|
|
113
|
+
tokensByViewport
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
export default useMediaQuerySpacing;
|
package/lib/package.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"@gorhom/portal": "^1.0.14",
|
|
13
13
|
"@react-native-picker/picker": "^2.9.0",
|
|
14
14
|
"@telus-uds/system-constants": "^3.0.0",
|
|
15
|
-
"@telus-uds/system-theme-tokens": "^4.15.
|
|
15
|
+
"@telus-uds/system-theme-tokens": "^4.15.1",
|
|
16
16
|
"airbnb-prop-types": "^2.16.0",
|
|
17
17
|
"css-mediaquery": "^0.1.2",
|
|
18
18
|
"expo-document-picker": "^13.0.1",
|
|
@@ -84,6 +84,6 @@
|
|
|
84
84
|
"standard-engine": {
|
|
85
85
|
"skip": true
|
|
86
86
|
},
|
|
87
|
-
"version": "3.
|
|
87
|
+
"version": "3.20.0",
|
|
88
88
|
"types": "types/index.d.ts"
|
|
89
89
|
}
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"@gorhom/portal": "^1.0.14",
|
|
13
13
|
"@react-native-picker/picker": "^2.9.0",
|
|
14
14
|
"@telus-uds/system-constants": "^3.0.0",
|
|
15
|
-
"@telus-uds/system-theme-tokens": "^4.15.
|
|
15
|
+
"@telus-uds/system-theme-tokens": "^4.15.1",
|
|
16
16
|
"airbnb-prop-types": "^2.16.0",
|
|
17
17
|
"css-mediaquery": "^0.1.2",
|
|
18
18
|
"expo-document-picker": "^13.0.1",
|
|
@@ -84,6 +84,6 @@
|
|
|
84
84
|
"standard-engine": {
|
|
85
85
|
"skip": true
|
|
86
86
|
},
|
|
87
|
-
"version": "3.
|
|
87
|
+
"version": "3.20.0",
|
|
88
88
|
"types": "types/index.d.ts"
|
|
89
89
|
}
|
|
@@ -56,6 +56,7 @@ const selectDescriptionTextStyles = (tokens) => ({
|
|
|
56
56
|
fontName: tokens?.descriptionFontName,
|
|
57
57
|
fontSize: tokens?.descriptionFontSize,
|
|
58
58
|
fontWeight: tokens?.descriptionFontWeight,
|
|
59
|
+
lineHeight: tokens?.descriptionLineHeight,
|
|
59
60
|
fontColor: tokens?.color
|
|
60
61
|
}),
|
|
61
62
|
paddingBottom: tokens?.descriptionTextPaddingBottom
|
|
@@ -62,36 +62,28 @@ const ExpandCollapseMiniControl = React.forwardRef(
|
|
|
62
62
|
const iconBaselineOffset = 0
|
|
63
63
|
const hoverTranslateY = 4
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const staticOffset = hoverTranslateY // Fixed downward adjustment to fine-tune vertical alignment
|
|
70
|
-
const sizeCompensation = -Math.abs(iconSize - fontSize) // Compensates when icon and text sizes differ significantly
|
|
65
|
+
const fontBaseline = fontSize / hoverTranslateY
|
|
66
|
+
const iconBaseline = iconSize / hoverTranslateY
|
|
67
|
+
const staticOffset = hoverTranslateY
|
|
68
|
+
const sizeCompensation = -Math.abs(iconSize - fontSize)
|
|
71
69
|
|
|
72
70
|
const baselineAlignment = fontBaseline + iconBaseline - staticOffset + sizeCompensation
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
// For native platforms, use baseline alignment with optional offset
|
|
76
|
-
return { iconTranslateY: baselineAlignment + iconBaselineOffset }
|
|
77
|
-
}
|
|
72
|
+
const mobileAdjustment = Platform.OS !== 'web' ? -2 : 0
|
|
78
73
|
|
|
79
74
|
if (isHovered) {
|
|
80
|
-
// Apply animation offset to the baseline-aligned position
|
|
81
|
-
// When expanded: move icon UP (1.3 the hover distance for clear movement)
|
|
82
|
-
// When collapsed: move icon DOWN (single hover distance)
|
|
83
75
|
const hoverMovementDistance = 1.3
|
|
84
76
|
const animationOffset = expanded
|
|
85
77
|
? -(hoverTranslateY * hoverMovementDistance)
|
|
86
78
|
: hoverTranslateY
|
|
87
79
|
|
|
88
80
|
return {
|
|
89
|
-
iconTranslateY:
|
|
81
|
+
iconTranslateY:
|
|
82
|
+
baselineAlignment + iconBaselineOffset + animationOffset + mobileAdjustment
|
|
90
83
|
}
|
|
91
84
|
}
|
|
92
85
|
|
|
93
|
-
|
|
94
|
-
return { iconTranslateY: baselineAlignment + iconBaselineOffset }
|
|
86
|
+
return { iconTranslateY: baselineAlignment + iconBaselineOffset + mobileAdjustment }
|
|
95
87
|
}
|
|
96
88
|
|
|
97
89
|
return (
|
|
@@ -103,6 +95,7 @@ const ExpandCollapseMiniControl = React.forwardRef(
|
|
|
103
95
|
...linkTokens,
|
|
104
96
|
...getTokens(linkState),
|
|
105
97
|
iconSize,
|
|
98
|
+
blockFontSize: fontSize,
|
|
106
99
|
blockLineHeight: lineHeight
|
|
107
100
|
})}
|
|
108
101
|
ref={ref}
|
package/src/Link/LinkBase.jsx
CHANGED
|
@@ -171,9 +171,12 @@ const LinkBase = React.forwardRef(
|
|
|
171
171
|
const themeTokens = resolveLinkTokens(linkState)
|
|
172
172
|
const outerBorderStyles = selectOuterBorderStyles(themeTokens)
|
|
173
173
|
const decorationStyles = selectDecorationStyles(themeTokens)
|
|
174
|
+
|
|
175
|
+
const mobileCompensation = null
|
|
176
|
+
|
|
174
177
|
return [
|
|
175
178
|
outerBorderStyles,
|
|
176
|
-
|
|
179
|
+
mobileCompensation,
|
|
177
180
|
blockLeftStyle,
|
|
178
181
|
decorationStyles,
|
|
179
182
|
hasIcon && staticStyles.rowContainer
|
|
@@ -191,11 +194,14 @@ const LinkBase = React.forwardRef(
|
|
|
191
194
|
const IconComponent = icon || themeTokens.icon
|
|
192
195
|
const { iconSpace } = themeTokens
|
|
193
196
|
|
|
197
|
+
const isTextOnlyLink = !IconComponent && !icon && accessibilityRole === 'link'
|
|
198
|
+
const adjustedIconSpace = Platform.OS !== 'web' && isTextOnlyLink ? 0 : iconSpace
|
|
199
|
+
|
|
194
200
|
return (
|
|
195
201
|
<IconText
|
|
196
202
|
icon={IconComponent}
|
|
197
203
|
iconPosition={iconPosition}
|
|
198
|
-
space={
|
|
204
|
+
space={adjustedIconSpace}
|
|
199
205
|
iconProps={{
|
|
200
206
|
...iconProps,
|
|
201
207
|
tokens: iconTokens,
|
|
@@ -274,15 +280,11 @@ const staticStyles = StyleSheet.create({
|
|
|
274
280
|
}
|
|
275
281
|
})
|
|
276
282
|
},
|
|
277
|
-
|
|
283
|
+
outerBorderCompensation: {
|
|
278
284
|
...(Platform.OS !== 'web' && {
|
|
279
|
-
margin: 0,
|
|
280
285
|
marginHorizontal: 2,
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
...(Platform.OS === 'android' && {
|
|
284
|
-
paddingHorizontal: 2,
|
|
285
|
-
paddingTop: 2
|
|
286
|
+
paddingHorizontal: Platform.OS === 'android' ? 2 : 0,
|
|
287
|
+
paddingTop: Platform.OS === 'android' ? 2 : 0
|
|
286
288
|
})
|
|
287
289
|
}
|
|
288
290
|
})
|
|
@@ -65,7 +65,6 @@ const selectContainerStyle = (windowHeight, windowWidth) => ({
|
|
|
65
65
|
})
|
|
66
66
|
|
|
67
67
|
const TOTAL_COLUMNS = 12
|
|
68
|
-
const MAX_ITEMS_THRESHOLD = 12
|
|
69
68
|
|
|
70
69
|
const MultiSelectFilter = React.forwardRef(
|
|
71
70
|
(
|
|
@@ -174,13 +173,15 @@ const MultiSelectFilter = React.forwardRef(
|
|
|
174
173
|
const getCopy = useCopy({ dictionary, copy })
|
|
175
174
|
const colSizeNotMobile = items.length > rowLimit ? 2 : 1
|
|
176
175
|
const colSize = viewport !== 'xs' ? colSizeNotMobile : 1
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
|
|
177
|
+
let rowLength = items.length
|
|
178
|
+
if (viewport !== 'xs' && colSize === 2) {
|
|
179
|
+
rowLength = Math.ceil(items.length / 2)
|
|
180
|
+
}
|
|
179
181
|
|
|
180
182
|
React.useEffect(() => {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}, [colSize])
|
|
183
|
+
setMaxWidth(items.length >= rowLimit)
|
|
184
|
+
}, [items.length, rowLimit])
|
|
184
185
|
|
|
185
186
|
React.useEffect(() => setCheckedIds(currentValues ?? []), [currentValues])
|
|
186
187
|
|
|
@@ -414,14 +415,14 @@ const MultiSelectFilter = React.forwardRef(
|
|
|
414
415
|
dismissWhenPressedOutside={dismissWhenPressedOutside}
|
|
415
416
|
onClose={onClose}
|
|
416
417
|
overlaidPosition={overlaidPosition}
|
|
417
|
-
maxHeight={items.length
|
|
418
|
+
maxHeight={items.length >= rowLimit ? true : maxHeight}
|
|
418
419
|
maxHeightSize={maxHeightSize}
|
|
419
420
|
maxWidthSize={maxWidthSize}
|
|
420
421
|
minHeight={minHeight}
|
|
421
422
|
minWidth={minWidth}
|
|
422
423
|
tokens={{
|
|
423
424
|
...tokens,
|
|
424
|
-
maxWidth: items.length
|
|
425
|
+
maxWidth: items.length >= rowLimit ? true : maxWidth,
|
|
425
426
|
borderColor: containerBorderColor
|
|
426
427
|
}}
|
|
427
428
|
copy={copy}
|
package/src/Spacer/Spacer.jsx
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { View, StyleSheet } from 'react-native'
|
|
4
|
+
import {
|
|
5
|
+
a11yProps,
|
|
6
|
+
selectSystemProps,
|
|
7
|
+
spacingProps,
|
|
8
|
+
useSpacingScale,
|
|
9
|
+
viewProps,
|
|
10
|
+
StyleSheet as StyleSheetUtils,
|
|
11
|
+
createMediaQueryStyles
|
|
12
|
+
} from '../utils'
|
|
13
|
+
import useMediaQuerySpacing from '../utils/useMediaQuerySpacing'
|
|
14
|
+
import useTheme from '../ThemeProvider/useTheme'
|
|
5
15
|
|
|
6
16
|
/**
|
|
7
17
|
* @typedef {import('../utils/props/spacingProps.js').SpacingValue} SpacingValue
|
|
@@ -56,11 +66,43 @@ const selectSizeStyle = (size, direction) => ({
|
|
|
56
66
|
* Spacer has no content and is ignored by tools such as screen readers. Use `Divider` for
|
|
57
67
|
* separations between elements that may be treated as semantically meaningful on web.
|
|
58
68
|
*/
|
|
59
|
-
const Spacer = React.forwardRef(({ space = 1, direction = 'column', ...rest }, ref) => {
|
|
60
|
-
const
|
|
61
|
-
|
|
69
|
+
const Spacer = React.forwardRef(({ space = 1, direction = 'column', dataSet, ...rest }, ref) => {
|
|
70
|
+
const {
|
|
71
|
+
themeOptions: { enableMediaQueryStyleSheet }
|
|
72
|
+
} = useTheme()
|
|
62
73
|
|
|
63
|
-
|
|
74
|
+
const { sizeByViewport } = useMediaQuerySpacing(space)
|
|
75
|
+
|
|
76
|
+
const fallbackSize = useSpacingScale(space)
|
|
77
|
+
const sizeStyle = selectSizeStyle(fallbackSize, direction)
|
|
78
|
+
|
|
79
|
+
let spacerStyles
|
|
80
|
+
let dataSetValue = dataSet
|
|
81
|
+
|
|
82
|
+
if (enableMediaQueryStyleSheet) {
|
|
83
|
+
const sizeKey = direction === 'row' ? 'width' : 'height'
|
|
84
|
+
const stylesByViewport = {
|
|
85
|
+
xs: { [sizeKey]: sizeByViewport.xs, ...staticStyles.stretch },
|
|
86
|
+
sm: { [sizeKey]: sizeByViewport.sm, ...staticStyles.stretch },
|
|
87
|
+
md: { [sizeKey]: sizeByViewport.md, ...staticStyles.stretch },
|
|
88
|
+
lg: { [sizeKey]: sizeByViewport.lg, ...staticStyles.stretch },
|
|
89
|
+
xl: { [sizeKey]: sizeByViewport.xl, ...staticStyles.stretch }
|
|
90
|
+
}
|
|
91
|
+
const mediaQueryStyles = createMediaQueryStyles(stylesByViewport)
|
|
92
|
+
|
|
93
|
+
const { ids, styles } = StyleSheetUtils.create({
|
|
94
|
+
spacer: {
|
|
95
|
+
...mediaQueryStyles
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
spacerStyles = styles.spacer
|
|
100
|
+
dataSetValue = { media: ids.spacer, ...dataSet }
|
|
101
|
+
} else {
|
|
102
|
+
spacerStyles = [staticStyles.stretch, sizeStyle]
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return <View ref={ref} style={spacerStyles} dataSet={dataSetValue} {...selectProps(rest)} />
|
|
64
106
|
})
|
|
65
107
|
Spacer.displayName = 'Spacer'
|
|
66
108
|
|
|
@@ -78,7 +120,12 @@ Spacer.propTypes = {
|
|
|
78
120
|
* - `'column'` (default) applies space vertically; has a fixed height and not width.
|
|
79
121
|
* - `'row'` applies space horizontally; has a fixed width and not height.
|
|
80
122
|
*/
|
|
81
|
-
direction: PropTypes.oneOf(['column', 'row'])
|
|
123
|
+
direction: PropTypes.oneOf(['column', 'row']),
|
|
124
|
+
/**
|
|
125
|
+
* Data attributes to be applied to the element. When media query stylesheet is enabled,
|
|
126
|
+
* this will include media query IDs for responsive styling.
|
|
127
|
+
*/
|
|
128
|
+
dataSet: PropTypes.object
|
|
82
129
|
}
|
|
83
130
|
|
|
84
131
|
const staticStyles = StyleSheet.create({
|
package/src/utils/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export { default as info } from './info'
|
|
|
9
9
|
export { default as useCopy } from './useCopy'
|
|
10
10
|
export { default as useHash } from './useHash'
|
|
11
11
|
export { default as useSpacingScale } from './useSpacingScale'
|
|
12
|
+
export { default as useMediaQuerySpacing } from './useMediaQuerySpacing'
|
|
12
13
|
export { default as useResponsiveProp } from './useResponsiveProp'
|
|
13
14
|
export { default as useOverlaidPosition } from './useOverlaidPosition'
|
|
14
15
|
export { default as useSafeLayoutEffect } from './useSafeLayoutEffect'
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { useThemeTokens } from '../ThemeProvider'
|
|
2
|
+
import { resolveResponsiveProp } from './useResponsiveProp'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import('@telus-uds/system-constants/viewports').Viewport} Viewport
|
|
6
|
+
* @typedef {import('./props/spacingProps.js').SpacingValue} SpacingValue
|
|
7
|
+
* @typedef {import('./props/spacingProps.js').SpacingIndex} SpacingIndex
|
|
8
|
+
* @typedef {import('./props/spacingProps.js').SpacingObject} SpacingObject
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A utility hook that simplifies implementing media query-based responsive spacing.
|
|
13
|
+
*
|
|
14
|
+
* This hook handles the complexity of:
|
|
15
|
+
* - Detecting if a space value is responsive (has viewport keys)
|
|
16
|
+
* - Fetching theme tokens for each viewport
|
|
17
|
+
* - Resolving the correct space index for each viewport
|
|
18
|
+
* - Extracting actual pixel values from theme tokens
|
|
19
|
+
*
|
|
20
|
+
* ## Usage
|
|
21
|
+
*
|
|
22
|
+
* ```jsx
|
|
23
|
+
* const { sizeByViewport } = useMediaQuerySpacing(space, 'spacingScale')
|
|
24
|
+
*
|
|
25
|
+
* // Use sizeByViewport to create media query styles
|
|
26
|
+
* const stylesByViewport = {
|
|
27
|
+
* xs: { padding: sizeByViewport.xs },
|
|
28
|
+
* sm: { padding: sizeByViewport.sm },
|
|
29
|
+
* md: { padding: sizeByViewport.md },
|
|
30
|
+
* lg: { padding: sizeByViewport.lg },
|
|
31
|
+
* xl: { padding: sizeByViewport.xl }
|
|
32
|
+
* }
|
|
33
|
+
* const mediaQueryStyles = createMediaQueryStyles(stylesByViewport)
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* ## Parameters
|
|
37
|
+
*
|
|
38
|
+
* @param {SpacingValue} spaceValue - A spacing value (number or responsive object with viewport keys)
|
|
39
|
+
* @param {string} tokenKey - The theme token key to use (e.g., 'spacingScale', 'Typography')
|
|
40
|
+
* @param {object} [tokens={}] - Additional tokens to pass to useThemeTokens
|
|
41
|
+
* @param {object} [variant={}] - Variant to pass to useThemeTokens
|
|
42
|
+
*
|
|
43
|
+
* ## Returns
|
|
44
|
+
*
|
|
45
|
+
* @returns {{
|
|
46
|
+
* spaceIndexByViewport: { xs: number, sm: number, md: number, lg: number, xl: number },
|
|
47
|
+
* sizeByViewport: { xs: number, sm: number, md: number, lg: number, xl: number },
|
|
48
|
+
* tokensByViewport: { xs: object, sm: object, md: object, lg: object, xl: object }
|
|
49
|
+
* }}
|
|
50
|
+
*
|
|
51
|
+
* - `spaceIndexByViewport`: The resolved space index for each viewport
|
|
52
|
+
* - `sizeByViewport`: The actual pixel/number values for each viewport
|
|
53
|
+
* - `tokensByViewport`: The full theme tokens for each viewport (for advanced use cases)
|
|
54
|
+
*/
|
|
55
|
+
const useMediaQuerySpacing = (spaceValue, tokenKey = 'spacingScale', tokens = {}, variant = {}) => {
|
|
56
|
+
const isResponsive =
|
|
57
|
+
typeof spaceValue === 'object' &&
|
|
58
|
+
spaceValue !== null &&
|
|
59
|
+
!spaceValue.space &&
|
|
60
|
+
!spaceValue.options
|
|
61
|
+
|
|
62
|
+
const getSpaceIndex = (viewport) => {
|
|
63
|
+
if (isResponsive) {
|
|
64
|
+
return resolveResponsiveProp(spaceValue, viewport)
|
|
65
|
+
}
|
|
66
|
+
if (typeof spaceValue === 'number') {
|
|
67
|
+
return spaceValue
|
|
68
|
+
}
|
|
69
|
+
return spaceValue?.space ?? 1
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const spaceIndexByViewport = {
|
|
73
|
+
xs: getSpaceIndex('xs'),
|
|
74
|
+
sm: getSpaceIndex('sm'),
|
|
75
|
+
md: getSpaceIndex('md'),
|
|
76
|
+
lg: getSpaceIndex('lg'),
|
|
77
|
+
xl: getSpaceIndex('xl')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const tokensXs = useThemeTokens(tokenKey, tokens, variant, {
|
|
81
|
+
space: spaceIndexByViewport.xs,
|
|
82
|
+
viewport: 'xs'
|
|
83
|
+
})
|
|
84
|
+
const tokensSm = useThemeTokens(tokenKey, tokens, variant, {
|
|
85
|
+
space: spaceIndexByViewport.sm,
|
|
86
|
+
viewport: 'sm'
|
|
87
|
+
})
|
|
88
|
+
const tokensMd = useThemeTokens(tokenKey, tokens, variant, {
|
|
89
|
+
space: spaceIndexByViewport.md,
|
|
90
|
+
viewport: 'md'
|
|
91
|
+
})
|
|
92
|
+
const tokensLg = useThemeTokens(tokenKey, tokens, variant, {
|
|
93
|
+
space: spaceIndexByViewport.lg,
|
|
94
|
+
viewport: 'lg'
|
|
95
|
+
})
|
|
96
|
+
const tokensXl = useThemeTokens(tokenKey, tokens, variant, {
|
|
97
|
+
space: spaceIndexByViewport.xl,
|
|
98
|
+
viewport: 'xl'
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const sizeByViewport = {
|
|
102
|
+
xs: tokensXs.size ?? 0,
|
|
103
|
+
sm: tokensSm.size ?? 0,
|
|
104
|
+
md: tokensMd.size ?? 0,
|
|
105
|
+
lg: tokensLg.size ?? 0,
|
|
106
|
+
xl: tokensXl.size ?? 0
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const tokensByViewport = {
|
|
110
|
+
xs: tokensXs,
|
|
111
|
+
sm: tokensSm,
|
|
112
|
+
md: tokensMd,
|
|
113
|
+
lg: tokensLg,
|
|
114
|
+
xl: tokensXl
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
spaceIndexByViewport,
|
|
119
|
+
sizeByViewport,
|
|
120
|
+
tokensByViewport
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export default useMediaQuerySpacing
|