@telus-uds/components-base 0.0.2-prerelease.3 → 0.0.2-prerelease.7

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 (266) hide show
  1. package/.ultra.cache.json +1 -0
  2. package/CHANGELOG.md +55 -0
  3. package/__fixtures__/testTheme.js +528 -42
  4. package/__tests__/Button/ButtonBase.test.jsx +3 -32
  5. package/__tests__/Checkbox/Checkbox.test.jsx +94 -0
  6. package/__tests__/Divider/Divider.test.jsx +26 -5
  7. package/__tests__/Feedback/Feedback.test.jsx +42 -0
  8. package/__tests__/FlexGrid/Col.test.jsx +5 -0
  9. package/__tests__/InputSupports/InputSupports.test.jsx +50 -0
  10. package/__tests__/List/List.test.jsx +60 -0
  11. package/__tests__/Radio/Radio.test.jsx +87 -0
  12. package/__tests__/Select/Select.test.jsx +93 -0
  13. package/__tests__/Skeleton/Skeleton.test.jsx +61 -0
  14. package/__tests__/Spacer/Spacer.test.jsx +63 -0
  15. package/__tests__/StackView/StackView.test.jsx +216 -0
  16. package/__tests__/StackView/StackWrap.test.jsx +47 -0
  17. package/__tests__/StackView/getStackedContent.test.jsx +295 -0
  18. package/__tests__/Tags/Tags.test.jsx +328 -0
  19. package/__tests__/TextInput/TextArea.test.jsx +34 -0
  20. package/__tests__/TextInput/TextInputBase.test.jsx +120 -0
  21. package/__tests__/Tooltip/Tooltip.test.jsx +65 -0
  22. package/__tests__/Tooltip/getTooltipPosition.test.js +79 -0
  23. package/__tests__/utils/useCopy.test.js +31 -0
  24. package/__tests__/utils/useResponsiveProp.test.jsx +202 -0
  25. package/__tests__/utils/{spacing.test.jsx → useSpacingScale.test.jsx} +1 -1
  26. package/__tests__/utils/useUniqueId.test.js +31 -0
  27. package/jest.config.js +8 -2
  28. package/lib/Box/Box.js +7 -2
  29. package/lib/Button/Button.js +10 -3
  30. package/lib/Button/ButtonBase.js +79 -75
  31. package/lib/Button/ButtonGroup.js +24 -49
  32. package/lib/Button/ButtonLink.js +5 -0
  33. package/lib/Checkbox/Checkbox.js +308 -0
  34. package/lib/Checkbox/CheckboxInput.native.js +6 -0
  35. package/lib/Checkbox/CheckboxInput.web.js +57 -0
  36. package/lib/Checkbox/index.js +2 -0
  37. package/lib/Divider/Divider.js +40 -2
  38. package/lib/Feedback/Feedback.js +132 -0
  39. package/lib/Feedback/index.js +2 -0
  40. package/lib/Icon/Icon.js +9 -6
  41. package/lib/Icon/IconText.js +72 -0
  42. package/lib/Icon/index.js +2 -1
  43. package/lib/InputLabel/InputLabel.js +88 -0
  44. package/lib/InputLabel/LabelContent.native.js +8 -0
  45. package/lib/InputLabel/LabelContent.web.js +17 -0
  46. package/lib/InputLabel/index.js +2 -0
  47. package/lib/InputSupports/InputSupports.js +90 -0
  48. package/lib/InputSupports/index.js +2 -0
  49. package/lib/InputSupports/propTypes.js +55 -0
  50. package/lib/Link/ChevronLink.js +35 -10
  51. package/lib/Link/InlinePressable.native.js +78 -0
  52. package/lib/Link/InlinePressable.web.js +32 -0
  53. package/lib/Link/Link.js +11 -10
  54. package/lib/Link/LinkBase.js +69 -124
  55. package/lib/Link/TextButton.js +20 -9
  56. package/lib/Link/index.js +2 -1
  57. package/lib/List/List.js +52 -0
  58. package/lib/List/ListItem.js +207 -0
  59. package/lib/List/index.js +2 -0
  60. package/lib/Pagination/PageButton.js +3 -26
  61. package/lib/Pagination/SideButton.js +32 -42
  62. package/lib/Radio/Radio.js +291 -0
  63. package/lib/Radio/RadioInput.native.js +6 -0
  64. package/lib/Radio/RadioInput.web.js +59 -0
  65. package/lib/Radio/index.js +2 -0
  66. package/lib/Select/Group.native.js +14 -0
  67. package/lib/Select/Group.web.js +18 -0
  68. package/lib/Select/Item.native.js +9 -0
  69. package/lib/Select/Item.web.js +15 -0
  70. package/lib/Select/Picker.native.js +87 -0
  71. package/lib/Select/Picker.web.js +63 -0
  72. package/lib/Select/Select.js +272 -0
  73. package/lib/Select/index.js +6 -0
  74. package/lib/Skeleton/Skeleton.js +119 -0
  75. package/lib/Skeleton/index.js +2 -0
  76. package/lib/Spacer/Spacer.js +98 -0
  77. package/lib/Spacer/index.js +2 -0
  78. package/lib/StackView/StackView.js +107 -0
  79. package/lib/StackView/StackWrap.js +32 -0
  80. package/lib/StackView/StackWrap.native.js +3 -0
  81. package/lib/StackView/StackWrapBox.js +90 -0
  82. package/lib/StackView/StackWrapGap.js +50 -0
  83. package/lib/StackView/common.js +30 -0
  84. package/lib/StackView/getStackedContent.js +111 -0
  85. package/lib/StackView/index.js +5 -0
  86. package/lib/Tags/Tags.js +217 -0
  87. package/lib/Tags/index.js +2 -0
  88. package/lib/TextInput/TextArea.js +82 -0
  89. package/lib/TextInput/TextInput.js +54 -0
  90. package/lib/TextInput/TextInputBase.js +229 -0
  91. package/lib/TextInput/index.js +3 -0
  92. package/lib/TextInput/propTypes.js +31 -0
  93. package/lib/ThemeProvider/useThemeTokens.js +54 -3
  94. package/lib/ToggleSwitch/ToggleSwitch.js +1 -1
  95. package/lib/Tooltip/Backdrop.native.js +35 -0
  96. package/lib/Tooltip/Backdrop.web.js +52 -0
  97. package/lib/Tooltip/Tooltip.js +315 -0
  98. package/lib/Tooltip/dictionary.js +8 -0
  99. package/lib/Tooltip/getTooltipPosition.js +164 -0
  100. package/lib/Tooltip/index.js +2 -0
  101. package/lib/TooltipButton/TooltipButton.js +64 -0
  102. package/lib/TooltipButton/index.js +2 -0
  103. package/lib/Typography/Typography.js +4 -23
  104. package/lib/ViewportProvider/ViewportProvider.js +25 -0
  105. package/lib/ViewportProvider/index.js +2 -43
  106. package/lib/ViewportProvider/useViewport.js +3 -0
  107. package/lib/ViewportProvider/useViewportListener.js +43 -0
  108. package/lib/index.js +15 -1
  109. package/lib/utils/a11y/index.js +1 -0
  110. package/lib/utils/a11y/textSize.js +33 -0
  111. package/lib/utils/index.js +7 -1
  112. package/lib/utils/info/index.js +7 -0
  113. package/lib/utils/info/platform/index.js +11 -0
  114. package/lib/utils/info/platform/platform.android.js +1 -0
  115. package/lib/utils/info/platform/platform.ios.js +1 -0
  116. package/lib/utils/info/platform/platform.native.js +4 -0
  117. package/lib/utils/info/platform/platform.web.js +1 -0
  118. package/lib/utils/info/versions.js +5 -0
  119. package/lib/utils/input.js +3 -1
  120. package/lib/utils/pressability.js +92 -0
  121. package/lib/utils/propTypes.js +77 -8
  122. package/lib/utils/useCopy.js +16 -0
  123. package/lib/utils/useResponsiveProp.js +47 -0
  124. package/lib/utils/{spacing/useSpacingScale.js → useSpacingScale.js} +30 -9
  125. package/lib/utils/useUniqueId.js +12 -0
  126. package/package.json +7 -5
  127. package/release-context.json +4 -4
  128. package/src/Box/Box.jsx +4 -2
  129. package/src/Button/Button.jsx +6 -3
  130. package/src/Button/ButtonBase.jsx +72 -75
  131. package/src/Button/ButtonGroup.jsx +22 -39
  132. package/src/Button/ButtonLink.jsx +11 -2
  133. package/src/Checkbox/Checkbox.jsx +275 -0
  134. package/src/Checkbox/CheckboxInput.native.jsx +6 -0
  135. package/src/Checkbox/CheckboxInput.web.jsx +55 -0
  136. package/src/Checkbox/index.js +3 -0
  137. package/src/Divider/Divider.jsx +38 -3
  138. package/src/Feedback/Feedback.jsx +108 -0
  139. package/src/Feedback/index.js +3 -0
  140. package/src/Icon/Icon.jsx +11 -6
  141. package/src/Icon/IconText.jsx +63 -0
  142. package/src/Icon/index.js +2 -1
  143. package/src/InputLabel/InputLabel.jsx +99 -0
  144. package/src/InputLabel/LabelContent.native.jsx +6 -0
  145. package/src/InputLabel/LabelContent.web.jsx +13 -0
  146. package/src/InputLabel/index.js +3 -0
  147. package/src/InputSupports/InputSupports.jsx +86 -0
  148. package/src/InputSupports/index.js +3 -0
  149. package/src/InputSupports/propTypes.js +44 -0
  150. package/src/Link/ChevronLink.jsx +28 -7
  151. package/src/Link/InlinePressable.native.jsx +73 -0
  152. package/src/Link/InlinePressable.web.jsx +37 -0
  153. package/src/Link/Link.jsx +17 -13
  154. package/src/Link/LinkBase.jsx +62 -139
  155. package/src/Link/TextButton.jsx +25 -11
  156. package/src/Link/index.js +2 -1
  157. package/src/List/List.jsx +47 -0
  158. package/src/List/ListItem.jsx +187 -0
  159. package/src/List/index.js +3 -0
  160. package/src/Pagination/PageButton.jsx +3 -17
  161. package/src/Pagination/SideButton.jsx +27 -38
  162. package/src/Radio/Radio.jsx +270 -0
  163. package/src/Radio/RadioInput.native.jsx +6 -0
  164. package/src/Radio/RadioInput.web.jsx +57 -0
  165. package/src/Radio/index.js +3 -0
  166. package/src/Select/Group.native.jsx +14 -0
  167. package/src/Select/Group.web.jsx +15 -0
  168. package/src/Select/Item.native.jsx +10 -0
  169. package/src/Select/Item.web.jsx +11 -0
  170. package/src/Select/Picker.native.jsx +95 -0
  171. package/src/Select/Picker.web.jsx +67 -0
  172. package/src/Select/Select.jsx +265 -0
  173. package/src/Select/index.js +8 -0
  174. package/src/Skeleton/Skeleton.jsx +101 -0
  175. package/src/Skeleton/index.js +3 -0
  176. package/src/Spacer/Spacer.jsx +91 -0
  177. package/src/Spacer/index.js +3 -0
  178. package/src/StackView/StackView.jsx +104 -0
  179. package/src/StackView/StackWrap.jsx +33 -0
  180. package/src/StackView/StackWrap.native.jsx +4 -0
  181. package/src/StackView/StackWrapBox.jsx +93 -0
  182. package/src/StackView/StackWrapGap.jsx +49 -0
  183. package/src/StackView/common.jsx +28 -0
  184. package/src/StackView/getStackedContent.jsx +106 -0
  185. package/src/StackView/index.js +6 -0
  186. package/src/Tags/Tags.jsx +206 -0
  187. package/src/Tags/index.js +3 -0
  188. package/src/TextInput/TextArea.jsx +78 -0
  189. package/src/TextInput/TextInput.jsx +52 -0
  190. package/src/TextInput/TextInputBase.jsx +220 -0
  191. package/src/TextInput/index.js +4 -0
  192. package/src/TextInput/propTypes.js +29 -0
  193. package/src/ThemeProvider/useThemeTokens.js +54 -3
  194. package/src/ToggleSwitch/ToggleSwitch.jsx +1 -1
  195. package/src/Tooltip/Backdrop.native.jsx +33 -0
  196. package/src/Tooltip/Backdrop.web.jsx +60 -0
  197. package/src/Tooltip/Tooltip.jsx +294 -0
  198. package/src/Tooltip/dictionary.js +8 -0
  199. package/src/Tooltip/getTooltipPosition.js +161 -0
  200. package/src/Tooltip/index.js +3 -0
  201. package/src/TooltipButton/TooltipButton.jsx +53 -0
  202. package/src/TooltipButton/index.js +3 -0
  203. package/src/Typography/Typography.jsx +4 -19
  204. package/src/ViewportProvider/ViewportProvider.jsx +21 -0
  205. package/src/ViewportProvider/index.jsx +2 -41
  206. package/src/ViewportProvider/useViewport.js +5 -0
  207. package/src/ViewportProvider/useViewportListener.js +43 -0
  208. package/src/index.js +15 -1
  209. package/src/utils/a11y/index.js +1 -0
  210. package/src/utils/a11y/textSize.js +30 -0
  211. package/src/utils/index.js +8 -1
  212. package/src/utils/info/index.js +8 -0
  213. package/src/utils/info/platform/index.js +11 -0
  214. package/src/utils/info/platform/platform.android.js +1 -0
  215. package/src/utils/info/platform/platform.ios.js +1 -0
  216. package/src/utils/info/platform/platform.native.js +4 -0
  217. package/src/utils/info/platform/platform.web.js +1 -0
  218. package/src/utils/info/versions.js +6 -0
  219. package/src/utils/input.js +2 -1
  220. package/src/utils/pressability.js +92 -0
  221. package/src/utils/propTypes.js +97 -13
  222. package/src/utils/useCopy.js +13 -0
  223. package/src/utils/useResponsiveProp.js +50 -0
  224. package/src/utils/{spacing/useSpacingScale.js → useSpacingScale.js} +25 -10
  225. package/src/utils/useUniqueId.js +14 -0
  226. package/stories/A11yText/A11yText.stories.jsx +11 -5
  227. package/stories/ActivityIndicator/ActivityIndicator.stories.jsx +11 -2
  228. package/stories/Box/Box.stories.jsx +29 -2
  229. package/stories/Button/Button.stories.jsx +21 -20
  230. package/stories/Button/ButtonGroup.stories.jsx +2 -1
  231. package/stories/Button/ButtonLink.stories.jsx +6 -4
  232. package/stories/Card/Card.stories.jsx +13 -1
  233. package/stories/Checkbox/Checkbox.stories.jsx +71 -0
  234. package/stories/Divider/Divider.stories.jsx +26 -2
  235. package/stories/ExpandCollapse/ExpandCollapse.stories.jsx +74 -79
  236. package/stories/Feedback/Feedback.stories.jsx +96 -0
  237. package/stories/FlexGrid/01 FlexGrid.stories.jsx +20 -7
  238. package/stories/Icon/Icon.stories.jsx +11 -3
  239. package/stories/InputLabel/InputLabel.stories.jsx +42 -0
  240. package/stories/Link/ChevronLink.stories.jsx +20 -4
  241. package/stories/Link/Link.stories.jsx +39 -3
  242. package/stories/Link/TextButton.stories.jsx +24 -2
  243. package/stories/List/List.stories.jsx +117 -0
  244. package/stories/Pagination/Pagination.stories.jsx +28 -14
  245. package/stories/Radio/Radio.stories.jsx +113 -0
  246. package/stories/Select/Select.stories.jsx +55 -0
  247. package/stories/SideNav/SideNav.stories.jsx +17 -2
  248. package/stories/Skeleton/Skeleton.stories.jsx +36 -0
  249. package/stories/Spacer/Spacer.stories.jsx +38 -0
  250. package/stories/StackView/StackView.stories.jsx +75 -0
  251. package/stories/StackView/StackWrap.stories.jsx +64 -0
  252. package/stories/Tags/Tags.stories.jsx +69 -0
  253. package/stories/TextInput/TextArea.stories.jsx +100 -0
  254. package/stories/TextInput/TextInput.stories.jsx +103 -0
  255. package/stories/ToggleSwitch/ToggleSwitch.stories.jsx +16 -3
  256. package/stories/Tooltip/Tooltip.stories.jsx +81 -0
  257. package/stories/TooltipButton/TooltipButton.stories.jsx +11 -0
  258. package/stories/Typography/Typography.stories.jsx +12 -3
  259. package/stories/platform-supports.web.jsx +1 -1
  260. package/stories/supports.jsx +110 -14
  261. package/lib/Pagination/useCopy.js +0 -10
  262. package/lib/utils/spacing/index.js +0 -2
  263. package/lib/utils/spacing/utils.js +0 -32
  264. package/src/Pagination/useCopy.js +0 -7
  265. package/src/utils/spacing/index.js +0 -3
  266. package/src/utils/spacing/utils.js +0 -28
