@telus-uds/components-base 3.22.0 → 3.24.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 (84) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/lib/cjs/Button/Button.js +2 -0
  3. package/lib/cjs/Button/ButtonBase.js +10 -5
  4. package/lib/cjs/Button/ButtonDropdown.js +2 -0
  5. package/lib/cjs/Button/ButtonGroup.js +45 -38
  6. package/lib/cjs/Button/propTypes.js +6 -0
  7. package/lib/cjs/Card/CardBase.js +97 -17
  8. package/lib/cjs/Card/PressableCardBase.js +12 -8
  9. package/lib/cjs/Carousel/Carousel.js +52 -19
  10. package/lib/cjs/Carousel/CarouselItem/CarouselItem.js +23 -3
  11. package/lib/cjs/HorizontalScroll/HorizontalScroll.js +5 -2
  12. package/lib/cjs/Icon/Icon.js +11 -11
  13. package/lib/cjs/Icon/IconText.js +0 -1
  14. package/lib/cjs/Listbox/GroupControl.js +44 -44
  15. package/lib/cjs/Listbox/Listbox.js +63 -20
  16. package/lib/cjs/Listbox/ListboxGroup.js +141 -9
  17. package/lib/cjs/Listbox/ListboxOverlay.js +13 -5
  18. package/lib/cjs/Listbox/PressableItem.js +8 -4
  19. package/lib/cjs/Listbox/SecondLevelHeader.js +201 -0
  20. package/lib/cjs/Listbox/dictionary.js +14 -0
  21. package/lib/cjs/Shortcuts/Shortcuts.js +169 -0
  22. package/lib/cjs/Shortcuts/ShortcutsItem.js +280 -0
  23. package/lib/cjs/Shortcuts/index.js +16 -0
  24. package/lib/cjs/TextInput/TextInputBase.js +5 -1
  25. package/lib/cjs/Tooltip/Tooltip.native.js +2 -0
  26. package/lib/cjs/Validator/Validator.js +171 -135
  27. package/lib/cjs/index.js +15 -0
  28. package/lib/esm/Button/Button.js +2 -0
  29. package/lib/esm/Button/ButtonBase.js +10 -5
  30. package/lib/esm/Button/ButtonDropdown.js +2 -0
  31. package/lib/esm/Button/ButtonGroup.js +44 -39
  32. package/lib/esm/Button/propTypes.js +6 -0
  33. package/lib/esm/Card/CardBase.js +97 -17
  34. package/lib/esm/Card/PressableCardBase.js +10 -8
  35. package/lib/esm/Carousel/Carousel.js +52 -19
  36. package/lib/esm/Carousel/CarouselItem/CarouselItem.js +23 -3
  37. package/lib/esm/HorizontalScroll/HorizontalScroll.js +6 -3
  38. package/lib/esm/Icon/Icon.js +11 -11
  39. package/lib/esm/Icon/IconText.js +0 -1
  40. package/lib/esm/Listbox/GroupControl.js +44 -44
  41. package/lib/esm/Listbox/Listbox.js +64 -21
  42. package/lib/esm/Listbox/ListboxGroup.js +143 -11
  43. package/lib/esm/Listbox/ListboxOverlay.js +13 -5
  44. package/lib/esm/Listbox/PressableItem.js +8 -4
  45. package/lib/esm/Listbox/SecondLevelHeader.js +194 -0
  46. package/lib/esm/Listbox/dictionary.js +8 -0
  47. package/lib/esm/Shortcuts/Shortcuts.js +160 -0
  48. package/lib/esm/Shortcuts/ShortcutsItem.js +273 -0
  49. package/lib/esm/Shortcuts/index.js +3 -0
  50. package/lib/esm/TextInput/TextInputBase.js +5 -1
  51. package/lib/esm/Tooltip/Tooltip.native.js +2 -0
  52. package/lib/esm/Validator/Validator.js +171 -135
  53. package/lib/esm/index.js +1 -0
  54. package/lib/package.json +2 -2
  55. package/package.json +2 -2
  56. package/src/Button/Button.jsx +2 -1
  57. package/src/Button/ButtonBase.jsx +18 -12
  58. package/src/Button/ButtonDropdown.jsx +2 -0
  59. package/src/Button/ButtonGroup.jsx +62 -45
  60. package/src/Button/propTypes.js +6 -0
  61. package/src/Card/CardBase.jsx +113 -14
  62. package/src/Card/PressableCardBase.jsx +17 -5
  63. package/src/Carousel/Carousel.jsx +58 -5
  64. package/src/Carousel/CarouselItem/CarouselItem.jsx +31 -3
  65. package/src/HorizontalScroll/HorizontalScroll.jsx +6 -3
  66. package/src/Icon/Icon.jsx +14 -14
  67. package/src/Icon/IconText.jsx +0 -1
  68. package/src/Listbox/GroupControl.jsx +72 -70
  69. package/src/Listbox/Listbox.jsx +67 -11
  70. package/src/Listbox/ListboxGroup.jsx +160 -27
  71. package/src/Listbox/ListboxOverlay.jsx +23 -5
  72. package/src/Listbox/PressableItem.jsx +8 -4
  73. package/src/Listbox/SecondLevelHeader.jsx +182 -0
  74. package/src/Listbox/dictionary.js +8 -0
  75. package/src/Shortcuts/Shortcuts.jsx +174 -0
  76. package/src/Shortcuts/ShortcutsItem.jsx +297 -0
  77. package/src/Shortcuts/index.js +4 -0
  78. package/src/TextInput/TextInputBase.jsx +5 -1
  79. package/src/Tooltip/Tooltip.native.jsx +2 -1
  80. package/src/Validator/Validator.jsx +180 -159
  81. package/src/index.js +1 -0
  82. package/types/Listbox.d.ts +24 -0
  83. package/types/Shortcuts.d.ts +136 -0
  84. package/types/index.d.ts +12 -0
