@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,275 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { Pressable, StyleSheet, Text, View } from 'react-native'
4
+
5
+ import CheckboxInput from './CheckboxInput'
6
+ // @todo move `LabelContent` outside of the `InputLabel` and fix
7
+ // the issue with the cursor not being pointer on Web
8
+ import CheckboxLabel from '../InputLabel/LabelContent'
9
+ import Feedback from '../Feedback'
10
+ import StackView from '../StackView'
11
+ import { applyShadowToken, applyTextStyles, useThemeTokensCallback } from '../ThemeProvider'
12
+ import { getTokensPropType, useInputValue, variantProp } from '../utils'
13
+ import { a11yProps } from '../utils/propTypes'
14
+ import useUniqueId from '../utils/useUniqueId'
15
+
16
+ const selectInputStyles = (
17
+ {
18
+ iconBackgroundColor,
19
+ inputBorderColor,
20
+ inputBorderRadius,
21
+ inputBorderWidth,
22
+ inputBackgroundColor,
23
+ inputHeight,
24
+ inputOutlineColor,
25
+ inputOutlineWidth,
26
+ inputShadow,
27
+ inputWidth
28
+ },
29
+ isChecked
30
+ ) => ({
31
+ borderColor: inputBorderColor,
32
+ borderWidth: inputBorderWidth,
33
+ borderRadius: inputBorderRadius,
34
+ backgroundColor: isChecked ? iconBackgroundColor : inputBackgroundColor,
35
+ outlineStyle: 'solid',
36
+ outlineColor: inputOutlineColor,
37
+ outlineWidth: inputOutlineWidth,
38
+ height: inputHeight,
39
+ width: inputWidth,
40
+ ...applyShadowToken(inputShadow)
41
+ })
42
+ const selectLabelStyles = ({
43
+ labelColor,
44
+ labelFontName,
45
+ labelFontSize,
46
+ labelFontWeight,
47
+ labelMarginLeft,
48
+ labelLineHeight
49
+ }) => ({
50
+ marginLeft: labelMarginLeft,
51
+ ...applyTextStyles({
52
+ color: labelColor,
53
+ fontName: labelFontName,
54
+ fontWeight: labelFontWeight,
55
+ fontSize: labelFontSize,
56
+ lineHeight: labelLineHeight
57
+ })
58
+ })
59
+ const selectContainerStyles = ({ containerMarginBottom }) => ({
60
+ marginBottom: containerMarginBottom
61
+ })
62
+ const selectIconTokens = ({ iconColor, iconSize }) => ({
63
+ color: iconColor,
64
+ size: iconSize
65
+ })
66
+ const selectFeedbackTokens = ({ feedbackMarginBottom, feedbackMarginTop, feedbackPosition }) => ({
67
+ feedbackPosition,
68
+ feedbackMarginBottom,
69
+ feedbackMarginTop
70
+ })
71
+
72
+ /**
73
+ * Basic Checkbox component.
74
+ *
75
+ * ## Component API
76
+ *
77
+ * Use `label` prop to provide a label for the checkbox.
78
+ * For a disabled `Checkbox` set the `inactive` prop to `true`.
79
+ *
80
+ * ### Controlled version
81
+ *
82
+ * If the checkbox is controlled from outside, it needs to receive `checked` and `onChange` props.
83
+ *
84
+ * ### Uncontrolled version
85
+ *
86
+ * In case of uncontrolled checkbox you can use `defaultChecked` prop to provide the initial value.
87
+ * Whenever the checkbox gets toggled, it calls the `onChange` callback with the new value (boolean).
88
+ *
89
+ * ### Using within forms
90
+ *
91
+ * You can pass `name` and `value` props if you need a particular checkbox to be a part of a group of
92
+ * checkboxes on a form.
93
+ *
94
+ * ### Validation and feedback
95
+ *
96
+ * You can use the `feedback` prop to provide a comment related to the checkbox element. If the comment
97
+ * is related to a validation error, the checkbox and the feedback can be styled accordingly by setting
98
+ * the `error` prop to `true` (can also be done without feedback).
99
+ *
100
+ * ## A11y guidelines
101
+ *
102
+ * Checkbox component accepts all the common accessibility props, but also sets some defaults, including
103
+ * accessibility role `'checkbox'` and accessibility state that depends on the other props (`checked`, `inactive`)
104
+ * or the internal state in case of uncontrolled checkbox.
105
+ *
106
+ */
107
+ const Checkbox = ({
108
+ checked,
109
+ defaultChecked,
110
+ error = false,
111
+ feedback,
112
+ id,
113
+ inactive,
114
+ label,
115
+ name,
116
+ onChange,
117
+ tokens,
118
+ value,
119
+ variant,
120
+ ...rest
121
+ }) => {
122
+ const { currentValue: isChecked, setValue: setIsChecked, isControlled } = useInputValue(
123
+ {
124
+ value: checked,
125
+ initialValue: defaultChecked,
126
+ onChange
127
+ },
128
+ 'useCheckboxValue'
129
+ )
130
+ const getTokens = useThemeTokensCallback('Checkbox', tokens, {
131
+ checked: isChecked,
132
+ inactive,
133
+ error,
134
+ ...variant
135
+ })
136
+ const defaultTokens = getTokens()
137
+ const { feedbackMarginBottom, feedbackMarginTop, feedbackPosition } = selectFeedbackTokens(
138
+ defaultTokens
139
+ )
140
+ const styles = StyleSheet.create({
141
+ feedbackContainer: { marginTop: feedbackMarginTop, marginBottom: feedbackMarginBottom }
142
+ })
143
+ const handleChange = () => {
144
+ if (!inactive) {
145
+ setIsChecked(!isChecked)
146
+ }
147
+ }
148
+ const accessibilityProps = a11yProps.select(rest)
149
+ const uniqueId = useUniqueId('checkbox')
150
+ const inputId = id ?? uniqueId
151
+
152
+ // @todo our current version of React Native Web doesn't include
153
+ // keyboard support on `Pressable` (which starts with 0.15.3), so
154
+ // the complete accessibility of the `Checkbox` component on Web
155
+ // (namely, change on key pressed) is pending RNW upgrade
156
+ // (see https://github.com/necolas/react-native-web/issues/1950)
157
+ return (
158
+ <View style={staticStyles.wrapper}>
159
+ <StackView direction={feedbackPosition === 'top' ? 'column-reverse' : 'column'} space={0}>
160
+ <Pressable
161
+ disabled={inactive}
162
+ onPress={handleChange}
163
+ accessibilityRole="checkbox"
164
+ accessibilityState={{ checked: isChecked, disabled: inactive }}
165
+ {...accessibilityProps}
166
+ >
167
+ {({ focused: focus, hovered: hover, pressed }) => {
168
+ const { icon: IconComponent, ...stateTokens } = getTokens({ focus, hover, pressed })
169
+ const iconTokens = selectIconTokens(stateTokens)
170
+
171
+ return (
172
+ <View style={[staticStyles.container, selectContainerStyles(stateTokens)]}>
173
+ <View
174
+ style={StyleSheet.flatten([
175
+ staticStyles.defaultInputStyles,
176
+ selectInputStyles(stateTokens, isChecked)
177
+ ])}
178
+ testID="Checkbox-Input"
179
+ >
180
+ {/* Add a real input on Web, skip on native platforms */}
181
+ <CheckboxInput
182
+ checked={isChecked}
183
+ defaultChecked={defaultChecked}
184
+ disabled={inactive}
185
+ id={inputId}
186
+ isControlled={isControlled}
187
+ name={name}
188
+ value={value}
189
+ />
190
+ {isChecked && IconComponent && (
191
+ <IconComponent tokens={iconTokens} testID="Checkbox-Icon" />
192
+ )}
193
+ </View>
194
+ {Boolean(label) && (
195
+ <Text style={selectLabelStyles(stateTokens)}>
196
+ <CheckboxLabel forId={inputId}>{label}</CheckboxLabel>
197
+ </Text>
198
+ )}
199
+ </View>
200
+ )
201
+ }}
202
+ </Pressable>
203
+ {Boolean(feedback) && (
204
+ <View style={styles.feedbackContainer}>
205
+ <Feedback
206
+ title={feedback}
207
+ variant={{ icon: error === true }}
208
+ validation={error === true ? 'error' : undefined}
209
+ />
210
+ </View>
211
+ )}
212
+ </StackView>
213
+ </View>
214
+ )
215
+ }
216
+
217
+ Checkbox.propTypes = {
218
+ ...a11yProps.propTypes,
219
+ /**
220
+ * Use `checked` for controlled Checkbox. For uncontrolled Checkbox, use the `defaultChecked` prop.
221
+ */
222
+ checked: PropTypes.bool,
223
+ /**
224
+ * Use `defaultChecked` to provide the initial value for an uncontrolled Checkbox.
225
+ */
226
+ defaultChecked: PropTypes.bool,
227
+ /**
228
+ * A detailed description of related validation error or additional instructions (depending on the `error` prop).
229
+ */
230
+ feedback: PropTypes.string,
231
+ /**
232
+ * Checkbox ID.
233
+ */
234
+ id: PropTypes.string,
235
+ /**
236
+ * Whether the corresponding input is disabled or active.
237
+ */
238
+ inactive: PropTypes.bool,
239
+ /**
240
+ * The label.
241
+ */
242
+ label: PropTypes.string,
243
+ /**
244
+ * Associate this checkbox with a group (set as the name attribute).
245
+ */
246
+ name: PropTypes.string,
247
+ /**
248
+ * Whether the underlying input triggered a validation error or not.
249
+ */
250
+ error: PropTypes.bool,
251
+ /**
252
+ * The value. Must be unique within the group.
253
+ */
254
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
255
+ /**
256
+ * Callback called when a controlled checkbox gets interacted with.
257
+ */
258
+ onChange: PropTypes.func,
259
+ /**
260
+ * Checkbox tokens.
261
+ */
262
+ tokens: getTokensPropType('Checkbox'),
263
+ /**
264
+ * Checkbox variant.
265
+ */
266
+ variant: variantProp.propType
267
+ }
268
+
269
+ export default Checkbox
270
+
271
+ const staticStyles = StyleSheet.create({
272
+ wrapper: { backgroundColor: 'transparent' },
273
+ container: { flexDirection: 'row', alignItems: 'center' },
274
+ defaultInputStyles: { alignItems: 'center', justifyContent: 'center' }
275
+ })
@@ -0,0 +1,6 @@
1
+ /**
2
+ * There's no checkbox input on native platforms, so this is a noop.
3
+ */
4
+ const CheckboxInput = () => null
5
+
6
+ export default CheckboxInput
@@ -0,0 +1,55 @@
1
+ import React, { forwardRef } from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ /**
5
+ * On Web we need to include an actual input but hide it.
6
+ */
7
+ const CheckboxInput = forwardRef(
8
+ ({ checked, defaultChecked, disabled, id, isControlled, name, onChange, value }, ref) => {
9
+ const handleClick = (event) => {
10
+ // Cancel the click dispatched via the label tag, since it's already wrapped
11
+ // in <Pressable>
12
+ event.preventDefault()
13
+ event.stopPropagation()
14
+ }
15
+
16
+ return (
17
+ <input
18
+ checked={isControlled ? checked : undefined}
19
+ defaultChecked={isControlled ? undefined : defaultChecked}
20
+ disabled={disabled}
21
+ hidden
22
+ id={id}
23
+ name={name}
24
+ onChange={onChange}
25
+ onClick={handleClick}
26
+ ref={ref}
27
+ type="checkbox"
28
+ value={value}
29
+ />
30
+ )
31
+ }
32
+ )
33
+ CheckboxInput.displayName = 'CheckboxInput'
34
+
35
+ CheckboxInput.propTypes = {
36
+ checked: PropTypes.bool,
37
+ defaultChecked: PropTypes.bool,
38
+ disabled: PropTypes.bool,
39
+ id: PropTypes.string.isRequired,
40
+ isControlled: PropTypes.bool.isRequired,
41
+ name: PropTypes.string,
42
+ onChange: PropTypes.func,
43
+ value: PropTypes.string
44
+ }
45
+
46
+ CheckboxInput.defaultProps = {
47
+ checked: undefined,
48
+ defaultChecked: undefined,
49
+ disabled: false,
50
+ name: undefined,
51
+ onChange: () => {},
52
+ value: undefined
53
+ }
54
+
55
+ export default CheckboxInput
@@ -0,0 +1,3 @@
1
+ import Checkbox from './Checkbox'
2
+
3
+ export default Checkbox
@@ -2,7 +2,12 @@ import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import { View, StyleSheet, Platform } from 'react-native'
4
4
  import { useThemeTokens } from '../ThemeProvider'
