@telus-uds/components-base 3.28.1 → 3.29.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 (54) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/lib/cjs/Autocomplete/Autocomplete.js +86 -32
  3. package/lib/cjs/Autocomplete/constants.js +2 -1
  4. package/lib/cjs/Card/CardBase.js +12 -0
  5. package/lib/cjs/Carousel/Carousel.js +1 -2
  6. package/lib/cjs/ColourToggle/ColourBubble.js +17 -3
  7. package/lib/cjs/ColourToggle/ColourToggle.js +8 -2
  8. package/lib/cjs/ExpandCollapse/Control.js +17 -3
  9. package/lib/cjs/ExpandCollapse/Panel.js +6 -0
  10. package/lib/cjs/ExpandCollapseMini/ExpandCollapseMini.js +14 -2
  11. package/lib/cjs/ExpandCollapseMini/ExpandCollapseMiniControl.js +15 -2
  12. package/lib/cjs/Link/ChevronLink.js +1 -0
  13. package/lib/cjs/Link/LinkBase.js +29 -13
  14. package/lib/cjs/Link/MobileIconTextContent.js +156 -0
  15. package/lib/cjs/Listbox/ListboxOverlay.js +7 -1
  16. package/lib/cjs/Listbox/PressableItem.js +2 -2
  17. package/lib/cjs/TabBar/TabBar.js +7 -2
  18. package/lib/cjs/TextInput/TextInputBase.js +2 -2
  19. package/lib/esm/Autocomplete/Autocomplete.js +87 -33
  20. package/lib/esm/Autocomplete/constants.js +1 -0
  21. package/lib/esm/Card/CardBase.js +12 -0
  22. package/lib/esm/Carousel/Carousel.js +1 -2
  23. package/lib/esm/ColourToggle/ColourBubble.js +17 -3
  24. package/lib/esm/ColourToggle/ColourToggle.js +8 -2
  25. package/lib/esm/ExpandCollapse/Control.js +17 -3
  26. package/lib/esm/ExpandCollapse/Panel.js +6 -0
  27. package/lib/esm/ExpandCollapseMini/ExpandCollapseMini.js +14 -2
  28. package/lib/esm/ExpandCollapseMini/ExpandCollapseMiniControl.js +15 -2
  29. package/lib/esm/Link/ChevronLink.js +1 -0
  30. package/lib/esm/Link/LinkBase.js +29 -13
  31. package/lib/esm/Link/MobileIconTextContent.js +147 -0
  32. package/lib/esm/Listbox/ListboxOverlay.js +7 -1
  33. package/lib/esm/Listbox/PressableItem.js +3 -3
  34. package/lib/esm/TabBar/TabBar.js +7 -2
  35. package/lib/esm/TextInput/TextInputBase.js +2 -2
  36. package/lib/package.json +1 -1
  37. package/package.json +1 -1
  38. package/src/Autocomplete/Autocomplete.jsx +142 -77
  39. package/src/Autocomplete/constants.js +1 -0
  40. package/src/Card/CardBase.jsx +12 -0
  41. package/src/Carousel/Carousel.jsx +1 -2
  42. package/src/ColourToggle/ColourBubble.jsx +18 -3
  43. package/src/ColourToggle/ColourToggle.jsx +7 -2
  44. package/src/ExpandCollapse/Control.jsx +24 -4
  45. package/src/ExpandCollapse/Panel.jsx +6 -0
  46. package/src/ExpandCollapseMini/ExpandCollapseMini.jsx +23 -3
  47. package/src/ExpandCollapseMini/ExpandCollapseMiniControl.jsx +14 -2
  48. package/src/Link/ChevronLink.jsx +1 -0
  49. package/src/Link/LinkBase.jsx +47 -20
  50. package/src/Link/MobileIconTextContent.jsx +129 -0
  51. package/src/Listbox/ListboxOverlay.jsx +9 -1
  52. package/src/Listbox/PressableItem.jsx +1 -1
  53. package/src/TabBar/TabBar.jsx +21 -4
  54. package/src/TextInput/TextInputBase.jsx +2 -2
