@telus-uds/components-base 1.4.0 → 1.6.1

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 (110) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/.turbo/turbo-lint.log +13 -0
  3. package/CHANGELOG.json +140 -1
  4. package/CHANGELOG.md +42 -2
  5. package/__tests__/FlexGrid/Row.test.jsx +100 -25
  6. package/__tests__/utils/containUniqueFields.test.js +25 -0
  7. package/component-docs.json +94 -16
  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/HorizontalScroll/HorizontalScroll.js +0 -1
  18. package/lib/HorizontalScroll/HorizontalScrollButton.js +23 -49
  19. package/lib/InputLabel/InputLabel.js +27 -25
  20. package/lib/Link/LinkBase.js +19 -6
  21. package/lib/Link/TextButton.js +1 -10
  22. package/lib/Modal/Modal.js +18 -18
  23. package/lib/Radio/Radio.js +23 -12
  24. package/lib/Radio/RadioGroup.js +12 -3
  25. package/lib/RadioCard/RadioCard.js +1 -1
  26. package/lib/RadioCard/RadioCardGroup.js +11 -2
  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/utils/containUniqueFields.js +34 -0
  36. package/lib/utils/index.js +10 -1
  37. package/lib/utils/props/handlerProps.js +72 -0
  38. package/lib/utils/props/index.js +14 -0
  39. package/lib/utils/props/inputSupportsProps.js +3 -5
  40. package/lib/utils/props/linkProps.js +3 -7
  41. package/lib-module/Button/ButtonBase.js +2 -2
  42. package/lib-module/Button/ButtonGroup.js +15 -6
  43. package/lib-module/Card/PressableCardBase.js +2 -2
  44. package/lib-module/Checkbox/Checkbox.js +28 -17
  45. package/lib-module/Checkbox/CheckboxGroup.js +20 -7
  46. package/lib-module/ExpandCollapse/Panel.js +10 -10
  47. package/lib-module/FlexGrid/Col/Col.js +13 -3
  48. package/lib-module/FlexGrid/Row/Row.js +8 -2
  49. package/lib-module/HorizontalScroll/HorizontalScroll.js +0 -1
  50. package/lib-module/HorizontalScroll/HorizontalScrollButton.js +24 -49
  51. package/lib-module/InputLabel/InputLabel.js +28 -25
  52. package/lib-module/Link/LinkBase.js +19 -6
  53. package/lib-module/Link/TextButton.js +1 -10
  54. package/lib-module/Modal/Modal.js +19 -19
  55. package/lib-module/Radio/Radio.js +24 -13
  56. package/lib-module/Radio/RadioGroup.js +13 -4
  57. package/lib-module/RadioCard/RadioCard.js +2 -2
  58. package/lib-module/RadioCard/RadioCardGroup.js +12 -3
  59. package/lib-module/Select/Select.js +2 -3
  60. package/lib-module/Tags/Tags.js +18 -11
  61. package/lib-module/TextInput/TextArea.js +3 -3
  62. package/lib-module/TextInput/TextInput.js +11 -3
  63. package/lib-module/TextInput/TextInputBase.js +2 -2
  64. package/lib-module/TextInput/propTypes.js +7 -1
  65. package/lib-module/ToggleSwitch/ToggleSwitch.js +6 -3
  66. package/lib-module/ToggleSwitch/ToggleSwitchGroup.js +15 -6
  67. package/lib-module/utils/containUniqueFields.js +26 -0
  68. package/lib-module/utils/index.js +2 -1
  69. package/lib-module/utils/props/handlerProps.js +59 -0
  70. package/lib-module/utils/props/index.js +1 -0
  71. package/lib-module/utils/props/inputSupportsProps.js +3 -5
  72. package/lib-module/utils/props/linkProps.js +3 -7
  73. package/package.json +5 -5
  74. package/src/Button/ButtonBase.jsx +8 -2
  75. package/src/Button/ButtonGroup.jsx +51 -34
  76. package/src/Card/PressableCardBase.jsx +6 -1
  77. package/src/Checkbox/Checkbox.jsx +35 -23
  78. package/src/Checkbox/CheckboxGroup.jsx +52 -22
  79. package/src/ExpandCollapse/Panel.jsx +9 -9
  80. package/src/FlexGrid/Col/Col.jsx +11 -2
  81. package/src/FlexGrid/Row/Row.jsx +8 -2
  82. package/src/HorizontalScroll/HorizontalScroll.jsx +1 -1
  83. package/src/HorizontalScroll/HorizontalScrollButton.jsx +21 -58
  84. package/src/InputLabel/InputLabel.jsx +36 -27
  85. package/src/Link/LinkBase.jsx +20 -4
  86. package/src/Link/TextButton.jsx +1 -19
  87. package/src/Modal/Modal.jsx +30 -26
  88. package/src/Radio/Radio.jsx +26 -14
  89. package/src/Radio/RadioGroup.jsx +39 -21
  90. package/src/RadioCard/RadioCard.jsx +6 -1
  91. package/src/RadioCard/RadioCardGroup.jsx +17 -1
  92. package/src/Select/Select.jsx +2 -2
  93. package/src/Tags/Tags.jsx +23 -9
  94. package/src/TextInput/TextArea.jsx +5 -1
  95. package/src/TextInput/TextInput.jsx +13 -3
  96. package/src/TextInput/TextInputBase.jsx +6 -1
  97. package/src/TextInput/propTypes.js +7 -1
  98. package/src/ToggleSwitch/ToggleSwitch.jsx +11 -2
  99. package/src/ToggleSwitch/ToggleSwitchGroup.jsx +19 -6
  100. package/src/utils/containUniqueFields.js +32 -0
  101. package/src/utils/index.js +1 -0
  102. package/src/utils/props/handlerProps.js +47 -0
  103. package/src/utils/props/index.js +1 -0
  104. package/src/utils/props/inputSupportsProps.js +3 -4
  105. package/src/utils/props/linkProps.js +3 -6
  106. package/stories/InputLabel/InputLabel.stories.jsx +25 -28
  107. package/stories/Modal/Modal.stories.jsx +25 -0
  108. package/stories/Search/Search.stories.jsx +4 -1
  109. package/stories/TextInput/TextInput.stories.jsx +40 -2
  110. package/__tests__/Link/LinkBase.test.jsx +0 -22