@@ -0,0 +1,202 @@
1
+ import React from 'react'
2
+ import { renderHook } from '@testing-library/react-hooks'
3
+ import useResponsiveProp from '../../src/utils/useResponsiveProp'
4
+ import Viewport from '../../__fixtures__/Viewport'
5
+
6
+ const wrapper = ({ children, viewport }) => <Viewport viewport={viewport}>{children}</Viewport>
7
+
8
+ describe('useResponsiveProp', () => {
9
+ it('returns viewport indexes that explicitly match current value', () => {
10
+ const responsiveProp = { xs: 1, sm: 2, md: 3, lg: 4, xl: 5 }
11
+ const { rerender, result } = renderHook((args) => useResponsiveProp(args.responsiveProp), {
12
+ wrapper,
13
+ initialProps: { viewport: 'xs', responsiveProp }
14
+ })
15
+ expect(result.current).toBe(1)
16
+ rerender({ viewport: 'sm', responsiveProp })
17
+ expect(result.current).toBe(2)
18
+ rerender({ viewport: 'md', responsiveProp })
19
+ expect(result.current).toBe(3)
20
+ rerender({ viewport: 'lg', responsiveProp })
21
+ expect(result.current).toBe(4)
22
+ rerender({ viewport: 'xl', responsiveProp })
23
+ expect(result.current).toBe(5)
24
+ })
25
+
26
+ it('returns viewport indexes that implicitly match current value', () => {
27
+ const responsiveProp = { sm: 2, lg: 4 }
28
+ const { rerender, result } = renderHook((args) => useResponsiveProp(args.responsiveProp), {
29
+ wrapper,
30
+ initialProps: { viewport: 'xs', responsiveProp }
31
+ })
32
+ expect(result.current).toBe(undefined)
33
+ rerender({ viewport: 'sm', responsiveProp })
34
+ expect(result.current).toBe(2)
35
+ rerender({ viewport: 'md', responsiveProp })
36
+ expect(result.current).toBe(2)
37
+ rerender({ viewport: 'lg', responsiveProp })
38
+ expect(result.current).toBe(4)
39
+ rerender({ viewport: 'xl', responsiveProp })
40
+ expect(result.current).toBe(4)
41
+ })
42
+
43
+ it('returns provided defaultValue where there is no match', () => {
44
+ const responsiveProp = { md: 2, lg: 4 }
45
+ const defaultValue = 'some default value'
46
+ const { rerender, result } = renderHook(
47
+ (args) => useResponsiveProp(args.responsiveProp, args.defaultValue),
48
+ {
49
+ wrapper,
50
+ initialProps: { viewport: 'xs', responsiveProp, defaultValue }
51
+ }
52
+ )
53
+ expect(result.current).toBe(defaultValue)
54
+ rerender({ viewport: 'sm', responsiveProp, defaultValue })
55
+ expect(result.current).toBe(defaultValue)
56
+ rerender({ viewport: 'md', responsiveProp, defaultValue })
57
+ expect(result.current).toBe(2)
58
+ rerender({ viewport: 'lg', responsiveProp, defaultValue })
59
+ expect(result.current).toBe(4)
60
+ rerender({ viewport: 'xl', responsiveProp, defaultValue })
61
+ expect(result.current).toBe(4)
62
+ })
63
+
64
+ it('applies falsy values appropriately (replacing `undefined` with default value)', () => {
65
+ const responsiveProp = { sm: '', md: null, lg: false, xl: undefined }
66
+ const defaultValue = 0
67
+ const { rerender, result } = renderHook(
68
+ (args) => useResponsiveProp(args.responsiveProp, args.defaultValue),
69
+ {
70
+ wrapper,
71
+ initialProps: { viewport: 'xs', responsiveProp, defaultValue }
72
+ }
73
+ )
74
+ expect(result.current).toBe(0)
75
+ rerender({ viewport: 'sm', responsiveProp, defaultValue })
76
+ expect(result.current).toBe('')
77
+ rerender({ viewport: 'md', responsiveProp, defaultValue })
78
+ expect(result.current).toBe(null)
79
+ rerender({ viewport: 'lg', responsiveProp, defaultValue })
80
+ expect(result.current).toBe(false)
81
+ rerender({ viewport: 'xl', responsiveProp, defaultValue })
82
+ expect(result.current).toBe(false)
83
+ })
84
+
85
+ it('returns a non-object prop unchanged', () => {
86
+ const exactFn = () => {}
87
+ const defaultValue = 'some value'
88
+ const { rerender, result } = renderHook(
89
+ (args) => useResponsiveProp(args.responsiveProp, args.defaultValue),
90
+ {
91
+ wrapper,
92
+ initialProps: { viewport: 'xs', responsiveProp: exactFn, defaultValue }
93
+ }
94
+ )
95
+ expect(result.current).toBe(exactFn)
96
+ rerender({ viewport: 'sm', responsiveProp: exactFn, defaultValue })
97
+ expect(result.current).toBe(exactFn)
98
+ rerender({ viewport: 'md', responsiveProp: exactFn, defaultValue })
99
+ expect(result.current).toBe(exactFn)
100
+ rerender({ viewport: 'lg', responsiveProp: exactFn, defaultValue })
101
+ expect(result.current).toBe(exactFn)
102
+ rerender({ viewport: 'xl', responsiveProp: exactFn, defaultValue })
103
+ expect(result.current).toBe(exactFn)
104
+ })
105
+
106
+ it('returns a falsy value including explicit null unchanged, default value if undefined', () => {
107
+ const defaultValue = 'some value'
108
+ const { rerender, result } = renderHook(
109
+ (args) => useResponsiveProp(args.responsiveProp, args.defaultValue),
110
+ {
111
+ wrapper,
112
+ initialProps: { viewport: 'xs', responsiveProp: null, defaultValue }
113
+ }
114
+ )
115
+ expect(result.current).toBe(null)
116
+ rerender({ viewport: 'sm', responsiveProp: false, defaultValue })
117
+ expect(result.current).toBe(false)
118
+ rerender({ viewport: 'md', responsiveProp: 0, defaultValue })
119
+ expect(result.current).toBe(0)
120
+ rerender({ viewport: 'lg', responsiveProp: '', defaultValue })
121
+ expect(result.current).toBe('')
122
+ rerender({ viewport: 'xl', responsiveProp: undefined, defaultValue })
123
+ expect(result.current).toBe(defaultValue)
124
+ })
125
+
126
+ it('returns a non-responsive object unchanged, rather than the default value', () => {
127
+ const nonresponsiveProp = { a: 1, b: 2 }
128
+ const defaultValue = 'some value'
129
+ const { rerender, result } = renderHook(
130
+ (args) => useResponsiveProp(args.responsiveProp, args.defaultValue),
131
+ {
132
+ wrapper,
133
+ initialProps: { viewport: 'xs', responsiveProp: nonresponsiveProp, defaultValue }
134
+ }
135
+ )
136
+ expect(result.current).toBe(nonresponsiveProp)
137
+ rerender({ viewport: 'sm', responsiveProp: nonresponsiveProp, defaultValue })
138
+ expect(result.current).toBe(nonresponsiveProp)
139
+ rerender({ viewport: 'md', responsiveProp: nonresponsiveProp, defaultValue })
140
+ expect(result.current).toBe(nonresponsiveProp)
141
+ rerender({ viewport: 'lg', responsiveProp: nonresponsiveProp, defaultValue })
142
+ expect(result.current).toBe(nonresponsiveProp)
143
+ rerender({ viewport: 'xl', responsiveProp: nonresponsiveProp, defaultValue })
144
+ expect(result.current).toBe(nonresponsiveProp)
145
+ })
146
+
147
+ it('returns the smallest viewport property if current viewport value is missing', () => {
148
+ const responsiveProp = { xl: 5, lg: 4, md: 3 }
149
+ const defaultValue = 'some value'
150
+ const { result } = renderHook(
151
+ (args) => useResponsiveProp(args.responsiveProp, args.defaultValue),
152
+ {
153
+ initialProps: { responsiveProp, defaultValue }
154
+ }
155
+ )
156
+ expect(result.current).toBe(3)
157
+ })
158
+
159
+ it('treats a responsive prop with extra non-responsive properties as responsive', () => {
160
+ const responsiveProp = { md: 3, lg: 4, options: { a: 1, b: 2 } }
161
+ const defaultValue = 'some value'
162
+ const { rerender, result } = renderHook(
163
+ (args) => useResponsiveProp(args.responsiveProp, args.defaultValue),
164
+ {
165
+ wrapper,
166
+ initialProps: { viewport: 'xs', responsiveProp, defaultValue }
167
+ }
168
+ )
169
+ expect(result.current).toBe(defaultValue)
170
+ rerender({ viewport: 'sm', responsiveProp, defaultValue })
171
+ expect(result.current).toBe(defaultValue)
172
+ rerender({ viewport: 'md', responsiveProp, defaultValue })
173
+ expect(result.current).toBe(3)
174
+ rerender({ viewport: 'lg', responsiveProp, defaultValue })
175
+ expect(result.current).toBe(4)
176
+ rerender({ viewport: 'xl', responsiveProp, defaultValue })
177
+ expect(result.current).toBe(4)
178
+ })
179
+
180
+ it('treats an object with only explicitly-undefined viewport properties as responsive', () => {
181
+ // For example, if some logic generates the values for each viewport of a responsive prop,
182
+ // and in a certain case, they happen to all come out as `undefined`.
183
+ const responsiveProp = { sm: undefined, lg: undefined, options: { a: 1, b: 2 } }
184
+ const defaultValue = 'some value'
185
+ const { rerender, result } = renderHook(
186
+ (args) => useResponsiveProp(args.responsiveProp, args.defaultValue),
187
+ {
188
+ wrapper,
189
+ initialProps: { viewport: 'xs', responsiveProp, defaultValue }
190
+ }
191
+ )
192
+ expect(result.current).toBe(defaultValue)
193
+ rerender({ viewport: 'sm', responsiveProp, defaultValue })
194
+ expect(result.current).toBe(defaultValue)
195
+ rerender({ viewport: 'md', responsiveProp, defaultValue })
196
+ expect(result.current).toBe(defaultValue)
197
+ rerender({ viewport: 'lg', responsiveProp, defaultValue })
198
+ expect(result.current).toBe(defaultValue)
199
+ rerender({ viewport: 'xl', responsiveProp, defaultValue })
200
+ expect(result.current).toBe(defaultValue)
201
+ })
202
+ })
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import { renderHook } from '@testing-library/react-hooks'
3
- import { useSpacingScale } from '../../src/utils/spacing'
3
+ import { useSpacingScale } from '../../src/utils'
4
4
  import Theme from '../../__fixtures__/Theme'
