@telus-uds/components-base 1.14.2 → 1.16.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 +40 -2
- package/__tests17__/A11yText/A11yText.test.jsx +34 -0
- package/__tests17__/ActivityIndicator/ActivityIndicator.test.jsx +68 -0
- package/__tests17__/ActivityIndicator/__snapshots__/ActivityIndicator.test.jsx.snap +299 -0
- package/__tests17__/Box/Box.test.jsx +111 -0
- package/__tests17__/Button/Button.test.jsx +86 -0
- package/__tests17__/Button/ButtonBase.test.jsx +82 -0
- package/__tests17__/Button/ButtonGroup.test.jsx +347 -0
- package/__tests17__/Button/ButtonLink.test.jsx +61 -0
- package/__tests17__/Card/Card.test.jsx +63 -0
- package/__tests17__/Carousel/Carousel.test.jsx +128 -0
- package/__tests17__/Carousel/CarouselTabs.test.jsx +142 -0
- package/__tests17__/Checkbox/Checkbox.test.jsx +94 -0
- package/__tests17__/Checkbox/CheckboxGroup.test.jsx +246 -0
- package/__tests17__/Divider/Divider.test.jsx +91 -0
- package/__tests17__/ExpandCollapse/ExpandCollapse.test.jsx +109 -0
- package/__tests17__/Feedback/Feedback.test.jsx +42 -0
- package/__tests17__/FlexGrid/Col.test.jsx +261 -0
- package/__tests17__/FlexGrid/FlexGrid.test.jsx +136 -0
- package/__tests17__/FlexGrid/Row.test.jsx +273 -0
- package/__tests17__/HorizontalScroll/HorizontalScroll.test.jsx +165 -0
- package/__tests17__/Icon/Icon.test.jsx +61 -0
- package/__tests17__/IconButton/IconButton.test.jsx +52 -0
- package/__tests17__/InputLabel/InputLabel.test.jsx +28 -0
- package/__tests17__/InputLabel/__snapshots__/InputLabel.test.jsx.snap +3 -0
- package/__tests17__/InputSupports/InputSupports.test.jsx +60 -0
- package/__tests17__/Link/Link.test.jsx +63 -0
- package/__tests17__/Link/TextButton.test.jsx +35 -0
- package/__tests17__/List/List.test.jsx +82 -0
- package/__tests17__/Modal/Modal.test.jsx +47 -0
- package/__tests17__/Notification/Notification.test.jsx +20 -0
- package/__tests17__/Pagination/Pagination.test.jsx +160 -0
- package/__tests17__/Progress/Progress.test.jsx +79 -0
- package/__tests17__/Radio/Radio.test.jsx +87 -0
- package/__tests17__/Radio/RadioGroup.test.jsx +220 -0
- package/__tests17__/RadioCard/RadioCard.test.jsx +87 -0
- package/__tests17__/RadioCard/RadioCardGroup.test.jsx +246 -0
- package/__tests17__/Search/Search.test.jsx +87 -0
- package/__tests17__/Select/Select.test.jsx +94 -0
- package/__tests17__/SideNav/SideNav.test.jsx +110 -0
- package/__tests17__/Skeleton/Skeleton.test.jsx +61 -0
- package/__tests17__/SkipLink/SkipLink.test.jsx +61 -0
- package/__tests17__/Spacer/Spacer.test.jsx +63 -0
- package/__tests17__/StackView/StackView.test.jsx +211 -0
- package/__tests17__/StackView/StackWrap.test.jsx +47 -0
- package/__tests17__/StackView/getStackedContent.test.jsx +295 -0
- package/__tests17__/StepTracker/StepTracker.test.jsx +108 -0
- package/__tests17__/Tabs/Tabs.test.jsx +49 -0
- package/__tests17__/Tags/Tags.test.jsx +327 -0
- package/__tests17__/TextInput/TextArea.test.jsx +35 -0
- package/__tests17__/TextInput/TextInputBase.test.jsx +125 -0
- package/__tests17__/ThemeProvider/ThemeProvider.test.jsx +80 -0
- package/__tests17__/ThemeProvider/useThemeTokens.test.jsx +514 -0
- package/__tests17__/ThemeProvider/utils/theme-tokens.test.js +41 -0
- package/__tests17__/ToggleSwitch/ToggleSwitch.test.jsx +82 -0
- package/__tests17__/ToggleSwitch/ToggleSwitchGroup.test.jsx +192 -0
- package/__tests17__/Tooltip/Tooltip.test.jsx +65 -0
- package/__tests17__/Tooltip/getTooltipPosition.test.js +79 -0
- package/__tests17__/Typography/typography.test.jsx +90 -0
- package/__tests17__/utils/children.test.jsx +128 -0
- package/__tests17__/utils/containUniqueFields.test.js +25 -0
- package/__tests17__/utils/input.test.js +375 -0
- package/__tests17__/utils/props.test.js +36 -0
- package/__tests17__/utils/semantics.test.jsx +34 -0
- package/__tests17__/utils/useCopy.test.js +42 -0
- package/__tests17__/utils/useResponsiveProp.test.jsx +202 -0
- package/__tests17__/utils/useSpacingScale.test.jsx +273 -0
- package/__tests17__/utils/useUniqueId.test.js +31 -0
- package/component-docs.json +120 -85
- package/lib/A11yInfoProvider/index.js +14 -5
- package/lib/Button/ButtonGroup.js +3 -2
- package/lib/Carousel/Carousel.js +18 -2
- package/lib/Carousel/CarouselTabs/CarouselTabs.js +6 -7
- package/lib/Checkbox/Checkbox.js +9 -6
- package/lib/ExpandCollapse/Control.js +6 -5
- package/lib/ExpandCollapse/Panel.js +5 -4
- package/lib/List/ListItem.js +10 -236
- package/lib/List/ListItemBase.js +162 -0
- package/lib/List/ListItemContent.js +85 -0
- package/lib/List/ListItemMark.js +158 -0
- package/lib/List/PressableListItemBase.js +147 -0
- package/lib/Notification/Notification.js +2 -1
- package/lib/Pagination/Pagination.js +4 -3
- package/lib/Radio/Radio.js +9 -6
- package/lib/RadioCard/RadioCard.js +9 -6
- package/lib/Select/Select.js +1 -0
- package/lib/Skeleton/Skeleton.js +18 -13
- package/lib/Skeleton/useSkeletonNativeAnimation.js +4 -2
- package/lib/Tabs/Tabs.js +12 -3
- package/lib/Tags/Tags.js +3 -3
- package/lib/TextInput/TextInput.js +5 -4
- package/lib/ToggleSwitch/ToggleSwitch.js +24 -19
- package/lib/ViewportProvider/useViewportListener.js +11 -5
- package/lib/utils/hasOwnProperty.js +18 -0
- package/lib/utils/props/a11yProps.js +171 -1
- package/lib/utils/props/getPropSelector.js +47 -5
- package/lib/utils/ssr.js +116 -1
- package/lib/utils/useResponsiveProp.js +5 -3
- package/lib/utils/withLinkRouter.js +3 -5
- package/lib-module/A11yInfoProvider/index.js +14 -4
- package/lib-module/Button/ButtonGroup.js +3 -2
- package/lib-module/Carousel/Carousel.js +16 -2
- package/lib-module/Carousel/CarouselTabs/CarouselTabs.js +7 -6
- package/lib-module/Checkbox/Checkbox.js +9 -6
- package/lib-module/ExpandCollapse/Control.js +6 -5
- package/lib-module/ExpandCollapse/Panel.js +5 -4
- package/lib-module/List/ListItem.js +13 -235
- package/lib-module/List/ListItemBase.js +139 -0
- package/lib-module/List/ListItemContent.js +66 -0
- package/lib-module/List/ListItemMark.js +143 -0
- package/lib-module/List/PressableListItemBase.js +117 -0
- package/lib-module/Notification/Notification.js +2 -1
- package/lib-module/Pagination/Pagination.js +5 -3
- package/lib-module/Radio/Radio.js +9 -6
- package/lib-module/RadioCard/RadioCard.js +9 -6
- package/lib-module/Select/Select.js +1 -0
- package/lib-module/Skeleton/Skeleton.js +15 -13
- package/lib-module/Skeleton/useSkeletonNativeAnimation.js +3 -2
- package/lib-module/Tabs/Tabs.js +13 -4
- package/lib-module/Tags/Tags.js +3 -3
- package/lib-module/TextInput/TextInput.js +5 -4
- package/lib-module/ToggleSwitch/ToggleSwitch.js +24 -19
- package/lib-module/ViewportProvider/useViewportListener.js +10 -4
- package/lib-module/utils/hasOwnProperty.js +11 -0
- package/lib-module/utils/props/a11yProps.js +169 -1
- package/lib-module/utils/props/getPropSelector.js +44 -5
- package/lib-module/utils/ssr.js +106 -0
- package/lib-module/utils/useResponsiveProp.js +3 -4
- package/lib-module/utils/withLinkRouter.js +3 -5
- package/package.json +12 -17
- package/src/A11yInfoProvider/index.jsx +20 -4
- package/src/Button/ButtonGroup.jsx +4 -2
- package/src/Carousel/Carousel.jsx +15 -2
- package/src/Carousel/CarouselTabs/CarouselTabs.jsx +5 -3
- package/src/Checkbox/Checkbox.jsx +7 -3
- package/src/ExpandCollapse/Control.jsx +8 -5
- package/src/ExpandCollapse/Panel.jsx +7 -5
- package/src/List/ListItem.jsx +12 -191
- package/src/List/ListItemBase.jsx +118 -0
- package/src/List/ListItemContent.jsx +52 -0
- package/src/List/ListItemMark.jsx +99 -0
- package/src/List/PressableListItemBase.jsx +102 -0
- package/src/Notification/Notification.jsx +1 -1
- package/src/Pagination/Pagination.jsx +6 -1
- package/src/Radio/Radio.jsx +7 -3
- package/src/RadioCard/RadioCard.jsx +7 -3
- package/src/Select/Select.jsx +1 -1
- package/src/Skeleton/Skeleton.jsx +25 -19
- package/src/Skeleton/useSkeletonNativeAnimation.js +3 -3
- package/src/Tabs/Tabs.jsx +19 -2
- package/src/Tags/Tags.jsx +3 -3
- package/src/TextInput/TextInput.jsx +4 -4
- package/src/ToggleSwitch/ToggleSwitch.jsx +3 -3
- package/src/ViewportProvider/useViewportListener.js +10 -5
- package/src/utils/hasOwnProperty.js +11 -0
- package/src/utils/props/a11yProps.js +107 -1
- package/src/utils/props/getPropSelector.js +45 -4
- package/src/utils/ssr.jsx +124 -0
- package/src/utils/useResponsiveProp.js +3 -3
- package/src/utils/withLinkRouter.jsx +1 -3
- package/src/utils/ssr.js +0 -35
|
@@ -98,6 +98,12 @@ const RadioCard = forwardRef(
|
|
|
98
98
|
const getCardTokens = (cardState) => selectPressableCardTokens(getTokens(cardState))
|
|
99
99
|
const { themeOptions } = useTheme()
|
|
100
100
|
|
|
101
|
+
const selectedProps = selectProps({
|
|
102
|
+
accessibilityRole: 'radio',
|
|
103
|
+
accessibilityState: { checked: isChecked, disabled: inactive },
|
|
104
|
+
...rest
|
|
105
|
+
})
|
|
106
|
+
|
|
101
107
|
return (
|
|
102
108
|
<PressableCardBase
|
|
103
109
|
ref={ref}
|
|
@@ -105,9 +111,7 @@ const RadioCard = forwardRef(
|
|
|
105
111
|
checked={isChecked}
|
|
106
112
|
tokens={getCardTokens}
|
|
107
113
|
onPress={handleChange}
|
|
108
|
-
|
|
109
|
-
accessibilityState={{ checked: isChecked, disabled: inactive }}
|
|
110
|
-
{...selectProps(rest)}
|
|
114
|
+
{...selectedProps}
|
|
111
115
|
>
|
|
112
116
|
{(cardState) => {
|
|
113
117
|
const { radioSpace, contentSpace, ...themeTokens } = getTokens(cardState)
|
package/src/Select/Select.jsx
CHANGED
|
@@ -215,7 +215,7 @@ const Select = forwardRef(
|
|
|
215
215
|
const { themeOptions } = useTheme()
|
|
216
216
|
|
|
217
217
|
return (
|
|
218
|
-
<InputSupports {...supportsProps} {...selectedProps}>
|
|
218
|
+
<InputSupports {...supportsProps} {...selectedProps} validation={validation}>
|
|
219
219
|
{({ inputId, ...props }) => (
|
|
220
220
|
<View style={selectOuterBorderStyles(themeTokens)}>
|
|
221
221
|
<Picker
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { forwardRef } from 'react'
|
|
2
|
-
import { Animated, Platform } from 'react-native'
|
|
2
|
+
import { Animated, Platform, StyleSheet, View } from 'react-native'
|
|
3
3
|
import propTypes from 'prop-types'
|
|
4
4
|
import StackView from '../StackView'
|
|
5
5
|
import { useThemeTokens } from '../ThemeProvider'
|
|
@@ -19,11 +19,10 @@ import skeletonWebAnimation from './skeletonWebAnimation'
|
|
|
19
19
|
|
|
20
20
|
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
|
|
21
21
|
|
|
22
|
-
const selectSkeletonStyles = ({ color, radius
|
|
22
|
+
const selectSkeletonStyles = ({ color, radius }) => ({
|
|
23
23
|
backgroundColor: color,
|
|
24
24
|
borderRadius: radius,
|
|
25
|
-
maxWidth: '100%'
|
|
26
|
-
...fadeAnimation
|
|
25
|
+
maxWidth: '100%'
|
|
27
26
|
})
|
|
28
27
|
|
|
29
28
|
const selectLineStyles = ({ skeletonHeight, lineWidth }) => ({
|
|
@@ -66,12 +65,13 @@ const Skeleton = forwardRef(
|
|
|
66
65
|
const skeletonHeight = useSpacingScale(spacingScaleValue)
|
|
67
66
|
const nativeAnimation = useSkeletonNativeAnimation()
|
|
68
67
|
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
const getAnimationBasedOnPlatform = () => {
|
|
69
|
+
const animation = Platform.OS === 'web' ? skeletonWebAnimation : nativeAnimation
|
|
70
|
+
// We must pass the animation styles through `StyleSheet.create`
|
|
71
|
+
// @see https://github.com/necolas/react-native-web/issues/2387
|
|
72
|
+
const styles = StyleSheet.create({ animation })
|
|
73
73
|
|
|
74
|
-
return
|
|
74
|
+
return styles.animation
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
const getLineWidth = () => {
|
|
@@ -97,16 +97,22 @@ const Skeleton = forwardRef(
|
|
|
97
97
|
return selectLineStyles({ skeletonHeight, lineWidth: getLineWidth() })
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
const renderSkeleton = (index = 0) =>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
const renderSkeleton = (index = 0) => {
|
|
101
|
+
// @see https://github.com/necolas/react-native-web/issues/2387
|
|
102
|
+
const Component = Platform.OS === 'web' ? View : Animated.View
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<Component
|
|
106
|
+
testID="skeleton"
|
|
107
|
+
key={`skeleton-${index + 1}`}
|
|
108
|
+
style={[
|
|
109
|
+
selectSkeletonStyles(themeTokens),
|
|
110
|
+
getAnimationBasedOnPlatform(),
|
|
111
|
+
getStyledBasedOnShape()
|
|
112
|
+
]}
|
|
113
|
+
/>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
110
116
|
|
|
111
117
|
return (
|
|
112
118
|
<StackView space={themeTokens.spaceBetweenLines} ref={ref} {...selectProps(rest)}>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react'
|
|
2
|
-
import { Animated } from 'react-native'
|
|
2
|
+
import { Animated, Platform } from 'react-native'
|
|
3
3
|
import { ANIMATION_DURATION, DEFAULT_OPACITY, OPACITY_STOP } from './skeleton.constant'
|
|
4
4
|
|
|
5
5
|
const useSkeletonNativeAnimation = () => {
|
|
@@ -10,12 +10,12 @@ const useSkeletonNativeAnimation = () => {
|
|
|
10
10
|
Animated.timing(fadeAnimation, {
|
|
11
11
|
toValue: OPACITY_STOP,
|
|
12
12
|
duration: ANIMATION_DURATION,
|
|
13
|
-
useNativeDriver:
|
|
13
|
+
useNativeDriver: Platform.OS !== 'web'
|
|
14
14
|
}),
|
|
15
15
|
Animated.timing(fadeAnimation, {
|
|
16
16
|
toValue: DEFAULT_OPACITY,
|
|
17
17
|
duration: ANIMATION_DURATION,
|
|
18
|
-
useNativeDriver:
|
|
18
|
+
useNativeDriver: Platform.OS !== 'web'
|
|
19
19
|
})
|
|
20
20
|
])
|
|
21
21
|
Animated.loop(fade).start()
|
package/src/Tabs/Tabs.jsx
CHANGED
|
@@ -6,6 +6,8 @@ import StackView from '../StackView'
|
|
|
6
6
|
import {
|
|
7
7
|
a11yProps,
|
|
8
8
|
getTokensPropType,
|
|
9
|
+
focusHandlerProps,
|
|
10
|
+
pressProps,
|
|
9
11
|
selectSystemProps,
|
|
10
12
|
useHash,
|
|
11
13
|
useInputValue,
|
|
@@ -20,6 +22,12 @@ import HorizontalScroll, {
|
|
|
20
22
|
import TabsItem from './TabsItem'
|
|
21
23
|
|
|
22
24
|
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
|
|
25
|
+
const [selectItemProps, selectedItemPropTypes] = selectSystemProps([
|
|
26
|
+
a11yProps,
|
|
27
|
+
focusHandlerProps,
|
|
28
|
+
pressProps,
|
|
29
|
+
viewProps
|
|
30
|
+
])
|
|
23
31
|
|
|
24
32
|
const { selectHorizontalScrollTokens, useItemPositions } = horizontalScrollUtils
|
|
25
33
|
|
|
@@ -99,15 +107,22 @@ const Tabs = forwardRef(
|
|
|
99
107
|
label,
|
|
100
108
|
id,
|
|
101
109
|
accessibilityRole = defaultTabItemAccessibiltyRole,
|
|
110
|
+
onPress,
|
|
102
111
|
ref: itemRef,
|
|
103
112
|
LinkRouter: ItemLinkRouter = LinkRouter,
|
|
104
|
-
linkRouterProps: itemLinkRouterProps
|
|
113
|
+
linkRouterProps: itemLinkRouterProps,
|
|
114
|
+
...itemRest
|
|
105
115
|
},
|
|
106
116
|
index
|
|
107
117
|
) => {
|
|
108
118
|
const itemId = id ?? label
|
|
109
119
|
const isSelected = Boolean(currentValue && currentValue === itemId)
|
|
110
|
-
const handlePress = (event) =>
|
|
120
|
+
const handlePress = (event) => {
|
|
121
|
+
if (typeof onPress === 'function') onPress(event)
|
|
122
|
+
setValue(itemId, event)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const itemProps = selectItemProps(itemRest)
|
|
111
126
|
return (
|
|
112
127
|
<TabsItem
|
|
113
128
|
ref={itemRef}
|
|
@@ -122,6 +137,7 @@ const Tabs = forwardRef(
|
|
|
122
137
|
accessibilityRole={accessibilityRole}
|
|
123
138
|
LinkRouter={ItemLinkRouter}
|
|
124
139
|
linkRouterProps={{ ...linkRouterProps, ...itemLinkRouterProps }}
|
|
140
|
+
{...itemProps}
|
|
125
141
|
>
|
|
126
142
|
{label}
|
|
127
143
|
</TabsItem>
|
|
@@ -140,6 +156,7 @@ Tabs.propTypes = {
|
|
|
140
156
|
...withLinkRouter.PropTypes,
|
|
141
157
|
items: PropTypes.arrayOf(
|
|
142
158
|
PropTypes.shape({
|
|
159
|
+
...selectedItemPropTypes,
|
|
143
160
|
...withLinkRouter.PropTypes,
|
|
144
161
|
href: PropTypes.string,
|
|
145
162
|
label: PropTypes.string,
|
package/src/Tags/Tags.jsx
CHANGED
|
@@ -127,12 +127,12 @@ const Tags = forwardRef(
|
|
|
127
127
|
toggleOneValue(id, event)
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
const itemProps = {
|
|
130
|
+
const itemProps = selectItemProps({
|
|
131
131
|
accessibilityState: { checked: isSelected },
|
|
132
132
|
accessibilityRole: itemA11yRole,
|
|
133
133
|
...a11yProps.getPositionInSet(items.length, index),
|
|
134
|
-
...
|
|
135
|
-
}
|
|
134
|
+
...itemRest
|
|
135
|
+
})
|
|
136
136
|
|
|
137
137
|
return (
|
|
138
138
|
<ButtonBase
|
|
@@ -41,7 +41,7 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([
|
|
|
41
41
|
* supported props and <a href="https://reactnative.dev/docs/textinput" target="_blank">React Native Web documentation</a> for
|
|
42
42
|
* their implementation on the web.
|
|
43
43
|
*/
|
|
44
|
-
const TextInput = forwardRef(({ tokens, variant = {}, ...rest }, ref) => {
|
|
44
|
+
const TextInput = forwardRef(({ tokens, nativeID, variant = {}, ...rest }, ref) => {
|
|
45
45
|
const { supportsProps, ...selectedProps } = selectProps(rest)
|
|
46
46
|
|
|
47
47
|
const inputProps = {
|
|
@@ -51,9 +51,9 @@ const TextInput = forwardRef(({ tokens, variant = {}, ...rest }, ref) => {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
return (
|
|
54
|
-
<InputSupports nativeID={
|
|
55
|
-
{({ inputId, ...
|
|
56
|
-
<TextInputBase ref={ref}
|
|
54
|
+
<InputSupports nativeID={nativeID} {...supportsProps}>
|
|
55
|
+
{({ inputId, ...propsFromInputSupports }) => (
|
|
56
|
+
<TextInputBase ref={ref} nativeID={inputId} {...propsFromInputSupports} {...inputProps} />
|
|
57
57
|
)}
|
|
58
58
|
</InputSupports>
|
|
59
59
|
)
|
|
@@ -26,7 +26,9 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([
|
|
|
26
26
|
viewProps
|
|
27
27
|
])
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
// We need to drop the icon before passing it to the `ButtonBase`, because it's
|
|
30
|
+
// being handled separately in this case
|
|
31
|
+
const selectButtonTokens = ({ icon: _, ...tokens }) =>
|
|
30
32
|
selectTokens('Button', {
|
|
31
33
|
...tokens,
|
|
32
34
|
// Width tokens are applied to our inner track. Disable Button width token so it wraps our track width.
|
|
@@ -215,8 +217,6 @@ ToggleSwitch.propTypes = {
|
|
|
215
217
|
|
|
216
218
|
const staticStyles = StyleSheet.create({
|
|
217
219
|
track: {
|
|
218
|
-
flexGrow: 1,
|
|
219
|
-
alignSelf: 'stretch',
|
|
220
220
|
flexDirection: 'row'
|
|
221
221
|
},
|
|
222
222
|
switch: {
|
|
@@ -28,11 +28,16 @@ const useViewportListenerCSR = (setViewport) => {
|
|
|
28
28
|
const onChange = ({ window }) => setViewport(viewports.select(window.width))
|
|
29
29
|
const listener = Dimensions.addEventListener('change', onChange)
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
return () => {
|
|
32
|
+
if (typeof listener?.remove === 'function') {
|
|
33
|
+
// Can't just return listener.remove because listener.emitter disappears, causing an internal error.
|
|
34
|
+
// See https://github.com/facebook/react-native/issues/34508
|
|
35
|
+
listener.remove()
|
|
36
|
+
// From RN 0.65.0, Dimensions.removeEventListener is deprecated for `remove` on addEventListener return value
|
|
37
|
+
} else if (typeof Dimensions.removeEventListener === 'function') {
|
|
38
|
+
Dimensions.removeEventListener('change', onChange)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
36
41
|
}, [setViewport])
|
|
37
42
|
}
|
|
38
43
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linter disallows object instance prototype methods like someObject.hasOwnProperty(key),
|
|
3
|
+
* but we can use this instead.
|
|
4
|
+
*
|
|
5
|
+
* @param {object} object
|
|
6
|
+
* @param {String} key
|
|
7
|
+
* @returns {Boolean}
|
|
8
|
+
*/
|
|
9
|
+
export default function hasOwnProperty(object, key) {
|
|
10
|
+
return Object.prototype.hasOwnProperty.call(object, key)
|
|
11
|
+
}
|
|
@@ -98,6 +98,101 @@ const a11yPropTypes = {
|
|
|
98
98
|
accessibilityValueText: PropTypes.string
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
const a11yPropTypesByPlatform = Platform.select({
|
|
102
|
+
// React Native Web adds many a11y props that alias aria-* attributes
|
|
103
|
+
// Types based on https://necolas.github.io/react-native-web/docs/accessibility/
|
|
104
|
+
web: a11yPropTypes,
|
|
105
|
+
// Ignore web-only props in native builds
|
|
106
|
+
default: nativeA11yPropTypes
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// These RNW-only props only exist in RNW >=0.18. Catch them and map them according to platform
|
|
110
|
+
// so all props work on RN, RNW >=0.18 and RNW <=0.18, regardless of which they were written for:
|
|
111
|
+
// - On native, bundle them into objects, like `accessibilityValue: { max: 100 }`
|
|
112
|
+
// - On web, split them into both of:
|
|
113
|
+
// - The appropriate aria-* attr, like `aria-valuenow`, which will work regardless of RNW version
|
|
114
|
+
// - The corresponding RNW >=0.18 prop, like `accessibilityValueNow`, which in some cases does more
|
|
115
|
+
// than just add the aria-* (e.g. `accessibilityDisabled` adds `disabled` if element supports it,
|
|
116
|
+
// and future releases might add more features here).
|
|
117
|
+
const rwnPropMappings = {
|
|
118
|
+
// Former accessibilityValue props.
|
|
119
|
+
accessibilityValueMax: (value) =>
|
|
120
|
+
Platform.select({
|
|
121
|
+
web: { 'aria-valuemax': value, accessibilityValueMax: value },
|
|
122
|
+
default: { accessibilityValue: { max: value } }
|
|
123
|
+
}),
|
|
124
|
+
accessibilityValueMin: (value) =>
|
|
125
|
+
Platform.select({
|
|
126
|
+
web: { 'aria-valuemin': value, accessibilityValueMin: value },
|
|
127
|
+
default: { accessibilityValue: { min: value } }
|
|
128
|
+
}),
|
|
129
|
+
accessibilityValueNow: (value) =>
|
|
130
|
+
Platform.select({
|
|
131
|
+
web: { 'aria-valuenow': value, accessibilityValueNow: value },
|
|
132
|
+
default: { accessibilityValue: { now: value } }
|
|
133
|
+
}),
|
|
134
|
+
accessibilityValueText: (value) =>
|
|
135
|
+
Platform.select({
|
|
136
|
+
web: { 'aria-valuetext': value, accessibilityValueText: value },
|
|
137
|
+
default: { accessibilityValue: { text: value } }
|
|
138
|
+
}),
|
|
139
|
+
|
|
140
|
+
// Former accessibilityState props
|
|
141
|
+
accessibilityBusy: (value) =>
|
|
142
|
+
Platform.select({
|
|
143
|
+
web: { 'aria-busy': value, accessibilityBusy: value },
|
|
144
|
+
default: { accessibilityState: { busy: value } }
|
|
145
|
+
}),
|
|
146
|
+
accessibilityChecked: (value) =>
|
|
147
|
+
Platform.select({
|
|
148
|
+
web: { 'aria-checked': value, accessibilityChecked: value },
|
|
149
|
+
default: { accessibilityState: { checked: value } }
|
|
150
|
+
}),
|
|
151
|
+
accessibilityDisabled: (value) =>
|
|
152
|
+
Platform.select({
|
|
153
|
+
web: {
|
|
154
|
+
'aria-disabled': value,
|
|
155
|
+
// RNW >= 0.18 maps `accessibilityDisabled` to `disabled` attr if element supports it
|
|
156
|
+
accessibilityDisabled: value,
|
|
157
|
+
// As of RNW 0.18.9, Pressable doesn't support `accessibilityDisabled`, only `disabled`,
|
|
158
|
+
// but everything else supports `accessibilityDisabled` but not `disabled`.
|
|
159
|
+
disabled: value
|
|
160
|
+
},
|
|
161
|
+
default: { accessibilityState: { disabled: value } }
|
|
162
|
+
}),
|
|
163
|
+
accessibilityExpanded: (value) =>
|
|
164
|
+
Platform.select({
|
|
165
|
+
web: { 'aria-expanded': value, accessibilityExpanded: value },
|
|
166
|
+
default: { accessibilityState: { expanded: value } }
|
|
167
|
+
}),
|
|
168
|
+
accessibilitySelected: (value) =>
|
|
169
|
+
Platform.select({
|
|
170
|
+
web: { 'aria-selected': value, accessibilitySelected: value },
|
|
171
|
+
default: { accessibilityState: { selected: value } }
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
if (Platform.OS === 'web') {
|
|
175
|
+
const mapIfDefined = (value, fn) => (value === undefined ? undefined : fn(value))
|
|
176
|
+
|
|
177
|
+
// On Web only, these React Native object props need manual mapping in RNW >=0.18
|
|
178
|
+
// which dropped support for the React Native shape of these props.
|
|
179
|
+
// Re-use our RNW 0.18 prop mappings to support both RNW <0.18 (aria-*) and
|
|
180
|
+
// new features added in >=0.18 (e.g. for accessibilityDisabled).
|
|
181
|
+
rwnPropMappings.accessibilityValue = ({ max, min, now, text } = {}) => ({
|
|
182
|
+
...mapIfDefined(max, rwnPropMappings.accessibilityValueMax),
|
|
183
|
+
...mapIfDefined(min, rwnPropMappings.accessibilityValueMin),
|
|
184
|
+
...mapIfDefined(now, rwnPropMappings.accessibilityValueNow),
|
|
185
|
+
...mapIfDefined(text, rwnPropMappings.accessibilityValueText)
|
|
186
|
+
})
|
|
187
|
+
rwnPropMappings.accessibilityState = ({ busy, checked, disabled, expanded, selected } = {}) => ({
|
|
188
|
+
...mapIfDefined(busy, rwnPropMappings.accessibilityBusy),
|
|
189
|
+
...mapIfDefined(checked, rwnPropMappings.accessibilityChecked),
|
|
190
|
+
...mapIfDefined(disabled, rwnPropMappings.accessibilityDisabled),
|
|
191
|
+
...mapIfDefined(expanded, rwnPropMappings.accessibilityExpanded),
|
|
192
|
+
...mapIfDefined(selected, rwnPropMappings.accessibilitySelected)
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
101
196
|
export default {
|
|
102
197
|
/**
|
|
103
198
|
* Proptypes for recognised React Native accessiblity (a11y) props.
|
|
@@ -110,7 +205,18 @@ export default {
|
|
|
110
205
|
* Where components accept React Native a11y props, pass { ...rest } from its props to this,
|
|
111
206
|
* then spread the returned object into the component's props (usually its outer container).
|
|
112
207
|
*/
|
|
113
|
-
select: getPropSelector(
|
|
208
|
+
select: getPropSelector(
|
|
209
|
+
// Allow all React Native accessibility props
|
|
210
|
+
a11yPropTypesByPlatform,
|
|
211
|
+
// Allow any `aria-*` attribute on web; ignore them on native
|
|
212
|
+
Platform.OS === 'web' && /^aria-/,
|
|
213
|
+
// For the props added and deprecated in React Native Web 0.18, convert them to
|
|
214
|
+
// a form that is platform-appropriate and RNW-version safe
|
|
215
|
+
(key, value) => {
|
|
216
|
+
const rnwPropMapper = rwnPropMappings[key]
|
|
217
|
+
return rnwPropMapper ? rnwPropMapper(value) : undefined
|
|
218
|
+
}
|
|
219
|
+
),
|
|
114
220
|
/**
|
|
115
221
|
* Use this to disable focus for elements which are visually hidden but still rendered.
|
|
116
222
|
*/
|
|
@@ -1,14 +1,55 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import merge from 'lodash.merge'
|
|
2
|
+
import hasOwnProperty from '../hasOwnProperty'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @callback PropSelectorCallback - a callback called for each prop passed to a component
|
|
6
|
+
* @param {string} key - the key for the prop to be tested
|
|
7
|
+
* @param {*} value - the value of the prop being passed in to the component
|
|
8
|
+
* @returns {object|undefined}
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {PropSelectorCallback} callback
|
|
13
|
+
* @param {object} items
|
|
14
|
+
* @param {string} key
|
|
15
|
+
* @param {*} value
|
|
16
|
+
* @returns {object|undefined}
|
|
17
|
+
*/
|
|
18
|
+
const applyCallback = (callback, items, key, value) => {
|
|
19
|
+
// If there's no callback, continue and look up keys as normal
|
|
20
|
+
if (typeof callback !== 'function') return undefined
|
|
21
|
+
|
|
22
|
+
const newItems = callback(key, value)
|
|
23
|
+
|
|
24
|
+
// If the callback doesn't return anything, continue and look up keys as normal
|
|
25
|
+
if (!newItems) return undefined
|
|
26
|
+
|
|
27
|
+
// If the callback returns items, merge them in, deep merging props that are objects
|
|
28
|
+
return merge({}, items, newItems)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Generates a function to filter an object of props down to a subset of allowed props, with
|
|
33
|
+
* optional prop alteration and re-mapping via an optional callback.
|
|
34
|
+
*
|
|
35
|
+
* @param {object} propTypes - an object where every defined key is a valid prop
|
|
36
|
+
* @param {*} [regexp] - an optional regular expression where any match is a valid prop
|
|
37
|
+
* @param {PropSelectorCallback} callback - optional function taking `(key, value)` returning either undefined or an object of new props to merge in
|
|
38
|
+
* @returns {object} - valid props for this component
|
|
39
|
+
*/
|
|
40
|
+
export default function getPropSelector(propTypes, regexp, callback) {
|
|
3
41
|
return (props) =>
|
|
4
42
|
Object.entries(props).reduce(
|
|
5
43
|
(items, [key, value]) =>
|
|
6
|
-
|
|
44
|
+
// If there's a callback and it matches something, applyCallback merges it in; return that
|
|
45
|
+
applyCallback(callback, items, key, value) ||
|
|
46
|
+
// If there's no callback match, check if this prop is valid and merge it in if it is
|
|
47
|
+
(hasOwnProperty(propTypes, key) || (regexp && regexp.test(key))
|
|
7
48
|
? {
|
|
8
49
|
...items,
|
|
9
50
|
[key]: value
|
|
10
51
|
}
|
|
11
|
-
: items,
|
|
52
|
+
: items),
|
|
12
53
|
{}
|
|
13
54
|
)
|
|
14
55
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { AppRegistry } from 'react-native'
|
|
3
|
+
/** @typedef {import('react').ComponentType} ReactComponent */
|
|
4
|
+
/** @typedef {import('react').ReactElement} ReactElement */
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns object with `renderApp` and `getStyles` functions.
|
|
8
|
+
* Weave these into your app's server-side render process:
|
|
9
|
+
*
|
|
10
|
+
* - Call `renderApp` first to do the actual server-side render
|
|
11
|
+
* - After the render is complete, call `getStyles`
|
|
12
|
+
* - Include the style tags returned by `getStyles` in the SSR <head>
|
|
13
|
+
*
|
|
14
|
+
* @param {string} [appName] - optional unique identifier if ssrStyles is called multiple times for multiple apps
|
|
15
|
+
* @param {object} [options] -
|
|
16
|
+
* - `styleGetters`: optional array of additional style getter functions to call after render
|
|
17
|
+
* - `collectStyles`: optional function, takes the rendered app, returns either the same or a new app element
|
|
18
|
+
* @param {boolean} [options.styleGetters]
|
|
19
|
+
* @param {(ReactElement) => ReactElement} [options.collectStyles]
|
|
20
|
+
*/
|
|
21
|
+
export const ssrStyles = (appName = 'UDS app', { styleGetters = [], collectStyles } = {}) => {
|
|
22
|
+
let hasAppRendered = false
|
|
23
|
+
return {
|
|
24
|
+
/**
|
|
25
|
+
* Server-side-renders the provided app in a way that supports collecting
|
|
26
|
+
* styles for Styled Components and React Native Web stylesheets.
|
|
27
|
+
*
|
|
28
|
+
* @param {ReactComponent} App - the root component for the app
|
|
29
|
+
* @param {object} [props] - props for this render e.g. page routing props
|
|
30
|
+
* @param {object} [options] -
|
|
31
|
+
* - `renderedByRNW`: pass as true if the main render is by AppRegistry.runApplication from React Native
|
|
32
|
+
* - `WrapperComponent`: Component rendered with no props around app content and inside root tag
|
|
33
|
+
* @param {boolean} [options.renderedByRNW]
|
|
34
|
+
* @param {ReactComponent} [options.WrapperComponent]
|
|
35
|
+
*
|
|
36
|
+
* @returns {ReactElement} - the rendered app output
|
|
37
|
+
*/
|
|
38
|
+
renderApp: (App, props, { renderedByRNW = false, WrapperComponent } = {}) => {
|
|
39
|
+
AppRegistry.registerComponent(appName, () => App)
|
|
40
|
+
// AppRegistry.getApplication renders the app in a container, and collects styles.
|
|
41
|
+
const { element, getStyleElement } = AppRegistry.getApplication(appName, {
|
|
42
|
+
WrapperComponent,
|
|
43
|
+
initialProps: props
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
let renderedApp =
|
|
47
|
+
// React Native Web's AppRegistry.getApplication assumes the app is rendered using
|
|
48
|
+
// AppRegistry.runApplication and wraps it in <AppContainer>, which wraps the entire app
|
|
49
|
+
// in two outer containers resembling <View style={{ flex: 1 }} pointerEvents="box-none">.
|
|
50
|
+
// So, use that IF user says AppRegistry.runApplication will do the client-side render.
|
|
51
|
+
(renderedByRNW && element) ||
|
|
52
|
+
// If the live app is not rendered using AppRegistry.runApplication, we need to
|
|
53
|
+
// re-render it without the <AppContainer> wrapper, to avoid SSR mismatch errors.
|
|
54
|
+
// Default to this as many platforms (e.g. NextJS) will use their own renderers.
|
|
55
|
+
WrapperComponent ? (
|
|
56
|
+
<WrapperComponent>
|
|
57
|
+
<App {...props} />
|
|
58
|
+
</WrapperComponent>
|
|
59
|
+
) : (
|
|
60
|
+
<App {...props} />
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
const getRNWStyle = () => getStyleElement({ key: 'react-native-stylesheet' })
|
|
64
|
+
styleGetters.push(getRNWStyle)
|
|
65
|
+
|
|
66
|
+
if (typeof collectStyles === 'function') {
|
|
67
|
+
renderedApp = collectStyles(renderedApp)
|
|
68
|
+
}
|
|
69
|
+
hasAppRendered = true
|
|
70
|
+
return renderedApp
|
|
71
|
+
},
|
|
72
|
+
/**
|
|
73
|
+
* Turns styles collected during renderApp into an array of React elements of
|
|
74
|
+
* HTML <style> tags ready for insertion into the SSR HTML's <head>.
|
|
75
|
+
*
|
|
76
|
+
* Must be called after `renderApp` has completed.
|
|
77
|
+
*
|
|
78
|
+
* @param {...ReactElement} existingStyles - any existing style tag elements to merge in
|
|
79
|
+
* @returns {ReactElement[]} - flat array of <style> React elements
|
|
80
|
+
*/
|
|
81
|
+
getStyles: (...existingStyles) => {
|
|
82
|
+
if (!hasAppRendered) throw new Error('Called getStyles before renderApp in ssrStyles')
|
|
83
|
+
return [...existingStyles, ...styleGetters.flatMap((getter) => getter())]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default ssrStyles
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @deprecated - use ssrStyles instead
|
|
92
|
+
*
|
|
93
|
+
* Registers the app's root component with React Native Web and generates
|
|
94
|
+
* the main <style> tag containing React Native Web stylesheet styles.
|
|
95
|
+
*
|
|
96
|
+
* @param {ReactComponent} AppRoot
|
|
97
|
+
* @param {string} [appName]
|
|
98
|
+
* @returns {ReactElement[]}
|
|
99
|
+
*/
|
|
100
|
+
|
|
101
|
+
export const getReactNativeWebSSRStyles = (AppRoot, appName = 'app') => {
|
|
102
|
+
AppRegistry.registerComponent(appName, () => AppRoot)
|
|
103
|
+
const { getStyleElement } = AppRegistry.getApplication(appName)
|
|
104
|
+
return [getStyleElement()]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @deprecated - use ssrStyles instead
|
|
109
|
+
*
|
|
110
|
+
* Gets style tags for each currently supported CSS-in-JS library and returns
|
|
111
|
+
* them alongside any existing style tags.
|
|
112
|
+
*
|
|
113
|
+
* @param {ReactComponent} AppRoot
|
|
114
|
+
* @param {string} [appName]
|
|
115
|
+
* @param {ReactElement[]} [existingStyles]
|
|
116
|
+
* @returns {ReactElement[]}
|
|
117
|
+
*/
|
|
118
|
+
export const getSSRStyles = (AppRoot, appName = 'app', existingStyles = []) => {
|
|
119
|
+
return [
|
|
120
|
+
...existingStyles,
|
|
121
|
+
...getReactNativeWebSSRStyles(AppRoot, appName)
|
|
122
|
+
// if any other CSS-in-JS is added e.g. styled-components generate and add its styles here
|
|
123
|
+
]
|
|
124
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { viewports } from '@telus-uds/system-constants'
|
|
2
2
|
import { useViewport } from '../ViewportProvider'
|
|
3
|
+
import hasOwnProperty from './hasOwnProperty'
|
|
3
4
|
|
|
4
|
-
const hasOwn = (objectProp, key) => Object.prototype.hasOwnProperty.call(objectProp, key)
|
|
5
5
|
const hasResponsiveProperties = (objectProp) =>
|
|
6
|
-
viewports.keys.some((key) =>
|
|
6
|
+
viewports.keys.some((key) => hasOwnProperty(objectProp, key))
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Resolves a prop which may be a responsive object with keys for viewports.
|
|
@@ -23,7 +23,7 @@ export const resolveResponsiveProp = (prop, viewport, defaultValue) => {
|
|
|
23
23
|
? // If there's a current viewport, return the closest match at or below it
|
|
24
24
|
viewports.inherit(prop)[viewport]
|
|
25
25
|
: // If no current viewport is available, default to smallest viewport
|
|
26
|
-
prop[viewports.keys.find((key) =>
|
|
26
|
+
prop[viewports.keys.find((key) => hasOwnProperty(prop, key))]
|
|
27
27
|
|
|
28
28
|
return value === undefined ? defaultValue : value
|
|
29
29
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import React, { forwardRef } from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
|
-
|
|
4
|
-
// Prototype-safe alternative to (linter-forbidden) someObject.hasOwnProperty()
|
|
5
|
-
const hasOwnProperty = (object, prop) => Object.prototype.hasOwnProperty.call(object, prop)
|
|
3
|
+
import hasOwnProperty from './hasOwnProperty'
|
|
6
4
|
|
|
7
5
|
/**
|
|
8
6
|
* Higher-order component that has no effect unless an additional prop `LinkRouter` is passed.
|
package/src/utils/ssr.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { AppRegistry } from 'react-native'
|
|
2
|
-
/** @typedef {import('react').ComponentType} ReactComponent */
|
|
3
|
-
/** @typedef {import('react').ReactElement} ReactElement */
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Registers the app's root component with React Native Web and generates
|
|
7
|
-
* the main <style> tag containing React Native Web stylesheet styles.
|
|
8
|
-
*
|
|
9
|
-
* @param {ReactComponent} AppRoot
|
|
10
|
-
* @param {string} [appName]
|
|
11
|
-
* @returns {ReactElement[]}
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
export const getReactNativeWebSSRStyles = (AppRoot, appName = 'app') => {
|
|
15
|
-
AppRegistry.registerComponent(appName, () => AppRoot)
|
|
16
|
-
const { getStyleElement } = AppRegistry.getApplication(appName)
|
|
17
|
-
return [getStyleElement()]
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Gets style tags for each currently supported CSS-in-JS library and returns
|
|
22
|
-
* them alongside any existing style tags.
|
|
23
|
-
*
|
|
24
|
-
* @param {ReactComponent} AppRoot
|
|
25
|
-
* @param {string} [appName]
|
|
26
|
-
* @param {ReactElement[]} [existingStyles]
|
|
27
|
-
* @returns {ReactElement[]}
|
|
28
|
-
*/
|
|
29
|
-
export const getSSRStyles = (AppRoot, appName = 'app', existingStyles = []) => {
|
|
30
|
-
return [
|
|
31
|
-
...existingStyles,
|
|
32
|
-
...getReactNativeWebSSRStyles(AppRoot, appName)
|
|
33
|
-
// if any other CSS-in-JS is added e.g. styled-components generate and add its styles here
|
|
34
|
-
]
|
|
35
|
-
}
|