@telus-uds/components-base 1.3.1 → 1.6.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 (105) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/.turbo/turbo-lint.log +13 -0
  3. package/CHANGELOG.json +142 -1
  4. package/CHANGELOG.md +51 -2
  5. package/__tests__/FlexGrid/Row.test.jsx +100 -25
  6. package/__tests__/utils/containUniqueFields.test.js +25 -0
  7. package/component-docs.json +50 -15
  8. package/lib/Button/ButtonBase.js +1 -1
  9. package/lib/Button/ButtonGroup.js +20 -12
  10. package/lib/Card/PressableCardBase.js +1 -1
  11. package/lib/Checkbox/Checkbox.js +27 -16
  12. package/lib/Checkbox/CheckboxGroup.js +19 -5
  13. package/lib/ExpandCollapse/Panel.js +10 -10
  14. package/lib/FlexGrid/Col/Col.js +13 -3
  15. package/lib/FlexGrid/Row/Row.js +8 -2
  16. package/lib/HorizontalScroll/HorizontalScroll.js +0 -1
  17. package/lib/HorizontalScroll/HorizontalScrollButton.js +23 -49
  18. package/lib/InputLabel/InputLabel.js +27 -25
  19. package/lib/Link/LinkBase.js +19 -6
  20. package/lib/Modal/Modal.js +18 -18
  21. package/lib/Notification/Notification.js +5 -6
  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/Select/Select.js +2 -3
  27. package/lib/Tags/Tags.js +23 -17
  28. package/lib/TextInput/TextArea.js +2 -2
  29. package/lib/TextInput/TextInput.js +12 -2
  30. package/lib/TextInput/TextInputBase.js +1 -1
  31. package/lib/TextInput/propTypes.js +8 -1
  32. package/lib/ToggleSwitch/ToggleSwitch.js +5 -2
  33. package/lib/ToggleSwitch/ToggleSwitchGroup.js +20 -12
  34. package/lib/utils/containUniqueFields.js +34 -0
  35. package/lib/utils/index.js +10 -1
  36. package/lib/utils/props/handlerProps.js +72 -0
  37. package/lib/utils/props/index.js +14 -0
  38. package/lib/utils/props/inputSupportsProps.js +3 -5
  39. package/lib-module/Button/ButtonBase.js +2 -2
  40. package/lib-module/Button/ButtonGroup.js +15 -6
  41. package/lib-module/Card/PressableCardBase.js +2 -2
  42. package/lib-module/Checkbox/Checkbox.js +28 -17
  43. package/lib-module/Checkbox/CheckboxGroup.js +20 -7
  44. package/lib-module/ExpandCollapse/Panel.js +10 -10
  45. package/lib-module/FlexGrid/Col/Col.js +13 -3
  46. package/lib-module/FlexGrid/Row/Row.js +8 -2
  47. package/lib-module/HorizontalScroll/HorizontalScroll.js +0 -1
  48. package/lib-module/HorizontalScroll/HorizontalScrollButton.js +24 -49
  49. package/lib-module/InputLabel/InputLabel.js +28 -25
  50. package/lib-module/Link/LinkBase.js +19 -6
  51. package/lib-module/Modal/Modal.js +19 -19
  52. package/lib-module/Notification/Notification.js +6 -6
  53. package/lib-module/Radio/Radio.js +24 -13
  54. package/lib-module/Radio/RadioGroup.js +13 -4
  55. package/lib-module/RadioCard/RadioCard.js +2 -2
  56. package/lib-module/RadioCard/RadioCardGroup.js +12 -3
  57. package/lib-module/Select/Select.js +2 -3
  58. package/lib-module/Tags/Tags.js +18 -11
  59. package/lib-module/TextInput/TextArea.js +3 -3
  60. package/lib-module/TextInput/TextInput.js +11 -3
  61. package/lib-module/TextInput/TextInputBase.js +2 -2
  62. package/lib-module/TextInput/propTypes.js +7 -1
  63. package/lib-module/ToggleSwitch/ToggleSwitch.js +6 -3
  64. package/lib-module/ToggleSwitch/ToggleSwitchGroup.js +15 -6
  65. package/lib-module/utils/containUniqueFields.js +26 -0
  66. package/lib-module/utils/index.js +2 -1
  67. package/lib-module/utils/props/handlerProps.js +59 -0
  68. package/lib-module/utils/props/index.js +1 -0
  69. package/lib-module/utils/props/inputSupportsProps.js +3 -5
  70. package/package.json +5 -5
  71. package/src/Button/ButtonBase.jsx +8 -2
  72. package/src/Button/ButtonGroup.jsx +51 -34
  73. package/src/Card/PressableCardBase.jsx +6 -1
  74. package/src/Checkbox/Checkbox.jsx +35 -23
  75. package/src/Checkbox/CheckboxGroup.jsx +52 -22
  76. package/src/ExpandCollapse/Panel.jsx +9 -9
  77. package/src/FlexGrid/Col/Col.jsx +11 -2
  78. package/src/FlexGrid/Row/Row.jsx +8 -2
  79. package/src/HorizontalScroll/HorizontalScroll.jsx +1 -1
  80. package/src/HorizontalScroll/HorizontalScrollButton.jsx +21 -58
  81. package/src/InputLabel/InputLabel.jsx +36 -27
  82. package/src/Link/LinkBase.jsx +20 -4
  83. package/src/Modal/Modal.jsx +30 -26
  84. package/src/Notification/Notification.jsx +7 -4
  85. package/src/Radio/Radio.jsx +26 -14
  86. package/src/Radio/RadioGroup.jsx +39 -21
  87. package/src/RadioCard/RadioCard.jsx +6 -1
  88. package/src/RadioCard/RadioCardGroup.jsx +17 -1
  89. package/src/Select/Select.jsx +2 -2
  90. package/src/Tags/Tags.jsx +23 -9
  91. package/src/TextInput/TextArea.jsx +5 -1
  92. package/src/TextInput/TextInput.jsx +13 -3
  93. package/src/TextInput/TextInputBase.jsx +6 -1
  94. package/src/TextInput/propTypes.js +7 -1
  95. package/src/ToggleSwitch/ToggleSwitch.jsx +11 -2
  96. package/src/ToggleSwitch/ToggleSwitchGroup.jsx +19 -6
  97. package/src/utils/containUniqueFields.js +32 -0
  98. package/src/utils/index.js +1 -0
  99. package/src/utils/props/handlerProps.js +47 -0
  100. package/src/utils/props/index.js +1 -0
  101. package/src/utils/props/inputSupportsProps.js +3 -4
  102. package/stories/InputLabel/InputLabel.stories.jsx +25 -28
  103. package/stories/Modal/Modal.stories.jsx +25 -0
  104. package/stories/Search/Search.stories.jsx +4 -1
  105. package/stories/TextInput/TextInput.stories.jsx +40 -2