5
5
  import Viewport from '../../__fixtures__/Viewport'
6
6
 
@@ -0,0 +1,31 @@
1
+ import { renderHook } from '@testing-library/react-hooks'
2
+ import useUniqueId from '../../lib/utils/useUniqueId'
3
+
4
+ describe('useUniqueId hook', () => {
5
+ it('returns the same id for each re-render, but a different id for a new instance', () => {
6
+ const { result, rerender } = renderHook(() => useUniqueId('prefix'))
7
+
8
+ expect(result.current).toBe('prefix-1')
9
+
10
+ rerender()
11
+
12
+ expect(result.current).toBe('prefix-1')
13
+
14
+ // has to be done within the same test case, as we can't ensure order in which tests are being run in parallel
15
+ const { result: result2, rerender: rerender2 } = renderHook(() => useUniqueId('prefix'))
16
+
17
+ expect(result.current).toBe('prefix-1')
18
+ expect(result2.current).toBe('prefix-2')
19
+
20
+ // ensure that instances' rerendering doesn't affect one another
21
+ rerender2()
22
+
23
+ expect(result.current).toBe('prefix-1')
24
+ expect(result2.current).toBe('prefix-2')
25
+
26
+ rerender()
27
+
28
+ expect(result.current).toBe('prefix-1')
29
+ expect(result2.current).toBe('prefix-2')
30
+ })
31
+ })
package/jest.config.js CHANGED
@@ -13,11 +13,17 @@ module.exports = {
13
13
  // __dirname here tells babel to look in components-base for babel root when running from monorepo root
14
14
  '\\.(js|jsx)$': ['babel-jest', { cwd: __dirname }]
15
15
  },
