@telus-uds/components-base 0.0.2-prerelease.9 → 1.1.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 (293) hide show
  1. package/.eslintrc.js +9 -0
  2. package/.storybook/main.js +4 -0
  3. package/.storybook/preview.js +37 -0
  4. package/.ultra.cache.json +1 -1
  5. package/CHANGELOG.md +50 -0
  6. package/README.md +4 -2
  7. package/__fixtures__/test-utils.js +25 -0
  8. package/__fixtures__/testTheme.js +4 -2
  9. package/__tests__/Button/ButtonGroup.test.jsx +4 -5
  10. package/__tests__/Checkbox/Checkbox.test.jsx +2 -2
  11. package/__tests__/Checkbox/CheckboxGroup.test.jsx +4 -5
  12. package/__tests__/ExpandCollapse/ExpandCollapse.test.jsx +2 -2
  13. package/__tests__/HorizontalScroll/HorizontalScroll.test.jsx +164 -0
  14. package/__tests__/Link/LinkBase.test.jsx +0 -14
  15. package/__tests__/Radio/Radio.test.jsx +2 -2
  16. package/__tests__/Radio/RadioGroup.test.jsx +4 -5
  17. package/__tests__/RadioCard/RadioCard.test.jsx +2 -2
  18. package/__tests__/RadioCard/RadioCardGroup.test.jsx +4 -5
  19. package/__tests__/Search/Search.test.jsx +9 -8
  20. package/__tests__/Select/Select.test.jsx +3 -2
  21. package/__tests__/Tabs/Tabs.test.jsx +1 -161
  22. package/__tests__/Tags/Tags.test.jsx +4 -5
  23. package/__tests__/TextInput/TextArea.test.jsx +3 -2
  24. package/__tests__/TextInput/TextInputBase.test.jsx +10 -5
  25. package/__tests__/ThemeProvider/ThemeProvider.test.jsx +77 -0
  26. package/__tests__/ThemeProvider/useThemeTokens.test.jsx +9 -5
  27. package/__tests__/ThemeProvider/utils/theme-tokens.test.js +41 -0
  28. package/__tests__/ToggleSwitch/ToggleSwitch.test.jsx +3 -2
  29. package/__tests__/utils/children.test.jsx +128 -0
  30. package/__tests__/utils/input.test.js +1 -1
  31. package/__tests__/utils/semantics.test.jsx +43 -0
  32. package/babel.config.js +9 -16
  33. package/component-docs.json +10313 -0
  34. package/generate-component-docs.js +56 -0
  35. package/lib/A11yText/index.js +10 -5
  36. package/lib/ActivityIndicator/Spinner.js +16 -13
  37. package/lib/ActivityIndicator/Spinner.native.js +12 -8
  38. package/lib/Box/Box.js +103 -8
  39. package/lib/Button/Button.js +9 -8
  40. package/lib/Button/ButtonBase.js +14 -7
  41. package/lib/Button/ButtonGroup.js +25 -10
  42. package/lib/Button/ButtonLink.js +10 -7
  43. package/lib/Card/Card.js +2 -0
  44. package/lib/Card/CardBase.js +13 -5
  45. package/lib/Card/PressableCardBase.js +12 -8
  46. package/lib/Checkbox/Checkbox.js +25 -14
  47. package/lib/Checkbox/CheckboxGroup.js +22 -12
  48. package/lib/Divider/Divider.js +12 -7
  49. package/lib/ExpandCollapse/Accordion.js +10 -4
  50. package/lib/ExpandCollapse/Control.js +12 -6
  51. package/lib/ExpandCollapse/ExpandCollapse.js +10 -5
  52. package/lib/ExpandCollapse/Panel.js +8 -7
  53. package/lib/Feedback/Feedback.js +10 -5
  54. package/lib/Fieldset/Fieldset.js +10 -5
  55. package/lib/Fieldset/FieldsetContainer.js +10 -5
  56. package/lib/Fieldset/FieldsetContainer.native.js +10 -5
  57. package/lib/Fieldset/Legend.js +10 -5
  58. package/lib/Fieldset/Legend.native.js +10 -5
  59. package/lib/FlexGrid/Col/Col.js +8 -5
  60. package/lib/FlexGrid/FlexGrid.js +31 -6
  61. package/lib/FlexGrid/Row/Row.js +12 -5
  62. package/lib/{Tabs → HorizontalScroll}/HorizontalScroll.js +5 -4
  63. package/lib/{Tabs/TabsScrollButton.js → HorizontalScroll/HorizontalScrollButton.js} +14 -8
  64. package/lib/{Tabs → HorizontalScroll}/ScrollViewEnd.js +0 -0
  65. package/lib/{Tabs → HorizontalScroll}/ScrollViewEnd.native.js +0 -0
  66. package/lib/{Tabs → HorizontalScroll}/dictionary.js +0 -0
  67. package/lib/HorizontalScroll/index.js +35 -0
  68. package/lib/{Tabs → HorizontalScroll}/itemPositions.js +0 -0
  69. package/lib/Icon/Icon.js +16 -9
  70. package/lib/Icon/IconText.js +8 -7
  71. package/lib/IconButton/IconButton.js +10 -5
  72. package/lib/InputLabel/InputLabel.js +33 -5
  73. package/lib/InputLabel/LabelContent.js +22 -12
  74. package/lib/InputLabel/LabelContent.native.js +23 -5
  75. package/lib/InputSupports/InputSupports.js +10 -5
  76. package/lib/Link/ChevronLink.js +12 -5
  77. package/lib/Link/InlinePressable.js +10 -4
  78. package/lib/Link/InlinePressable.native.js +5 -4
  79. package/lib/Link/Link.js +12 -5
  80. package/lib/Link/LinkBase.js +12 -5
  81. package/lib/Link/TextButton.js +10 -5
  82. package/lib/List/List.js +6 -6
  83. package/lib/List/ListItem.js +28 -33
  84. package/lib/List/index.js +15 -0
  85. package/lib/Modal/Modal.js +10 -5
  86. package/lib/Notification/Notification.js +21 -5
  87. package/lib/Pagination/PageButton.js +16 -11
  88. package/lib/Pagination/Pagination.js +12 -7
  89. package/lib/Pagination/SideButton.js +12 -7
  90. package/lib/Pagination/usePagination.js +2 -2
  91. package/lib/Progress/Progress.js +10 -5
  92. package/lib/Progress/ProgressBar.js +21 -10
  93. package/lib/Progress/ProgressBarBackground.js +12 -8
  94. package/lib/Radio/Radio.js +14 -13
  95. package/lib/Radio/RadioButton.js +20 -9
  96. package/lib/Radio/RadioGroup.js +24 -13
  97. package/lib/RadioCard/RadioCard.js +14 -10
  98. package/lib/RadioCard/RadioCardGroup.js +13 -12
  99. package/lib/Search/Search.js +29 -18
  100. package/lib/Select/Picker.js +11 -6
  101. package/lib/Select/Picker.native.js +21 -6
  102. package/lib/Select/Select.js +46 -4
  103. package/lib/SideNav/Item.js +10 -5
  104. package/lib/SideNav/ItemsGroup.js +10 -5
  105. package/lib/SideNav/SideNav.js +11 -7
  106. package/lib/Skeleton/Skeleton.js +15 -15
  107. package/lib/Skeleton/skeletonWebAnimation.js +1 -1
  108. package/lib/Spacer/Spacer.js +19 -7
  109. package/lib/StackView/StackView.js +26 -7
  110. package/lib/StackView/StackWrap.js +24 -13
  111. package/lib/StackView/StackWrapBox.js +34 -8
  112. package/lib/StackView/StackWrapGap.js +16 -7
  113. package/lib/StackView/common.js +4 -2
  114. package/lib/StackView/getStackedContent.js +2 -2
  115. package/lib/StepTracker/StepTracker.js +10 -5
  116. package/lib/Tabs/Tabs.js +26 -19
  117. package/lib/Tabs/TabsItem.js +16 -12
  118. package/lib/Tags/Tags.js +27 -11
  119. package/lib/TextInput/TextArea.js +7 -5
  120. package/lib/TextInput/TextInput.js +12 -6
  121. package/lib/TextInput/TextInputBase.js +12 -8
  122. package/lib/ThemeProvider/ThemeProvider.js +14 -10
  123. package/lib/ThemeProvider/useSetTheme.js +6 -1
  124. package/lib/ThemeProvider/utils/styles.js +2 -2
  125. package/lib/ThemeProvider/utils/theme-tokens.js +39 -8
  126. package/lib/ToggleSwitch/ToggleSwitch.js +11 -6
  127. package/lib/Tooltip/Backdrop.js +10 -2
  128. package/lib/Tooltip/Tooltip.js +5 -4
  129. package/lib/Typography/Typography.js +40 -24
  130. package/lib/index.js +36 -1
  131. package/lib/utils/a11y/index.js +13 -0
  132. package/lib/utils/a11y/propTypes.js +61 -0
  133. package/lib/utils/a11y/propTypes.native.js +47 -0
  134. package/lib/utils/a11y/semantics.js +173 -0
  135. package/lib/utils/animation/useVerticalExpandAnimation.js +1 -1
  136. package/lib/utils/children.js +55 -8
  137. package/lib/utils/input.js +27 -17
  138. package/lib/utils/propTypes.js +40 -68
  139. package/lib/utils/useCopy.js +1 -1
  140. package/lib/utils/useHash.js +8 -4
  141. package/lib/utils/useSpacingScale.js +1 -3
  142. package/lib/utils/useUniqueId.js +1 -1
  143. package/package.json +14 -6
  144. package/release-context.json +4 -4
  145. package/src/A11yText/index.jsx +6 -4
  146. package/src/ActivityIndicator/Spinner.jsx +5 -3
  147. package/src/ActivityIndicator/Spinner.native.jsx +5 -3
  148. package/src/Box/Box.jsx +125 -39
  149. package/src/Button/Button.jsx +7 -4
  150. package/src/Button/ButtonBase.jsx +86 -77
  151. package/src/Button/ButtonGroup.jsx +81 -69
  152. package/src/Button/ButtonLink.jsx +18 -13
  153. package/src/Card/Card.jsx +2 -2
  154. package/src/Card/CardBase.jsx +6 -4
  155. package/src/Card/PressableCardBase.jsx +71 -64
  156. package/src/Checkbox/Checkbox.jsx +118 -108
  157. package/src/Checkbox/CheckboxGroup.jsx +72 -62
  158. package/src/Divider/Divider.jsx +7 -4
  159. package/src/ExpandCollapse/Accordion.jsx +3 -2
  160. package/src/ExpandCollapse/Control.jsx +40 -43
  161. package/src/ExpandCollapse/ExpandCollapse.jsx +26 -23
  162. package/src/ExpandCollapse/Panel.jsx +69 -63
  163. package/src/Feedback/Feedback.jsx +36 -33
  164. package/src/Fieldset/Fieldset.jsx +63 -56
  165. package/src/Fieldset/FieldsetContainer.jsx +14 -5
  166. package/src/Fieldset/FieldsetContainer.native.jsx +7 -4
  167. package/src/Fieldset/Legend.jsx +7 -2
  168. package/src/Fieldset/Legend.native.jsx +7 -2
  169. package/src/FlexGrid/Col/Col.jsx +139 -132
  170. package/src/FlexGrid/FlexGrid.jsx +79 -51
  171. package/src/FlexGrid/Row/Row.jsx +55 -48
  172. package/src/HorizontalScroll/HorizontalScroll.jsx +168 -0
  173. package/src/HorizontalScroll/HorizontalScrollButton.jsx +105 -0
  174. package/src/{Tabs → HorizontalScroll}/ScrollViewEnd.jsx +0 -0
  175. package/src/{Tabs → HorizontalScroll}/ScrollViewEnd.native.jsx +0 -0
  176. package/src/{Tabs → HorizontalScroll}/dictionary.js +0 -0
  177. package/src/HorizontalScroll/index.js +17 -0
  178. package/src/{Tabs → HorizontalScroll}/itemPositions.js +0 -0
  179. package/src/Icon/Icon.jsx +37 -35
  180. package/src/Icon/IconText.jsx +22 -17
  181. package/src/IconButton/IconButton.jsx +49 -42
  182. package/src/InputLabel/InputLabel.jsx +53 -38
  183. package/src/InputLabel/LabelContent.jsx +14 -6
  184. package/src/InputLabel/LabelContent.native.jsx +11 -2
  185. package/src/InputSupports/InputSupports.jsx +29 -34
  186. package/src/Link/ChevronLink.jsx +26 -16
  187. package/src/Link/InlinePressable.jsx +5 -3
  188. package/src/Link/InlinePressable.native.jsx +5 -3
  189. package/src/Link/Link.jsx +22 -16
  190. package/src/Link/LinkBase.jsx +67 -58
  191. package/src/Link/TextButton.jsx +30 -23
  192. package/src/List/List.jsx +6 -7
  193. package/src/List/ListItem.jsx +70 -90
  194. package/src/List/index.js +5 -0
  195. package/src/Modal/Modal.jsx +9 -4
  196. package/src/Notification/Notification.jsx +58 -43
  197. package/src/Pagination/PageButton.jsx +37 -34
  198. package/src/Pagination/Pagination.jsx +88 -92
  199. package/src/Pagination/SideButton.jsx +44 -41
  200. package/src/Progress/Progress.jsx +5 -4
  201. package/src/Progress/ProgressBar.jsx +42 -29
  202. package/src/Progress/ProgressBarBackground.jsx +5 -3
  203. package/src/Radio/Radio.jsx +85 -78
  204. package/src/Radio/RadioButton.jsx +54 -43
  205. package/src/Radio/RadioGroup.jsx +74 -63
  206. package/src/RadioCard/RadioCard.jsx +75 -68
  207. package/src/RadioCard/RadioCardGroup.jsx +82 -75
  208. package/src/Search/Search.jsx +127 -106
  209. package/src/Select/Picker.jsx +49 -42
  210. package/src/Select/Picker.native.jsx +56 -49
  211. package/src/Select/Select.jsx +115 -72
  212. package/src/SideNav/Item.jsx +53 -46
  213. package/src/SideNav/ItemsGroup.jsx +50 -43
  214. package/src/SideNav/SideNav.jsx +68 -60
  215. package/src/Skeleton/Skeleton.jsx +9 -13
  216. package/src/Spacer/Spacer.jsx +11 -4
  217. package/src/StackView/StackView.jsx +47 -23
  218. package/src/StackView/StackWrap.jsx +14 -12
  219. package/src/StackView/StackWrapBox.jsx +62 -28
  220. package/src/StackView/StackWrapGap.jsx +46 -24
  221. package/src/StackView/common.jsx +3 -2
  222. package/src/StepTracker/StepTracker.jsx +73 -62
  223. package/src/Tabs/Tabs.jsx +70 -62
  224. package/src/Tabs/TabsItem.jsx +111 -103
  225. package/src/Tags/Tags.jsx +114 -102
  226. package/src/TextInput/TextArea.jsx +5 -4
  227. package/src/TextInput/TextInput.jsx +5 -4
  228. package/src/TextInput/TextInputBase.jsx +84 -77
  229. package/src/ThemeProvider/ThemeProvider.jsx +11 -7
  230. package/src/ThemeProvider/useSetTheme.js +4 -0
  231. package/src/ThemeProvider/utils/theme-tokens.js +28 -0
  232. package/src/ToggleSwitch/ToggleSwitch.jsx +49 -50
  233. package/src/Tooltip/Tooltip.jsx +134 -130
  234. package/src/Typography/Typography.jsx +67 -44
  235. package/src/index.js +3 -1
  236. package/src/utils/a11y/index.js +1 -0
  237. package/src/utils/a11y/propTypes.js +61 -0
  238. package/src/utils/a11y/propTypes.native.js +39 -0
  239. package/src/utils/a11y/semantics.js +162 -0
  240. package/src/utils/children.jsx +60 -7
  241. package/src/utils/input.js +20 -17
  242. package/src/utils/propTypes.js +30 -76
  243. package/src/utils/useCopy.js +1 -1
  244. package/src/utils/useHash.js +8 -3
  245. package/stories/A11yText/A11yText.stories.jsx +3 -3
  246. package/stories/ActivityIndicator/ActivityIndicator.stories.jsx +2 -2
  247. package/stories/Box/Box.stories.jsx +2 -2
  248. package/stories/Button/Button.stories.jsx +3 -3
  249. package/stories/Button/ButtonGroup.stories.jsx +2 -2
  250. package/stories/Button/ButtonLink.stories.jsx +2 -2
  251. package/stories/Card/Card.stories.jsx +2 -2
  252. package/stories/Checkbox/Checkbox.stories.jsx +2 -2
  253. package/stories/Divider/Divider.stories.jsx +2 -2
  254. package/stories/ExpandCollapse/ExpandCollapse.stories.jsx +3 -3
  255. package/stories/Feedback/Feedback.stories.jsx +2 -2
  256. package/stories/FlexGrid/01 FlexGrid.stories.jsx +2 -2
  257. package/stories/FlexGrid/02 Row.stories.jsx +2 -2
  258. package/stories/FlexGrid/03 Col.stories.jsx +2 -2
  259. package/stories/Icon/Icon.stories.jsx +2 -2
  260. package/stories/IconButton/IconButton.stories.jsx +2 -2
  261. package/stories/InputLabel/InputLabel.stories.jsx +2 -2
  262. package/stories/Link/ChevronLink.stories.jsx +2 -2
  263. package/stories/Link/Link.stories.jsx +2 -2
  264. package/stories/Link/TextButton.stories.jsx +2 -2
  265. package/stories/List/List.stories.jsx +2 -2
  266. package/stories/Modal/Modal.stories.jsx +2 -2
  267. package/stories/Notification/Notification.stories.jsx +2 -2
  268. package/stories/Pagination/Pagination.stories.jsx +2 -2
  269. package/stories/Progress/Progress.stories.jsx +2 -2
  270. package/stories/Radio/Radio.stories.jsx +2 -2
  271. package/stories/RadioCard/RadioCard.stories.jsx +2 -2
  272. package/stories/Search/Search.stories.jsx +2 -2
  273. package/stories/Select/Select.stories.jsx +2 -2
  274. package/stories/SideNav/SideNav.stories.jsx +2 -2
  275. package/stories/SideNav/SideNavItem.stories.jsx +2 -2
  276. package/stories/SideNav/SideNavItemsGroup.stories.jsx +2 -2
  277. package/stories/Skeleton/Skeleton.stories.jsx +3 -3
  278. package/stories/Spacer/Spacer.stories.jsx +2 -2
  279. package/stories/StackView/StackView.stories.jsx +2 -2
  280. package/stories/StackView/StackWrap.stories.jsx +2 -2
  281. package/stories/StepTracker/StepTracker.stories.jsx +2 -2
  282. package/stories/Tabs/Tabs.stories.jsx +2 -2
  283. package/stories/Tags/Tags.stories.jsx +2 -2
  284. package/stories/TextInput/TextArea.stories.jsx +2 -2
  285. package/stories/TextInput/TextInput.stories.jsx +2 -2
  286. package/stories/ToggleSwitch/ToggleSwitch.stories.jsx +2 -2
  287. package/stories/Tooltip/Tooltip.stories.jsx +2 -2
  288. package/stories/TooltipButton/TooltipButton.stories.jsx +2 -2
  289. package/stories/Typography/Typography.stories.jsx +2 -2
  290. package/stories/platform-supports.jsx +1 -1
  291. package/stories/supports.jsx +4 -5
  292. package/src/Tabs/HorizontalScroll.jsx +0 -165
  293. package/src/Tabs/TabsScrollButton.jsx +0 -100