5
- import { getTokensPropType, variantProp } from '../utils/propTypes'
5
+ import Spacer from '../Spacer'
6
+ import { getTokensPropType, variantProp, spacingProps } from '../utils'
7
+ /**
8
+ * @typedef {import('../utils/propTypes.js').SpacingIndex} SpacingIndex
9
+ * @typedef {import('../utils/propTypes.js').SpacingObject} SpacingObject
10
+ */
6
11
 
7
12
  /**
8
13
  * A basic divider component, horizontal by default. Color and thickness can be controlled by theme.
@@ -16,11 +21,31 @@ import { getTokensPropType, variantProp } from '../utils/propTypes'
16
21
  *
17
22
  * In a flexbox row, vertical dividers will automatically size to their parent height.
18
23
  *
24
+ * ## Space
25
+ *
26
+ * Use this to create **space either side of the divider**. For simple cases, pass a number referring to
27
+ * a position on the theme's spacing scale; for example, this will put a Spacer of the smallest supported
28
+ * size either side of the divider:
29
+ *
30
+ * ```jsx
31
+ * <Divider space={1} />
32
+ * ```
33
+ *
34
+ * `space` prop uses `useSpacingScale` and may accept a {@link SpacingObject} or a {@link SpacingIndex} number.
35
+ *
36
+ * To **reduce the length of a divider** as well as creating space between it and its neighbours, wrap it in
37
+ * a `Box` component. For example, this will have the second-smallest theme-supported spacing value between
38
+ * the dividing line and its neighbours, and will shorten the line at either end by the same amount:
39
+ *
40
+ * ```jsx
41
+ * <Box space={2}><Divider /></Box>
42
+ * ```
43
+ *
19
44
  * ## Accessibility
20
45
  *
21
46
  * For accessibility purposes a divider component will be described with ARIA attributes, i.e. `role="separator"` and `aria-orientation="vertical/horizontal"`.
22
47
  */
