@telus-uds/components-base 1.11.0 → 1.13.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 +36 -2
- package/component-docs.json +796 -33
- package/lib/BaseProvider/index.js +7 -2
- package/lib/Button/ButtonBase.js +18 -14
- package/lib/Button/ButtonGroup.js +7 -0
- package/lib/Carousel/Carousel.js +83 -58
- package/lib/Carousel/CarouselContext.js +22 -8
- package/lib/Carousel/CarouselFirstFocus/CarouselFirstFocus.js +73 -0
- package/lib/Carousel/CarouselStepTracker/CarouselStepTracker.js +56 -0
- package/lib/Carousel/CarouselStepTracker/index.js +13 -0
- package/lib/Carousel/CarouselTabs/CarouselTabs.js +70 -0
- package/lib/Carousel/CarouselTabs/CarouselTabsPanel.js +95 -0
- package/lib/Carousel/CarouselTabs/CarouselTabsPanelItem.js +148 -0
- package/lib/Carousel/CarouselTabs/index.js +13 -0
- package/lib/Carousel/CarouselThumbnail.js +99 -0
- package/lib/Carousel/CarouselThumbnailNavigation.js +87 -0
- package/lib/Carousel/dictionary.js +4 -2
- package/lib/Carousel/index.js +10 -1
- package/lib/Checkbox/Checkbox.js +7 -3
- package/lib/Checkbox/CheckboxGroup.js +8 -1
- package/lib/Feedback/Feedback.js +18 -10
- package/lib/Icon/IconText.js +5 -0
- package/lib/InputLabel/InputLabel.js +11 -5
- package/lib/Link/InlinePressable.js +1 -8
- package/lib/Link/LinkBase.js +12 -9
- package/lib/List/ListItem.js +7 -3
- package/lib/Notification/Notification.js +7 -2
- package/lib/Pagination/Pagination.js +7 -3
- package/lib/Radio/RadioGroup.js +8 -0
- package/lib/RadioCard/RadioCard.js +6 -1
- package/lib/RadioCard/RadioCardGroup.js +7 -0
- package/lib/Select/Select.js +7 -3
- package/lib/SkipLink/SkipLink.js +216 -0
- package/lib/SkipLink/index.js +13 -0
- package/lib/StepTracker/Step.js +8 -4
- package/lib/StepTracker/StepTracker.js +7 -3
- package/lib/Tabs/TabsItem.js +4 -0
- package/lib/TextInput/TextInputBase.js +7 -3
- package/lib/ThemeProvider/ThemeProvider.js +25 -3
- package/lib/ThemeProvider/utils/styles.js +8 -1
- package/lib/ThemeProvider/utils/theme-tokens.js +1 -1
- package/lib/ToggleSwitch/ToggleSwitchGroup.js +7 -0
- package/lib/Typography/Typography.js +6 -2
- package/lib/index.js +9 -0
- package/lib/utils/index.js +9 -0
- package/lib-module/BaseProvider/index.js +7 -2
- package/lib-module/Button/ButtonBase.js +7 -3
- package/lib-module/Button/ButtonGroup.js +7 -0
- package/lib-module/Carousel/Carousel.js +80 -57
- package/lib-module/Carousel/CarouselContext.js +21 -8
- package/lib-module/Carousel/CarouselFirstFocus/CarouselFirstFocus.js +51 -0
- package/lib-module/Carousel/CarouselStepTracker/CarouselStepTracker.js +42 -0
- package/lib-module/Carousel/CarouselStepTracker/index.js +2 -0
- package/lib-module/Carousel/CarouselTabs/CarouselTabs.js +50 -0
- package/lib-module/Carousel/CarouselTabs/CarouselTabsPanel.js +76 -0
- package/lib-module/Carousel/CarouselTabs/CarouselTabsPanelItem.js +126 -0
- package/lib-module/Carousel/CarouselTabs/index.js +2 -0
- package/lib-module/Carousel/CarouselThumbnail.js +85 -0
- package/lib-module/Carousel/CarouselThumbnailNavigation.js +66 -0
- package/lib-module/Carousel/dictionary.js +4 -2
- package/lib-module/Carousel/index.js +2 -1
- package/lib-module/Checkbox/Checkbox.js +8 -4
- package/lib-module/Checkbox/CheckboxGroup.js +8 -1
- package/lib-module/Feedback/Feedback.js +19 -11
- package/lib-module/Icon/IconText.js +5 -0
- package/lib-module/InputLabel/InputLabel.js +12 -6
- package/lib-module/Link/InlinePressable.js +1 -8
- package/lib-module/Link/LinkBase.js +13 -10
- package/lib-module/List/ListItem.js +8 -4
- package/lib-module/Notification/Notification.js +8 -3
- package/lib-module/Pagination/Pagination.js +8 -4
- package/lib-module/Radio/RadioGroup.js +8 -0
- package/lib-module/RadioCard/RadioCard.js +7 -2
- package/lib-module/RadioCard/RadioCardGroup.js +7 -0
- package/lib-module/Select/Select.js +8 -4
- package/lib-module/SkipLink/SkipLink.js +188 -0
- package/lib-module/SkipLink/index.js +2 -0
- package/lib-module/StepTracker/Step.js +9 -5
- package/lib-module/StepTracker/StepTracker.js +8 -4
- package/lib-module/Tabs/TabsItem.js +5 -1
- package/lib-module/TextInput/TextInputBase.js +8 -4
- package/lib-module/ThemeProvider/ThemeProvider.js +24 -3
- package/lib-module/ThemeProvider/utils/styles.js +8 -1
- package/lib-module/ThemeProvider/utils/theme-tokens.js +1 -1
- package/lib-module/ToggleSwitch/ToggleSwitchGroup.js +7 -0
- package/lib-module/Typography/Typography.js +7 -3
- package/lib-module/index.js +1 -0
- package/lib-module/utils/index.js +1 -0
- package/package.json +46 -47
- package/src/BaseProvider/index.jsx +6 -3
- package/src/Button/ButtonBase.jsx +8 -3
- package/src/Button/ButtonGroup.jsx +6 -0
- package/src/Carousel/Carousel.jsx +91 -64
- package/src/Carousel/CarouselContext.jsx +29 -5
- package/src/Carousel/CarouselFirstFocus/CarouselFirstFocus.jsx +49 -0
- package/src/Carousel/CarouselStepTracker/CarouselStepTracker.jsx +36 -0
- package/src/Carousel/CarouselStepTracker/index.js +3 -0
- package/src/Carousel/CarouselTabs/CarouselTabs.jsx +37 -0
- package/src/Carousel/CarouselTabs/CarouselTabsPanel.jsx +69 -0
- package/src/Carousel/CarouselTabs/CarouselTabsPanelItem.jsx +119 -0
- package/src/Carousel/CarouselTabs/index.js +3 -0
- package/src/Carousel/CarouselThumbnail.jsx +77 -0
- package/src/Carousel/CarouselThumbnailNavigation.jsx +53 -0
- package/src/Carousel/dictionary.js +4 -2
- package/src/Carousel/index.js +1 -0
- package/src/Checkbox/Checkbox.jsx +14 -11
- package/src/Checkbox/CheckboxGroup.jsx +8 -1
- package/src/Feedback/Feedback.jsx +14 -7
- package/src/Icon/IconText.jsx +2 -0
- package/src/InputLabel/InputLabel.jsx +13 -12
- package/src/Link/InlinePressable.jsx +2 -8
- package/src/Link/LinkBase.jsx +17 -20
- package/src/List/ListItem.jsx +9 -4
- package/src/Notification/Notification.jsx +5 -3
- package/src/Pagination/Pagination.jsx +6 -4
- package/src/Radio/RadioGroup.jsx +7 -0
- package/src/RadioCard/RadioCard.jsx +3 -2
- package/src/RadioCard/RadioCardGroup.jsx +6 -0
- package/src/Select/Select.jsx +12 -3
- package/src/SkipLink/SkipLink.jsx +179 -0
- package/src/SkipLink/index.js +3 -0
- package/src/StepTracker/Step.jsx +12 -4
- package/src/StepTracker/StepTracker.jsx +11 -10
- package/src/Tabs/TabsItem.jsx +3 -2
- package/src/TextInput/TextInputBase.jsx +11 -3
- package/src/ThemeProvider/ThemeProvider.jsx +22 -3
- package/src/ThemeProvider/utils/styles.js +9 -1
- package/src/ThemeProvider/utils/theme-tokens.js +1 -1
- package/src/ToggleSwitch/ToggleSwitchGroup.jsx +6 -0
- package/src/Typography/Typography.jsx +11 -12
- package/src/index.js +1 -0
- package/src/utils/index.js +1 -0
package/package.json
CHANGED
|
@@ -1,21 +1,55 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
2
|
+
"author": "TELUS Digital",
|
|
3
|
+
"browserslist": [
|
|
4
|
+
"extends @telus-uds/browserslist-config"
|
|
5
|
+
],
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/telus/universal-design-system/issues"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"airbnb-prop-types": "^2.16.0",
|
|
11
|
+
"@telus-uds/system-constants": "^1.0.4",
|
|
12
|
+
"@telus-uds/system-theme-tokens": "^2.2.0",
|
|
13
|
+
"lodash.debounce": "^4.0.8",
|
|
14
|
+
"lodash.merge": "^4.6.2",
|
|
15
|
+
"prop-types": "^15.7.2",
|
|
16
|
+
"react-native-picker-select": "^8.0.4",
|
|
17
|
+
"semver": "^7.3.5"
|
|
18
|
+
},
|
|
4
19
|
"description": "Base components",
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@storybook/addon-a11y": "^6.5.6",
|
|
22
|
+
"@storybook/addon-essentials": "^6.5.6",
|
|
23
|
+
"@storybook/cli": "^6.5.6",
|
|
24
|
+
"@storybook/react": "^6.5.6",
|
|
25
|
+
"@storybook/builder-webpack5": "^6.5.6",
|
|
26
|
+
"@storybook/manager-webpack5": "^6.5.6",
|
|
27
|
+
"@telus-uds/browserslist-config": "^1.0.4",
|
|
28
|
+
"@testing-library/jest-native": "^4.0.1",
|
|
29
|
+
"@testing-library/react-hooks": "^7.0.1",
|
|
30
|
+
"@testing-library/react-native": "^7.2.0",
|
|
31
|
+
"react-test-renderer": "^16.3.2",
|
|
32
|
+
"webpack": "5.x"
|
|
33
|
+
},
|
|
34
|
+
"directories": {
|
|
35
|
+
"lib": "lib",
|
|
36
|
+
"test": "__tests__"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/telus/universal-design-system#readme",
|
|
5
39
|
"keywords": [
|
|
6
40
|
"base"
|
|
7
41
|
],
|
|
8
|
-
"author": "TELUS Digital",
|
|
9
|
-
"homepage": "https://github.com/telus/universal-design-system#readme",
|
|
10
42
|
"license": "UNLICENSED",
|
|
11
43
|
"main": "lib/index.js",
|
|
12
44
|
"module": "lib-module/index.js",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
"
|
|
45
|
+
"name": "@telus-uds/components-base",
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"react": "^17.0.2",
|
|
48
|
+
"react-dom": "^17.0.2",
|
|
49
|
+
"react-native": "*",
|
|
50
|
+
"react-native-web": "~0.17.5"
|
|
18
51
|
},
|
|
52
|
+
"react-native": "src/index.js",
|
|
19
53
|
"repository": {
|
|
20
54
|
"type": "git",
|
|
21
55
|
"url": "git+https://github.com/telus/universal-design-system.git"
|
|
@@ -31,46 +65,11 @@
|
|
|
31
65
|
"build:code": "npm run build:main && npm run build:module",
|
|
32
66
|
"build:docs": "babel-node --plugins=@nearform/babel-plugin-react-docgen generate-component-docs.js",
|
|
33
67
|
"storybook": "start-storybook",
|
|
34
|
-
"build-storybook": "build-storybook"
|
|
35
|
-
"dev": "npm run build:code --watch"
|
|
36
|
-
},
|
|
37
|
-
"bugs": {
|
|
38
|
-
"url": "https://github.com/telus/universal-design-system/issues"
|
|
68
|
+
"build-storybook": "build-storybook"
|
|
39
69
|
},
|
|
70
|
+
"sideEffects": false,
|
|
40
71
|
"standard-engine": {
|
|
41
72
|
"skip": true
|
|
42
73
|
},
|
|
43
|
-
"
|
|
44
|
-
"extends @telus-uds/browserslist-config"
|
|
45
|
-
],
|
|
46
|
-
"peerDependencies": {
|
|
47
|
-
"react": "^17.0.2",
|
|
48
|
-
"react-dom": "^17.0.2",
|
|
49
|
-
"react-native": "*",
|
|
50
|
-
"react-native-web": "~0.17.5"
|
|
51
|
-
},
|
|
52
|
-
"devDependencies": {
|
|
53
|
-
"@storybook/addon-a11y": "^6.5.6",
|
|
54
|
-
"@storybook/addon-essentials": "^6.5.6",
|
|
55
|
-
"@storybook/cli": "^6.5.6",
|
|
56
|
-
"@storybook/react": "^6.5.6",
|
|
57
|
-
"@storybook/builder-webpack5": "^6.5.6",
|
|
58
|
-
"@storybook/manager-webpack5": "^6.5.6",
|
|
59
|
-
"@telus-uds/browserslist-config": "^1.0.4",
|
|
60
|
-
"@testing-library/jest-native": "^4.0.1",
|
|
61
|
-
"@testing-library/react-hooks": "^7.0.1",
|
|
62
|
-
"@testing-library/react-native": "^7.2.0",
|
|
63
|
-
"react-test-renderer": "^16.3.2",
|
|
64
|
-
"webpack": "5.x"
|
|
65
|
-
},
|
|
66
|
-
"dependencies": {
|
|
67
|
-
"airbnb-prop-types": "^2.16.0",
|
|
68
|
-
"@telus-uds/system-constants": "^1.0.4",
|
|
69
|
-
"@telus-uds/system-theme-tokens": "^2.1.0",
|
|
70
|
-
"lodash.debounce": "^4.0.8",
|
|
71
|
-
"lodash.merge": "^4.6.2",
|
|
72
|
-
"prop-types": "^15.7.2",
|
|
73
|
-
"react-native-picker-select": "^8.0.4",
|
|
74
|
-
"semver": "^7.3.5"
|
|
75
|
-
}
|
|
74
|
+
"version": "1.13.0"
|
|
76
75
|
}
|
|
@@ -4,17 +4,20 @@ import A11yInfoProvider from '../A11yInfoProvider'
|
|
|
4
4
|
import ViewportProvider from '../ViewportProvider'
|
|
5
5
|
import ThemeProvider from '../ThemeProvider'
|
|
6
6
|
|
|
7
|
-
const BaseProvider = ({ defaultTheme, children }) => (
|
|
7
|
+
const BaseProvider = ({ defaultTheme, children, themeOptions }) => (
|
|
8
8
|
<A11yInfoProvider>
|
|
9
9
|
<ViewportProvider>
|
|
10
|
-
<ThemeProvider defaultTheme={defaultTheme}
|
|
10
|
+
<ThemeProvider defaultTheme={defaultTheme} themeOptions={themeOptions}>
|
|
11
|
+
{children}
|
|
12
|
+
</ThemeProvider>
|
|
11
13
|
</ViewportProvider>
|
|
12
14
|
</A11yInfoProvider>
|
|
13
15
|
)
|
|
14
16
|
|
|
15
17
|
BaseProvider.propTypes = {
|
|
18
|
+
children: PropTypes.node.isRequired,
|
|
16
19
|
defaultTheme: ThemeProvider.propTypes?.defaultTheme,
|
|
17
|
-
|
|
20
|
+
themeOptions: PropTypes.shape({ forceAbsoluteFontSizing: PropTypes.bool })
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
export default BaseProvider
|
|
@@ -2,7 +2,7 @@ import React, { forwardRef } from 'react'
|
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
3
|
import { Pressable, View, StyleSheet, Platform } from 'react-native'
|
|
4
4
|
|
|
5
|
-
import { applyTextStyles, applyShadowToken, applyOuterBorder } from '../ThemeProvider
|
|
5
|
+
import { applyTextStyles, applyShadowToken, applyOuterBorder, useTheme } from '../ThemeProvider'
|
|
6
6
|
import buttonPropTypes from './propTypes'
|
|
7
7
|
import {
|
|
8
8
|
a11yProps,
|
|
@@ -121,13 +121,17 @@ const selectBorderStyles = ({ borderColor, borderWidth, borderRadius }) => ({
|
|
|
121
121
|
borderRadius
|
|
122
122
|
})
|
|
123
123
|
|
|
124
|
-
const selectTextStyles = (
|
|
124
|
+
const selectTextStyles = (
|
|
125
|
+
{ fontSize, color, lineHeight, fontName, fontWeight, textAlign },
|
|
126
|
+
themeOptions
|
|
127
|
+
) =>
|
|
125
128
|
applyTextStyles({
|
|
126
129
|
fontSize,
|
|
127
130
|
color,
|
|
128
131
|
lineHeight,
|
|
129
132
|
fontName,
|
|
130
133
|
fontWeight,
|
|
134
|
+
themeOptions,
|
|
131
135
|
textAlign
|
|
132
136
|
})
|
|
133
137
|
|
|
@@ -174,6 +178,7 @@ const ButtonBase = forwardRef(
|
|
|
174
178
|
selectOuterWidthStyles(themeTokens)
|
|
175
179
|
]
|
|
176
180
|
}
|
|
181
|
+
const { themeOptions } = useTheme()
|
|
177
182
|
|
|
178
183
|
return (
|
|
179
184
|
<Pressable
|
|
@@ -189,7 +194,7 @@ const ButtonBase = forwardRef(
|
|
|
189
194
|
const themeTokens = resolveButtonTokens(pressableState)
|
|
190
195
|
const containerStyles = selectInnerContainerStyles(themeTokens)
|
|
191
196
|
const borderStyles = selectBorderStyles(themeTokens)
|
|
192
|
-
const textStyles = [selectTextStyles(themeTokens), staticStyles.text]
|
|
197
|
+
const textStyles = [selectTextStyles(themeTokens, themeOptions), staticStyles.text]
|
|
193
198
|
|
|
194
199
|
// If the container has a width set, fill it instead of sizing from content.
|
|
195
200
|
// If in future we support text alignments other than center, add here.
|
|
@@ -45,6 +45,7 @@ const ButtonGroup = forwardRef(
|
|
|
45
45
|
legend,
|
|
46
46
|
tooltip,
|
|
47
47
|
hint,
|
|
48
|
+
hintPosition = 'inline',
|
|
48
49
|
validation,
|
|
49
50
|
feedback,
|
|
50
51
|
name: inputGroupName,
|
|
@@ -94,6 +95,7 @@ const ButtonGroup = forwardRef(
|
|
|
94
95
|
legend={legend}
|
|
95
96
|
tooltip={tooltip}
|
|
96
97
|
hint={hint}
|
|
98
|
+
hintPosition={hintPosition}
|
|
97
99
|
space={fieldSpace}
|
|
98
100
|
feedback={feedback}
|
|
99
101
|
readOnly={readOnly}
|
|
@@ -217,6 +219,10 @@ ButtonGroup.propTypes = {
|
|
|
217
219
|
* Optional additional text giving more detail to help a user make a choice.
|
|
218
220
|
*/
|
|
219
221
|
hint: PropTypes.string,
|
|
222
|
+
/**
|
|
223
|
+
* Position of the hint relative to label. Use `below` to display a larger hint below the label.
|
|
224
|
+
*/
|
|
225
|
+
hintPosition: PropTypes.oneOf(['inline', 'below']),
|
|
220
226
|
/**
|
|
221
227
|
* Optional tooltip text content to include alongside the legend and hint.
|
|
222
228
|
*/
|
|
@@ -11,15 +11,17 @@ import {
|
|
|
11
11
|
selectSystemProps,
|
|
12
12
|
a11yProps,
|
|
13
13
|
viewProps,
|
|
14
|
-
useCopy
|
|
14
|
+
useCopy,
|
|
15
|
+
unpackFragment
|
|
15
16
|
} from '../utils'
|
|
16
17
|
import { useA11yInfo } from '../A11yInfoProvider'
|
|
17
18
|
import { CarouselProvider } from './CarouselContext'
|
|
18
19
|
import CarouselItem from './CarouselItem'
|
|
19
|
-
import StepTracker from '../StepTracker'
|
|
20
|
-
import StackView from '../StackView'
|
|
21
20
|
import IconButton from '../IconButton'
|
|
22
|
-
|
|
21
|
+
import SkipLink from '../SkipLink'
|
|
22
|
+
import A11yText from '../A11yText'
|
|
23
|
+
import CarouselStepTracker from './CarouselStepTracker'
|
|
24
|
+
import CarouselThumbnailNavigation from './CarouselThumbnailNavigation'
|
|
23
25
|
import dictionary from './dictionary'
|
|
24
26
|
|
|
25
27
|
const staticStyles = StyleSheet.create({
|
|
@@ -33,19 +35,6 @@ const staticStyles = StyleSheet.create({
|
|
|
33
35
|
}
|
|
34
36
|
})
|
|
35
37
|
|
|
36
|
-
const staticTokens = {
|
|
37
|
-
stackView: {
|
|
38
|
-
justifyContent: 'center'
|
|
39
|
-
},
|
|
40
|
-
stepTracker: {
|
|
41
|
-
showStepLabel: false,
|
|
42
|
-
showStepTrackerLabel: true,
|
|
43
|
-
knobCompletedBackgroundColor: 'none',
|
|
44
|
-
connectorCompletedColor: 'none',
|
|
45
|
-
connectorColor: 'none'
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
38
|
const selectContainerStyles = (width) => ({
|
|
50
39
|
backgroundColor: 'transparent',
|
|
51
40
|
overflow: 'hidden',
|
|
@@ -160,27 +149,36 @@ const Carousel = React.forwardRef(
|
|
|
160
149
|
onAnimationStart,
|
|
161
150
|
onAnimationEnd,
|
|
162
151
|
onIndexChanged,
|
|
152
|
+
skipLinkHref,
|
|
153
|
+
refocus,
|
|
154
|
+
title = 'carousel',
|
|
163
155
|
springConfig = undefined,
|
|
164
|
-
|
|
156
|
+
thumbnails = undefined,
|
|
157
|
+
panelNavigation = thumbnails ? (
|
|
158
|
+
<CarouselThumbnailNavigation thumbnails={thumbnails} />
|
|
159
|
+
) : (
|
|
160
|
+
<CarouselStepTracker />
|
|
161
|
+
),
|
|
165
162
|
tag = 'ul',
|
|
166
|
-
accessibilityRole
|
|
167
|
-
accessibilityLabel =
|
|
163
|
+
accessibilityRole,
|
|
164
|
+
accessibilityLabel = title,
|
|
165
|
+
accessibilityLiveRegion = 'polite',
|
|
168
166
|
copy,
|
|
169
167
|
...rest
|
|
170
168
|
},
|
|
171
169
|
ref
|
|
172
170
|
) => {
|
|
173
171
|
const viewport = useViewport()
|
|
172
|
+
const themeTokens = useThemeTokens('Carousel', tokens, variant, {
|
|
173
|
+
viewport
|
|
174
|
+
})
|
|
174
175
|
const {
|
|
175
176
|
previousIcon,
|
|
176
177
|
nextIcon,
|
|
177
178
|
showPreviousNextNavigation,
|
|
178
179
|
showPanelNavigation,
|
|
179
|
-
spaceBetweenSlideAndPreviousNextNavigation
|
|
180
|
-
|
|
181
|
-
} = useThemeTokens('Carousel', tokens, variant, {
|
|
182
|
-
viewport
|
|
183
|
-
})
|
|
180
|
+
spaceBetweenSlideAndPreviousNextNavigation
|
|
181
|
+
} = themeTokens
|
|
184
182
|
const [activeIndex, setActiveIndex] = React.useState(0)
|
|
185
183
|
|
|
186
184
|
const [isAnimating, setIsAnimating] = React.useState(false)
|
|
@@ -201,7 +199,7 @@ const Carousel = React.forwardRef(
|
|
|
201
199
|
|
|
202
200
|
const getCopy = useCopy({ dictionary, copy })
|
|
203
201
|
|
|
204
|
-
const childrenArray =
|
|
202
|
+
const childrenArray = unpackFragment(children)
|
|
205
203
|
const systemProps = selectProps({
|
|
206
204
|
...rest,
|
|
207
205
|
accessibilityRole,
|
|
@@ -220,15 +218,12 @@ const Carousel = React.forwardRef(
|
|
|
220
218
|
})
|
|
221
219
|
const [previousNextNavigationButtonWidth, setPreviousNextNavigationButtonWidth] =
|
|
222
220
|
React.useState(0)
|
|
221
|
+
const firstFocusRef = React.useRef(null)
|
|
223
222
|
const pan = React.useRef(new Animated.ValueXY()).current
|
|
224
223
|
const animatedX = React.useRef(0)
|
|
225
224
|
const animatedY = React.useRef(0)
|
|
226
225
|
const isFirstSlide = !activeIndex
|
|
227
226
|
const isLastSlide = activeIndex + 1 >= children.length
|
|
228
|
-
const panelNavigationTokens = {
|
|
229
|
-
...staticTokens.stepTracker,
|
|
230
|
-
containerPaddingTop: spaceBetweenSlideAndPanelNavigation
|
|
231
|
-
}
|
|
232
227
|
|
|
233
228
|
const onContainerLayout = ({
|
|
234
229
|
nativeEvent: {
|
|
@@ -308,8 +303,9 @@ const Carousel = React.forwardRef(
|
|
|
308
303
|
updateOffset()
|
|
309
304
|
handleAnimationStart(activeIndex)
|
|
310
305
|
updateIndex(delta)
|
|
306
|
+
if (refocus) firstFocusRef.current?.focus()
|
|
311
307
|
},
|
|
312
|
-
[updateIndex, updateOffset, activeIndex, handleAnimationStart]
|
|
308
|
+
[updateIndex, updateOffset, activeIndex, handleAnimationStart, refocus]
|
|
313
309
|
)
|
|
314
310
|
|
|
315
311
|
const goToNeighboring = React.useCallback(
|
|
@@ -405,22 +401,31 @@ const Carousel = React.forwardRef(
|
|
|
405
401
|
// Related discussion - https://github.com/telus/universal-design-system/issues/1549
|
|
406
402
|
const previousNextIconButtonVariants = { size: previousNextIconSize, raised: true }
|
|
407
403
|
|
|
408
|
-
const getCopyWithPlaceholders = (
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
404
|
+
const getCopyWithPlaceholders = React.useCallback(
|
|
405
|
+
(copyKey) => {
|
|
406
|
+
const copyText = getCopy(copyKey)
|
|
407
|
+
.replace(/%\{title\}/g, title)
|
|
408
|
+
.replace(/%\{itemLabel\}/g, itemLabel)
|
|
409
|
+
.replace(/%\{stepNumber\}/g, activeIndex + 1)
|
|
410
|
+
.replace(/%\{stepCount\}/g, childrenArray.length)
|
|
413
411
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
412
|
+
// First word might be a lowercase placeholder: capitalize the first letter
|
|
413
|
+
return `${copyText[0].toUpperCase()}${copyText.slice(1)}`
|
|
414
|
+
},
|
|
415
|
+
[activeIndex, childrenArray.length, itemLabel, getCopy, title]
|
|
416
|
+
)
|
|
417
417
|
|
|
418
418
|
return (
|
|
419
419
|
<CarouselProvider
|
|
420
420
|
activeIndex={activeIndex}
|
|
421
|
+
goTo={goTo}
|
|
422
|
+
getCopyWithPlaceholders={getCopyWithPlaceholders}
|
|
423
|
+
itemLabel={itemLabel}
|
|
421
424
|
totalItems={childrenArray.length}
|
|
425
|
+
themeTokens={themeTokens}
|
|
426
|
+
firstFocusRef={firstFocusRef}
|
|
427
|
+
refocus={refocus}
|
|
422
428
|
width={containerLayout.width}
|
|
423
|
-
goTo={goTo}
|
|
424
429
|
>
|
|
425
430
|
<View style={staticStyles.root} onLayout={onContainerLayout} ref={ref} {...systemProps}>
|
|
426
431
|
{showPreviousNextNavigation && (
|
|
@@ -447,6 +452,19 @@ const Carousel = React.forwardRef(
|
|
|
447
452
|
/>
|
|
448
453
|
</View>
|
|
449
454
|
)}
|
|
455
|
+
{Boolean(skipLinkHref) && (
|
|
456
|
+
<SkipLink ref={firstFocusRef} href={skipLinkHref}>
|
|
457
|
+
{getCopyWithPlaceholders('skipLink')}
|
|
458
|
+
</SkipLink>
|
|
459
|
+
)}
|
|
460
|
+
<A11yText
|
|
461
|
+
// Read the current slide position to screen readers on slide.
|
|
462
|
+
// If it's set to refocus and doesn't have a SkipLink to focus to, focus this.
|
|
463
|
+
ref={!skipLinkHref && refocus ? firstFocusRef : null}
|
|
464
|
+
accessibilityLiveRegion={!skipLinkHref && refocus ? undefined : 'polite'}
|
|
465
|
+
focusable={!skipLinkHref && refocus}
|
|
466
|
+
text={getCopyWithPlaceholders('stepTrackerLabel')}
|
|
467
|
+
/>
|
|
450
468
|
<View style={selectContainerStyles(containerLayout.width)}>
|
|
451
469
|
<Animated.View
|
|
452
470
|
style={StyleSheet.flatten([
|
|
@@ -457,6 +475,9 @@ const Carousel = React.forwardRef(
|
|
|
457
475
|
])}
|
|
458
476
|
{...panResponder.panHandlers}
|
|
459
477
|
{...getA11yPropsFromHtmlTag(tag)}
|
|
478
|
+
// In iframes on Mac (e.g. in Storybook), this content may be misread or read twice.
|
|
479
|
+
// This is a known Voiceover bug: https://github.com/phetsims/a11y-research/issues/132
|
|
480
|
+
accessibilityLiveRegion={accessibilityLiveRegion}
|
|
460
481
|
>
|
|
461
482
|
{childrenArray.map((element, index) => {
|
|
462
483
|
const hidden = !isAnimating && index !== activeIndex
|
|
@@ -490,24 +511,7 @@ const Carousel = React.forwardRef(
|
|
|
490
511
|
</View>
|
|
491
512
|
)}
|
|
492
513
|
</View>
|
|
493
|
-
{showPanelNavigation ?
|
|
494
|
-
<StackView direction="row" tokens={staticTokens.stackView}>
|
|
495
|
-
{onRenderPanelNavigation ? (
|
|
496
|
-
onRenderPanelNavigation({ activeIndex, totalItems: childrenArray.length })
|
|
497
|
-
) : (
|
|
498
|
-
<StepTracker
|
|
499
|
-
current={activeIndex}
|
|
500
|
-
steps={childrenArray.map((_, index) => String(index))}
|
|
501
|
-
copy={{
|
|
502
|
-
// Give StepTracker copy from Carousel's language and dictionary
|
|
503
|
-
stepLabel: getCopyWithPlaceholders('stepLabel'),
|
|
504
|
-
stepTrackerLabel: getCopyWithPlaceholders('stepTrackerLabel')
|
|
505
|
-
}}
|
|
506
|
-
tokens={panelNavigationTokens}
|
|
507
|
-
/>
|
|
508
|
-
)}
|
|
509
|
-
</StackView>
|
|
510
|
-
) : null}
|
|
514
|
+
{showPanelNavigation ? panelNavigation : null}
|
|
511
515
|
</CarouselProvider>
|
|
512
516
|
)
|
|
513
517
|
}
|
|
@@ -542,6 +546,16 @@ Carousel.propTypes = {
|
|
|
542
546
|
* Carousel uses `Animated.spring` to animate slide changes, use this option to pass custom animation configuration
|
|
543
547
|
*/
|
|
544
548
|
springConfig: PropTypes.object,
|
|
549
|
+
/**
|
|
550
|
+
* An array of objects containing information on the thumbnails to be rendered as navigation panel
|
|
551
|
+
*/
|
|
552
|
+
thumbnails: PropTypes.arrayOf(
|
|
553
|
+
PropTypes.shape({
|
|
554
|
+
accessibilityLabel: PropTypes.string,
|
|
555
|
+
alt: PropTypes.string,
|
|
556
|
+
src: PropTypes.string
|
|
557
|
+
})
|
|
558
|
+
),
|
|
545
559
|
/**
|
|
546
560
|
* Minimal part of slide width must be swiped for changing index.
|
|
547
561
|
* Otherwise animation restore current slide. Default value 0.2 means that 20% must be swiped for change index
|
|
@@ -569,20 +583,33 @@ Carousel.propTypes = {
|
|
|
569
583
|
*/
|
|
570
584
|
onIndexChanged: PropTypes.func,
|
|
571
585
|
/**
|
|
572
|
-
*
|
|
573
|
-
*
|
|
574
|
-
|
|
575
|
-
|
|
586
|
+
* If this is a complex carousel with a lot of focusable content, pass a href for a skip link. Typically, this will be an anchor link
|
|
587
|
+
* with the ID of a focusable element immediately after the Carousel, e.g. `'#section-2-heading'`.
|
|
588
|
+
*/
|
|
589
|
+
skipLinkHref: PropTypes.string,
|
|
590
|
+
/**
|
|
591
|
+
* If true, whenever a new slide comes into view, the focus of the Carousel switches to the start.
|
|
592
|
+
*
|
|
593
|
+
* Pass this as true when using carousel items that contain interactive content, so a user can easily tab into that content.
|
|
594
|
+
*
|
|
595
|
+
* If skipLinkHref is passed, the focus target will be the SkipLink; if not, it'll be an empty element before the slide content.
|
|
596
|
+
*/
|
|
597
|
+
refocus: PropTypes.bool,
|
|
598
|
+
/**
|
|
599
|
+
* Use this to render a custom panel navigation element instead of the default StepTracker's based navigation
|
|
600
|
+
* You can make use of `useCarousel` within your custom panel navigation component to hook into various Carousel states such as:
|
|
601
|
+
* - activeIndex: index of current slide
|
|
602
|
+
* - totalItems: total number of slides
|
|
576
603
|
* Use it as follows:
|
|
577
604
|
* ```js
|
|
578
605
|
* <Carousel
|
|
579
|
-
*
|
|
606
|
+
* panelNavigation={<CustomPanelNavigation />}
|
|
580
607
|
* >
|
|
581
608
|
* <Carousel.Item>First Slide</Carousel.Item>
|
|
582
609
|
* </Carousel>
|
|
583
610
|
* ```
|
|
584
611
|
*/
|
|
585
|
-
|
|
612
|
+
panelNavigation: PropTypes.element,
|
|
586
613
|
/**
|
|
587
614
|
* When slide animation start
|
|
588
615
|
* This function is also provided with a parameter indicating the current slide index before animation starts
|
|
@@ -1,12 +1,32 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
|
+
import { getTokensPropType } from '../utils'
|
|
3
4
|
|
|
4
5
|
const CarouselContext = React.createContext()
|
|
5
6
|
|
|
6
|
-
const CarouselProvider = ({
|
|
7
|
+
const CarouselProvider = ({
|
|
8
|
+
activeIndex,
|
|
9
|
+
children,
|
|
10
|
+
goTo,
|
|
11
|
+
getCopyWithPlaceholders,
|
|
12
|
+
itemLabel,
|
|
13
|
+
refocus = false,
|
|
14
|
+
themeTokens,
|
|
15
|
+
totalItems,
|
|
16
|
+
width
|
|
17
|
+
}) => {
|
|
7
18
|
const value = React.useMemo(
|
|
8
|
-
() => ({
|
|
9
|
-
|
|
19
|
+
() => ({
|
|
20
|
+
activeIndex,
|
|
21
|
+
goTo,
|
|
22
|
+
getCopyWithPlaceholders,
|
|
23
|
+
itemLabel,
|
|
24
|
+
refocus,
|
|
25
|
+
themeTokens,
|
|
26
|
+
totalItems,
|
|
27
|
+
width
|
|
28
|
+
}),
|
|
29
|
+
[activeIndex, goTo, getCopyWithPlaceholders, itemLabel, refocus, totalItems, themeTokens, width]
|
|
10
30
|
)
|
|
11
31
|
return <CarouselContext.Provider value={value}>{children}</CarouselContext.Provider>
|
|
12
32
|
}
|
|
@@ -22,9 +42,13 @@ function useCarousel() {
|
|
|
22
42
|
CarouselProvider.propTypes = {
|
|
23
43
|
children: PropTypes.arrayOf(PropTypes.element).isRequired,
|
|
24
44
|
activeIndex: PropTypes.number.isRequired,
|
|
45
|
+
goTo: PropTypes.func.isRequired,
|
|
46
|
+
getCopyWithPlaceholders: PropTypes.func.isRequired,
|
|
47
|
+
itemLabel: PropTypes.string.isRequired,
|
|
48
|
+
refocus: PropTypes.bool,
|
|
49
|
+
themeTokens: getTokensPropType('Carousel'),
|
|
25
50
|
totalItems: PropTypes.number.isRequired,
|
|
26
|
-
width: PropTypes.number.isRequired
|
|
27
|
-
goTo: PropTypes.func.isRequired
|
|
51
|
+
width: PropTypes.number.isRequired
|
|
28
52
|
}
|
|
29
53
|
|
|
30
54
|
export { CarouselProvider, useCarousel }
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react'
|
|
2
|
+
import { Pressable, Platform, StyleSheet } from 'react-native'
|
|
3
|
+
import { PropTypes } from 'prop-types'
|
|
4
|
+
|
|
5
|
+
import { useCarousel } from '../CarouselContext'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Focus target so that when a new slide is shown, the user can tab into
|
|
9
|
+
* its content using the keyboard.
|
|
10
|
+
*
|
|
11
|
+
* @TODO rework this after integrating with SkipLink when available.
|
|
12
|
+
*/
|
|
13
|
+
const CarouselFirstFocus = forwardRef(({ title }, ref) => {
|
|
14
|
+
const { getCopyWithPlaceholders } = useCarousel()
|
|
15
|
+
|
|
16
|
+
// TODO: integrate skip link description if behaving as skip link.
|
|
17
|
+
// Consider moving this content to aria-live area while only the skip link is focused.
|
|
18
|
+
const accessibilityLabel = `${title}, ${getCopyWithPlaceholders('stepTrackerLabel')}`
|
|
19
|
+
|
|
20
|
+
const accessibilityRole = Platform.select({
|
|
21
|
+
web: 'link', // The focused item will ultimately be a skip link.
|
|
22
|
+
default: 'button' // 'link' role usually denotes opening browser on Native.
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Pressable
|
|
27
|
+
// TODO: integrate skip link functionality, jump focus to after Carousel
|
|
28
|
+
onPress={undefined}
|
|
29
|
+
ref={ref}
|
|
30
|
+
accessibilityLabel={accessibilityLabel}
|
|
31
|
+
accessibilityRole={accessibilityRole}
|
|
32
|
+
style={StyleSheet.absoluteFill}
|
|
33
|
+
focusable
|
|
34
|
+
/>
|
|
35
|
+
)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
CarouselFirstFocus.displayName = 'CarouselFirstFocus'
|
|
39
|
+
CarouselFirstFocus.propTypes = {
|
|
40
|
+
/**
|
|
41
|
+
* Simple description of this carousel for screenreaders, to be read before
|
|
42
|
+
* "{itemLabel} {index} of {count}.
|
|
43
|
+
*
|
|
44
|
+
* For example, "Summer offers" in "Summer offers, offer 1 of 3"
|
|
45
|
+
*/
|
|
46
|
+
title: PropTypes.string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default CarouselFirstFocus
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useCarousel } from '../CarouselContext'
|
|
3
|
+
import StepTracker from '../../StepTracker'
|
|
4
|
+
import StackView from '../../StackView'
|
|
5
|
+
|
|
6
|
+
const CarouselStepTracker = () => {
|
|
7
|
+
const { activeIndex, totalItems, getCopyWithPlaceholders, themeTokens } = useCarousel()
|
|
8
|
+
const stackViewTokens = {
|
|
9
|
+
justifyContent: 'center'
|
|
10
|
+
}
|
|
11
|
+
const stepTrackerTokens = {
|
|
12
|
+
showStepLabel: false,
|
|
13
|
+
showStepTrackerLabel: true,
|
|
14
|
+
knobCompletedBackgroundColor: 'none',
|
|
15
|
+
connectorCompletedColor: 'none',
|
|
16
|
+
connectorColor: 'none',
|
|
17
|
+
containerPaddingTop: themeTokens.spaceBetweenSlideAndPanelNavigation
|
|
18
|
+
}
|
|
19
|
+
const steps = Array.from(Array(totalItems)).map((_, index) => String(index))
|
|
20
|
+
return (
|
|
21
|
+
<StackView direction="row" tokens={stackViewTokens}>
|
|
22
|
+
<StepTracker
|
|
23
|
+
current={activeIndex}
|
|
24
|
+
steps={steps}
|
|
25
|
+
copy={{
|
|
26
|
+
// Give StepTracker copy from Carousel's language and dictionary
|
|
27
|
+
stepLabel: getCopyWithPlaceholders('stepLabel'),
|
|
28
|
+
stepTrackerLabel: getCopyWithPlaceholders('stepTrackerLabel')
|
|
29
|
+
}}
|
|
30
|
+
tokens={stepTrackerTokens}
|
|
31
|
+
/>
|
|
32
|
+
</StackView>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default CarouselStepTracker
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
|
|
4
|
+
import { useResponsiveProp } from '../../utils'
|
|
5
|
+
import Carousel from '../Carousel'
|
|
6
|
+
import CarouselTabsPanel from './CarouselTabsPanel'
|
|
7
|
+
|
|
8
|
+
const CarouselTabs = forwardRef(({ items, refocus = true, ...carouselProps }, ref) => {
|
|
9
|
+
const panelNavigation = useResponsiveProp({ md: <CarouselTabsPanel items={items} /> })
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<Carousel refocus={refocus} {...carouselProps} ref={ref} panelNavigation={panelNavigation}>
|
|
13
|
+
{items.map(({ title, content }) => (
|
|
14
|
+
<React.Fragment key={title}>{content}</React.Fragment>
|
|
15
|
+
))}
|
|
16
|
+
</Carousel>
|
|
17
|
+
)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
CarouselTabs.displayName = 'CarouselTabs'
|
|
21
|
+
CarouselTabs.propTypes = {
|
|
22
|
+
/**
|
|
23
|
+
* An array of objects where title is the string shown in the slide's tab and content is the JSX for the slide itself
|
|
24
|
+
*/
|
|
25
|
+
items: PropTypes.arrayOf(
|
|
26
|
+
PropTypes.shape({
|
|
27
|
+
title: PropTypes.string.isRequired,
|
|
28
|
+
content: PropTypes.node.isRequired
|
|
29
|
+
})
|
|
30
|
+
),
|
|
31
|
+
...Carousel.propTypes
|
|
32
|
+
}
|
|
33
|
+
// CarouselTabs doesn't require `children` prop, it uses `items` instead.
|
|
34
|
+
// eslint-disable-next-line react/forbid-foreign-prop-types
|
|
35
|
+
if (CarouselTabs.propTypes?.children) delete CarouselTabs.propTypes?.children
|
|
36
|
+
|
|
37
|
+
export default CarouselTabs
|