package/src/Tabs/Tabs.jsx CHANGED
@@ -1,88 +1,96 @@
1
- import React, { useCallback } from 'react'
1
+ import React, { forwardRef, useCallback } from 'react'
2
2
  import PropTypes from 'prop-types'
3
+ import ABBPropTypes from 'airbnb-prop-types'
3
4
 
4
5
  import { useThemeTokens } from '../ThemeProvider'
5
6
  import StackView from '../StackView'
6
7
  import { getTokensPropType, variantProp, useHash, useInputValue } from '../utils'
7
- import HorizontalScroll, { selectHorizontalScrollTokens } from './HorizontalScroll'
8
+ import HorizontalScroll, {
9
+ horizontalScrollUtils,
10
+ HorizontalScrollButton
11
+ } from '../HorizontalScroll'
8
12
  import TabsItem from './TabsItem'
9
- import TabsScrollButton from './TabsScrollButton'
10
- import { useItemPositions } from './itemPositions'
13
+
14
+ const { selectHorizontalScrollTokens, useItemPositions } = horizontalScrollUtils
11
15
 
12
16
  /**
13
17
  * Tabs renders a horizontally-scrolling menu of selectable buttons which may link
14
18
  * to a page or control what content is displayed on this page.
15
19
  */
16
- const Tabs = ({
17
- tokens,
18
- itemTokens,
19
- scrollButtonTokens,
20
- variant,
21
- value,
22
- initialValue,
23
- onChange,
24
- items = []
25
- }) => {
26
- const { space, ...themeTokens } = useThemeTokens('Tabs', tokens, variant)
27
- const { currentValue, setValue } = useInputValue({ value, initialValue, onChange })
20
+ const Tabs = forwardRef(
21
+ (
22
+ { tokens, itemTokens, scrollButtonTokens, variant, value, initialValue, onChange, items = [] },
23
+ ref
24
+ ) => {
25
+ const { space, ...themeTokens } = useThemeTokens('Tabs', tokens, variant)
26
+ const { currentValue, setValue } = useInputValue({ value, initialValue, onChange })
28
27
 
29
- const [itemPositions, isPositioningReady] = useItemPositions()
28
+ const [itemPositions, isPositioningReady] = useItemPositions()
30
29
 
31
- useHash(
32
- useCallback(
33
- (hash) => {
34
- const hashItem = hash && items.find(({ href }) => hash === href)
35
- const hashId = hashItem && (hashItem.id || hashItem.label)
36
- if (hashId) setTimeout(setValue(hashId), 500)
37
- },
38
- [items, setValue]
39
- ),
40
- isPositioningReady
41
- )
30
+ useHash(
31
+ useCallback(
32
+ (hash, event) => {
33
+ const hashItem = hash && items.find(({ href }) => hash === href)
34
+ const hashId = hashItem && (hashItem.id || hashItem.label)
35
+ if (hashId) setTimeout(setValue(hashId, event), 500)
36
+ },
37
+ [items, setValue]
38
+ ),
39
+ isPositioningReady
40
+ )
42
41
 
43
- return (
44
- <HorizontalScroll
45
- ScrollButton={TabsScrollButton}
46
- itemPositions={itemPositions}
47
- tokens={selectHorizontalScrollTokens(themeTokens)}
48
- scrollButtonTokens={scrollButtonTokens}
49
- accessibilityRole="tablist"
50
- >
51
- <StackView space={space} direction="row">
52
- {items.map(({ href, label, id }, index) => {
53
- const itemId = id ?? label
54
- const isSelected = Boolean(currentValue && currentValue === itemId)
55
- const handlePress = () => setValue(itemId)
56
- return (
57
- <TabsItem
58
- key={itemId}
59
- href={href}
60
- variant={variant}
61
- tokens={itemTokens}
62
- onPress={handlePress}
63
- selected={isSelected}
64
- itemPositions={itemPositions}
65
- index={index}
66
- >
67
- {label}
68
- </TabsItem>
69
- )
70
- })}
71
- </StackView>
72
- </HorizontalScroll>
73
- )
74
- }
42
+ return (
43
+ <HorizontalScroll
44
+ ref={ref}
45
+ ScrollButton={HorizontalScrollButton}
46
+ itemPositions={itemPositions}
47
+ tokens={selectHorizontalScrollTokens(themeTokens)}
48
+ scrollButtonTokens={scrollButtonTokens}
49
+ accessibilityRole="tablist"
50
+ >
51
+ <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
+ })}
72
+ </StackView>
73
+ </HorizontalScroll>
74
+ )
75
+ }
76
+ )
77
+ Tabs.displayName = 'Tabs'
75
78
 
