@telus-uds/components-base 0.0.2-prerelease.4 → 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 (96) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/__fixtures__/testTheme.js +83 -11
  3. package/__tests__/Divider/Divider.test.jsx +26 -5
  4. package/__tests__/Feedback/Feedback.test.jsx +42 -0
  5. package/__tests__/Spacer/Spacer.test.jsx +63 -0
  6. package/__tests__/StackView/StackView.test.jsx +242 -0
  7. package/__tests__/StackView/StackWrap.test.jsx +47 -0
  8. package/__tests__/StackView/getStackedContent.test.jsx +295 -0
  9. package/__tests__/TextInput/TextInput.test.jsx +146 -0
  10. package/__tests__/utils/useUniqueId.test.js +31 -0
  11. package/lib/Box/Box.js +7 -2
  12. package/lib/Button/ButtonBase.js +6 -16
  13. package/lib/Button/ButtonGroup.js +13 -22
  14. package/lib/Divider/Divider.js +40 -2
  15. package/lib/Feedback/Feedback.js +110 -0
  16. package/lib/Feedback/index.js +2 -0
  17. package/lib/Icon/Icon.js +1 -1
  18. package/lib/InputLabel/InputLabel.js +86 -0
  19. package/lib/InputLabel/LabelContent.native.js +8 -0
  20. package/lib/InputLabel/LabelContent.web.js +17 -0
  21. package/lib/InputLabel/index.js +2 -0
  22. package/lib/Link/LinkBase.js +9 -3
  23. package/lib/Spacer/Spacer.js +98 -0
  24. package/lib/Spacer/index.js +2 -0
  25. package/lib/StackView/StackView.js +105 -0
  26. package/lib/StackView/StackWrap.js +32 -0
  27. package/lib/StackView/StackWrap.native.js +3 -0
  28. package/lib/StackView/StackWrapBox.js +85 -0
  29. package/lib/StackView/StackWrapGap.js +45 -0
  30. package/lib/StackView/common.js +30 -0
  31. package/lib/StackView/getStackedContent.js +111 -0
  32. package/lib/StackView/index.js +5 -0
  33. package/lib/TextInput/TextInput.js +337 -0
  34. package/lib/TextInput/index.js +2 -0
  35. package/lib/Typography/Typography.js +0 -4
  36. package/lib/index.js +6 -1
  37. package/lib/utils/input.js +3 -1
  38. package/lib/utils/propTypes.js +9 -1
  39. package/lib/utils/useUniqueId.js +12 -0
  40. package/package.json +2 -2
  41. package/release-context.json +4 -4
  42. package/src/Box/Box.jsx +4 -2
  43. package/src/Button/ButtonBase.jsx +6 -18
  44. package/src/Button/ButtonGroup.jsx +13 -17
  45. package/src/Divider/Divider.jsx +38 -3
  46. package/src/Feedback/Feedback.jsx +99 -0
  47. package/src/Feedback/index.js +3 -0
  48. package/src/Icon/Icon.jsx +2 -1
  49. package/src/InputLabel/InputLabel.jsx +99 -0
  50. package/src/InputLabel/LabelContent.native.jsx +6 -0
  51. package/src/InputLabel/LabelContent.web.jsx +13 -0
  52. package/src/InputLabel/index.js +3 -0
  53. package/src/Link/LinkBase.jsx +9 -3
  54. package/src/Spacer/Spacer.jsx +91 -0
  55. package/src/Spacer/index.js +3 -0
  56. package/src/StackView/StackView.jsx +103 -0
  57. package/src/StackView/StackWrap.jsx +33 -0
  58. package/src/StackView/StackWrap.native.jsx +4 -0
  59. package/src/StackView/StackWrapBox.jsx +82 -0
  60. package/src/StackView/StackWrapGap.jsx +39 -0
  61. package/src/StackView/common.jsx +28 -0
  62. package/src/StackView/getStackedContent.jsx +106 -0
  63. package/src/StackView/index.js +6 -0
  64. package/src/TextInput/TextInput.jsx +325 -0
  65. package/src/TextInput/index.js +3 -0
  66. package/src/Typography/Typography.jsx +0 -4
  67. package/src/index.js +6 -1
  68. package/src/utils/input.js +2 -1
  69. package/src/utils/propTypes.js +9 -0
  70. package/src/utils/useUniqueId.js +14 -0
  71. package/stories/A11yText/A11yText.stories.jsx +11 -5
  72. package/stories/ActivityIndicator/ActivityIndicator.stories.jsx +11 -2
  73. package/stories/Box/Box.stories.jsx +29 -2
  74. package/stories/Button/Button.stories.jsx +17 -21
  75. package/stories/Button/ButtonGroup.stories.jsx +2 -1
  76. package/stories/Button/ButtonLink.stories.jsx +6 -4
  77. package/stories/Card/Card.stories.jsx +13 -1
  78. package/stories/Divider/Divider.stories.jsx +26 -2
  79. package/stories/ExpandCollapse/ExpandCollapse.stories.jsx +74 -79
  80. package/stories/Feedback/Feedback.stories.jsx +97 -0
  81. package/stories/FlexGrid/01 FlexGrid.stories.jsx +20 -7
  82. package/stories/Icon/Icon.stories.jsx +11 -3
  83. package/stories/InputLabel/InputLabel.stories.jsx +37 -0
  84. package/stories/Link/ChevronLink.stories.jsx +20 -4
  85. package/stories/Link/Link.stories.jsx +24 -2
  86. package/stories/Link/TextButton.stories.jsx +24 -2
  87. package/stories/Pagination/Pagination.stories.jsx +28 -14
  88. package/stories/SideNav/SideNav.stories.jsx +17 -2
  89. package/stories/Spacer/Spacer.stories.jsx +33 -0
  90. package/stories/StackView/StackView.stories.jsx +65 -0
  91. package/stories/StackView/StackWrap.stories.jsx +52 -0
  92. package/stories/TextInput/TextInput.stories.jsx +103 -0
  93. package/stories/ToggleSwitch/ToggleSwitch.stories.jsx +16 -3
  94. package/stories/Typography/Typography.stories.jsx +12 -3
  95. package/stories/platform-supports.web.jsx +1 -1
  96. package/stories/supports.jsx +109 -13
