@telus-uds/components-base 1.94.0 → 1.95.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 +20 -2
- package/lib/Autocomplete/Autocomplete.js +2 -1
- package/lib/DownloadApp/DownloadApp.js +168 -0
- package/lib/DownloadApp/dictionary.js +17 -0
- package/lib/DownloadApp/index.js +10 -0
- package/lib/Icon/IconText.js +19 -2
- package/lib/Link/LinkBase.js +2 -2
- package/lib/Modal/Modal.js +1 -1
- package/lib/TabBar/TabBar.js +133 -0
- package/lib/TabBar/TabBarItem.js +184 -0
- package/lib/TabBar/index.js +10 -0
- package/lib/TextInput/TextInputBase.js +2 -1
- package/lib/Tooltip/getTooltipPosition.js +8 -9
- package/lib/index.js +16 -0
- package/lib-module/Autocomplete/Autocomplete.js +2 -1
- package/lib-module/DownloadApp/DownloadApp.js +160 -0
- package/lib-module/DownloadApp/dictionary.js +10 -0
- package/lib-module/DownloadApp/index.js +2 -0
- package/lib-module/Icon/IconText.js +19 -2
- package/lib-module/Link/LinkBase.js +2 -2
- package/lib-module/Modal/Modal.js +1 -1
- package/lib-module/TabBar/TabBar.js +125 -0
- package/lib-module/TabBar/TabBarItem.js +177 -0
- package/lib-module/TabBar/index.js +2 -0
- package/lib-module/TextInput/TextInputBase.js +2 -1
- package/lib-module/Tooltip/getTooltipPosition.js +8 -9
- package/lib-module/index.js +2 -0
- package/package.json +2 -2
- package/src/Autocomplete/Autocomplete.jsx +2 -1
- package/src/DownloadApp/DownloadApp.jsx +165 -0
- package/src/DownloadApp/dictionary.js +10 -0
- package/src/DownloadApp/index.js +3 -0
- package/src/Icon/IconText.jsx +21 -4
- package/src/Link/LinkBase.jsx +2 -2
- package/src/Modal/Modal.jsx +1 -1
- package/src/TabBar/TabBar.jsx +123 -0
- package/src/TabBar/TabBarItem.jsx +149 -0
- package/src/TabBar/index.js +3 -0
- package/src/TextInput/TextInputBase.jsx +1 -1
- package/src/Tooltip/getTooltipPosition.js +11 -12
- package/src/index.js +2 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
function
|
|
1
|
+
function getAbsolutePosition(position) {
|
|
2
2
|
const {
|
|
3
3
|
left,
|
|
4
4
|
right,
|
|
@@ -9,7 +9,7 @@ function normalizePosition(position) {
|
|
|
9
9
|
} = position;
|
|
10
10
|
|
|
11
11
|
// adjust the coordinates so that it fits within the window
|
|
12
|
-
const
|
|
12
|
+
const finalPosition = {
|
|
13
13
|
left: Math.max(0, left),
|
|
14
14
|
right: Math.max(0, right),
|
|
15
15
|
top: Math.max(0, top),
|
|
@@ -19,15 +19,14 @@ function normalizePosition(position) {
|
|
|
19
19
|
const getAbsoluteDiff = (value1, value2) => Math.abs(Math.abs(value1) - Math.abs(value2));
|
|
20
20
|
|
|
21
21
|
// adjust the width by whatever has been subtracted from left or right
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
|
|
22
|
+
finalPosition.width = width - Math.abs(getAbsoluteDiff(left, finalPosition.left) - getAbsoluteDiff(right, finalPosition.right));
|
|
23
|
+
if (finalPosition.top !== top) {
|
|
24
|
+
finalPosition.bottom += finalPosition.top - top;
|
|
25
25
|
}
|
|
26
|
-
const isNormalized = normalized.right !== right || normalized.left !== left || normalized.top !== top;
|
|
27
26
|
return {
|
|
28
|
-
...
|
|
27
|
+
...finalPosition,
|
|
29
28
|
...rest,
|
|
30
|
-
isNormalized
|
|
29
|
+
isNormalized: false
|
|
31
30
|
};
|
|
32
31
|
}
|
|
33
32
|
function invertPosition(position) {
|
|
@@ -166,6 +165,6 @@ function getTooltipPosition(position, _ref2) {
|
|
|
166
165
|
|
|
167
166
|
// prefer 'below' over 'above', since we can always expand the document downwards,
|
|
168
167
|
// and 'above' might cause issues on small viewports with large tooltips
|
|
169
|
-
return
|
|
168
|
+
return getAbsolutePosition(leastOverflowing.position === 'above' ? findRectByPosition('below', boundingRects) : leastOverflowing);
|
|
170
169
|
}
|
|
171
170
|
export default getTooltipPosition;
|
package/lib-module/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export * from './Checkbox';
|
|
|
15
15
|
export { default as CheckboxCard } from './CheckboxCard';
|
|
16
16
|
export { default as CheckboxCardGroup } from './CheckboxCardGroup';
|
|
17
17
|
export { default as ColourToggle } from './ColourToggle';
|
|
18
|
+
export { default as DownloadApp } from './DownloadApp';
|
|
18
19
|
export { default as Divider } from './Divider';
|
|
19
20
|
export { default as ExpandCollapse, Accordion } from './ExpandCollapse';
|
|
20
21
|
export { default as Feedback } from './Feedback';
|
|
@@ -57,6 +58,7 @@ export * from './StackView';
|
|
|
57
58
|
export { default as Status } from './Status';
|
|
58
59
|
export { default as StepTracker } from './StepTracker';
|
|
59
60
|
export { default as Tabs } from './Tabs';
|
|
61
|
+
export { default as TabBar } from './TabBar';
|
|
60
62
|
export { default as Tags } from './Tags';
|
|
61
63
|
export * from './TextInput';
|
|
62
64
|
export { default as Timeline } from './Timeline';
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"@floating-ui/react-native": "^0.8.1",
|
|
12
12
|
"@gorhom/portal": "^1.0.14",
|
|
13
13
|
"@telus-uds/system-constants": "^1.3.0",
|
|
14
|
-
"@telus-uds/system-theme-tokens": "^2.
|
|
14
|
+
"@telus-uds/system-theme-tokens": "^2.64.0",
|
|
15
15
|
"airbnb-prop-types": "^2.16.0",
|
|
16
16
|
"css-mediaquery": "^0.1.2",
|
|
17
17
|
"expo-linear-gradient": "^12.5.0",
|
|
@@ -86,6 +86,6 @@
|
|
|
86
86
|
"standard-engine": {
|
|
87
87
|
"skip": true
|
|
88
88
|
},
|
|
89
|
-
"version": "1.
|
|
89
|
+
"version": "1.95.0",
|
|
90
90
|
"types": "types/index.d.ts"
|
|
91
91
|
}
|
|
@@ -102,6 +102,7 @@ const Autocomplete = React.forwardRef(
|
|
|
102
102
|
validation,
|
|
103
103
|
value,
|
|
104
104
|
helpText = '',
|
|
105
|
+
loadingLabel,
|
|
105
106
|
...rest
|
|
106
107
|
},
|
|
107
108
|
ref
|
|
@@ -334,7 +335,7 @@ const Autocomplete = React.forwardRef(
|
|
|
334
335
|
ref={openOverlayRef}
|
|
335
336
|
>
|
|
336
337
|
{isLoading ? (
|
|
337
|
-
<Loading label={getCopy('loading')} />
|
|
338
|
+
<Loading label={loadingLabel ?? getCopy('loading')} />
|
|
338
339
|
) : (
|
|
339
340
|
<Suggestions
|
|
340
341
|
hasResults={getCopy('hasResults')}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import { Pressable, Platform, StyleSheet } from 'react-native'
|
|
4
|
+
import {
|
|
5
|
+
a11yProps,
|
|
6
|
+
linkProps,
|
|
7
|
+
hrefAttrsProp,
|
|
8
|
+
viewProps,
|
|
9
|
+
getTokensPropType,
|
|
10
|
+
resolvePressableTokens,
|
|
11
|
+
variantProp,
|
|
12
|
+
copyPropTypes,
|
|
13
|
+
selectSystemProps,
|
|
14
|
+
useCopy
|
|
15
|
+
} from '../utils'
|
|
16
|
+
import { useThemeTokensCallback } from '../ThemeProvider'
|
|
17
|
+
import defaultDictionary from './dictionary'
|
|
18
|
+
|
|
19
|
+
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, linkProps, viewProps])
|
|
20
|
+
|
|
21
|
+
const selectOuterStyles = ({ borderColor, borderWidth, borderGap, borderRadius, padding }) => ({
|
|
22
|
+
outline: 'none',
|
|
23
|
+
borderColor,
|
|
24
|
+
borderWidth,
|
|
25
|
+
borderGap,
|
|
26
|
+
borderRadius,
|
|
27
|
+
padding
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const DownloadApp = React.forwardRef(
|
|
31
|
+
(
|
|
32
|
+
{
|
|
33
|
+
copy = 'en',
|
|
34
|
+
dictionary = defaultDictionary,
|
|
35
|
+
type = 'ios',
|
|
36
|
+
href,
|
|
37
|
+
onPress,
|
|
38
|
+
tokens = {},
|
|
39
|
+
variant = {},
|
|
40
|
+
...props
|
|
41
|
+
},
|
|
42
|
+
ref
|
|
43
|
+
) => {
|
|
44
|
+
const getCopy = useCopy({ dictionary, copy })
|
|
45
|
+
const { hrefAttrs, rest } = hrefAttrsProp.bundle(props)
|
|
46
|
+
|
|
47
|
+
const selectedProps = selectProps({
|
|
48
|
+
accessibilityRole: href ? 'link' : 'button',
|
|
49
|
+
href,
|
|
50
|
+
onPress: linkProps.handleHref({ href, onPress }),
|
|
51
|
+
hrefAttrs,
|
|
52
|
+
...rest
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const getDownloadAppTokens = useThemeTokensCallback('DownloadApp', tokens, variant)
|
|
56
|
+
const resolveDownloadAppTokens = (pressableState) => {
|
|
57
|
+
const themeTokens = resolvePressableTokens(getDownloadAppTokens, pressableState, {})
|
|
58
|
+
return selectOuterStyles(themeTokens)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const { androidENIcon, androidFRIcon, iosENIcon, iosFRIcon } = getDownloadAppTokens()
|
|
62
|
+
|
|
63
|
+
const imageResources = {
|
|
64
|
+
en: {
|
|
65
|
+
android: androidENIcon,
|
|
66
|
+
ios: iosENIcon
|
|
67
|
+
},
|
|
68
|
+
fr: {
|
|
69
|
+
android: androidFRIcon,
|
|
70
|
+
ios: iosFRIcon
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const IconComponent = imageResources[copy][type]
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<Pressable
|
|
77
|
+
ref={ref}
|
|
78
|
+
style={(pressState) => [resolveDownloadAppTokens(pressState), staticStyles.row]}
|
|
79
|
+
{...selectedProps}
|
|
80
|
+
>
|
|
81
|
+
<IconComponent
|
|
82
|
+
style={staticImageSizes[type][copy]}
|
|
83
|
+
alt={type === 'ios' ? getCopy('altTextIos') : getCopy('altTextAndroid')}
|
|
84
|
+
/>
|
|
85
|
+
</Pressable>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
DownloadApp.displayName = 'DownloadApp'
|
|
90
|
+
|
|
91
|
+
const dictionaryContentShape = PropTypes.shape({
|
|
92
|
+
altTextAndroid: PropTypes.string.isRequired,
|
|
93
|
+
altTextIos: PropTypes.string.isRequired
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
DownloadApp.propTypes = {
|
|
97
|
+
...selectedSystemPropTypes,
|
|
98
|
+
copy: copyPropTypes,
|
|
99
|
+
/**
|
|
100
|
+
* Override the default dictionary, by passing the complete dictionary object for `en` and `fr`
|
|
101
|
+
*/
|
|
102
|
+
dictionary: PropTypes.shape({
|
|
103
|
+
en: dictionaryContentShape,
|
|
104
|
+
fr: dictionaryContentShape
|
|
105
|
+
}),
|
|
106
|
+
/**
|
|
107
|
+
* Select the type of image to show.
|
|
108
|
+
*/
|
|
109
|
+
type: PropTypes.oneOf(['android', 'ios']),
|
|
110
|
+
/**
|
|
111
|
+
* It's a simple link that opens the Download Button instead of the onPress function.
|
|
112
|
+
*/
|
|
113
|
+
href: PropTypes.string,
|
|
114
|
+
/**
|
|
115
|
+
* Function called when the button is pressed. Required unless the button has a href.
|
|
116
|
+
*/
|
|
117
|
+
onPress: PropTypes.func,
|
|
118
|
+
/**
|
|
119
|
+
* DownloadApp tokens.
|
|
120
|
+
*/
|
|
121
|
+
tokens: getTokensPropType('DownloadApp'),
|
|
122
|
+
/**
|
|
123
|
+
* DownloadApp variant.
|
|
124
|
+
*/
|
|
125
|
+
variant: variantProp.propType
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const staticStyles = StyleSheet.create({
|
|
129
|
+
row: {
|
|
130
|
+
...Platform.select({
|
|
131
|
+
web: {
|
|
132
|
+
display: 'inline-flex',
|
|
133
|
+
width: 'fit-content'
|
|
134
|
+
},
|
|
135
|
+
default: {
|
|
136
|
+
alignSelf: 'flex-start'
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const staticImageSizes = {
|
|
143
|
+
android: {
|
|
144
|
+
en: {
|
|
145
|
+
width: 162,
|
|
146
|
+
height: 48
|
|
147
|
+
},
|
|
148
|
+
fr: {
|
|
149
|
+
width: 162,
|
|
150
|
+
height: 48
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
ios: {
|
|
154
|
+
en: {
|
|
155
|
+
width: 144,
|
|
156
|
+
height: 48
|
|
157
|
+
},
|
|
158
|
+
fr: {
|
|
159
|
+
width: 152,
|
|
160
|
+
height: 48
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export default DownloadApp
|
package/src/Icon/IconText.jsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
|
-
import { Platform, View } from 'react-native'
|
|
4
|
-
|
|
3
|
+
import { Platform, View, Text, StyleSheet } from 'react-native'
|
|
5
4
|
import Icon, { iconComponentPropTypes } from './Icon'
|
|
6
5
|
import { getStackedContent } from '../StackView'
|
|
7
6
|
import { spacingProps } from '../utils'
|
|
@@ -46,6 +45,18 @@ const IconText = React.forwardRef(
|
|
|
46
45
|
const mobile = Platform.OS === 'android' ? iconAdjustedAndroid : iconAdjustedIOS
|
|
47
46
|
const adjustedContainer = Platform.OS === 'web' ? iconContent : mobile
|
|
48
47
|
|
|
48
|
+
const adjustedContainerWithPosition = (
|
|
49
|
+
<View style={staticStyles.adjustedContainer}>{adjustedContainer}</View>
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if (iconPosition === 'inline') {
|
|
53
|
+
return (
|
|
54
|
+
<Text>
|
|
55
|
+
{children} {adjustedContainerWithPosition}
|
|
56
|
+
</Text>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
49
60
|
return getStackedContent(
|
|
50
61
|
iconPosition === 'left' ? [adjustedContainer, children] : [children, adjustedContainer],
|
|
51
62
|
{ space, direction: 'row' }
|
|
@@ -62,9 +73,9 @@ IconText.propTypes = {
|
|
|
62
73
|
*/
|
|
63
74
|
space: spacingProps.types.spacingValue,
|
|
64
75
|
/**
|
|
65
|
-
* Whether the icon should be to the left of the content
|
|
76
|
+
* Whether the icon should be to the left of the content, the right of the content or inline with the content.
|
|
66
77
|
*/
|
|
67
|
-
iconPosition: PropTypes.oneOf(['left', 'right']),
|
|
78
|
+
iconPosition: PropTypes.oneOf(['left', 'right', 'inline']),
|
|
68
79
|
/**
|
|
69
80
|
* A valid UDS icon component imported from a UDS palette.
|
|
70
81
|
*/
|
|
@@ -83,4 +94,10 @@ IconText.propTypes = {
|
|
|
83
94
|
/* eslint-enable react/no-unused-prop-types */
|
|
84
95
|
}
|
|
85
96
|
|
|
97
|
+
const staticStyles = StyleSheet.create({
|
|
98
|
+
adjustedContainer: {
|
|
99
|
+
position: 'absolute'
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
86
103
|
export default IconText
|
package/src/Link/LinkBase.jsx
CHANGED
|
@@ -240,9 +240,9 @@ LinkBase.propTypes = {
|
|
|
240
240
|
*/
|
|
241
241
|
icon: PropTypes.elementType,
|
|
242
242
|
/**
|
|
243
|
-
* When `icon` is provided, use `iconPosition` to place the Icon to the left or
|
|
243
|
+
* When `icon` is provided, use `iconPosition` to place the Icon to the left, right or inline with Link.
|
|
244
244
|
*/
|
|
245
|
-
iconPosition: PropTypes.oneOf(['left', 'right']),
|
|
245
|
+
iconPosition: PropTypes.oneOf(['left', 'right', 'inline']),
|
|
246
246
|
/**
|
|
247
247
|
* On Web if href is passed, React Native Web maps this object's props to
|
|
248
248
|
* `rel`, `target` and `download` attrs.
|
package/src/Modal/Modal.jsx
CHANGED
|
@@ -147,7 +147,7 @@ const Modal = React.forwardRef(
|
|
|
147
147
|
if (Platform.OS === 'web') {
|
|
148
148
|
const handleFocus = () => {
|
|
149
149
|
// If the focus is on the last item of the web modal container, move it to the close button
|
|
150
|
-
if (document.activeElement === focusTrapRef.current) {
|
|
150
|
+
if (document.activeElement === focusTrapRef.current && closeButtonRef.current) {
|
|
151
151
|
closeButtonRef.current.focus()
|
|
152
152
|
}
|
|
153
153
|
return undefined
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import { View, StyleSheet } from 'react-native'
|
|
4
|
+
import TabBarItem from './TabBarItem'
|
|
5
|
+
import { useThemeTokens } from '../ThemeProvider'
|
|
6
|
+
import { variantProp, getTokensPropType, selectSystemProps, a11yProps, viewProps } from '../utils'
|
|
7
|
+
|
|
8
|
+
const selectTabBarContainerStyles = ({
|
|
9
|
+
paddingTop,
|
|
10
|
+
paddingBottom,
|
|
11
|
+
borderTopWidth,
|
|
12
|
+
borderTopColor,
|
|
13
|
+
backgroundColor
|
|
14
|
+
}) => ({
|
|
15
|
+
paddingTop,
|
|
16
|
+
paddingBottom,
|
|
17
|
+
borderTopWidth,
|
|
18
|
+
borderTopColor,
|
|
19
|
+
backgroundColor
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const selectTabBarItemContainerStyles = ({ paddingLeft, paddingRight, gap }) => ({
|
|
23
|
+
paddingLeft,
|
|
24
|
+
paddingRight,
|
|
25
|
+
gap
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* TabBar component renders a navigation bar with multiple TabBarItems.
|
|
32
|
+
* It allows users to switch between different views or sections.
|
|
33
|
+
*
|
|
34
|
+
* @component
|
|
35
|
+
* @example
|
|
36
|
+
* const items = [
|
|
37
|
+
* { id: '1', label: 'Home', icon: <HomeIcon />, iconActive: <HomeActiveIcon /> },
|
|
38
|
+
* { id: '2', label: 'Profile', icon: <ProfileIcon />, iconActive: <ProfileActiveIcon /> },
|
|
39
|
+
* ]
|
|
40
|
+
* return (
|
|
41
|
+
* <TabBar
|
|
42
|
+
* items={items}
|
|
43
|
+
* initiallySelectedItem="1"
|
|
44
|
+
* onChange={(itemId) => console.log(itemId)}
|
|
45
|
+
* />
|
|
46
|
+
* )
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
const TabBar = React.forwardRef(
|
|
50
|
+
({ items = [], initiallySelectedItem = '0', onChange, variant, tokens, ...rest }, ref) => {
|
|
51
|
+
const [isSelected, setIsSelected] = React.useState(initiallySelectedItem)
|
|
52
|
+
const themeTokens = useThemeTokens('TabBar', tokens, variant)
|
|
53
|
+
|
|
54
|
+
const handlePress = (itemId) => {
|
|
55
|
+
setIsSelected(itemId)
|
|
56
|
+
onChange?.(itemId)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<View
|
|
61
|
+
ref={ref}
|
|
62
|
+
style={[styles.tabBar, selectTabBarContainerStyles(themeTokens)]}
|
|
63
|
+
{...selectProps(rest)}
|
|
64
|
+
>
|
|
65
|
+
<View style={[styles.tabBarItem, selectTabBarItemContainerStyles(themeTokens)]}>
|
|
66
|
+
{items.map((item, index) => (
|
|
67
|
+
<TabBarItem
|
|
68
|
+
key={item.id}
|
|
69
|
+
label={item.label}
|
|
70
|
+
href={item.href}
|
|
71
|
+
isSelected={isSelected === item.id}
|
|
72
|
+
icon={item.icon}
|
|
73
|
+
iconActive={item.iconActive}
|
|
74
|
+
onPress={() => handlePress(item.id)}
|
|
75
|
+
id={`tab-item-${index}`}
|
|
76
|
+
accessibilityRole="tablist"
|
|
77
|
+
/>
|
|
78
|
+
))}
|
|
79
|
+
</View>
|
|
80
|
+
</View>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
TabBar.displayName = 'TabBar'
|
|
86
|
+
|
|
87
|
+
TabBar.propTypes = {
|
|
88
|
+
...selectedSystemPropTypes,
|
|
89
|
+
/** Array of TabBarItems to be displayed in TabBar */
|
|
90
|
+
items: PropTypes.arrayOf(
|
|
91
|
+
PropTypes.shape({
|
|
92
|
+
id: PropTypes.string.isRequired,
|
|
93
|
+
icon: PropTypes.node,
|
|
94
|
+
iconActive: PropTypes.node,
|
|
95
|
+
label: PropTypes.string.isRequired,
|
|
96
|
+
href: PropTypes.string
|
|
97
|
+
})
|
|
98
|
+
).isRequired,
|
|
99
|
+
/** Id of the initially selected item. */
|
|
100
|
+
initiallySelectedItem: PropTypes.number,
|
|
101
|
+
/** Callback function to handle item selection change. */
|
|
102
|
+
onChange: PropTypes.func,
|
|
103
|
+
/** Variant of TabBar for styling purposes. */
|
|
104
|
+
variant: variantProp.propType,
|
|
105
|
+
/** Tokens for theming and styling. */
|
|
106
|
+
tokens: getTokensPropType('TabBar')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const styles = StyleSheet.create({
|
|
110
|
+
tabBar: {
|
|
111
|
+
flex: 1,
|
|
112
|
+
justifyContent: 'center',
|
|
113
|
+
alignItems: 'center'
|
|
114
|
+
},
|
|
115
|
+
tabBarItem: {
|
|
116
|
+
flex: 1,
|
|
117
|
+
flexDirection: 'row',
|
|
118
|
+
justifyContent: 'space-between',
|
|
119
|
+
width: '100%'
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
export default TabBar
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import { Pressable, View, StyleSheet } from 'react-native'
|
|
4
|
+
import { getTokensPropType, resolvePressableTokens, variantProp } from '../utils'
|
|
5
|
+
import { useThemeTokensCallback } from '../ThemeProvider'
|
|
6
|
+
import Typography from '../Typography'
|
|
7
|
+
import Spacer from '../Spacer'
|
|
8
|
+
|
|
9
|
+
const selectTypographyStyles = (
|
|
10
|
+
{ fontWeight, color, lineHeight, fontName, activeColor },
|
|
11
|
+
isSelected
|
|
12
|
+
) => ({
|
|
13
|
+
fontWeight,
|
|
14
|
+
color: isSelected ? activeColor : color,
|
|
15
|
+
lineHeight,
|
|
16
|
+
fontName
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const selectIconContainerStyles = ({ paddingTop, paddingBottom }) => ({
|
|
20
|
+
paddingTop,
|
|
21
|
+
paddingBottom,
|
|
22
|
+
alignItems: 'center'
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const selectIconStyles = ({ iconSize, iconColor, activeColor }, isSelected) => ({
|
|
26
|
+
size: iconSize,
|
|
27
|
+
color: isSelected ? activeColor : iconColor
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const selectContainerStyles = ({ borderRadius, backgroundColor }) => ({
|
|
31
|
+
borderRadius,
|
|
32
|
+
backgroundColor,
|
|
33
|
+
alignSelf: 'center',
|
|
34
|
+
alignItems: 'center'
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const TabBarItem = React.forwardRef(
|
|
38
|
+
(
|
|
39
|
+
{
|
|
40
|
+
href,
|
|
41
|
+
variant,
|
|
42
|
+
tokens,
|
|
43
|
+
label,
|
|
44
|
+
isSelected,
|
|
45
|
+
id,
|
|
46
|
+
onPress,
|
|
47
|
+
icon: IconComponent,
|
|
48
|
+
iconActive: IconActiveComponent,
|
|
49
|
+
accessibilityRole = 'tab'
|
|
50
|
+
},
|
|
51
|
+
ref
|
|
52
|
+
) => {
|
|
53
|
+
const getTokens = useThemeTokensCallback('TabBarItem', tokens, variant)
|
|
54
|
+
|
|
55
|
+
const getPressableStyle = ({ pressed, focused, hovered }) => {
|
|
56
|
+
const resolvedTokens = resolvePressableTokens(
|
|
57
|
+
getTokens,
|
|
58
|
+
{ pressed, focused, hovered },
|
|
59
|
+
{ isSelected }
|
|
60
|
+
)
|
|
61
|
+
return {
|
|
62
|
+
...resolvedTokens,
|
|
63
|
+
outline: 'none' // removes the default browser :focus outline
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<Pressable
|
|
69
|
+
ref={ref}
|
|
70
|
+
href={href}
|
|
71
|
+
nativeID={id}
|
|
72
|
+
onPress={onPress}
|
|
73
|
+
style={({ pressed, focused, hovered }) => [
|
|
74
|
+
styles.flexContainer,
|
|
75
|
+
getPressableStyle({ pressed, focused, hovered })
|
|
76
|
+
]}
|
|
77
|
+
accessibilityRole={accessibilityRole}
|
|
78
|
+
testID={id}
|
|
79
|
+
>
|
|
80
|
+
{(pressableState) => {
|
|
81
|
+
const resolvedTokens = getPressableStyle(pressableState)
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<View style={selectContainerStyles(resolvedTokens)}>
|
|
85
|
+
<View style={[selectIconContainerStyles(resolvedTokens), styles.iconContainer]}>
|
|
86
|
+
{isSelected
|
|
87
|
+
? IconActiveComponent && (
|
|
88
|
+
<IconActiveComponent {...selectIconStyles(resolvedTokens, isSelected)} />
|
|
89
|
+
)
|
|
90
|
+
: IconComponent && <IconComponent {...selectIconStyles(resolvedTokens)} />}
|
|
91
|
+
</View>
|
|
92
|
+
<Spacer space={1} />
|
|
93
|
+
<Typography
|
|
94
|
+
variant={{ size: 'micro' }}
|
|
95
|
+
tokens={selectTypographyStyles(resolvedTokens, isSelected)}
|
|
96
|
+
align="center"
|
|
97
|
+
>
|
|
98
|
+
{label}
|
|
99
|
+
</Typography>
|
|
100
|
+
<Spacer space={1} />
|
|
101
|
+
</View>
|
|
102
|
+
)
|
|
103
|
+
}}
|
|
104
|
+
</Pressable>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
TabBarItem.displayName = 'TabBarItem'
|
|
110
|
+
|
|
111
|
+
TabBarItem.propTypes = {
|
|
112
|
+
/** The icon to be displayed when the item is not selected. */
|
|
113
|
+
icon: PropTypes.elementType,
|
|
114
|
+
/** The icon to be displayed when the item is selected. */
|
|
115
|
+
iconActive: PropTypes.elementType,
|
|
116
|
+
/** Tokens for theming and styling the TabBarItem. */
|
|
117
|
+
tokens: getTokensPropType('TabBarItem'),
|
|
118
|
+
/** Variant of the TabBarItem for styling purposes. */
|
|
119
|
+
variant: variantProp.propType,
|
|
120
|
+
/** Callback function to handle press events. */
|
|
121
|
+
onPress: PropTypes.func,
|
|
122
|
+
/** URL to navigate to when the item is pressed. */
|
|
123
|
+
href: PropTypes.string,
|
|
124
|
+
/** Indicates whether the item is selected. */
|
|
125
|
+
isSelected: PropTypes.bool,
|
|
126
|
+
/** Unique identifier for the item. */
|
|
127
|
+
id: PropTypes.string,
|
|
128
|
+
/** Label text for the item. */
|
|
129
|
+
label: PropTypes.string.isRequired,
|
|
130
|
+
/** Accessibility role for the item. */
|
|
131
|
+
accessibilityRole: PropTypes.string
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const styles = StyleSheet.create({
|
|
135
|
+
flexContainer: {
|
|
136
|
+
flex: 1,
|
|
137
|
+
alignItems: 'center'
|
|
138
|
+
},
|
|
139
|
+
iconContainer: {
|
|
140
|
+
flex: 1,
|
|
141
|
+
justifyContent: 'center',
|
|
142
|
+
alignItems: 'center',
|
|
143
|
+
minWidth: 44,
|
|
144
|
+
minHeight: 40,
|
|
145
|
+
aspectRatio: 1.1
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
export default TabBarItem
|
|
@@ -61,7 +61,7 @@ const selectInputStyles = (
|
|
|
61
61
|
typeof value === 'number' ? Math.max(0, value - borderWidth) : value
|
|
62
62
|
|
|
63
63
|
const textStyles = applyTextStyles({
|
|
64
|
-
fontName,
|
|
64
|
+
fontName: isPassword ? undefined : fontName, // In this case, we don't want to apply the fontName to the input if it's a password because Monotype don't have support for the masked characters in mobile.
|
|
65
65
|
fontSize,
|
|
66
66
|
lineHeight,
|
|
67
67
|
fontWeight,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
function
|
|
1
|
+
function getAbsolutePosition(position) {
|
|
2
2
|
const { left, right, bottom, top, width, ...rest } = position
|
|
3
3
|
|
|
4
4
|
// adjust the coordinates so that it fits within the window
|
|
5
|
-
const
|
|
5
|
+
const finalPosition = {
|
|
6
6
|
left: Math.max(0, left),
|
|
7
7
|
right: Math.max(0, right),
|
|
8
8
|
top: Math.max(0, top),
|
|
@@ -12,21 +12,20 @@ function normalizePosition(position) {
|
|
|
12
12
|
const getAbsoluteDiff = (value1, value2) => Math.abs(Math.abs(value1) - Math.abs(value2))
|
|
13
13
|
|
|
14
14
|
// adjust the width by whatever has been subtracted from left or right
|
|
15
|
-
|
|
15
|
+
finalPosition.width =
|
|
16
16
|
width -
|
|
17
|
-
Math.abs(
|
|
17
|
+
Math.abs(
|
|
18
|
+
getAbsoluteDiff(left, finalPosition.left) - getAbsoluteDiff(right, finalPosition.right)
|
|
19
|
+
)
|
|
18
20
|
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
+
if (finalPosition.top !== top) {
|
|
22
|
+
finalPosition.bottom += finalPosition.top - top
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
const isNormalized =
|
|
24
|
-
normalized.right !== right || normalized.left !== left || normalized.top !== top
|
|
25
|
-
|
|
26
25
|
return {
|
|
27
|
-
...
|
|
26
|
+
...finalPosition,
|
|
28
27
|
...rest,
|
|
29
|
-
isNormalized
|
|
28
|
+
isNormalized: false
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
31
|
|
|
@@ -151,7 +150,7 @@ function getTooltipPosition(
|
|
|
151
150
|
|
|
152
151
|
// prefer 'below' over 'above', since we can always expand the document downwards,
|
|
153
152
|
// and 'above' might cause issues on small viewports with large tooltips
|
|
154
|
-
return
|
|
153
|
+
return getAbsolutePosition(
|
|
155
154
|
leastOverflowing.position === 'above'
|
|
156
155
|
? findRectByPosition('below', boundingRects)
|
|
157
156
|
: leastOverflowing
|