@telus-uds/components-base 1.1.0 → 1.2.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/.ultra.cache.json +1 -1
- package/CHANGELOG.md +8 -0
- package/__fixtures__/Accessible.js +4 -2
- package/__fixtures__/Accessible.native.js +5 -2
- package/__fixtures__/testTheme.js +9 -0
- package/__tests__/HorizontalScroll/HorizontalScroll.test.jsx +1 -0
- package/__tests__/ToggleSwitch/ToggleSwitch.test.jsx +10 -0
- package/__tests__/ToggleSwitch/ToggleSwitchGroup.test.jsx +192 -0
- package/component-docs.json +614 -796
- package/lib/Button/ButtonBase.js +20 -6
- package/lib/Card/PressableCardBase.js +9 -3
- package/lib/Checkbox/Checkbox.js +0 -2
- package/lib/IconButton/IconButton.js +8 -3
- package/lib/Link/LinkBase.js +10 -3
- package/lib/Pagination/PageButton.js +3 -1
- package/lib/Pagination/Pagination.js +16 -4
- package/lib/Pagination/SideButton.js +3 -1
- package/lib/Radio/Radio.js +0 -2
- package/lib/Tabs/Tabs.js +12 -4
- package/lib/Tabs/TabsItem.js +12 -6
- package/lib/ToggleSwitch/ToggleSwitch.js +99 -37
- package/lib/ToggleSwitch/ToggleSwitchGroup.js +230 -0
- package/lib/ToggleSwitch/index.js +14 -4
- package/lib/index.js +13 -8
- package/lib/utils/index.js +10 -1
- package/lib/utils/propTypes.js +26 -1
- package/lib/utils/withLinkRouter.js +98 -0
- package/package.json +2 -2
- package/release-context.json +4 -4
- package/src/Button/ButtonBase.jsx +11 -4
- package/src/Card/PressableCardBase.jsx +6 -4
- package/src/Checkbox/Checkbox.jsx +0 -2
- package/src/IconButton/IconButton.jsx +6 -4
- package/src/Link/LinkBase.jsx +6 -4
- package/src/Pagination/PageButton.jsx +3 -2
- package/src/Pagination/Pagination.jsx +29 -2
- package/src/Pagination/SideButton.jsx +2 -2
- package/src/Radio/Radio.jsx +0 -2
- package/src/Tabs/Tabs.jsx +49 -22
- package/src/Tabs/TabsItem.jsx +11 -7
- package/src/ToggleSwitch/ToggleSwitch.jsx +92 -34
- package/src/ToggleSwitch/ToggleSwitchGroup.jsx +203 -0
- package/src/ToggleSwitch/index.js +2 -1
- package/src/index.js +1 -1
- package/src/utils/index.js +1 -0
- package/src/utils/propTypes.js +30 -0
- package/src/utils/withLinkRouter.jsx +68 -0
- package/stories/TextInput/TextArea.stories.jsx +1 -0
- package/stories/ToggleSwitch/ToggleSwitch.stories.jsx +5 -1
- package/stories/ToggleSwitch/ToggleSwitchGroup.stories.jsx +81 -0
package/src/Link/LinkBase.jsx
CHANGED
|
@@ -6,9 +6,11 @@ import {
|
|
|
6
6
|
hrefAttrsProp,
|
|
7
7
|
variantProp,
|
|
8
8
|
linkProps,
|
|
9
|
-
getTokensPropType
|
|
9
|
+
getTokensPropType,
|
|
10
|
+
clickProps
|
|
10
11
|
} from '../utils/propTypes'
|
|
11
12
|
import { resolvePressableTokens } from '../utils/pressability'
|
|
13
|
+
import { withLinkRouter } from '../utils'
|
|
12
14
|
|
|
13
15
|
import InlinePressable from './InlinePressable'
|
|
14
16
|
import { applyTextStyles, applyOuterBorder } from '../ThemeProvider'
|
|
@@ -92,7 +94,6 @@ const LinkBase = forwardRef(
|
|
|
92
94
|
(
|
|
93
95
|
{
|
|
94
96
|
href,
|
|
95
|
-
onPress,
|
|
96
97
|
icon,
|
|
97
98
|
iconPosition = icon ? 'left' : undefined,
|
|
98
99
|
iconProps,
|
|
@@ -101,10 +102,11 @@ const LinkBase = forwardRef(
|
|
|
101
102
|
children,
|
|
102
103
|
accessibilityRole = 'link',
|
|
103
104
|
dataSet,
|
|
104
|
-
...
|
|
105
|
+
...rawRest
|
|
105
106
|
},
|
|
106
107
|
ref
|
|
107
108
|
) => {
|
|
109
|
+
const { onPress, ...props } = clickProps.toPressProps(rawRest)
|
|
108
110
|
const { hrefAttrs, rest } = hrefAttrsProp.bundle(props)
|
|
109
111
|
const linkPropSet = linkProps.select({
|
|
110
112
|
accessibilityRole,
|
|
@@ -200,4 +202,4 @@ const staticStyles = StyleSheet.create({
|
|
|
200
202
|
}
|
|
201
203
|
})
|
|
202
204
|
|
|
203
|
-
export default LinkBase
|
|
205
|
+
export default withLinkRouter(LinkBase)
|
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
hrefAttrsProp,
|
|
11
11
|
linkProps,
|
|
12
12
|
selectTokens,
|
|
13
|
-
variantProp
|
|
13
|
+
variantProp,
|
|
14
|
+
withLinkRouter
|
|
14
15
|
} from '../utils'
|
|
15
16
|
|
|
16
17
|
import useCopy from '../utils/useCopy'
|
|
@@ -66,4 +67,4 @@ PageButton.propTypes = {
|
|
|
66
67
|
tokens: getTokensPropType('PaginationPageButton')
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
export default PageButton
|
|
70
|
+
export default withLinkRouter(PageButton)
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import React, { forwardRef } from 'react'
|
|
2
2
|
import { View, Text, StyleSheet } from 'react-native'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
componentPropType,
|
|
6
|
+
copyPropTypes,
|
|
7
|
+
getTokensPropType,
|
|
8
|
+
variantProp,
|
|
9
|
+
withLinkRouter
|
|
10
|
+
} from '../utils'
|
|
5
11
|
import { applyTextStyles, useThemeTokens } from '../ThemeProvider'
|
|
6
12
|
import { useViewport } from '../ViewportProvider'
|
|
7
13
|
import Box from '../Box'
|
|
@@ -20,7 +26,19 @@ const selectTextStyles = ({ color, fontName, fontSize, fontWeight, lineHeight })
|
|
|
20
26
|
})
|
|
21
27
|
|
|
22
28
|
const Pagination = forwardRef(
|
|
23
|
-
(
|
|
29
|
+
(
|
|
30
|
+
{
|
|
31
|
+
children,
|
|
32
|
+
copy = 'en',
|
|
33
|
+
variant,
|
|
34
|
+
tokens,
|
|
35
|
+
sideButtonVariant,
|
|
36
|
+
sideButtonTokens,
|
|
37
|
+
LinkRouter,
|
|
38
|
+
linkRouterProps
|
|
39
|
+
},
|
|
40
|
+
ref
|
|
41
|
+
) => {
|
|
24
42
|
const viewport = useViewport()
|
|
25
43
|
const { truncateAbove, gap, ...themeTokens } = useThemeTokens('Pagination', tokens, variant, {
|
|
26
44
|
viewport
|
|
@@ -58,16 +76,22 @@ const Pagination = forwardRef(
|
|
|
58
76
|
copy={copy}
|
|
59
77
|
variant={sideButtonVariant}
|
|
60
78
|
tokens={sideButtonTokens}
|
|
79
|
+
LinkRouter={LinkRouter}
|
|
80
|
+
linkRouterProps={linkRouterProps}
|
|
61
81
|
/>
|
|
62
82
|
),
|
|
63
83
|
...items.map((child, itemIndex) => {
|
|
64
84
|
const buttonLabel = `${itemIndex + 1}`
|
|
65
85
|
const itemProps = getItemProps(itemIndex)
|
|
86
|
+
const ItemLinkRouter = itemProps.LinkRouter ?? LinkRouter
|
|
87
|
+
const itemLinkRouterProps = { ...linkRouterProps, ...itemProps.linkRouterProps }
|
|
66
88
|
|
|
67
89
|
if (shouldRenderButton(itemIndex)) {
|
|
68
90
|
return (
|
|
69
91
|
<PageButton
|
|
70
92
|
{...itemProps}
|
|
93
|
+
LinkRouter={ItemLinkRouter}
|
|
94
|
+
linkRouterProps={itemLinkRouterProps}
|
|
71
95
|
label={buttonLabel}
|
|
72
96
|
copy={copy}
|
|
73
97
|
isActive={isItemActive(itemIndex)}
|
|
@@ -88,6 +112,8 @@ const Pagination = forwardRef(
|
|
|
88
112
|
copy={copy}
|
|
89
113
|
variant={sideButtonVariant}
|
|
90
114
|
tokens={sideButtonTokens}
|
|
115
|
+
LinkRouter={LinkRouter}
|
|
116
|
+
linkRouterProps={linkRouterProps}
|
|
91
117
|
/>
|
|
92
118
|
)
|
|
93
119
|
]
|
|
@@ -112,6 +138,7 @@ PageButton.displayName = 'PageButton'
|
|
|
112
138
|
Pagination.PageButton = PageButton
|
|
113
139
|
|
|
114
140
|
Pagination.propTypes = {
|
|
141
|
+
...withLinkRouter.propTypes,
|
|
115
142
|
children: componentPropType('PageButton'),
|
|
116
143
|
copy: copyPropTypes,
|
|
117
144
|
variant: variantProp.propType,
|
|
@@ -7,7 +7,7 @@ import ButtonBase from '../Button/ButtonBase'
|
|
|
7
7
|
import { IconText } from '../Icon'
|
|
8
8
|
import { useThemeTokensCallback } from '../ThemeProvider'
|
|
9
9
|
import { useViewport } from '../ViewportProvider'
|
|
10
|
-
import { copyPropTypes, hrefAttrsProp, linkProps, selectTokens } from '../utils'
|
|
10
|
+
import { copyPropTypes, hrefAttrsProp, linkProps, selectTokens, withLinkRouter } from '../utils'
|
|
11
11
|
|
|
12
12
|
import dictionary from './dictionary'
|
|
13
13
|
import useCopy from '../utils/useCopy'
|
|
@@ -82,4 +82,4 @@ SideButton.propTypes = {
|
|
|
82
82
|
...linkProps.types
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
export default SideButton
|
|
85
|
+
export default withLinkRouter(SideButton)
|
package/src/Radio/Radio.jsx
CHANGED
|
@@ -2,8 +2,6 @@ import React, { forwardRef } from 'react'
|
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
3
|
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
|
4
4
|
|
|
5
|
-
// @todo move `LabelContent` outside of the `InputLabel` and fix
|
|
6
|
-
// the issue with the cursor not being pointer on Web
|
|
7
5
|
import RadioLabel from '../InputLabel/LabelContent'
|
|
8
6
|
import RadioButton, { selectRadioButtonTokens } from './RadioButton'
|
|
9
7
|
import { applyShadowToken, applyTextStyles, useThemeTokensCallback } from '../ThemeProvider'
|
package/src/Tabs/Tabs.jsx
CHANGED
|
@@ -4,7 +4,7 @@ import ABBPropTypes from 'airbnb-prop-types'
|
|
|
4
4
|
|
|
5
5
|
import { useThemeTokens } from '../ThemeProvider'
|
|
6
6
|
import StackView from '../StackView'
|
|
7
|
-
import { getTokensPropType, variantProp, useHash, useInputValue } from '../utils'
|
|
7
|
+
import { getTokensPropType, variantProp, useHash, useInputValue, withLinkRouter } from '../utils'
|
|
8
8
|
import HorizontalScroll, {
|
|
9
9
|
horizontalScrollUtils,
|
|
10
10
|
HorizontalScrollButton
|
|
@@ -19,7 +19,18 @@ const { selectHorizontalScrollTokens, useItemPositions } = horizontalScrollUtils
|
|
|
19
19
|
*/
|
|
20
20
|
const Tabs = forwardRef(
|
|
21
21
|
(
|
|
22
|
-
{
|
|
22
|
+
{
|
|
23
|
+
tokens,
|
|
24
|
+
itemTokens,
|
|
25
|
+
scrollButtonTokens,
|
|
26
|
+
variant,
|
|
27
|
+
value,
|
|
28
|
+
initialValue,
|
|
29
|
+
onChange,
|
|
30
|
+
items = [],
|
|
31
|
+
LinkRouter,
|
|
32
|
+
linkRouterProps
|
|
33
|
+
},
|
|
23
34
|
ref
|
|
24
35
|
) => {
|
|
25
36
|
const { space, ...themeTokens } = useThemeTokens('Tabs', tokens, variant)
|
|
@@ -49,26 +60,40 @@ const Tabs = forwardRef(
|
|
|
49
60
|
accessibilityRole="tablist"
|
|
50
61
|
>
|
|
51
62
|
<StackView space={space} direction="row">
|
|
52
|
-
{items.map(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
ref
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
63
|
+
{items.map(
|
|
64
|
+
(
|
|
65
|
+
{
|
|
66
|
+
href,
|
|
67
|
+
label,
|
|
68
|
+
id,
|
|
69
|
+
ref: itemRef,
|
|
70
|
+
LinkRouter: ItemLinkRouter = LinkRouter,
|
|
71
|
+
linkRouterProps: itemLinkRouterProps
|
|
72
|
+
},
|
|
73
|
+
index
|
|
74
|
+
) => {
|
|
75
|
+
const itemId = id ?? label
|
|
76
|
+
const isSelected = Boolean(currentValue && currentValue === itemId)
|
|
77
|
+
const handlePress = (event) => setValue(itemId, event)
|
|
78
|
+
return (
|
|
79
|
+
<TabsItem
|
|
80
|
+
ref={itemRef}
|
|
81
|
+
key={itemId}
|
|
82
|
+
href={href}
|
|
83
|
+
variant={variant}
|
|
84
|
+
tokens={itemTokens}
|
|
85
|
+
onPress={handlePress}
|
|
86
|
+
selected={isSelected}
|
|
87
|
+
itemPositions={itemPositions}
|
|
88
|
+
index={index}
|
|
89
|
+
LinkRouter={ItemLinkRouter}
|
|
90
|
+
linkRouterProps={{ ...linkRouterProps, ...itemLinkRouterProps }}
|
|
91
|
+
>
|
|
92
|
+
{label}
|
|
93
|
+
</TabsItem>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
)}
|
|
72
97
|
</StackView>
|
|
73
98
|
</HorizontalScroll>
|
|
74
99
|
)
|
|
@@ -77,8 +102,10 @@ const Tabs = forwardRef(
|
|
|
77
102
|
Tabs.displayName = 'Tabs'
|
|
78
103
|
|
|
79
104
|
Tabs.propTypes = {
|
|
105
|
+
...withLinkRouter.PropTypes,
|
|
80
106
|
items: PropTypes.arrayOf(
|
|
81
107
|
PropTypes.shape({
|
|
108
|
+
...withLinkRouter.PropTypes,
|
|
82
109
|
href: PropTypes.string,
|
|
83
110
|
label: PropTypes.string,
|
|
84
111
|
id: PropTypes.string,
|
package/src/Tabs/TabsItem.jsx
CHANGED
|
@@ -9,7 +9,9 @@ import {
|
|
|
9
9
|
variantProp,
|
|
10
10
|
getTokensPropType,
|
|
11
11
|
linkProps,
|
|
12
|
-
a11yProps
|
|
12
|
+
a11yProps,
|
|
13
|
+
clickProps,
|
|
14
|
+
withLinkRouter
|
|
13
15
|
} from '../utils'
|
|
14
16
|
import Spacer from '../Spacer'
|
|
15
17
|
import { horizontalScrollUtils } from '../HorizontalScroll'
|
|
@@ -73,7 +75,6 @@ const selectContainerStyles = ({
|
|
|
73
75
|
const TabsItem = forwardRef(
|
|
74
76
|
(
|
|
75
77
|
{
|
|
76
|
-
onPress,
|
|
77
78
|
href,
|
|
78
79
|
variant,
|
|
79
80
|
tokens,
|
|
@@ -86,10 +87,13 @@ const TabsItem = forwardRef(
|
|
|
86
87
|
? // Web links can't be aria-selected but can be aria-current
|
|
87
88
|
{ current: selected ? 'page' : false }
|
|
88
89
|
: { selected },
|
|
89
|
-
...
|
|
90
|
+
...rawRest
|
|
90
91
|
},
|
|
91
92
|
ref
|
|
92
93
|
) => {
|
|
94
|
+
// Convert onClick etc to onPress etc if used in an integration
|
|
95
|
+
const { onPress, ...rest } = clickProps.toPressProps(rawRest)
|
|
96
|
+
|
|
93
97
|
const getTokens = useThemeTokensCallback('TabsItem', tokens, variant)
|
|
94
98
|
const resolveTokens = (pressableState) =>
|
|
95
99
|
resolvePressableTokens(getTokens, pressableState, { selected })
|
|
@@ -105,9 +109,9 @@ const TabsItem = forwardRef(
|
|
|
105
109
|
const openHref = href && linkProps.handleHref({ href })
|
|
106
110
|
const handlePress =
|
|
107
111
|
onPress || openHref
|
|
108
|
-
? () => {
|
|
109
|
-
if (onPress) onPress()
|
|
110
|
-
if (openHref) openHref()
|
|
112
|
+
? (...args) => {
|
|
113
|
+
if (onPress) onPress(...args)
|
|
114
|
+
if (openHref) openHref(...args)
|
|
111
115
|
}
|
|
112
116
|
: undefined
|
|
113
117
|
|
|
@@ -209,4 +213,4 @@ const staticStyles = StyleSheet.create({
|
|
|
209
213
|
}
|
|
210
214
|
})
|
|
211
215
|
|
|
212
|
-
export default TabsItem
|
|
216
|
+
export default withLinkRouter(TabsItem)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React, { forwardRef } from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
|
-
import { Platform,
|
|
3
|
+
import { Platform, StyleSheet, View } from 'react-native'
|
|
4
4
|
|
|
5
|
+
import InputLabel from '../InputLabel'
|
|
5
6
|
import ButtonBase from '../Button/ButtonBase'
|
|
7
|
+
import StackView from '../StackView'
|
|
6
8
|
import { useThemeTokensCallback, applyShadowToken } from '../ThemeProvider'
|
|
7
9
|
import {
|
|
8
10
|
a11yProps,
|
|
@@ -12,6 +14,7 @@ import {
|
|
|
12
14
|
selectTokens
|
|
13
15
|
} from '../utils/propTypes'
|
|
14
16
|
import { useInputValue } from '../utils/input'
|
|
17
|
+
import { useUniqueId } from '../utils'
|
|
15
18
|
|
|
16
19
|
const selectButtonTokens = (tokens) =>
|
|
17
20
|
selectTokens('Button', {
|
|
@@ -54,12 +57,40 @@ const selectSwitchStyles = ({
|
|
|
54
57
|
})
|
|
55
58
|
})
|
|
56
59
|
|
|
60
|
+
const selectLabelStyles = ({ labelMarginLeft }) => ({ marginLeft: labelMarginLeft })
|
|
61
|
+
const selectLabelTokens = ({
|
|
62
|
+
labelColor,
|
|
63
|
+
labelFontName,
|
|
64
|
+
labelFontSize,
|
|
65
|
+
labelFontWeight,
|
|
66
|
+
labelLineHeight
|
|
67
|
+
}) => ({
|
|
68
|
+
color: labelColor,
|
|
69
|
+
fontName: labelFontName,
|
|
70
|
+
fontWeight: labelFontWeight,
|
|
71
|
+
fontSize: labelFontSize,
|
|
72
|
+
lineHeight: labelLineHeight
|
|
73
|
+
})
|
|
74
|
+
|
|
57
75
|
const ToggleSwitch = forwardRef(
|
|
58
76
|
(
|
|
59
|
-
{
|
|
77
|
+
{
|
|
78
|
+
value,
|
|
79
|
+
initialValue,
|
|
80
|
+
onChange,
|
|
81
|
+
id,
|
|
82
|
+
label,
|
|
83
|
+
inactive,
|
|
84
|
+
tokens,
|
|
85
|
+
tooltip,
|
|
86
|
+
variant,
|
|
87
|
+
accessibilityRole = 'switch',
|
|
88
|
+
accessibilityLabel = label
|
|
89
|
+
},
|
|
60
90
|
ref
|
|
61
91
|
) => {
|
|
62
92
|
const getTokens = useThemeTokensCallback('ToggleSwitch', tokens, variant)
|
|
93
|
+
const themeTokens = getTokens()
|
|
63
94
|
|
|
64
95
|
const { currentValue, setValue } = useInputValue({
|
|
65
96
|
value,
|
|
@@ -68,43 +99,58 @@ const ToggleSwitch = forwardRef(
|
|
|
68
99
|
})
|
|
69
100
|
|
|
70
101
|
const handlePress = (event) => setValue(!currentValue, event)
|
|
71
|
-
|
|
72
102
|
const getButtonTokens = (buttonState) => selectButtonTokens(getTokens(buttonState))
|
|
103
|
+
const uniqueId = useUniqueId('toggleSwitch')
|
|
104
|
+
const inputId = id ?? uniqueId
|
|
73
105
|
|
|
74
106
|
return (
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
107
|
+
<StackView space={2} direction="row">
|
|
108
|
+
{Boolean(label) && (
|
|
109
|
+
<View style={selectLabelStyles(themeTokens)}>
|
|
110
|
+
<InputLabel
|
|
111
|
+
forId={inputId}
|
|
112
|
+
label={label}
|
|
113
|
+
tokens={selectLabelTokens(themeTokens)}
|
|
114
|
+
tooltip={tooltip}
|
|
115
|
+
/>
|
|
116
|
+
</View>
|
|
117
|
+
)}
|
|
118
|
+
<ButtonBase
|
|
119
|
+
id={id}
|
|
120
|
+
ref={ref}
|
|
121
|
+
selected={currentValue}
|
|
122
|
+
inactive={inactive}
|
|
123
|
+
tokens={getButtonTokens}
|
|
124
|
+
accessibilityLabel={accessibilityLabel}
|
|
125
|
+
accessibilityRole={accessibilityRole}
|
|
126
|
+
accessibilityState={{ checked: currentValue }}
|
|
127
|
+
onPress={handlePress}
|
|
128
|
+
>
|
|
129
|
+
{(buttonState) => {
|
|
130
|
+
const stateTokens = getTokens(buttonState)
|
|
131
|
+
const IconComponent = stateTokens.icon
|
|
132
|
+
const switchStyles = selectSwitchStyles(stateTokens)
|
|
133
|
+
const trackStyles = selectTrackStyles(stateTokens)
|
|
134
|
+
const iconTokens = selectIconTokens(stateTokens)
|
|
90
135
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
136
|
+
// If drag-slide support is needed, use a PanResponder and apply these to an Animated value.
|
|
137
|
+
// Use translate transforms for smoothest non-thread-blocking animations and to allow drag.
|
|
138
|
+
const slideStart = 0
|
|
139
|
+
const slideEnd =
|
|
140
|
+
stateTokens.width - stateTokens.switchSize - stateTokens.trackBorderWidth * 2
|
|
141
|
+
const switchOffset = buttonState.selected ? slideEnd : slideStart
|
|
142
|
+
const switchPositionStyle = { transform: [{ translateX: switchOffset }] }
|
|
98
143
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
144
|
+
return (
|
|
145
|
+
<View style={[staticStyles.track, trackStyles]}>
|
|
146
|
+
<View style={[staticStyles.switch, switchStyles, switchPositionStyle]}>
|
|
147
|
+
{IconComponent && <IconComponent {...iconTokens} />}
|
|
148
|
+
</View>
|
|
103
149
|
</View>
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
</
|
|
150
|
+
)
|
|
151
|
+
}}
|
|
152
|
+
</ButtonBase>
|
|
153
|
+
</StackView>
|
|
108
154
|
)
|
|
109
155
|
}
|
|
110
156
|
)
|
|
@@ -131,11 +177,23 @@ ToggleSwitch.propTypes = {
|
|
|
131
177
|
* this should always be passed and used to control the state of the switch.
|
|
132
178
|
*/
|
|
133
179
|
onChange: PropTypes.func,
|
|
180
|
+
/**
|
|
181
|
+
* Input ID.
|
|
182
|
+
*/
|
|
183
|
+
id: PropTypes.string,
|
|
184
|
+
/**
|
|
185
|
+
* An optional label.
|
|
186
|
+
*/
|
|
187
|
+
label: PropTypes.string,
|
|
134
188
|
/**
|
|
135
189
|
* If passed, the switch does not respond to user input and may recieve different
|
|
136
190
|
* theme tokens if the theme supports inactive appearance.
|
|
137
191
|
*/
|
|
138
|
-
inactive: PropTypes.bool
|
|
192
|
+
inactive: PropTypes.bool,
|
|
193
|
+
/**
|
|
194
|
+
* Content of an optional Tooltip. If set, a tooltip button will be shown next to the label.
|
|
195
|
+
*/
|
|
196
|
+
tooltip: PropTypes.string
|
|
139
197
|
}
|
|
140
198
|
|
|
141
199
|
const staticStyles = StyleSheet.create({
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import ABBPropTypes from 'airbnb-prop-types'
|
|
4
|
+
import { Platform } from 'react-native'
|
|
5
|
+
|
|
6
|
+
import ToggleSwitch from './ToggleSwitch'
|
|
7
|
+
import Fieldset from '../Fieldset'
|
|
8
|
+
import { getStackedContent } from '../StackView'
|
|
9
|
+
import { useViewport } from '../ViewportProvider'
|
|
10
|
+
import { useThemeTokens } from '../ThemeProvider'
|
|
11
|
+
import { a11yProps, pressProps, variantProp, getTokensPropType } from '../utils/propTypes'
|
|
12
|
+
import { useMultipleInputValues } from '../utils/input'
|
|
13
|
+
|
|
14
|
+
const ToggleSwitchGroup = forwardRef(
|
|
15
|
+
(
|
|
16
|
+
{
|
|
17
|
+
variant,
|
|
18
|
+
tokens,
|
|
19
|
+
items = [],
|
|
20
|
+
values,
|
|
21
|
+
initialValues,
|
|
22
|
+
maxValues = 1,
|
|
23
|
+
onChange,
|
|
24
|
+
readOnly = false,
|
|
25
|
+
inactive = false,
|
|
26
|
+
feedback,
|
|
27
|
+
hint,
|
|
28
|
+
tooltip,
|
|
29
|
+
legend,
|
|
30
|
+
name: inputGroupName,
|
|
31
|
+
accessibilityRole = maxValues === 1
|
|
32
|
+
? 'radiogroup' // radiogroup is cross-platform; only web aria has generic groups
|
|
33
|
+
: Platform.select({ web: 'group', default: 'none' }),
|
|
34
|
+
toggleSwitchTokens,
|
|
35
|
+
validation,
|
|
36
|
+
...rest
|
|
37
|
+
},
|
|
38
|
+
ref
|
|
39
|
+
) => {
|
|
40
|
+
const viewport = useViewport()
|
|
41
|
+
|
|
42
|
+
const { space, fieldSpace } = useThemeTokens('ToggleSwitchGroup', tokens, variant, {
|
|
43
|
+
viewport
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const { currentValues, toggleOneValue } = useMultipleInputValues({
|
|
47
|
+
initialValues,
|
|
48
|
+
values,
|
|
49
|
+
maxValues,
|
|
50
|
+
onChange,
|
|
51
|
+
readOnly
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const a11y = a11yProps.select({
|
|
55
|
+
accessibilityRole,
|
|
56
|
+
...rest
|
|
57
|
+
})
|
|
58
|
+
const itemA11yRole = a11y.accessibilityRole === 'radiogroup' ? 'radio' : 'switch'
|
|
59
|
+
|
|
60
|
+
const toggleSwitches = items.map(
|
|
61
|
+
(
|
|
62
|
+
{
|
|
63
|
+
label,
|
|
64
|
+
id = label,
|
|
65
|
+
accessibilityLabel = label,
|
|
66
|
+
onChange: itemOnChange,
|
|
67
|
+
ref: itemRef,
|
|
68
|
+
tooltip: itemTooltip
|
|
69
|
+
},
|
|
70
|
+
index
|
|
71
|
+
) => {
|
|
72
|
+
const isSelected = currentValues.includes(id)
|
|
73
|
+
|
|
74
|
+
const handleChange = (newCheckedState, event) => {
|
|
75
|
+
if (typeof itemOnChange === 'function') itemOnChange(newCheckedState, event)
|
|
76
|
+
toggleOneValue(id, event)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const itemA11y = {
|
|
80
|
+
accessibilityState: { checked: isSelected },
|
|
81
|
+
accessibilityRole: itemA11yRole,
|
|
82
|
+
accessibilityLabel,
|
|
83
|
+
...a11yProps.getPositionInSet(items.length, index)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<ToggleSwitch
|
|
88
|
+
id={id}
|
|
89
|
+
ref={itemRef}
|
|
90
|
+
key={id}
|
|
91
|
+
onChange={handleChange}
|
|
92
|
+
tokens={toggleSwitchTokens}
|
|
93
|
+
value={isSelected}
|
|
94
|
+
inactive={inactive}
|
|
95
|
+
label={label}
|
|
96
|
+
tooltip={itemTooltip}
|
|
97
|
+
{...itemA11y}
|
|
98
|
+
/>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<Fieldset
|
|
105
|
+
ref={ref}
|
|
106
|
+
name={inputGroupName}
|
|
107
|
+
legend={legend}
|
|
108
|
+
tooltip={tooltip}
|
|
109
|
+
hint={hint}
|
|
110
|
+
space={fieldSpace}
|
|
111
|
+
feedback={feedback}
|
|
112
|
+
inactive={inactive}
|
|
113
|
+
validation={validation}
|
|
114
|
+
{...a11y}
|
|
115
|
+
>
|
|
116
|
+
{getStackedContent(toggleSwitches, { space, direction: 'column' })}
|
|
117
|
+
</Fieldset>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
ToggleSwitchGroup.displayName = 'ToggleSwitchGroup'
|
|
122
|
+
|
|
123
|
+
ToggleSwitchGroup.propTypes = {
|
|
124
|
+
...a11yProps.propTypes,
|
|
125
|
+
...pressProps.propTypes,
|
|
126
|
+
tokens: getTokensPropType('ToggleSwitchGroup'),
|
|
127
|
+
variant: variantProp.propType,
|
|
128
|
+
/**
|
|
129
|
+
* The maximum number of items a user may select at once. Defaults to 1 and behaves
|
|
130
|
+
* like radio buttons. To have no limit and allow any number of selections, pass `null`.
|
|
131
|
+
*/
|
|
132
|
+
maxValues: PropTypes.number,
|
|
133
|
+
/**
|
|
134
|
+
* The options a user may select
|
|
135
|
+
*/
|
|
136
|
+
items: PropTypes.arrayOf(
|
|
137
|
+
PropTypes.shape({
|
|
138
|
+
/**
|
|
139
|
+
* The text displayed to the user on the label.
|
|
140
|
+
*/
|
|
141
|
+
label: PropTypes.string.isRequired,
|
|
142
|
+
/**
|
|
143
|
+
* An optional accessibility label may be passed to each ToggleSwitch
|
|
144
|
+
* and will be applied as normal for a React Native accessibilityLabel prop.
|
|
145
|
+
*/
|
|
146
|
+
accessibilityLabel: PropTypes.string,
|
|
147
|
+
/**
|
|
148
|
+
* An optional unique string may be provided to identify this option,
|
|
149
|
+
* which will be used in code and passed to any onChange function.
|
|
150
|
+
* If not provided, the label is used.
|
|
151
|
+
*/
|
|
152
|
+
id: PropTypes.string,
|
|
153
|
+
/**
|
|
154
|
+
* An optional ref for one individual ToggleSwitch in the ToggleSwitchGroup
|
|
155
|
+
*/
|
|
156
|
+
ref: ABBPropTypes.ref()
|
|
157
|
+
})
|
|
158
|
+
),
|
|
159
|
+
/**
|
|
160
|
+
* If provided, this function is called when the current selection is changed
|
|
161
|
+
* and is passed an array of the `id`s of all currently selected `items`.
|
|
162
|
+
*/
|
|
163
|
+
onChange: PropTypes.func,
|
|
164
|
+
/**
|
|
165
|
+
* If the selected item(s) in the toggle switch group are to be controlled externally by
|
|
166
|
+
* a parent component, pass an array of strings as well as an `onChange` handler.
|
|
167
|
+
* Passing an array for "values" makes the ToggleSwitchGroup a "controlled" component that
|
|
168
|
+
* expects its state to be handled via `onChange` and so doesn't handle it itself.
|
|
169
|
+
*/
|
|
170
|
+
values: PropTypes.arrayOf(PropTypes.string),
|
|
171
|
+
/**
|
|
172
|
+
* If `values` is not passed, making the ToggleSwitchGroup an "uncontrolled" component
|
|
173
|
+
* managing its own selected state, a default set of selections may be provided.
|
|
174
|
+
* Changing the `initialValues` does not change the user's selections.
|
|
175
|
+
*/
|
|
176
|
+
initialValues: PropTypes.arrayOf(PropTypes.string),
|
|
177
|
+
/**
|
|
178
|
+
* Optional additional text giving more detail to help a user make a choice.
|
|
179
|
+
*/
|
|
180
|
+
hint: PropTypes.string,
|
|
181
|
+
/**
|
|
182
|
+
* Optional tooltip text content to include alongside the legend and hint.
|
|
183
|
+
*/
|
|
184
|
+
tooltip: PropTypes.string,
|
|
185
|
+
/**
|
|
186
|
+
* If provided, a Feedback element is rendered containing this text.
|
|
187
|
+
*/
|
|
188
|
+
feedback: PropTypes.string,
|
|
189
|
+
/**
|
|
190
|
+
* Main text used to describe this group, used in Fieldset's Legend element.
|
|
191
|
+
*/
|
|
192
|
+
legend: PropTypes.string,
|
|
193
|
+
/**
|
|
194
|
+
* Toggle switch token overrides.
|
|
195
|
+
*/
|
|
196
|
+
toggleSwitchTokens: getTokensPropType('ToggleSwitch'),
|
|
197
|
+
/**
|
|
198
|
+
* Current validation status of the group, passed to the feedback element if there is one.
|
|
199
|
+
*/
|
|
200
|
+
validation: PropTypes.oneOf(['error', 'success'])
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export default ToggleSwitchGroup
|