@telus-uds/components-base 3.28.1 → 3.29.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 +25 -1
- package/lib/cjs/Autocomplete/Autocomplete.js +86 -32
- package/lib/cjs/Autocomplete/constants.js +2 -1
- package/lib/cjs/Card/CardBase.js +12 -0
- package/lib/cjs/Carousel/Carousel.js +1 -2
- package/lib/cjs/ColourToggle/ColourBubble.js +17 -3
- package/lib/cjs/ColourToggle/ColourToggle.js +8 -2
- package/lib/cjs/ExpandCollapse/Control.js +17 -3
- package/lib/cjs/ExpandCollapse/Panel.js +6 -0
- package/lib/cjs/ExpandCollapseMini/ExpandCollapseMini.js +14 -2
- package/lib/cjs/ExpandCollapseMini/ExpandCollapseMiniControl.js +15 -2
- package/lib/cjs/Link/ChevronLink.js +1 -0
- package/lib/cjs/Link/LinkBase.js +29 -13
- package/lib/cjs/Link/MobileIconTextContent.js +156 -0
- package/lib/cjs/Listbox/ListboxOverlay.js +7 -1
- package/lib/cjs/Listbox/PressableItem.js +2 -2
- package/lib/cjs/TabBar/TabBar.js +7 -2
- package/lib/cjs/TextInput/TextInputBase.js +2 -2
- package/lib/esm/Autocomplete/Autocomplete.js +87 -33
- package/lib/esm/Autocomplete/constants.js +1 -0
- package/lib/esm/Card/CardBase.js +12 -0
- package/lib/esm/Carousel/Carousel.js +1 -2
- package/lib/esm/ColourToggle/ColourBubble.js +17 -3
- package/lib/esm/ColourToggle/ColourToggle.js +8 -2
- package/lib/esm/ExpandCollapse/Control.js +17 -3
- package/lib/esm/ExpandCollapse/Panel.js +6 -0
- package/lib/esm/ExpandCollapseMini/ExpandCollapseMini.js +14 -2
- package/lib/esm/ExpandCollapseMini/ExpandCollapseMiniControl.js +15 -2
- package/lib/esm/Link/ChevronLink.js +1 -0
- package/lib/esm/Link/LinkBase.js +29 -13
- package/lib/esm/Link/MobileIconTextContent.js +147 -0
- package/lib/esm/Listbox/ListboxOverlay.js +7 -1
- package/lib/esm/Listbox/PressableItem.js +3 -3
- package/lib/esm/TabBar/TabBar.js +7 -2
- package/lib/esm/TextInput/TextInputBase.js +2 -2
- package/lib/package.json +1 -1
- package/package.json +1 -1
- package/src/Autocomplete/Autocomplete.jsx +142 -77
- package/src/Autocomplete/constants.js +1 -0
- package/src/Card/CardBase.jsx +12 -0
- package/src/Carousel/Carousel.jsx +1 -2
- package/src/ColourToggle/ColourBubble.jsx +18 -3
- package/src/ColourToggle/ColourToggle.jsx +7 -2
- package/src/ExpandCollapse/Control.jsx +24 -4
- package/src/ExpandCollapse/Panel.jsx +6 -0
- package/src/ExpandCollapseMini/ExpandCollapseMini.jsx +23 -3
- package/src/ExpandCollapseMini/ExpandCollapseMiniControl.jsx +14 -2
- package/src/Link/ChevronLink.jsx +1 -0
- package/src/Link/LinkBase.jsx +47 -20
- package/src/Link/MobileIconTextContent.jsx +129 -0
- package/src/Listbox/ListboxOverlay.jsx +9 -1
- package/src/Listbox/PressableItem.jsx +1 -1
- package/src/TabBar/TabBar.jsx +21 -4
- package/src/TextInput/TextInputBase.jsx +2 -2
package/src/Link/LinkBase.jsx
CHANGED
|
@@ -16,6 +16,7 @@ import { resolvePressableTokens } from '../utils/pressability'
|
|
|
16
16
|
import { withLinkRouter } from '../utils'
|
|
17
17
|
|
|
18
18
|
import InlinePressable from './InlinePressable'
|
|
19
|
+
import MobileIconTextContent from './MobileIconTextContent'
|
|
19
20
|
import { applyTextStyles, applyOuterBorder, useTheme } from '../ThemeProvider'
|
|
20
21
|
import { IconText, iconComponentPropTypes } from '../Icon'
|
|
21
22
|
|
|
@@ -133,6 +134,7 @@ const LinkBase = React.forwardRef(
|
|
|
133
134
|
tokens = {},
|
|
134
135
|
children,
|
|
135
136
|
dataSet,
|
|
137
|
+
useMeasuredMobileIconLayout = false,
|
|
136
138
|
accessibilityRole = 'link',
|
|
137
139
|
...rawRest
|
|
138
140
|
},
|
|
@@ -171,12 +173,14 @@ const LinkBase = React.forwardRef(
|
|
|
171
173
|
const themeTokens = resolveLinkTokens(linkState)
|
|
172
174
|
const outerBorderStyles = selectOuterBorderStyles(themeTokens)
|
|
173
175
|
const decorationStyles = selectDecorationStyles(themeTokens)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
+
const shouldUseMeasuredMobileContent =
|
|
177
|
+
Platform.OS !== 'web' && useMeasuredMobileIconLayout
|
|
176
178
|
|
|
177
179
|
return [
|
|
178
180
|
outerBorderStyles,
|
|
179
|
-
|
|
181
|
+
shouldUseMeasuredMobileContent
|
|
182
|
+
? staticStyles.measuredMobileOuterBorderCompensation
|
|
183
|
+
: null,
|
|
180
184
|
blockLeftStyle,
|
|
181
185
|
decorationStyles,
|
|
182
186
|
hasIcon && staticStyles.rowContainer
|
|
@@ -196,28 +200,50 @@ const LinkBase = React.forwardRef(
|
|
|
196
200
|
|
|
197
201
|
const isTextOnlyLink = !IconComponent && !icon && accessibilityRole === 'link'
|
|
198
202
|
const adjustedIconSpace = Platform.OS !== 'web' && isTextOnlyLink ? 0 : iconSpace
|
|
203
|
+
const shouldUseMeasuredMobileContent =
|
|
204
|
+
Platform.OS !== 'web' && useMeasuredMobileIconLayout
|
|
205
|
+
const textBaselineStyle = shouldUseMeasuredMobileContent ? null : staticStyles.baseline
|
|
206
|
+
|
|
207
|
+
const linkTextContent = (
|
|
208
|
+
<Text
|
|
209
|
+
style={[
|
|
210
|
+
textStyles,
|
|
211
|
+
blockTextStyles,
|
|
212
|
+
textBaselineStyle,
|
|
213
|
+
staticStyles.bubblePointerEvents
|
|
214
|
+
]}
|
|
215
|
+
>
|
|
216
|
+
{typeof children === 'function' ? children(linkState) : children}
|
|
217
|
+
</Text>
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
const sharedIconProps = {
|
|
221
|
+
...iconProps,
|
|
222
|
+
tokens: iconTokens,
|
|
223
|
+
style: staticStyles.bubblePointerEvents
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (shouldUseMeasuredMobileContent) {
|
|
227
|
+
return (
|
|
228
|
+
<MobileIconTextContent
|
|
229
|
+
icon={IconComponent}
|
|
230
|
+
iconPosition={iconPosition}
|
|
231
|
+
space={adjustedIconSpace}
|
|
232
|
+
iconProps={sharedIconProps}
|
|
233
|
+
>
|
|
234
|
+
{linkTextContent}
|
|
235
|
+
</MobileIconTextContent>
|
|
236
|
+
)
|
|
237
|
+
}
|
|
199
238
|
|
|
200
239
|
return (
|
|
201
240
|
<IconText
|
|
202
241
|
icon={IconComponent}
|
|
203
242
|
iconPosition={iconPosition}
|
|
204
243
|
space={adjustedIconSpace}
|
|
205
|
-
iconProps={
|
|
206
|
-
...iconProps,
|
|
207
|
-
tokens: iconTokens,
|
|
208
|
-
style: staticStyles.bubblePointerEvents
|
|
209
|
-
}}
|
|
244
|
+
iconProps={sharedIconProps}
|
|
210
245
|
>
|
|
211
|
-
|
|
212
|
-
style={[
|
|
213
|
-
textStyles,
|
|
214
|
-
blockTextStyles,
|
|
215
|
-
staticStyles.baseline,
|
|
216
|
-
staticStyles.bubblePointerEvents
|
|
217
|
-
]}
|
|
218
|
-
>
|
|
219
|
-
{typeof children === 'function' ? children(linkState) : children}
|
|
220
|
-
</Text>
|
|
246
|
+
{linkTextContent}
|
|
221
247
|
</IconText>
|
|
222
248
|
)
|
|
223
249
|
}}
|
|
@@ -280,11 +306,12 @@ const staticStyles = StyleSheet.create({
|
|
|
280
306
|
}
|
|
281
307
|
})
|
|
282
308
|
},
|
|
283
|
-
|
|
309
|
+
measuredMobileOuterBorderCompensation: {
|
|
284
310
|
...(Platform.OS !== 'web' && {
|
|
285
311
|
marginHorizontal: 2,
|
|
312
|
+
marginVertical: 2,
|
|
286
313
|
paddingHorizontal: Platform.OS === 'android' ? 2 : 0,
|
|
287
|
-
|
|
314
|
+
paddingVertical: Platform.OS === 'android' ? 2 : 0
|
|
288
315
|
})
|
|
289
316
|
}
|
|
290
317
|
})
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import { Text, View, StyleSheet } from 'react-native'
|
|
4
|
+
|
|
5
|
+
import Icon, { iconComponentPropTypes } from '../Icon/Icon'
|
|
6
|
+
import { spacingProps } from '../utils'
|
|
7
|
+
|
|
8
|
+
const MobileIconTextContent = React.forwardRef(
|
|
9
|
+
({ space = 0, iconPosition = 'left', icon: IconComponent, iconProps = {}, children }, ref) => {
|
|
10
|
+
const [translateY, setTranslateY] = React.useState(0)
|
|
11
|
+
const latestTranslateYRef = React.useRef(0)
|
|
12
|
+
const layoutsRef = React.useRef({
|
|
13
|
+
container: null,
|
|
14
|
+
text: null,
|
|
15
|
+
icon: null
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const applyAlignment = React.useCallback(() => {
|
|
19
|
+
const { container, text, icon } = layoutsRef.current
|
|
20
|
+
|
|
21
|
+
if (!container || !icon || !icon.height) return
|
|
22
|
+
|
|
23
|
+
const targetY = text ? text.y + text.height / 2 : container.height / 2
|
|
24
|
+
const iconY = icon.y + icon.height / 2
|
|
25
|
+
const nextTranslateY = Math.round((targetY - iconY) * 100) / 100
|
|
26
|
+
|
|
27
|
+
if (!Number.isFinite(nextTranslateY)) return
|
|
28
|
+
if (Math.abs(nextTranslateY - latestTranslateYRef.current) < 0.5) return
|
|
29
|
+
|
|
30
|
+
latestTranslateYRef.current = nextTranslateY
|
|
31
|
+
setTranslateY(nextTranslateY)
|
|
32
|
+
}, [])
|
|
33
|
+
|
|
34
|
+
const handleContainerLayout = React.useCallback(
|
|
35
|
+
({ nativeEvent: { layout } }) => {
|
|
36
|
+
layoutsRef.current.container = layout
|
|
37
|
+
applyAlignment()
|
|
38
|
+
},
|
|
39
|
+
[applyAlignment]
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
const handleTextLayout = React.useCallback(
|
|
43
|
+
({ nativeEvent: { layout } }) => {
|
|
44
|
+
layoutsRef.current.text = layout
|
|
45
|
+
applyAlignment()
|
|
46
|
+
},
|
|
47
|
+
[applyAlignment]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
const handleIconLayout = React.useCallback(
|
|
51
|
+
({ nativeEvent: { layout } }) => {
|
|
52
|
+
layoutsRef.current.icon = layout
|
|
53
|
+
applyAlignment()
|
|
54
|
+
},
|
|
55
|
+
[applyAlignment]
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const iconContent = IconComponent ? (
|
|
59
|
+
<Icon ref={ref} icon={IconComponent} scalesWithText {...iconProps} />
|
|
60
|
+
) : null
|
|
61
|
+
|
|
62
|
+
const iconWrapper = IconComponent ? (
|
|
63
|
+
<View
|
|
64
|
+
onLayout={handleIconLayout}
|
|
65
|
+
style={[staticStyles.iconContainer, { transform: [{ translateY }] }]}
|
|
66
|
+
>
|
|
67
|
+
{iconContent}
|
|
68
|
+
</View>
|
|
69
|
+
) : null
|
|
70
|
+
|
|
71
|
+
if (iconPosition === 'inline') {
|
|
72
|
+
return (
|
|
73
|
+
<Text onLayout={handleContainerLayout}>
|
|
74
|
+
<Text onLayout={handleTextLayout}>{children}</Text>{' '}
|
|
75
|
+
<View style={staticStyles.inlineIconContainer}>{iconWrapper}</View>
|
|
76
|
+
</Text>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const iconSpaceStyle = iconPosition === 'left' ? { marginRight: space } : { marginLeft: space }
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<View onLayout={handleContainerLayout} style={staticStyles.rowContainer}>
|
|
84
|
+
{iconPosition === 'left' && <View style={iconSpaceStyle}>{iconWrapper}</View>}
|
|
85
|
+
<View onLayout={handleTextLayout}>{children}</View>
|
|
86
|
+
{iconPosition === 'right' && <View style={iconSpaceStyle}>{iconWrapper}</View>}
|
|
87
|
+
</View>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
MobileIconTextContent.displayName = 'MobileIconTextContent'
|
|
93
|
+
|
|
94
|
+
MobileIconTextContent.propTypes = {
|
|
95
|
+
/**
|
|
96
|
+
* Amount of space between text and icon. Uses the theme spacing scale.
|
|
97
|
+
*/
|
|
98
|
+
space: spacingProps.types.spacingValue,
|
|
99
|
+
/**
|
|
100
|
+
* Position of the icon relative to text.
|
|
101
|
+
*/
|
|
102
|
+
iconPosition: PropTypes.oneOf(['left', 'right', 'inline']),
|
|
103
|
+
/**
|
|
104
|
+
* A valid UDS icon component imported from a UDS palette.
|
|
105
|
+
*/
|
|
106
|
+
icon: PropTypes.elementType,
|
|
107
|
+
/**
|
|
108
|
+
* Props passed to the icon component.
|
|
109
|
+
*/
|
|
110
|
+
iconProps: PropTypes.exact(iconComponentPropTypes),
|
|
111
|
+
/**
|
|
112
|
+
* Content rendered alongside the icon.
|
|
113
|
+
*/
|
|
114
|
+
children: PropTypes.node
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const staticStyles = StyleSheet.create({
|
|
118
|
+
rowContainer: {
|
|
119
|
+
flexDirection: 'row'
|
|
120
|
+
},
|
|
121
|
+
iconContainer: {
|
|
122
|
+
alignSelf: 'flex-start'
|
|
123
|
+
},
|
|
124
|
+
inlineIconContainer: {
|
|
125
|
+
position: 'absolute'
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
export default MobileIconTextContent
|
|
@@ -28,6 +28,7 @@ const DropdownOverlay = React.forwardRef(
|
|
|
28
28
|
isReady = false,
|
|
29
29
|
overlaidPosition,
|
|
30
30
|
maxWidth,
|
|
31
|
+
maxHeight,
|
|
31
32
|
minWidth,
|
|
32
33
|
onLayout,
|
|
33
34
|
tokens,
|
|
@@ -50,12 +51,18 @@ const DropdownOverlay = React.forwardRef(
|
|
|
50
51
|
staticStyles.positioner,
|
|
51
52
|
!isReady && staticStyles.hidden
|
|
52
53
|
]}
|
|
54
|
+
onMouseDown={(e) => {
|
|
55
|
+
e.preventDefault()
|
|
56
|
+
}}
|
|
53
57
|
>
|
|
54
58
|
<Card
|
|
55
59
|
tokens={{
|
|
56
60
|
shadow: systemTokens.shadow,
|
|
57
61
|
borderRadius: systemTokens.borderRadius,
|
|
58
|
-
...(Platform.OS === 'web' && {
|
|
62
|
+
...(Platform.OS === 'web' && {
|
|
63
|
+
maxHeight,
|
|
64
|
+
overflowY: 'auto'
|
|
65
|
+
}),
|
|
59
66
|
paddingBottom: paddingVertical,
|
|
60
67
|
paddingTop: paddingVertical,
|
|
61
68
|
paddingLeft: paddingHorizontal,
|
|
@@ -89,6 +96,7 @@ DropdownOverlay.propTypes = {
|
|
|
89
96
|
width: PropTypes.number
|
|
90
97
|
}),
|
|
91
98
|
maxWidth: PropTypes.number,
|
|
99
|
+
maxHeight: PropTypes.number,
|
|
92
100
|
minWidth: PropTypes.number,
|
|
93
101
|
onLayout: PropTypes.func,
|
|
94
102
|
tokens: PropTypes.object,
|
|
@@ -120,7 +120,7 @@ const PressableItem = React.forwardRef(
|
|
|
120
120
|
>
|
|
121
121
|
{(pressableState) => {
|
|
122
122
|
return (
|
|
123
|
-
<Text style={selectTextStyles(resolveButtonTokens(pressableState))}>{children}
|
|
123
|
+
<Text style={selectTextStyles(resolveButtonTokens(pressableState))}>{children}</Text>
|
|
124
124
|
)
|
|
125
125
|
}}
|
|
126
126
|
</Pressable>
|
package/src/TabBar/TabBar.jsx
CHANGED
|
@@ -42,12 +42,24 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, vie
|
|
|
42
42
|
* items={items}
|
|
43
43
|
* initiallySelectedItem="1"
|
|
44
44
|
* onChange={(itemId) => console.log(itemId)}
|
|
45
|
+
* accessibilityLabel="Main navigation"
|
|
45
46
|
* />
|
|
46
47
|
* )
|
|
47
48
|
*/
|
|
48
49
|
|
|
49
50
|
const TabBar = React.forwardRef(
|
|
50
|
-
(
|
|
51
|
+
(
|
|
52
|
+
{
|
|
53
|
+
items = [],
|
|
54
|
+
initiallySelectedItem = '0',
|
|
55
|
+
onChange,
|
|
56
|
+
variant,
|
|
57
|
+
tokens,
|
|
58
|
+
accessibilityLabel,
|
|
59
|
+
...rest
|
|
60
|
+
},
|
|
61
|
+
ref
|
|
62
|
+
) => {
|
|
51
63
|
const [isSelected, setIsSelected] = React.useState(initiallySelectedItem)
|
|
52
64
|
const themeTokens = useThemeTokens('TabBar', tokens, variant)
|
|
53
65
|
|
|
@@ -62,7 +74,11 @@ const TabBar = React.forwardRef(
|
|
|
62
74
|
style={[styles.tabBar, selectTabBarContainerStyles(themeTokens)]}
|
|
63
75
|
{...selectProps(rest)}
|
|
64
76
|
>
|
|
65
|
-
<View
|
|
77
|
+
<View
|
|
78
|
+
style={[styles.tabBarItem, selectTabBarItemContainerStyles(themeTokens)]}
|
|
79
|
+
accessibilityRole="tablist"
|
|
80
|
+
accessibilityLabel={accessibilityLabel}
|
|
81
|
+
>
|
|
66
82
|
{items.map((item, index) => (
|
|
67
83
|
<TabBarItem
|
|
68
84
|
key={item.id}
|
|
@@ -73,7 +89,6 @@ const TabBar = React.forwardRef(
|
|
|
73
89
|
iconActive={item.iconActive}
|
|
74
90
|
onPress={() => handlePress(item.id)}
|
|
75
91
|
id={`tab-item-${index}`}
|
|
76
|
-
accessibilityRole="tablist"
|
|
77
92
|
tokens={item.tokens}
|
|
78
93
|
/>
|
|
79
94
|
))}
|
|
@@ -105,7 +120,9 @@ TabBar.propTypes = {
|
|
|
105
120
|
/** Variant of TabBar for styling purposes. */
|
|
106
121
|
variant: variantProp.propType,
|
|
107
122
|
/** Tokens for theming and styling. */
|
|
108
|
-
tokens: getTokensPropType('TabBar')
|
|
123
|
+
tokens: getTokensPropType('TabBar'),
|
|
124
|
+
/** Accessible label for the tab bar navigation region, used by screen readers to identify the tablist. */
|
|
125
|
+
accessibilityLabel: PropTypes.string
|
|
109
126
|
}
|
|
110
127
|
|
|
111
128
|
const styles = StyleSheet.create({
|
|
@@ -255,8 +255,8 @@ const TextInputBase = React.forwardRef(
|
|
|
255
255
|
}, [element, pattern])
|
|
256
256
|
|
|
257
257
|
const handleChangeText = (event) => {
|
|
258
|
-
const text = event.nativeEvent?.text
|
|
259
|
-
let filteredText = isNumeric ? text?.replace(/[^\d]/g, '') : text
|
|
258
|
+
const text = event.nativeEvent?.text ?? event.target?.value
|
|
259
|
+
let filteredText = isNumeric ? text?.replace(/[^\d]/g, '') || undefined : text
|
|
260
260
|
if (type === 'card' && filteredText) {
|
|
261
261
|
const formattedValue = filteredText.replace(/[^a-zA-Z0-9]/g, '')
|
|
262
262
|
const regex = new RegExp(`([a-zA-Z0-9]{4})(?=[a-zA-Z0-9])`, 'g')
|