@telus-uds/components-base 2.3.0 → 2.5.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 (102) hide show
  1. package/CHANGELOG.md +34 -2
  2. package/lib/A11yInfoProvider/index.js +2 -2
  3. package/lib/Autocomplete/Autocomplete.js +22 -32
  4. package/lib/Autocomplete/Suggestions.js +1 -1
  5. package/lib/BaseProvider/HydrationContext.js +1 -2
  6. package/lib/BaseProvider/index.js +1 -2
  7. package/lib/Button/ButtonDropdown.js +1 -1
  8. package/lib/Card/Card.js +12 -13
  9. package/lib/Card/CardBase.js +1 -1
  10. package/lib/Card/PressableCardBase.js +1 -1
  11. package/lib/CardGroup/CardGroup.js +3 -3
  12. package/lib/Carousel/Carousel.js +5 -6
  13. package/lib/Carousel/CarouselStepTracker/CarouselStepTracker.js +3 -0
  14. package/lib/Carousel/CarouselTabs/CarouselTabs.js +1 -2
  15. package/lib/Carousel/CarouselTabs/CarouselTabsPanel.js +1 -1
  16. package/lib/Carousel/CarouselTabs/CarouselTabsPanelItem.js +1 -1
  17. package/lib/Carousel/CarouselThumbnail.js +1 -1
  18. package/lib/Checkbox/Checkbox.js +1 -1
  19. package/lib/ColourToggle/ColourToggle.js +1 -1
  20. package/lib/ExpandCollapseMini/ExpandCollapseMini.js +77 -0
  21. package/lib/ExpandCollapseMini/ExpandCollapseMiniControl.js +126 -0
  22. package/lib/ExpandCollapseMini/index.js +2 -0
  23. package/lib/Footnote/Footnote.js +4 -4
  24. package/lib/HorizontalScroll/HorizontalScroll.js +1 -2
  25. package/lib/Icon/Icon.js +1 -1
  26. package/lib/Icon/IconText.js +2 -3
  27. package/lib/IconButton/IconButton.js +1 -2
  28. package/lib/InputLabel/InputLabel.js +36 -2
  29. package/lib/InputSupports/InputSupports.js +31 -8
  30. package/lib/InputSupports/dictionary.js +12 -0
  31. package/lib/InputSupports/useInputSupports.js +12 -3
  32. package/lib/Link/LinkBase.js +25 -18
  33. package/lib/List/List.js +1 -2
  34. package/lib/List/ListItemContent.js +1 -1
  35. package/lib/Listbox/Listbox.js +5 -8
  36. package/lib/Listbox/PressableItem.js +4 -4
  37. package/lib/Modal/Modal.js +4 -7
  38. package/lib/MultiSelectFilter/MultiSelectFilter.js +55 -42
  39. package/lib/Notification/Notification.js +15 -13
  40. package/lib/OrderedList/OrderedList.js +2 -3
  41. package/lib/Pagination/usePagination.js +1 -2
  42. package/lib/PriceLockup/utils/renderFootnoteContent.js +2 -2
  43. package/lib/PriceLockup/utils/renderFootnoteLinks.js +2 -2
  44. package/lib/PriceLockup/utils/renderPrice.js +2 -2
  45. package/lib/ProductCard/ProductCard.js +2 -3
  46. package/lib/Progress/ProgressBarBackground.js +2 -2
  47. package/lib/QuickLinksFeature/QuickLinksFeature.js +1 -2
  48. package/lib/Radio/Radio.js +1 -1
  49. package/lib/Search/Search.js +41 -11
  50. package/lib/Select/Picker.js +2 -2
  51. package/lib/Select/Picker.native.js +8 -4
  52. package/lib/Select/constants.js +4 -2
  53. package/lib/StackView/StackWrap.js +1 -4
  54. package/lib/StackView/getStackedContent.js +1 -2
  55. package/lib/StepTracker/StepTracker.js +1 -2
  56. package/lib/TabBar/TabBar.js +1 -1
  57. package/lib/Tabs/Tabs.js +1 -1
  58. package/lib/Tabs/TabsItem.js +2 -2
  59. package/lib/TextInput/TextArea.js +7 -6
  60. package/lib/TextInput/TextInput.js +7 -6
  61. package/lib/TextInput/TextInputBase.js +57 -25
  62. package/lib/ThemeProvider/utils/theme-tokens.js +2 -4
  63. package/lib/Timeline/Timeline.js +1 -2
  64. package/lib/Tooltip/Tooltip.native.js +4 -4
  65. package/lib/Typography/Typography.js +4 -5
  66. package/lib/Validator/Validator.js +9 -14
  67. package/lib/ViewportProvider/useViewportListener.js +1 -1
  68. package/lib/index.js +1 -0
  69. package/lib/utils/children.js +2 -6
  70. package/lib/utils/input.js +1 -1
  71. package/lib/utils/props/componentPropType.js +1 -2
  72. package/lib/utils/props/inputSupportsProps.js +15 -3
  73. package/lib/utils/props/selectSystemProps.js +2 -2
  74. package/lib/utils/ssr-media-query/create-stylesheet/create-stylesheet-mobile.js +1 -1
  75. package/lib/utils/ssr-media-query/create-stylesheet/index.js +2 -3
  76. package/lib/utils/ssr-media-query/utils/inject.js +3 -5
  77. package/lib/utils/useHash.js +1 -4
  78. package/lib/utils/useOverlaidPosition.js +25 -4
  79. package/lib/utils/useScrollBlocking.js +2 -4
  80. package/lib/utils/useSpacingScale.js +2 -2
  81. package/package.json +2 -2
  82. package/src/Carousel/CarouselStepTracker/CarouselStepTracker.jsx +3 -0
  83. package/src/ExpandCollapseMini/ExpandCollapseMini.jsx +76 -0
  84. package/src/ExpandCollapseMini/ExpandCollapseMiniControl.jsx +119 -0
  85. package/src/ExpandCollapseMini/index.js +3 -0
  86. package/src/InputLabel/InputLabel.jsx +39 -2
  87. package/src/InputSupports/InputSupports.jsx +33 -7
  88. package/src/InputSupports/dictionary.js +12 -0
  89. package/src/InputSupports/useInputSupports.js +24 -3
  90. package/src/Link/LinkBase.jsx +25 -18
  91. package/src/Modal/Modal.jsx +1 -1
  92. package/src/MultiSelectFilter/MultiSelectFilter.jsx +55 -27
  93. package/src/Notification/Notification.jsx +9 -3
  94. package/src/Search/Search.jsx +52 -24
  95. package/src/Select/Picker.native.jsx +10 -4
  96. package/src/Select/constants.js +4 -1
  97. package/src/TextInput/TextArea.jsx +12 -5
  98. package/src/TextInput/TextInput.jsx +13 -6
  99. package/src/TextInput/TextInputBase.jsx +57 -10
  100. package/src/index.js +1 -0
  101. package/src/utils/props/inputSupportsProps.js +15 -3
  102. package/src/utils/useOverlaidPosition.js +23 -0
