@telus-uds/components-base 1.7.1 → 1.8.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 (66) hide show
  1. package/.storybook/main.js +7 -0
  2. package/.turbo/turbo-build.log +3 -3
  3. package/.turbo/turbo-lint.log +3 -13
  4. package/CHANGELOG.json +62 -1
  5. package/CHANGELOG.md +19 -2
  6. package/__fixtures__/Accessible.js +1 -2
  7. package/__fixtures__/Accessible.native.js +1 -2
  8. package/__tests__/FlexGrid/Col.test.jsx +5 -0
  9. package/__tests__/InputLabel/InputLabel.test.jsx +28 -0
  10. package/__tests__/InputLabel/__snapshots__/InputLabel.test.jsx.snap +3 -0
  11. package/__tests__/InputSupports/InputSupports.test.jsx +10 -0
  12. package/component-docs.json +264 -18
  13. package/lib/Button/ButtonGroup.js +118 -45
  14. package/lib/Checkbox/CheckboxGroup.js +3 -3
  15. package/lib/ExpandCollapse/Panel.js +2 -1
  16. package/lib/Fieldset/Fieldset.js +7 -0
  17. package/lib/InputLabel/InputLabel.js +8 -1
  18. package/lib/InputSupports/InputSupports.js +7 -0
  19. package/lib/Notification/Notification.js +1 -1
  20. package/lib/Radio/RadioGroup.js +12 -5
  21. package/lib/RadioCard/RadioCardGroup.js +7 -0
  22. package/lib/Search/Search.js +1 -1
  23. package/lib/Skeleton/Skeleton.js +48 -2
  24. package/lib/ToggleSwitch/ToggleSwitch.js +7 -0
  25. package/lib/ToggleSwitch/ToggleSwitchGroup.js +7 -0
  26. package/lib/Tooltip/Tooltip.js +1 -1
  27. package/lib/utils/animation/useVerticalExpandAnimation.js +26 -13
  28. package/lib/utils/props/inputSupportsProps.js +7 -0
  29. package/lib-module/Button/ButtonGroup.js +117 -45
  30. package/lib-module/Checkbox/CheckboxGroup.js +3 -3
  31. package/lib-module/ExpandCollapse/Panel.js +2 -1
  32. package/lib-module/Fieldset/Fieldset.js +7 -0
  33. package/lib-module/InputLabel/InputLabel.js +8 -1
  34. package/lib-module/InputSupports/InputSupports.js +7 -0
  35. package/lib-module/Notification/Notification.js +1 -1
  36. package/lib-module/Radio/RadioGroup.js +12 -5
  37. package/lib-module/RadioCard/RadioCardGroup.js +7 -0
  38. package/lib-module/Search/Search.js +1 -1
  39. package/lib-module/Skeleton/Skeleton.js +49 -3
  40. package/lib-module/ToggleSwitch/ToggleSwitch.js +7 -0
  41. package/lib-module/ToggleSwitch/ToggleSwitchGroup.js +7 -0
  42. package/lib-module/Tooltip/Tooltip.js +1 -1
  43. package/lib-module/utils/animation/useVerticalExpandAnimation.js +26 -14
  44. package/lib-module/utils/props/inputSupportsProps.js +7 -0
  45. package/package.json +9 -4
  46. package/src/Button/ButtonGroup.jsx +106 -41
  47. package/src/Checkbox/Checkbox.jsx +7 -4
  48. package/src/Checkbox/CheckboxGroup.jsx +3 -3
  49. package/src/ExpandCollapse/Panel.jsx +3 -1
  50. package/src/Fieldset/Fieldset.jsx +6 -0
  51. package/src/InputLabel/InputLabel.jsx +17 -2
  52. package/src/InputSupports/InputSupports.jsx +9 -1
  53. package/src/Notification/Notification.jsx +1 -1
  54. package/src/Radio/Radio.jsx +5 -1
  55. package/src/Radio/RadioGroup.jsx +11 -5
  56. package/src/RadioCard/RadioCard.jsx +5 -1
  57. package/src/RadioCard/RadioCardGroup.jsx +6 -0
  58. package/src/Search/Search.jsx +1 -1
  59. package/src/Skeleton/Skeleton.jsx +56 -3
  60. package/src/ToggleSwitch/ToggleSwitch.jsx +6 -0
  61. package/src/ToggleSwitch/ToggleSwitchGroup.jsx +6 -0
  62. package/src/Tooltip/Tooltip.jsx +1 -1
  63. package/src/utils/animation/useVerticalExpandAnimation.js +25 -12
  64. package/src/utils/props/inputSupportsProps.js +6 -1
  65. package/src/utils/props/tokens.js +21 -19
  66. package/stories/Tabs/Tabs.stories.jsx +4 -3