23
- const Divider = ({ variant, vertical = false, tokens, testID }) => {
48
+ const Divider = ({ variant, vertical = false, space, tokens, testID }) => {
24
49
  const { color, width } = useThemeTokens('Divider', tokens, variant)
25
50
 
26
51
  const borderStyles = vertical
@@ -43,11 +68,21 @@ const Divider = ({ variant, vertical = false, tokens, testID }) => {
43
68
  : // There are no such equivalent attributes for native
44
69
  {}
45
70
 
46
- return <View style={[styles.divider, borderStyles]} testID={testID} {...a11y} />
71
+ const divider = <View style={[styles.divider, borderStyles]} testID={testID} {...a11y} />
72
+ if (!space) return divider
73
+ const spacerProps = { space, direction: vertical ? 'row' : 'column' }
74
+ return (
75
+ <>
76
+ <Spacer {...spacerProps} testID={testID ? `${testID}-Spacer-before` : undefined} />
77
+ {divider}
78
+ <Spacer {...spacerProps} testID={testID ? `${testID}-Spacer-after` : undefined} />
79
+ </>
80
+ )
47
81
  }
48
82
 
49
83
  Divider.propTypes = {
50
84
  tokens: getTokensPropType('Divider'),
85
+ space: spacingProps.types.spacingValue,
51
86
  variant: variantProp.propType,
52
87
  /**
53
88
  * By default, the divider is a horizontal line the full width of its parent.
@@ -0,0 +1,108 @@
1
+ import React from 'react'
2
+ import { StyleSheet, Text, View } from 'react-native'
3
+ import PropTypes from 'prop-types'
4
+
5
+ import { applyTextStyles, useThemeTokens } from '../ThemeProvider'
6
+ import { a11yProps, getTokensPropType, selectTokens, variantProp } from '../utils'
7
+ import StackView from '../StackView'
8
+
9
+ const selectStyles = (tokens) => selectTokens('Feedback', tokens)
10
+
11
+ const selectTitleTextStyles = ({ titleFontSize, ...tokens }) =>
12
+ applyTextStyles(selectTokens('Typography', { ...tokens, fontSize: titleFontSize }))
13
+ const selectContentTextStyles = ({ contentFontSize, ...tokens }) =>
14
+ applyTextStyles(selectTokens('Typography', { ...tokens, fontSize: contentFontSize }))
15
+
16
+ const selectIconTokens = ({ iconSize, iconColor }) => ({
17
+ size: iconSize,
18
+ color: iconColor
19
+ })
20
+
21
+ const selectIconContainerStyles = ({ iconGap }) => ({
22
+ paddingRight: iconGap
23
+ })
24
+
25
+ /**
26
+ * A feedback box commonly used with form fields.
27
+ *
28
+ * ### Standalone usage
29
+ * While its primary use is to facilitate feedback states for other form components such as `TextInput`,
30
+ * you may use it standalone.
31
+ *
32
+ * ### Complex content
33
+ * You may pass any React tree as the children of this component, bear in mind that a render function
34
+ * is better suited for styling children based on Feedback's variant.
35
+ *
36
+ * ### Using a render function
37
+ * When a function is passed for rendering content, it will receive the feedback text styles and
38
+ * variant as arguments.
39
+ *
40
+ * ### Accessibility
41
+ * All accessibility props set on this component will be applied to the outer container.
42
+ */
43
+ const Feedback = ({ title, children, id, validation, tokens, variant, ...rest }) => {
44
+ const themeTokens = useThemeTokens('Feedback', tokens, { ...variant, validation })
45
+ const { space } = themeTokens
46
+
47
+ const { icon: IconComponent } = themeTokens
48
+
49
+ const titleTextStyles = selectTitleTextStyles(themeTokens)
50
+ const contentTextStyles = selectContentTextStyles(themeTokens)
51
+
52
+ const content =
53
+ typeof children === 'string' ? <Text style={contentTextStyles}>{children}</Text> : children
54
+
55
+ const accessibilityProps = a11yProps.select({
56
+ accessibilityRole: validation === 'error' ? 'alert' : undefined,
57
+ ...rest
58
+ })
59
+
60
+ return (
61
+ <View style={selectStyles(themeTokens)} {...accessibilityProps} nativeID={id}>
62
+ <StackView space={space}>
63
+ {title !== undefined && (
64
+ <View style={staticStyles.title}>
65
+ {IconComponent && (
66
+ <View style={selectIconContainerStyles(themeTokens)}>
67
+ <IconComponent tokens={selectIconTokens(themeTokens)} />
68
+ </View>
69
+ )}
70
+ <Text style={titleTextStyles}>{title}</Text>
71
+ </View>
72
+ )}
73
+ {content && typeof content === 'function'
74
+ ? content({ textStyles: contentTextStyles, variant })
75
+ : content}
76
+ </StackView>
77
+ </View>
78
+ )
79
+ }
80
+
81
+ Feedback.propTypes = {
82
+ /**
83
+ * Emphasized summary of the feedback. If an icon is set, it is rendered next to the title.
84
+ */
85
+ title: PropTypes.string,
86
+ /**
87
+ * Feedback content rendered below the title. A render function `({textStyles, variant}) => {}` is supported.
88
+ */
89
+ children: PropTypes.oneOfType([PropTypes.string, PropTypes.node, PropTypes.func]),
90
+ /**
91
+ * Use to visually mark the Feedback as valid or invalid.
92
+ */
93
+ validation: PropTypes.oneOf(['error', 'success']),
94
+ /** @ignore */
95
+ id: PropTypes.string,
96
+ tokens: getTokensPropType('Feedback'),
97
+ variant: variantProp.propType
98
+ }
99
+
100
+ export default Feedback
101
+
102
+ const staticStyles = StyleSheet.create({
103
+ title: {
104
+ display: 'flex',
105
+ flexDirection: 'row',
106
+ alignItems: 'center'
107
+ }
108
+ })
@@ -0,0 +1,3 @@
1
+ import Feedback from './Feedback'
2
+
3
+ export default Feedback
package/src/Icon/Icon.jsx CHANGED
@@ -3,23 +3,28 @@ import { Platform, View } from 'react-native'
3
3
  import PropTypes from 'prop-types'
4
4
 
5
5
  import { useThemeTokens } from '../ThemeProvider'
6
- import { getTokensPropType, variantProp } from '../utils/propTypes'
6
+ import { getTokensPropType, scaleWithText, variantProp } from '../utils'
7
7
 
8
- const Icon = ({ IconSvg, variant, label, titleId, tokens = {} }) => {
8
+ const Icon = ({ IconSvg, variant, label, titleId, tokens, scalesWithText = false }) => {
9
9
  const themeTokens = useThemeTokens('Icon', tokens, variant)
10
+
11
+ const size = scalesWithText ? scaleWithText(themeTokens.size) : themeTokens.size
12
+
10
13
  const iconContent = (
11
- <IconSvg title={label} titleId={titleId} size={themeTokens.size} color={themeTokens.color} />
14
+ <IconSvg title={label} titleId={titleId} size={size} color={themeTokens.color} />
12
15
  )
13
16
 
14
17
  return Platform.OS === 'web' ? (
15
18
  <View
16
19
  // eslint-disable-next-line react-native/no-inline-styles
17
20
  style={{
18
- // TODO: https://github.com/telus/universal-design-system/issues/487
19
- transition: 'transform 200ms',
21
+ // TODO: systematise animations.
22
+ // https://github.com/telus/universal-design-system/issues/487
23
+ transition: 'transform 200ms, color 200ms',
20
24
  transform: [
21
25
  themeTokens.scale ? `scale(${themeTokens.scale})` : '',
22
- themeTokens.translateX ? `translateX(${themeTokens.translateX}px)` : ''
26
+ themeTokens.translateX ? `translateX(${themeTokens.translateX}px)` : '',
27
+ themeTokens.translateY ? `translateY(${themeTokens.translateY}px)` : ''
23
28
  ]
24
29
  .filter((exists) => exists)
25
30
  .join(' ')
@@ -0,0 +1,63 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { Platform, View } from 'react-native'
4
+
5
+ import { iconComponentPropTypes } from './Icon'
6
+ import { getStackedContent } from '../StackView'
7
+ import { spacingProps } from '../utils'
8
+
9
+ /**
10
+ * Returns an icon and some text with a sized gap between them. This is a utility component
11
+ * intended for use when creating components, not intended for use in applications on its own.
12
+ *
13
+ * IconText does not wrap its children, so should be used either:
14
+ * - inline within a <Text> element
15
+ * - within a container with `flexDirection: 'row'`
16
+ */
17
+ const IconText = ({ space, iconPosition = 'left', icon: IconComponent, iconProps, children }) => {
18
+ const iconContent = IconComponent && <IconComponent scalesWithText {...iconProps} />
19
+
20
+ // Inline images on Android are always baseline-aligned which makes them look misaligned - offset it.
21
+ // See abandoned issue https://github.com/facebook/react-native/issues/6529
22
+ const size = iconProps?.tokens?.size
23
+ const iconAdjusted =
24
+ Platform.OS === 'android' && iconContent && size ? (
25
+ <View style={{ transform: [{ translateY: size * 0.2 }] }}>{iconContent}</View>
26
+ ) : (
27
+ iconContent
28
+ )
29
+
30
+ return getStackedContent(
31
+ iconPosition === 'left' ? [iconAdjusted, children] : [children, iconAdjusted],
32
+ { space, direction: 'row' }
33
+ )
34
+ }
35
+
36
+ IconText.propTypes = {
37
+ /**
38
+ * Amount of space to separate the text content and icon. Uses the themes's spacing scale
39
+ * (see useSpacingScale for more info).
40
+ */
41
+ space: spacingProps.types.spacingValue,
42
+ /**
43
+ * Whether the icon should be to the left of the content or the right of the content.
44
+ */
45
+ iconPosition: PropTypes.oneOf(['left', 'right']),
46
+ /**
47
+ * A valid UDS icon component imported from a UDS palette.
48
+ */
49
+ icon: PropTypes.func,
50
+ /**
51
+ * Props that will be passed to the icon component. By default the icon's `scalesWithText`
52
+ * prop will be set as "true" so that the icon continues to match the size of the text
53
+ * if users use accessibility settings to increase text size.
54
+ */
55
+ iconProps: PropTypes.exact(iconComponentPropTypes),
56
+ /**
57
+ * Content to be rendered alongside the Icon component. Usually this should be a
58
+ * `<Typography>` component, or a component that renders `<Text>`.
59
+ */
60
+ children: PropTypes.node
61
+ }
62
+
63
+ export default IconText
package/src/Icon/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import Icon, { iconComponentPropTypes, iconSvgPropTypes } from './Icon'
2
+ import IconText from './IconText'
2
3
 
3
4
  export default Icon
4
- export { iconComponentPropTypes, iconSvgPropTypes }
5
+ export { iconComponentPropTypes, iconSvgPropTypes, IconText }