@@ -0,0 +1,126 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import Platform from "react-native-web/dist/exports/Platform";
4
+ import { Link } from '../Link';
5
+ import { useThemeTokens } from '../ThemeProvider';
6
+ import { htmlAttrs, viewProps, selectSystemProps } from '../utils';
7
+ import { jsx as _jsx } from "react/jsx-runtime";
8
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs, viewProps]);
9
+
10
+ // The ExpandCollapseControl has all the appropriate role, a11y, press handling etc
11
+ // and a more appropriate press area, defer interaction handling to it.
12
+ const presentationOnly = {
13
+ accessibilityRole: null,
14
+ // Treat as regular flow content with the Control
15
+ pointerEvents: 'none',
16
+ // Stop RNW from stopping clicks from bubbling to Control
17
+ focusable: false // Stop RNW from setting tabIndex={0}: focus goes to Control only
18
+ };
19
+ const selectLinkTokens = _ref => {
20
+ let {
21
+ color,
22
+ textLine,
23
+ lineHeight,
24
+ fontSize
25
+ } = _ref;
26
+ return {
27
+ color,
28
+ textLine,
29
+ blockLineHeight: lineHeight,
30
+ blockFontSize: fontSize
31
+ };
32
+ };
33
+ const ExpandCollapseMiniControl = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
34
+ let {
35
+ pressableState,
36
+ collapseTitle,
37
+ expandTitle = collapseTitle,
38
+ iconPosition = 'right',
39
+ tokens,
40
+ variant = {},
41
+ ...rest
42
+ } = _ref2;
43
+ const {
44
+ expanded,
45
+ hover,
46
+ focus
47
+ } = pressableState || {};
48
+ // we only want focus outline when focusing, if user is pressing we don't want the border.
49
+ const {
50
+ outerBorderColor
51
+ } = useThemeTokens('Link', {}, {}, {
52
+ focus: Platform.OS !== 'web' ? expanded : focus
53
+ });
54
+ const {
55
+ size,
56
+ icon,
57
+ ...themeTokens
58
+ } = useThemeTokens('ExpandCollapseMiniControl', tokens, variant, {
59
+ expanded,
60
+ focus
61
+ });
62
+
63
+ // Choose hover styles when any part of Control is hoverred
64
+ const appearance = {
65
+ ...variant,
66
+ hover
67
+ };
68
+ const getTokens = linkState => {
69
+ const {
70
+ hover: linkHover
71
+ } = linkState || {};
72
+ const isHovered = hover || linkHover;
73
+ if (Platform.OS !== 'web') {
74
+ return {
75
+ iconTranslateY: -1
76
+ };
77
+ }
78
+ if (isHovered) {
79
+ // Include vertical icon animation on hover alongside built-in Link theme, the size is size4
80
+ return {
81
+ iconTranslateY: (expanded ? -1 : 1) * size
82
+ };
83
+ }
84
+ return {};
85
+ };
86
+ return /*#__PURE__*/_jsx(Link, {
87
+ variant: appearance,
88
+ icon: icon,
89
+ iconPosition: iconPosition,
90
+ tokens: linkState => ({
91
+ ...getTokens(linkState),
92
+ ...selectLinkTokens(themeTokens),
93
+ outerBorderColor
94
+ }),
95
+ ref: ref,
96
+ ...presentationOnly,
97
+ ...selectProps(rest),
98
+ children: expanded ? expandTitle : collapseTitle
99
+ });
100
+ });
101
+ ExpandCollapseMiniControl.displayName = 'ExpandCollapseMiniControl';
102
+ ExpandCollapseMiniControl.propTypes = {
103
+ ...selectedSystemPropTypes,
104
+ ...Link.propTypes,
105
+ /**
106
+ * Optional function to call on pressing the panel's control, in addition to opening or closing the panel
107
+ */
108
+ onPress: PropTypes.func,
109
+ /**
110
+ * ExpandCollapseMiniControl title when expanded
111
+ */
112
+ expandTitle: PropTypes.string.isRequired,
113
+ /**
114
+ * ExpandCollapseMiniControl title when collapsed
115
+ */
116
+ collapseTitle: PropTypes.string.isRequired,
117
+ /**
118
+ * React Native's `Pressable`'s state object
119
+ */
120
+ pressableState: PropTypes.object,
121
+ /**
122
+ * Optional variant object to override the default theme tokens
123
+ */
124
+ variant: PropTypes.object
125
+ };
126
+ export default ExpandCollapseMiniControl;
@@ -0,0 +1,2 @@
1
+ import ExpandCollapseMini from './ExpandCollapseMini';
2
+ export default ExpandCollapseMini;
@@ -206,8 +206,8 @@ const Footnote = /*#__PURE__*/React.forwardRef((_ref6, ref) => {
206
206
  style: [selectFootnoteHeaderContentStyle(themeTokens), staticStyles.headerContent],
207
207
  children: [/*#__PURE__*/_jsx(Typography, {
208
208
  tokens: {
209
- fontSize: themeTokens === null || themeTokens === void 0 ? void 0 : themeTokens.headerFontSize,
210
- lineHeight: themeTokens === null || themeTokens === void 0 ? void 0 : themeTokens.headerLineHeight
209
+ fontSize: themeTokens?.headerFontSize,
210
+ lineHeight: themeTokens?.headerLineHeight
211
211
  },
212
212
  variant: {
213
213
  size: 'h4'
@@ -219,9 +219,9 @@ const Footnote = /*#__PURE__*/React.forwardRef((_ref6, ref) => {
219
219
  children: /*#__PURE__*/_jsx(View, {
220
220
  style: [selectFootnoteCloseButtonStyle(themeTokens), staticStyles.closeButton],
221
221
  children: /*#__PURE__*/_jsx(Icon, {
222
- icon: themeTokens === null || themeTokens === void 0 ? void 0 : themeTokens.closeIcon,
222
+ icon: themeTokens?.closeIcon,
223
223
  tokens: {
224
- size: themeTokens === null || themeTokens === void 0 ? void 0 : themeTokens.closeButtonIconSize
224
+ size: themeTokens?.closeButtonIconSize
225
225
  }
226
226
  })
227
227
  })
@@ -74,8 +74,7 @@ const HorizontalScroll = /*#__PURE__*/React.forwardRef((_ref, ref) => {
74
74
  const showPreviousButton = scrollOffset > 0;
75
75
  const scrollRef = React.useRef(null);
76
76
  const scrollTo = targetX => {
77
- var _scrollRef$current;
78
- if (typeof ((_scrollRef$current = scrollRef.current) === null || _scrollRef$current === void 0 ? void 0 : _scrollRef$current.scrollTo) === 'function') {
77
+ if (typeof scrollRef.current?.scrollTo === 'function') {
79
78
  const x = getItemPositionScrollTarget(targetX, scrollMax, itemPositions.positions, buttonClearance);
80
79
  scrollRef.current.scrollTo({
81
80
  x,
package/lib/Icon/Icon.js CHANGED
@@ -22,7 +22,7 @@ const Icon = /*#__PURE__*/React.forwardRef((_ref, ref) => {
22
22
  size: size,
23
23
  color: themeTokens.color
24
24
  });
25
- const paddingStyles = variant !== null && variant !== void 0 && variant.padding ? {
25
+ const paddingStyles = variant?.padding ? {
26
26
  padding: themeTokens.width,
27
27
  width: themeTokens.size + themeTokens.width * 2,
28
28
  // sets the diameter of the circle which is the size of the icon plus twice the general padding established to obtain a perfect circle
@@ -18,7 +18,6 @@ import { spacingProps } from '../utils';
18
18
  */
19
19
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
20
20
  const IconText = /*#__PURE__*/React.forwardRef((_ref, ref) => {
21
- var _iconProps$tokens, _iconProps$tokens2;
22
21
  let {
23
22
  space,
24
23
  iconPosition = 'left',
@@ -35,8 +34,8 @@ const IconText = /*#__PURE__*/React.forwardRef((_ref, ref) => {
35
34
 
36
35
  // Inline images on Android are always baseline-aligned which makes them look misaligned - offset it.
37
36
  // See abandoned issue https://github.com/facebook/react-native/issues/6529
38
- const size = (iconProps === null || iconProps === void 0 || (_iconProps$tokens = iconProps.tokens) === null || _iconProps$tokens === void 0 ? void 0 : _iconProps$tokens.size) ?? 0;
39
- const valueTranslateY = iconProps === null || iconProps === void 0 || (_iconProps$tokens2 = iconProps.tokens) === null || _iconProps$tokens2 === void 0 ? void 0 : _iconProps$tokens2.translateY;
37
+ const size = iconProps?.tokens?.size ?? 0;
38
+ const valueTranslateY = iconProps?.tokens?.translateY;
40
39
  /**
41
40
  * These calculations were carried out using a set of linear equations to calculate that the
42
41
  * position of the icon "->"" is aligned to the first line of the tooltip text on IOS and Android.
@@ -134,13 +134,12 @@ const IconButton = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
134
134
  accessibilityRole
135
135
  });
136
136
  const handlePress = () => {
137
- var _ref$current;
138
137
  linkProps.handleHref({
139
138
  href,
140
139
  onPress
141
140
  })({
142
141
  nativeEvent: {
143
- target: ref === null || ref === void 0 || (_ref$current = ref.current) === null || _ref$current === void 0 ? void 0 : _ref$current.id
142
+ target: ref?.current?.id
144
143
  }
145
144
  });
146
145
  };
@@ -49,6 +49,8 @@ const InputLabel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
49
49
  tooltip,
50
50
  tokens,
51
51
  variant,
52
+ characterCount,
53
+ maxCharacterAllowed,
52
54
  ...rest
53
55
  } = _ref3;
54
56
  const themeTokens = useThemeTokens('InputLabel', tokens, variant);
@@ -81,11 +83,26 @@ const InputLabel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
81
83
  content: tooltip,
82
84
  copy: copy
83
85
  })
86
+ }), maxCharacterAllowed && isHintInline && /*#__PURE__*/_jsxs(Text, {
87
+ style: [selectHintStyles({
88
+ ...themeTokens
89
+ }), staticStyles.characterCountlabel],
90
+ children: [characterCount, "/", maxCharacterAllowed]
84
91
  })]
85
- }), hint && !isHintInline && /*#__PURE__*/_jsx(Text, {
92
+ }), hint && !maxCharacterAllowed && !isHintInline && /*#__PURE__*/_jsx(Text, {
86
93
  style: [selectHintStyles(themeTokens), staticStyles.hintBelow],
87
94
  nativeID: hintId,
88
95
  children: hint
96
+ }), hint && maxCharacterAllowed && !isHintInline && /*#__PURE__*/_jsxs(View, {
97
+ style: staticStyles.container,
98
+ children: [/*#__PURE__*/_jsx(Text, {
99
+ style: [selectHintStyles(themeTokens), staticStyles.flexHintBelow],
100
+ nativeID: hintId,
101
+ children: hint
102
+ }), /*#__PURE__*/_jsxs(Text, {
103
+ style: [selectHintStyles(themeTokens), staticStyles.characterCountlabel],
104
+ children: [characterCount, "/", maxCharacterAllowed]
105
+ })]
89
106
  })]
90
107
  });
91
108
  });
@@ -120,6 +137,14 @@ InputLabel.propTypes = {
120
137
  * Content of an optional `Tooltip`. If set, a tooltip button will be shown next to the label.
121
138
  */
122
139
  tooltip: PropTypes.string,
140
+ /**
141
+ * Current number of characterts of an input text.
142
+ */
143
+ characterCount: PropTypes.number,
144
+ /**
145
+ * Max number of characters that allows an input text.
146
+ */
147
+ maxCharacterAllowed: PropTypes.number,
123
148
  tokens: getTokensPropType('InputLabel'),
124
149
  variant: variantProp.propType
125
150
  };
@@ -129,13 +154,22 @@ const staticStyles = StyleSheet.create({
129
154
  flexShrink: 1,
130
155
  flexDirection: 'row'
131
156
  },
157
+ characterCountlabel: {
158
+ marginLeft: 'auto',
159
+ marginTop: 'auto'
160
+ },
132
161
  label: {
133
- flexShrink: 1
162
+ flexShrink: 1,
163
+ alignSelf: 'center'
134
164
  },
135
165
  hintBelow: {
136
166
  flexBasis: '100%',
137
167
  flexShrink: 0
138
168
  },
169
+ flexHintBelow: {
170
+ flexBasis: '100%',
171
+ flexShrink: 1
172
+ },
139
173
  tooltipAlign: {
140
174
  alignSelf: 'center',
141
175
  justifyContent: 'center'
@@ -5,7 +5,8 @@ import Feedback from '../Feedback';
5
5
  import StackView from '../StackView';
6
6
  import { useThemeTokens } from '../ThemeProvider';
7
7
  import useInputSupports from './useInputSupports';
8
- import { getTokensPropType } from '../utils';
8
+ import { getTokensPropType, useCopy } from '../utils';
9
+ import dictionary from './dictionary';
9
10
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
11
  const InputSupports = /*#__PURE__*/React.forwardRef((_ref, ref) => {
11
12
  let {
@@ -19,11 +20,19 @@ const InputSupports = /*#__PURE__*/React.forwardRef((_ref, ref) => {
19
20
  feedbackProps = {},
20
21
  tooltip,
21
22
  validation,
23
+ maxCharacterAllowed,
24
+ inputValue,
22
25
  nativeID
23
26
  } = _ref;
24
27
  const {
25
28
  space
26
29
  } = useThemeTokens('InputSupports');
30
+ const getCopy = useCopy({
31
+ dictionary,
32
+ copy
33
+ });
34
+ const maxCharsReachedErrorMessage = inputValue?.length > maxCharacterAllowed ? getCopy('maxCharsMessage').replace(/%\{charCount\}/g, maxCharacterAllowed) : '';
35
+ const feedbackValidation = inputValue?.length > maxCharacterAllowed ? 'error' : validation;
27
36
  const {
28
37
  inputId,
29
38
  hintId,
@@ -34,7 +43,10 @@ const InputSupports = /*#__PURE__*/React.forwardRef((_ref, ref) => {
34
43
  hint,
35
44
  label,
36
45
  validation,
37
- nativeID
46
+ nativeID,
47
+ copy,
48
+ maxCharacterAllowed,
49
+ charactersCount: maxCharacterAllowed - inputValue?.length
38
50
  });
39
51
  return /*#__PURE__*/_jsxs(StackView, {
40
52
  space: space,
@@ -46,14 +58,17 @@ const InputSupports = /*#__PURE__*/React.forwardRef((_ref, ref) => {
46
58
  hintPosition: hintPosition,
47
59
  hintId: hintId,
48
60
  tooltip: tooltip,
49
- forId: inputId
61
+ forId: inputId,
62
+ characterCount: inputValue?.length,
63
+ maxCharacterAllowed: maxCharacterAllowed
50
64
  }), typeof children === 'function' ? children({
51
65
  inputId,
52
- ...a11yProps
53
- }) : children, feedback ? /*#__PURE__*/_jsx(Feedback, {
66
+ ...a11yProps,
67
+ validation: feedbackValidation
68
+ }) : children, feedback || maxCharsReachedErrorMessage ? /*#__PURE__*/_jsx(Feedback, {
54
69
  id: feedbackId,
55
- title: feedback,
56
- validation: validation,
70
+ title: feedback || maxCharsReachedErrorMessage,
71
+ validation: feedbackValidation,
57
72
  tokens: feedbackTokens,
58
73
  variant: {
59
74
  icon: feedbackProps.showFeedbackIcon
@@ -105,6 +120,14 @@ InputSupports.propTypes = {
105
120
  /**
106
121
  * ID for DOM element on web
107
122
  */
108
- nativeID: PropTypes.string
123
+ nativeID: PropTypes.string,
124
+ /**
125
+ * The text value of a TextArea or TextInput
126
+ */
127
+ inputValue: PropTypes.string,
128
+ /**
129
+ * Max number of characters that allows an input text.
130
+ */
131
+ maxCharacterAllowed: PropTypes.number
109
132
  };
110
133
  export default InputSupports;
@@ -0,0 +1,12 @@
1
+ export default {
2
+ en: {
3
+ maxCharacters: 'Maximum %{charCount} characters',
4
+ charactersRemaining: '%{charCount} characters remaining',
5
+ maxCharsMessage: 'Must not exceed %{charCount} characters'
6
+ },
7
+ fr: {
8
+ maxCharacters: '%{charCount} caractères maximum',
9
+ charactersRemaining: '%{charCount} caractères restants',
10
+ maxCharsMessage: 'Ne doit pas dépasser %{charCount} caractères'
11
+ }
12
+ };
@@ -1,4 +1,6 @@
1
1
  import useUniqueId from '../utils/useUniqueId';
2
+ import { useCopy } from '../utils';
3
+ import dictionary from './dictionary';
2
4
  const joinDefined = array => array.filter(item => item !== undefined).join(' ');
3
5
  const useInputSupports = _ref => {
4
6
  let {
@@ -6,19 +8,26 @@ const useInputSupports = _ref => {
6
8
  feedback,
7
9
  validation,
8
10
  hint,
9
- nativeID
11
+ nativeID,
12
+ copy,
13
+ maxCharacterAllowed,
14
+ charactersCount
10
15
  } = _ref;
16
+ const getCopy = useCopy({
17
+ dictionary,
18
+ copy
19
+ });
11
20
  const hasValidationError = validation === 'error';
12
21
  const inputId = useUniqueId('input');
13
22
  const hintId = useUniqueId('input-hint');
14
23
  const feedbackId = useUniqueId('input-feedback');
15
24
  const a11yProps = {
16
25
  accessibilityLabel: label,
17
- accessibilityHint: joinDefined([!hasValidationError && feedback, hint]),
26
+ accessibilityHint: joinDefined([!hasValidationError && feedback, hint, maxCharacterAllowed ? getCopy('maxCharacters').replace(/%\{charCount\}/g, maxCharacterAllowed) : undefined]),
18
27
  // native only -> replaced with describedBy on web
19
28
  accessibilityDescribedBy: joinDefined([!hasValidationError && feedback && feedbackId,
20
29
  // feedback receives a11yRole=alert on error, so there's no need to include it here
21
- hint && hintId]),
30
+ hint && hintId, charactersCount ? getCopy('charactersRemaining').replace(/%\{charCount\}/g, charactersCount) : undefined]),
22
31
  accessibilityInvalid: hasValidationError
23
32
  };
24
33
  return {
@@ -19,20 +19,15 @@ const selectOuterBorderStyles = _ref => {
19
19
  borderRadius,
20
20
  outerBorderOutline
21
21
  } = _ref;
22
- return (
23
- // A view wrapper with a border on native messes up inline text alignment
24
- // so for now make focus styles strictly web-only
25
- Platform.OS === 'web' ? {
26
- // Allow theme to define outline, or, turn off outline and use border if rounded corners required
27
- outline: outerBorderOutline,
28
- ...applyOuterBorder({
29
- outerBorderColor,
30
- outerBorderWidth,
31
- outerBorderGap
32
- }),
33
- borderRadius
34
- } : {}
35
- );
22
+ return {
23
+ outline: outerBorderOutline,
24
+ ...applyOuterBorder({
25
+ outerBorderColor,
26
+ outerBorderWidth,
27
+ outerBorderGap
28
+ }),
29
+ borderRadius
30
+ };
36
31
  };
37
32
  const selectTextStyles = _ref2 => {
38
33
  let {
@@ -92,7 +87,8 @@ const selectIconTokens = _ref5 => {
92
87
  color,
93
88
  iconSize,
94
89
  blockFontSize,
95
- iconTranslateX
90
+ iconTranslateX,
91
+ iconTranslateY
96
92
  } = _ref5;
97
93
  /**
98
94
  * These calculations were carried out using a set of linear equations to calculate that the
@@ -100,11 +96,11 @@ const selectIconTokens = _ref5 => {
100
96
  * The base equation is: X/4 + Y/4 - 4 - |X - Y| = Z
101
97
  * where X = blockFontSize, Y = iconSize and Z = translateY
102
98
  */
103
- const translateY = blockFontSize / 4 + iconSize / 4 - 4 - Math.abs(iconSize - blockFontSize);
99
+ const translateY = iconTranslateY ?? blockFontSize / 4 + iconSize / 4 - 4 - Math.abs(iconSize - blockFontSize);
104
100
  return {
105
101
  color,
106
102
  translateX: iconTranslateX,
107
- translateY: translateY < 0 ? 0 : translateY,
103
+ translateY,
108
104
  size: iconSize
109
105
  };
110
106
  };
@@ -182,7 +178,7 @@ const LinkBase = /*#__PURE__*/React.forwardRef((_ref6, ref) => {
182
178
  const themeTokens = resolveLinkTokens(linkState);
183
179
  const outerBorderStyles = selectOuterBorderStyles(themeTokens);
184
180
  const decorationStyles = selectDecorationStyles(themeTokens);
185
- return [outerBorderStyles, blockLeftStyle, decorationStyles, hasIcon && staticStyles.rowContainer];
181
+ return [outerBorderStyles, staticStyles.outerBorderStyles, blockLeftStyle, decorationStyles, hasIcon && staticStyles.rowContainer];
186
182
  },
187
183
  children: linkState => {
188
184
  const themeTokens = resolveLinkTokens(linkState);
@@ -264,6 +260,17 @@ const staticStyles = StyleSheet.create({
264
260
  pointerEvents: 'none'
265
261
  }
266
262
  })
263
+ },
264
+ outerBorderStyles: {
265
+ ...(Platform.OS !== 'web' && {
266
+ margin: 0,
267
+ marginHorizontal: 2,
268
+ padding: 0
269
+ }),
270
+ ...(Platform.OS === 'android' && {
271
+ paddingHorizontal: 2,
272
+ paddingTop: 2
273
+ })
267
274
  }
268
275
  });
269
276
  export default withLinkRouter(LinkBase);
package/lib/List/List.js CHANGED
@@ -7,8 +7,7 @@ import { a11yProps, getTokensPropType, selectSystemProps, variantProp, viewProps
7
7
  import { jsx as _jsx } from "react/jsx-runtime";
8
8
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
9
9
  const isListItem = element => {
10
- var _element$type, _element$type2;
11
- const elementName = (element === null || element === void 0 || (_element$type = element.type) === null || _element$type === void 0 ? void 0 : _element$type.displayName) || (element === null || element === void 0 || (_element$type2 = element.type) === null || _element$type2 === void 0 ? void 0 : _element$type2.name);
10
+ const elementName = element?.type?.displayName || element?.type?.name;
12
11
  // Match our own ListItem, and also, custom list items
13
12
  return Boolean(elementName.match(/Item/));
14
13
  };
@@ -49,7 +49,7 @@ const ListItemContent = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
49
49
  } = useTheme();
50
50
  const textStyles = selectItemTextStyles(tokens, themeOptions);
51
51
  return /*#__PURE__*/_jsx(View, {
52
- style: [staticStyles.wrap, (tokens === null || tokens === void 0 ? void 0 : tokens.itemUnderline) && {
52
+ style: [staticStyles.wrap, tokens?.itemUnderline && {
53
53
  textDecorationLine: tokens.itemUnderline,
54
54
  textDecorationColor: tokens.itemFontColor
55
55
  }],
@@ -42,36 +42,33 @@ const Listbox = /*#__PURE__*/React.forwardRef((_ref, ref) => {
42
42
  // We need to keep track of each item's ref in order to be able to
43
43
  // focus on a specific item via keyboard navigation
44
44
  const itemRefs = React.useRef([]);
45
- if (firstItemRef !== null && firstItemRef !== void 0 && firstItemRef.current) itemRefs.current[0] = firstItemRef.current;
45
+ if (firstItemRef?.current) itemRefs.current[0] = firstItemRef.current;
46
46
  const [focusedIndex, setFocusedIndex] = React.useState(0);
47
47
  const handleKeydown = React.useCallback(event => {
48
48
  const nextItemRef = itemRefs.current[focusedIndex + 1];
49
49
  const prevItemRef = itemRefs.current[focusedIndex - 1];
50
50
  if (event.key === 'ArrowUp' || event.shiftKey && event.key === 'Tab') {
51
- var _parentRef$current;
52
51
  // Move the focus to the previous item or to the parent one if on the first
53
52
  if (prevItemRef) {
54
53
  event.preventDefault();
55
54
  prevItemRef.focus();
56
- } else if (parentRef) (_parentRef$current = parentRef.current) === null || _parentRef$current === void 0 || _parentRef$current.focus();
55
+ } else if (parentRef) parentRef.current?.focus();
57
56
  setFocusedIndex(focusedIndex - 1);
58
57
  } else if ((event.key === 'ArrowDown' || event.key === 'Tab') && nextItemRef) {
59
58
  event.preventDefault();
60
59
  setFocusedIndex(focusedIndex + 1);
61
60
  nextItemRef.focus();
62
61
  } else if (event.key === 'Escape') {
63
- var _parentRef$current2, _parentRef$current3;
64
62
  // Close the dropdown
65
- parentRef === null || parentRef === void 0 || (_parentRef$current2 = parentRef.current) === null || _parentRef$current2 === void 0 || _parentRef$current2.click();
63
+ parentRef?.current?.click();
66
64
  // Return focus to the dropdown control after leaving the last item
67
- parentRef === null || parentRef === void 0 || (_parentRef$current3 = parentRef.current) === null || _parentRef$current3 === void 0 || _parentRef$current3.focus();
65
+ parentRef?.current?.focus();
68
66
  if (onClose) onClose(event);
69
67
  } else if (!nextItemRef && firstItemRef) {
70
- var _firstItemRef$current;
71
68
  // If the last item is focused, move the focus to the first one
72
69
  event.preventDefault();
73
70
  setFocusedIndex(0);
74
- (_firstItemRef$current = firstItemRef.current) === null || _firstItemRef$current === void 0 || _firstItemRef$current.focus();
71
+ firstItemRef.current?.focus();
75
72
  }
76
73
  }, [focusedIndex, onClose, parentRef, firstItemRef]);
77
74
 
@@ -90,11 +90,11 @@ const PressableItem = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
90
90
  return themeTokens;
91
91
  };
92
92
  const handleKeyPress = event => {
93
- if (['Enter', ' '].includes(event === null || event === void 0 ? void 0 : event.key)) {
94
- onPress === null || onPress === void 0 || onPress(event);
95
- } else if ((event === null || event === void 0 ? void 0 : event.key) === 'ArrowDown') {
93
+ if (['Enter', ' '].includes(event?.key)) {
94
+ onPress?.(event);
95
+ } else if (event?.key === 'ArrowDown') {
96
96
  nextItemRef.current.focus();
97
- } else if ((event === null || event === void 0 ? void 0 : event.key) === 'ArrowUp' && prevItemRef !== null && prevItemRef !== void 0 && prevItemRef.current) {
97
+ } else if (event?.key === 'ArrowUp' && prevItemRef?.current) {
98
98
  prevItemRef.current.focus();
99
99
  }
100
100
  };
@@ -100,7 +100,6 @@ const selectScrollViewStyles = () => ({
100
100
  * - Don’t use modals consecutively
101
101
  */
102
102
  const Modal = /*#__PURE__*/React.forwardRef((_ref5, ref) => {
103
- var _modalContentRef$curr2;
104
103
  let {
105
104
  children,
106
105
  isOpen,
@@ -147,12 +146,11 @@ const Modal = /*#__PURE__*/React.forwardRef((_ref5, ref) => {
147
146
  };
148
147
  const manageFocus = React.useCallback(event => {
149
148
  if (event.key === 'Tab') {
150
- var _modalBodyRef$current;
151
- const focusableElements = Array.from(modalBodyRef === null || modalBodyRef === void 0 || (_modalBodyRef$current = modalBodyRef.current) === null || _modalBodyRef$current === void 0 ? void 0 : _modalBodyRef$current.querySelectorAll(`
149
+ const focusableElements = Array.from(modalBodyRef?.current?.querySelectorAll(`
152
150
  a[href], button, textarea, input, select,
153
151
  [tabindex]:not([tabindex="-1"]),
154
152
  [contenteditable="true"]
155
- `));
153
+ `) || []);
156
154
  const firstElement = focusableElements[0];
157
155
  const lastElement = focusableElements[focusableElements.length - 1];
158
156
  if (event.shiftKey && document.activeElement === firstElement) {
@@ -184,10 +182,9 @@ const Modal = /*#__PURE__*/React.forwardRef((_ref5, ref) => {
184
182
  }, [manageFocus]);
185
183
  React.useEffect(() => {
186
184
  if (isOpen) {
187
- var _modalContentRef$curr;
188
- modalContentRef === null || modalContentRef === void 0 || (_modalContentRef$curr = modalContentRef.current) === null || _modalContentRef$curr === void 0 || _modalContentRef$curr.focus();
185
+ modalContentRef?.current?.focus();
189
186
  }
190
- }, [isOpen, modalContentRef === null || modalContentRef === void 0 || (_modalContentRef$curr2 = modalContentRef.current) === null || _modalContentRef$curr2 === void 0 ? void 0 : _modalContentRef$curr2.focus]);
187
+ }, [isOpen, modalContentRef?.current?.focus]);
191
188
  if (!isOpen) {
192
189
  return null;
193
190
  }