@@ -236,18 +236,18 @@ CheckboxGroup.propTypes = { ...selectedSystemPropTypes,
236
236
  onChange: _propTypes.default.func,
237
237
 
238
238
  /**
239
- * If true, the radio cards cannot be selected by the user and simply show their current state.
239
+ * If true, the checkboxes cannot be selected by the user and simply show their current state.
240
240
  */
241
241
  readOnly: _propTypes.default.bool,
242
242
 
243
243
  /**
244
- * If true, the checkbox cannot be interacted with, elements are set as `disabled` and if the
244
+ * If true, the checkboxes cannot be interacted with, elements are set as `disabled` and if the
245
245
  * theme supports `inactive` appearances rules, these are applied.
246
246
  */
247
247
  inactive: _propTypes.default.bool,
248
248
 
249
249
  /**
250
- * On Web, this is passed to the `name` attribute of the fieldset and each radio input.
250
+ * On Web, this is passed to the `name` attribute of the fieldset and each checkbox input.
251
251
  */
252
252
  name: _propTypes.default.string
253
253
  };
@@ -97,7 +97,7 @@ const ExpandCollapsePanel = /*#__PURE__*/(0, _react.forwardRef)(({
97
97
  }
98
98
  };
99
99
 
100
- const animatedStyles = (0, _utils.useVerticalExpandAnimation)({
100
+ const [animatedStyles, animatedRef] = (0, _utils.useVerticalExpandAnimation)({
101
101
  containerHeight,
102
102
  isExpanded,
103
103
  tokens: themeTokens
@@ -118,6 +118,7 @@ const ExpandCollapsePanel = /*#__PURE__*/(0, _react.forwardRef)(({
118
118
  onPress: handleControlPress,
119
119
  children: control
120
120
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Animated.default.View, {
121
+ ref: animatedRef,
121
122
  style: [overflowContainerStyles, animatedStyles, staticStyles.itemsContainer],
122
123
  ...focusabilityProps,
123
124
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
@@ -38,6 +38,7 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
38
38
  * Follows the same theming and most of the same props as InputSupports.
39
39
  */
40
40
  const Fieldset = /*#__PURE__*/(0, _react.forwardRef)(({
41
+ copy = 'en',
41
42
  space,
42
43
  feedback,
43
44
  feedbackPosition = 'top',
@@ -64,6 +65,7 @@ const Fieldset = /*#__PURE__*/(0, _react.forwardRef)(({
64
65
  });
65
66
  const legendContent = legend && /*#__PURE__*/(0, _jsxRuntime.jsx)(_Legend.default, {
66
67
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_InputLabel.default, {
68
+ copy: copy,
67
69
  label: legend,
68
70
  hint: hint,
69
71
  hintPosition: hintPosition,
@@ -100,6 +102,11 @@ Fieldset.displayName = 'Fieldset';
100
102
  Fieldset.propTypes = {
101
103
  children: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.node]),
102
104
 
105
+ /**
106
+ * Whether the English or French copy will be used (e.g. for accessibility labels).
107
+ */
108
+ copy: _propTypes.default.oneOf(['en', 'fr']),
109
+
103
110
  /**
104
111
  * The accessibility role of the `<fieldset>` element itself. Other React Native accessibility
105
112
  * props are not supported because there is not an appropriate counterpart for Fieldsets.
@@ -56,6 +56,7 @@ const selectGapStyles = ({
56
56
  });
57
57
 
58
58
  const InputLabel = /*#__PURE__*/(0, _react.forwardRef)(({
59
+ copy = 'en',
59
60
  label,
60
61
  forId,
61
62
  hint,
@@ -89,7 +90,8 @@ const InputLabel = /*#__PURE__*/(0, _react.forwardRef)(({
89
90
  height: themeTokens.fontSize * themeTokens.lineHeight
90
91
  }],
91
92
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Tooltip.default, {
92
- content: tooltip
93
+ content: tooltip,
94
+ copy: copy
93
95
  })
94
96
  })]
95
97
  }), hint && !isHintInline && /*#__PURE__*/(0, _jsxRuntime.jsx)(_Text.default, {
@@ -102,6 +104,11 @@ const InputLabel = /*#__PURE__*/(0, _react.forwardRef)(({
102
104
  InputLabel.displayName = 'InputLabel';
103
105
  InputLabel.propTypes = { ...selectedSystemPropTypes,
104
106
 
107
+ /**
108
+ * Whether the English or French copy will be used (e.g. for accessibility labels).
109
+ */
110
+ copy: _propTypes.default.oneOf(['en', 'fr']),
111
+
105
112
  /**
106
113
  * The input label.
107
114
  */
@@ -29,6 +29,7 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
29
29
 
30
30
  const InputSupports = /*#__PURE__*/(0, _react.forwardRef)(({
31
31
  children,
32
+ copy = 'en',
32
33
  label,
33
34
  hint,
34
35
  hintPosition = 'inline',
@@ -54,6 +55,7 @@ const InputSupports = /*#__PURE__*/(0, _react.forwardRef)(({
54
55
  space: space,
55
56
  ref: ref,
56
57
  children: [label && /*#__PURE__*/(0, _jsxRuntime.jsx)(_InputLabel.default, {
58
+ copy: copy,
57
59
  label: label,
58
60
  hint: hint,
59
61
  hintPosition: hintPosition,
@@ -74,6 +76,11 @@ InputSupports.displayName = 'InputSupports';
74
76
  InputSupports.propTypes = {
75
77
  children: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.node]),
76
78
 
79
+ /**
80
+ * Whether the English or French copy will be used (e.g. for accessibility labels).
81
+ */
82
+ copy: _propTypes.default.oneOf(['en', 'fr']),
83
+
77
84
  /**
78
85
  * The input label.
79
86
  */
@@ -199,7 +199,7 @@ Notification.propTypes = { ...selectedSystemPropTypes,
199
199
  dismissible: _propTypes.default.bool,
200
200
 
201
201
  /**
202
- * Select english or french copy for the accessible label of the dismiss button.
202
+ * Select English or French copy for the accessible label of the dismiss button.
203
203
  */
204
204
  copy: _propTypes.default.oneOfType([_propTypes.default.oneOf(['en', 'fr']), _propTypes.default.shape({
205
205
  dismiss: _propTypes.default.string
@@ -52,7 +52,7 @@ const [selectItemProps, selectedItemPropTypes] = (0, _utils.selectSystemProps)([
52
52
  * ### Uncontrolled version
53
53
  *
54
54
  * If the RadioGroup manages its own state, you can use `initialCheckedId` prop to provide the initial value.
55
- * Whenever the radio card gets toggled, it calls the `onChange` callback with the new value (string).
55
+ * Whenever the radio gets toggled, it calls the `onChange` callback with the new value (string).
56
56
  *
57
57
  * ### Use in forms
58
58
  *
@@ -83,6 +83,7 @@ const [selectItemProps, selectedItemPropTypes] = (0, _utils.selectSystemProps)([
83
83
  */
84
84
 
85
85
  const RadioGroup = /*#__PURE__*/(0, _react.forwardRef)(({
86
+ copy = 'en',
86
87
  tokens,
87
88
  radioTokens,
88
89
  variant,
@@ -151,6 +152,7 @@ const RadioGroup = /*#__PURE__*/(0, _react.forwardRef)(({
151
152
  }, radioId);
152
153
  });
153
154
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Fieldset.default, {
155
+ copy: copy,
154
156
  ref: ref,
155
157
  name: inputGroupName,
156
158
  legend: legend,
@@ -171,6 +173,11 @@ const RadioGroup = /*#__PURE__*/(0, _react.forwardRef)(({
171
173
  RadioGroup.displayName = 'RadioGroup';
172
174
  RadioGroup.propTypes = { ...selectedSystemPropTypes,
173
175
 
176
+ /**
177
+ * Whether the English or French copy will be used (e.g. for accessibility labels).
178
+ */
179
+ copy: _propTypes.default.oneOf(['en', 'fr']),
180
+
174
181
  /**
175
182
  * Optional theme token overrides for the outer RadioGroup component
176
183
  */
@@ -222,12 +229,12 @@ RadioGroup.propTypes = { ...selectedSystemPropTypes,
222
229
  feedback: _propTypes.default.string,
223
230
 
224
231
  /**
225
- * If provided, the radio card with this id is selected on first render.
232
+ * If provided, the radio with this id is selected on first render.
226
233
  */
227
234
  initialCheckedId: _propTypes.default.string,
228
235
 
229
236
  /**
230
- * If not undefined, the radio card with this id is selected (or none is selected if `null`), and the
237
+ * If not undefined, the radio with this id is selected (or none is selected if `null`), and the
231
238
  * element's selection state will be controlled by its parent using the `onChange` function.
232
239
  */
233
240
  checkedId: _propTypes.default.string,
@@ -239,12 +246,12 @@ RadioGroup.propTypes = { ...selectedSystemPropTypes,
239
246
  onChange: _propTypes.default.func,
240
247
 
241
248
  /**
242
- * If true, the radio cards cannot be selected by the user and simply show their current state.
249
+ * If true, the radios cannot be selected by the user and simply show their current state.
243
250
  */
244
251
  readOnly: _propTypes.default.bool,
245
252
 
246
253
  /**
247
- * If true, the radio card cannot be interacted with, elements are set as `disabled` and if the
254
+ * If true, the radios cannot be interacted with, elements are set as `disabled` and if the
248
255
  * theme supports `inactive` appearances rules, these are applied.
249
256
  */
250
257
  inactive: _propTypes.default.bool,
@@ -83,6 +83,7 @@ const [selectItemProps, selectedItemPropTypes] = (0, _utils.selectSystemProps)([
83
83
  */
84
84
 
85
85
  const RadioCardGroup = /*#__PURE__*/(0, _react.forwardRef)(({
86
+ copy = 'en',
86
87
  tokens,
87
88
  radioCardTokens,
88
89
  variant,
@@ -128,6 +129,7 @@ const RadioCardGroup = /*#__PURE__*/(0, _react.forwardRef)(({
128
129
  }
129
130
 
130
131
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Fieldset.default, {
132
+ copy: copy,
131
133
  ref: ref,
132
134
  name: inputGroupName,
133
135
  legend: legend,
@@ -177,6 +179,11 @@ const RadioCardGroup = /*#__PURE__*/(0, _react.forwardRef)(({
177
179
  RadioCardGroup.displayName = 'RadioCardGroup';
178
180
  RadioCardGroup.propTypes = { ...selectedSystemPropTypes,
179
181
 
182
+ /**
183
+ * Whether the English or French copy will be used (e.g. for accessibility labels).
184
+ */
185
+ copy: _propTypes.default.oneOf(['en', 'fr']),
186
+
180
187
  /**
181
188
  * Optional theme token overrides for the outer RadioCardGroup component
182
189
  */
@@ -240,7 +240,7 @@ Search.propTypes = { ...selectedContainerPropTypes,
240
240
  accessibilityLabel: _propTypes.default.string,
241
241
 
242
242
  /**
243
- * Select english or french copy for the accessible labels.
243
+ * Select English or French copy for the accessible labels.
244
244
  * You may also pass in a custom dictionary object.
245
245
  */
246
246
  copy: _propTypes.default.oneOfType([_propTypes.default.oneOf(['en', 'fr']), _propTypes.default.shape({
@@ -68,13 +68,23 @@ const Skeleton = /*#__PURE__*/(0, _react.forwardRef)(({
68
68
  tokens,
69
69
  variant,
70
70
  size,
71
+ sizeIndex = size,
72
+ sizePixels,
71
73
  characters,
72
74
  lines,
73
75
  shape = 'line',
74
76
  ...rest
75
77
  }, ref) => {
76
78
  const themeTokens = (0, _ThemeProvider.useThemeTokens)('Skeleton', tokens, variant);
77
- const skeletonHeight = (0, _utils.useSpacingScale)(size || themeTokens.size);
79
+ const pixels = (0, _utils.useResponsiveProp)(sizePixels);
80
+ const spacingScaleValue = typeof pixels === 'number' ? // Size by an exact number of pixels
81
+ {
82
+ options: {
83
+ size: pixels
84
+ }
85
+ } : // Size by an index on the spacing scale (getting default index from theme if none provided)
86
+ sizeIndex || themeTokens.size;
87
+ const skeletonHeight = (0, _utils.useSpacingScale)(spacingScaleValue);
78
88
  const nativeAnimation = (0, _useSkeletonNativeAnimation.default)();
79
89
 
80
90
  const getAnimationBaseOnPlatform = () => {
@@ -132,9 +142,45 @@ Skeleton.displayName = 'Skeleton';
132
142
  Skeleton.propTypes = { ...selectedSystemPropTypes,
133
143
  tokens: (0, _utils.getTokensPropType)('Skeleton'),
134
144
  variant: _utils.variantProp.propType,
135
- size: _propTypes.default.number,
145
+
146
+ /**
147
+ * Sets the size of Skeleton lines or shape according to the theme's spacing scale. For example, size={1} gives the smallest non-zero theme-defined spacing size.
148
+ *
149
+ * May also accept an object with responsive viewport keys or spacing scale options - see `useSpacingScale` for details.
150
+ */
151
+ sizeIndex: _utils.spacingProps.types.spacingValue,
152
+
153
+ /**
154
+ * @deprecated alias for `sizeIndex`
155
+ */
156
+ size: _utils.spacingProps.types.spacingValue,
157
+
158
+ /**
159
+ * Sets the size of Skeleton lines or shape to an exact number of pixels. Use when it's necessary to exactly match sizes of images or other boxes.
160
+ *
161
+ * Accepts a number or an object with responsive viewport keys, e.g. { xs: 32, lg: 64 } would be 32px at xs, sm and md and 64 at lg and xl viewports.
162
+ */
163
+ sizePixels: _utils.responsiveProps.getTypeOptionallyByViewport(_propTypes.default.number),
164
+
165
+ /**
166
+ * Determines the width of simulated lines of text if the Skeleton's shape is 'line' (the default shape).
167
+ *
168
+ * Only has any affect if shape is line (the default). If unset, takes a default value from the theme.
169
+ */
136
170
  characters: _propTypes.default.number,
171
+
172
+ /**
173
+ * Determines how many Skeleton items are rendered (default 1).
174
+ *
175
+ * Recommended usage is to simulate paragraphs of text when Skeleton's shape is 'line' (the default shape).
176
+ *
177
+ * The amount of spacing between multiple lines is controlled by theme tokens.
178
+ */
137
179
  lines: _propTypes.default.number,
180
+
181
+ /**
182
+ * Determines if the skeleton should resemble lines of text (default), a circle, or a square box with themed rounded corners.
183
+ */
138
184
  shape: _propTypes.default.oneOf(['line', 'circle', 'box'])
139
185
  };
140
186
  var _default = Skeleton;
@@ -107,6 +107,7 @@ const selectLabelTokens = ({
107
107
  });
108
108
 
109
109
  const ToggleSwitch = /*#__PURE__*/(0, _react.forwardRef)(({
110
+ copy = 'en',
110
111
  value,
111
112
  initialValue,
112
113
  onChange,
@@ -143,6 +144,7 @@ const ToggleSwitch = /*#__PURE__*/(0, _react.forwardRef)(({
143
144
  children: [Boolean(label) && /*#__PURE__*/(0, _jsxRuntime.jsx)(_View.default, {
144
145
  style: [selectLabelStyles(themeTokens), staticStyles.containText],
145
146
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_InputLabel.default, {
147
+ copy: copy,
146
148
  forId: inputId,
147
149
  label: label,
148
150
  tokens: selectLabelTokens(themeTokens),
@@ -191,6 +193,11 @@ const ToggleSwitch = /*#__PURE__*/(0, _react.forwardRef)(({
191
193
  });
192
194
  ToggleSwitch.displayName = 'ToggleSwitch';
193
195
  ToggleSwitch.propTypes = { ...selectedSystemPropTypes,
196
+
197
+ /**
198
+ * Whether the English or French copy will be used (e.g. for accessibility labels).
199
+ */
200
+ copy: _propTypes.default.oneOf(['en', 'fr']),
194
201
  tokens: (0, _utils.getTokensPropType)('ToggleSwitch'),
195
202
  variant: _utils.variantProp.propType,
196
203
 
@@ -36,6 +36,7 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
36
36
  const [selectProps, selectedSystemPropTypes] = (0, _utils.selectSystemProps)([_utils.a11yProps, _utils.viewProps]);
37
37
  const [selectItemProps, selectedItemPropTypes] = (0, _utils.selectSystemProps)([_utils.a11yProps, _utils.focusHandlerProps, _utils.viewProps]);
38
38
  const ToggleSwitchGroup = /*#__PURE__*/(0, _react.forwardRef)(({
39
+ copy = 'en',
39
40
  variant,
40
41
  tokens,
41
42
  items = [],
@@ -112,6 +113,7 @@ const ToggleSwitchGroup = /*#__PURE__*/(0, _react.forwardRef)(({
112
113
  ..._utils.a11yProps.getPositionInSet(items.length, index)
113
114
  };
114
115
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_ToggleSwitch.default, {
116
+ copy: copy,
115
117
  id: id,
116
118
  ref: itemRef,
117
119
  onChange: handleChange,
@@ -143,6 +145,11 @@ const ToggleSwitchGroup = /*#__PURE__*/(0, _react.forwardRef)(({
143
145
  });
144
146
  ToggleSwitchGroup.displayName = 'ToggleSwitchGroup';
145
147
  ToggleSwitchGroup.propTypes = { ...selectedSystemPropTypes,
148
+
149
+ /**
150
+ * Whether the English or French copy will be used (e.g. for accessibility labels).
151
+ */
152
+ copy: _propTypes.default.oneOf(['en', 'fr']),
146
153
  tokens: (0, _utils.getTokensPropType)('ToggleSwitchGroup'),
147
154
  variant: _utils.variantProp.propType,
148
155
 
@@ -330,7 +330,7 @@ Tooltip.propTypes = { ...selectedSystemPropTypes,
330
330
  content: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.node]),
331
331
 
332
332
  /**
333
- * Select english or french copy for the accessible label.
333
+ * Select English or French copy for the accessible label.
334
334
  */
335
335
  copy: _propTypes.default.oneOf(['en', 'fr']),
336
336
 
@@ -22,14 +22,25 @@ function useVerticalExpandAnimation({
22
22
  isExpanded,
23
23
  tokens
24
24
  }) {
25
+ const [isAnimating, setIsAnimating] = (0, _react.useState)(false);
25
26
  const expandAnimatedValue = (0, _react.useRef)(new _Animated.default.Value(0)).current;
27
+ const elementRef = (0, _react.useRef)(null);
26
28
  const {
27
29
  expandDuration,
28
30
  collapseDuration
29
- } = tokens;
31
+ } = tokens; // Treat as animating from when expanded state changes, until animation completes
32
+
33
+ (0, _react.useEffect)(() => setIsAnimating(true), [isExpanded]);
30
34
  (0, _react.useEffect)(() => {
35
+ const onComplete = () => !isExpanded && setIsAnimating(false);
36
+
31
37
  if (_Platform.default.OS === 'web') {
32
- return;
38
+ if (!elementRef.current) return () => {}; // React Native Web does not pass `onTransitionEnd` through, must attach manually.
39
+ // https://github.com/necolas/react-native-web/pull/1713
40
+
41
+ const element = elementRef.current;
42
+ element.addEventListener('transitionend', onComplete);
43
+ return () => element.removeEventListener('transitionend', onComplete);
33
44
  }
34
45
 
35
46
  const animationConfig = {
@@ -39,25 +50,27 @@ function useVerticalExpandAnimation({
39
50
  useNativeDriver: false
40
51
  };
41
52
 
42
- _Animated.default.timing(expandAnimatedValue, animationConfig).start();
43
- }, [isExpanded, expandAnimatedValue, containerHeight, expandDuration, collapseDuration]);
44
- let containerStyles; // don't visually collapse the container until we have it measured
53
+ const animation = _Animated.default.timing(expandAnimatedValue, animationConfig);
54
+
55
+ animation.start(onComplete);
56
+ return () => animation.stop();
57
+ }, [isExpanded, expandAnimatedValue, containerHeight, expandDuration, collapseDuration]); // Without `visibility: 'hidden', descendents are focusable on web even when collapsed
58
+
59
+ const containerStyles = !isExpanded && !isAnimating ? {
60
+ visibility: 'hidden'
61
+ } : {}; // don't visually collapse the container until we have it measured
45
62
 
46
63
  if (containerHeight !== null) {
47
64
  if (_Platform.default.OS === 'web') {
48
65
  const transitionDuration = isExpanded ? expandDuration : collapseDuration;
49
- containerStyles = {
50
- transition: `height ${transitionDuration}ms ease-in-out`,
51
- height: isExpanded ? containerHeight : 0
52
- };
66
+ containerStyles.transition = `height ${transitionDuration}ms ease-in-out`;
67
+ containerStyles.height = isExpanded ? containerHeight : 0;
53
68
  } else {
54
- containerStyles = {
55
- height: expandAnimatedValue
56
- };
69
+ containerStyles.height = expandAnimatedValue;
57
70
  }
58
71
  }
59
72
 
60
- return containerStyles;
73
+ return [containerStyles, elementRef];
61
74
  }
62
75
 
63
76
  var _default = useVerticalExpandAnimation;
@@ -11,6 +11,11 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
11
11
 
12
12
  var _default = {
13
13
  types: {
14
+ /**
15
+ * Whether the English or French copy will be used (e.g. for accessibility labels).
16
+ */
17
+ copy: _propTypes.default.oneOf(['en', 'fr']),
18
+
14
19
  /**
15
20
  * The input label.
16
21
  */
@@ -43,6 +48,7 @@ var _default = {
43
48
  validation: _propTypes.default.oneOf(['error', 'success'])
44
49
  },
45
50
  select: ({
51
+ copy,
46
52
  label,
47
53
  hint,
48
54
  hintPosition,
@@ -51,6 +57,7 @@ var _default = {
51
57
  validation
52
58
  }) => ({
53
59
  supportsProps: {
60
+ copy,
54
61
  label,
55
62
  hint,
56
63
  hintPosition,
@@ -4,6 +4,7 @@ import ABBPropTypes from 'airbnb-prop-types';
4
4
  import Platform from "react-native-web/dist/exports/Platform";
5
5
  import ButtonBase from './ButtonBase';
6
6
  import { StackWrap } from '../StackView';
7
+ import Fieldset from '../Fieldset';
7
8
  import { useViewport } from '../ViewportProvider';
8
9
  import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider';
9
10
  import { a11yProps, containUniqueFields, focusHandlerProps, pressProps, getTokensPropType, selectSystemProps, selectTokens, useMultipleInputValues, variantProp, viewProps } from '../utils';
@@ -21,6 +22,13 @@ const ButtonGroup = /*#__PURE__*/forwardRef(({
21
22
  onChange,
22
23
  readOnly = false,
23
24
  inactive = false,
25
+ legend,
26
+ tooltip,
27
+ hint,
28
+ validation,
29
+ feedback,
30
+ name: inputGroupName,
31
+ copy,
24
32
  accessibilityRole = maxValues === 1 ? 'radiogroup' // radiogroup is cross-platform; only web aria has generic groups
25
33
  : Platform.select({
26
34
  web: 'group',
@@ -35,7 +43,8 @@ const ButtonGroup = /*#__PURE__*/forwardRef(({
35
43
  const stackTokens = selectTokens('StackView', themeTokens);
36
44
  const {
37
45
  direction,
38
- space
46
+ space,
47
+ fieldSpace
39
48
  } = themeTokens;
40
49
  const getButtonTokens = useThemeTokensCallback('ButtonGroupItem', tokens, variant);
41
50
  const {
@@ -57,54 +66,71 @@ const ButtonGroup = /*#__PURE__*/forwardRef(({
57
66
 
58
67
  if (!containUniqueFields(items, uniqueFields)) {
59
68
  throw new Error(`ButtonGroup items must have unique ${uniqueFields.join(', ')}`);
60
- }
69
+ } // Some web screenreaders e.g. MacOS Voiceover don't handle radiogroups properly unless radio is direct child of radiogroup
61
70
 
62
- return /*#__PURE__*/_jsx(StackWrap, { ...systemProps,
63
- space: space,
64
- direction: direction,
65
- tokens: stackTokens,
71
+
72
+ const innerRole = Platform.OS === 'web' && accessibilityRole === 'radiogroup' ? accessibilityRole : undefined;
73
+ return /*#__PURE__*/_jsx(Fieldset, { ...systemProps,
66
74
  ref: ref,
67
- children: items.map(({
68
- label,
69
- id = label,
70
- accessibilityLabel,
71
- ref: itemRef,
72
- ...itemRest
73
- }, index) => {
74
- const isSelected = currentValues.includes(id); // Pass an object of relevant component state as first argument for any passed-in press handlers
75
-
76
- const pressHandlers = getPressHandlersWithArgs(rest, [{
77
- id,
75
+ name: inputGroupName,
76
+ legend: legend,
77
+ tooltip: tooltip,
78
+ hint: hint,
79
+ space: fieldSpace,
80
+ feedback: feedback,
81
+ readOnly: readOnly,
82
+ inactive: inactive,
83
+ validation: validation,
84
+ accessibilityRole: accessibilityRole,
85
+ ...selectProps(rest),
86
+ children: /*#__PURE__*/_jsx(StackWrap, {
87
+ accessibilityRole: innerRole,
88
+ space: space,
89
+ direction: direction,
90
+ tokens: stackTokens,
91
+ ref: ref,
92
+ children: items.map(({
78
93
  label,
79
- currentValues
80
- }]);
81
-
82
- const handlePress = event => {
83
- if (pressHandlers.onPress) pressHandlers.onPress(event);
84
- toggleOneValue(id, event);
85
- };
86
-
87
- const itemA11y = {
88
- accessibilityState: {
89
- checked: isSelected
90
- },
91
- accessibilityRole: itemA11yRole,
94
+ id = label,
92
95
  accessibilityLabel,
93
- ...a11yProps.getPositionInSet(items.length, index)
94
- }; // Ensure button is direct child of group as MacOS voiceover only applies "X of Y" to
95
- // "radio" if it's a direct child of "radiogroup", even if aria-posinset etc exists
96
-
97
- return /*#__PURE__*/_jsx(ButtonBase, {
98
96
  ref: itemRef,
99
- ...pressHandlers,
100
- onPress: handlePress,
101
- tokens: getButtonTokens,
102
- selected: isSelected,
103
- inactive: inactive,
104
- ...itemA11y,
105
- ...selectItemProps(itemRest),
106
- children: label
107
- }, id);
97
+ ...itemRest
98
+ }, index) => {
99
+ const isSelected = currentValues.includes(id); // Pass an object of relevant component state as first argument for any passed-in press handlers
100
+
101
+ const pressHandlers = getPressHandlersWithArgs(rest, [{
102
+ id,
103
+ label,
104
+ currentValues
105
+ }]);
106
+
107
+ const handlePress = event => {
108
+ if (pressHandlers.onPress) pressHandlers.onPress(event);
109
+ toggleOneValue(id, event);
110
+ };
111
+
112
+ const itemA11y = {
113
+ accessibilityState: {
114
+ checked: isSelected
115
+ },
116
+ accessibilityRole: itemA11yRole,
117
+ accessibilityLabel,
118
+ ...a11yProps.getPositionInSet(items.length, index)
119
+ }; // Ensure button is direct child of group as MacOS voiceover only applies "X of Y" to
120
+ // "radio" if it's a direct child of "radiogroup", even if aria-posinset etc exists
121
+
122
+ return /*#__PURE__*/_jsx(ButtonBase, {
123
+ ref: itemRef,
124
+ ...pressHandlers,
125
+ onPress: handlePress,
126
+ tokens: getButtonTokens,
127
+ selected: isSelected,
128
+ inactive: inactive,
129
+ ...itemA11y,
130
+ ...selectItemProps(itemRest),
131
+ children: label
132
+ }, id);
133
+ })
108
134
  })
109
135
  });
110
136
  });
@@ -168,6 +194,52 @@ ButtonGroup.propTypes = { ...selectedSystemPropTypes,
168
194
  * managing its own selected state, a default set of selections may be provided.
169
195
  * Changing the `initialValues` does not change the user's selections.
170
196
  */
171
- initialValues: PropTypes.arrayOf(PropTypes.string)
197
+ initialValues: PropTypes.arrayOf(PropTypes.string),
198
+
199
+ /**
200
+ * Main text used to describe this group, used in Fieldset's Legend element.
201
+ */
202
+ legend: PropTypes.string,
203
+
204
+ /**
205
+ * Optional additional text giving more detail to help a user make a choice.
206
+ */
207
+ hint: PropTypes.string,
208
+
209
+ /**
210
+ * Optional tooltip text content to include alongside the legend and hint.
211
+ */
212
+ tooltip: PropTypes.string,
213
+
214
+ /**
215
+ * Current validation status of the group, passed to the feedback element if there is one.
216
+ */
217
+ validation: PropTypes.oneOf(['error', 'success']),
218
+
219
+ /**
220
+ * If provided, a Feedback element is rendered containing this text.
221
+ */
222
+ feedback: PropTypes.string,
223
+
224
+ /**
225
+ * If true, the buttons cannot be selected by the user and simply show their current state.
226
+ */
227
+ readOnly: PropTypes.bool,
228
+
229
+ /**
230
+ * If true, the buttons cannot be interacted with, elements are set as `disabled` and if the
231
+ * theme supports `inactive` appearances rules, these are applied.
232
+ */
233
+ inactive: PropTypes.bool,
234
+
235
+ /**
236
+ * On Web, this is passed to the `name` attribute of the fieldset.
237
+ */
238
+ name: PropTypes.string,
239
+
240
+ /**
241
+ * Sets the language of microcopy in subcomponents (e.g. Tooltip's default accessibility label).
242
+ */
243
+ copy: PropTypes.oneOf(['en', 'fr'])
172
244
  };
173
245
  export default ButtonGroup;