@telus-uds/components-base 1.24.1 → 1.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +25 -2
  2. package/lib/Box/Box.js +3 -1
  3. package/lib/Divider/Divider.js +3 -0
  4. package/lib/FlexGrid/FlexGrid.js +3 -1
  5. package/lib/Link/InlinePressable.js +8 -1
  6. package/lib/Link/LinkBase.js +6 -8
  7. package/lib/StackView/StackView.js +3 -1
  8. package/lib/StackView/StackWrapBox.js +3 -1
  9. package/lib/StackView/StackWrapGap.js +3 -1
  10. package/lib/ThemeProvider/utils/styles.js +1 -3
  11. package/lib/Tooltip/Tooltip.js +85 -158
  12. package/lib/Tooltip/Tooltip.native.js +357 -0
  13. package/lib/Tooltip/index.js +6 -1
  14. package/lib/Tooltip/shared.js +39 -0
  15. package/lib/Typography/Typography.js +30 -3
  16. package/lib/utils/floating-ui/index.js +43 -0
  17. package/lib/utils/floating-ui/index.native.js +43 -0
  18. package/lib-module/Box/Box.js +3 -1
  19. package/lib-module/Divider/Divider.js +3 -0
  20. package/lib-module/FlexGrid/FlexGrid.js +3 -1
  21. package/lib-module/Link/InlinePressable.js +8 -1
  22. package/lib-module/Link/LinkBase.js +6 -8
  23. package/lib-module/StackView/StackView.js +3 -1
  24. package/lib-module/StackView/StackWrapBox.js +3 -1
  25. package/lib-module/StackView/StackWrapGap.js +3 -1
  26. package/lib-module/ThemeProvider/utils/styles.js +1 -3
  27. package/lib-module/Tooltip/Tooltip.js +85 -155
  28. package/lib-module/Tooltip/Tooltip.native.js +326 -0
  29. package/lib-module/Tooltip/index.js +4 -1
  30. package/lib-module/Tooltip/shared.js +27 -0
  31. package/lib-module/Typography/Typography.js +29 -3
  32. package/lib-module/utils/floating-ui/index.js +1 -0
  33. package/lib-module/utils/floating-ui/index.native.js +1 -0
  34. package/package.json +4 -2
  35. package/src/Box/Box.jsx +1 -0
  36. package/src/Divider/Divider.jsx +3 -0
  37. package/src/FlexGrid/FlexGrid.jsx +1 -0
  38. package/src/Link/InlinePressable.jsx +9 -3
  39. package/src/Link/LinkBase.jsx +17 -10
  40. package/src/StackView/StackView.jsx +1 -0
  41. package/src/StackView/StackWrapBox.jsx +1 -0
  42. package/src/StackView/StackWrapGap.jsx +1 -0
  43. package/src/ThemeProvider/utils/styles.js +1 -3
  44. package/src/Tooltip/Tooltip.jsx +79 -156
  45. package/src/Tooltip/Tooltip.native.jsx +283 -0
  46. package/src/Tooltip/index.js +5 -1
  47. package/src/Tooltip/shared.js +27 -0
  48. package/src/Typography/Typography.jsx +27 -2
  49. package/src/utils/floating-ui/index.js +1 -0
  50. package/src/utils/floating-ui/index.native.js +1 -0
