@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
@@ -1,14 +1,6 @@
1
- import React, { useState } from 'react'
1
+ import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import {
4
- Text,
5
- Platform,
6
- StyleSheet,
7
- Pressable,
8
- PixelRatio,
9
- View,
10
- TouchableWithoutFeedback
11
- } from 'react-native'
3
+ import { Text, Platform, StyleSheet } from 'react-native'
12
4
  import {
13
5
  a11yProps,
14
6
  hrefAttrsProp,
@@ -16,12 +8,11 @@ import {
16
8
  linkProps,
17
9
  getTokensPropType
18
10
  } from '../utils/propTypes'
11
+ import { resolvePressableTokens } from '../utils/pressability'
19
12
 
20
- import { useThemeTokens, applyTextStyles } from '../ThemeProvider'
21
-
22
- const selectContentStyles = ({ color }) => ({
23
- color
24
- })
13
+ import InlinePressable from './InlinePressable'
14
+ import { applyTextStyles } from '../ThemeProvider'
15
+ import { IconText, iconComponentPropTypes } from '../Icon'
25
16
 
26
17
  const selectOuterBorderStyles = ({
27
18
  outerBorderColor,
@@ -40,13 +31,23 @@ const selectOuterBorderStyles = ({
40
31
  outline: outerBorderOutline,
41
32
  borderWidth: outerBorderWidth,
42
33
  borderColor: outerBorderColor,
43
- borderRadius: outerBorderRadius
34
+ borderRadius: outerBorderRadius,
35
+ // Stops focus ring stretching horizontally if parent has display: block
36
+ // width: fit-content isn't supported on Firefox; can't cascade props like CSS `width: fit-content; width: --moz-fit-content;`
37
+ display: 'inline-flex'
44
38
  }
45
39
  : {}
46
40
 
47
- const selectTextStyles = ({ textLine, textLineStyle }) => ({
41
+ const selectTextStyles = ({ color, textLine, textLineStyle }) => ({
42
+ color,
48
43
  textDecorationLine: textLine,
49
- textDecorationStyle: textLineStyle
44
+ textDecorationStyle: textLineStyle,
45
+ ...Platform.select({
46
+ web: {
47
+ // TODO: https://github.com/telus/universal-design-system/issues/487
48
+ transition: 'color 200ms'
49
+ }
50
+ })
50
51
  })
51
52
 
52
53
  const selectBlockStyles = ({ blockFontWeight, blockFontSize, blockLineHeight, blockFontName }) =>
@@ -57,18 +58,11 @@ const selectBlockStyles = ({ blockFontWeight, blockFontSize, blockLineHeight, bl
57
58
  fontName: blockFontName
58
59
  })
59
60
 
60
- const selectIconStyles = ({
61
- iconSize,
62
- iconGapBefore,
63
- iconGapAfter,
64
- iconScale,
65
- iconTranslateX
66
- }) => ({
67
- scale: iconScale,
61
+ const selectIconTokens = ({ color, iconSize, iconTranslateX, iconTranslateY }) => ({
62
+ color,
68
63
  translateX: iconTranslateX,
69
- size: iconSize,
70
- gapBefore: iconGapBefore,
71
- gapAfter: iconGapAfter
64
+ translateY: iconTranslateY,
65
+ size: iconSize
72
66
  })
73
67
 
74
68
  /**
@@ -98,135 +92,61 @@ const LinkBase = ({
98
92
  onPress,
99
93
  icon,
100
94
  iconPosition = icon ? 'left' : undefined,
101
- iconVariant,
95
+ iconProps,
102
96
  variant,
103
- tokens,
97
+ tokens = {},
104
98
  children,
105
99
  accessibilityRole = 'link',
106
100
  ...props
107
101
  }) => {
108
- const handlePress = linkProps.handleHref({ href, onPress })
109
-
110
- const [isFocused, setIsFocused] = useState(false)
111
- const handleFocus = () => setIsFocused(true)
112
- const handleBlur = () => setIsFocused(false)
113
-
114
- const [isHovered, setIsHovered] = useState(false)
115
- const handleHoverIn = () => setIsHovered(true)
116
- const handleHoverOut = () => setIsHovered(false)
117
-
118
- const [isPressed, setIsPressed] = useState(false)
119
- const handlePressIn = () => setIsPressed(true)
120
- const handlePressOut = () => setIsPressed(false)
121
-
122
102
  const { hrefAttrs, rest } = hrefAttrsProp.bundle(props)
123
103
  const linkPropSet = linkProps.select({
124
104
  accessibilityRole,
125
105
  href,
126
- onPress: handlePress,
127
- onPressIn: handlePressIn,
128
- onPressOut: handlePressOut,
129
- onFocus: handleFocus,
130
- onBlur: handleBlur,
131
- onMouseEnter: handleHoverIn,
132
- onMouseLeave: handleHoverOut,
106
+ onPress: linkProps.handleHref({ href, onPress }),
133
107
  ...hrefAttrsProp.spread(hrefAttrs),
134
108
  ...rest
135
109
  })
136
110
 
137
- const themeTokens = useThemeTokens('Link', tokens, variant, {
138
- focus: isFocused,
139
- hover: isHovered,
140
- pressed: isPressed,
141
- iconPosition
142
- })
143
- const IconComponent = icon || themeTokens.icon
144
- const hasIcon = !!IconComponent
145
-
146
- const textStyles = selectTextStyles(themeTokens)
147
- const outerBorderStyles = selectOuterBorderStyles(themeTokens)
148
- const contentStyles = selectContentStyles(themeTokens)
149
- const blockStyles = selectBlockStyles(themeTokens)
150
- const iconStyles = selectIconStyles(themeTokens)
151
-
152
- // TODO: re-apply support for inline links
153
- const isNested = false
111
+ const resolveTokens = (pressState) => resolvePressableTokens(tokens, pressState, { iconPosition })
154
112
 
155
113
  // On web, this makes focus rings wrap only the link, not the entire block
156
114
  const blockLeftStyle = Platform.OS === 'web' && staticStyles.blockLeft
157
115
 
158
- // Apply typographic text styles if not inheriting, and block positioning if a block
159
- const blockTextStyles = !isNested && blockStyles
160
-
161
- if (!hasIcon) {
162
- // onPressIn / onPressOut only work if on TouchableWithoutFeedback, onFocus only works if on Text
163
- const { onPress: _onPress, onPressIn, onPressOut, ...textProps } = linkPropSet
164
- return (
165
- // TouchableWithoutFeedback modifies its child so this stack is all (touchable) Text: no Views
166
- <TouchableWithoutFeedback onPressIn={onPressIn} onPressOut={onPressOut} onPress={_onPress}>
167
- <Text
168
- {...textProps}
169
- style={[outerBorderStyles, blockLeftStyle, contentStyles, blockTextStyles, textStyles]}
170
- >
171
- {children}
172
- </Text>
173
- </TouchableWithoutFeedback>
174
- )
175
- }
176
-
177
- // Scale icon with text, but with a cap so text isn't squashed at large scales
178
- const iconScale = Math.min(PixelRatio.getFontScale(), 2)
179
-
180
- const iconTokens = {
181
- size: iconStyles.size ? iconStyles.size * iconScale : undefined,
182
- color: contentStyles.color ?? undefined,
183
- scale: iconStyles.scale ?? undefined,
184
- translateX: iconStyles.translateX ?? undefined
185
- }
186
-
187
- const iconContent = <IconComponent tokens={iconTokens} variant={iconVariant} />
188
-
189
- const iconBlock = isNested ? (
190
- // TODO: when reimplementing inline support, insert an appropriate space character
191
- // based on the value of gapBefore and gapAfter on native (no inline margins/padding)
192
- iconContent
193
- ) : (
194
- <View
195
- style={[
196
- iconPosition === 'left' && { marginRight: iconStyles.gapAfter * iconScale },
197
- iconPosition === 'right' && { marginLeft: iconStyles.gapBefore * iconScale }
198
- ]}
199
- >
200
- {iconContent}
201
- </View>
202
- )
203
-
204
- // TODO: this doesn't work well when nested inline but does allow block-links to have icons align
205
- // properly on Android which purely inline links can't do. Add an isNested case that is similar to
206
- // https://github.com/telus/universal-design-system/pull/233
207
116
  return (
208
- <Pressable
117
+ <InlinePressable
209
118
  {...linkPropSet}
210
- style={[outerBorderStyles, blockLeftStyle, staticStyles.rowContainer]}
119
+ style={(linkState) => {
120
+ const themeTokens = resolveTokens(linkState)
121
+ const outerBorderStyles = selectOuterBorderStyles(themeTokens)
122
+ const hasIcon = Boolean(icon || themeTokens.icon)
123
+ return [outerBorderStyles, blockLeftStyle, hasIcon && staticStyles.rowContainer]
124
+ }}
211
125
  >
212
- {iconPosition === 'left' && iconBlock}
213
- <Text
214
- style={[
215
- contentStyles,
216
- blockTextStyles,
217
- textStyles,
218
- Platform.select({
219
- web: {
220
- // TODO: https://github.com/telus/universal-design-system/issues/487
221
- transition: 'color 200ms'
222
- }
223
- })
224
- ]}
225
- >
226
- {children}
227
- </Text>
228
- {iconPosition === 'right' && iconBlock}
229
- </Pressable>
126
+ {(linkState) => {
127
+ const themeTokens = resolveTokens(linkState)
128
+ const textStyles = selectTextStyles(themeTokens)
129
+ const iconTokens = selectIconTokens(themeTokens)
130
+
131
+ // TODO: may need to apply some smarter text inheritance here if inline to avoid native
132
+ // issues like double-application of line heights breaking align-items: baseline
133
+ const blockTextStyles = selectBlockStyles(themeTokens)
134
+
135
+ const IconComponent = icon || themeTokens.icon
136
+ const { iconSpace } = themeTokens
137
+
138
+ return (
139
+ <IconText
140
+ icon={IconComponent}
141
+ iconPosition={iconPosition}
142
+ space={iconSpace}
143
+ iconProps={{ ...iconProps, tokens: iconTokens }}
144
+ >
145
+ <Text style={[textStyles, blockTextStyles, staticStyles.baseline]}>{children}</Text>
146
+ </IconText>
147
+ )
148
+ }}
149
+ </InlinePressable>
230
150
  )
231
151
  }
232
152
 
@@ -238,7 +158,7 @@ LinkBase.propTypes = {
238
158
  /**
239
159
  * Optional variant that may be passed down to the link's icon if there is one
240
160
  */
241
- iconVariant: variantProp.propType,
161
+ iconProps: PropTypes.exact(iconComponentPropTypes),
242
162
  /**
243
163
  * A function component for an SVG icon to render inside the link. Inherits size and color from
244
164
  * the link and any Typography the link is nested inside.
@@ -263,6 +183,9 @@ const staticStyles = StyleSheet.create({
263
183
  flexDirection: 'row',
264
184
  alignItems: 'center',
265
185
  justifyContent: 'flex-start'
186
+ },
187
+ baseline: {
188
+ alignSelf: 'baseline'
266
189
  }
267
190
  })
268
191
 
@@ -1,24 +1,36 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
 
4
+ import { useThemeTokensCallback } from '../ThemeProvider'
4
5
  import LinkBase from './LinkBase'
5
6
 
7
+ /**
8
+ * `TextButton` is a button that looks like a Link. It uses the same theming and variants as
9
+ * Link but has the accessibility role of a `Button`. It should be used for actions that
10
+ * take place on the current page, or for navigation within an app.
11
+ */
6
12
  const TextButton = ({
7
13
  onPress,
8
14
  children,
9
- variant = {},
15
+ variant,
16
+ tokens,
17
+ // TODO: this may need to use `link` role on Web in the case of being passed both `href` and
18
+ // `onPress` in an omniplatform app that uses React Navigation's useLinkProps for internal nav.
10
19
  accessibilityRole = 'button',
11
20
  ...linkProps
12
- }) => (
13
- <LinkBase
14
- onPress={onPress}
15
- accessibilityRole={accessibilityRole}
16
- variant={{ component: 'TextButton', ...variant }}
17
- {...linkProps}
18
- >
19
- {children}
20
- </LinkBase>
21
- )
21
+ }) => {
22
+ const getTokens = useThemeTokensCallback('Link', tokens, variant)
23
+ return (
24
+ <LinkBase
25
+ onPress={onPress}
26
+ accessibilityRole={accessibilityRole}
27
+ tokens={getTokens}
28
+ {...linkProps}
29
+ >
30
+ {children}
31
+ </LinkBase>
32
+ )
33
+ }
22
34
 
23
35
  TextButton.propTypes = {
24
36
  ...LinkBase.propTypes,
@@ -26,6 +38,8 @@ TextButton.propTypes = {
26
38
  }
27
39
 
28
40
  // Remove incompatible Link prop (if this build includes propTypes)
41
+ // TODO: test if this works with web navigation in omniplatform React Navigation
42
+ // https://github.com/telus/universal-design-system/issues/665
29
43
  // eslint-disable-next-line react/forbid-foreign-prop-types
30
44
  if (TextButton.propTypes?.href) delete TextButton.propTypes.href
31
45
 
package/src/Link/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import ChevronLink from './ChevronLink'
2
2
  import Link from './Link'
3
+ import LinkBase from './LinkBase'
3
4
  import TextButton from './TextButton'
4
5
 
5
- export { ChevronLink, Link, TextButton }
6
+ export { ChevronLink, Link, LinkBase, TextButton }
@@ -0,0 +1,47 @@
1
+ import React, { cloneElement, Children } from 'react'
2
+ import { View, Platform } from 'react-native'
3
+ import PropTypes from 'prop-types'
4
+ import { getTokensPropType, variantProp, componentPropType } from '../utils'
5
+ import { a11yProps } from '../utils/propTypes'
6
+ import ListItem from './ListItem'
7
+
8
+ /**
9
+ * A Unordered List component has a child a ListItem that
10
+ * allows icon, dividers and customized typography
11
+ */
12
+ const List = ({ children, showDivider, tokens, variant, ...rest }) => {
13
+ const accessibilityRole = Platform.select({ web: 'list', default: 'none' })
14
+ const a11y = a11yProps.select(rest)
15
+ const items = Children.map(children, (child, index) => {
16
+ if (child.type.name === ListItem.name) {
17
+ return cloneElement(child, {
18
+ showDivider,
19
+ isLastItem: index + 1 === Children.count(children),
20
+ tokens,
21
+ variant
22
+ })
23
+ }
24
+ return child
25
+ })
26
+
27
+ return (
28
+ <View accessibilityRole={accessibilityRole} {...a11y}>
29
+ {items}
30
+ </View>
31
+ )
32
+ }
33
+
34
+ List.Item = ListItem
35
+
36
+ List.propTypes = {
37
+ ...a11yProps.types,
38
+ tokens: getTokensPropType('List'),
39
+ variant: variantProp.propType,
40
+ children: componentPropType('ListItem'),
41
+ /**
42
+ * In case it is not the last item allow display divider
43
+ */
44
+ showDivider: PropTypes.bool
45
+ }
46
+
47
+ export default List
@@ -0,0 +1,187 @@
1
+ import React from 'react'
2
+ import { View, Platform, Text, StyleSheet } from 'react-native'
3
+ import PropTypes from 'prop-types'
4
+ import { useThemeTokens, applyTextStyles } from '../ThemeProvider'
5
+ import { getTokensPropType, variantProp } from '../utils'
6
+
7
+ const selectBulletStyles = ({ itemBulletWidth, itemBulletHeight, itemBulletColor }) => ({
8
+ width: itemBulletWidth,
9
+ height: itemBulletHeight,
10
+ borderRadius: itemBulletHeight / 2,
11
+ backgroundColor: itemBulletColor
12
+ })
13
+
14
+ const selectBulletContainerStyles = ({ itemBulletContainerWidth, itemBulletContainerAlign }) => ({
15
+ width: itemBulletContainerWidth,
16
+ alignItems: itemBulletContainerAlign,
17
+ justifyContent: itemBulletContainerAlign
18
+ })
19
+
20
+ const selectItemIconTokens = ({ itemIconSize, itemIconColor }) => ({
21
+ size: itemIconSize,
22
+ color: itemIconColor
23
+ })
24
+
25
+ const selectSideItemContainerStyles = ({ listGutter }) => ({
26
+ marginRight: listGutter
27
+ })
28
+
29
+ const selectItemStyles = ({ itemFontWeight, itemFontSize, itemLineHeight, itemFontName }) =>
30
+ applyTextStyles({
31
+ fontWeight: itemFontWeight,
32
+ fontSize: itemFontSize,
33
+ lineHeight: itemLineHeight,
34
+ fontName: itemFontName
35
+ })
36
+
37
+ const selectItemBlockStyles = ({ interItemMargin }) => ({
38
+ marginBottom: interItemMargin
39
+ })
40
+
41
+ const selectDividerStyles = ({ dividerColor, dividerSize, interItemMarginWithDivider }) => ({
42
+ borderBottomWidth: dividerSize,
43
+ borderColor: dividerColor,
44
+ marginBottom: interItemMarginWithDivider,
45
+ paddingBottom: interItemMarginWithDivider
46
+ })
47
+
48
+ /**
49
+ * ListItem is responsible for rendering icon or a bullet as side item
50
+ */
51
+ const ListItem = ({
52
+ tokens,
53
+ variant,
54
+ icon,
55
+ iconColor,
56
+ iconSize,
57
+ showDivider,
58
+ children,
59
+ isLastItem
60
+ }) => {
61
+ const themeTokens = useThemeTokens('List', tokens, variant)
62
+
63
+ const itemStyles = selectItemStyles(themeTokens)
64
+ const itemBlockStyles = selectItemBlockStyles(themeTokens)
65
+ const dividerStyles = selectDividerStyles(themeTokens)
66
+ const itemBulletContainerStyles = selectBulletContainerStyles(themeTokens)
67
+ const itemBulletStyles = selectBulletStyles(themeTokens)
68
+ const iconTokens = selectItemIconTokens(themeTokens)
69
+ const sideItemContainerStyles = selectSideItemContainerStyles(themeTokens)
70
+ const accessibilityRole = Platform.select({ web: 'listitem', default: 'item' })
71
+
72
+ const areChildrenStrings = () => {
73
+ if (Array.isArray(children)) {
74
+ return children.every((child) => typeof child === 'string')
75
+ }
76
+
77
+ return typeof children === 'string'
78
+ }
79
+
80
+ const renderItem = () => {
81
+ if (areChildrenStrings()) {
82
+ return <Text style={itemStyles}>{children}</Text>
83
+ }
84
+
85
+ return <View>{children}</View>
86
+ }
87
+
88
+ /**
89
+ * Function responsible returning styling, in case the item is the last shouldn't
90
+ * add extra margin on the bottom, if "showDivider" is true it should add a divider
91
+ * and custom margin and padding, otherwise just adds a margin to the bottom
92
+ */
93
+ const getContainerStyle = () => {
94
+ if (isLastItem) {
95
+ return undefined
96
+ }
97
+
98
+ if (showDivider) {
99
+ return dividerStyles
100
+ }
101
+
102
+ return itemBlockStyles
103
+ }
104
+
105
+ /**
106
+ * Renders item bullet or Icon in case it's defined
107
+ * in case children are string the icon is centered otherwise
108
+ * it will align itself at start of the container
109
+ */
110
+ const renderMarker = () => {
111
+ const IconComponent = icon || <></>
112
+
113
+ if (icon) {
114
+ return (
115
+ <View
116
+ style={[
117
+ sideItemContainerStyles,
118
+ areChildrenStrings() ? staticStyles.centeredIcons : undefined
119
+ ]}
120
+ >
121
+ <IconComponent
122
+ tokens={{
123
+ ...iconTokens,
124
+ size: iconSize || iconTokens.size,
125
+ color: iconColor || iconTokens.color
126
+ }}
127
+ />
128
+ </View>
129
+ )
130
+ }
131
+
132
+ return (
133
+ <View style={[sideItemContainerStyles, itemBulletContainerStyles]}>
134
+ <View style={itemBulletStyles} testID="unordered-item-bullet" />
135
+ </View>
136
+ )
137
+ }
138
+
139
+ return (
140
+ <View
141
+ style={[staticStyles.itemContainer, getContainerStyle()]}
142
+ accessibilityRole={accessibilityRole}
143
+ >
144
+ {renderMarker()}
145
+ {renderItem()}
146
+ </View>
147
+ )
148
+ }
149
+
150
+ const staticStyles = StyleSheet.create({
151
+ itemContainer: {
152
+ flexDirection: 'row'
153
+ },
154
+ centeredIcons: {
155
+ justifyContent: 'center'
156
+ }
157
+ })
158
+
159
+ ListItem.propTypes = {
160
+ tokens: getTokensPropType('List'),
161
+ variant: variantProp.propType,
162
+ children: PropTypes.node.isRequired,
163
+ /**
164
+ * Renders side item icon
165
+ */
166
+ icon: PropTypes.func,
167
+ /**
168
+ * Will set display icon color
169
+ */
170
+ iconColor: PropTypes.string,
171
+ /**
172
+ * Allow the user define the icon size if not defined the theme's file
173
+ */
174
+ iconSize: PropTypes.number,
175
+ /**
176
+ * @ignore
177
+ * Defined by parent if it's last item on the list
178
+ */
179
+ isLastItem: PropTypes.bool,
180
+ /**
181
+ * @ignore
182
+ * In case it is not the last item allow display divider
183
+ */
184
+ showDivider: PropTypes.bool
185
+ }
186
+
187
+ export default ListItem
@@ -0,0 +1,3 @@
1
+ import List from './List'
2
+
3
+ export default List
@@ -1,9 +1,8 @@
1
1
  import React from 'react'
2
- import { Text } from 'react-native'
3
2
  import PropTypes from 'prop-types'
4
3
 
5
4
  import ButtonBase from '../Button/ButtonBase'
6
- import { applyTextStyles, useThemeTokensCallback } from '../ThemeProvider'
5
+ import { useThemeTokensCallback } from '../ThemeProvider'
7
6
  import {
8
7
  a11yProps,
9
8
  copyPropTypes,
@@ -14,26 +13,15 @@ import {
14
13
  variantProp
15
14
  } from '../utils'
16
15
 
17
- import useCopy from './useCopy'
16
+ import useCopy from '../utils/useCopy'
18
17
  import dictionary from './dictionary'
19
18
 
20
- const selectTextStyles = ({ color, fontName, fontSize, fontWeight, lineHeight, textLine }) =>
21
- applyTextStyles({
22
- color,
23
- fontName,
24
- fontSize,
25
- fontWeight,
26
- lineHeight,
27
- textDecorationLine: textLine
28
- })
29
-
30
19
  function PageButton({ label, onPress, href, isActive, copy, variant, tokens, ...props }) {
31
20
  const getTokens = useThemeTokensCallback('PaginationPageButton', tokens, variant)
32
21
 
33
22
  const getCopy = useCopy({ dictionary, copy })
34
23
 
35
24
  const getButtonTokens = (buttonState) => selectTokens('Button', getTokens(buttonState))
36
- const getTextStyles = (buttonState) => selectTextStyles(getTokens(buttonState))
37
25
 
38
26
  const activeProps = isActive
39
27
  ? {
@@ -61,9 +49,7 @@ function PageButton({ label, onPress, href, isActive, copy, variant, tokens, ...
61
49
 
62
50
  return (
63
51
  <ButtonBase {...buttonProps} tokens={getButtonTokens} {...activeProps}>
64
- {(buttonState) => {
65
- return <Text style={getTextStyles(buttonState)}>{label}</Text>
66
- }}
52
+ {label}
67
53
  </ButtonBase>
68
54
  )
69
55
  }