16
- setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'],
16
+ setupFilesAfterEnv: [
17
+ '@testing-library/jest-native/extend-expect',
18
+ // Fail tests that use console.error or console.warn
19
+ '<rootDir>/../../jest-no-console'
20
+ ],
17
21
  moduleNameMapper: {
18
22
  '.+\\.(otf|svg|png|jpg)$': 'identity-obj-proxy'
19
23
  },
20
- transformIgnorePatterns: preset.transformIgnorePatterns,
24
+ transformIgnorePatterns: [
25
+ 'node_modules/(?!(jest-)?react-native|@react-native-community|@react-native-picker)'
26
+ ],
21
27
  // Count everything in src when calculating coverage
22
28
  collectCoverageFrom: ['src/**/*.{js,jsx}']
23
29
  }
package/lib/Box/Box.js CHANGED
@@ -102,6 +102,7 @@ const Box = ({
102
102
  variant,
103
103
  tokens,
104
104
  scroll,
105
+ testID,
105
106
  ...rest
106
107
  }) => {
107
108
  const a11y = a11yProps.select(rest);
@@ -117,11 +118,14 @@ const Box = ({
117
118
  if (scroll) {
118
119
  const scrollProps = typeof scroll === 'object' ? scroll : {};
119
120
  scrollProps.contentContainerStyle = [styles, scrollProps.contentContainerStyle];
120
- return /*#__PURE__*/React.createElement(ScrollView, Object.assign({}, scrollProps, a11y), children);
121
+ return /*#__PURE__*/React.createElement(ScrollView, Object.assign({}, scrollProps, a11y, {
122
+ testID: testID
123
+ }), children);
121
124
  }
122
125
 
123
126
  return /*#__PURE__*/React.createElement(View, Object.assign({}, a11y, {
124
- style: styles
127
+ style: styles,
128
+ testID: testID
125
129
  }), children);
126
130
  };
127
131
 
@@ -136,6 +140,7 @@ Box.propTypes = {
136
140
  scroll: PropTypes.oneOfType([PropTypes.bool, ScrollView.propTypes ? PropTypes.shape(ScrollView.propTypes) : PropTypes.object]),
137
141
  variant: variantProp.propType,
138
142
  tokens: getTokensPropType('Box'),
143
+ testID: PropTypes.string,
139
144
  children: PropTypes.node.isRequired
140
145
  };
141
146
  export default Box;
@@ -1,14 +1,21 @@
1
1
  import React from 'react';
2
2
  import ButtonBase from './ButtonBase';
3
3
  import buttonPropTypes from './propTypes';
4
+ import { useThemeTokensCallback } from '../ThemeProvider';
4
5
  import { a11yProps } from '../utils/propTypes';
5
6
 
6
7
  const Button = ({
7
8
  accessibilityRole = 'button',
9
+ tokens,
10
+ variant,
8
11
  ...props
9
- }) => /*#__PURE__*/React.createElement(ButtonBase, Object.assign({}, props, {
10
- accessibilityRole: accessibilityRole
11
- }));
12
+ }) => {
13
+ const getTokens = useThemeTokensCallback('Button', tokens, variant);
14
+ return /*#__PURE__*/React.createElement(ButtonBase, Object.assign({}, props, {
15
+ tokens: getTokens,
16
+ accessibilityRole: accessibilityRole
17
+ }));
18
+ };
12
19
 