76
79
  Tabs.propTypes = {
77
80
  items: PropTypes.arrayOf(
78
- PropTypes.shape({ href: PropTypes.string, label: PropTypes.string, id: PropTypes.string })
81
+ PropTypes.shape({
82
+ href: PropTypes.string,
83
+ label: PropTypes.string,
84
+ id: PropTypes.string,
85
+ ref: ABBPropTypes.ref()
86
+ })
79
87
  ),
80
88
  value: PropTypes.string,
81
89
  initialValue: PropTypes.string,
82
90
  onChange: PropTypes.func,
83
91
  tokens: getTokensPropType('Tabs'),
84
92
  itemTokens: getTokensPropType('TabsItem'),
85
- scrollButtonTokens: getTokensPropType('TabsScrollButton'),
93
+ scrollButtonTokens: getTokensPropType('HorizontalScrollButton'),
86
94
  variant: variantProp.propType
87
95
  }
88
96
 
@@ -1,7 +1,6 @@
1
- import React, { useEffect } from 'react'
1
+ import React, { forwardRef, useEffect } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import { Platform, Pressable, StyleSheet, Text, View } from 'react-native'
4
- import system from '@telus-uds/system-themes/src/tokens/rn'
5
4
 
6
5
  import { applyTextStyles, useThemeTokensCallback } from '../ThemeProvider'
