@telus-uds/components-base 0.0.2-prerelease.1 → 0.0.2-prerelease.5

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 (161) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/__fixtures__/testTheme.js +264 -84
  3. package/__tests__/Box/Box.test.jsx +81 -58
  4. package/__tests__/Card/Card.test.jsx +63 -0
  5. package/__tests__/Divider/Divider.test.jsx +26 -5
  6. package/__tests__/Feedback/Feedback.test.jsx +42 -0
  7. package/__tests__/FlexGrid/Col.test.jsx +5 -0
  8. package/__tests__/Pagination/Pagination.test.jsx +160 -0
  9. package/__tests__/Spacer/Spacer.test.jsx +63 -0
  10. package/__tests__/StackView/StackView.test.jsx +242 -0
  11. package/__tests__/StackView/StackWrap.test.jsx +47 -0
  12. package/__tests__/StackView/getStackedContent.test.jsx +295 -0
  13. package/__tests__/TextInput/TextInput.test.jsx +146 -0
  14. package/__tests__/ThemeProvider/useThemeTokens.test.jsx +5 -3
  15. package/__tests__/utils/spacing.test.jsx +273 -0
  16. package/__tests__/utils/useUniqueId.test.js +31 -0
  17. package/babel.config.json +8 -0
  18. package/jest.config.js +7 -6
  19. package/lib/A11yInfoProvider/index.js +2 -2
  20. package/lib/A11yText/index.js +1 -3
  21. package/lib/ActivityIndicator/Spinner.web.js +3 -5
  22. package/lib/Box/Box.js +117 -82
  23. package/lib/Button/Button.js +1 -3
  24. package/lib/Button/ButtonBase.js +9 -21
  25. package/lib/Button/ButtonGroup.js +14 -25
  26. package/lib/Button/ButtonLink.js +1 -3
  27. package/lib/Card/Card.js +103 -0
  28. package/lib/Card/index.js +2 -0
  29. package/lib/Divider/Divider.js +40 -4
  30. package/lib/ExpandCollapse/Accordion.js +1 -3
  31. package/lib/ExpandCollapse/Control.js +3 -5
  32. package/lib/ExpandCollapse/Panel.js +2 -4
  33. package/lib/Feedback/Feedback.js +110 -0
  34. package/lib/Feedback/index.js +2 -0
  35. package/lib/FlexGrid/Col/Col.js +3 -5
  36. package/lib/FlexGrid/FlexGrid.js +1 -3
  37. package/lib/FlexGrid/Row/Row.js +1 -3
  38. package/lib/FlexGrid/providers/GutterContext.js +1 -1
  39. package/lib/Icon/Icon.js +1 -1
  40. package/lib/InputLabel/InputLabel.js +86 -0
  41. package/lib/InputLabel/LabelContent.native.js +8 -0
  42. package/lib/InputLabel/LabelContent.web.js +17 -0
  43. package/lib/InputLabel/index.js +2 -0
  44. package/lib/Link/ChevronLink.js +1 -3
  45. package/lib/Link/Link.js +1 -3
  46. package/lib/Link/LinkBase.js +11 -7
  47. package/lib/Link/TextButton.js +1 -3
  48. package/lib/Pagination/PageButton.js +85 -0
  49. package/lib/Pagination/Pagination.js +118 -0
  50. package/lib/Pagination/SideButton.js +108 -0
  51. package/lib/Pagination/dictionary.js +18 -0
  52. package/lib/Pagination/index.js +2 -0
  53. package/lib/Pagination/useCopy.js +10 -0
  54. package/lib/Pagination/usePagination.js +70 -0
  55. package/lib/SideNav/Item.js +4 -6
  56. package/lib/SideNav/ItemsGroup.js +11 -11
  57. package/lib/SideNav/SideNav.js +2 -4
  58. package/lib/Spacer/Spacer.js +98 -0
  59. package/lib/Spacer/index.js +2 -0
  60. package/lib/StackView/StackView.js +105 -0
  61. package/lib/StackView/StackWrap.js +32 -0
  62. package/lib/StackView/StackWrap.native.js +3 -0
  63. package/lib/StackView/StackWrapBox.js +85 -0
  64. package/lib/StackView/StackWrapGap.js +45 -0
  65. package/lib/StackView/common.js +30 -0
  66. package/lib/StackView/getStackedContent.js +111 -0
  67. package/lib/StackView/index.js +5 -0
  68. package/lib/TextInput/TextInput.js +337 -0
  69. package/lib/TextInput/index.js +2 -0
  70. package/lib/ThemeProvider/ThemeProvider.js +2 -2
  71. package/lib/ThemeProvider/useThemeTokens.js +34 -6
  72. package/lib/ThemeProvider/utils/theme-tokens.js +37 -9
  73. package/lib/ToggleSwitch/ToggleSwitch.js +17 -47
  74. package/lib/Typography/Typography.js +1 -7
  75. package/lib/ViewportProvider/index.js +1 -1
  76. package/lib/index.js +8 -1
  77. package/lib/utils/index.js +2 -1
  78. package/lib/utils/input.js +3 -1
  79. package/lib/utils/propTypes.js +103 -8
  80. package/lib/utils/spacing/index.js +2 -0
  81. package/lib/utils/spacing/useSpacingScale.js +102 -0
  82. package/lib/utils/spacing/utils.js +32 -0
  83. package/lib/utils/useUniqueId.js +12 -0
  84. package/package.json +6 -9
  85. package/release-context.json +4 -4
  86. package/src/Box/Box.jsx +117 -80
  87. package/src/Button/ButtonBase.jsx +8 -21
  88. package/src/Button/ButtonGroup.jsx +13 -17
  89. package/src/Card/Card.jsx +101 -0
  90. package/src/Card/index.js +3 -0
  91. package/src/Divider/Divider.jsx +38 -3
  92. package/src/ExpandCollapse/Control.jsx +2 -3
  93. package/src/Feedback/Feedback.jsx +99 -0
  94. package/src/Feedback/index.js +3 -0
  95. package/src/FlexGrid/Col/Col.jsx +4 -2
  96. package/src/Icon/Icon.jsx +2 -1
  97. package/src/InputLabel/InputLabel.jsx +99 -0
  98. package/src/InputLabel/LabelContent.native.jsx +6 -0
  99. package/src/InputLabel/LabelContent.web.jsx +13 -0
  100. package/src/InputLabel/index.js +3 -0
  101. package/src/Link/LinkBase.jsx +9 -3
  102. package/src/Pagination/PageButton.jsx +80 -0
  103. package/src/Pagination/Pagination.jsx +135 -0
  104. package/src/Pagination/SideButton.jsx +93 -0
  105. package/src/Pagination/dictionary.js +18 -0
  106. package/src/Pagination/index.js +3 -0
  107. package/src/Pagination/useCopy.js +7 -0
  108. package/src/Pagination/usePagination.js +69 -0
  109. package/src/SideNav/Item.jsx +3 -3
  110. package/src/SideNav/ItemsGroup.jsx +11 -13
  111. package/src/Spacer/Spacer.jsx +91 -0
  112. package/src/Spacer/index.js +3 -0
  113. package/src/StackView/StackView.jsx +103 -0
  114. package/src/StackView/StackWrap.jsx +33 -0
  115. package/src/StackView/StackWrap.native.jsx +4 -0
  116. package/src/StackView/StackWrapBox.jsx +82 -0
  117. package/src/StackView/StackWrapGap.jsx +39 -0
  118. package/src/StackView/common.jsx +28 -0
  119. package/src/StackView/getStackedContent.jsx +106 -0
  120. package/src/StackView/index.js +6 -0
  121. package/src/TextInput/TextInput.jsx +325 -0
  122. package/src/TextInput/index.js +3 -0
  123. package/src/ThemeProvider/useThemeTokens.js +34 -7
  124. package/src/ThemeProvider/utils/theme-tokens.js +37 -8
  125. package/src/ToggleSwitch/ToggleSwitch.jsx +23 -43
  126. package/src/Typography/Typography.jsx +0 -4
  127. package/src/index.js +8 -1
  128. package/src/utils/index.js +1 -0
  129. package/src/utils/input.js +2 -1
  130. package/src/utils/propTypes.js +105 -16
  131. package/src/utils/spacing/index.js +3 -0
  132. package/src/utils/spacing/useSpacingScale.js +93 -0
  133. package/src/utils/spacing/utils.js +28 -0
  134. package/src/utils/useUniqueId.js +14 -0
  135. package/stories/A11yText/A11yText.stories.jsx +11 -5
  136. package/stories/ActivityIndicator/ActivityIndicator.stories.jsx +11 -2
  137. package/stories/Box/Box.stories.jsx +46 -17
  138. package/stories/Button/Button.stories.jsx +17 -21
  139. package/stories/Button/ButtonGroup.stories.jsx +2 -1
  140. package/stories/Button/ButtonLink.stories.jsx +6 -4
  141. package/stories/Card/Card.stories.jsx +62 -0
  142. package/stories/Divider/Divider.stories.jsx +26 -2
  143. package/stories/ExpandCollapse/ExpandCollapse.stories.jsx +74 -79
  144. package/stories/Feedback/Feedback.stories.jsx +97 -0
  145. package/stories/FlexGrid/01 FlexGrid.stories.jsx +20 -7
  146. package/stories/Icon/Icon.stories.jsx +11 -3
  147. package/stories/InputLabel/InputLabel.stories.jsx +37 -0
  148. package/stories/Link/ChevronLink.stories.jsx +20 -4
  149. package/stories/Link/Link.stories.jsx +24 -3
  150. package/stories/Link/TextButton.stories.jsx +24 -3
  151. package/stories/Pagination/Pagination.stories.jsx +64 -0
  152. package/stories/SideNav/SideNav.stories.jsx +17 -2
  153. package/stories/Spacer/Spacer.stories.jsx +33 -0
  154. package/stories/StackView/StackView.stories.jsx +65 -0
  155. package/stories/StackView/StackWrap.stories.jsx +52 -0
  156. package/stories/TextInput/TextInput.stories.jsx +103 -0
  157. package/stories/ToggleSwitch/ToggleSwitch.stories.jsx +16 -3
  158. package/stories/Typography/Typography.stories.jsx +12 -3
  159. package/stories/platform-supports.web.jsx +1 -1
  160. package/stories/supports.jsx +113 -13
  161. package/babel.config.js +0 -3