@@ -0,0 +1,326 @@
1
+ import React, { forwardRef, useEffect, useRef, useState } from 'react';
2
+ import Dimensions from "react-native-web/dist/exports/Dimensions";
3
+ import Platform from "react-native-web/dist/exports/Platform";
4
+ import Pressable from "react-native-web/dist/exports/Pressable";
5
+ import StyleSheet from "react-native-web/dist/exports/StyleSheet";
6
+ import Text from "react-native-web/dist/exports/Text";
7
+ import View from "react-native-web/dist/exports/View";
8
+ import propTypes from './shared';
9
+ import { applyShadowToken, applyTextStyles, useThemeTokens } from '../ThemeProvider';
10
+ import { a11yProps, selectSystemProps, selectTokens, viewProps } from '../utils';
11
+ import Backdrop from './Backdrop';
12
+ import getTooltipPosition from './getTooltipPosition';
13
+ import TooltipButton from '../TooltipButton';
14
+ import useCopy from '../utils/useCopy';
15
+ import dictionary from './dictionary';
16
+ import { jsx as _jsx } from "react/jsx-runtime";
17
+ import { jsxs as _jsxs } from "react/jsx-runtime";
18
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
19
+
20
+ const selectTooltipStyles = _ref => {
21
+ let {
22
+ backgroundColor,
23
+ paddingTop,
24
+ paddingBottom,
25
+ paddingLeft,
26
+ paddingRight,
27
+ borderRadius
28
+ } = _ref;
29
+ return {
30
+ backgroundColor,
31
+ paddingTop,
32
+ paddingBottom,
33
+ paddingLeft,
34
+ paddingRight,
35
+ borderRadius
36
+ };
37
+ };
38
+
39
+ const selectTooltipShadowStyles = _ref2 => {
40
+ let {
41
+ shadow,
42
+ borderRadius
43
+ } = _ref2;
44
+ return {
45
+ borderRadius,
46
+ ...applyShadowToken(shadow)
47
+ };
48
+ };
49
+
50
+ const selectTooltipPositionStyles = _ref3 => {
51
+ let {
52
+ top,
53
+ left,
54
+ width
55
+ } = _ref3;
56
+ return {
57
+ top,
58
+ left,
59
+ width
60
+ };
61
+ };
62
+
63
+ const selectArrowStyles = (_ref4, _ref5) => {
64
+ let {
65
+ backgroundColor,
66
+ arrowWidth,
67
+ arrowBorderRadius,
68
+ shadow
69
+ } = _ref4;
70
+ let {
71
+ position,
72
+ width: tooltipWidth,
73
+ height: tooltipHeight
74
+ } = _ref5;
75
+ // the arrow width is actually a diagonal of the rectangle that we'll use as a tip
76
+ const rectangleSide = Math.sqrt(arrowWidth * arrowWidth / 2); // position the arrow at the side and center of the tooltip - this happens before rotation
77
+ // so we use the rectangle size as basis
78
+
79
+ const verticalOffset = -1 * rectangleSide / 2;
80
+ const horizontalOffset = rectangleSide / 2; // percentage-based absolute positioning doesn't act well on native, so we have to
81
+ // calculate the pixel values
82
+
83
+ const directionalStyles = {
84
+ above: {
85
+ bottom: verticalOffset,
86
+ left: tooltipWidth / 2 - horizontalOffset,
87
+ transform: [{
88
+ rotateZ: '45deg'
89
+ }]
90
+ },
91
+ below: {
92
+ top: verticalOffset,
93
+ left: tooltipWidth / 2 - horizontalOffset,
94
+ transform: [{
95
+ rotateZ: '-135deg'
96
+ }]
97
+ },
98
+ left: {
99
+ right: verticalOffset,
100
+ top: tooltipHeight / 2 - horizontalOffset,
101
+ transform: [{
102
+ rotateZ: '-45deg'
103
+ }]
104
+ },
105
+ right: {
106
+ left: verticalOffset,
107
+ top: tooltipHeight / 2 - horizontalOffset,
108
+ transform: [{
109
+ rotateZ: '135deg'
110
+ }]
111
+ }
112
+ };
113
+ return {
114
+ backgroundColor,
115
+ width: rectangleSide,
116
+ height: rectangleSide,
117
+ borderBottomRightRadius: arrowBorderRadius,
118
+ // this corner will be the arrow tip after rotation
119
+ ...applyShadowToken(shadow),
120
+ ...directionalStyles[position]
121
+ };
122
+ };
123
+
124
+ const selectTextStyles = tokens => applyTextStyles(selectTokens('Typography', tokens));
125
+
126
+ const defaultControl = (pressableState, variant) => /*#__PURE__*/_jsx(TooltipButton, {
127
+ pressableState: pressableState,
128
+ variant: variant
129
+ });
130
+ /**
131
+ * Tooltip provides a descriptive and detailed explanation or instructions. It can be used next to an input label
132
+ * to help a user fill it in, or as a standalone component.
133
+ *
134
+ * By default the TooltipButton component will be used as a control for triggering the tooltip, but you may attach
135
+ * a tooltip to any other component. A render function can be used to adjust the control's styling on state changes (hover, focus, etc.).
136
+ *
137
+ * ### Positioning
138
+ * By default a Tooltip will be automatically positioned in a way that ensures it fits within the viewport.
139
+ * You may suggest a position with a prop - it will be used, unless the tooltip would end up outside the viewport.
140
+ *
141
+ * ### Usage criteria
142
+ * - You may use one when the information is useful only to a small percentage of users (ie. tech savvy people wouldn't need this info).
143
+ * - Tooltips may also be useful when vertical space is an issue.
144
+ */
145
+
146
+
147
+ const Tooltip = /*#__PURE__*/forwardRef((_ref6, ref) => {
148
+ let {
149
+ children,
150
+ content,
151
+ position = 'auto',
152
+ copy = 'en',
153
+ tokens,
154
+ variant,
155
+ ...rest
156
+ } = _ref6;
157
+ const [isOpen, setIsOpen] = useState(false);
158
+ const controlRef = useRef();
159
+ const [controlLayout, setControlLayout] = useState(null);
160
+ const [tooltipDimensions, setTooltipDimensions] = useState(null);
161
+ const [windowDimensions, setWindowDimensions] = useState(Dimensions.get('window'));
162
+ const [tooltipPosition, setTooltipPosition] = useState(null);
163
+ const getCopy = useCopy({
164
+ dictionary,
165
+ copy
166
+ });
167
+ const themeTokens = useThemeTokens('Tooltip', tokens, variant);
168
+ const {
169
+ arrowWidth,
170
+ arrowOffset
171
+ } = themeTokens;
172
+ useEffect(() => {
173
+ const subscription = Dimensions.addEventListener('change', _ref7 => {
174
+ let {
175
+ window
176
+ } = _ref7;
177
+ setWindowDimensions(window);
178
+ });
179
+ return () => subscription === null || subscription === void 0 ? void 0 : subscription.remove();
180
+ });
181
+
182
+ const toggleIsOpen = () => setIsOpen(!isOpen);
183
+
184
+ const close = () => setIsOpen(false);
185
+
186
+ const getPressableState = _ref8 => {
187
+ let {
188
+ pressed,
189
+ hovered,
190
+ focused
191
+ } = _ref8;
192
+ return {
193
+ pressed,
194
+ hover: hovered,
195
+ focus: focused
196
+ };
197
+ };
198
+
199
+ const onTooltipLayout = _ref9 => {
200
+ let {
201
+ nativeEvent: {
202
+ layout: {
203
+ width,
204
+ height
205
+ }
206
+ }
207
+ } = _ref9;
208
+
209
+ if (tooltipDimensions === null || tooltipDimensions.width !== width || tooltipDimensions.height !== height) {
210
+ setTooltipDimensions({
211
+ width: Platform.select({
212
+ web: width + 0.3,
213
+ // avoids often unnecessary line breaks due to subpixel rendering of fonts
214
+ native: width
215
+ }),
216
+ height
217
+ });
218
+ }
219
+ };
220
+
221
+ useEffect(() => {
222
+ if (isOpen) {
223
+ controlRef.current.measureInWindow((x, y, width, height) => {
224
+ setControlLayout({
225
+ x,
226
+ y,
227
+ width,
228
+ height
229
+ });
230
+ });
231
+ } else {
232
+ setControlLayout(null);
233
+ setTooltipDimensions(null);
234
+ setTooltipPosition(null);
235
+ }
236
+ }, [isOpen]);
237
+ useEffect(() => {
238
+ setIsOpen(false);
239
+ }, [windowDimensions]);
240
+ useEffect(() => {
241
+ if (tooltipPosition !== null && !(tooltipPosition !== null && tooltipPosition !== void 0 && tooltipPosition.isNormalized) || !isOpen || controlLayout === null || tooltipDimensions == null) {
242
+ return;
243
+ }
244
+
245
+ const updatedPosition = getTooltipPosition(position, {
246
+ controlLayout,
247
+ tooltipDimensions,
248
+ windowDimensions,
249
+ arrowWidth,
250
+ arrowOffset
251
+ }); // avoid ending up in an infinite normalization loop
252
+
253
+ if (tooltipPosition !== null && tooltipPosition !== void 0 && tooltipPosition.isNormalized && updatedPosition.isNormalized) {
254
+ return;
255
+ }
256
+
257
+ setTooltipPosition(updatedPosition);
258
+ }, [isOpen, position, tooltipDimensions, controlLayout, windowDimensions, arrowWidth, arrowOffset, tooltipPosition]);
259
+ const control = children !== undefined ? children : defaultControl;
260
+ const pressableStyles = control === defaultControl ? Platform.select({
261
+ web: {
262
+ outline: 'none'
263
+ }
264
+ }) : undefined;
265
+ const pressableHitSlop = control === defaultControl ? {
266
+ top: 10,
267
+ bottom: 10,
268
+ left: 10,
269
+ right: 10
270
+ } : undefined;
271
+ return /*#__PURE__*/_jsxs(View, {
272
+ style: staticStyles.container,
273
+ ...selectProps(rest),
274
+ children: [/*#__PURE__*/_jsx(Pressable, {
275
+ onPress: toggleIsOpen,
276
+ ref: controlRef,
277
+ onBlur: close,
278
+ style: pressableStyles,
279
+ hitSlop: pressableHitSlop,
280
+ accessibilityLabel: getCopy('a11yText'),
281
+ accessibilityRole: "button",
282
+ children: typeof control === 'function' ? pressableState => control(getPressableState(pressableState), variant) : control
283
+ }), isOpen && /*#__PURE__*/_jsx(Backdrop, {
284
+ onPress: close,
285
+ children: /*#__PURE__*/_jsxs(View, {
286
+ ref: ref,
287
+ style: [staticStyles.tooltip, selectTooltipShadowStyles(themeTokens), // applied separately so that it doesn't cover the arrow
288
+ tooltipPosition && selectTooltipPositionStyles(tooltipPosition), (tooltipPosition === null || (tooltipPosition === null || tooltipPosition === void 0 ? void 0 : tooltipPosition.isNormalized)) && staticStyles.tooltipHidden // visually hide the tooltip until we have a final measurement
289
+ ],
290
+ onLayout: onTooltipLayout,
291
+ accessibilityRole: "alert",
292
+ children: [/*#__PURE__*/_jsx(View, {
293
+ style: [staticStyles.arrow, tooltipPosition && selectArrowStyles(themeTokens, tooltipPosition)]
294
+ }), /*#__PURE__*/_jsx(View, {
295
+ style: selectTooltipStyles(themeTokens),
296
+ children: /*#__PURE__*/_jsx(Text, {
297
+ style: selectTextStyles(themeTokens),
298
+ children: content
299
+ })
300
+ })]
301
+ })
302
+ })]
303
+ });
304
+ });
305
+ Tooltip.displayName = 'NativeTooltip';
306
+ Tooltip.propTypes = { ...selectedSystemPropTypes,
307
+ ...propTypes
308
+ };
309
+ export default Tooltip;
310
+ const staticStyles = StyleSheet.create({
311
+ container: {
312
+ alignItems: 'flex-start'
313
+ },
314
+ tooltip: {
315
+ position: 'absolute',
316
+ maxWidth: 240,
317
+ top: 0,
318
+ left: 0
319
+ },
320
+ tooltipHidden: {
321
+ opacity: 0
322
+ },
323
+ arrow: {
324
+ position: 'absolute'
325
+ }
326
+ });
@@ -1,2 +1,5 @@
1
- import Tooltip from './Tooltip';
1
+ import Dimensions from "react-native-web/dist/exports/Dimensions";
2
+ import NonNativeTooltip from './Tooltip';
3
+ import NativeToolTip from './Tooltip.native';
4
+ const Tooltip = !Dimensions.get('screen').width <= 340 ? NativeToolTip : NonNativeTooltip;
2
5
  export default Tooltip;
