@telus-uds/components-base 3.23.0 → 3.25.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 (73) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/lib/cjs/Button/ButtonGroup.js +9 -2
  3. package/lib/cjs/Card/CardBase.js +97 -17
  4. package/lib/cjs/Card/PressableCardBase.js +12 -8
  5. package/lib/cjs/Carousel/Carousel.js +35 -4
  6. package/lib/cjs/FlexGrid/FlexGrid.js +31 -35
  7. package/lib/cjs/HorizontalScroll/HorizontalScroll.js +5 -2
  8. package/lib/cjs/Icon/Icon.js +3 -0
  9. package/lib/cjs/IconButton/IconButton.js +15 -5
  10. package/lib/cjs/Listbox/GroupControl.js +12 -6
  11. package/lib/cjs/Listbox/Listbox.js +41 -7
  12. package/lib/cjs/Listbox/ListboxGroup.js +139 -8
  13. package/lib/cjs/Listbox/ListboxOverlay.js +10 -5
  14. package/lib/cjs/Listbox/SecondLevelHeader.js +201 -0
  15. package/lib/cjs/Listbox/dictionary.js +14 -0
  16. package/lib/cjs/Shortcuts/Shortcuts.js +169 -0
  17. package/lib/cjs/Shortcuts/ShortcutsItem.js +280 -0
  18. package/lib/cjs/Shortcuts/index.js +16 -0
  19. package/lib/cjs/TextInput/TextInputBase.js +2 -3
  20. package/lib/cjs/Tooltip/Tooltip.native.js +2 -0
  21. package/lib/cjs/index.js +15 -0
  22. package/lib/cjs/utils/index.js +9 -1
  23. package/lib/cjs/utils/resolveContentMaxWidth.js +30 -0
  24. package/lib/esm/Button/ButtonGroup.js +9 -2
  25. package/lib/esm/Card/CardBase.js +97 -17
  26. package/lib/esm/Card/PressableCardBase.js +10 -8
  27. package/lib/esm/Carousel/Carousel.js +37 -6
  28. package/lib/esm/FlexGrid/FlexGrid.js +31 -35
  29. package/lib/esm/HorizontalScroll/HorizontalScroll.js +6 -3
  30. package/lib/esm/Icon/Icon.js +3 -0
  31. package/lib/esm/IconButton/IconButton.js +15 -5
  32. package/lib/esm/Listbox/GroupControl.js +12 -6
  33. package/lib/esm/Listbox/Listbox.js +41 -7
  34. package/lib/esm/Listbox/ListboxGroup.js +141 -10
  35. package/lib/esm/Listbox/ListboxOverlay.js +10 -5
  36. package/lib/esm/Listbox/SecondLevelHeader.js +194 -0
  37. package/lib/esm/Listbox/dictionary.js +8 -0
  38. package/lib/esm/Shortcuts/Shortcuts.js +160 -0
  39. package/lib/esm/Shortcuts/ShortcutsItem.js +273 -0
  40. package/lib/esm/Shortcuts/index.js +3 -0
  41. package/lib/esm/TextInput/TextInputBase.js +2 -3
  42. package/lib/esm/Tooltip/Tooltip.native.js +2 -0
  43. package/lib/esm/index.js +1 -0
  44. package/lib/esm/utils/index.js +2 -1
  45. package/lib/esm/utils/resolveContentMaxWidth.js +24 -0
  46. package/lib/package.json +2 -2
  47. package/package.json +2 -2
  48. package/src/Button/ButtonGroup.jsx +20 -3
  49. package/src/Card/CardBase.jsx +113 -14
  50. package/src/Card/PressableCardBase.jsx +17 -5
  51. package/src/Carousel/Carousel.jsx +38 -6
  52. package/src/FlexGrid/FlexGrid.jsx +30 -39
  53. package/src/HorizontalScroll/HorizontalScroll.jsx +6 -3
  54. package/src/Icon/Icon.jsx +3 -0
  55. package/src/IconButton/IconButton.jsx +12 -5
  56. package/src/Listbox/GroupControl.jsx +41 -33
  57. package/src/Listbox/Listbox.jsx +41 -2
  58. package/src/Listbox/ListboxGroup.jsx +158 -26
  59. package/src/Listbox/ListboxOverlay.jsx +18 -5
  60. package/src/Listbox/SecondLevelHeader.jsx +182 -0
  61. package/src/Listbox/dictionary.js +8 -0
  62. package/src/Shortcuts/Shortcuts.jsx +174 -0
  63. package/src/Shortcuts/ShortcutsItem.jsx +297 -0
  64. package/src/Shortcuts/index.js +4 -0
  65. package/src/TextInput/TextInputBase.jsx +2 -2
  66. package/src/Tooltip/Tooltip.native.jsx +2 -1
  67. package/src/index.js +1 -0
  68. package/src/utils/index.js +1 -0
  69. package/src/utils/resolveContentMaxWidth.js +28 -0
  70. package/types/Listbox.d.ts +24 -0
  71. package/types/Shortcuts.d.ts +136 -0
  72. package/types/Status.d.ts +42 -0
  73. package/types/index.d.ts +15 -0