package/src/Box/Box.jsx CHANGED
@@ -99,6 +99,7 @@ const Box = ({
99
99
  variant,
100
100
  tokens,
101
101
  scroll,
102
+ testID,
102
103
  ...rest
103
104
  }) => {
104
105
  const a11y = a11yProps.select(rest)
@@ -116,13 +117,13 @@ const Box = ({
116
117
  const scrollProps = typeof scroll === 'object' ? scroll : {}
117
118
  scrollProps.contentContainerStyle = [styles, scrollProps.contentContainerStyle]
118
119
  return (
119
- <ScrollView {...scrollProps} {...a11y}>
120
+ <ScrollView {...scrollProps} {...a11y} testID={testID}>
120
121
  {children}
121
122
  </ScrollView>
122
123
  )
123
124
  }
124
125
  return (
125
- <View {...a11y} style={styles}>
126
+ <View {...a11y} style={styles} testID={testID}>
126
127
  {children}
127
128
  </View>
128
129
  )
@@ -142,6 +143,7 @@ Box.propTypes = {
142
143
  ]),
143
144
  variant: variantProp.propType,
144
145
  tokens: getTokensPropType('Box'),
146
+ testID: PropTypes.string,
145
147
  children: PropTypes.node.isRequired
146
148
  }
147
149
 
@@ -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
  }
@@ -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
 
