@telus-uds/components-base 3.18.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +32 -1
  2. package/jest.config.cjs +10 -2
  3. package/lib/cjs/Box/Box.js +114 -62
  4. package/lib/cjs/Box/backgroundImageStylesMap.js +136 -28
  5. package/lib/cjs/Button/ButtonDropdown.js +1 -0
  6. package/lib/cjs/Carousel/Carousel.js +1 -1
  7. package/lib/cjs/ExpandCollapseMini/ExpandCollapseMiniControl.js +8 -21
  8. package/lib/cjs/Link/LinkBase.js +8 -9
  9. package/lib/cjs/MultiSelectFilter/MultiSelectFilter.js +10 -10
  10. package/lib/cjs/Spacer/Spacer.js +65 -5
  11. package/lib/cjs/StepTracker/Step.js +12 -1
  12. package/lib/cjs/StepTracker/StepTracker.js +15 -4
  13. package/lib/cjs/TabBar/TabBar.js +4 -2
  14. package/lib/cjs/TabBar/index.js +2 -0
  15. package/lib/cjs/Tooltip/Backdrop.js +1 -1
  16. package/lib/cjs/utils/index.js +17 -1
  17. package/lib/cjs/utils/isTouchDevice.js +34 -0
  18. package/lib/cjs/utils/useMediaQuerySpacing.js +121 -0
  19. package/lib/esm/Box/Box.js +113 -63
  20. package/lib/esm/Box/backgroundImageStylesMap.js +134 -27
  21. package/lib/esm/Button/ButtonDropdown.js +1 -0
  22. package/lib/esm/Carousel/Carousel.js +2 -2
  23. package/lib/esm/ExpandCollapseMini/ExpandCollapseMiniControl.js +8 -21
  24. package/lib/esm/Link/LinkBase.js +8 -9
  25. package/lib/esm/MultiSelectFilter/MultiSelectFilter.js +10 -10
  26. package/lib/esm/Spacer/Spacer.js +66 -6
  27. package/lib/esm/StepTracker/Step.js +12 -1
  28. package/lib/esm/StepTracker/StepTracker.js +15 -4
  29. package/lib/esm/TabBar/TabBar.js +4 -2
  30. package/lib/esm/TabBar/index.js +2 -0
  31. package/lib/esm/Tooltip/Backdrop.js +1 -1
  32. package/lib/esm/utils/index.js +3 -1
  33. package/lib/esm/utils/isTouchDevice.js +27 -0
  34. package/lib/esm/utils/useMediaQuerySpacing.js +116 -0
  35. package/lib/package.json +2 -2
  36. package/package.json +2 -2
  37. package/src/Box/Box.jsx +97 -55
  38. package/src/Box/backgroundImageStylesMap.js +48 -15
  39. package/src/Button/ButtonDropdown.jsx +1 -0
  40. package/src/Carousel/Carousel.jsx +3 -2
  41. package/src/ExpandCollapseMini/ExpandCollapseMiniControl.jsx +9 -16
  42. package/src/Link/LinkBase.jsx +11 -9
  43. package/src/MultiSelectFilter/MultiSelectFilter.jsx +11 -10
  44. package/src/Spacer/Spacer.jsx +54 -7
  45. package/src/StepTracker/Step.jsx +47 -27
  46. package/src/StepTracker/StepTracker.jsx +9 -1
  47. package/src/TabBar/TabBar.jsx +3 -1
  48. package/src/TabBar/index.js +3 -0
  49. package/src/Tooltip/Backdrop.jsx +1 -1
  50. package/src/utils/index.js +2 -0
  51. package/src/utils/isTouchDevice.js +34 -0
  52. package/src/utils/useMediaQuerySpacing.js +124 -0
@@ -7,8 +7,8 @@ import StyleSheet from "react-native-web/dist/exports/StyleSheet";
7
7
  import ImageBackground from "react-native-web/dist/exports/ImageBackground";