@@ -6,9 +6,18 @@ import { applyShadowToken } from '../ThemeProvider'
6
6
  import { getTokensPropType, responsiveProps, useResponsiveProp, formatImageSource } from '../utils'
7
7
  import { a11yProps, viewProps, selectSystemProps } from '../utils/props'
8
8
  import backgroundImageStylesMap from './backgroundImageStylesMap'
9
+ import FlexGrid from '../FlexGrid/FlexGrid'
10
+ import FlexGridRow from '../FlexGrid/Row'
11
+ import FlexGridCol from '../FlexGrid/Col'
9
12
 
10
13
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
11
14
 
15
+ const GRID_COLUMNS = 12
16
+
17
+ const isOverlayColor = (color) => {
18
+ return color && typeof color === 'string' && color.startsWith('rgba(')
19
+ }
20
+
12
21
  const setBackgroundImage = ({
13
22
  src,
14
23
  alt,
@@ -22,12 +31,10 @@ const setBackgroundImage = ({
22
31
  const borderWidth = cardStyle?.borderWidth || 0
23
32
  const adjustedBorderRadius = Math.max(0, borderRadius - borderWidth)
24
33
 
25
- // For contain mode with position and align, use CSS background properties for web
26
34
  if (backgroundImageResizeMode === 'contain' && backgroundImagePosition && backgroundImageAlign) {
27
35
  const positionKey = `${backgroundImagePosition}-${backgroundImageAlign}`
28
36
 
29
37
  if (Platform.OS === 'web') {
30
- // Create background position based on position and align
31
38
  let backgroundPosition
32
39
 
33
40
  switch (positionKey) {
@@ -77,7 +84,7 @@ const setBackgroundImage = ({
77
84
  </View>
78
85
  )
79
86
  }
80
- // For React Native, apply positioning styles with full dimensions
87
+
81
88
  const positionStyles = backgroundImageStylesMap[positionKey] || {}
82
89
 
83
90
  return (
@@ -95,7 +102,6 @@ const setBackgroundImage = ({
95
102
  )
96
103
  }
97
104
 
98
- // Use ImageBackground for all other resize modes and React Native
99
105
  return (
100
106
  <ImageBackground
101
107
  source={src}
@@ -121,6 +127,10 @@ export const selectStyles = ({
121
127
  paddingLeft,
122
128
  paddingRight,
123
129
  paddingTop,
130
+ marginTop,
131
+ marginBottom,
132
+ marginLeft,
133
+ marginRight,
124
134
  minWidth,
125
135
  shadow,
126
136
  backgroundGradient,
@@ -128,9 +138,27 @@ export const selectStyles = ({
128
138
  maxHeight,
129
139
  overflowY
130
140
  }) => {
141
+ const hasGradient = (gradient || backgroundGradient) && Platform.OS === 'web'
142
+
143
+ let backgroundImageValue = null
144
+
145
+ if (hasGradient) {
146
+ const gradientObj = gradient || backgroundGradient
147
+ const gradientString = `linear-gradient(${gradientObj.angle}deg, ${gradientObj.stops[0].color}, ${gradientObj.stops[1].color})`
148
+ const shouldApplyOverlay =
149
+ (gradient || (backgroundGradient && backgroundColor && backgroundColor !== 'transparent')) &&
150
+ isOverlayColor(backgroundColor)
151
+
152
+ backgroundImageValue = shouldApplyOverlay
153
+ ? `linear-gradient(${backgroundColor}, ${backgroundColor}), ${gradientString}`
154
+ : gradientString
155
+ }
156
+
157
+ const boxShadowColor = isOverlayColor(backgroundColor) ? backgroundColor : 'white'
158
+
131
159
  return {
132
160
  flex,
133
- backgroundColor,
161
+ backgroundColor: hasGradient ? 'transparent' : backgroundColor,
134
162
  borderColor,
135
163
  borderRadius,
136
164
  borderWidth,
@@ -138,19 +166,23 @@ export const selectStyles = ({
138
166
  paddingLeft,
139
167
  paddingRight,
140
168
  paddingTop,
169
+ marginTop,
170
+ marginBottom,
171
+ marginLeft,
172
+ marginRight,
141
173
  minWidth,
142
174
  ...applyShadowToken(shadow),
143
175
  ...(gradient && Platform.OS === 'web'
144
176
  ? {
145
- backgroundImage: `linear-gradient(${gradient.angle}deg, ${gradient.stops[0].color}, ${gradient.stops[1].color})`,
177
+ backgroundImage: backgroundImageValue,
146
178
  backgroundOrigin: `border-box`,
147
- boxShadow: `inset 0 1000px white`,
179
+ boxShadow: `inset 0 1000px ${boxShadowColor}`,
148
180
  border: `${borderWidth}px solid transparent`
149
181
  }
150
182
  : {}),
151
183
  ...(backgroundGradient && Platform.OS === 'web'
152
184
  ? {
153
- backgroundImage: `linear-gradient(${backgroundGradient.angle}deg, ${backgroundGradient.stops[0].color}, ${backgroundGradient.stops[1].color})`
185
+ backgroundImage: backgroundImageValue
154
186
  }
155
187
  : {}),
156
188
  ...(Platform.OS === 'web' ? { maxHeight, overflowY } : {})
@@ -162,8 +194,8 @@ export const selectStyles = ({
162
194
  * intended to be used in apps or sites directly: build themed components on top of this.
163
195
  */
164
196
  const CardBase = React.forwardRef(
165
- ({ children, tokens, dataSet, backgroundImage, ...rest }, ref) => {
166
- const cardStyle = selectStyles(typeof tokens === 'function' ? tokens() : tokens)
197
+ ({ children, tokens, dataSet, backgroundImage, fullBleedContent, cardState, ...rest }, ref) => {
198
+ const cardStyle = selectStyles(typeof tokens === 'function' ? tokens(cardState) : tokens)
167
199
  const props = selectProps(rest)
168
200
 
169
201
  let content = children
@@ -174,12 +206,16 @@ const CardBase = React.forwardRef(
174
206
  const backgroundImageAlign = useResponsiveProp(align)
175
207
  const imageSourceViewport = formatImageSource(useResponsiveProp(src))
176
208
 
209
+ const {
210
+ content: fullBleedImageContent,
211
+ position: fullBleedContentPosition,
212
+ imgCol
213
+ } = fullBleedContent || {}
214
+ const fullBleedPosition = useResponsiveProp(fullBleedContentPosition, 'bottom')
215
+
177
216
  if (backgroundImage && src) {
178
- // When there's a background image, separate the padding from the container style
179
- // so the image can fill the entire container without padding interference
180
217
  const { paddingTop, paddingBottom, paddingLeft, paddingRight, ...containerStyle } = cardStyle
181
218
 
182
- // Only create padding wrapper if there's actually padding defined
183
219
  const hasPadding = paddingTop || paddingBottom || paddingLeft || paddingRight
184
220
  const paddedContent = hasPadding ? (
185
221
  <View style={{ paddingTop, paddingBottom, paddingLeft, paddingRight }}>{children}</View>
@@ -204,6 +240,53 @@ const CardBase = React.forwardRef(
204
240
  )
205
241
  }
206
242
 
243
+ if (fullBleedContent && fullBleedImageContent) {
244
+ const { paddingTop, paddingBottom, paddingLeft, paddingRight, ...containerStyle } = cardStyle
245
+ const imageColumns = imgCol || { xs: GRID_COLUMNS }
246
+
247
+ const textColumns = {}
248
+ Object.keys(imageColumns).forEach((breakpoint) => {
249
+ textColumns[breakpoint] = GRID_COLUMNS - (imageColumns[breakpoint] || GRID_COLUMNS)
250
+ })
251
+
252
+ const imageFirst = fullBleedPosition === 'top' || fullBleedPosition === 'left'
253
+
254
+ const imageColContent = (
255
+ <FlexGridCol {...imageColumns} style={staticStyles.fullBleedImageCol}>
256
+ {fullBleedImageContent}
257
+ </FlexGridCol>
258
+ )
259
+
260
+ const textColContent = (
261
+ <FlexGridCol
262
+ {...textColumns}
263
+ style={{ paddingTop, paddingBottom, paddingLeft, paddingRight }}
264
+ >
265
+ {children}
266
+ </FlexGridCol>
267
+ )
268
+
269
+ return (
270
+ <View style={containerStyle} dataSet={dataSet} ref={ref} {...props}>
271
+ <FlexGrid>
272
+ <FlexGridRow>
273
+ {imageFirst ? (
274
+ <>
275
+ {imageColContent}
276
+ {textColContent}
277
+ </>
278
+ ) : (
279
+ <>
280
+ {textColContent}
281
+ {imageColContent}
282
+ </>
283
+ )}
284
+ </FlexGridRow>
285
+ </FlexGrid>
286
+ </View>
287
+ )
288
+ }
289
+
207
290
  return (
208
291
  <View style={cardStyle} dataSet={dataSet} ref={ref} {...props}>
209
292
  {content}
@@ -213,6 +296,18 @@ const CardBase = React.forwardRef(
213
296
  )
214
297
  CardBase.displayName = 'CardBase'
215
298
 
299
+ export const fullBleedContentPropTypes = PropTypes.shape({
300
+ content: PropTypes.node.isRequired,
301
+ alt: PropTypes.string,
302
+ position: responsiveProps.getTypeOptionallyByViewport(
303
+ PropTypes.oneOf(['bottom', 'left', 'right', 'top'])
304
+ ),
305
+ align: responsiveProps.getTypeOptionallyByViewport(
306
+ PropTypes.oneOf(['start', 'end', 'center', 'stretch'])
307
+ ),
308
+ imgCol: PropTypes.object
309
+ })
310
+
216
311
  const staticStyles = StyleSheet.create({
217
312
  imageBackground: { width: '100%', height: '100%' },
218
313
  contentOverlay: {
@@ -231,6 +326,9 @@ const staticStyles = StyleSheet.create({
231
326
  position: 'absolute',
232
327
  width: '100%',
233
328
  height: '100%'
329
+ },
330
+ fullBleedImageCol: {
331
+ padding: 0
234
332
  }
235
333
  })
236
334
 
@@ -255,7 +353,8 @@ CardBase.propTypes = {
255
353
  align: responsiveProps.getTypeOptionallyByViewport(
256
354
  PropTypes.oneOf(['start', 'end', 'center', 'stretch'])
257
355
  )
258
- })
356
+ }),
357
+ fullBleedContent: fullBleedContentPropTypes
259
358
  }
260
359
 
261
360
  export default CardBase
@@ -18,7 +18,7 @@ import {
18
18
  viewProps,
19
19
  withLinkRouter
20
20
  } from '../utils'
21
- import CardBase from './CardBase'
21
+ import CardBase, { fullBleedContentPropTypes } from './CardBase'
22
22
 
23
23
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([
24
24
  a11yProps,
@@ -37,6 +37,10 @@ const tokenKeys = [
37
37
  'paddingLeft',
38
38
  'paddingRight',
39
39
  'paddingTop',
40
+ 'marginTop',
41
+ 'marginBottom',
42
+ 'marginLeft',
43
+ 'marginRight',
40
44
  'minWidth',
41
45
  'shadow',
42
46
  'contentAlignItems',
@@ -81,6 +85,7 @@ const PressableCardBase = React.forwardRef(
81
85
  hrefAttrs,
82
86
  dataSet,
83
87
  backgroundImage,
88
+ fullBleedContent,
84
89
  accessibilityRole = href ? 'link' : undefined,
85
90
  ...rawRest
86
91
  },
@@ -159,13 +164,14 @@ const PressableCardBase = React.forwardRef(
159
164
  setFocused(false)
160
165
  setPressed(false)
161
166
  }}
162
- style={{ ...staticStyles.linkContainer, textDecoration: 'none' }}
167
+ style={staticStyles.linkContainer}
163
168
  {...(hrefAttrs || {})}
164
169
  role={accessibilityRole}
165
170
  >
166
171
  <CardBase
167
172
  tokens={getCardTokens({ pressed, focused, hovered })}
168
173
  backgroundImage={backgroundImage}
174
+ fullBleedContent={fullBleedContent}
169
175
  >
170
176
  {typeof children === 'function'
171
177
  ? children(getCardState({ pressed, focused, hovered }))
@@ -188,7 +194,11 @@ const PressableCardBase = React.forwardRef(
188
194
  {...selectProps({ ...rest, accessibilityRole })}
189
195
  >
190
196
  {(pressableState) => (
191
- <CardBase tokens={getCardTokens(pressableState)} backgroundImage={backgroundImage}>
197
+ <CardBase
198
+ tokens={getCardTokens(pressableState)}
199
+ backgroundImage={backgroundImage}
200
+ fullBleedContent={fullBleedContent}
201
+ >
192
202
  {typeof children === 'function' ? children(getCardState(pressableState)) : children}
193
203
  </CardBase>
194
204
  )}
@@ -206,7 +216,8 @@ const staticStyles = StyleSheet.create({
206
216
  flex: 1,
207
217
  display: 'flex',
208
218
  alignItems: 'stretch',
209
- justifyContent: 'flex-start'
219
+ justifyContent: 'flex-start',
220
+ textDecorationLine: 'none'
210
221
  }
211
222
  })
212
223
 
@@ -234,7 +245,8 @@ PressableCardBase.propTypes = {
234
245
  PropTypes.oneOf(['start', 'end', 'center', 'stretch']),
235
246
  PropTypes.object
236
247
  ])
237
- })
248
+ }),
249
+ fullBleedContent: fullBleedContentPropTypes
238
250
  }
239
251
 
240
252
  export default withLinkRouter(PressableCardBase)
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import { View, Animated, PanResponder, StyleSheet, Platform, Dimensions } from 'react-native'
3
3
  import PropTypes from 'prop-types'
4
- import { useThemeTokens } from '../ThemeProvider'
4
+ import { useThemeTokens, useTheme } from '../ThemeProvider'
5
5
  import { useViewport } from '../ViewportProvider'
6
6
  import {
7
7
  getTokensPropType,
@@ -13,7 +13,9 @@ import {
13
13
  viewProps,
14
14
  useCopy,
15
15
  unpackFragment,
16
- isTouchDevice
16
+ isTouchDevice,
17
+ useResponsiveProp,
18
+ resolveContentMaxWidth
17
19
  } from '../utils'
18
20
  import { useA11yInfo } from '../A11yInfoProvider'
19
21
  import { CarouselProvider } from './CarouselContext'
@@ -252,11 +254,18 @@ const selectRootContainerStyles = (enableHero, viewport) => {
252
254
  return {}
253
255
  }
254
256
 
255
- const selectMainContainerStyles = (enableHero, viewport) => {
257
+ const selectMainContainerStyles = (enableHero, viewport, maxWidth) => {
256
258
  if (enableHero && viewport === 'xl' && Platform.OS === 'web') {
257
259
  return {
258
260
  width: '100%',
259
- maxWidth: 1200
261
+ maxWidth: maxWidth || 1200
262
+ }
263
+ }
264
+ if (maxWidth !== null && maxWidth !== undefined) {
265
+ return {
266
+ maxWidth,
267
+ alignSelf: 'center',
268
+ width: '100%'
260
269
  }
261
270
  }
262
271
  return {}
@@ -402,6 +411,7 @@ const Carousel = React.forwardRef(
402
411
  loopDuration = transitionDuration,
403
412
  autoPlay = false,
404
413
  enablePeeking = false,
414
+ contentMaxWidth,
405
415
  ...rest
406
416
  },
407
417
  ref
@@ -409,6 +419,11 @@ const Carousel = React.forwardRef(
409
419
  let childrenArray = unpackFragment(children)
410
420
  const isTransitioningRef = React.useRef(false)
411
421
  const viewport = useViewport()
422
+ const { themeOptions } = useTheme()
423
+
424
+ const contentMaxWidthValue = useResponsiveProp(contentMaxWidth)
425
+ const responsiveWidth = useResponsiveProp(themeOptions?.contentMaxWidth)
426
+ const maxWidth = resolveContentMaxWidth(contentMaxWidthValue, responsiveWidth)
412
427
  const totalItems = getTotalItems(enableDisplayMultipleItemsPerSlide, childrenArray, viewport)
413
428
  const autoPlayFeatureEnabled =
414
429
  autoPlay && slideDuration > 0 && transitionDuration > 0 && totalItems > 1
@@ -1031,7 +1046,7 @@ const Carousel = React.forwardRef(
1031
1046
 
1032
1047
  return (
1033
1048
  <View style={selectRootContainerStyles(enableHero, viewport)}>
1034
- <View style={selectMainContainerStyles(enableHero, viewport)}>
1049
+ <View style={selectMainContainerStyles(enableHero, viewport, maxWidth)}>
1035
1050
  <CarouselProvider
1036
1051
  activeIndex={activeIndex}
1037
1052
  goTo={goTo}
@@ -1463,7 +1478,24 @@ Carousel.propTypes = {
1463
1478
  * If set to `true`, the Carousel will show multiple slides at once
1464
1479
  * - Default value is `false`
1465
1480
  */
1466
- enableDisplayMultipleItemsPerSlide: PropTypes.bool
1481
+ enableDisplayMultipleItemsPerSlide: PropTypes.bool,
1482
+ /**
1483
+ * The maximum width of the content in the Carousel.
1484
+ * This prop accepts responsive values for different viewports. If a number is provided,
1485
+ * it will be the max content width for the desired viewport.
1486
+ * - `xs`: 'max' | 'full' | <number>
1487
+ * - `sm`: 'max' | 'full' | <number>
1488
+ * - `md`: 'max' | 'full' | <number>
1489
+ * - `lg`: 'max' | 'full' | <number>
1490
+ * - `xl`: 'max' | 'full' | <number>
1491
+ */
1492
+ contentMaxWidth: PropTypes.shape({
1493
+ xl: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
1494
+ lg: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
1495
+ md: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
1496
+ sm: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
1497
+ xs: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number])
1498
+ })
1467
1499
  }
1468
1500
 
1469
1501
  Carousel.Item = CarouselItem
@@ -13,6 +13,7 @@ import {
13
13
  createMediaQueryStyles,
14
14
  useResponsiveProp
15
15
  } from '../utils'
16
+ import resolveContentMaxWidth from '../utils/resolveContentMaxWidth'
16
17
  import Row from './Row'
17
18
  import Col from './Col'
18
19
  import GutterContext from './providers/GutterContext'
@@ -22,43 +23,16 @@ import { useViewport } from '../ViewportProvider'
22
23
 
23
24
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
24
25
 
25
- const CONTENT_MAX_WIDTH = 'max'
26
- const CONTENT_FULL_WIDTH = 'full'
27
-
28
- /**
29
- * Resolves the maximum width for content based on the provided value and responsive width.
30
- *
31
- * @param {number|string|null|undefined} contentMinWidthValue - The minimum width value for the content.
32
- * Can be a number, a special string constant (e.g., CONTENT_FULL_WIDTH, CONTENT_MAX_WIDTH), or null/undefined.
33
- * @param {number} responsiveWidth - The responsive width to use when contentMinWidthValue is CONTENT_MAX_WIDTH.
34
- * @returns {number|string|null} The resolved maximum width value, or null if full width is desired.
35
- */
36
- const resolveContentMaxWidth = (contentMinWidthValue, responsiveWidth) => {
37
- if (!contentMinWidthValue || contentMinWidthValue === CONTENT_FULL_WIDTH) {
38
- return null
39
- }
40
-
41
- if (Number.isFinite(contentMinWidthValue)) {
42
- return contentMinWidthValue
43
- }
44
-
45
- if (contentMinWidthValue === CONTENT_MAX_WIDTH) {
46
- return responsiveWidth
47
- }
48
-
49
- return contentMinWidthValue
50
- }
51
-
52
26
  /**
53
- * Calculates the maximum width for a given viewport based on limitWidth and contentMinWidth settings.
27
+ * Calculates the maximum width for a given viewport based on limitWidth and contentMaxWidth settings.
54
28
  *
55
29
  * @param {string} viewportKey - The viewport key ('xs', 'sm', 'md', 'lg', 'xl')
56
30
  * @param {boolean} limitWidth - Whether to limit the width to viewport breakpoints
57
- * @param {any} contentMinWidth - The contentMinWidth prop value
31
+ * @param {any} contentWidthProp - The contentMaxWidth (or contentMinWidth) prop value
58
32
  * @param {number|string|null} maxWidth - The resolved max width value
59
33
  * @returns {number|string|null} The calculated maximum width for the viewport
60
34
  */
61
- const getMaxWidthForViewport = (viewportKey, limitWidth, contentMinWidth, maxWidth) => {
35
+ const getMaxWidthForViewport = (viewportKey, limitWidth, contentWidthProp, maxWidth) => {
62
36
  if (limitWidth) {
63
37
  if (viewportKey === 'xs') {
64
38
  return null
@@ -66,7 +40,7 @@ const getMaxWidthForViewport = (viewportKey, limitWidth, contentMinWidth, maxWid
66
40
  return viewports.map.get(viewportKey)
67
41
  }
68
42
 
69
- if (contentMinWidth) {
43
+ if (contentWidthProp) {
70
44
  return maxWidth
71
45
  }
72
46
 
@@ -92,6 +66,7 @@ const FlexGrid = React.forwardRef(
92
66
  accessibilityRole,
93
67
  children,
94
68
  dataSet,
69
+ contentMaxWidth,
95
70
  contentMinWidth,
96
71
  ...rest
97
72
  },
@@ -107,29 +82,31 @@ const FlexGrid = React.forwardRef(
107
82
  let flexgridStyles
108
83
  let mediaIds
109
84
 
110
- const contentMinWidthValue = useResponsiveProp(contentMinWidth)
85
+ // Support both contentMaxWidth and deprecated contentMinWidth for backwards compatibility
86
+ const contentWidthProp = contentMaxWidth || contentMinWidth
87
+ const contentWidthValue = useResponsiveProp(contentWidthProp)
111
88
  const responsiveWidth = useResponsiveProp(themeOptions?.contentMaxWidth)
112
- const maxWidth = resolveContentMaxWidth(contentMinWidthValue, responsiveWidth)
89
+ const maxWidth = resolveContentMaxWidth(contentWidthValue, responsiveWidth)
113
90
 
114
91
  const stylesByViewport = {
115
92
  xs: {
116
- maxWidth: getMaxWidthForViewport('xs', limitWidth, contentMinWidth, maxWidth),
93
+ maxWidth: getMaxWidthForViewport('xs', limitWidth, contentWidthProp, maxWidth),
117
94
  flexDirection: reverseLevel[0] ? 'column-reverse' : 'column'
118
95
  },
119
96
  sm: {
120
- maxWidth: getMaxWidthForViewport('sm', limitWidth, contentMinWidth, maxWidth),
97
+ maxWidth: getMaxWidthForViewport('sm', limitWidth, contentWidthProp, maxWidth),
121
98
  flexDirection: reverseLevel[1] ? 'column-reverse' : 'column'
122
99
  },
123
100
  md: {
124
- maxWidth: getMaxWidthForViewport('md', limitWidth, contentMinWidth, maxWidth),
101
+ maxWidth: getMaxWidthForViewport('md', limitWidth, contentWidthProp, maxWidth),
125
102
  flexDirection: reverseLevel[2] ? 'column-reverse' : 'column'
126
103
  },
127
104
  lg: {
128
- maxWidth: getMaxWidthForViewport('lg', limitWidth, contentMinWidth, maxWidth),
105
+ maxWidth: getMaxWidthForViewport('lg', limitWidth, contentWidthProp, maxWidth),
129
106
  flexDirection: reverseLevel[3] ? 'column-reverse' : 'column'
130
107
  },
131
108
  xl: {
132
- maxWidth: getMaxWidthForViewport('xl', limitWidth, contentMinWidth, maxWidth),
109
+ maxWidth: getMaxWidthForViewport('xl', limitWidth, contentWidthProp, maxWidth),
133
110
  flexDirection: reverseLevel[4] ? 'column-reverse' : 'column'
134
111
  }
135
112
  }
@@ -223,7 +200,7 @@ FlexGrid.propTypes = {
223
200
  */
224
201
  children: PropTypes.node.isRequired,
225
202
  /**
226
- * The minimum width of the content in the FlexGrid.
203
+ * The maximum width of the content in the FlexGrid.
227
204
  * This prop accepts responsive values for different viewports. If a number is provided,
228
205
  * it will be the max content width for the desired viewport.
229
206
  * - `xs`: 'max' | 'full' | <number>
@@ -232,6 +209,20 @@ FlexGrid.propTypes = {
232
209
  * - `lg`: 'max' | 'full' | <number>
233
210
  * - `xl`: 'max' | 'full' | <number>
234
211
  */
212
+ contentMaxWidth: PropTypes.shape({
213
+ xl: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
214
+ lg: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
215
+ md: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
216
+ sm: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
217
+ xs: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number])
218
+ }),
219
+ /**
220
+ * @deprecated Use `contentMaxWidth` instead. This prop will be removed in a future version.
221
+ *
222
+ * The minimum width of the content in the FlexGrid.
223
+ * This prop accepts responsive values for different viewports. If a number is provided,
224
+ * it will be the max content width for the desired viewport.
225
+ */
235
226
  contentMinWidth: PropTypes.shape({
236
227
  xl: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
237
228
  lg: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
@@ -8,6 +8,7 @@ import {
8
8
  getTokensSetPropType,
9
9
  selectSystemProps,
10
10
  selectTokens,
11
+ variantProp,
11
12
  viewProps
12
13
  } from '../utils'
13
14
  import ScrollViewEnd from './ScrollViewEnd'
@@ -38,7 +39,7 @@ const selectBorderStyles = (borderBottomWidth, borderBottomColor) => ({
38
39
  * @TODO revisit `ScrollButton` after IconButton is stable.
39
40
  */
40
41
  const HorizontalScroll = React.forwardRef(
41
- ({ ScrollButton, tokens, itemPositions, children, ...rest }, ref) => {
42
+ ({ ScrollButton, tokens, itemPositions, variant, children, ...rest }, ref) => {
42
43
  const {
43
44
  nextIcon,
44
45
  previousIcon,
@@ -74,8 +75,9 @@ const HorizontalScroll = React.forwardRef(
74
75
  }
75
76
 
76
77
  const scrollMax = Math.max(0, contentWidth - containerWidth)
77
- const showNextButton = scrollOffset < scrollMax
78
- const showPreviousButton = scrollOffset > 0
78
+ const hideNavigationButtons = variant?.hideNavigationButtons || false
79
+ const showNextButton = scrollOffset < scrollMax && !hideNavigationButtons
80
+ const showPreviousButton = scrollOffset > 0 && !hideNavigationButtons
79
81
 
80
82
  const scrollRef = React.useRef(null)
81
83
  const scrollTo = (targetX) => {
@@ -173,6 +175,7 @@ HorizontalScroll.propTypes = {
173
175
  itemPositions: itemPositionsPropType,
174
176
  ScrollButton: PropTypes.elementType,
175
177
  tokens: getTokensSetPropType(tokenKeys, { allowFunction: true }),
178
+ variant: variantProp.propType,
176
179
  children: PropTypes.node
177
180
  }
178
181
 
package/src/Icon/Icon.jsx CHANGED
@@ -14,6 +14,7 @@ const Icon = React.forwardRef(
14
14
  tokens,
15
15
  scalesWithText = false,
16
16
  style = {},
17
+ testID,
17
18
  dataSet
18
19
  },
19
20
  ref
@@ -45,6 +46,7 @@ const Icon = React.forwardRef(
45
46
  const getIconContentForMobile = () => {
46
47
  return (
47
48
  <View
49
+ testID={testID}
48
50
  style={{
49
51
  backgroundColor: themeTokens.backgroundColor,
50
52
  borderRadius: themeTokens.borderRadius,
@@ -58,6 +60,7 @@ const Icon = React.forwardRef(
58
60
 
59
61
  return Platform.OS === 'web' ? (
60
62
  <View
63
+ testID={testID}
61
64
  ref={ref}
62
65
  // eslint-disable-next-line react-native/no-inline-styles
63
66
  style={{
@@ -136,6 +136,7 @@ const IconButton = React.forwardRef(
136
136
  hrefAttrs,
137
137
  testID,
138
138
  accessibilityRole = href ? 'link' : 'button',
139
+ inactive = false,
139
140
  ...rawRest
140
141
  },
141
142
  ref
@@ -149,9 +150,10 @@ const IconButton = React.forwardRef(
149
150
  linkProps.handleHref({ href, onPress })({ nativeEvent: { target: ref?.current?.id } })
150
151
  }
151
152
 
152
- const getTokens = useThemeTokensCallback('IconButton', tokens, variant)
153
+ const mergedVariant = inactive ? { ...variant, inactive: true } : variant
154
+ const getTokens = useThemeTokensCallback('IconButton', tokens, mergedVariant)
153
155
  const getOuterStyle = (pressableState) =>
154
- selectOuterStyle(getTokens(resolvePressableState(pressableState), variant.password))
156
+ selectOuterStyle(getTokens(resolvePressableState(pressableState), mergedVariant.password))
155
157
 
156
158
  return (
157
159
  <Pressable
@@ -162,16 +164,17 @@ const IconButton = React.forwardRef(
162
164
  style={getOuterStyle}
163
165
  {...selectedProps}
164
166
  testID={testID}
167
+ disabled={inactive}
165
168
  >
166
169
  {(pressableState) => {
167
170
  const themeTokens = getTokens(resolvePressableState(pressableState))
168
171
  return (
169
- <View style={selectInnerStyle(themeTokens, variant.password)}>
172
+ <View style={selectInnerStyle(themeTokens, mergedVariant.password)}>
170
173
  <Icon
171
174
  icon={IconComponent || themeTokens.icon}
172
175
  title={selectedProps.accessibilityLabel}
173
176
  tokens={selectTokens('Icon', themeTokens, 'icon')}
174
- variant={variant}
177
+ variant={mergedVariant}
175
178
  />
176
179
  </View>
177
180
  )
@@ -206,7 +209,11 @@ IconButton.propTypes = {
206
209
  /**
207
210
  * Function to execute when the `Iconbutton` is pressed
208
211
  */
209
- onPress: PropTypes.func
212
+ onPress: PropTypes.func,
213
+ /**
214
+ * When true, applies the inactive variant styling
215
+ */
216
+ inactive: PropTypes.bool
210
217
  }
211
218
 
212
219
  const staticStyles = StyleSheet.create({