@@ -18,6 +18,7 @@ const getOuterBorderOffset = ({ outerBorderGap = 0, outerBorderWidth = 0 }) =>
18
18
  outerBorderGap + outerBorderWidth
19
19
 
20
20
  const selectOuterContainerStyles = ({
21
+ alignSelf,
21
22
  opacity,
22
23
  outerBorderColor,
23
24
  outerBorderWidth,
@@ -25,6 +26,7 @@ const selectOuterContainerStyles = ({
25
26
  outerBorderRadius = 0,
26
27
  outerBackgroundColor
27
28
  }) => ({
29
+ alignSelf,
28
30
  padding: outerBorderGap,
29
31
  borderWidth: outerBorderWidth,
30
32
  borderColor: outerBorderColor,
@@ -33,25 +35,12 @@ const selectOuterContainerStyles = ({
33
35
  opacity
34
36
  })
35
37
 
36
- const selectOuterWidthStyles = ({
37
- outerBorderGap,
38
- outerBorderWidth,
39
- width,
40
- // TODO: make margin the responsibility of a parent
41
- // https://github.com/telus/universal-design-system/issues/525
42
- marginTop = 0,
43
- marginBottom = 0,
44
- marginLeft = 0,
45
- marginRight = 0
46
- }) => {
38
+ const selectOuterWidthStyles = ({ outerBorderGap, outerBorderWidth, width }) => {
47
39
  // The inner container's bounding box is the bounding box of the button overall
48
40
  // so this many device pixels will sit outside of the overall bounding box
49
41
  const outerBorderOffset = getOuterBorderOffset({ outerBorderGap, outerBorderWidth })
50
42
  const widthStyles = {
51
- marginTop: marginTop - outerBorderOffset,
52
- marginBottom: marginBottom - outerBorderOffset,
53
- marginLeft: marginLeft - outerBorderOffset,
54
- marginRight: marginRight - outerBorderOffset
43
+ margin: 0 - outerBorderOffset
55
44
  }
56
45
 
57
46
  if (!width) {
@@ -59,9 +48,8 @@ const selectOuterWidthStyles = ({
59
48
  ...widthStyles,
60
49
  // Wrap content, stopping a flex parent's default align-items: stretch stretching focus ring beyond content
61
50
  ...Platform.select({
62
- web: { width: 'fit-content' },
63
- // No fit-content or inline-block in RN. TODO: we might need to provide a prop to allow flex-end or center
64
- native: { alignSelf: 'flex-start' }
51
+ // width: fit-content isn't supported on Firefox; can't cascade props like CSS `width: fit-content; width: --moz-fit-content;`
52
+ web: { display: 'inline-flex' }
65
53
  })
66
54
  }
67
55
  }
@@ -152,7 +140,7 @@ const ButtonBase = ({
152
140
  selected = false,
153
141
  ...rest
154
142
  }) => {
155
- const getTokens = useThemeTokensCallback('Button')
143
+ const getTokens = useThemeTokensCallback('Button', tokens, variant)
156
144
  const getButtonState = ({ pressed, focused, hovered }) => ({
157
145
  pressed,
158
146
  focus: focused,
@@ -160,8 +148,7 @@ const ButtonBase = ({
160
148
  inactive,
161
149
  selected
162
150
  })
163
- const getTokensByPressableState = (pressableState) =>
164
- getTokens(tokens, variant, getButtonState(pressableState))
151
+ const getTokensByPressableState = (pressableState) => getTokens(getButtonState(pressableState))
165
152
 
166
153
  const a11y = a11yProps.select(rest)
167
154
 
@@ -1,21 +1,20 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { View, Platform } from 'react-native'
3
+ import { Platform } from 'react-native'
4
4
 
5
5
  import ButtonBase from './ButtonBase'
6
+ import { StackWrap } from '../StackView'
6
7
  import { useViewport } from '../ViewportProvider'
7
8
  import { useThemeTokens } from '../ThemeProvider'
8
- import { a11yProps, pressProps, variantProp, getTokensPropType } from '../utils/propTypes'
9
+ import {
10
+ a11yProps,
11
+ pressProps,
12
+ variantProp,
13
+ getTokensPropType,
14
+ selectTokens
15
+ } from '../utils/propTypes'
9
16
  import { useMultipleInputValues } from '../utils/input'
10
17
 
11
- const selectContainerStyles = ({ direction }) => ({
12
- flexDirection: direction
13
- })
14
-
15
- const selectItemTokens = ({ gap }, index) => ({
16
- marginLeft: index && gap
17
- })
18
-
19
18
  const ButtonGroup = ({
20
19
  variant,
21
20
  buttonVariant = {},
@@ -34,7 +33,8 @@ const ButtonGroup = ({
34
33
  }) => {
35
34
  const viewport = useViewport()
36
35
  const themeTokens = useThemeTokens('ButtonGroup', tokens, variant, { viewport })
37
- const containerStyles = selectContainerStyles(themeTokens)
36
+ const stackTokens = selectTokens('StackView', themeTokens)
37
+ const { direction, space } = themeTokens
38
38
 
39
39
  const { currentValues, toggleOneValue } = useMultipleInputValues({
40
40
  initialValues,
@@ -51,9 +51,8 @@ const ButtonGroup = ({
51
51
  const itemA11yRole = a11y.accessibilityRole === 'radioGroup' ? 'radio' : 'checkbox'
52
52
 
53
53
  return (
54
- <View style={containerStyles} {...a11y}>
54
+ <StackWrap {...a11y} space={space} direction={direction} tokens={stackTokens}>
55
55
  {items.map(({ label, id = label, accessibilityLabel }, index) => {
56
- const itemTokens = selectItemTokens(themeTokens, index)
57
56
  const isSelected = currentValues.includes(id)
58
57
 
59
58
  // Allow handlers to be passed down for blur, hover, focus, pressIn, etc
@@ -87,15 +86,12 @@ const ButtonGroup = ({
87
86
 
88
87
  // Ensure button is direct child of group as MacOS voiceover only applies "X of Y" to
89
88
  // "radio" if it's a direct child of "radiogroup", even if aria-posinset etc exists
90
- // See also: TODO: make margin the responsibility of a parent
91
- // https://github.com/telus/universal-design-system/issues/525
92
89
  return (
93
90
  <ButtonBase
94
91
  key={id}
95
92
  {...pressHandlers}
96
93
  onPress={handlePress}
97
94
  variant={{ component: 'ButtonGroup', ...buttonVariant }}
98
- tokens={itemTokens}
99
95
  selected={isSelected}
100
96
  inactive={inactive}
101
97
  {...itemA11y}
@@ -104,7 +100,7 @@ const ButtonGroup = ({
104
100
  </ButtonBase>
105
101
  )
106
102
  })}
107
- </View>
103
+ </StackWrap>
108
104
  )
109
105
  }
110
106
 
@@ -0,0 +1,101 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { View } from 'react-native'
4
+
5
+ import { applyShadowToken, useThemeTokens } from '../ThemeProvider'
6
+ import { getTokensPropType, variantProp } from '../utils'
7
+ import { useViewport } from '../ViewportProvider'
8
+ import { a11yProps } from '../utils/propTypes'
9
+
10
+ // Ensure explicit selection of tokens
11
+ const selectStyles = ({
12
+ backgroundColor,
13
+ borderColor,
14
+ borderRadius,
15
+ borderWidth,
16
+ paddingBottom,
17
+ paddingLeft,
18
+ paddingRight,
19
+ paddingTop,
20
+ shadow
21
+ }) => ({
22
+ backgroundColor,
23
+ borderColor,
24
+ borderRadius,
25
+ borderWidth,
26
+ paddingBottom,
27
+ paddingLeft,
28
+ paddingRight,
29
+ paddingTop,
30
+ ...applyShadowToken(shadow)
31
+ })
32
+
33
+ /**
34
+ * A basic card component, unstyled by default.
35
+ *
36
+ * ## Component API
37
+ *
38
+ * ### Background
39
+ *
40
+ * In order to control the background of a card, the following tokens are currently
41
+ * supported:
42
+ *
43
+ * - `backgroundColor`
44
+ *
45
+ * ### Border
46
+ *
47
+ * The following border tokens can be used:
48
+ *
49
+ * - `borderColor`
50
+ * - `borderRadius`
51
+ * - `borderWidth`
52
+ *
53
+ * ### Padding
54
+ *
55
+ * Please use the following tokens to control the padding:
56
+ *
57
+ * - `paddingBottom`
58
+ * - `paddingLeft`
59
+ * - `paddingRight`
60
+ * - `paddingTop`
61
+ *
62
+ * Note that various viewport sizes are supported as well.
63
+ *
64
+ * ### Shadow
65
+ *
66
+ * Feel free to use the following properties within the `shadow` token:
67
+ *
68
+ * - `inset`: boolean
69
+ * - `color`: string
70
+ * - `offsetX`: number
71
+ * - `offsetY`: number
72
+ * - `blur`: number
73
+ * - `spread`: number
74
+ *
75
+ * ## Accessibility
76
+ *
77
+ * Card supports all the common a11y props. Please remember that by marking a card as `accessible`
78
+ * you automatically make inaccessible its children, which may or may not be appropriate
79
+ * depending on what you are trying to achieve.
80
+ */
81
+ const Card = ({ children, tokens, variant, ...rest }) => {
82
+ const viewport = useViewport()
83
+ const themeTokens = useThemeTokens('Card', tokens, variant, { viewport })
84
+ const cardStyle = selectStyles(themeTokens)
85
+ const a11y = a11yProps.select(rest)
86
+
87
+ return (
88
+ <View style={cardStyle} {...a11y}>
89
+ {children}
90
+ </View>
91
+ )
92
+ }
93
+
94
+ Card.propTypes = {
95
+ children: PropTypes.node,
96
+ tokens: getTokensPropType('Card'),
97
+ variant: variantProp.propType,
98
+ ...a11yProps.types
99
+ }
100
+
101
+ export default Card
@@ -0,0 +1,3 @@
1
+ import Card from './Card'
2
+
3
+ export default Card
@@ -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.
@@ -56,7 +56,7 @@ const ExpandCollapseControl = ({
56
56
  variant,
57
57
  rest
58
58
  }) => {
59
- const getTokens = useThemeTokensCallback('ExpandCollapseControl')
59
+ const getTokens = useThemeTokensCallback('ExpandCollapseControl', tokens, variant)
60
60
 
61
61
  const a11y = a11yProps.select({ ...rest, accessibilityRole })
62
62
  a11y.accessibilityState = {
@@ -70,8 +70,7 @@ const ExpandCollapseControl = ({
70
70
  focus: focused,
71
71
  expanded: isExpanded
72
72
  })
73
- const getControlTokens = (pressableState) =>
74
- getTokens(tokens, variant, getControlState(pressableState))
73
+ const getControlTokens = (pressableState) => getTokens(getControlState(pressableState))
75
74
  const getPressableStyle = (pressableState) =>
76
75
  selectContainerStyles(getControlTokens(pressableState))
77
76
 
@@ -0,0 +1,99 @@
1
+ import React from 'react'
2
+ import { Text, View, StyleSheet } 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
+
8
+ const selectStyles = (tokens) => selectTokens('Feedback', tokens)
9
+
10
+ const selectTitleTextStyles = ({ titleFontSize, ...tokens }) =>
11
+ applyTextStyles(selectTokens('Typography', { ...tokens, fontSize: titleFontSize }))
12
+ const selectContentTextStyles = ({ contentFontSize, ...tokens }) =>
13
+ applyTextStyles(selectTokens('Typography', { ...tokens, fontSize: contentFontSize }))
14
+
15
+ const selectIconTokens = ({ iconSize, iconColor }) => ({
16
+ size: iconSize,
17
+ color: iconColor
18
+ })
19
+
20
+ const selectIconContainerStyles = ({ iconGap }) => ({
21
+ paddingRight: iconGap
22
+ })
23
+
24
+ /**
25
+ * A feedback box commonly used with form fields.
26
+ *
27
+ * ### Standalone usage
28
+ * While its primary use is to facilitate feedback states for other form components such as `TextInput`,
29
+ * you may use it standalone.
30
+ *
31
+ * ### Complex content
32
+ * You may pass any React tree as the children of this component, bear in mind that a render function
33
+ * is better suited for styling children based on Feedback's variant.
34
+ *
35
+ * ### Using a render function
36
+ * When a function is passed for rendering content, it will receive the feedback text styles and
37
+ * variant as arguments.
38
+ *
39
+ * ### Accessibility
40
+ * All accessibility props set on this component will be applied to the outer container.
41
+ */
42
+ const Feedback = ({ title, children, tokens, variant, ...rest }) => {
43
+ const themeTokens = useThemeTokens('Feedback', tokens, variant)
44
+
45
+ const { icon: IconComponent } = themeTokens
46
+
47
+ const titleTextStyles = selectTitleTextStyles(themeTokens)
48
+ const contentTextStyles = selectContentTextStyles(themeTokens)
49
+
50
+ const content =
51
+ typeof children === 'string' ? <Text style={contentTextStyles}>{children}</Text> : children
52
+
53
+ const accessibilityProps = a11yProps.select(rest)
54
+
55
+ // TODO: use Stack to separate the title from content (space 2)
56
+
57
+ return (
58
+ <View style={selectStyles(themeTokens)} {...accessibilityProps}>
59
+ {title !== undefined && (
60
+ <View style={staticStyles.title}>
61
+ {IconComponent && (
62
+ <View style={selectIconContainerStyles(themeTokens)}>
63
+ <IconComponent tokens={selectIconTokens(themeTokens)} />
64
+ </View>
65
+ )}
66
+ <Text style={titleTextStyles}>{title}</Text>
67
+ </View>
68
+ )}
69
+ {content && typeof content === 'function' ? (
70
+ content({ textStyles: contentTextStyles, variant })
71
+ ) : (
72
+ <View>{content}</View>
73
+ )}
74
+ </View>
75
+ )
76
+ }
77
+
78
+ Feedback.propTypes = {
79
+ /**
80
+ * Emphasized summary of the feedback. If an icon is set, it is rendered next to the title.
81
+ */
82
+ title: PropTypes.string,
83
+ /**
84
+ * Feedback content rendered below the title. A render function `({textStyles, variant}) => {}` is supported.
85
+ */
86
+ children: PropTypes.oneOfType([PropTypes.string, PropTypes.node, PropTypes.func]),
87
+ tokens: getTokensPropType('Feedback'),
88
+ variant: variantProp.propType
89
+ }
90
+
91
+ export default Feedback
92
+
93
+ const staticStyles = StyleSheet.create({
94
+ title: {
95
+ display: 'flex',
96
+ flexDirection: 'row',
97
+ alignItems: 'center'
98
+ }
99
+ })
@@ -0,0 +1,3 @@
1
+ import Feedback from './Feedback'
2
+
3
+ export default Feedback
@@ -6,7 +6,7 @@ import { viewports } from '@telus-uds/system-constants'
6
6
  import GutterContext from '../providers/GutterContext'
7
7
  import { useViewport } from '../../ViewportProvider'
8
8
  import applyInheritance from '../helpers'
9
- import { responsivePropTypeFactory } from '../../utils'
9
+ import { responsiveProps } from '../../utils'
10
10
 
11
11
  const Col = ({
12
12
  horizontalAlign,
@@ -258,7 +258,9 @@ Col.propTypes = {
258
258
  *
259
259
  * Accepts a `PropType.string` following the [responsive prop](#/Layout?id=responsive) structure.
260
260
  */
261
- horizontalAlign: responsivePropTypeFactory(PropTypes.oneOf(['left', 'center', 'right']))
261
+ horizontalAlign: responsiveProps.getTypeOptionallyByViewport(
262
+ PropTypes.oneOf(['left', 'center', 'right'])
263
+ )
262
264
  }
263
265
 
264
266
  export default Col
package/src/Icon/Icon.jsx CHANGED
@@ -19,7 +19,8 @@ const Icon = ({ IconSvg, variant, label, titleId, tokens = {} }) => {
19
19
  transition: 'transform 200ms',
20
20
  transform: [
21
21
  themeTokens.scale ? `scale(${themeTokens.scale})` : '',
22
- themeTokens.translateX ? `translateX(${themeTokens.translateX}px)` : ''
22
+ themeTokens.translateX ? `translateX(${themeTokens.translateX}px)` : '',
23
+ themeTokens.translateY ? `translateY(${themeTokens.translateY}px)` : ''
23
24
  ]
24
25
  .filter((exists) => exists)
25
26
  .join(' ')
@@ -0,0 +1,99 @@
1
+ import React from 'react'
2
+ import { View, Text, StyleSheet } from 'react-native'
3
+ import PropTypes from 'prop-types'
4
+
5
+ import { applyTextStyles, useThemeTokens } from '../ThemeProvider'
6
+ import { getTokensPropType, selectTokens, variantProp } from '../utils'
7
+
8
+ import LabelContent from './LabelContent'
9
+
10
+ const selectLabelStyles = (tokens) => applyTextStyles(selectTokens('Typography', tokens))
11
+
12
+ const selectHintStyles = ({
13
+ hintColor,
14
+ hintFontName,
15
+ hintFontSize,
16
+ hintFontWeight,
17
+ hintLineHeight
18
+ }) =>
19
+ applyTextStyles({
20
+ color: hintColor,
21
+ fontName: hintFontName,
22
+ fontSize: hintFontSize,
23
+ fontWeight: hintFontWeight,
24
+ lineHeight: hintLineHeight
25
+ })
26
+
27
+ const selectGapStyles = ({ gap }) => ({ marginRight: gap })
28
+
29
+ function InputLabel({
30
+ label,
31
+ forId,
32
+ hint,
33
+ hintPosition = 'inline',
34
+ hintId,
35
+ tooltip,
36
+ tokens,
37
+ variant
38
+ }) {
39
+ const themeTokens = useThemeTokens('InputLabel', tokens, variant)
40
+
41
+ // TODO: use the actual Tooltip component here
42
+ const hasTooltip = tooltip !== undefined
43
+ const isHintInline = hintPosition === 'inline'
44
+
45
+ return (
46
+ <View style={[staticStyles.container, !isHintInline && staticStyles.containerWithHintBelow]}>
47
+ <Text
48
+ style={[selectLabelStyles(themeTokens), selectGapStyles(themeTokens), staticStyles.label]}
49
+ >
50
+ <LabelContent forId={forId}>{label}</LabelContent>
51
+ </Text>
52
+ {hint && isHintInline && (
53
+ <Text
54
+ style={[selectHintStyles(themeTokens), hasTooltip && selectGapStyles(themeTokens)]}
55
+ nativeID={hintId}
56
+ >
57
+ {hint}
58
+ </Text>
59
+ )}
60
+ {tooltip && <Text>?</Text>}
61
+ {hint && !isHintInline && (
62
+ <Text style={[selectHintStyles(themeTokens), staticStyles.hintBelow]} nativeID={hintId}>
63
+ {hint}
64
+ </Text>
65
+ )}
66
+ </View>
67
+ )
68
+ }
69
+
70
+ InputLabel.propTypes = {
71
+ label: PropTypes.string.isRequired,
72
+ forId: PropTypes.string,
73
+ hint: PropTypes.string,
74
+ hintPosition: PropTypes.oneOf(['inline', 'below']),
75
+ hintId: PropTypes.string,
76
+ tooltip: PropTypes.string,
77
+ tokens: getTokensPropType('InputLabel'),
78
+ variant: variantProp.propType
79
+ }
80
+
81
+ export default InputLabel
82
+
83
+ const staticStyles = StyleSheet.create({
84
+ container: {
85
+ display: 'flex',
86
+ flexDirection: 'row',
87
+ alignItems: 'center'
88
+ },
89
+ containerWithHintBelow: {
90
+ flexWrap: 'wrap'
91
+ },
92
+ label: {
93
+ flexShrink: 0
94
+ },
95
+ hintBelow: {
96
+ flexBasis: '100%',
97
+ flexShrink: 0
98
+ }
99
+ })
@@ -0,0 +1,6 @@
1
+ // Since there's no equivalent for web's <label> element we're simply rendering whatever is passed to this component.
2
+ function LabelContent({ children }) {
3
+ return children
4
+ }
5
+
6
+ export default LabelContent
@@ -0,0 +1,13 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ function LabelContent({ children, forId }) {
5
+ return <label htmlFor={forId}>{children}</label>
6
+ }
7
+
8
+ export default LabelContent
9
+
10
+ LabelContent.propTypes = {
11
+ children: PropTypes.string,
12
+ forId: PropTypes.string
13
+ }
@@ -0,0 +1,3 @@
1
+ import InputLabel from './InputLabel'
2
+
3
+ export default InputLabel
@@ -40,7 +40,10 @@ const selectOuterBorderStyles = ({
40
40
  outline: outerBorderOutline,
41
41
  borderWidth: outerBorderWidth,
42
42
  borderColor: outerBorderColor,
43
- borderRadius: outerBorderRadius
43
+ borderRadius: outerBorderRadius,
44
+ // Stops focus ring stretching horizontally if parent has display: block
45
+ // width: fit-content isn't supported on Firefox; can't cascade props like CSS `width: fit-content; width: --moz-fit-content;`
46
+ display: 'inline-flex'
44
47
  }
45
48
  : {}
46
49
 
@@ -62,10 +65,12 @@ const selectIconStyles = ({
62
65
  iconGapBefore,
63
66
  iconGapAfter,
64
67
  iconScale,
65
- iconTranslateX
68
+ iconTranslateX,
69
+ iconTranslateY
66
70
  }) => ({
67
71
  scale: iconScale,
68
72
  translateX: iconTranslateX,
73
+ translateY: iconTranslateY,
69
74
  size: iconSize,
70
75
  gapBefore: iconGapBefore,
71
76
  gapAfter: iconGapAfter
@@ -181,7 +186,8 @@ const LinkBase = ({
181
186
  size: iconStyles.size ? iconStyles.size * iconScale : undefined,
182
187
  color: contentStyles.color ?? undefined,
183
188
  scale: iconStyles.scale ?? undefined,
184
- translateX: iconStyles.translateX ?? undefined
189
+ translateX: iconStyles.translateX ?? undefined,
190
+ translateY: iconStyles.translateY ?? undefined
185
191
  }
186
192
 
187
193
  const iconContent = <IconComponent tokens={iconTokens} variant={iconVariant} />