@telus-uds/components-base 1.0.1 → 1.3.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.
Files changed (193) hide show
  1. package/.storybook/main.js +4 -0
  2. package/.storybook/preview.js +37 -0
  3. package/.turbo/turbo-build.log +5 -0
  4. package/CHANGELOG.md +32 -0
  5. package/README.md +1 -1
  6. package/__fixtures__/Accessible.js +4 -2
  7. package/__fixtures__/Accessible.native.js +5 -2
  8. package/__fixtures__/testTheme.js +9 -0
  9. package/__tests__/FlexGrid/Col.test.jsx +6 -10
  10. package/__tests__/HorizontalScroll/HorizontalScroll.test.jsx +1 -0
  11. package/__tests__/ToggleSwitch/ToggleSwitch.test.jsx +10 -0
  12. package/__tests__/ToggleSwitch/ToggleSwitchGroup.test.jsx +192 -0
  13. package/__tests__/utils/props.test.js +36 -0
  14. package/__tests__/utils/semantics.test.jsx +1 -10
  15. package/babel.config.js +9 -16
  16. package/component-docs.json +10142 -0
  17. package/generate-component-docs.js +59 -0
  18. package/lib/A11yText/index.js +2 -2
  19. package/lib/ActivityIndicator/index.js +3 -3
  20. package/lib/Box/Box.js +4 -4
  21. package/lib/Button/Button.js +2 -2
  22. package/lib/Button/ButtonBase.js +20 -6
  23. package/lib/Button/ButtonGroup.js +10 -12
  24. package/lib/Button/ButtonLink.js +4 -4
  25. package/lib/Button/propTypes.js +4 -2
  26. package/lib/Card/Card.js +2 -2
  27. package/lib/Card/CardBase.js +6 -9
  28. package/lib/Card/PressableCardBase.js +12 -6
  29. package/lib/Checkbox/Checkbox.js +3 -5
  30. package/lib/Divider/Divider.js +2 -2
  31. package/lib/FlexGrid/FlexGrid.js +4 -4
  32. package/lib/FlexGrid/helpers/index.js +1 -4
  33. package/lib/IconButton/IconButton.js +11 -6
  34. package/lib/Link/LinkBase.js +19 -12
  35. package/lib/List/List.js +4 -5
  36. package/lib/List/ListItem.js +14 -27
  37. package/lib/List/index.js +15 -0
  38. package/lib/Pagination/PageButton.js +4 -8
  39. package/lib/Pagination/Pagination.js +16 -4
  40. package/lib/Pagination/SideButton.js +3 -1
  41. package/lib/Progress/Progress.js +2 -2
  42. package/lib/Progress/ProgressBar.js +2 -2
  43. package/lib/Radio/Radio.js +3 -5
  44. package/lib/Spacer/Spacer.js +2 -2
  45. package/lib/StackView/StackWrap.js +9 -5
  46. package/lib/StackView/getStackedContent.js +1 -1
  47. package/lib/StepTracker/StepTracker.js +2 -2
  48. package/lib/Tabs/Tabs.js +12 -4
  49. package/lib/Tabs/TabsItem.js +12 -6
  50. package/lib/Tags/Tags.js +9 -9
  51. package/lib/ThemeProvider/useThemeTokens.js +3 -3
  52. package/lib/ThemeProvider/utils/theme-tokens.js +3 -3
  53. package/lib/ToggleSwitch/ToggleSwitch.js +105 -43
  54. package/lib/ToggleSwitch/ToggleSwitchGroup.js +230 -0
  55. package/lib/ToggleSwitch/index.js +14 -4
  56. package/lib/index.js +28 -9
  57. package/lib/utils/a11y/semantics.js +4 -3
  58. package/lib/utils/index.js +14 -5
  59. package/lib/utils/pressability.js +2 -2
  60. package/lib/utils/props/a11yProps.js +153 -0
  61. package/lib/utils/props/clickProps.js +36 -0
  62. package/lib/utils/props/componentPropType.js +70 -0
  63. package/lib/utils/props/copyPropTypes.js +14 -0
  64. package/lib/utils/props/getPropSelector.js +13 -0
  65. package/lib/utils/props/hrefAttrsProp.js +41 -0
  66. package/lib/utils/props/index.js +149 -0
  67. package/lib/utils/props/linkProps.js +64 -0
  68. package/lib/utils/props/paddingProp.js +20 -0
  69. package/lib/utils/props/pressProps.js +57 -0
  70. package/lib/utils/props/rectProp.js +20 -0
  71. package/lib/utils/props/responsiveProps.js +40 -0
  72. package/lib/utils/props/selectSystemProps.js +31 -0
  73. package/lib/utils/props/spacingProps.js +71 -0
  74. package/lib/utils/props/tokens.js +145 -0
  75. package/lib/utils/props/variantProp.js +28 -0
  76. package/lib/utils/props/viewProps.js +34 -0
  77. package/lib/utils/useResponsiveProp.js +1 -1
  78. package/lib/utils/useSpacingScale.js +4 -4
  79. package/lib/utils/withLinkRouter.js +98 -0
  80. package/package.json +9 -5
  81. package/release-context.json +4 -4
  82. package/src/A11yText/index.jsx +1 -1
  83. package/src/ActivityIndicator/index.jsx +1 -1
  84. package/src/Box/Box.jsx +5 -4
  85. package/src/Button/Button.jsx +1 -1
  86. package/src/Button/ButtonBase.jsx +11 -4
  87. package/src/Button/ButtonGroup.jsx +17 -8
  88. package/src/Button/ButtonLink.jsx +1 -1
  89. package/src/Button/propTypes.js +2 -1
  90. package/src/Card/Card.jsx +1 -1
  91. package/src/Card/CardBase.jsx +6 -5
  92. package/src/Card/PressableCardBase.jsx +7 -5
  93. package/src/Checkbox/Checkbox.jsx +1 -3
  94. package/src/Divider/Divider.jsx +2 -2
  95. package/src/FlexGrid/FlexGrid.jsx +11 -5
  96. package/src/FlexGrid/helpers/index.js +1 -3
  97. package/src/IconButton/IconButton.jsx +7 -5
  98. package/src/Link/LinkBase.jsx +7 -5
  99. package/src/List/List.jsx +2 -4
  100. package/src/List/ListItem.jsx +11 -26
  101. package/src/List/index.js +5 -0
  102. package/src/Pagination/PageButton.jsx +5 -8
  103. package/src/Pagination/Pagination.jsx +29 -2
  104. package/src/Pagination/SideButton.jsx +2 -2
  105. package/src/Progress/Progress.jsx +1 -1
  106. package/src/Progress/ProgressBar.jsx +1 -1
  107. package/src/Radio/Radio.jsx +1 -3
  108. package/src/Spacer/Spacer.jsx +2 -2
  109. package/src/StackView/StackWrap.jsx +7 -6
  110. package/src/StackView/getStackedContent.jsx +1 -1
  111. package/src/StepTracker/StepTracker.jsx +1 -1
  112. package/src/Tabs/Tabs.jsx +49 -22
  113. package/src/Tabs/TabsItem.jsx +11 -7
  114. package/src/Tags/Tags.jsx +1 -7
  115. package/src/ThemeProvider/useThemeTokens.js +3 -3
  116. package/src/ThemeProvider/utils/theme-tokens.js +3 -3
  117. package/src/ToggleSwitch/ToggleSwitch.jsx +93 -41
  118. package/src/ToggleSwitch/ToggleSwitchGroup.jsx +203 -0
  119. package/src/ToggleSwitch/index.js +2 -1
  120. package/src/index.js +2 -2
  121. package/src/utils/a11y/semantics.js +3 -2
  122. package/src/utils/index.js +2 -1
  123. package/src/utils/pressability.js +1 -1
  124. package/src/utils/props/a11yProps.js +151 -0
  125. package/src/utils/props/clickProps.js +31 -0
  126. package/src/utils/props/componentPropType.js +67 -0
  127. package/src/utils/props/copyPropTypes.js +3 -0
  128. package/src/utils/props/getPropSelector.js +14 -0
  129. package/src/utils/props/hrefAttrsProp.js +25 -0
  130. package/src/utils/props/index.js +15 -0
  131. package/src/utils/props/linkProps.js +43 -0
  132. package/src/utils/props/paddingProp.js +10 -0
  133. package/src/utils/props/pressProps.js +45 -0
  134. package/src/utils/props/rectProp.js +10 -0
  135. package/src/utils/props/responsiveProps.js +30 -0
  136. package/src/utils/props/selectSystemProps.js +25 -0
  137. package/src/utils/props/spacingProps.js +58 -0
  138. package/src/utils/props/tokens.js +150 -0
  139. package/src/utils/props/variantProp.js +20 -0
  140. package/src/utils/props/viewProps.js +23 -0
  141. package/src/utils/useResponsiveProp.js +1 -1
  142. package/src/utils/useSpacingScale.js +4 -4
  143. package/src/utils/withLinkRouter.jsx +68 -0
  144. package/stories/A11yText/A11yText.stories.jsx +1 -1
  145. package/stories/ActivityIndicator/ActivityIndicator.stories.jsx +1 -1
  146. package/stories/Box/Box.stories.jsx +1 -1
  147. package/stories/Button/Button.stories.jsx +1 -1
  148. package/stories/Button/ButtonGroup.stories.jsx +1 -1
  149. package/stories/Button/ButtonLink.stories.jsx +1 -1
  150. package/stories/Card/Card.stories.jsx +1 -1
  151. package/stories/Checkbox/Checkbox.stories.jsx +1 -1
  152. package/stories/Divider/Divider.stories.jsx +1 -1
  153. package/stories/ExpandCollapse/ExpandCollapse.stories.jsx +1 -1
  154. package/stories/Feedback/Feedback.stories.jsx +1 -1
  155. package/stories/FlexGrid/01 FlexGrid.stories.jsx +1 -1
  156. package/stories/FlexGrid/02 Row.stories.jsx +1 -1
  157. package/stories/FlexGrid/03 Col.stories.jsx +1 -1
  158. package/stories/Icon/Icon.stories.jsx +1 -1
  159. package/stories/IconButton/IconButton.stories.jsx +1 -1
  160. package/stories/InputLabel/InputLabel.stories.jsx +1 -1
  161. package/stories/Link/ChevronLink.stories.jsx +1 -1
  162. package/stories/Link/Link.stories.jsx +1 -1
  163. package/stories/Link/TextButton.stories.jsx +1 -1
  164. package/stories/List/List.stories.jsx +1 -1
  165. package/stories/Modal/Modal.stories.jsx +1 -1
  166. package/stories/Notification/Notification.stories.jsx +1 -1
  167. package/stories/Pagination/Pagination.stories.jsx +1 -1
  168. package/stories/Progress/Progress.stories.jsx +1 -1
  169. package/stories/Radio/Radio.stories.jsx +1 -1
  170. package/stories/RadioCard/RadioCard.stories.jsx +1 -1
  171. package/stories/Search/Search.stories.jsx +1 -1
  172. package/stories/Select/Select.stories.jsx +1 -1
  173. package/stories/SideNav/SideNav.stories.jsx +1 -1
  174. package/stories/SideNav/SideNavItem.stories.jsx +1 -1
  175. package/stories/SideNav/SideNavItemsGroup.stories.jsx +1 -1
  176. package/stories/Skeleton/Skeleton.stories.jsx +1 -1
  177. package/stories/Spacer/Spacer.stories.jsx +1 -1
  178. package/stories/StackView/StackView.stories.jsx +1 -1
  179. package/stories/StackView/StackWrap.stories.jsx +1 -1
  180. package/stories/StepTracker/StepTracker.stories.jsx +1 -1
  181. package/stories/Tabs/Tabs.stories.jsx +1 -1
  182. package/stories/Tags/Tags.stories.jsx +1 -1
  183. package/stories/TextInput/TextArea.stories.jsx +2 -1
  184. package/stories/TextInput/TextInput.stories.jsx +1 -1
  185. package/stories/ToggleSwitch/ToggleSwitch.stories.jsx +6 -2
  186. package/stories/ToggleSwitch/ToggleSwitchGroup.stories.jsx +81 -0
  187. package/stories/Tooltip/Tooltip.stories.jsx +1 -1
  188. package/stories/TooltipButton/TooltipButton.stories.jsx +1 -1
  189. package/stories/Typography/Typography.stories.jsx +1 -1
  190. package/stories/supports.jsx +2 -3
  191. package/.ultra.cache.json +0 -1
  192. package/lib/utils/propTypes.js +0 -623
  193. package/src/utils/propTypes.js +0 -640
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
- { tokens, itemTokens, scrollButtonTokens, variant, value, initialValue, onChange, items = [] },
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(({ href, label, id, ref: itemRef }, index) => {
53
- const itemId = id ?? label
54
- const isSelected = Boolean(currentValue && currentValue === itemId)
55
- const handlePress = (event) => setValue(itemId, event)
56
- return (
57
- <TabsItem
58
- ref={itemRef}
59
- key={itemId}
60
- href={href}
61
- variant={variant}
62
- tokens={itemTokens}
63
- onPress={handlePress}
64
- selected={isSelected}
65
- itemPositions={itemPositions}
66
- index={index}
67
- >
68
- {label}
69
- </TabsItem>
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,
@@ -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
- ...rest
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)
package/src/Tags/Tags.jsx CHANGED
@@ -8,13 +8,7 @@ import Icon from '../Icon'
8
8
  import { StackWrap, getStackedContent } from '../StackView'
9
9
  import { useViewport } from '../ViewportProvider'
10
10
  import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider'
11
- import {
12
- a11yProps,
13
- pressProps,
14
- variantProp,
15
- getTokensPropType,
16
- selectTokens
17
- } from '../utils/propTypes'
11
+ import { a11yProps, pressProps, variantProp, getTokensPropType, selectTokens } from '../utils/props'
18
12
  import { useMultipleInputValues } from '../utils/input'
19
13
  import { getPressHandlersWithArgs } from '../utils/pressability'
20
14
 
@@ -2,9 +2,9 @@ import { useCallback } from 'react'
2
2
  import useTheme from './useTheme'
3
3
  import { getComponentTheme, getThemeTokens, resolveThemeTokens, mergeAppearances } from './utils'
4
4
  /**
5
- * @typedef {import('../utils/propTypes.js').AppearanceSet} AppearanceSet
6
- * @typedef {import('../utils/propTypes.js').TokensProp} TokensProp
7
- * @typedef {import('../utils/propTypes.js').TokensSet} TokensSet
5
+ * @typedef {import('../utils/props/variantProp.js').AppearanceSet} AppearanceSet
6
+ * @typedef {import('../utils/props/tokens.js').TokensProp} TokensProp
7
+ * @typedef {import('../utils/props/tokens.js').TokensSet} TokensSet
8
8
  */
9
9
 
10
10
  /**
@@ -3,9 +3,9 @@ import semVerSatisfies from 'semver/functions/satisfies'
3
3
  import pkg from '../../../package.json'
4
4
 
5
5
  /**
6
- * @typedef {import('../../utils/propTypes.js').AppearanceSet} AppearanceSet
7
- * @typedef {import('../../utils/propTypes.js').TokensProp} TokensProp
8
- * @typedef {import('../../utils/propTypes.js').TokensSet} TokensSet
6
+ * @typedef {import('../../utils/props/variantProp.js').AppearanceSet} AppearanceSet
7
+ * @typedef {import('../../utils/props/tokens.js').TokensProp} TokensProp
8
+ * @typedef {import('../../utils/props/tokens.js').TokensSet} TokensSet
9
9
  */
10
10
 
11
11
  /**
@@ -1,17 +1,14 @@
1
1
  import React, { forwardRef } from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { Platform, View, StyleSheet } from 'react-native'
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
- import {
8
- a11yProps,
9
- pressProps,
10
- variantProp,
11
- getTokensPropType,
12
- selectTokens
13
- } from '../utils/propTypes'
9
+ import { a11yProps, pressProps, variantProp, getTokensPropType, selectTokens } from '../utils/props'
14
10
  import { useInputValue } from '../utils/input'
11
+ import { useUniqueId } from '../utils'
15
12
 
16
13
  const selectButtonTokens = (tokens) =>
17
14
  selectTokens('Button', {
@@ -54,12 +51,40 @@ const selectSwitchStyles = ({
54
51
  })
55
52
  })
56
53
 
54
+ const selectLabelStyles = ({ labelMarginLeft }) => ({ marginLeft: labelMarginLeft })
55
+ const selectLabelTokens = ({
56
+ labelColor,
57
+ labelFontName,
58
+ labelFontSize,
59
+ labelFontWeight,
60
+ labelLineHeight
61
+ }) => ({
62
+ color: labelColor,
63
+ fontName: labelFontName,
64
+ fontWeight: labelFontWeight,
65
+ fontSize: labelFontSize,
66
+ lineHeight: labelLineHeight
67
+ })
68
+
57
69
  const ToggleSwitch = forwardRef(
58
70
  (
59
- { value, initialValue, onChange, inactive, tokens, variant, accessibilityRole = 'switch' },
71
+ {
72
+ value,
73
+ initialValue,
74
+ onChange,
75
+ id,
76
+ label,
77
+ inactive,
78
+ tokens,
79
+ tooltip,
80
+ variant,
81
+ accessibilityRole = 'switch',
82
+ accessibilityLabel = label
83
+ },
60
84
  ref
61
85
  ) => {
62
86
  const getTokens = useThemeTokensCallback('ToggleSwitch', tokens, variant)
87
+ const themeTokens = getTokens()
63
88
 
64
89
  const { currentValue, setValue } = useInputValue({
65
90
  value,
@@ -68,43 +93,58 @@ const ToggleSwitch = forwardRef(
68
93
  })
69
94
 
70
95
  const handlePress = (event) => setValue(!currentValue, event)
71
-
72
96
  const getButtonTokens = (buttonState) => selectButtonTokens(getTokens(buttonState))
97
+ const uniqueId = useUniqueId('toggleSwitch')
98
+ const inputId = id ?? uniqueId
73
99
 
74
100
  return (
75
- <ButtonBase
76
- ref={ref}
77
- selected={currentValue}
78
- inactive={inactive}
79
- tokens={getButtonTokens}
80
- accessibilityRole={accessibilityRole}
81
- accessibilityState={{ checked: currentValue }}
82
- onPress={handlePress}
83
- >
84
- {(buttonState) => {
85
- const themeTokens = getTokens(buttonState)
86
- const IconComponent = themeTokens.icon
87
- const switchStyles = selectSwitchStyles(themeTokens)
88
- const trackStyles = selectTrackStyles(themeTokens)
89
- const iconTokens = selectIconTokens(themeTokens)
101
+ <StackView space={2} direction="row">
102
+ {Boolean(label) && (
103
+ <View style={selectLabelStyles(themeTokens)}>
104
+ <InputLabel
105
+ forId={inputId}
106
+ label={label}
107
+ tokens={selectLabelTokens(themeTokens)}
108
+ tooltip={tooltip}
109
+ />
110
+ </View>
111
+ )}
112
+ <ButtonBase
113
+ id={id}
114
+ ref={ref}
115
+ selected={currentValue}
116
+ inactive={inactive}
117
+ tokens={getButtonTokens}
118
+ accessibilityLabel={accessibilityLabel}
119
+ accessibilityRole={accessibilityRole}
120
+ accessibilityState={{ checked: currentValue }}
121
+ onPress={handlePress}
122
+ >
123
+ {(buttonState) => {
124
+ const stateTokens = getTokens(buttonState)
125
+ const IconComponent = stateTokens.icon
126
+ const switchStyles = selectSwitchStyles(stateTokens)
127
+ const trackStyles = selectTrackStyles(stateTokens)
128
+ const iconTokens = selectIconTokens(stateTokens)
90
129
 
91
- // If drag-slide support is needed, use a PanResponder and apply these to an Animated value.
92
- // Use translate transforms for smoothest non-thread-blocking animations and to allow drag.
93
- const slideStart = 0
94
- const slideEnd =
95
- themeTokens.width - themeTokens.switchSize - themeTokens.trackBorderWidth * 2
96
- const switchOffset = buttonState.selected ? slideEnd : slideStart
97
- const switchPositionStyle = { transform: [{ translateX: switchOffset }] }
130
+ // If drag-slide support is needed, use a PanResponder and apply these to an Animated value.
131
+ // Use translate transforms for smoothest non-thread-blocking animations and to allow drag.
132
+ const slideStart = 0
133
+ const slideEnd =
134
+ stateTokens.width - stateTokens.switchSize - stateTokens.trackBorderWidth * 2
135
+ const switchOffset = buttonState.selected ? slideEnd : slideStart
136
+ const switchPositionStyle = { transform: [{ translateX: switchOffset }] }
98
137
 
99
- return (
100
- <View style={[staticStyles.track, trackStyles]}>
101
- <View style={[staticStyles.switch, switchStyles, switchPositionStyle]}>
102
- {IconComponent && <IconComponent {...iconTokens} />}
138
+ return (
139
+ <View style={[staticStyles.track, trackStyles]}>
140
+ <View style={[staticStyles.switch, switchStyles, switchPositionStyle]}>
141
+ {IconComponent && <IconComponent {...iconTokens} />}
142
+ </View>
103
143
  </View>
104
- </View>
105
- )
106
- }}
107
- </ButtonBase>
144
+ )
145
+ }}
146
+ </ButtonBase>
147
+ </StackView>
108
148
  )
109
149
  }
110
150
  )
@@ -131,11 +171,23 @@ ToggleSwitch.propTypes = {
131
171
  * this should always be passed and used to control the state of the switch.
132
172
  */
133
173
  onChange: PropTypes.func,
174
+ /**
175
+ * Input ID.
176
+ */
177
+ id: PropTypes.string,
178
+ /**
179
+ * An optional label.
180
+ */
181
+ label: PropTypes.string,
134
182
  /**
135
183
  * If passed, the switch does not respond to user input and may recieve different
136
184
  * theme tokens if the theme supports inactive appearance.
137
185
  */
138
- inactive: PropTypes.bool
186
+ inactive: PropTypes.bool,
187
+ /**
188
+ * Content of an optional Tooltip. If set, a tooltip button will be shown next to the label.
189
+ */
190
+ tooltip: PropTypes.string
139
191
  }
140
192
 
141
193
  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/props'
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
@@ -1,3 +1,4 @@
1
1
  import ToggleSwitch from './ToggleSwitch'
2
+ import ToggleSwitchGroup from './ToggleSwitchGroup'
2
3
 
3
- export default ToggleSwitch
4
+ export { ToggleSwitch, ToggleSwitchGroup }
package/src/index.js CHANGED
@@ -17,7 +17,7 @@ export * from './Icon'
17
17
  export { default as IconButton } from './IconButton'
18
18
  export { default as InputLabel } from './InputLabel'
19
19
  export * from './Link'
20
- export { default as List } from './List'
20
+ export { default as List, ListItem, ListBase } from './List'
21
21
  export { default as Modal } from './Modal'
22
22
  export { default as Notification } from './Notification'
23
23
  export { default as Pagination } from './Pagination'
@@ -37,7 +37,7 @@ export { default as StepTracker } from './StepTracker'
37
37
  export { default as Tabs } from './Tabs'
38
38
  export { default as Tags } from './Tags'
39
39
  export * from './TextInput'
40
- export { default as ToggleSwitch } from './ToggleSwitch'
40
+ export * from './ToggleSwitch'
41
41
  export { default as Tooltip } from './Tooltip'
42
42
  export { default as TooltipButton } from './TooltipButton'
43
43
  export { default as Typography } from './Typography'
@@ -150,8 +150,9 @@ export const getA11yPropsFromHtmlTag = (tag, nativeRole) => {
150
150
  if (nativeRole !== undefined && Platform.OS !== 'web') return { accessibilityRole: nativeRole }
151
151
 
152
152
  if (tag) {
153
- const accessibilityRole = tagsToRoles[tag]
154
- if (accessibilityRole) return { accessibilityRole }
153
+ // aria roles don't have native counterparts and RN may throw an error if an unrecognised role is passed
154
+ const ariaRole = Platform.OS === 'web' && tagsToRoles[tag]
155
+ if (ariaRole) return { accessibilityRole: ariaRole }
155
156
 
156
157
  const accessibilityLevel = getHeadingLevel(tag)
157
158
  if (accessibilityLevel) return { accessibilityRole: 'header', accessibilityLevel }
@@ -3,7 +3,7 @@ export * from './animation'
3
3
  export * from './children'
4
4
  export * from './input'
5
5
  export * from './pressability'
6
- export * from './propTypes'
6
+ export * from './props'
7
7
 
8
8
  export { default as info } from './info'
9
9
  export { default as useCopy } from './useCopy'
@@ -12,3 +12,4 @@ export { default as useSpacingScale } from './useSpacingScale'
12
12
  export { default as useResponsiveProp } from './useResponsiveProp'
13
13
  export * from './useResponsiveProp'
14
14
  export { default as useUniqueId } from './useUniqueId'
15
+ export { default as withLinkRouter } from './withLinkRouter'
@@ -1,5 +1,5 @@
1
1
  import { Platform, StyleSheet } from 'react-native'
2
- import { pressProps } from './propTypes'
2
+ import pressProps from './props/pressProps'
3
3
 
4
4
  /**
5
5
  * @typedef {import('react').ReactNode} ReactNode