7
6
  import {
@@ -13,7 +12,9 @@ import {
13
12
  a11yProps
14
13
  } from '../utils'
15
14
  import Spacer from '../Spacer'
16
- import { itemPositionsPropType, getItemPositionLayoutHandler } from './itemPositions'
15
+ import { horizontalScrollUtils } from '../HorizontalScroll'
16
+
17
+ const { itemPositionsPropType, getItemPositionLayoutHandler } = horizontalScrollUtils
17
18
 
18
19
  const selectHighlightBarStyles = ({
19
20
  highlightColor,
@@ -41,9 +42,9 @@ const selectHighlightTriangleStyles = ({ highlightColor, highlightTriangleSize }
41
42
  width: 0,
42
43
  borderWidth: highlightTriangleSize,
43
44
  borderTopColor: highlightColor,
44
- borderBottomColor: system.color.transparent,
45
- borderLeftColor: system.color.transparent,
46
- borderRightColor: system.color.transparent
45
+ borderBottomColor: 'transparent',
46
+ borderLeftColor: 'transparent',
47
+ borderRightColor: 'transparent'
47
48
  }
48
49
  })
49
50
 
@@ -69,108 +70,115 @@ const selectContainerStyles = ({
69
70
  *
70
71
  * This is rendered automatically by `Tabs` and isn't intended be used directly.
71
72
  */
72
- const TabsItem = ({
73
- onPress,
74
- href,
75
- variant,
76
- tokens,
77
- selected,
78
- itemPositions,
79
- index,
80
- children,
81
- accessibilityRole = href ? 'link' : 'tab',
82
- accessibilityState = Platform.OS === 'web' && accessibilityRole === 'link'
83
- ? // Web links can't be aria-selected but can be aria-current
84
- { current: selected ? 'page' : false }
85
- : { selected },
86
- ...rest
87
- }) => {
88
- const getTokens = useThemeTokensCallback('TabsItem', tokens, variant)
89
- const resolveTokens = (pressableState) =>
90
- resolvePressableTokens(getTokens, pressableState, { selected })
91
-
92
- const getPressableStyle = (pressableState) => {
93
- const { maxWidth } = resolveTokens(pressableState)
94
- return [{ maxWidth }, Platform.OS === 'web' && { outline: 'none' }]
95
- }
73
+ const TabsItem = forwardRef(
74
+ (
75
+ {
76
+ onPress,
77
+ href,
78
+ variant,
79
+ tokens,
80
+ selected,
81
+ itemPositions,
82
+ index,
83
+ children,
84
+ accessibilityRole = href ? 'link' : 'tab',
85
+ accessibilityState = Platform.OS === 'web' && accessibilityRole === 'link'
86
+ ? // Web links can't be aria-selected but can be aria-current
87
+ { current: selected ? 'page' : false }
88
+ : { selected },
89
+ ...rest
90
+ },
91
+ ref
92
+ ) => {
93
+ const getTokens = useThemeTokensCallback('TabsItem', tokens, variant)
94
+ const resolveTokens = (pressableState) =>
95
+ resolvePressableTokens(getTokens, pressableState, { selected })
96
+
97
+ const getPressableStyle = (pressableState) => {
98
+ const { maxWidth } = resolveTokens(pressableState)
99
+ return [{ maxWidth }, Platform.OS === 'web' && { outline: 'none' }]
100
+ }
96
101
 
97
- const handleLayout = getItemPositionLayoutHandler(itemPositions.positions, index)
102
+ const handleLayout = getItemPositionLayoutHandler(itemPositions.positions, index)
103
+
104
+ // On press, update the selection, call any press handler, and open any href in platform-appropriate way
105
+ const openHref = href && linkProps.handleHref({ href })
106
+ const handlePress =
107
+ onPress || openHref
108
+ ? () => {
109
+ if (onPress) onPress()
110
+ if (openHref) openHref()
111
+ }
112
+ : undefined
113
+
114
+ const a11y = {
115
+ accessibilityRole,
116
+ accessibilityState,
117
+ ...a11yProps.select(rest)
118
+ }
98
119
 
99
- // On press, update the selection, call any press handler, and open any href in platform-appropriate way
100
- const openHref = href && linkProps.handleHref({ href })
101
- const handlePress =
102
- onPress || openHref
103
- ? () => {
104
- if (onPress) onPress()
105
- if (openHref) openHref()
120
+ useEffect(() => {
121
+ // If this is selected while off-screen, scroll it into view
122
+ if (selected) {
123
+ const position = itemPositions.positions[index]
124
+ const scrollEnd = itemPositions.scrollOffset + itemPositions.containerWidth
125
+ if (
126
+ // is off the right edge, or
127
+ (scrollEnd && position?.end > scrollEnd) ||
128
+ // is off the left edge
129
+ (typeof position?.start === 'number' && position.start < itemPositions.scrollOffset)
130
+ ) {
131
+ itemPositions.scrollTo(position.start)
106
132
  }
107
- : undefined
108
-
109
- const a11y = {
110
- accessibilityRole,
111
- accessibilityState,
112
- ...a11yProps.select(rest)
113
- }
114
-
115
- useEffect(() => {
116
- // If this is selected while off-screen, scroll it into view
117
- if (selected) {
118
- const position = itemPositions.positions[index]
119
- const scrollEnd = itemPositions.scrollOffset + itemPositions.containerWidth
120
- if (
121
- // is off the right edge, or
122
- (scrollEnd && position?.end > scrollEnd) ||
123
- // is off the left edge
124
- (typeof position?.start === 'number' && position.start < itemPositions.scrollOffset)
125
- ) {
126
- itemPositions.scrollTo(position.start)
127
133
  }
128
- }
129
- // itemPositions is a ref object so this should only re-run when `selected` (or `index`) change
130
- }, [selected, index, itemPositions])
131
-
132
- return (
133
- <Pressable
134
- onPress={handlePress}
135
- href={href}
136
- style={getPressableStyle}
137
- onLayout={handleLayout}
138
- {...a11y}
139
- >
140
- {(pressableState) => {
141
- const { space, textAlign, ...themeTokens } = resolveTokens(pressableState)
142
-
143
- const hasHighlightBar = Boolean(themeTokens.highlightBarHeight)
144
- const highlightBarStyle = hasHighlightBar && selectHighlightBarStyles(themeTokens)
145
-
146
- const hasHighlightTriangle = Boolean(themeTokens.highlightTriangleSize)
147
- const highlightTriangleStyle =
148
- hasHighlightTriangle && selectHighlightTriangleStyles(themeTokens)
149
-
150
- const containerStyles = selectContainerStyles(themeTokens)
151
- const textStyles = applyTextStyles({
152
- ...selectTokens('Typography', themeTokens),
153
- textAlign
154
- })
155
-
156
- return (
157
- <View style={staticStyles.container}>
158
- <View style={containerStyles}>
159
- <Text style={textStyles}>{children}</Text>
160
- </View>
161
- <Spacer space={space} />
162
- {hasHighlightBar && <View style={[staticStyles.absolute, highlightBarStyle]} />}
163
- {hasHighlightTriangle && (
164
- <View style={[staticStyles.absolute, highlightTriangleStyle.container]}>
165
- <View style={highlightTriangleStyle.triangle} />
134
+ // itemPositions is a ref object so this should only re-run when `selected` (or `index`) change
135
+ }, [selected, index, itemPositions])
136
+
137
+ return (
138
+ <Pressable
139
+ ref={ref}
140
+ onPress={handlePress}
141
+ href={href}
142
+ style={getPressableStyle}
143
+ onLayout={handleLayout}
144
+ {...a11y}
145
+ >
146
+ {(pressableState) => {
147
+ const { space, textAlign, ...themeTokens } = resolveTokens(pressableState)
148
+
149
+ const hasHighlightBar = Boolean(themeTokens.highlightBarHeight)
150
+ const highlightBarStyle = hasHighlightBar && selectHighlightBarStyles(themeTokens)
151
+
152
+ const hasHighlightTriangle = Boolean(themeTokens.highlightTriangleSize)
153
+ const highlightTriangleStyle =
154
+ hasHighlightTriangle && selectHighlightTriangleStyles(themeTokens)
155
+
156
+ const containerStyles = selectContainerStyles(themeTokens)
157
+ const textStyles = applyTextStyles({
158
+ ...selectTokens('Typography', themeTokens),
159
+ textAlign
160
+ })
161
+
162
+ return (
163
+ <View style={staticStyles.container}>
164
+ <View style={containerStyles}>
165
+ <Text style={textStyles}>{children}</Text>
166
166
  </View>
167
- )}
168
- </View>
169
- )
170
- }}
171
- </Pressable>
172
- )
173
- }
167
+ <Spacer space={space} />
168
+ {hasHighlightBar && <View style={[staticStyles.absolute, highlightBarStyle]} />}
169
+ {hasHighlightTriangle && (
170
+ <View style={[staticStyles.absolute, highlightTriangleStyle.container]}>
171
+ <View style={highlightTriangleStyle.triangle} />
172
+ </View>
173
+ )}
174
+ </View>
175
+ )
176
+ }}
177
+ </Pressable>
178
+ )
179
+ }
180
+ )
181
+ TabsItem.displayName = 'TabsItem'
174
182
 
175
183
  TabsItem.propTypes = {
176
184
  ...a11yProps.propTypes,
package/src/Tags/Tags.jsx CHANGED
@@ -1,5 +1,6 @@
1
- import React from 'react'
1
+ import React, { forwardRef } from 'react'
2
2
  import PropTypes from 'prop-types'
3
+ import ABBPropTypes from 'airbnb-prop-types'
3
4
  import { Platform, Text, View } from 'react-native'
4
5
 
5
6
  import ButtonBase from '../Button/ButtonBase'
@@ -51,106 +52,113 @@ const selectIconTextTokens = ({
51
52
  }
52
53
  })
53
54
 
54
- const Tags = ({
55
- variant,
56
- tokens,
57
- items = [],
58
- values,
59
- initialValues,
60
- maxValues,
61
- onChange,
62
- readOnly = false,
63
- inactive = false,
64
- accessibilityRole = Platform.select({ web: 'group', default: 'none' }),
65
- ...rest
66
- }) => {
67
- const viewport = useViewport()
68
- const themeTokens = useThemeTokens('Tags', tokens, variant, { viewport })
69
- const stackTokens = selectTokens('StackView', themeTokens)
70
- const { direction, space } = themeTokens
71
-
72
- const getItemTokens = useThemeTokensCallback('TagsItem', tokens, variant)
73
- const getButtonTokens = (buttonState) => selectTokens('Button', getItemTokens(buttonState))
74
-
75
- const { currentValues, toggleOneValue } = useMultipleInputValues({
76
- initialValues,
77
- values,
78
- maxValues,
79
- onChange,
80
- readOnly
81
- })
82
-
83
- const a11y = a11yProps.select({
84
- accessibilityRole,
85
- ...rest
86
- })
87
- const itemA11yRole = 'checkbox'
88
-
89
- return (
90
- <StackWrap {...a11y} space={space} direction={direction} tokens={stackTokens}>
91
- {items.map(({ label, id = label, accessibilityLabel }, index) => {
92
- const isSelected = currentValues.includes(id)
93
-
94
- // Pass an object of relevant component state as first argument for any passed-in press handlers
95
- const pressHandlers = getPressHandlersWithArgs(rest, [{ id, label, currentValues }])
96
-
97
- const handlePress = () => {
98
- if (pressHandlers.onPress) pressHandlers.onPress()
99
- toggleOneValue(id)
100
- }
101
-
102
- const itemA11y = {
103
- accessibilityState: { checked: isSelected },
104
- accessibilityRole: itemA11yRole,
105
- accessibilityLabel,
106
- ...a11yProps.getPositionInSet(items.length, index)
107
- }
108
-
109
- return (
110
- <ButtonBase
111
- key={id}
112
- {...pressHandlers}
113
- onPress={handlePress}
114
- tokens={getButtonTokens}
115
- selected={isSelected}
116
- inactive={inactive}
117
- {...itemA11y}
118
- >
119
- {({ textStyles, ...buttonState }) => {
120
- // TODO: once Icon/IconButton designs are stable, see if this sort of styling around
121
- // an icon should go in Icon itself, or possibly via an IconText token set. Related issues:
122
- // - Icon: https://github.com/telus/universal-design-system/issues/327
123
- // - IconButton: https://github.com/telus/universal-design-system/issues/281
124
- // - Token sets: https://github.com/telus/universal-design-system/issues/782
125
-
126
- const itemTokens = getItemTokens(buttonState)
127
-
128
- const {
129
- iconTokens,
130
- iconPosition,
131
- iconSpace,
132
- iconWrapperStyle,
133
- icon: IconComponent
134
- } = selectIconTextTokens(itemTokens)
135
-
136
- const iconContent = IconComponent ? (
137
- <View style={iconWrapperStyle}>
138
- <Icon icon={IconComponent} tokens={iconTokens} />
139
- </View>
140
- ) : null
141
- const textContent = <Text style={textStyles}>{label}</Text>
142
-
143
- return getStackedContent(
144
- iconPosition === 'left' ? [iconContent, textContent] : [textContent, iconContent],
145
- { space: iconSpace, direction: 'row' }
146
- )
147
- }}
148
- </ButtonBase>
149
- )
150
- })}
151
- </StackWrap>
152
- )
153
- }
55
+ const Tags = forwardRef(
56
+ (
57
+ {
58
+ variant,
59
+ tokens,
60
+ items = [],
61
+ values,
62
+ initialValues,
63
+ maxValues,
64
+ onChange,
65
+ readOnly = false,
66
+ inactive = false,
67
+ accessibilityRole = Platform.select({ web: 'group', default: 'none' }),
68
+ ...rest
69
+ },
70
+ ref
71
+ ) => {
72
+ const viewport = useViewport()
73
+ const themeTokens = useThemeTokens('Tags', tokens, variant, { viewport })
74
+ const stackTokens = selectTokens('StackView', themeTokens)
75
+ const { direction, space } = themeTokens
76
+
77
+ const getItemTokens = useThemeTokensCallback('TagsItem', tokens, variant)
78
+ const getButtonTokens = (buttonState) => selectTokens('Button', getItemTokens(buttonState))
79
+
80
+ const { currentValues, toggleOneValue } = useMultipleInputValues({
81
+ initialValues,
82
+ values,
83
+ maxValues,
84
+ onChange,
85
+ readOnly
86
+ })
87
+
88
+ const a11y = a11yProps.select({
89
+ accessibilityRole,
90
+ ...rest
91
+ })
92
+ const itemA11yRole = 'checkbox'
93
+
94
+ return (
95
+ <StackWrap ref={ref} {...a11y} space={space} direction={direction} tokens={stackTokens}>
96
+ {items.map(({ label, id = label, accessibilityLabel, ref: itemRef }, index) => {
97
+ const isSelected = currentValues.includes(id)
98
+
99
+ // Pass an object of relevant component state as first argument for any passed-in press handlers
100
+ const pressHandlers = getPressHandlersWithArgs(rest, [{ id, label, currentValues }])
101
+
102
+ const handlePress = (event) => {
103
+ if (pressHandlers.onPress) pressHandlers.onPress()
104
+ toggleOneValue(id, event)
105
+ }
106
+
107
+ const itemA11y = {
108
+ accessibilityState: { checked: isSelected },
109
+ accessibilityRole: itemA11yRole,
110
+ accessibilityLabel,
111
+ ...a11yProps.getPositionInSet(items.length, index)
112
+ }
113
+
114
+ return (
115
+ <ButtonBase
116
+ ref={itemRef}
117
+ key={id}
118
+ {...pressHandlers}
119
+ onPress={handlePress}
120
+ tokens={getButtonTokens}
121
+ selected={isSelected}
122
+ inactive={inactive}
123
+ {...itemA11y}
124
+ >
125
+ {({ textStyles, ...buttonState }) => {
126
+ // TODO: once Icon/IconButton designs are stable, see if this sort of styling around
127
+ // an icon should go in Icon itself, or possibly via an IconText token set. Related issues:
128
+ // - Icon: https://github.com/telus/universal-design-system/issues/327
129
+ // - IconButton: https://github.com/telus/universal-design-system/issues/281
130
+ // - Token sets: https://github.com/telus/universal-design-system/issues/782
131
+
132
+ const itemTokens = getItemTokens(buttonState)
133
+
134
+ const {
135
+ iconTokens,
136
+ iconPosition,
137
+ iconSpace,
138
+ iconWrapperStyle,
139
+ icon: IconComponent
140
+ } = selectIconTextTokens(itemTokens)
141
+
142
+ const iconContent = IconComponent ? (
143
+ <View style={iconWrapperStyle}>
144
+ <Icon icon={IconComponent} tokens={iconTokens} />
145
+ </View>
146
+ ) : null
147
+ const textContent = <Text style={textStyles}>{label}</Text>
148
+
149
+ return getStackedContent(
150
+ iconPosition === 'left' ? [iconContent, textContent] : [textContent, iconContent],
151
+ { space: iconSpace, direction: 'row' }
152
+ )
153
+ }}
154
+ </ButtonBase>
155
+ )
156
+ })}
157
+ </StackWrap>
158
+ )
159
+ }
160
+ )
161
+ Tags.displayName = 'Tags'
154
162
 
155
163
  Tags.propTypes = {
156
164
  ...a11yProps.propTypes,
@@ -181,7 +189,11 @@ Tags.propTypes = {
181
189
  * which will be used in code and passed to any onChange function.
182
190
  * If not provided, the label is used.
183
191
  */
184
- id: PropTypes.string
192
+ id: PropTypes.string,
193
+ /**
194
+ * An optional ref for one individual Tag button in the tags
195
+ */
196
+ ref: ABBPropTypes.ref()
185
197
  })
186
198
  ),
187
199
  /**