@@ -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
 
@@ -129,7 +129,7 @@ const HorizontalScroll = forwardRef(
129
129
  showsHorizontalScrollIndicator={false}
130
130
  contentContainerStyle={[
131
131
  staticStyles.scrollContainer,
132
- { marginBottom: gutter, borderBottomWidth, borderBottomColor }
132
+ { borderBottomWidth, borderBottomColor }
133
133
  ]}
134
134
  {...selectProps(rest)}
135
135
  >
@@ -1,35 +1,14 @@
1
1
  import React, { forwardRef } from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { Pressable, StyleSheet } from 'react-native'
4
-
5
- import { useThemeTokensCallback } from '../ThemeProvider'
6
- import {
7
- resolvePressableTokens,
8
- selectTokens,
9
- variantProp,
10
- getTokensPropType,
11
- useCopy,
12
- copyPropTypes,
13
- a11yProps
14
- } from '../utils'
15
- import Icon from '../Icon'
16
- import Typography from '../Typography'
3
+ import { StyleSheet, View } from 'react-native'
4
+ import { variantProp, getTokensPropType, useCopy, copyPropTypes, a11yProps } from '../utils'
5
+ import { useThemeTokens } from '../ThemeProvider'
6
+ import IconButton from '../IconButton'
17
7
  import dictionary from './dictionary'
18
8
 
19
- const selectButtonStyles = (
20
- { borderRadius, backgroundColor, borderColor, borderWidth, padding },
21
- direction
22
- ) => [
23
- staticStyles.absolute,
24
- staticStyles[direction],
25
- {
26
- borderRadius,
27
- backgroundColor,
28
- borderColor,
29
- borderWidth,
30
- padding
31
- }
32
- ]
9
+ const selectContainerStyles = ({ offset }) => ({
10
+ marginTop: offset ? -offset : 0
11
+ })
33
12
 
34
13
  /**
35
14
  * Button within a Tabs component showing users that content is available to the left or
@@ -40,41 +19,25 @@ const selectButtonStyles = (
40
19
  * @TODO when IconButton is complete and stable revisit this and update interaction state styles.
41
20
  */
42
21
  const HorizontalScrollButton = forwardRef(
43
- ({ direction = 'next', icon, onPress, offset, variant, tokens, copy }, ref) => {
44
- const getTokens = useThemeTokensCallback('HorizontalScrollButton', tokens, variant)
45
- const resolveButtonTokens = (pressableState) =>
46
- resolvePressableTokens(getTokens, pressableState)
47
- const getPressableStyle = (pressableState) => [
48
- selectButtonStyles(resolveButtonTokens(pressableState), direction),
49
- { marginTop: -1 * (offset || 0) }
50
- ]
51
-
22
+ ({ direction = 'next', icon, offset, onPress, variant, tokens, copy }, ref) => {
23
+ const themeTokens = useThemeTokens('HorizontalScrollButton', tokens, variant)
52
24
  const getCopy = useCopy({ dictionary, copy })
53
25
  const label = direction === 'previous' ? getCopy('previousText') : getCopy('nextText')
54
26
 
55
27
  return (
56
- <Pressable
57
- ref={ref}
58
- style={getPressableStyle}
59
- onPress={onPress}
60
- accessibilityLabel={label}
61
- accessibilityRole="button"
62
- // For keyboard-tab or screenreader-swipe navigation, users can just go through all items
63
- {...a11yProps.nonFocusableProps}
28
+ <View
29
+ style={[staticStyles.absolute, staticStyles[direction], selectContainerStyles({ offset })]}
64
30
  >
65
- {(pressableState) => {
66
- const iconTokens = selectTokens('Icon', resolveButtonTokens(pressableState), 'icon')
67
- return icon ? (
68
- <Icon icon={icon} tokens={iconTokens} />
69
- ) : (
70
- <Typography
71
- tokens={{ fontSize: iconTokens.size, lineHeight: 1, color: iconTokens.color }}
72
- >
73
- {direction === 'next' ? '→' : '←'}
74
- </Typography>
75
- )
76
- }}
77
- </Pressable>
31
+ <IconButton
32
+ accessibilityLabel={label}
33
+ icon={icon}
34
+ onPress={onPress}
35
+ ref={ref}
36
+ tokens={themeTokens}
37
+ variant={variant}
38
+ {...a11yProps.nonFocusableProps}
39
+ />
40
+ </View>
78
41
  )
79
42
  }
80
43
  )
@@ -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
@@ -16,10 +16,11 @@ import {
16
16
  selectSystemProps,
17
17
  useCopy,
18
18
  variantProp,
19
- viewProps
19
+ viewProps,
20
+ selectTokens
20
21
  } from '../utils'
21
22
  import { useViewport } from '../ViewportProvider'
22
- import ButtonBase from '../Button/ButtonBase'
23
+ import IconButton from '../IconButton'
23
24
  import dictionary from './dictionary'
24
25
 
25
26
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
@@ -70,11 +71,6 @@ const selectCloseButtonContainerStyles = ({ paddingRight, paddingTop }) => ({
70
71
  paddingTop
71
72
  })
72
73
 
73
- const selectCloseIconProps = ({ closeIconSize, closeIconColor }) => ({
74
- size: closeIconSize,
75
- color: closeIconColor
76
- })
77
-
78
74
  /**
79
75
  * A modal window is a secondary window that opens on top of the main one.
80
76
  * Users have to interact with it before they can carry out their task and return to the main window.
@@ -90,7 +86,7 @@ const selectCloseIconProps = ({ closeIconSize, closeIconColor }) => ({
90
86
  * - Don’t use modals consecutively
91
87
  */
92
88
  const Modal = forwardRef(
93
- ({ children, isOpen, onClose, maxWidth, tokens, variant, copy, ...rest }, ref) => {
89
+ ({ children, isOpen, onClose, maxWidth, tokens, variant, copy, closeButton, ...rest }, ref) => {
94
90
  const viewport = useViewport()
95
91
  const themeTokens = useThemeTokens('Modal', tokens, variant, { viewport, maxWidth })
96
92
 
@@ -107,11 +103,14 @@ const Modal = forwardRef(
107
103
  if (event.key === 'Escape') onClose()
108
104
  }
109
105
 
106
+ // Show the custom react node passed to `closedButton` or the default close button if `closeButton` is `undefined`.
107
+ // Hide the close button if `closeButton` is `null`.
108
+ const showCloseButton = closeButton !== null
109
+
110
110
  if (!isOpen) {
111
111
  return null
112
112
  }
113
113
 
114
- // TODO: replace the close button with IconButton when implemented (https://github.com/telus/universal-design-system/issues/281)
115
114
  return (
116
115
  <NativeModal transparent {...selectProps(rest)}>
117
116
  <View style={[staticStyles.positioningContainer]}>
@@ -124,23 +123,24 @@ const Modal = forwardRef(
124
123
  style={[staticStyles.modal, selectModalStyles(themeTokens)]}
125
124
  onKeyUp={handleKeyUp}
126
125
  >
127
- <View
128
- style={[
129
- staticStyles.closeButtonContainer,
130
- selectCloseButtonContainerStyles(themeTokens)
131
- ]}
132
- >
133
- <ButtonBase
134
- onPress={handleClose}
135
- accessibilityRole="button"
136
- accessibilityLabel={closeLabel}
126
+ {showCloseButton && (
127
+ <View
128
+ style={[
129
+ staticStyles.closeButtonContainer,
130
+ selectCloseButtonContainerStyles(themeTokens)
131
+ ]}
137
132
  >
138
- {
139
- // TODO: add close button interactive states after IconButton is done
140
- () => <CloseIconComponent {...selectCloseIconProps(themeTokens)} />
141
- }
142
- </ButtonBase>
143
- </View>
133
+ {closeButton || (
134
+ <IconButton
135
+ onPress={handleClose}
136
+ icon={CloseIconComponent}
137
+ accessibilityRole="button"
138
+ accessibilityLabel={closeLabel}
139
+ tokens={selectTokens('IconButton', themeTokens, 'close')}
140
+ />
141
+ )}
142
+ </View>
143
+ )}
144
144
  {children}
145
145
  </View>
146
146
  </View>
@@ -164,7 +164,11 @@ Modal.propTypes = {
164
164
  onClose: PropTypes.func,
165
165
  maxWidth: PropTypes.bool,
166
166
  tokens: getTokensPropType('Modal'),
167
- variant: variantProp.propType
167
+ variant: variantProp.propType,
168
+ /**
169
+ * Pass a react node to override the default close button or pass `null` to hide the close button.
170
+ */
171
+ closeButton: PropTypes.node
168
172
  }
169
173
 
170
174
  export default Modal