@@ -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,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
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} />
@@ -0,0 +1,91 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { StyleSheet, View } from 'react-native'
4
+
5
+ import { useSpacingScale, spacingProps } from '../utils'
6
+ /**
7
+ * @typedef {import('../utils/propTypes.js').SpacingValue} SpacingValue
8
+ * @typedef {import('../utils/propTypes.js').SpacingObject} SpacingObject
9
+ */
10
+
11
+ const selectSizeStyle = (size, direction) => ({
12
+ // Only apply space in one direction at a time, else large spacers will increase the
13
+ // 'across' size of the parent and mess up siblings with styles like `alignItems: stretch`.
14
+ [direction === 'row' ? 'width' : 'height']: size
15
+ })
16
+
17
+ /**
18
+ * An empty element used to create a gap between components that is sized according to the theme spacing scale,
19
+ * passing its `space` prop ({@link SpacingValue}) to `useSpacingScale`.
20
+ *
21
+ * ## Simple spacing
22
+ *
23
+ * For most simple uses, pass a number to Spacer's `space` prop referring to an index in the theme's
24
+ * spacing scale. For example, for a spacer of the theme's smallest non-zero spacing size:
25
+ *
26
+ * ```jsx
27
+ * <Spacer space={1} />
28
+ * ```
29
+ *
30
+ * ## Responsive spacing
31
+ *
32
+ * Different spacing scale sizes may be chosen for different viewports by passing `space` a {@link SpacingObject}
33
+ * using keys for the viewports at and above which certain spacing indexes should apply.
34
+ *
35
+ * This can be useful when a Spacer is inside a layout with a shape that changes between viewports.
36
+ *
37
+ * For example, a spacer might need no width below 'md' viewport because the items it separates will be on separate lines,
38
+ * but may need a visible size above that viewport, and a wider size at the highest viewport:
39
+ *
40
+ * ```jsx
41
+ * <Spacer space={{ xs: 0, md: 2, xl: 3 }} />
42
+ * ```
43
+ *
44
+ * Note that themes may also define viewport-specific behaviour for indexes on the spacing scale. These viewport
45
+ * props should only be used where necessary to achieve a certain responsive layoutm and shouldn't be used to replace
46
+ * or deviate from responsive spacing behaviour in a theme that is intended to apply universally.
47
+ *
48
+ * ## More options
49
+ *
50
+ * See `useSpacingScale` hook for more options that may be used with the `space` prop.
51
+ *
52
+ * ## Accessibility
53
+ *
54
+ * Spacer has no content and is ignored by tools such as screen readers. Use `Divider` for
55
+ * separations between elements that may be treated as semantically meaningful on web.
56
+ */
57
+ const Spacer = ({ space = 1, direction = 'column', testID }) => {
58
+ const size = useSpacingScale(space)
59
+ const sizeStyle = selectSizeStyle(size, direction)
60
+ return <View testID={testID} style={[staticStyles.stretch, sizeStyle]} />
61
+ }
62
+
63
+ Spacer.propTypes = {
64
+ /**
65
+ * The size of the spacer according to the theme's spacing scale.
66
+ * Either a number corresponding to a position on the theme's spacing scale (1 is smallest, 2 is second smallest, etc),
67
+ * or, a SpacingObject with viewport keys and options (see `useSpacingScale` for details).
68
+ */
69
+ space: spacingProps.types.spacingValue,
70
+ /**
71
+ * The spacer applies space in only one direction, which is controlled by the `direction` prop:
72
+ *
73
+ * - `'column'` (default) applies space vertically; has a fixed height and not width.
74
+ * - `'row'` applies space horizontally; has a fixed width and not height.
75
+ */
76
+ direction: PropTypes.oneOf(['column', 'row']),
77
+ /**
78
+ * @ignore
79
+ * In tests, the only way to select Spacers is with testID prop and getByTestId, since they have no content,
80
+ * no accessibility role, and there is no equivalent of nextSibling in React Native Testing Library.
81
+ */
82
+ testID: PropTypes.string
83
+ }
84
+
85
+ const staticStyles = StyleSheet.create({
86
+ stretch: {
87
+ alignSelf: 'stretch'
88
+ }
89
+ })
90
+
91
+ export default Spacer
@@ -0,0 +1,3 @@
1
+ import Spacer from './Spacer'
2
+
3
+ export default Spacer