13
20
  Button.propTypes = { ...a11yProps.types,
14
21
  ...buttonPropTypes
@@ -1,17 +1,8 @@
1
1
  import React from 'react';
2
2
  import { Pressable, Text, View, StyleSheet, Platform } from 'react-native';
3
- import { useThemeTokensCallback } from '../ThemeProvider';
4
3
  import { applyTextStyles, applyShadowToken } from '../ThemeProvider/utils';
5
4
  import buttonPropTypes from './propTypes';
6
- import { a11yProps, hrefAttrsProp, linkProps } from '../utils/propTypes';
7
-
8
- const getCursorStyle = (inactive, accessibilityRole) => {
9
- if (inactive) return 'not-allowed'; // These roles should result in cursor: pointer but don't in current RNW releases
10
-
11
- if (['checkbox', 'radio', 'switch'].includes(accessibilityRole)) return 'pointer'; // For everything else, let React Native Web figure it out internally
12
-
13
- return undefined;
14
- };
5
+ import { a11yProps, getCursorStyle, hrefAttrsProp, linkProps, resolvePressableState, resolvePressableTokens } from '../utils';
15
6
 
16
7
  const getOuterBorderOffset = ({
17
8
  outerBorderGap = 0,
@@ -19,6 +10,7 @@ const getOuterBorderOffset = ({
19
10
  }) => outerBorderGap + outerBorderWidth;
20
11
 
21
12
  const selectOuterContainerStyles = ({
13
+ alignSelf,
22
14
  opacity,
23
15
  outerBorderColor,
24
16
  outerBorderWidth,
@@ -26,6 +18,7 @@ const selectOuterContainerStyles = ({
26
18
  outerBorderRadius = 0,
27
19
  outerBackgroundColor
28
20
  }) => ({
21
+ alignSelf,
29
22
  padding: outerBorderGap,
30
23
  borderWidth: outerBorderWidth,
31
24
  borderColor: outerBorderColor,
@@ -37,13 +30,7 @@ const selectOuterContainerStyles = ({
37
30
  const selectOuterWidthStyles = ({
38
31
  outerBorderGap,
39
32
  outerBorderWidth,
40
- width,
41
- // TODO: make margin the responsibility of a parent
42
- // https://github.com/telus/universal-design-system/issues/525
43
- marginTop = 0,
44
- marginBottom = 0,
45
- marginLeft = 0,
46
- marginRight = 0
33
+ width
47
34
  }) => {
48
35
  // The inner container's bounding box is the bounding box of the button overall
49
36
  // so this many device pixels will sit outside of the overall bounding box
@@ -52,22 +39,16 @@ const selectOuterWidthStyles = ({
52
39
  outerBorderWidth
53
40
  });
54
41
  const widthStyles = {
55
- marginTop: marginTop - outerBorderOffset,
56
- marginBottom: marginBottom - outerBorderOffset,
57
- marginLeft: marginLeft - outerBorderOffset,
58
- marginRight: marginRight - outerBorderOffset
42
+ margin: 0 - outerBorderOffset
59
43
  };
60
44
 
61
45
  if (!width) {
62
46
  return { ...widthStyles,
63
47
  // Wrap content, stopping a flex parent's default align-items: stretch stretching focus ring beyond content
64
48
  ...Platform.select({
49
+ // width: fit-content isn't supported on Firefox; can't cascade props like CSS `width: fit-content; width: --moz-fit-content;`
65
50
  web: {
66
- width: 'fit-content'
67
- },
68
- // No fit-content or inline-block in RN. TODO: we might need to provide a prop to allow flex-end or center
69
- native: {
70
- alignSelf: 'flex-start'
51
+ display: 'inline-flex'
71
52
  }
72
53
  })
73
54
  };
@@ -104,7 +85,8 @@ const selectInnerContainerStyles = ({
104
85
  paddingTop,
105
86
  paddingBottom,
106
87
  shadow,
107
- borderWidth
88
+ borderWidth,
89
+ minWidth
108
90
  }) => {
109
91
  // Subtract border width from padding so overall button width/height doesn't
110
92
  // jump around if the border width changes (avoiding NaN and negative padding)
@@ -116,6 +98,7 @@ const selectInnerContainerStyles = ({
116
98
  paddingTop: offsetBorder(paddingTop),
117
99
  paddingBottom: offsetBorder(paddingBottom),
118
100
  backgroundColor,
101
+ minWidth,
119
102
  ...applyShadowToken(shadow)
120
103
  };
121
104
  };
@@ -148,76 +131,83 @@ const selectTextStyles = ({
148
131
 
149
132
  const selectWebOnlyStyles = (inactive, themeTokens, {
150
133
  accessibilityRole
151
- }) => Platform.OS === 'web' ? {
152
- // if it would overflow the container, wraps instead
153
- maxWidth: `calc(100% + ${getOuterBorderOffset(themeTokens) * 2}px)`,
154
- cursor: getCursorStyle(inactive, accessibilityRole),
155
- outline: 'none' // removes the default browser :focus outline
134
+ }) => {
135
+ return Platform.select({
136
+ web: {
137
+ // if it would overflow the container, wraps instead
138
+ maxWidth: `calc(100% + ${getOuterBorderOffset(themeTokens) * 2}px)`,
139
+ outline: 'none',
140
+ // removes the default browser :focus outline
141
+ ...getCursorStyle(inactive, accessibilityRole)
142
+ },
143
+ default: {}
144
+ });
145
+ }; // TODO: see if this can be made into a generalised utility, ideally when
146
+ // there is a stable, generic, generalised approach to within-component text
147
+
148
+
149
+ const resolveChildren = (children, {
150
+ textStyles,
151
+ state
152
+ }) => {
153
+ switch (typeof children) {
154
+ case 'function':
155
+ return children(state);
156
156
 
157
- } : {};
157
+ case 'string':
158
+ return /*#__PURE__*/React.createElement(Text, {
159
+ style: textStyles
160
+ }, children);
161
+
162
+ default:
163
+ return children;
164
+ }
165
+ };
158
166
 
159
167
  const ButtonBase = ({
160
168
  href,
161
169
  hrefAttrs,
162
170
  children,
163
- variant,
164
171
  onPress,
165
- tokens,
172
+ tokens = {},
166
173
  disabled = false,
167
174
  // alias for inactive
168
175
  inactive = disabled,
169
176
  selected = false,
170
177
  ...rest
171
178
  }) => {
172
- const getTokens = useThemeTokensCallback('Button', tokens, variant);
173
-
174
- const getButtonState = ({
175
- pressed,
176
- focused,
177
- hovered
178
- }) => ({
179
- pressed,
180
- focus: focused,
181
- hover: hovered,
179
+ const extraButtonState = {
182
180
  inactive,
183
181
  selected
184
- });
182
+ };
185
183
 
186
- const getTokensByPressableState = pressableState => getTokens(getButtonState(pressableState));
184
+ const resolveTokens = pressableState => resolvePressableTokens(tokens, pressableState, extraButtonState);
187
185
 
188
186
  const a11y = a11yProps.select(rest);
189
187
 
190
188
  const getPressableStyle = pressableState => {
191
- const themeTokens = getTokensByPressableState(pressableState);
192
- return [staticStyles.wrapper, selectWebOnlyStyles(inactive, themeTokens, a11y), selectOuterContainerStyles(themeTokens), selectOuterWidthStyles(themeTokens)];
189
+ const themeTokens = resolveTokens(pressableState);
190
+ return [staticStyles.row, selectWebOnlyStyles(inactive, themeTokens, a11y), selectOuterContainerStyles(themeTokens), selectOuterWidthStyles(themeTokens)];
193
191
  };
194
192
 
195
- const handlePress = linkProps.handleHref({
196
- href,
197
- onPress
198
- });
199
193
  return /*#__PURE__*/React.createElement(Pressable, Object.assign({
200
- onPress: handlePress,
194
+ href: href,
195
+ onPress: linkProps.handleHref({
196
+ href,
197
+ onPress
198
+ }),
201
199
  style: getPressableStyle,
202
- disabled: inactive,
203
- href: href
200
+ disabled: inactive
204
201
  }, hrefAttrsProp.spread(hrefAttrs), a11y), pressableState => {
205
- const themeTokens = getTokensByPressableState(pressableState);
202
+ const themeTokens = resolveTokens(pressableState);
206
203
  const containerStyles = selectInnerContainerStyles(themeTokens);
207
204
  const borderStyles = selectBorderStyles(themeTokens);
208
- const textStyles = { ...selectTextStyles(themeTokens),
209
- ...Platform.select({
210
- // TODO: https://github.com/telus/universal-design-system/issues/487
211
- web: {
212
- transition: 'color 200ms'
213
- }
214
- })
215
- }; // If the container has a width set, fill it instead of sizing from content.
205
+ const textStyles = [selectTextStyles(themeTokens), staticStyles.text]; // If the container has a width set, fill it instead of sizing from content.
216
206
  // If in future we support text alignments other than center, add here.
217
207
 
218
- const stretchStyles = !!themeTokens.width && staticStyles.center;
208
+ const stretchStyles = themeTokens.width ? staticStyles.stretch : staticStyles.align;
219
209
  return /*#__PURE__*/React.createElement(View, {
220
- style: [containerStyles, borderStyles, stretchStyles, Platform.select({
210
+ style: [containerStyles, borderStyles, stretchStyles, staticStyles.row, Platform.select({
221
211
  web: {
222
212
  maxWidth: '100%',
223
213
  // ensure overflowing content wraps
@@ -225,11 +215,12 @@ const ButtonBase = ({
225
215
  transition: 'background-color 200ms, border-color 200ms'
226
216
  }
227
217
  })]
228
- }, typeof children === 'function' ? children({ ...getButtonState(pressableState),
218
+ }, resolveChildren(children, {
219
+ state: { ...resolvePressableState(pressableState, extraButtonState),
220
+ textStyles
221
+ },
229
222
  textStyles
230
- }) : /*#__PURE__*/React.createElement(Text, {
231
- style: textStyles
232
- }, children));
223
+ }));
233
224
  });
234
225
  };
235
226
 
@@ -238,11 +229,24 @@ ButtonBase.propTypes = { ...a11yProps.types,
238
229
  ...linkProps.types
239
230
  };
240
231
  const staticStyles = StyleSheet.create({
241
- wrapper: {
242
- flexDirection: 'row' // ensures alignSelf is horizontal
243
-
232
+ row: {
233
+ // Apply all button alignment horizontally; no vertical stacking within a button
234
+ flexDirection: 'row'
235
+ },
236
+ text: {
237
+ flexGrow: 1,
238
+ // On native but not web, flexShrink here wraps text prematurely
239
+ ...Platform.select({
240
+ // TODO: https://github.com/telus/universal-design-system/issues/487
241
+ web: {
242
+ transition: 'color 200ms'
243
+ }
244
+ })
245
+ },
246
+ align: {
247
+ alignItems: 'center'
244
248
  },
245
- center: {
249
+ stretch: {
246
250
  flex: 1,
247
251
  alignItems: 'center',
248
252
  justifyContent: 'center'