@@ -0,0 +1,27 @@
1
+ import PropTypes from 'prop-types';
2
+ import { getTokensPropType, variantProp } from '../utils';
3
+ const propTypes = {
4
+ /**
5
+ * Used to render the control (i.e. tooltip trigger). If a render function is used it will receive the
6
+ * pressable state and tooltip variant as an argument.
7
+ */
8
+ children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
9
+
10
+ /**
11
+ * The message. Can be raw text or text components.
12
+ */
13
+ content: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
14
+
15
+ /**
16
+ * Select English or French copy for the accessible label.
17
+ */
18
+ copy: PropTypes.oneOf(['en', 'fr']),
19
+
20
+ /**
21
+ * Use to place the tooltip in a specific location (only if it fits within viewport).
22
+ */
23
+ position: PropTypes.oneOf(['auto', 'above', 'right', 'below', 'left']),
24
+ tokens: getTokensPropType('Tooltip'),
25
+ variant: variantProp.propType
26
+ };
27
+ export default propTypes;
@@ -66,20 +66,46 @@ const Typography = /*#__PURE__*/forwardRef((_ref2, ref) => {
66
66
  dataSet,
67
67
  maxFontSizeMultiplier: getMaxFontMultiplier(themeTokens)
68
68
  };
69
- const containerProps = { ...getA11yPropsFromHtmlTag(tag, accessibilityRole),
69
+ const containerProps = {
70
+ accessibilityRole,
71
+ ...getA11yPropsFromHtmlTag(tag, accessibilityRole),
70
72
  ...selectContainerProps(rest)
71
73
  };
74
+
75
+ const resetTagStyling = child => {
76
+ if (typeof child === 'object' && ((child === null || child === void 0 ? void 0 : child.type) === 'sub' || (child === null || child === void 0 ? void 0 : child.type) === 'sup')) {
77
+ var _child$props;
78
+
79
+ const sanitizedChild = /*#__PURE__*/React.cloneElement(child, {
80
+ style: { ...(child === null || child === void 0 ? void 0 : (_child$props = child.props) === null || _child$props === void 0 ? void 0 : _child$props.style),
81
+ lineHeight: 0
82
+ }
83
+ });
84
+ return sanitizedChild;
85
+ }
86
+
87
+ return child;
88
+ };
89
+
90
+ const sanitizeChildren = () => {
91
+ if (Array.isArray(children)) {
92
+ return children.map(resetTagStyling);
93
+ }
94
+
95
+ return resetTagStyling(children);
96
+ };
97
+
72
98
  return block ? /*#__PURE__*/_jsx(View, {
73
99
  ref: ref,
74
100
  ...containerProps,
75
101
  children: /*#__PURE__*/_jsx(Text, { ...resolvedTextProps,
76
- children: children
102
+ children: sanitizeChildren(children)
77
103
  })
78
104
  }) : /*#__PURE__*/_jsx(Text, {
79
105
  ref: ref,
80
106
  ...containerProps,
81
107
  ...resolvedTextProps,
82
- children: children
108
+ children: sanitizeChildren(children)
83
109
  });
84
110
  });