@@ -24,6 +24,7 @@ import Suggestions from './Suggestions'
24
24
  import {
25
25
  DEFAULT_MAX_SUGGESTIONS,
26
26
  DEFAULT_MIN_TO_SUGGESTION,
27
+ DEFAULT_MAX_DROPDOWN_HEIGHT,
27
28
  INPUT_LEFT_PADDING,
28
29
  MIN_LISTBOX_WIDTH
29
30
  } from './constants'
@@ -51,20 +52,44 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([
51
52
  const highlightAllMatches = (str, substring = '', matchIndexes = [], resultsTextColor) => (
52
53
  // Wrapping all in bold
53
54
  <Typography variant={{ bold: false }} tokens={{ color: resultsTextColor }}>
54
- {matchIndexes.reduce(
55
- (acc, matchIndex, index) => [
56
- ...acc,
57
- // Add a piece of the string up to the first occurrence of the substring
58
- index === 0 && (str.slice(0, matchIndex) ?? ''),
59
- // Unbold the occurrence of the substring (while keeping the original casing)
60
- <Typography key={matchIndex} variant={{ bold: true }} tokens={{ color: resultsTextColor }}>
61
- {str.slice(matchIndex, matchIndex + substring.length)}
62
- </Typography>,
63
- // Add the rest of the string until the next occurrence or the end of it
64
- str.slice(matchIndex + substring.length, matchIndexes[index + 1] ?? str.length)
65
- ],
66
- []
67
- )}
55
+ {matchIndexes
56
+ .reduce((acc, matchIndex, index) => {
57
+ const prefix = index === 0 ? str.slice(0, matchIndex) : null
58
+ const match = str.slice(matchIndex, matchIndex + substring.length)
59
+ const suffix = str.slice(
60
+ matchIndex + substring.length,
61
+ matchIndexes[index + 1] ?? str.length
62
+ )
63
+ return [
64
+ ...acc,
65
+ prefix ? (
66
+ <Typography
67
+ key={`pre-${matchIndex}`}
68
+ variant={{ bold: false }}
69
+ tokens={{ color: resultsTextColor }}
70
+ >
71
+ {prefix}
72
+ </Typography>
73
+ ) : null,
74
+ <Typography
75
+ key={matchIndex}
76
+ variant={{ bold: true }}
77
+ tokens={{ color: resultsTextColor }}
78
+ >
79
+ {match}
80
+ </Typography>,
81
+ suffix ? (
82
+ <Typography
83
+ key={`post-${matchIndex}`}
84
+ variant={{ bold: false }}
85
+ tokens={{ color: resultsTextColor }}
86
+ >
87
+ {suffix}
88
+ </Typography>
89
+ ) : null
90
+ ]
91
+ }, [])
92
+ .filter(Boolean)}
68
93
  </Typography>
69
94
  )
70
95
  const highlight = (items = [], text = '', color) =>
@@ -94,12 +119,14 @@ const Autocomplete = React.forwardRef(
94
119
  isLoading = false,
95
120
  items,
96
121
  maxSuggestions = DEFAULT_MAX_SUGGESTIONS,
122
+ maxDropdownHeight = DEFAULT_MAX_DROPDOWN_HEIGHT,
97
123
  minToSuggestion = DEFAULT_MIN_TO_SUGGESTION,
98
124
  noResults,
99
125
  onChange,
100
126
  onClear,
101
127
  onSelect,
102
128
  readOnly,
129
+ showOptionsOnFocus = false,
103
130
  validation,
104
131
  value,
105
132
  helpText = '',
@@ -144,7 +171,7 @@ const Autocomplete = React.forwardRef(
144
171
 
145
172
  const { supportsProps, ...selectedProps } = selectProps(rest)
146
173
  const { hint, label: inputLabel } = supportsProps
147
- const hintExpansionEnabled = isFocused && helpText && !currentValue
174
+ const hintExpansionEnabled = isFocused && !!helpText && !currentValue
148
175
  const {
149
176
  overlaidPosition,
150
177
  sourceRef: inputRef,
@@ -198,9 +225,12 @@ const Autocomplete = React.forwardRef(
198
225
  }
199
226
 
200
227
  const handleChange = (newValue) => {
201
- onChange?.(newValue || '')
228
+ onChange?.(newValue)
202
229
  setCurrentValue(newValue)
203
- setIsExpanded(newValue?.length >= minToSuggestion)
230
+ const shouldExpand =
231
+ newValue?.length >= minToSuggestion ||
232
+ (showOptionsOnFocus && isFocused && newValue?.length === 0)
233
+ setIsExpanded(shouldExpand)
204
234
  if (!isControlled && initialItems !== undefined) {
205
235
  setCurrentItems(
206
236
  initialItems.filter(({ label }) =>
@@ -211,21 +241,24 @@ const Autocomplete = React.forwardRef(
211
241
  }
212
242
  const handleSelect = (selectedId) => {
213
243
  onSelect?.(selectedId)
214
- const {
215
- label: newValue,
216
- nested,
217
- title
218
- } = (isControlled ? items : currentItems)?.find(({ id }) => id === selectedId)
244
+ const selectedItem = (isControlled ? items : currentItems)?.find(
245
+ ({ id }) => id === selectedId
246
+ )
247
+ const { label, nested, title } = selectedItem
248
+
219
249
  if (title) return
220
250
  if (!nested) {
221
251
  setNestedSelectedValue(null)
222
- onChange?.(newValue)
252
+ onChange?.(label)
223
253
  setIsExpanded(false)
254
+ setCurrentValue(label)
224
255
  }
225
- setCurrentValue(newValue)
226
- if (!isControlled && inputRef?.current) inputRef.current.value = newValue
256
+ if (!isControlled && inputRef?.current) inputRef.current.value = label
227
257
 
228
- if (nested) setNestedSelectedValue(newValue)
258
+ if (nested) {
259
+ setNestedSelectedValue(label)
260
+ setCurrentValue(label)
261
+ }
229
262
 
230
263
  inputRef.current.focus()
231
264
  }
@@ -278,17 +311,13 @@ const Autocomplete = React.forwardRef(
278
311
  }, [inputRef])
279
312
 
280
313
  const handleClose = (event) => {
281
- if (
282
- (event.type === 'keydown' && (event.key === 'Escape' || event.key === '27')) ||
283
- (event.type === 'click' && !openOverlayRef?.current?.contains(event.target)) ||
284
- (event.type === 'touchstart' &&
285
- openOverlayRef?.current &&
286
- event.touches[0].target &&
287
- !openOverlayRef?.current?.contains(event.touches[0].target))
288
- ) {
314
+ if (event.type === 'keydown' && (event.key === 'Escape' || event.key === '27')) {
289
315
  setIsExpanded(false)
290
316
  setNestedSelectedValue(null)
291
- } else if (
317
+ return
318
+ }
319
+
320
+ if (
292
321
  event.type === 'keydown' &&
293
322
  (event.key === 'ArrowDown' || event.key === 'Tab') &&
294
323
  isExpanded &&
@@ -297,13 +326,30 @@ const Autocomplete = React.forwardRef(
297
326
  ) {
298
327
  event.preventDefault()
299
328
  targetRef.current.focus()
329
+ return
330
+ }
331
+
332
+ if (event.type === 'click' || event.type === 'touchstart') {
333
+ const clickTarget = event.type === 'click' ? event.target : event.touches[0]?.target
334
+ const isOutsideOverlay =
335
+ openOverlayRef?.current && !openOverlayRef.current.contains(clickTarget)
336
+ const isOutsideInput = inputRef?.current && !inputRef.current.contains(clickTarget)
337
+
338
+ if (isOutsideOverlay && isOutsideInput) {
339
+ setIsExpanded(false)
340
+ setNestedSelectedValue(null)
341
+ }
300
342
  }
301
343
  }
302
- const itemsToShow = currentValue
303
- ? itemsToSuggest(
304
- highlight(isControlled ? items : currentItems, currentValue, resultsTextColor)
305
- )
306
- : []
344
+ // Calculate items to show based on current state
345
+ let itemsToShow = []
346
+ if (currentValue?.length > 0) {
347
+ itemsToShow = itemsToSuggest(
348
+ highlight(isControlled ? items : currentItems, currentValue, resultsTextColor)
349
+ )
350
+ } else if (showOptionsOnFocus && isFocused) {
351
+ itemsToShow = itemsToSuggest(isControlled ? items : currentItems || initialItems)
352
+ }
307
353
  const helpTextToShow = isFocused && !currentValue ? helpText : noResults ?? getCopy('noResults')
308
354
 
309
355
  return (
@@ -337,9 +383,15 @@ const Autocomplete = React.forwardRef(
337
383
  onChange={handleChange}
338
384
  onFocus={() => {
339
385
  setisFocused(true)
386
+ if (showOptionsOnFocus && (!currentValue || currentValue.length === 0)) {
387
+ setIsExpanded(true)
388
+ }
340
389
  }}
341
390
  onBlur={() => {
342
391
  setisFocused(false)
392
+ if (showOptionsOnFocus && (!currentValue || currentValue.length === 0)) {
393
+ setIsExpanded(false)
394
+ }
343
395
  }}
344
396
  onClear={onClear}
345
397
  onKeyPress={handleClose}
@@ -368,45 +420,50 @@ const Autocomplete = React.forwardRef(
368
420
  )
369
421
  }}
370
422
  </InputSupports>
371
- {(isExpanded || hintExpansionEnabled) && isInputVisible && (
372
- <>
373
- <Listbox.Overlay
374
- overlaidPosition={overlaidPosition}
375
- isReady={isReady}
376
- minWidth={fullWidth ? inputWidth : MIN_LISTBOX_WIDTH}
377
- maxWidth={inputWidth}
378
- onLayout={handleMeasure}
379
- tokens={restOfTokens}
380
- ref={openOverlayRef}
381
- >
382
- {isLoading ? (
383
- <Loading label={loadingLabel ?? getCopy('loading')} />
384
- ) : (
385
- <Suggestions
386
- hasResults={getCopy('hasResults')}
387
- id="autocomplete"
388
- items={
389
- nestedSelectedValue
390
- ? itemsToSuggest(highlight(otherItems, nestedSelectedValue, resultsTextColor))
391
- : itemsToShow
392
- }
393
- noResults={helpTextToShow}
394
- onClose={handleClose}
395
- onSelect={handleSelect}
396
- parentRef={inputRef}
397
- ref={targetRef}
423
+ {(isExpanded || hintExpansionEnabled) &&
424
+ isInputVisible &&
425
+ (itemsToShow.length > 0 || isExpanded || hintExpansionEnabled) && (
426
+ <>
427
+ <Listbox.Overlay
428
+ overlaidPosition={overlaidPosition}
429
+ isReady={isReady}
430
+ minWidth={fullWidth ? inputWidth : MIN_LISTBOX_WIDTH}
431
+ maxWidth={inputWidth}
432
+ maxHeight={maxDropdownHeight}
433
+ onLayout={handleMeasure}
434
+ tokens={restOfTokens}
435
+ ref={openOverlayRef}
436
+ >
437
+ {isLoading ? (
438
+ <Loading label={loadingLabel ?? getCopy('loading')} />
439
+ ) : (
440
+ <Suggestions
441
+ hasResults={getCopy('hasResults')}
442
+ id="autocomplete"
443
+ items={
444
+ nestedSelectedValue
445
+ ? itemsToSuggest(
446
+ highlight(otherItems, nestedSelectedValue, resultsTextColor)
447
+ )
448
+ : itemsToShow
449
+ }
450
+ noResults={helpTextToShow}
451
+ onClose={handleClose}
452
+ onSelect={handleSelect}
453
+ parentRef={inputRef}
454
+ ref={targetRef}
455
+ />
456
+ )}
457
+ </Listbox.Overlay>
458
+ {targetRef?.current && (
459
+ <View
460
+ // This catches and shifts focus to other interactive elements.
461
+ onFocus={() => targetRef?.current?.focus()}
462
+ tabIndex={0}
398
463
  />
399
464
  )}
400
- </Listbox.Overlay>
401
- {targetRef?.current && (
402
- <View
403
- // This catches and shifts focus to other interactive elements.
404
- onFocus={() => targetRef?.current?.focus()}
405
- tabIndex={0}
406
- />
407
- )}
408
- </>
409
- )}
465
+ </>
466
+ )}
410
467
  </View>
411
468
  )
412
469
  }
@@ -468,6 +525,10 @@ Autocomplete.propTypes = {
468
525
  * Maximum number of suggestions provided at the same time
469
526
  */
470
527
  maxSuggestions: PropTypes.number,
528
+ /**
529
+ * Maximum height (in pixels) of the dropdown before scrolling is enabled
530
+ */
531
+ maxDropdownHeight: PropTypes.number,
471
532
  /**
472
533
  * Text or JSX to render when no results are available
473
534
  */
@@ -488,6 +549,10 @@ Autocomplete.propTypes = {
488
549
  * Callback function to be called when an item is selected from the list
489
550
  */
490
551
  onSelect: PropTypes.func,
552
+ /**
553
+ * When true, displays all available options when the input receives focus (even without typing)
554
+ */
555
+ showOptionsOnFocus: PropTypes.bool,
491
556
  /**
492
557
  * Input value for controlled usage
493
558
  */
@@ -1,4 +1,5 @@
1
1
  export const DEFAULT_MIN_TO_SUGGESTION = 1
2
2
  export const DEFAULT_MAX_SUGGESTIONS = 5
3
+ export const DEFAULT_MAX_DROPDOWN_HEIGHT = 336 // Approximately 7 items (48px each)
3
4
  export const INPUT_LEFT_PADDING = 16
4
5
  export const MIN_LISTBOX_WIDTH = 288
@@ -59,9 +59,21 @@ const setBackgroundImage = ({
59
59
  case 'left-center':
60
60
  backgroundPosition = 'left center'
61
61
  break
62
+ case 'left-start':
63
+ backgroundPosition = 'left top'
64
+ break
65
+ case 'left-end':
66
+ backgroundPosition = 'left bottom'
67
+ break
62
68
  case 'right-center':
63
69
  backgroundPosition = 'right center'
64
70
  break
71
+ case 'right-start':
72
+ backgroundPosition = 'right top'
73
+ break
74
+ case 'right-end':
75
+ backgroundPosition = 'right bottom'
76
+ break
65
77
  default:
66
78
  backgroundPosition = 'center center'
67
79
  }
@@ -1056,8 +1056,7 @@ const Carousel = React.forwardRef(
1056
1056
  // Related discussion - https://github.com/telus/universal-design-system/issues/1549
1057
1057
  const previousNextIconButtonVariants = {
1058
1058
  size: previousNextIconSize,
1059
- raised: !variant?.inverse && true,
1060
- inverse: variant?.inverse
1059
+ raised: true
1061
1060
  }
1062
1061
 
1063
1062
  const getCopyWithPlaceholders = React.useCallback(
@@ -5,6 +5,7 @@ import { View, Pressable, Platform } from 'react-native'
5
5
  import { resolvePressableTokens } from '../utils/pressability'
6
6
  import { applyShadowToken } from '../ThemeProvider'
7
7
  import { getTokensPropType } from '../utils'
8
+ import Tooltip from '../Tooltip'
8
9
 
9
10
  const selectGeneralBubbleTokens = ({
10
11
  outerBubbleHeight,
@@ -52,14 +53,14 @@ const selectBorderBubbleTokens = ({
52
53
  })
53
54
 
54
55
  const ColourBubble = React.forwardRef(
55
- ({ tokens = {}, id, colourHexCode, colourName, isSelected, onPress }, ref) => {
56
+ ({ tokens = {}, id, colourHexCode, colourName, isSelected, onPress, showTooltip }, ref) => {
56
57
  const defaultTokens = tokens({ selected: isSelected })
57
58
 
58
59
  const resolveColourBubbleTokens = (pressState) => resolvePressableTokens(tokens, pressState, {})
59
60
 
60
61
  const themeTokens = React.useMemo(() => tokens(), [tokens])
61
62
 
62
- return (
63
+ const pressable = (
63
64
  <Pressable
64
65
  style={(state) => [
65
66
  selectGeneralBubbleTokens(resolveColourBubbleTokens(state)),
@@ -76,6 +77,16 @@ const ColourBubble = React.forwardRef(
76
77
  <View style={[selectInnerBubbleTokens(themeTokens), { backgroundColor: colourHexCode }]} />
77
78
  </Pressable>
78
79
  )
80
+
81
+ if (showTooltip) {
82
+ return (
83
+ <Tooltip content={colourName} activateOnHover>
84
+ {pressable}
85
+ </Tooltip>
86
+ )
87
+ }
88
+
89
+ return pressable
79
90
  }
80
91
  )
81
92
  ColourBubble.displayName = 'ColourBubble'
@@ -106,7 +117,11 @@ ColourBubble.propTypes = {
106
117
  * of the color is changed of all currently `items`.
107
118
  * Receives two parameters: item object selected and the event
108
119
  */
109
- onPress: PropTypes.func
120
+ onPress: PropTypes.func,
121
+ /**
122
+ * When true, wraps the bubble in a Tooltip that displays the colourName on hover (web only).
123
+ */
124
+ showTooltip: PropTypes.bool
110
125
  }
111
126
 
112
127
  export default ColourBubble
@@ -11,7 +11,7 @@ import ColourBubble from './ColourBubble'
11
11
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
12
12
 
13
13
  const ColourToggle = React.forwardRef(
14
- ({ tokens, variant, defaultColourId, items, onChange, ...rest }, ref) => {
14
+ ({ tokens, variant, defaultColourId, items, onChange, showTooltips, ...rest }, ref) => {
15
15
  const [currentColourId, setCurrentColourId] = React.useState(defaultColourId)
16
16
  const getTokens = useThemeTokensCallback('ColourToggle', tokens, variant)
17
17
 
@@ -40,6 +40,7 @@ const ColourToggle = React.forwardRef(
40
40
  colourHexCode={colourHexCode}
41
41
  colourName={colourName}
42
42
  onPress={handleChangeColour}
43
+ showTooltip={showTooltips}
43
44
  />
44
45
  )
45
46
  })}
@@ -77,7 +78,11 @@ ColourToggle.propTypes = {
77
78
  /**
78
79
  * If provided, this function is called when the current selection of the color is changed of all currently `items`. Receives two parameters: item object selected and the event
79
80
  */
80
- onChange: PropTypes.func
81
+ onChange: PropTypes.func,
82
+ /**
83
+ * When true, displays each colour's name as a tooltip on hover (web only).
84
+ */
85
+ showTooltips: PropTypes.bool
81
86
  }
82
87
 
83
88
  export default ColourToggle
@@ -54,10 +54,11 @@ function selectIconContainerStyles({ iconGap, iconPaddingTop, iconPosition }) {
54
54
  }
55
55
  }
56
56
 
57
- function selectTextContainerStyles({ textLine }) {
57
+ function selectTextContainerStyles({ textLine, controlAlign }) {
58
58
  return {
59
59
  textDecorationLine: textLine,
60
- flex: 1
60
+ flex: 1,
61
+ ...(controlAlign && { alignItems: controlAlign })
61
62
  }
62
63
  }
63
64
 
@@ -70,7 +71,16 @@ function selectIconTokens(tokens) {
70
71
 
71
72
  const ExpandCollapseControl = React.forwardRef(
72
73
  (
73
- { onPress, isExpanded, children, tokens, accessibilityRole = 'button', variant, ...rest },
74
+ {
75
+ onPress,
76
+ isExpanded,
77
+ children,
78
+ tokens,
79
+ controlAlign,
80
+ accessibilityRole = 'button',
81
+ variant,
82
+ ...rest
83
+ },
74
84
  ref
75
85
  ) => {
76
86
  const getTokens = useThemeTokensCallback('ExpandCollapseControl', tokens, variant)
@@ -110,7 +120,13 @@ const ExpandCollapseControl = React.forwardRef(
110
120
  </View>
111
121
  )}
112
122
  <View
113
- style={[selectTextContainerStyles(themeTokens), staticStyles.bubblePointerEvents]}
123
+ style={[
124
+ selectTextContainerStyles({
125
+ ...themeTokens,
126
+ ...(controlAlign && { controlAlign })
127
+ }),
128
+ staticStyles.bubblePointerEvents
129
+ ]}
114
130
  >
115
131
  {typeof children === 'function'
116
132
  ? children(getControlState(pressableState))
@@ -144,6 +160,10 @@ ExpandCollapseControl.propTypes = {
144
160
  * Whether the linked ExpandCollapsePanel is opened or closed. Allows themes to set `expanded` styles.
145
161
  */
146
162
  isExpanded: PropTypes.bool,
163
+ /**
164
+ * Optional alignment for control content. Overrides token-driven alignment.
165
+ */
166
+ controlAlign: PropTypes.oneOf(['flex-start', 'center', 'flex-end']),
147
167
  /**
148
168
  * Function called when the ExpandCollapse is pressed.
149
169
  */
@@ -90,6 +90,7 @@ const ExpandCollapsePanel = React.forwardRef(
90
90
  onPress,
91
91
  control,
92
92
  controlTokens,
93
+ controlAlign,
93
94
  children,
94
95
  tokens,
95
96
  variant,
@@ -174,6 +175,7 @@ const ExpandCollapsePanel = React.forwardRef(
174
175
  {...selectedProps}
175
176
  isExpanded={isExpanded}
176
177
  tokens={controlTokens}
178
+ controlAlign={controlAlign}
177
179
  variant={variant}
178
180
  onPress={handleControlPress}
179
181
  ref={controlRef}
@@ -284,6 +286,10 @@ ExpandCollapsePanel.propTypes = {
284
286
  * Optional theme token overrides that may be passed to the ExpandCollapseControl element.
285
287
  */
286
288
  controlTokens: getTokensPropType('ExpandCollapseControl'),
289
+ /**
290
+ * Optional alignment for control content.
291
+ */
292
+ controlAlign: PropTypes.oneOf(['flex-start', 'center', 'flex-end']),
287
293
  /**
288
294
  * An optional ref to be attached to the control
289
295
  */
@@ -7,9 +7,24 @@ import ExpandCollapseMiniControl from './ExpandCollapseMiniControl'
7
7
 
8
8
  const [selectContainerProps, selectedContainerPropTypes] = selectSystemProps([contentfulProps])
9
9
 
10
+ const alignMap = {
11
+ start: 'flex-start',
12
+ middle: 'center',
13
+ end: 'flex-end'
14
+ }
15
+
10
16
  const ExpandCollapseMini = React.forwardRef(
11
17
  (
12
- { children, onToggle = () => {}, tokens = {}, nativeID, initialOpen = false, dataSet, ...rest },
18
+ {
19
+ children,
20
+ onToggle = () => {},
21
+ tokens = {},
22
+ nativeID,
23
+ initialOpen = false,
24
+ dataSet,
25
+ align,
26
+ ...rest
27
+ },
13
28
  ref
14
29
  ) => {
15
30
  const expandCollapeMiniPanelId = useUniqueId('ExpandCollapseMiniPanel')
@@ -40,10 +55,11 @@ const ExpandCollapseMini = React.forwardRef(
40
55
  textLine: tokens.textLine ?? 'none',
41
56
  backgroundColor: 'transparent'
42
57
  }}
58
+ controlAlign={align && alignMap[align]}
43
59
  // TODO refactor
44
60
  // eslint-disable-next-line react/no-unstable-nested-components
45
61
  control={(pressableState) => (
46
- <ExpandCollapseMiniControl pressableState={pressableState} {...rest} />
62
+ <ExpandCollapseMiniControl pressableState={pressableState} align={align} {...rest} />
47
63
  )}
48
64
  controlRef={ref}
49
65
  nativeID={nativeID}
@@ -87,7 +103,11 @@ ExpandCollapseMini.propTypes = {
87
103
  /**
88
104
  * The dataSet prop allows to pass data-* attributes element to the component.
89
105
  */
90
- dataSet: PropTypes.object
106
+ dataSet: PropTypes.object,
107
+ /**
108
+ * Controls the horizontal alignment of the trigger label and icon within the panel width.
109
+ */
110
+ align: PropTypes.oneOf(['start', 'middle', 'end'])
91
111
  }
92
112
 
93
113
  export default ExpandCollapseMini
@@ -7,6 +7,12 @@ import { htmlAttrs, viewProps, selectSystemProps } from '../utils'
7
7
 
8
8
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs, viewProps])
9
9
 
10
+ const alignSelfMap = {
11
+ start: 'flex-start',
12
+ middle: 'center',
13
+ end: 'flex-end'
14
+ }
15
+
10
16
  // The ExpandCollapseControl has all the appropriate role, a11y, press handling etc
11
17
  // and a more appropriate press area, defer interaction handling to it.
12
18
  const presentationOnly = {
@@ -24,6 +30,7 @@ const ExpandCollapseMiniControl = React.forwardRef(
24
30
  iconPosition = 'right',
25
31
  tokens,
26
32
  variant = {},
33
+ align,
27
34
  ...rest
28
35
  },
29
36
  ref
@@ -96,7 +103,8 @@ const ExpandCollapseMiniControl = React.forwardRef(
96
103
  ...getTokens(linkState),
97
104
  iconSize,
98
105
  blockFontSize: fontSize,
99
- blockLineHeight: lineHeight
106
+ blockLineHeight: lineHeight,
107
+ ...(align && { alignSelf: alignSelfMap[align] })
100
108
  })}
101
109
  ref={ref}
102
110
  {...presentationOnly}
@@ -132,7 +140,11 @@ ExpandCollapseMiniControl.propTypes = {
132
140
  /**
133
141
  * Optional variant object to override the default theme tokens
134
142
  */
135
- variant: PropTypes.object
143
+ variant: PropTypes.object,
144
+ /**
145
+ * Controls the horizontal alignment of the trigger label and icon
146
+ */
147
+ align: PropTypes.oneOf(['start', 'middle', 'end'])
136
148
  }
137
149
 
138
150
  export default ExpandCollapseMiniControl
@@ -34,6 +34,7 @@ const ChevronLink = React.forwardRef(
34
34
  return (
35
35
  <LinkBase
36
36
  {...otherlinkProps}
37
+ useMeasuredMobileIconLayout
37
38
  iconPosition={direction}
38
39
  tokens={getTokens}
39
40
  dataSet={dataSet}