@telus-uds/components-base 1.5.0 → 1.7.0

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 (125) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/.turbo/turbo-lint.log +13 -0
  3. package/CHANGELOG.json +154 -1
  4. package/CHANGELOG.md +47 -2
  5. package/__tests__/FlexGrid/Row.test.jsx +100 -25
  6. package/__tests__/utils/containUniqueFields.test.js +25 -0
  7. package/component-docs.json +78 -26
  8. package/generate-component-docs.js +20 -7
  9. package/lib/Button/ButtonBase.js +1 -1
  10. package/lib/Button/ButtonGroup.js +20 -12
  11. package/lib/Card/PressableCardBase.js +1 -1
  12. package/lib/Checkbox/Checkbox.js +27 -16
  13. package/lib/Checkbox/CheckboxGroup.js +19 -5
  14. package/lib/ExpandCollapse/Panel.js +10 -10
  15. package/lib/FlexGrid/Col/Col.js +13 -3
  16. package/lib/FlexGrid/Row/Row.js +8 -2
  17. package/lib/InputLabel/InputLabel.js +27 -25
  18. package/lib/Link/LinkBase.js +19 -6
  19. package/lib/Link/TextButton.js +1 -10
  20. package/lib/List/ListItem.js +22 -12
  21. package/lib/Modal/Modal.js +18 -18
  22. package/lib/Radio/Radio.js +23 -12
  23. package/lib/Radio/RadioGroup.js +12 -3
  24. package/lib/RadioCard/RadioCard.js +1 -1
  25. package/lib/RadioCard/RadioCardGroup.js +11 -2
  26. package/lib/Search/Search.js +27 -19
  27. package/lib/Select/Select.js +2 -3
  28. package/lib/Tags/Tags.js +23 -17
  29. package/lib/TextInput/TextArea.js +2 -2
  30. package/lib/TextInput/TextInput.js +12 -2
  31. package/lib/TextInput/TextInputBase.js +1 -1
  32. package/lib/TextInput/propTypes.js +8 -1
  33. package/lib/ToggleSwitch/ToggleSwitch.js +5 -2
  34. package/lib/ToggleSwitch/ToggleSwitchGroup.js +20 -12
  35. package/lib/Typography/Typography.js +12 -10
  36. package/lib/index.js +22 -1
  37. package/lib/utils/containUniqueFields.js +34 -0
  38. package/lib/utils/index.js +10 -1
  39. package/lib/utils/input.js +5 -6
  40. package/lib/utils/props/handlerProps.js +72 -0
  41. package/lib/utils/props/index.js +32 -0
  42. package/lib/utils/props/inputSupportsProps.js +3 -5
  43. package/lib/utils/props/linkProps.js +3 -7
  44. package/lib/utils/props/textInputProps.js +206 -0
  45. package/lib/utils/props/textProps.js +72 -0
  46. package/lib-module/Button/ButtonBase.js +2 -2
  47. package/lib-module/Button/ButtonGroup.js +15 -6
  48. package/lib-module/Card/PressableCardBase.js +2 -2
  49. package/lib-module/Checkbox/Checkbox.js +28 -17
  50. package/lib-module/Checkbox/CheckboxGroup.js +20 -7
  51. package/lib-module/ExpandCollapse/Panel.js +10 -10
  52. package/lib-module/FlexGrid/Col/Col.js +13 -3
  53. package/lib-module/FlexGrid/Row/Row.js +8 -2
  54. package/lib-module/InputLabel/InputLabel.js +28 -25
  55. package/lib-module/Link/LinkBase.js +19 -6
  56. package/lib-module/Link/TextButton.js +1 -10
  57. package/lib-module/List/ListItem.js +22 -12
  58. package/lib-module/Modal/Modal.js +19 -19
  59. package/lib-module/Radio/Radio.js +24 -13
  60. package/lib-module/Radio/RadioGroup.js +13 -4
  61. package/lib-module/RadioCard/RadioCard.js +2 -2
  62. package/lib-module/RadioCard/RadioCardGroup.js +12 -3
  63. package/lib-module/Search/Search.js +29 -21
  64. package/lib-module/Select/Select.js +2 -3
  65. package/lib-module/Tags/Tags.js +18 -11
  66. package/lib-module/TextInput/TextArea.js +3 -3
  67. package/lib-module/TextInput/TextInput.js +11 -3
  68. package/lib-module/TextInput/TextInputBase.js +2 -2
  69. package/lib-module/TextInput/propTypes.js +7 -1
  70. package/lib-module/ToggleSwitch/ToggleSwitch.js +6 -3
  71. package/lib-module/ToggleSwitch/ToggleSwitchGroup.js +15 -6
  72. package/lib-module/Typography/Typography.js +13 -11
  73. package/lib-module/index.js +1 -1
  74. package/lib-module/utils/containUniqueFields.js +26 -0
  75. package/lib-module/utils/index.js +2 -1
  76. package/lib-module/utils/input.js +6 -6
  77. package/lib-module/utils/props/handlerProps.js +59 -0
  78. package/lib-module/utils/props/index.js +3 -0
  79. package/lib-module/utils/props/inputSupportsProps.js +3 -5
  80. package/lib-module/utils/props/linkProps.js +3 -7
  81. package/lib-module/utils/props/textInputProps.js +193 -0
  82. package/lib-module/utils/props/textProps.js +59 -0
  83. package/package.json +5 -5
  84. package/src/Button/ButtonBase.jsx +8 -2
  85. package/src/Button/ButtonGroup.jsx +51 -34
  86. package/src/Card/PressableCardBase.jsx +6 -1
  87. package/src/Checkbox/Checkbox.jsx +35 -23
  88. package/src/Checkbox/CheckboxGroup.jsx +52 -22
  89. package/src/ExpandCollapse/Panel.jsx +9 -9
  90. package/src/FlexGrid/Col/Col.jsx +11 -2
  91. package/src/FlexGrid/Row/Row.jsx +8 -2
  92. package/src/InputLabel/InputLabel.jsx +36 -27
  93. package/src/Link/LinkBase.jsx +20 -4
  94. package/src/Link/TextButton.jsx +1 -19
  95. package/src/List/ListItem.jsx +17 -9
  96. package/src/Modal/Modal.jsx +30 -26
  97. package/src/Radio/Radio.jsx +26 -14
  98. package/src/Radio/RadioGroup.jsx +39 -21
  99. package/src/RadioCard/RadioCard.jsx +6 -1
  100. package/src/RadioCard/RadioCardGroup.jsx +17 -1
  101. package/src/Search/Search.jsx +33 -21
  102. package/src/Select/Select.jsx +2 -2
  103. package/src/Tags/Tags.jsx +23 -9
  104. package/src/TextInput/TextArea.jsx +7 -1
  105. package/src/TextInput/TextInput.jsx +15 -3
  106. package/src/TextInput/TextInputBase.jsx +8 -1
  107. package/src/TextInput/propTypes.js +7 -1
  108. package/src/ToggleSwitch/ToggleSwitch.jsx +11 -2
  109. package/src/ToggleSwitch/ToggleSwitchGroup.jsx +19 -6
  110. package/src/Typography/Typography.jsx +13 -9
  111. package/src/index.js +4 -1
  112. package/src/utils/containUniqueFields.js +32 -0
  113. package/src/utils/index.js +1 -0
  114. package/src/utils/input.js +5 -7
  115. package/src/utils/props/handlerProps.js +47 -0
  116. package/src/utils/props/index.js +3 -0
  117. package/src/utils/props/inputSupportsProps.js +3 -4
  118. package/src/utils/props/linkProps.js +3 -6
  119. package/src/utils/props/textInputProps.js +177 -0
  120. package/src/utils/props/textProps.js +58 -0
  121. package/stories/InputLabel/InputLabel.stories.jsx +25 -28
  122. package/stories/Modal/Modal.stories.jsx +25 -0
  123. package/stories/Search/Search.stories.jsx +53 -3
  124. package/stories/TextInput/TextInput.stories.jsx +40 -2
  125. package/__tests__/Link/LinkBase.test.jsx +0 -22