85
111
  Typography.displayName = 'Typography';
@@ -0,0 +1 @@
1
+ export { useFloating, arrow, offset, shift, flip, autoPlacement } from '@floating-ui/react-dom';
@@ -0,0 +1 @@
1
+ export { useFloating, arrow, offset, shift, flip, autoPlacement } from '@floating-ui/react-native';
package/package.json CHANGED
@@ -7,6 +7,8 @@
7
7
  "url": "https://github.com/telus/universal-design-system/issues"
8
8
  },
9
9
  "dependencies": {
10
+ "@floating-ui/react-dom": "^1.0.1",
11
+ "@floating-ui/react-native": "^0.8.1",
10
12
  "@gorhom/portal": "^1.0.14",
11
13
  "@telus-uds/system-constants": "^1.2.0",
12
14
  "@telus-uds/system-theme-tokens": "^2.9.0",
@@ -64,11 +66,11 @@
64
66
  "build:main": "babel src -d lib",
65
67
  "build:module": "babel src -d lib-module --env-name module",
66
68
  "build:code": "npm run build:main && npm run build:module",
67
- "build:docs": "babel-node --plugins=@nearform/babel-plugin-react-docgen generate-component-docs.js"
69
+ "build:docs": "babel-node --plugins=@telus-uds/babel-plugin-react-docgen generate-component-docs.js"
68
70
  },