@@ -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) => {
@@ -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
@@ -1,5 +1,5 @@
1
1
  import React, { forwardRef, useState } from 'react'
2
- import { StyleSheet, Text, View } from 'react-native'
2
+ import { StyleSheet, View } from 'react-native'
3
3
 
4
4
  import PropTypes from 'prop-types'
5
5
  import { applyTextStyles, useThemeTokens } from '../ThemeProvider'
@@ -9,7 +9,8 @@ import {
9
9
  selectSystemProps,
10
10
  selectTokens,
11
11
  variantProp,
12
- viewProps
12
+ viewProps,
13
+ wrapStringsInText
13
14
  } from '../utils'
14
15
  import ButtonBase from '../Button/ButtonBase'
15
16
  import useCopy from '../utils/useCopy'
@@ -102,8 +103,10 @@ const Notification = forwardRef(
102
103
 
103
104
  const textStyles = selectTextStyles(themeTokens)
104
105
 
105
- const content =
106
- typeof children === 'string' ? <Text style={textStyles}>{children}</Text> : children
106
+ const content = wrapStringsInText(
107
+ typeof children === 'function' ? children({ textStyles }) : children,
108
+ { style: textStyles }
109
+ )
107
110
 
108
111
  const { icon: IconComponent, dismissIcon: DismissIconComponent } = themeTokens
109
112
 
@@ -7,6 +7,7 @@ import RadioButton, { selectRadioButtonTokens } from './RadioButton'
7
7
  import { applyShadowToken, applyTextStyles, useThemeTokensCallback } from '../ThemeProvider'
8
8
  import {
9
9
  a11yProps,
10
+ focusHandlerProps,
10
11
  getTokensPropType,
11
12
  selectSystemProps,
12
13
  useInputValue,
@@ -16,7 +17,11 @@ import {
16
17
  } from '../utils'
17
18
  import StackView from '../StackView'
18
19
 
19
- const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
20
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([
21
+ a11yProps,
22
+ focusHandlerProps,
23
+ viewProps
24
+ ])
20
25
 
21
26
  const selectContainerStyles = ({
22
27
  containerBackgroundColor,
@@ -154,23 +159,29 @@ const Radio = forwardRef(
154
159
  >
155
160
  {({ focused: focus, hovered: hover, pressed }) => {
156
161
  const stateTokens = getTokens({ focus, hover, pressed })
162
+ const labelStyles = selectLabelStyles(stateTokens)
163
+ const alignWithLabel = label
164
+ ? [staticStyles.alignWithLabel, { height: labelStyles.lineHeight }]
165
+ : null
157
166
 
158
167
  return (
159
168
  <StackView space={0}>
160
169
  <View style={[staticStyles.container, selectContainerStyles(stateTokens)]}>
161
- <RadioButton
162
- tokens={selectRadioButtonTokens(stateTokens)}
163
- isControlled={isControlled}
164
- isChecked={isChecked}
165
- inactive={inactive}
166
- defaultChecked={defaultChecked}
167
- inputId={inputId}
168
- handleChange={handleChange}
169
- name={inputName}
170
- value={value}
171
- />
170
+ <View style={alignWithLabel}>
171
+ <RadioButton
172
+ tokens={selectRadioButtonTokens(stateTokens)}
173
+ isControlled={isControlled}
174
+ isChecked={isChecked}
175
+ inactive={inactive}
176
+ defaultChecked={defaultChecked}
177
+ inputId={inputId}
178
+ handleChange={handleChange}
179
+ name={inputName}
180
+ value={value}
181
+ />
182
+ </View>
172
183
  {Boolean(label) && (
173
- <Text style={selectLabelStyles(stateTokens)}>
184
+ <Text style={labelStyles}>
174
185
  <RadioLabel forId={inputId}>{label}</RadioLabel>
175
186
  </Text>
176
187
  )}
@@ -242,5 +253,6 @@ Radio.propTypes = {
242
253
  export default Radio
243
254
 
244
255
  const staticStyles = StyleSheet.create({
245
- container: { flexDirection: 'row', alignItems: 'center' }
256
+ container: { flexDirection: 'row', alignItems: 'center' },
257
+ alignWithLabel: { alignSelf: 'flex-start', justifyContent: 'center' }
246
258
  })
@@ -6,6 +6,8 @@ import { useViewport } from '../ViewportProvider'
6
6
  import { useThemeTokens } from '../ThemeProvider'
7
7
  import {
8
8
  a11yProps,
9
+ containUniqueFields,
10
+ focusHandlerProps,
9
11
  getTokensPropType,
10
12
  selectSystemProps,
11
13
  useInputValue,
@@ -17,6 +19,11 @@ import Radio from './Radio'
17
19
  import Fieldset from '../Fieldset'
18
20
 
19
21
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
22
+ const [selectItemProps, selectedItemPropTypes] = selectSystemProps([
23
+ a11yProps,
24
+ focusHandlerProps,
25
+ viewProps
26
+ ])
20
27
 
21
28
  /**
22
29
  * A group of Radios that behave as a radio group. Use when users select a single choice from mutually
@@ -100,28 +107,38 @@ const RadioGroup = forwardRef(
100
107
  onChange,
101
108
  readOnly: readOnly || inactive
102
109
  })
103
- const radios = items.map(({ label, id, onChange: itemOnChange, ref: itemRef }, index) => {
104
- const radioId = id || `Radio[${index}]`
105
- const isChecked = currentValue === radioId
106
- const handleChange = (newCheckedState, event) => {
107
- if (typeof itemOnChange === 'function') itemOnChange(newCheckedState, event)
108
- if (newCheckedState) setValue(radioId, event)
110
+
111
+ const uniqueFields = ['id', 'label']
112
+ if (!containUniqueFields(items, uniqueFields)) {
113
+ throw new Error(`RadioGroup items must have unique ${uniqueFields.join(', ')}`)
114
+ }
115
+
116
+ const radios = items.map(
117
+ ({ label, id, onChange: itemOnChange, ref: itemRef, ...itemRest }, index) => {
118
+ const radioId = id || `Radio[${index}]`
119
+ const isChecked = currentValue === radioId
120
+ const handleChange = (newCheckedState, event) => {
121
+ if (typeof itemOnChange === 'function') itemOnChange(newCheckedState, event)
122
+ if (newCheckedState) setValue(radioId, event)
123
+ }
124
+
125
+ return (
126
+ <Radio
127
+ ref={itemRef}
128
+ key={radioId}
129
+ id={radioId}
130
+ checked={isChecked}
131
+ onChange={handleChange}
132
+ inactive={inactive}
133
+ label={label}
134
+ name={inputGroupName}
135
+ tokens={radioTokens}
136
+ variant={variant}
137
+ {...selectItemProps(itemRest)}
138
+ />
139
+ )
109
140
  }
110
- return (
111
- <Radio
112
- ref={itemRef}
113
- key={radioId}
114
- id={radioId}
115
- checked={isChecked}
116
- onChange={handleChange}
117
- inactive={inactive}
118
- label={label}
119
- name={inputGroupName}
120
- tokens={radioTokens}
121
- variant={variant}
122
- />
123
- )
124
- })
141
+ )
125
142
 
126
143
  return (
127
144
  <Fieldset
@@ -163,6 +180,7 @@ RadioGroup.propTypes = {
163
180
  */
164
181
  items: PropTypes.arrayOf(
165
182
  PropTypes.exact({
183
+ ...selectedItemPropTypes,
166
184
  label: PropTypes.string,
167
185
  id: PropTypes.string,
168
186
  onChange: PropTypes.func,
@@ -5,6 +5,7 @@ import { StyleSheet, Text, View } from 'react-native'
5
5
  import { useThemeTokensCallback, applyTextStyles } from '../ThemeProvider'
6
6
  import {
7
7
  a11yProps,
8
+ focusHandlerProps,
8
9
  getTokensPropType,
9
10
  selectSystemProps,
10
11
  selectTokens,
@@ -17,7 +18,11 @@ import { PressableCardBase, selectPressableCardTokens } from '../Card'
17
18
  import StackView from '../StackView'
18
19
  import RadioButton, { selectRadioButtonTokens } from '../Radio/RadioButton'
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
  /**
23
28
  * A Card that behaves like a radio button. Use when users select a single choice from mutually exclusive options