8
8
  import Image from "react-native-web/dist/exports/Image";
9
9
  import { useTheme, useThemeTokens, useResponsiveThemeTokens, useThemeTokensCallback } from '../ThemeProvider';
10
- import { a11yProps, createMediaQueryStyles, getA11yPropsFromHtmlTag, getTokensPropType, layoutTags, responsiveProps, selectSystemProps, spacingProps, useResponsiveProp, useSpacingScale, variantProp, viewProps, StyleSheet as RNMQStyleSheet, getSpacingScale } from '../utils';
11
- import backgroundImageStylesMap from './backgroundImageStylesMap';
10
+ import { a11yProps, createMediaQueryStyles, getA11yPropsFromHtmlTag, getTokensPropType, layoutTags, responsiveProps, selectSystemProps, spacingProps, useResponsiveProp, useSpacingScale, variantProp, viewProps, StyleSheet as RNMQStyleSheet, getSpacingScale, formatImageSource } from '../utils';
11
+ import backgroundImageStylesMap, { backgroundPositions } from './backgroundImageStylesMap';
12
12
  import { useViewport } from '../ViewportProvider';
13
13
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
14
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
@@ -68,39 +68,51 @@ const setBackgroundImage = _ref2 => {
68
68
  backgroundImageResizeMode,
69
69
  backgroundImagePosition,
70
70
  backgroundImageAlign,
71
- backgroundImageWidth,
72
- backgroundImageHeight,
73
- content
71
+ content,
72
+ testID
74
73
  } = _ref2;
75
- if (backgroundImageResizeMode === 'contain') {
76
- const containedViewStyle = {
77
- ...staticStyles.containedView,
78
- width: backgroundImageWidth,
79
- height: backgroundImageHeight,
80
- ...backgroundImageStylesMap[`${backgroundImagePosition}-${backgroundImageAlign}`]
81
- };
74
+ const backgroundImageTestID = testID ? `${testID}-background-image` : undefined;
75
+ if (backgroundImageResizeMode === 'contain' && backgroundImagePosition && backgroundImageAlign) {
76
+ const positionKey = `${backgroundImagePosition}-${backgroundImageAlign}`;
77
+ if (Platform.OS === 'web') {
78
+ const backgroundPosition = backgroundPositions[positionKey] || 'center center';
79
+ const backgroundImageStyle = {
80
+ backgroundImage: `url(${src})`,
81
+ backgroundSize: 'contain',
82
+ backgroundRepeat: 'no-repeat',
83
+ backgroundPosition
84
+ };
85
+ return /*#__PURE__*/_jsx(View, {
86
+ style: [staticStyles.imageBackground, backgroundImageStyle],
87
+ "aria-label": alt,
88
+ testID: backgroundImageTestID,
89
+ children: content
90
+ });
91
+ }
92
+ const positionStyles = backgroundImageStylesMap[positionKey] || {};
82
93
  return /*#__PURE__*/_jsxs(View, {
83
- style: staticStyles.containedContainer,
84
- children: [/*#__PURE__*/_jsx(View, {
85
- style: containedViewStyle,
86
- children: /*#__PURE__*/_jsx(Image, {
87
- source: {
88
- uri: src
89
- },
90
- alt: alt,
91
- style: staticStyles.containedImage,
92
- accessibilityIgnoresInvertColors: true
93
- })
94
- }), content]
94
+ style: staticStyles.containContainer,
95
+ children: [/*#__PURE__*/_jsx(Image, {
96
+ source: src,
97
+ resizeMode: backgroundImageResizeMode,
98
+ style: [staticStyles.containImage, positionStyles],
99
+ accessible: true,
100
+ accessibilityLabel: alt,
101
+ accessibilityIgnoresInvertColors: true,
102
+ testID: backgroundImageTestID
103
+ }), /*#__PURE__*/_jsx(View, {
104
+ style: staticStyles.contentOverlay,
105
+ children: content
106
+ })]
95
107
  });
96
108
  }
97
109
  return /*#__PURE__*/_jsx(ImageBackground, {
98
- source: {
99
- uri: src
100
- },
101
- alt: alt,
102
- style: staticStyles.backgroundImageContainer,
110
+ source: src,
103
111
  resizeMode: backgroundImageResizeMode,
112
+ style: staticStyles.imageBackground,
113
+ accessible: true,
114
+ accessibilityLabel: alt,
115
+ testID: backgroundImageTestID,
104
116
  children: content
105
117
  });
106
118
  };