69
71
  "sideEffects": false,
70
72
  "standard-engine": {
71
73
  "skip": true
72
74
  },
73
- "version": "1.24.1"
75
+ "version": "1.25.0"
74
76
  }
package/src/Box/Box.jsx CHANGED
@@ -137,6 +137,7 @@ const Box = forwardRef(
137
137
  ref
138
138
  ) => {
139
139
  const props = {
140
+ accessibilityRole,
140
141
  ...getA11yPropsFromHtmlTag(tag, accessibilityRole),
141
142
  ...selectProps(rest)
142
143
  }
@@ -72,7 +72,10 @@ const Divider = forwardRef(({ variant, vertical = false, space, tokens, ...rest
72
72
  Platform.OS === 'web'
73
73
  ? // There are aria attributes for dividers on web
74
74
  {
75
+ // Early versions of React Native Web support 'aria-orientation' while
76
+ // later versions only support accessibilityOrientation and map it. Supply both.
75
77
  'aria-orientation': vertical ? 'vertical' : 'horizontal',
78
+ accessibilityOrientation: vertical ? 'vertical' : 'horizontal',
76
79
  accessibilityRole: 'separator'
77
80
  }
78
81
  : // There are no such equivalent attributes for native
@@ -70,6 +70,7 @@ const FlexGrid = forwardRef(
70
70
  }
71
71
 
72
72
  const props = {
73
+ accessibilityRole,
73
74
  ...getA11yPropsFromHtmlTag(tag, accessibilityRole),
74
75
  ...selectProps(rest)
75
76
  }
@@ -9,16 +9,18 @@ import { Pressable, StyleSheet } from 'react-native'
9
9
  * InlinePressable is an alternative to React Native's Pressable that works better when nested
10
10
  * inline inside Text. It accepts the same props as React Native's Pressable.
11
11
  *
12
+ * On Web it simply passes its props to Pressable and defaults to `inline-flex` instead of `flex`.
13
+ *
12
14
  * @param {PressableProps} PressableProps
13
15
  */
14
16
  // React Native exports prop Types but not propTypes, use JSDoc types here rather than duplicate RN
15
17
  // eslint-disable-next-line react/prop-types
16
- const InlinePressable = forwardRef(({ children, style, ...props }, ref) => (
18
+ const InlinePressable = forwardRef(({ children, inlineFlex = true, style, ...props }, ref) => (
17
19
  <Pressable
18
20
  ref={ref}
19
21
  style={(pressState) => [
20
- staticStyles.inline,
21
- typeof style === 'function' ? style(pressState) : style
22
+ typeof style === 'function' ? style(pressState) : style,
23
+ staticStyles[inlineFlex ? 'inlineFlex' : 'inline']
22
24
  ]}
23
25
  {...props}
24
26
  >
@@ -29,7 +31,11 @@ InlinePressable.displayName = 'InlinePressable'
29
31
 
30
32
  const staticStyles = StyleSheet.create({
31
33
  inline: {
34
+ // Stop Pressable defaulting to (block) flex
32
35
  display: 'inline'
36
+ },
37
+ inlineFlex: {
38
+ display: 'inline-flex'
33
39
  }
34
40
  })
35
41
 
@@ -20,10 +20,13 @@ import { IconText, iconComponentPropTypes } from '../Icon'
20
20
 
21
21
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, linkProps, viewProps])
22
22
 
23
- const selectOuterBorderStyles = (
24
- { outerBorderColor, outerBorderWidth, outerBorderGap, borderRadius, outerBorderOutline },
25
- hasIcon
26
- ) =>
23
+ const selectOuterBorderStyles = ({
24
+ outerBorderColor,
25
+ outerBorderWidth,
26
+ outerBorderGap,
27
+ borderRadius,
28
+ outerBorderOutline
29
+ }) =>
27
30
  // A view wrapper with a border on native messes up inline text alignment
28
31
  // so for now make focus styles strictly web-only
29
32
  Platform.OS === 'web'
@@ -35,10 +38,7 @@ const selectOuterBorderStyles = (
35
38
  outerBorderWidth,
36
39
  outerBorderGap,
37
40
  borderRadius
38
- }),
39
- // Stops focus ring stretching horizontally if parent has display: block
40
- // width: fit-content isn't supported on Firefox; can't cascade props like CSS `width: fit-content; width: --moz-fit-content;`
41
- display: hasIcon ? 'inline-flex' : 'inline' // Stop Pressable defaulting to (block) flex
41
+ })
42
42
  }
43
43
  : {}
44
44
 
@@ -144,12 +144,19 @@ const LinkBase = forwardRef(
144
144
  return (
145
145
  <InlinePressable
146
146
  {...selectedProps}
147
+ inlineFlex={hasIcon} // assuming links without icons should be inline (even if they are long)
147
148
  ref={ref}
148
149
  style={(linkState) => {
149
150
  const themeTokens = resolveLinkTokens(linkState)
150
- const outerBorderStyles = selectOuterBorderStyles(themeTokens, hasIcon)
151
+ const outerBorderStyles = selectOuterBorderStyles(themeTokens)
151
152
  const decorationStyles = selectDecorationStyles(themeTokens)
152
- return [outerBorderStyles, blockLeftStyle, decorationStyles, staticStyles.rowContainer]
153
+
154
+ return [
155
+ outerBorderStyles,
156
+ blockLeftStyle,
157
+ decorationStyles,
158
+ hasIcon && staticStyles.rowContainer
159
+ ]
153
160
  }}
154
161
  >
155
162
  {(linkState) => {
@@ -82,6 +82,7 @@ const StackView = forwardRef(
82
82
  const viewport = useViewport()
83
83
  const direction = useResponsiveProp(directionProp, 'column')
84
84
  const selectedProps = selectProps({
85
+ accessibilityRole,
85
86
  ...getA11yPropsFromHtmlTag(tag, accessibilityRole),
86
87
  ...rest
87
88
  })
@@ -67,6 +67,7 @@ const StackWrapBox = forwardRef(
67
67
  const themeTokens = useThemeTokens('StackView', tokens, variant, { viewport })
68
68
  const flexStyles = selectFlexStyles(themeTokens)
69
69
  const selectedProps = selectProps({
70
+ accessibilityRole,
70
71
  ...getA11yPropsFromHtmlTag(tag, accessibilityRole),
71
72
  ...rest
72
73
  })
@@ -46,6 +46,7 @@ const StackWrapGap = forwardRef(
46
46
  const themeTokens = useThemeTokens('StackView', tokens, variant, { viewport })
47
47
  const flexStyles = selectFlexStyles(themeTokens)
48
48
  const selectedProps = selectProps({
49
+ accessibilityRole,
49
50
  ...getA11yPropsFromHtmlTag(tag, accessibilityRole),
50
51
  ...rest
51
52
  })
@@ -45,9 +45,7 @@ export function applyTextStyles({
45
45
  // Don't set undefined font families. May need some validation here that the font is available.
46
46
  // Android doesn't recognise font weights natively so apply custom font weights via `fontFamily`.
47
47
  styles.fontFamily = `${fontName}${fontWeight}${fontStyle}`
48
- }
49
-
50
- if (fontWeight) {
48
+ } else if (fontWeight) {
51
49
  // If using system default font, apply the font weight directly.
52
50
  // Font weight support in Android is limited to 'bold' or anything else === 'normal'.
53
51
  styles.fontWeight = Platform.OS === 'android' && Number(fontWeight) > 400 ? 'bold' : fontWeight