@@ -9,17 +9,25 @@ import { useViewport } from '../ViewportProvider'
9
9
  import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider'
10
10
  import {
11
11
  a11yProps,
12
+ containUniqueFields,
13
+ focusHandlerProps,
12
14
  pressProps,
13
15
  getTokensPropType,
14
16
  selectSystemProps,
15
17
  selectTokens,
18
+ useMultipleInputValues,
16
19
  variantProp,
17
20
  viewProps
18
- } from '../utils/props'
19
- import { useMultipleInputValues } from '../utils/input'
21
+ } from '../utils'
20
22
  import { getPressHandlersWithArgs } from '../utils/pressability'
21
23
 
22
- const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, pressProps, viewProps])
24
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
25
+ const [selectItemProps, selectedItemPropTypes] = selectSystemProps([
26
+ a11yProps,
27
+ focusHandlerProps,
28
+ pressProps,
29
+ viewProps
30
+ ])
23
31
 
24
32
  const ButtonGroup = forwardRef(
25
33
  (
@@ -61,6 +69,11 @@ const ButtonGroup = forwardRef(
61
69
  })
62
70
  const itemA11yRole = systemProps.accessibilityRole === 'radiogroup' ? 'radio' : 'checkbox'
63
71
 
72
+ const uniqueFields = ['id', 'label']
73
+ if (!containUniqueFields(items, uniqueFields)) {
74
+ throw new Error(`ButtonGroup items must have unique ${uniqueFields.join(', ')}`)
75
+ }
76
+
64
77
  return (
65
78
  <StackWrap
66
79
  {...systemProps}
@@ -69,41 +82,44 @@ const ButtonGroup = forwardRef(
69
82
  tokens={stackTokens}
70
83
  ref={ref}
71
84
  >
72
- {items.map(({ label, id = label, accessibilityLabel, ref: itemRef }, index) => {
73
- const isSelected = currentValues.includes(id)
85
+ {items.map(
86
+ ({ label, id = label, accessibilityLabel, ref: itemRef, ...itemRest }, index) => {
87
+ const isSelected = currentValues.includes(id)
74
88
 
75
- // Pass an object of relevant component state as first argument for any passed-in press handlers
76
- const pressHandlers = getPressHandlersWithArgs(rest, [{ id, label, currentValues }])
89
+ // Pass an object of relevant component state as first argument for any passed-in press handlers
90
+ const pressHandlers = getPressHandlersWithArgs(rest, [{ id, label, currentValues }])
77
91
 
78
- const handlePress = (event) => {
79
- if (pressHandlers.onPress) pressHandlers.onPress()
80
- toggleOneValue(id, event)
81
- }
92
+ const handlePress = (event) => {
93
+ if (pressHandlers.onPress) pressHandlers.onPress(event)
94
+ toggleOneValue(id, event)
95
+ }
82
96
 
83
- const itemA11y = {
84
- accessibilityState: { checked: isSelected },
85
- accessibilityRole: itemA11yRole,
86
- accessibilityLabel,
87
- ...a11yProps.getPositionInSet(items.length, index)
88
- }
97
+ const itemA11y = {
98
+ accessibilityState: { checked: isSelected },
99
+ accessibilityRole: itemA11yRole,
100
+ accessibilityLabel,
101
+ ...a11yProps.getPositionInSet(items.length, index)
102
+ }
89
103
 
90
- // Ensure button is direct child of group as MacOS voiceover only applies "X of Y" to
91
- // "radio" if it's a direct child of "radiogroup", even if aria-posinset etc exists
92
- return (
93
- <ButtonBase
94
- ref={itemRef}
95
- key={id}
96
- {...pressHandlers}
97
- onPress={handlePress}
98
- tokens={getButtonTokens}
99
- selected={isSelected}
100
- inactive={inactive}
101
- {...itemA11y}
102
- >
103
- {label}
104
- </ButtonBase>
105
- )
106
- })}
104
+ // Ensure button is direct child of group as MacOS voiceover only applies "X of Y" to
105
+ // "radio" if it's a direct child of "radiogroup", even if aria-posinset etc exists
106
+ return (
107
+ <ButtonBase
108
+ ref={itemRef}
109
+ key={id}
110
+ {...pressHandlers}
111
+ onPress={handlePress}
112
+ tokens={getButtonTokens}
113
+ selected={isSelected}
114
+ inactive={inactive}
115
+ {...itemA11y}
116
+ {...selectItemProps(itemRest)}
117
+ >
118
+ {label}
119
+ </ButtonBase>
120
+ )
121
+ }
122
+ )}
107
123
  </StackWrap>
108
124
  )
109
125
  }
@@ -124,6 +140,7 @@ ButtonGroup.propTypes = {
124
140
  */
125
141
  items: PropTypes.arrayOf(
126
142
  PropTypes.shape({
143
+ ...selectedItemPropTypes,
127
144
  /**
128
145
  * The text displayed to the user in the button, describing this option,
129
146
  * passed to the button as its child.
@@ -7,6 +7,7 @@ import { applyOuterBorder, validateThemeTokens } from '../ThemeProvider'
7
7
  import {
8
8
  a11yProps,
9
9
  clickProps,
10
+ focusHandlerProps,
10
11
  getTokenNames,
11
12
  getTokensSetPropType,
12
13
  linkProps,
@@ -20,7 +21,11 @@ import {
20
21
  } from '../utils'
21
22
  import CardBase from './CardBase'
22
23
 
23
- const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
24
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([
25
+ a11yProps,
26
+ focusHandlerProps,
27
+ viewProps
28
+ ])
24
29
 
25
30
  const tokenKeys = [
26
31
  ...getTokenNames('Card'),
@@ -9,6 +9,7 @@ import StackView from '../StackView'
9
9
  import { applyShadowToken, applyTextStyles, useThemeTokensCallback } from '../ThemeProvider'
10
10
  import {
11
11
  a11yProps,
12
+ focusHandlerProps,
12
13
  getTokensPropType,
13
14
  selectSystemProps,
14
15
  useInputValue,
@@ -17,7 +18,11 @@ import {
17
18
  } from '../utils'
18
19
  import useUniqueId from '../utils/useUniqueId'
19
20
 
20
- const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
21
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([
22
+ a11yProps,
23
+ focusHandlerProps,
24
+ viewProps
25
+ ])
21
26
 
22
27
  const selectInputStyles = (
23
28
  {
@@ -179,32 +184,38 @@ const Checkbox = forwardRef(
179
184
  {({ focused: focus, hovered: hover, pressed }) => {
180
185
  const { icon: IconComponent, ...stateTokens } = getTokens({ focus, hover, pressed })
181
186
  const iconTokens = selectIconTokens(stateTokens)
187
+ const labelStyles = selectLabelStyles(stateTokens)
188
+ const alignWithLabel = label
189
+ ? [staticStyles.alignWithLabel, { height: labelStyles.lineHeight }]
190
+ : null
182
191
 
183
192
  return (
184
193
  <View style={staticStyles.container}>
185
- <View
186
- style={StyleSheet.flatten([
187
- staticStyles.defaultInputStyles,
188
- selectInputStyles(stateTokens, isChecked)
189
- ])}
190
- testID="Checkbox-Input"
191
- >
192
- {/* Add a real input on Web, skip on native platforms */}
193
- <CheckboxInput
194
- checked={isChecked}
195
- defaultChecked={defaultChecked}
196
- disabled={inactive}
197
- id={inputId}
198
- isControlled={isControlled}
199
- name={name}
200
- value={value}
201
- />
202
- {isChecked && IconComponent && (
203
- <IconComponent {...iconTokens} testID="Checkbox-Icon" />
204
- )}
194
+ <View style={alignWithLabel}>
195
+ <View
196
+ style={[
197
+ staticStyles.defaultInputStyles,
198
+ selectInputStyles(stateTokens, isChecked)
199
+ ]}
200
+ testID="Checkbox-Input"
201
+ >
202
+ {/* Add a real input on Web, skip on native platforms */}
203
+ <CheckboxInput
204
+ checked={isChecked}
205
+ defaultChecked={defaultChecked}
206
+ disabled={inactive}
207
+ id={inputId}
208
+ isControlled={isControlled}
209
+ name={name}
210
+ value={value}
211
+ />
212
+ {isChecked && IconComponent && (
213
+ <IconComponent {...iconTokens} testID="Checkbox-Icon" />
214
+ )}
215
+ </View>
205
216
  </View>
206
217
  {Boolean(label) && (
207
- <Text style={selectLabelStyles(stateTokens)}>
218
+ <Text style={labelStyles}>
208
219
  <CheckboxLabel forId={inputId}>{label}</CheckboxLabel>
209
220
  </Text>
210
221
  )}
@@ -285,5 +296,6 @@ export default Checkbox
285
296
  const staticStyles = StyleSheet.create({
286
297
  wrapper: { backgroundColor: 'transparent' },
287
298
  container: { flexDirection: 'row', alignItems: 'center' },
288
- defaultInputStyles: { alignItems: 'center', justifyContent: 'center' }
299
+ defaultInputStyles: { alignItems: 'center', justifyContent: 'center' },
300
+ alignWithLabel: { alignSelf: 'flex-start', justifyContent: 'center' }
289
301
  })
@@ -4,11 +4,27 @@ import ABBPropTypes from 'airbnb-prop-types'
4
4
 
5
5
  import { useViewport } from '../ViewportProvider'
6
6
  import { useThemeTokens } from '../ThemeProvider'
7
- import { getTokensPropType, useMultipleInputValues, variantProp } from '../utils'
7
+ import {
8
+ a11yProps,
9
+ containUniqueFields,
10
+ focusHandlerProps,
11
+ getTokensPropType,
12
+ selectSystemProps,
13
+ useMultipleInputValues,
14
+ variantProp,
15
+ viewProps
16
+ } from '../utils'
8
17
  import { getStackedContent } from '../StackView'
9
18
  import Checkbox from './Checkbox'
10
19
  import Fieldset from '../Fieldset'
11
20
 
21
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
22
+ const [selectItemProps, selectedItemPropTypes] = selectSystemProps([
23
+ a11yProps,
24
+ focusHandlerProps,
25
+ viewProps
26
+ ])
27
+
12
28
  /**
13
29
  * A group of Checkboxs that behave as a fieldset. Use when users select any number of choices from options.
14
30
  *
@@ -73,7 +89,8 @@ const CheckboxGroup = forwardRef(
73
89
  onChange,
74
90
  readOnly,
75
91
  name: inputGroupName,
76
- inactive
92
+ inactive,
93
+ ...rest
77
94
  },
78
95
  ref
79
96
  ) => {
@@ -89,27 +106,37 @@ const CheckboxGroup = forwardRef(
89
106
  onChange,
90
107
  readOnly: readOnly || inactive
91
108
  })
92
- const checkboxes = items.map(({ label, id, onChange: itemOnChange, ref: itemRef }, index) => {
93
- const checkboxId = id || `Checkbox[${index}]`
94
- const handleChange = (newCheckedState, event) => {
95
- if (typeof itemOnChange === 'function') itemOnChange(newCheckedState, event)
96
- toggleOneValue(checkboxId, event)
109
+
110
+ const uniqueFields = ['id', 'label']
111
+ if (!containUniqueFields(items, uniqueFields)) {
112
+ throw new Error(`CheckboxGroup items must have unique ${uniqueFields.join(', ')}`)
113
+ }
114
+
115
+ const checkboxes = items.map(
116
+ ({ label, id, onChange: itemOnChange, ref: itemRef, ...itemRest }, index) => {
117
+ const checkboxId = id || `Checkbox[${index}]`
118
+ const handleChange = (newCheckedState, event) => {
119
+ if (typeof itemOnChange === 'function') itemOnChange(newCheckedState, event)
120
+ toggleOneValue(checkboxId, event)
121
+ }
122
+
123
+ return (
124
+ <Checkbox
125
+ ref={itemRef}
126
+ key={checkboxId}
127
+ id={checkboxId}
128
+ checked={currentValues.includes(checkboxId)}
129
+ onChange={handleChange}
130
+ inactive={inactive}
131
+ label={label}
132
+ name={inputGroupName}
133
+ tokens={radioTokens}
134
+ variant={variant}
135
+ {...selectItemProps(itemRest)}
136
+ />
137
+ )
97
138
  }
98
- return (
99
- <Checkbox
100
- ref={itemRef}
101
- key={checkboxId}
102
- id={checkboxId}
103
- checked={currentValues.includes(checkboxId)}
104
- onChange={handleChange}
105
- inactive={inactive}
106
- label={label}
107
- name={inputGroupName}
108
- tokens={radioTokens}
109
- variant={variant}
110
- />
111
- )
112
- })
139
+ )
113
140
 
114
141
  return (
115
142
  <Fieldset
@@ -122,6 +149,7 @@ const CheckboxGroup = forwardRef(
122
149
  feedback={feedback}
123
150
  inactive={inactive}
124
151
  validation={validation}
152
+ {...selectProps(rest)}
125
153
  >
126
154
  {getStackedContent(checkboxes, { space, direction: 'column' })}
127
155
  </Fieldset>
@@ -131,6 +159,7 @@ const CheckboxGroup = forwardRef(
131
159
  CheckboxGroup.displayName = 'CheckboxGroup'
132
160
 
133
161
  CheckboxGroup.propTypes = {
162
+ ...selectedSystemPropTypes,
134
163
  /**
135
164
  * Optional theme token overrides for the outer CheckboxGroup component
136
165
  */
@@ -148,6 +177,7 @@ CheckboxGroup.propTypes = {
148
177
  */
149
178
  items: PropTypes.arrayOf(
150
179
  PropTypes.exact({
180
+ ...selectedItemPropTypes,
151
181
  label: PropTypes.string,
152
182
  id: PropTypes.string,
153
183
  onChange: PropTypes.func,
@@ -74,8 +74,8 @@ const ExpandCollapsePanel = forwardRef(
74
74
  }
75
75
 
76
76
  const onContainerLayout = (event) => {
77
- if (containerHeight === null) {
78
- const { layout: { height = 0 } = {} } = event.nativeEvent
77
+ const { layout: { height = 0 } = {} } = event.nativeEvent
78
+ if (Platform.OS === 'web' || (Platform.OS !== 'web' && containerHeight === null)) {
79
79
  setContainerHeight(height)
80
80
  }
81
81
  }
@@ -104,14 +104,14 @@ const ExpandCollapsePanel = forwardRef(
104
104
  >
105
105
  {control}
106
106
  </ExpandCollapseControl>
107
- <View style={overflowContainerStyles} {...focusabilityProps}>
108
- <Animated.View
109
- onLayout={onContainerLayout}
110
- style={[staticStyles.itemsContainer, animatedStyles]}
111
- >
107
+ <Animated.View
108
+ style={[overflowContainerStyles, animatedStyles, staticStyles.itemsContainer]}
109
+ {...focusabilityProps}
110
+ >
111
+ <View onLayout={onContainerLayout}>
112
112
  <View style={selectContainerStyles(themeTokens)}>{children}</View>
113
- </Animated.View>
114
- </View>
113
+ </View>
114
+ </Animated.View>
115
115
  </View>
116
116
  )
117
117
  }
@@ -23,6 +23,7 @@ const Col = forwardRef(
23
23
  mdOffset,
24
24
  lgOffset,
25
25
  xlOffset,
26
+ flex,
26
27
  ...viewProps
27
28
  },
28
29
  ref
@@ -106,7 +107,10 @@ const Col = forwardRef(
106
107
 
107
108
  let hidingStyles = {}
108
109
 
109
- const shown = Platform.OS === 'web' ? 'block' : 'flex'
110
+ // TODO: consider setting this to always 'flex' in a major release.
111
+ // `display: block` is invalid in native apps.
112
+ // See https://telusdigital.atlassian.net/browse/UDS1-92
113
+ const shown = !flex && Platform.OS === 'web' ? 'block' : 'flex'
110
114
 
111
115
  if (viewPort === viewports.xs) {
112
116
  hidingStyles = {
@@ -267,7 +271,12 @@ Col.propTypes = {
267
271
  */
268
272
  horizontalAlign: responsiveProps.getTypeOptionallyByViewport(
269
273
  PropTypes.oneOf(['left', 'center', 'right'])
270
- )
274
+ ),
275
+ /**
276
+ * (web only) Stretches the column to fill vertical space using `display: flex`.
277
+ * In native apps, FlexGrid.Col behaves as if this is always true (as do all `View`s).
278
+ */
279
+ flex: PropTypes.bool
271
280
  }
272
281
 
273
282
  export default Col
@@ -72,21 +72,27 @@ const Row = forwardRef(
72
72
  const reverseLevel = applyInheritance([xsReverse, smReverse, mdReverse, lgReverse, xlReverse])
73
73
 
74
74
  let flexDirection = ''
75
+ let flexWrap = ''
75
76
 
76
77
  if (viewPort === viewports.xs) {
77
78
  flexDirection = reverseLevel[0] ? 'row-reverse' : 'row'
79
+ flexWrap = reverseLevel[0] ? 'wrap-reverse' : 'wrap'
78
80
  }
79
81
  if (viewPort === viewports.sm) {
80
82
  flexDirection = reverseLevel[1] ? 'row-reverse' : 'row'
83
+ flexWrap = reverseLevel[1] ? 'wrap-reverse' : 'wrap'
81
84
  }
82
85
  if (viewPort === viewports.md) {
83
86
  flexDirection = reverseLevel[2] ? 'row-reverse' : 'row'
87
+ flexWrap = reverseLevel[2] ? 'wrap-reverse' : 'wrap'
84
88
  }
85
89
  if (viewPort === viewports.lg) {
86
90
  flexDirection = reverseLevel[3] ? 'row-reverse' : 'row'
91
+ flexWrap = reverseLevel[3] ? 'wrap-reverse' : 'wrap'
87
92
  }
88
93
  if (viewPort === viewports.xl) {
89
94
  flexDirection = reverseLevel[4] ? 'row-reverse' : 'row'
95
+ flexWrap = reverseLevel[4] ? 'wrap-reverse' : 'wrap'
90
96
  }
91
97
 
92
98
  return (
@@ -97,6 +103,7 @@ const Row = forwardRef(
97
103
  styles.row,
98
104
  {
99
105
  flexDirection,
106
+ flexWrap,
100
107
  ...horizontalAlignStyles(horizontalAlign),
101
108
  ...verticalAlignStyles(verticalAlign),
102
109
  ...distributeStyles(distribute)
@@ -118,8 +125,7 @@ const styles = StyleSheet.create({
118
125
  flexGrow: 0,
119
126
  flexShrink: 1,
120
127
  flexBasis: 'auto',
121
- flexDirection: 'row',
122
- flexWrap: 'wrap'
128
+ flexDirection: 'row'
123
129
  }
124
130
  })
125
131
 
@@ -47,35 +47,46 @@ const InputLabel = forwardRef(
47
47
  const isHintInline = hintPosition === 'inline'
48
48
 
49
49
  return (
50
- <View
51
- ref={ref}
52
- style={[staticStyles.container, !isHintInline && staticStyles.containerWithHintBelow]}
53
- {...selectProps(rest)}
54
- >
55
- <Text
56
- style={[selectLabelStyles(themeTokens), selectGapStyles(themeTokens), staticStyles.label]}
57
- >
58
- <LabelContent forId={forId}>{label}</LabelContent>
59
- </Text>
60
- {hint && isHintInline && (
50
+ <>
51
+ <View ref={ref} style={staticStyles.container} {...selectProps(rest)}>
61
52
  <Text
62
- style={[selectHintStyles(themeTokens), hasTooltip && selectGapStyles(themeTokens)]}
63
- nativeID={hintId}
53
+ style={[
54
+ selectLabelStyles(themeTokens),
55
+ selectGapStyles(themeTokens),
56
+ staticStyles.label
57
+ ]}
64
58
  >
65
- {hint}
59
+ <LabelContent forId={forId}>{label}</LabelContent>
66
60
  </Text>
67
- )}
68
- {hasTooltip && (
69
- <View style={staticStyles.tooltipAlign}>
70
- <Tooltip content={tooltip} />
71
- </View>
72
- )}
61
+ {hint && isHintInline && (
62
+ <Text
63
+ style={[
64
+ selectHintStyles(themeTokens),
65
+ hasTooltip && selectGapStyles(themeTokens),
66
+ staticStyles.label
67
+ ]}
68
+ nativeID={hintId}
69
+ >
70
+ {hint}
71
+ </Text>
72
+ )}
73
+ {hasTooltip && (
74
+ <View
75
+ style={[
76
+ staticStyles.tooltipAlign,
77
+ { height: themeTokens.fontSize * themeTokens.lineHeight }
78
+ ]}
79
+ >
80
+ <Tooltip content={tooltip} />
81
+ </View>
82
+ )}
83
+ </View>
73
84
  {hint && !isHintInline && (
74
85
  <Text style={[selectHintStyles(themeTokens), staticStyles.hintBelow]} nativeID={hintId}>
75
86
  {hint}
76
87
  </Text>
77
88
  )}
78
- </View>
89
+ </>
79
90
  )
80
91
  }
81
92
  )
@@ -115,21 +126,19 @@ export default InputLabel
115
126
 
116
127
  const staticStyles = StyleSheet.create({
117
128
  container: {
118
- display: 'flex',
129
+ flexShrink: 1,
119
130
  flexDirection: 'row',
120
131
  alignItems: 'baseline'
121
132
  },
122
- containerWithHintBelow: {
123
- flexWrap: 'wrap'
124
- },
125
133
  label: {
126
- flexShrink: 0
134
+ flexShrink: 1
127
135
  },
128
136
  hintBelow: {
129
137
  flexBasis: '100%',
130
138
  flexShrink: 0
131
139
  },
132
140
  tooltipAlign: {
133
- alignSelf: 'center'
141
+ alignSelf: 'flex-start',
142
+ justifyContent: 'center'
134
143
  }
135
144
  })
@@ -45,10 +45,8 @@ const selectOuterBorderStyles = ({
45
45
  }
46
46
  : {}
47
47
 
48
- const selectTextStyles = ({ color, textLine, textLineStyle }) => ({
48
+ const selectTextStyles = ({ color }) => ({
49
49
  color,
50
- textDecorationLine: textLine,
51
- textDecorationStyle: textLineStyle,
52
50
  ...Platform.select({
53
51
  web: {
54
52
  // TODO: https://github.com/telus/universal-design-system/issues/487
@@ -65,6 +63,18 @@ const selectBlockStyles = ({ blockFontWeight, blockFontSize, blockLineHeight, bl
65
63
  fontName: blockFontName
66
64
  })
67
65
 
66
+ const selectDecorationStyles = ({ color, textLine, textLineStyle }) => ({
67
+ color,
68
+ textDecorationLine: textLine,
69
+ textDecorationStyle: textLineStyle,
70
+ ...Platform.select({
71
+ web: {
72
+ // TODO: https://github.com/telus/universal-design-system/issues/487
73
+ transition: 'color 200ms'
74
+ }
75
+ })
76
+ })
77
+
68
78
  const selectIconTokens = ({ color, iconSize, iconTranslateX, iconTranslateY }) => ({
69
79
  color,
70
80
  translateX: iconTranslateX,
@@ -132,8 +142,14 @@ const LinkBase = forwardRef(
132
142
  style={(linkState) => {
133
143
  const themeTokens = resolveLinkTokens(linkState)
134
144
  const outerBorderStyles = selectOuterBorderStyles(themeTokens)
145
+ const decorationStyles = selectDecorationStyles(themeTokens)
135
146
  const hasIcon = Boolean(icon || themeTokens.icon)
136
- return [outerBorderStyles, blockLeftStyle, hasIcon && staticStyles.rowContainer]
147
+ return [
148
+ outerBorderStyles,
149
+ blockLeftStyle,
150
+ decorationStyles,
151
+ hasIcon && staticStyles.rowContainer
152
+ ]
137
153
  }}
138
154
  >
139
155
  {(linkState) => {
@@ -10,19 +10,7 @@ import LinkBase from './LinkBase'
10
10
  * take place on the current page, or for navigation within an app.
11
11
  */
12
12
  const TextButton = forwardRef(
13
- (
14
- {
15
- onPress,
16
- children,
17
- variant,
18
- tokens,
19
- // TODO: this may need to use `link` role on Web in the case of being passed both `href` and
20
- // `onPress` in an omniplatform app that uses React Navigation's useLinkProps for internal nav.
21
- accessibilityRole = 'button',
22
- ...linkProps
23
- },
24
- ref
25
- ) => {
13
+ ({ onPress, children, variant, tokens, accessibilityRole = 'button', ...linkProps }, ref) => {
26
14
  const getTokens = useThemeTokensCallback('Link', tokens, variant)
27
15
  return (
28
16
  <LinkBase
@@ -44,10 +32,4 @@ TextButton.propTypes = {
44
32
  onPress: PropTypes.func.isRequired
45
33
  }
46
34
 
47
- // Remove incompatible Link prop (if this build includes propTypes)
48
- // TODO: test if this works with web navigation in omniplatform React Navigation
49
- // https://github.com/telus/universal-design-system/issues/665
50
- // eslint-disable-next-line react/forbid-foreign-prop-types
51
- if (TextButton.propTypes?.href) delete TextButton.propTypes.href
52
-
53
35
  export default TextButton