@@ -257,31 +269,61 @@ const Box = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
257
269
  align = ''
258
270
  } = backgroundImage || {};
259
271
  const backgroundImageResizeMode = useResponsiveProp(resizeMode, 'cover');
260
- const backgroundImagePosition = useResponsiveProp(position, 'none');
261
- const backgroundImageAlign = useResponsiveProp(align, 'stretch');
262
- const [backgroundImageWidth, setBackgroundImageWidth] = React.useState(0);
263
- const [backgroundImageHeight, setBackgroundImageHeight] = React.useState(0);
264
- if (backgroundImage) content = setBackgroundImage({
265
- src,
266
- alt,
267
- backgroundImageResizeMode,
268
- backgroundImagePosition,
269
- backgroundImageAlign,
270
- backgroundImageWidth,
271
- backgroundImageHeight,
272
- content
273
- });
274
- React.useEffect(() => {
275
- if (backgroundImage && backgroundImageWidth === 0 && backgroundImageHeight === 0) {
276
- Image.getSize(src, (width, height) => {
277
- // Only update the state if the size has changed
278
- if (width !== backgroundImageWidth || height !== backgroundImageHeight) {
279
- setBackgroundImageWidth(width);
280
- setBackgroundImageHeight(height);
281
- }
272
+ const backgroundImagePosition = useResponsiveProp(position);
273
+ const backgroundImageAlign = useResponsiveProp(align);
274
+ const imageSourceViewport = formatImageSource(useResponsiveProp(src));
275
+ if (backgroundImage && src) {
276
+ const {
277
+ paddingTop,
278
+ paddingBottom,
279
+ paddingLeft,
280
+ paddingRight,
281
+ ...containerStyle
282
+ } = boxStyles;
283
+ const hasPadding = paddingTop || paddingBottom || paddingLeft || paddingRight;
284
+ const paddedContent = hasPadding ? /*#__PURE__*/_jsx(View, {
285
+ style: {
286
+ paddingTop,
287
+ paddingBottom,
288
+ paddingLeft,
289
+ paddingRight
290
+ },
291
+ children: children
292
+ }) : children;
293
+ content = setBackgroundImage({
294
+ src: imageSourceViewport,
295
+ alt,
296
+ backgroundImageResizeMode,
297
+ backgroundImagePosition,
298
+ backgroundImageAlign,
299
+ content: paddedContent,
300
+ testID
301
+ });
302
+ const dataSetValue = boxMediaIds ? {
303
+ media: boxMediaIds,
304
+ ...dataSet
305
+ } : dataSet;
306
+ if (scroll) {
307
+ const scrollProps = typeof scroll === 'object' ? scroll : {};
308
+ scrollProps.contentContainerStyle = [containerStyle, scrollProps.contentContainerStyle];
309
+ return /*#__PURE__*/_jsx(ScrollView, {
310
+ ...scrollProps,
311
+ ...props,
312
+ testID: testID,
313
+ dataSet: dataSetValue,
314
+ ref: ref,
315
+ children: content
282
316
  });
283
317
  }
284
- }, [backgroundImage, backgroundImageWidth, backgroundImageHeight, src]);
318
+ return /*#__PURE__*/_jsx(View, {
319
+ ...props,
320
+ style: containerStyle,
321
+ testID: testID,
322
+ dataSet: dataSetValue,
323
+ ref: ref,
324
+ children: content
325
+ });
326
+ }
285
327
  const dataSetValue = boxMediaIds ? {
286
328
  media: boxMediaIds,
287
329
  ...dataSet
@@ -394,10 +436,12 @@ Box.propTypes = {
394
436
  */
395
437
  customGradient: PropTypes.func,
396
438
  /**
397
- * Use this prop to add a background image to the box.
439
+ * Apply background image to the box.
398
440
  */
399
441
  backgroundImage: PropTypes.shape({
400
- src: PropTypes.string.isRequired,
442
+ // The image src is either a URI string or a number (when a local image src is bundled in IOS or Android app)
443
+ // src is an object when used responsively to provide different image sources for different screen sizes
444
+ src: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]).isRequired,
401
445
  alt: PropTypes.string,
402
446
  resizeMode: responsiveProps.getTypeOptionallyByViewport(PropTypes.oneOf(['cover', 'contain', 'stretch', 'repeat', 'center'])),
403
447
  position: responsiveProps.getTypeOptionallyByViewport(PropTypes.oneOf(['none', 'bottom', 'left', 'right', 'top'])),
@@ -406,18 +450,24 @@ Box.propTypes = {
406
450
  };
407
451
  export default Box;
408
452
  const staticStyles = StyleSheet.create({
409
- backgroundImageContainer: {
410
- flex: 1
453
+ imageBackground: {
454
+ width: '100%',
455
+ height: '100%'
411
456
  },
412
- containedContainer: {
413
- flex: 1,
414
- overflow: 'hidden'
457
+ contentOverlay: {
458
+ position: 'relative',
459
+ width: '100%',
460
+ height: '100%',
461
+ zIndex: 1
415
462
  },
416
- containedView: {
417
- zIndex: -1,
418
- position: 'absolute'
463
+ containContainer: {
464
+ width: '100%',
465
+ height: '100%',
466
+ overflow: 'hidden',
467
+ position: 'relative'
419
468
  },
420
- containedImage: {
469
+ containImage: {
470
+ position: 'absolute',
421
471
  width: '100%',
422
472
  height: '100%'
423
473
  }
@@ -1,11 +1,15 @@
1
- export default {
1
+ import Platform from "react-native-web/dist/exports/Platform";
2
+ const webStyles = {
2
3
  'top-start': {
3
- top: 0
4
+ top: 0,
5
+ left: 0
4
6
  },
5
7
  'top-center': {
6
- left: 0,
7
- right: 0,
8
- marginHorizontal: 'auto'
8
+ top: 0,
9
+ left: '50%',
10
+ transform: [{
11
+ translateX: '-50%'
12
+ }]
9
13
  },
10
14
  'top-end': {
11
15
  top: 0,
@@ -16,60 +20,160 @@ export default {
16
20
  right: 0
17
21
  },
18
22
  'left-start': {
19
- top: 0
23
+ top: 0,
24
+ left: 0
20
25
  },
21
26
  'left-center': {
22
- top: 0,
27
+ top: '50%',
28
+ left: 0,
29
+ transform: [{
30
+ translateY: '-50%'
31
+ }]
32
+ },
33
+ 'right-center': {
34
+ top: '50%',
35
+ right: 0,
36
+ transform: [{
37
+ translateY: '-50%'
38
+ }]
39
+ },
40
+ 'bottom-start': {
41
+ bottom: 0,
42
+ left: 0
43
+ },
44
+ 'left-end': {
45
+ bottom: 0,
46
+ left: 0
47
+ },
48
+ 'bottom-center': {
49
+ bottom: 0,
50
+ left: '50%',
51
+ transform: [{
52
+ translateX: '-50%'
53
+ }]
54
+ },
55
+ 'bottom-end': {
23
56
  bottom: 0,
24
- marginVertical: 'auto'
57
+ right: 0
58
+ },
59
+ 'right-end': {
60
+ bottom: 0,
61
+ right: 0
25
62
  },
26
- 'none-start': {
63
+ 'top-stretch': {
64
+ top: 0,
65
+ left: 0,
66
+ right: 0,
67
+ width: '100%'
68
+ },
69
+ 'left-stretch': {
27
70
  top: 0,
28
71
  bottom: 0,
29
- marginVertical: 'auto'
72
+ left: 0,
73
+ height: '100%'
30
74
  },
31
- 'none-center': {
75
+ 'right-stretch': {
32
76
  top: 0,
77
+ bottom: 0,
78
+ right: 0,
79
+ height: '100%'
80
+ },
81
+ 'bottom-stretch': {
33
82
  bottom: 0,
34
83
  left: 0,
35
84
  right: 0,
36
- margin: 'auto'
85
+ width: '100%'
86
+ }
87
+ };
88
+ const webBackgroundPositions = {
89
+ 'top-start': 'left top',
90
+ 'top-center': 'center top',
91
+ 'top-end': 'right top',
92
+ 'bottom-start': 'left bottom',
93
+ 'bottom-center': 'center bottom',
94
+ 'bottom-end': 'right bottom',
95
+ 'left-center': 'left center',
96
+ 'right-center': 'right center'
97
+ };
98
+ const nativeStyles = {
99
+ 'top-start': {
100
+ top: 0,
101
+ left: 0,
102
+ width: 150,
103
+ height: 200
37
104
  },
38
- 'right-center': {
105
+ 'top-center': {
106
+ top: 0,
107
+ left: '50%',
108
+ marginLeft: -75,
109
+ width: 150,
110
+ height: 200
111
+ },
112
+ 'top-end': {
39
113
  top: 0,
40
- bottom: 0,
41
114
  right: 0,
42
- marginVertical: 'auto'
115
+ width: 150,
116
+ height: 200
43
117
  },
44
- 'none-end': {
118
+ 'right-start': {
45
119
  top: 0,
46
- bottom: 0,
47
120
  right: 0,
48
- marginVertical: 'auto'
121
+ width: 150,
122
+ height: 200
123
+ },
124
+ 'left-start': {
125
+ top: 0,
126
+ left: 0,
127
+ width: 150,
128
+ height: 200
129
+ },
130
+ 'left-center': {
131
+ left: 0,
132
+ top: '50%',
133
+ marginTop: -100,
134
+ width: 150,
135
+ height: 200
136
+ },
137
+ 'right-center': {
138
+ right: 0,
139
+ top: '50%',
140
+ marginTop: -100,
141
+ width: 150,
142
+ height: 200
49
143
  },
50
144
  'bottom-start': {
51
145
  bottom: 0,
52
- left: 0
146
+ left: 0,
147
+ width: 150,
148
+ height: 200
53
149
  },
54
150
  'left-end': {
55
151
  bottom: 0,
56
- left: 0
152
+ left: 0,
153
+ width: 150,
154
+ height: 200
57
155
  },
58
156
  'bottom-center': {
59
- left: 0,
60
- right: 0,
61
157
  bottom: 0,
62
- marginHorizontal: 'auto'
158
+ left: '50%',
159
+ marginLeft: -75,
160
+ width: 150,
161
+ height: 200
63
162
  },
64
163
  'bottom-end': {
164
+ bottom: 0,
65
165
  right: 0,
66
- bottom: 0
166
+ width: 150,
167
+ height: 200
67
168
  },
68
169
  'right-end': {
170
+ bottom: 0,
69
171
  right: 0,
70
- bottom: 0
172
+ width: 150,
173
+ height: 200
71
174
  },
72
175
  'top-stretch': {
176
+ top: 0,
73
177
  left: 0,
74
178
  right: 0,
75
179
  width: '100%'
@@ -77,6 +181,7 @@ export default {
77
181
  'left-stretch': {
78
182
  top: 0,
79
183
  bottom: 0,
184
+ left: 0,
80
185
  height: '100%'
81
186
  },
82
187
  'right-stretch': {
@@ -86,9 +191,11 @@ export default {
86
191
  height: '100%'
87
192
  },
88
193
  'bottom-stretch': {
194
+ bottom: 0,
89
195
  left: 0,
90
196
  right: 0,
91
- bottom: 0,
92
197
  width: '100%'
93
198
  }
94
- };
199
+ };
200
+ export const backgroundPositions = Platform.OS === 'web' ? webBackgroundPositions : {};
201
+ export default Platform.OS === 'web' ? webStyles : nativeStyles;
@@ -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
@@ -8,7 +8,7 @@ import Dimensions from "react-native-web/dist/exports/Dimensions";
8
8
  import PropTypes from 'prop-types';
9
9
  import { useThemeTokens } from '../ThemeProvider';
10
10
  import { useViewport } from '../ViewportProvider';
11
- import { getTokensPropType, getA11yPropsFromHtmlTag, layoutTags, variantProp, selectSystemProps, a11yProps, viewProps, useCopy, unpackFragment } from '../utils';
11
+ import { getTokensPropType, getA11yPropsFromHtmlTag, layoutTags, variantProp, selectSystemProps, a11yProps, viewProps, useCopy, unpackFragment, isTouchDevice } from '../utils';
12
12
  import { useA11yInfo } from '../A11yInfoProvider';
13
13
  import { CarouselProvider } from './CarouselContext';
14
14
  import CarouselItem from './CarouselItem';
@@ -717,7 +717,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
717
717
  return false;
718
718
  }
719
719
  if (Platform.OS === 'web') {
720
- return !!(viewport === 'xs' || viewport === 'sm');
720
+ return !!(viewport === 'xs' || viewport === 'sm' || viewport === 'md' && isTouchDevice());
721
721
  }
722
722
  return true;
723
723
  }, [viewport, totalItems]);
@@ -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
- // Calculate baseline alignment to vertically center icon with text
71
- // This combines font and icon metrics with adjustments for visual balance
72
- const fontBaseline = fontSize / hoverTranslateY; // Quarter of font size - adjusts for text's visual center point
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
- if (Platform.OS !== 'web') {
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,
@@ -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
- return [outerBorderStyles, staticStyles.outerBorderStyles, blockLeftStyle, decorationStyles, hasIcon && staticStyles.rowContainer];
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: iconSpace,
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
- outerBorderStyles: {
269
+ outerBorderCompensation: {
267
270
  ...(Platform.OS !== 'web' && {
268
- margin: 0,
269
271
  marginHorizontal: 2,
270
- padding: 0
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
- const itemsLengthNotMobile = items.length > 24 ? items.length / 2 : rowLimit;
175
- const rowLength = viewport !== 'xs' ? itemsLengthNotMobile : items.length;
173
+ let rowLength = items.length;
174
+ if (viewport !== 'xs' && colSize === 2) {
175
+ rowLength = Math.ceil(items.length / 2);
176
+ }
176
177
  React.useEffect(() => {
177
- if (colSize === 1) return setMaxWidth(false);
178
- return colSize === 2 && setMaxWidth(true);
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 > MAX_ITEMS_THRESHOLD ? true : maxHeight,
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 > MAX_ITEMS_THRESHOLD ? true : maxWidth,
433
+ maxWidth: items.length >= rowLimit ? true : maxWidth,
434
434
  borderColor: containerBorderColor
435
435
  },
436
436
  copy: copy,
@@ -482,9 +482,9 @@ MultiSelectFilter.propTypes = {
482
482
  */
483
483
  label: PropTypes.string.isRequired,
484
484
  /**
485
- * The text for the subtitle
485
+ * The text for the subtitle. Can also be JSX.
486
486
  */
487
- subtitle: PropTypes.string,
487
+ subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
488
488
  /**
489
489
  * An optional unique string may be provided to identify the ButtonDropdown.
490
490
  * If not provided, the label is used.