@@ -44,11 +44,10 @@ const Validator = /*#__PURE__*/_react.default.forwardRef((_ref2, ref) => {
44
44
  supportsProps
45
45
  } = selectProps(rest);
46
46
  const strValidation = supportsProps.validation;
47
- const [, setIndividualCodes] = _react.default.useState({});
48
- const [text, setText] = _react.default.useState(value);
49
47
  const validatorsLength = 6;
50
48
  const prefix = 'code';
51
49
  const sufixValidation = 'Validation';
50
+ const [codes, setCodes] = _react.default.useState(() => Array(validatorsLength).fill(''));
52
51
  const [isHover, setIsHover] = _react.default.useState(false);
53
52
  const handleMouseOver = () => {
54
53
  setIsHover(true);
@@ -59,84 +58,87 @@ const Validator = /*#__PURE__*/_react.default.forwardRef((_ref2, ref) => {
59
58
  const themeTokens = (0, _ThemeProvider.useThemeTokens)('TextInput', tokens, variant, {
60
59
  hover: isHover
61
60
  });
62
- const [codeReferences, singleCodes] = _react.default.useMemo(() => {
63
- const codes = [];
64
- const valueCodes = {};
65
- Array.from({
61
+
62
+ // Create refs for input elements
63
+ const codeReferences = _react.default.useMemo(() => {
64
+ return Array.from({
66
65
  length: validatorsLength
67
- }, (_, i) => {
68
- codes[prefix + i] = /*#__PURE__*/_react.default.createRef();
69
- valueCodes[prefix + i] = '';
70
- valueCodes[prefix + i + sufixValidation] = '';
71
- return null;
66
+ }, () => /*#__PURE__*/_react.default.createRef());
67
+ }, [validatorsLength]);
68
+
69
+ // Keep onChange and mask in refs to avoid re-creating event listeners
70
+ const onChangeRef = _react.default.useRef(onChange);
71
+ const maskRef = _react.default.useRef(mask);
72
+ _react.default.useEffect(() => {
73
+ onChangeRef.current = onChange;
74
+ maskRef.current = mask;
75
+ }, [onChange, mask]);
76
+
77
+ // Update a single code digit
78
+ const updateCode = _react.default.useCallback((index, digit) => {
79
+ setCodes(prevCodes => {
80
+ const newCodes = [...prevCodes];
81
+ newCodes[index] = digit;
82
+ if (onChangeRef.current) {
83
+ const codeString = newCodes.join('');
84
+ const singleCodesObj = {};
85
+ newCodes.forEach((code, i) => {
86
+ singleCodesObj[prefix + i] = code;
87
+ singleCodesObj[prefix + i + sufixValidation] = code ? 'success' : '';
88
+ });
89
+ onChangeRef.current(codeString, singleCodesObj);
90
+ }
91
+ return newCodes;
72
92
  });
73
- return [codes, valueCodes];
74
- }, [validatorsLength, prefix, sufixValidation]);
75
- const handleSingleCodes = _react.default.useCallback((codeId, val, validation) => {
76
- singleCodes[codeId] = val;
77
- singleCodes[codeId + sufixValidation] = validation;
78
- setIndividualCodes(prev => ({
79
- ...prev,
80
- [codeId]: val
81
- }));
82
- }, [singleCodes, sufixValidation]);
83
- const changeDataMasking = _react.default.useCallback(boxElement => {
84
- let charMasking = '';
85
- const element = boxElement;
86
- if (mask && mask.length === 1) {
87
- charMasking = mask;
88
- } else if (mask && mask.length > 1) {
89
- charMasking = mask.substring(0, 1);
90
- }
91
- if (charMasking && element) {
92
- element.value = charMasking;
93
- }
94
- }, [mask]);
95
- const handleChangeCode = _react.default.useCallback(() => {
96
- const code = Array.from({
97
- length: validatorsLength
98
- }, (_, i) => singleCodes[prefix + i] || '').join('');
99
- if (typeof onChange === 'function') {
100
- onChange(code, singleCodes);
101
- }
102
- }, [validatorsLength, singleCodes, prefix, onChange]);
103
- const handleChangeCodeValues = _react.default.useCallback((event, codeId, nextIndex) => {
104
- const codeElement = codeReferences[codeId]?.current;
93
+ }, [prefix, sufixValidation]);
94
+
95
+ // Handle input change
96
+ const handleInputChange = _react.default.useCallback((index, event) => {
105
97
  const val = event.nativeEvent?.value || event.target?.value;
106
98
 
107
- // Only allow numeric characters and limit to single digit
108
- const numericOnly = val.replace(/\D/g, '').substring(0, 1);
109
- if (codeElement && codeElement.value) {
110
- codeElement.value = numericOnly;
111
- }
112
- handleSingleCodes(codeId, numericOnly, numericOnly ? 'success' : '');
113
- handleChangeCode();
114
- if (nextIndex === validatorsLength) {
115
- codeElement?.blur();
116
- changeDataMasking(codeElement);
99
+ // This prevents the infinite loop where setting element.value triggers another input event
100
+ if (maskRef.current && val === maskRef.current.substring(0, 1)) {
117
101
  return;
118
102
  }
119
- if (numericOnly.length > 0) {
120
- const nextElement = codeReferences[prefix + nextIndex]?.current;
103
+ const numericOnly = val.replace(/\D/g, '').substring(0, 1);
104
+
105
+ // Update state
106
+ updateCode(index, numericOnly);
107
+
108
+ // Update DOM element
109
+ const element = codeReferences[index]?.current;
110
+ if (element) {
111
+ if (maskRef.current && numericOnly) {
112
+ element.value = maskRef.current.substring(0, 1);
113
+ } else {
114
+ element.value = numericOnly;
115
+ }
116
+ }
117
+
118
+ // Move to next field if digit entered
119
+ if (numericOnly && index < validatorsLength - 1) {
120
+ const nextElement = codeReferences[index + 1]?.current;
121
121
  nextElement?.focus();
122
- changeDataMasking(codeElement);
122
+ } else if (index === validatorsLength - 1) {
123
+ element?.blur();
123
124
  }
124
- }, [codeReferences, handleSingleCodes, handleChangeCode, validatorsLength, changeDataMasking, prefix]);
125
- const handleKeyPress = (event, currentIndex, previousIndex) => {
125
+ }, [codeReferences, updateCode, validatorsLength]);
126
+
127
+ // Handle backspace
128
+ const handleKeyPress = _react.default.useCallback((index, event) => {
126
129
  if (!(event.keyCode === 8 || event.code === 'Backspace')) {
127
130
  return;
128
131
  }
129
- if (currentIndex > 0) {
130
- const currentElement = codeReferences[prefix + currentIndex]?.current;
131
- const previousElement = codeReferences[prefix + previousIndex]?.current;
132
- if (currentElement && currentElement.value) {
133
- currentElement.value = '';
134
- }
132
+ const currentElement = codeReferences[index]?.current;
133
+ if (currentElement) {
134
+ currentElement.value = '';
135
+ }
136
+ updateCode(index, '');
137
+ if (index > 0) {
138
+ const previousElement = codeReferences[index - 1]?.current;
135
139
  previousElement?.focus();
136
140
  }
137
- handleSingleCodes(prefix + currentIndex, '', '');
138
- handleChangeCode();
139
- };
141
+ }, [codeReferences, updateCode]);
140
142
  const getCodeComponents = () => {
141
143
  return Array.from({
142
144
  length: validatorsLength
@@ -145,11 +147,14 @@ const Validator = /*#__PURE__*/_react.default.forwardRef((_ref2, ref) => {
145
147
  const codeInputProps = {
146
148
  nativeID: codeId,
147
149
  keyboardType: 'numeric',
148
- ref: codeReferences[codeId] ?? null,
149
- validation: strValidation || singleCodes[codeId + sufixValidation],
150
+ ref: codeReferences[i] ?? null,
151
+ validation: strValidation || (codes[i] ? 'success' : ''),
150
152
  tokens: selectCodeTextInputTokens(themeTokens),
153
+ // Only use secureTextEntry in React Native, web handles mask differently
154
+ secureTextEntry: !!(_Platform.default.OS !== 'web' && mask),
155
+ selectTextOnFocus: _Platform.default.OS !== 'web',
151
156
  onFocus: () => {
152
- const element = codeReferences[codeId]?.current;
157
+ const element = codeReferences[i]?.current;
153
158
  if (_Platform.default.OS === 'web' && element?.select) {
154
159
  return element.select() ?? null;
155
160
  }
@@ -158,11 +163,35 @@ const Validator = /*#__PURE__*/_react.default.forwardRef((_ref2, ref) => {
158
163
  }
159
164
  return null;
160
165
  },
161
- onKeyPress: event => handleKeyPress(event, i, i - 1),
166
+ onKeyPress: event => handleKeyPress(i, event),
162
167
  onMouseOver: handleMouseOver,
163
168
  onMouseOut: handleMouseOut,
164
169
  inactive
165
170
  };
171
+
172
+ // For React Native, use onChangeText and maxLength
173
+ if (_Platform.default.OS !== 'web') {
174
+ codeInputProps.maxLength = 1;
175
+ codeInputProps.value = codes[i];
176
+ codeInputProps.onChange = () => {};
177
+ codeInputProps.onChangeText = text => {
178
+ if (text) {
179
+ updateCode(i, text);
180
+ if (i < validatorsLength - 1) {
181
+ setTimeout(() => {
182
+ codeReferences[i + 1]?.current?.focus();
183
+ }, 50);
184
+ }
185
+ } else {
186
+ updateCode(i, '');
187
+ if (i > 0) {
188
+ setTimeout(() => {
189
+ codeReferences[i - 1]?.current?.focus();
190
+ }, 50);
191
+ }
192
+ }
193
+ };
194
+ }
166
195
  if (!codeInputProps.validation) {
167
196
  delete codeInputProps.validation;
168
197
  }
@@ -174,89 +203,96 @@ const Validator = /*#__PURE__*/_react.default.forwardRef((_ref2, ref) => {
174
203
  }, codeId);
175
204
  });
176
205
  };
206
+
207
+ // Sync external value prop to internal state
177
208
  _react.default.useEffect(() => {
178
- if (Number(value).toString() !== 'NaN') {
179
- setText(value);
209
+ if (value && Number(value).toString() !== 'NaN') {
210
+ const digits = value.split('').slice(0, validatorsLength);
211
+ const newCodes = Array(validatorsLength).fill('');
212
+ digits.forEach((digit, i) => {
213
+ if (/\d/.test(digit)) {
214
+ newCodes[i] = digit;
215
+ }
216
+ });
217
+ setCodes(newCodes);
180
218
  }
181
- }, [value]);
219
+ }, [value, validatorsLength]);
220
+
221
+ // Sync codes state to DOM elements
182
222
  _react.default.useEffect(() => {
183
- Array.from({
184
- length: validatorsLength
185
- }, (_, i) => {
186
- const element = codeReferences[prefix + i]?.current;
223
+ codes.forEach((code, i) => {
224
+ const element = codeReferences[i]?.current;
187
225
  if (element && element.value !== undefined) {
188
- if (mask && text[i]) {
189
- element.value = mask;
226
+ if (mask && code) {
227
+ element.value = mask.substring(0, 1);
190
228
  } else {
191
- element.value = text[i] ?? '';
229
+ element.value = code;
192
230
  }
193
231
  }
194
- handleSingleCodes(prefix + i, text[i] ?? '', text[i] ? 'success' : '');
195
- return null;
196
232
  });
197
- }, [text, mask, validatorsLength, prefix, codeReferences, handleSingleCodes]);
233
+ }, [codes, codeReferences, mask]);
234
+
235
+ // Setup event listeners - only runs once on mount
198
236
  _react.default.useEffect(() => {
199
- const handlePasteCode = event => {
237
+ if (_Platform.default.OS !== 'web') {
238
+ return undefined;
239
+ }
240
+ const handlePaste = event => {
200
241
  event.preventDefault();
201
-
202
- // Clear current state first
203
- setText('');
204
-
205
- // Clear all individual input fields and their state
206
- Array.from({
207
- length: validatorsLength
208
- }, (_, i) => {
209
- const element = codeReferences[prefix + i]?.current;
210
- if (element && element.value !== undefined) {
211
- element.value = '';
212
- }
213
- handleSingleCodes(prefix + i, '', '');
214
- return null;
215
- });
216
242
  const clipBoardText = event.clipboardData.getData('text');
217
-
218
- // Validate that input contains only digits and truncate to 6 characters
219
243
  const numericOnly = clipBoardText.replace(/\D/g, '').substring(0, validatorsLength);
220
- if (numericOnly.length > 0) {
221
- setText(numericOnly);
244
+ const newCodes = Array(validatorsLength).fill('');
245
+ numericOnly.split('').forEach((digit, i) => {
246
+ newCodes[i] = digit;
247
+ });
248
+ setCodes(newCodes);
249
+ if (onChangeRef.current) {
250
+ const singleCodesObj = {};
251
+ newCodes.forEach((code, i) => {
252
+ singleCodesObj[prefix + i] = code;
253
+ singleCodesObj[prefix + i + sufixValidation] = code ? 'success' : '';
254
+ });
255
+ onChangeRef.current(numericOnly, singleCodesObj);
222
256
  }
223
257
  };
224
258
  const handleCopy = event => {
225
- const clipBoardText = Array.from({
226
- length: validatorsLength
227
- }, (_, i) => singleCodes[prefix + i] || '').join('');
228
- event.clipboardData.setData('text/plain', clipBoardText);
229
- event.preventDefault();
259
+ setCodes(currentCodes => {
260
+ const clipBoardText = currentCodes.join('');
261
+ event.clipboardData.setData('text/plain', clipBoardText);
262
+ event.preventDefault();
263
+ return currentCodes;
264
+ });
230
265
  };
231
- if (_Platform.default.OS === 'web') {
232
- Array.from({
233
- length: validatorsLength
234
- }, (_, i) => {
235
- const element = codeReferences[prefix + i]?.current;
236
- if (element && typeof element.addEventListener === 'function') {
237
- element.addEventListener('paste', handlePasteCode);
238
- element.addEventListener('copy', handleCopy);
239
- element.addEventListener('input', event => handleChangeCodeValues(event, prefix + i, i + 1));
266
+
267
+ // Add event listeners to each input
268
+ codeReferences.forEach((inputRef, i) => {
269
+ const element = inputRef?.current;
270
+ if (element && typeof element.addEventListener === 'function') {
271
+ element.addEventListener('paste', handlePaste);
272
+ element.addEventListener('copy', handleCopy);
273
+ element.addEventListener('input', event => handleInputChange(i, event));
274
+ }
275
+ });
276
+
277
+ // Cleanup
278
+ return () => {
279
+ codeReferences.forEach(inputRef => {
280
+ const element = inputRef?.current;
281
+ if (element && typeof element.removeEventListener === 'function') {
282
+ element.removeEventListener('paste', handlePaste);
283
+ element.removeEventListener('copy', handleCopy);
284
+ element.removeEventListener('input', event => handleInputChange(event.target.dataset.index, event));
240
285
  }
241
- return null;
242
286
  });
243
- }
244
- return () => {
245
- if (_Platform.default.OS === 'web') {
246
- Array.from({
247
- length: validatorsLength
248
- }, (_, i) => {
249
- const element = codeReferences[prefix + i]?.current;
250
- if (element && typeof element.removeEventListener === 'function') {
251
- element.removeEventListener('paste', handlePasteCode);
252
- element.removeEventListener('copy', handleCopy);
253
- element.removeEventListener('input', event => handleChangeCodeValues(event, prefix + i, i + 1));
254
- }
255
- return null;
256
- });
257
- }
258
287
  };
259
- }, [validatorsLength, prefix, codeReferences, handleChangeCodeValues, handleSingleCodes, singleCodes]);
288
+ /*
289
+ * codeReferences and handleInputChange are intentionally omitted from dependencies
290
+ * because we want event listeners to be registered ONLY ONCE when the component mounts.
291
+ * codeReferences is stable (created with useMemo) and handleInputChange is stable (useCallback).
292
+ * Including them would cause unnecessary re-registration of listeners on each render.
293
+ */
294
+ // eslint-disable-next-line react-hooks/exhaustive-deps
295
+ }, []);
260
296
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_InputSupports.default, {
261
297
  ...supportsProps,
262
298
  feedbackProps: {
package/lib/cjs/index.js CHANGED
@@ -59,6 +59,8 @@ var _exportNames = {
59
59
  Responsive: true,
60
60
  Search: true,
61
61
  Select: true,
62
+ Shortcuts: true,
63
+ ShortcutsItem: true,
62
64
  SideNav: true,
63
65
  Skeleton: true,
64
66
  SkipLink: true,
@@ -432,6 +434,18 @@ Object.defineProperty(exports, "Select", {
432
434
  return _Select.default;
433
435
  }
434
436
  });
437
+ Object.defineProperty(exports, "Shortcuts", {
438
+ enumerable: true,
439
+ get: function () {
440
+ return _Shortcuts.default;
441
+ }
442
+ });
443
+ Object.defineProperty(exports, "ShortcutsItem", {
444
+ enumerable: true,
445
+ get: function () {
446
+ return _Shortcuts.ShortcutsItem;
447
+ }
448
+ });
435
449
  Object.defineProperty(exports, "SideNav", {
436
450
  enumerable: true,
437
451
  get: function () {
@@ -744,6 +758,7 @@ var _RadioCard = _interopRequireWildcard(require("./RadioCard"));
744
758
  var _Responsive = _interopRequireDefault(require("./Responsive"));
745
759
  var _Search = _interopRequireDefault(require("./Search"));
746
760
  var _Select = _interopRequireDefault(require("./Select"));
761
+ var _Shortcuts = _interopRequireWildcard(require("./Shortcuts"));
747
762
  var _SideNav = _interopRequireDefault(require("./SideNav"));
748
763
  var _Skeleton = _interopRequireDefault(require("./Skeleton"));
749
764
  var _SkipLink = _interopRequireDefault(require("./SkipLink"));
@@ -10,6 +10,7 @@ const Button = /*#__PURE__*/React.forwardRef((_ref, ref) => {
10
10
  accessibilityRole = 'button',
11
11
  tokens,
12
12
  variant,
13
+ heightFull = true,
13
14
  ...props
14
15
  } = _ref;
15
16
  const viewport = useViewport();
@@ -27,6 +28,7 @@ const Button = /*#__PURE__*/React.forwardRef((_ref, ref) => {
27
28
  return /*#__PURE__*/_jsx(ButtonBase, {
28
29
  ...props,
29
30
  tokens: getTokens,
31
+ heightFull: heightFull,
30
32
  accessibilityRole: accessibilityRole,
31
33
  ref: ref,
32
34
  viewport: viewport
@@ -33,23 +33,27 @@ const selectFlexAndWidthStyles = _ref2 => {
33
33
  }
34
34
  return styles;
35
35
  };
36
- const selectOuterContainerStyles = _ref3 => {
36
+ const selectOuterContainerStyles = (_ref3, heightFull) => {
37
37
  let {
38
38
  opacity,
39
39
  outerBorderColor,
40
40
  outerBorderWidth,
41
41
  outerBorderGap,
42
42
  borderRadius,
43
- outerBackgroundColor
43
+ outerBackgroundColor,
44
+ alignSelf
44
45
  } = _ref3;
45
46
  return {
47
+ backgroundColor: outerBackgroundColor,
48
+ opacity,
46
49
  ...Platform.select({
47
50
  native: {
51
+ alignSelf: alignSelf !== undefined ? alignSelf : 'flex-start'
52
+ },
53
+ web: heightFull ? {} : {
48
54
  alignSelf: 'flex-start'
49
55
  }
50
56
  }),
51
- backgroundColor: outerBackgroundColor,
52
- opacity,
53
57
  ...applyOuterBorder({
54
58
  outerBorderGap,
55
59
  outerBorderWidth,
@@ -248,6 +252,7 @@ const ButtonBase = /*#__PURE__*/React.forwardRef((_ref12, ref) => {
248
252
  icon,
249
253
  iconPosition = icon ? 'left' : undefined,
250
254
  iconProps,
255
+ heightFull = true,
251
256
  ...rawRest
252
257
  } = _ref12;
253
258
  const {
@@ -307,7 +312,7 @@ const ButtonBase = /*#__PURE__*/React.forwardRef((_ref12, ref) => {
307
312
  }
308
313
  const themeTokens = resolveButtonTokens(pressableState);
309
314
  const flexAndWidthStyles = themeTokens.width === '100%' && themeTokens.flex === 1 ? selectFlexAndWidthStyles(themeTokens) : {};
310
- return [staticStyles.row, selectWebOnlyStyles(inactive, themeTokens, systemProps), selectOuterContainerStyles(themeTokens), ...(Object.keys(flexAndWidthStyles).length > 0 ? [flexAndWidthStyles] : []), selectOuterSizeStyles(themeTokens)];
315
+ return [staticStyles.row, selectWebOnlyStyles(inactive, themeTokens, systemProps), selectOuterContainerStyles(themeTokens, heightFull), ...(Object.keys(flexAndWidthStyles).length > 0 ? [flexAndWidthStyles] : []), selectOuterSizeStyles(themeTokens)];
311
316
  };
312
317
  const dataSetProp = flexAndWidthStylesIds || rawRest.dataSet ? {
313
318
  dataSet: {
@@ -103,6 +103,7 @@ const ButtonDropdown = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
103
103
  accessibilityRole = 'radio',
104
104
  description,
105
105
  singleOption,
106
+ heightFull = true,
106
107
  ...props
107
108
  } = _ref2;
108
109
  const isFullWidth = variant?.width === FULL_WIDTH_STYLE;
@@ -145,6 +146,7 @@ const ButtonDropdown = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
145
146
  ...pressHandlers,
146
147
  onPress: handlePress,
147
148
  tokens: getButtonTokens,
149
+ heightFull: heightFull,
148
150
  inactive: singleOption || inactive,
149
151
  icon: () => null,
150
152
  accessibilityRole: accessibilityRole,
@@ -1,26 +1,18 @@
1
- import React from 'react';
1
+ import React, { useCallback, useMemo } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import ABBPropTypes from 'airbnb-prop-types';
4
4
  import Platform from "react-native-web/dist/exports/Platform";
5
+ import View from "react-native-web/dist/exports/View";
6
+ import StyleSheet from "react-native-web/dist/exports/StyleSheet";
5
7
  import ButtonBase from './ButtonBase';
6
- import { StackWrap } from '../StackView';
7
8
  import Fieldset from '../Fieldset';
8
9
  import { useViewport } from '../ViewportProvider';
9
10
  import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider';
10
- import { a11yProps, containUniqueFields, focusHandlerProps, pressProps, getTokensPropType, selectSystemProps, selectTokens, useMultipleInputValues, variantProp, viewProps } from '../utils';
11
+ import { useSpacingScale, a11yProps, containUniqueFields, focusHandlerProps, pressProps, getTokensPropType, selectSystemProps, useMultipleInputValues, variantProp, viewProps } from '../utils';
11
12
  import { getPressHandlersWithArgs } from '../utils/pressability';
12
13
  import { jsx as _jsx } from "react/jsx-runtime";
13
14
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
14
15
  const [selectItemProps, selectedItemPropTypes] = selectSystemProps([a11yProps, focusHandlerProps, pressProps, viewProps]);
15
- const getStackWrapTokens = variant => {
16
- return Platform.select({
17
- web: {
18
- justifyContent: variant?.width === 'equal' ? 'space-evenly' : 'flex-start',
19
- width: variant?.width === 'equal' ? '100%' : 'auto'
20
- },
21
- default: {}
22
- });
23
- };
24
16
  const ButtonGroup = /*#__PURE__*/React.forwardRef((_ref, ref) => {
25
17
  let {
26
18
  variant,
@@ -53,12 +45,6 @@ const ButtonGroup = /*#__PURE__*/React.forwardRef((_ref, ref) => {
53
45
  const themeTokens = useThemeTokens('ButtonGroup', tokens, variant, {
54
46
  viewport
55
47
  });
56
- const themeStackTokens = selectTokens('StackView', themeTokens);
57
- const variantStackTokens = getStackWrapTokens(variant);
58
- const stackTokens = {
59
- ...themeStackTokens,
60
- ...variantStackTokens
61
- };
62
48
  const {
63
49
  direction,
64
50
  space,
@@ -68,15 +54,34 @@ const ButtonGroup = /*#__PURE__*/React.forwardRef((_ref, ref) => {
68
54
  padding,
69
55
  gap
70
56
  } = themeTokens;
57
+ const isMobileNonContained = Platform.OS !== 'web' && (!variant || variant?.style !== 'contained');
71
58
  const themeButtonTokensCallback = useThemeTokensCallback('ButtonGroupItem', tokens, variant);
72
- const getButtonTokens = state => {
59
+ const gapValue = useSpacingScale(gap || space);
60
+ const getButtonTokens = useCallback(state => {
73
61
  const themeButtonTokens = themeButtonTokensCallback(state);
62
+ const shouldUseTransparentBackground = isMobileNonContained && !state.selected && !state.pressed && !state.hover && !state.focus;
74
63
  return {
75
64
  ...themeButtonTokens,
76
- width: variant?.width === 'equal' ? '100%' : 'auto',
77
- flex: variant?.width === 'equal' ? 1 : undefined
65
+ ...(variant?.width === 'equal' && staticStyles.equalWidth),
66
+ ...(shouldUseTransparentBackground && {
67
+ backgroundColor: 'transparent'
68
+ }),
69
+ alignSelf: themeButtonTokens.width ? 'flex-start' : 'center'
78
70
  };
79
- };
71
+ }, [themeButtonTokensCallback, isMobileNonContained, variant?.width]);
72
+ const fieldsetStyles = useMemo(() => ({
73
+ ...staticStyles.fieldsetBase,
74
+ borderRadius,
75
+ backgroundColor: isMobileNonContained ? 'transparent' : backgroundColor || 'transparent',
76
+ padding,
77
+ width: variant?.width === 'equal' ? '100%' : 'auto'
78
+ }), [borderRadius, backgroundColor, padding, variant?.width, isMobileNonContained]);
79
+ const viewStyles = useMemo(() => ({
80
+ ...staticStyles.viewBase,
81
+ flexDirection: direction === 'column' ? 'column' : 'row',
82
+ gap: gapValue || 0,
83
+ justifyContent: variant?.width === 'equal' ? 'space-evenly' : 'flex-start'
84
+ }), [direction, gapValue, variant?.width]);
80
85
  const {
81
86
  currentValues,
82
87
  toggleOneValue
@@ -113,25 +118,11 @@ const ButtonGroup = /*#__PURE__*/React.forwardRef((_ref, ref) => {
113
118
  inactive: inactive,
114
119
  validation: validation,
115
120
  accessibilityRole: accessibilityRole,
116
- style: {
117
- borderRadius,
118
- backgroundColor,
119
- padding,
120
- ...(Platform.OS === 'web' ? {
121
- gap,
122
- width: variant?.width === 'equal' ? '100%' : 'auto'
123
- } : {
124
- alignSelf: 'flex-start'
125
- })
126
- },
121
+ style: fieldsetStyles,
127
122
  ...selectProps(rest),
128
- children: /*#__PURE__*/_jsx(StackWrap, {
123
+ children: /*#__PURE__*/_jsx(View, {
129
124
  accessibilityRole: innerRole,
130
- space: space,
131
- direction: direction,
132
- tokens: stackTokens,
133
- gap: gap,
134
- ref: ref,
125
+ style: viewStyles,
135
126
  children: items.map((_ref2, index) => {
136
127
  let {
137
128
  label,
@@ -291,4 +282,18 @@ ButtonGroup.propTypes = {
291
282
  */
292
283
  copy: PropTypes.oneOf(['en', 'fr'])
293
284
  };
285
+ const staticStyles = StyleSheet.create({
286
+ fieldsetBase: {
287
+ alignSelf: 'flex-start',
288
+ display: 'inline'
289
+ },
290
+ viewBase: {
291
+ flexWrap: 'wrap',
292
+ alignItems: 'center'
293
+ },
294
+ equalWidth: {
295
+ width: '100%',
296
+ flex: 1
297
+ }
298
+ });
294
299
  export default ButtonGroup;
@@ -6,6 +6,12 @@ import { iconComponentPropTypes } from '../Icon';
6
6
  export const textAndA11yText = ABBPropTypes.childrenOf(PropTypes.oneOfType([ABBPropTypes.elementType(A11yText), PropTypes.string]));
7
7
  const buttonPropTypes = {
8
8
  tokens: getTokensPropType('Button'),
9
+ /**
10
+ * If true, the button will honor the align-items value from its parent flex container.
11
+ * If false, the button will always align to 'flex-start' in a flex container.
12
+ * Currently, `heightFull`'s default behaviour will expand to the full height of its parent's flex container. In an upcoming major release, the default behaviour will be changed to expand based on the content. To maintain the expected behaviour, you must explicitly set `heightFull: true`
13
+ */
14
+ heightFull: PropTypes.bool,
9
15
  /**
10
16
  * If true, prevents the button from being pressed, changes the cursor (on web) and accessibility
11
17
  * attributes to communicate this to the user, and applies `inactive: true` appearances from the theme