@telus-uds/components-base 3.24.0 → 3.26.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 (35) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/lib/cjs/Button/ButtonGroup.js +9 -2
  3. package/lib/cjs/Carousel/Carousel.js +90 -30
  4. package/lib/cjs/Carousel/Constants.js +13 -2
  5. package/lib/cjs/FlexGrid/FlexGrid.js +31 -35
  6. package/lib/cjs/IconButton/IconButton.js +15 -5
  7. package/lib/cjs/InputSupports/InputSupports.js +2 -1
  8. package/lib/cjs/PriceLockup/PriceLockup.js +1 -1
  9. package/lib/cjs/TextInput/TextInputBase.js +2 -3
  10. package/lib/cjs/utils/index.js +9 -1
  11. package/lib/cjs/utils/resolveContentMaxWidth.js +30 -0
  12. package/lib/esm/Button/ButtonGroup.js +9 -2
  13. package/lib/esm/Carousel/Carousel.js +84 -24
  14. package/lib/esm/Carousel/Constants.js +12 -1
  15. package/lib/esm/FlexGrid/FlexGrid.js +31 -35
  16. package/lib/esm/IconButton/IconButton.js +15 -5
  17. package/lib/esm/InputSupports/InputSupports.js +2 -1
  18. package/lib/esm/PriceLockup/PriceLockup.js +1 -1
  19. package/lib/esm/TextInput/TextInputBase.js +2 -3
  20. package/lib/esm/utils/index.js +2 -1
  21. package/lib/esm/utils/resolveContentMaxWidth.js +24 -0
  22. package/lib/package.json +2 -2
  23. package/package.json +2 -2
  24. package/src/Button/ButtonGroup.jsx +20 -3
  25. package/src/Carousel/Carousel.jsx +104 -30
  26. package/src/Carousel/Constants.js +14 -0
  27. package/src/FlexGrid/FlexGrid.jsx +30 -39
  28. package/src/IconButton/IconButton.jsx +12 -5
  29. package/src/InputSupports/InputSupports.jsx +6 -1
  30. package/src/PriceLockup/PriceLockup.jsx +1 -1
  31. package/src/TextInput/TextInputBase.jsx +2 -2
  32. package/src/utils/index.js +1 -0
  33. package/src/utils/resolveContentMaxWidth.js +28 -0
  34. package/types/Status.d.ts +42 -0
  35. package/types/index.d.ts +3 -0
@@ -151,7 +151,17 @@ const ButtonGroup = React.forwardRef(
151
151
  >
152
152
  <View accessibilityRole={innerRole} style={viewStyles}>
153
153
  {items.map(
154
- ({ label, id = label, accessibilityLabel, ref: itemRef, ...itemRest }, index) => {
154
+ (
155
+ {
156
+ label,
157
+ id = label,
158
+ accessibilityLabel,
159
+ ref: itemRef,
160
+ inactive: itemInactive,
161
+ ...itemRest
162
+ },
163
+ index
164
+ ) => {
155
165
  const isSelected = currentValues.includes(id)
156
166
 
157
167
  // Pass an object of relevant component state as first argument for any passed-in press handlers
@@ -171,6 +181,8 @@ const ButtonGroup = React.forwardRef(
171
181
  ...a11yProps.getPositionInSet(items.length, index)
172
182
  }
173
183
 
184
+ const isInactive = itemInactive !== undefined ? itemInactive : inactive
185
+
174
186
  // Ensure button is direct child of group as MacOS voiceover only applies "X of Y" to
175
187
  // "radio" if it's a direct child of "radiogroup", even if aria-posinset etc exists
176
188
  return (
@@ -182,7 +194,7 @@ const ButtonGroup = React.forwardRef(
182
194
  onPress={handlePress}
183
195
  tokens={getButtonTokens}
184
196
  selected={isSelected}
185
- inactive={inactive}
197
+ inactive={isInactive}
186
198
  icon={iconProp}
187
199
  {...selectItemProps({
188
200
  ...itemRest,
@@ -243,7 +255,12 @@ ButtonGroup.propTypes = {
243
255
  /**
244
256
  * An optional ref for one individual button in the ButtonGroup
245
257
  */
246
- ref: ABBPropTypes.ref()
258
+ ref: ABBPropTypes.ref(),
259
+ /**
260
+ * If true, this individual button cannot be interacted with. Takes precedence
261
+ * over the group-level `inactive` prop. Useful for disabling specific options.
262
+ */
263
+ inactive: PropTypes.bool
247
264
  })
248
265
  ),
249
266
  /**
@@ -1,7 +1,15 @@
1
1
  import React from 'react'
2
- import { View, Animated, PanResponder, StyleSheet, Platform, Dimensions } from 'react-native'
2
+ import {
3
+ View,
4
+ Animated,
5
+ PanResponder,
6
+ StyleSheet,
7
+ Platform,
8
+ Dimensions,
9
+ Easing
10
+ } from 'react-native'
3
11
  import PropTypes from 'prop-types'
4
- import { useThemeTokens } from '../ThemeProvider'
12
+ import { useThemeTokens, useTheme } from '../ThemeProvider'
5
13
  import { useViewport } from '../ViewportProvider'
6
14
  import {
7
15
  getTokensPropType,
@@ -13,7 +21,9 @@ import {
13
21
  viewProps,
14
22
  useCopy,
15
23
  unpackFragment,
16
- isTouchDevice
24
+ isTouchDevice,
25
+ useResponsiveProp,
26
+ resolveContentMaxWidth
17
27
  } from '../utils'
18
28
  import { useA11yInfo } from '../A11yInfoProvider'
19
29
  import { CarouselProvider } from './CarouselContext'
@@ -36,15 +46,13 @@ import {
36
46
  DEFAULT_VIEWPORT_MARGIN,
37
47
  PEEKING_MULTIPLIER,
38
48
  ACTIVE_INDEX_OFFSET_MULTIPLIER,
39
- NEGATIVE_MULTIPLIER
49
+ NEGATIVE_MULTIPLIER,
50
+ TRANSITION_MODES,
51
+ SWIPE_RELEASE_STYLES,
52
+ INSTANT_ANIMATION_DURATION,
53
+ DEFAULT_SWIPE_RELEASE_DURATION
40
54
  } from './Constants'
41
55
 
42
- const TRANSITION_MODES = {
43
- MANUAL: 'manual',
44
- AUTOMATIC: 'automatic',
45
- SWIPE: 'swipe'
46
- }
47
-
48
56
  const staticStyles = StyleSheet.create({
49
57
  root: {
50
58
  backgroundColor: 'transparent',
@@ -252,11 +260,18 @@ const selectRootContainerStyles = (enableHero, viewport) => {
252
260
  return {}
253
261
  }
254
262
 
255
- const selectMainContainerStyles = (enableHero, viewport) => {
263
+ const selectMainContainerStyles = (enableHero, viewport, maxWidth) => {
256
264
  if (enableHero && viewport === 'xl' && Platform.OS === 'web') {
257
265
  return {
258
266
  width: '100%',
259
- maxWidth: 1200
267
+ maxWidth: maxWidth || 1200
268
+ }
269
+ }
270
+ if (maxWidth !== null && maxWidth !== undefined) {
271
+ return {
272
+ maxWidth,
273
+ alignSelf: 'center',
274
+ width: '100%'
260
275
  }
261
276
  }
262
277
  return {}
@@ -402,6 +417,9 @@ const Carousel = React.forwardRef(
402
417
  loopDuration = transitionDuration,
403
418
  autoPlay = false,
404
419
  enablePeeking = false,
420
+ contentMaxWidth,
421
+ swipeReleaseStyle = SWIPE_RELEASE_STYLES.INSTANT,
422
+ swipeReleaseDuration = DEFAULT_SWIPE_RELEASE_DURATION,
405
423
  ...rest
406
424
  },
407
425
  ref
@@ -409,6 +427,11 @@ const Carousel = React.forwardRef(
409
427
  let childrenArray = unpackFragment(children)
410
428
  const isTransitioningRef = React.useRef(false)
411
429
  const viewport = useViewport()
430
+ const { themeOptions } = useTheme()
431
+
432
+ const contentMaxWidthValue = useResponsiveProp(contentMaxWidth)
433
+ const responsiveWidth = useResponsiveProp(themeOptions?.contentMaxWidth)
434
+ const maxWidth = resolveContentMaxWidth(contentMaxWidthValue, responsiveWidth)
412
435
  const totalItems = getTotalItems(enableDisplayMultipleItemsPerSlide, childrenArray, viewport)
413
436
  const autoPlayFeatureEnabled =
414
437
  autoPlay && slideDuration > 0 && transitionDuration > 0 && totalItems > 1
@@ -538,14 +561,29 @@ const Carousel = React.forwardRef(
538
561
  }, [pan, animatedX, heroPan, heroAnimatedX, enableHero, viewport, enablePeeking])
539
562
 
540
563
  const animate = React.useCallback(
541
- (panToAnimate, toValue, toIndex) => {
564
+ (panToAnimate, toValue, toIndex, isSwipeRelease = false) => {
542
565
  const applicableTransitionDuration =
543
566
  isLastSlide && toIndex === 0 ? loopDuration : transitionDuration
544
567
  const handleAnimationEndToIndex = (...args) => handleAnimationEnd(toIndex, ...args)
545
- if (reduceMotionRef.current || isSwiping.current) {
546
- Animated.timing(panToAnimate, { toValue, duration: 1, useNativeDriver: false }).start(
547
- handleAnimationEndToIndex
548
- )
568
+ if (reduceMotionRef.current) {
569
+ Animated.timing(panToAnimate, {
570
+ toValue,
571
+ duration: INSTANT_ANIMATION_DURATION,
572
+ useNativeDriver: false
573
+ }).start(handleAnimationEndToIndex)
574
+ } else if (isSwipeRelease && swipeReleaseStyle === SWIPE_RELEASE_STYLES.EASE_OUT) {
575
+ Animated.timing(panToAnimate, {
576
+ toValue,
577
+ duration: swipeReleaseDuration,
578
+ easing: Easing.out(Easing.cubic),
579
+ useNativeDriver: false
580
+ }).start(handleAnimationEndToIndex)
581
+ } else if (isSwiping.current || isSwipeRelease) {
582
+ Animated.timing(panToAnimate, {
583
+ toValue,
584
+ duration: INSTANT_ANIMATION_DURATION,
585
+ useNativeDriver: false
586
+ }).start(handleAnimationEndToIndex)
549
587
  } else if (isAutoPlayEnabled) {
550
588
  Animated.timing(panToAnimate, {
551
589
  ...springConfig,
@@ -576,7 +614,9 @@ const Carousel = React.forwardRef(
576
614
  isLastSlide,
577
615
  isAutoPlayEnabled,
578
616
  enablePeeking,
579
- enableDisplayMultipleItemsPerSlide
617
+ enableDisplayMultipleItemsPerSlide,
618
+ swipeReleaseStyle,
619
+ swipeReleaseDuration
580
620
  ]
581
621
  )
582
622
 
@@ -597,6 +637,7 @@ const Carousel = React.forwardRef(
597
637
  const toValue = { x: 0, y: 0 }
598
638
  let skipChanges = !delta
599
639
  let calcDelta = delta
640
+ const isSwipeRelease = transitionMode === TRANSITION_MODES.SWIPE
600
641
  if (activeIndexRef.current <= 0 && delta < 0) {
601
642
  skipChanges = transitionMode !== TRANSITION_MODES.AUTOMATIC
602
643
  calcDelta = totalItems + delta
@@ -608,10 +649,10 @@ const Carousel = React.forwardRef(
608
649
  const index = activeIndexRef.current + calcDelta
609
650
  if (skipChanges) {
610
651
  isTransitioningRef.current = true
611
- animate(pan, toValue, index)
652
+ animate(pan, toValue, index, isSwipeRelease)
612
653
 
613
654
  if (enableHero) {
614
- animate(heroPan, toValue, index)
655
+ animate(heroPan, toValue, index, isSwipeRelease)
615
656
  }
616
657
  return calcDelta
617
658
  }
@@ -630,9 +671,9 @@ const Carousel = React.forwardRef(
630
671
  const heroToValue = { x: 0, y: 0 }
631
672
  heroToValue.x = heroContainerLayoutRef.current.width * -1 * calcDelta
632
673
  isTransitioningRef.current = true
633
- animate(pan, toValue, index)
674
+ animate(pan, toValue, index, isSwipeRelease)
634
675
  if (enableHero) {
635
- animate(heroPan, heroToValue, index)
676
+ animate(heroPan, heroToValue, index, isSwipeRelease)
636
677
  }
637
678
  if (isCarouselPlaying) {
638
679
  stopAutoplay()
@@ -851,14 +892,14 @@ const Carousel = React.forwardRef(
851
892
  }
852
893
  const correction = gesture.moveX - gesture.x0
853
894
 
895
+ isSwiping.current = false
896
+
854
897
  if (Math.abs(correction) < containerLayoutRef.current.width * minDistanceForAction) {
855
- animate(pan, { x: 0, y: 0 }, 0)
898
+ animate(pan, { x: 0, y: 0 }, activeIndexRef.current, true)
856
899
  } else {
857
900
  const delta = correction > 0 ? -1 : 1
858
901
  updateIndex(delta, TRANSITION_MODES.SWIPE)
859
902
  }
860
-
861
- isSwiping.current = false
862
903
  }
863
904
  }),
864
905
  [
@@ -909,14 +950,14 @@ const Carousel = React.forwardRef(
909
950
  }
910
951
  const correction = gesture.moveX - gesture.x0
911
952
 
953
+ isSwiping.current = false
954
+
912
955
  if (Math.abs(correction) < containerLayoutRef.current.width * minDistanceForAction) {
913
- animate(heroPan, { x: 0, y: 0 }, 0)
956
+ animate(heroPan, { x: 0, y: 0 }, activeIndexRef.current, true)
914
957
  } else {
915
958
  const delta = correction > 0 ? -1 : 1
916
959
  updateIndex(delta, TRANSITION_MODES.SWIPE)
917
960
  }
918
-
919
- isSwiping.current = false
920
961
  }
921
962
  }),
922
963
  [
@@ -1031,7 +1072,7 @@ const Carousel = React.forwardRef(
1031
1072
 
1032
1073
  return (
1033
1074
  <View style={selectRootContainerStyles(enableHero, viewport)}>
1034
- <View style={selectMainContainerStyles(enableHero, viewport)}>
1075
+ <View style={selectMainContainerStyles(enableHero, viewport, maxWidth)}>
1035
1076
  <CarouselProvider
1036
1077
  activeIndex={activeIndex}
1037
1078
  goTo={goTo}
@@ -1463,7 +1504,40 @@ Carousel.propTypes = {
1463
1504
  * If set to `true`, the Carousel will show multiple slides at once
1464
1505
  * - Default value is `false`
1465
1506
  */
1466
- enableDisplayMultipleItemsPerSlide: PropTypes.bool
1507
+ enableDisplayMultipleItemsPerSlide: PropTypes.bool,
1508
+ /**
1509
+ * The maximum width of the content in the Carousel.
1510
+ * This prop accepts responsive values for different viewports. If a number is provided,
1511
+ * it will be the max content width for the desired viewport.
1512
+ * - `xs`: 'max' | 'full' | <number>
1513
+ * - `sm`: 'max' | 'full' | <number>
1514
+ * - `md`: 'max' | 'full' | <number>
1515
+ * - `lg`: 'max' | 'full' | <number>
1516
+ * - `xl`: 'max' | 'full' | <number>
1517
+ */
1518
+ contentMaxWidth: PropTypes.shape({
1519
+ xl: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
1520
+ lg: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
1521
+ md: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
1522
+ sm: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
1523
+ xs: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number])
1524
+ }),
1525
+ /**
1526
+ * Animation style for swipe release transitions.
1527
+ * - `'instant'`: Immediate snap to position (current default)
1528
+ * - `'ease-out'`: Smooth deceleration animation
1529
+ * - Default value is `'instant'`
1530
+ * - Use `swipeReleaseDuration` to customize the animation duration when using `'ease-out'`
1531
+ *
1532
+ * @deprecated The default will change to `'ease-out'` in Q2 2026 (introduced Jan 2026).
1533
+ */
1534
+ swipeReleaseStyle: PropTypes.oneOf(['instant', 'ease-out']),
1535
+ /**
1536
+ * Duration in milliseconds of the ease-out animation when releasing a swipe gesture.
1537
+ * Only applies when `swipeReleaseStyle` is set to `'ease-out'`.
1538
+ * - Default value is `500` (500ms)
1539
+ */
1540
+ swipeReleaseDuration: PropTypes.number
1467
1541
  }
1468
1542
 
1469
1543
  Carousel.Item = CarouselItem
@@ -9,3 +9,17 @@ export const DEFAULT_VIEWPORT_MARGIN = 10
9
9
  export const PEEKING_MULTIPLIER = 2
10
10
  export const ACTIVE_INDEX_OFFSET_MULTIPLIER = 1
11
11
  export const NEGATIVE_MULTIPLIER = -1
12
+
13
+ export const TRANSITION_MODES = {
14
+ MANUAL: 'manual',
15
+ AUTOMATIC: 'automatic',
16
+ SWIPE: 'swipe'
17
+ }
18
+
19
+ export const SWIPE_RELEASE_STYLES = {
20
+ INSTANT: 'instant',
21
+ EASE_OUT: 'ease-out'
22
+ }
23
+
24
+ export const INSTANT_ANIMATION_DURATION = 1
25
+ export const DEFAULT_SWIPE_RELEASE_DURATION = 500
@@ -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]),
@@ -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({
@@ -66,7 +66,12 @@ const InputSupports = React.forwardRef(
66
66
  />
67
67
  )}
68
68
  {typeof children === 'function'
69
- ? children({ inputId, ...a11yProps, validation: feedbackValidation })
69
+ ? children({
70
+ inputId,
71
+ ...a11yProps,
72
+ validation: feedbackValidation,
73
+ accessibilityDescribedBy: feedbackId
74
+ })
70
75
  : children}
71
76
  {feedback || maxCharsReachedErrorMessage ? (
72
77
  <Feedback
@@ -121,7 +121,7 @@ const PriceLockup = React.forwardRef(
121
121
  >
122
122
  {topText ? (
123
123
  <View style={staticStyles.topText}>
124
- {renderTypography(topText, topTextTypographyTokens)}
124
+ {renderTypography(topText, topTextTypographyTokens, undefined, fontColor)}
125
125
  </View>
126
126
  ) : null}
127
127
  {renderPrice(
@@ -313,7 +313,7 @@ const TextInputBase = React.forwardRef(
313
313
  icon={ClearButtonIcon}
314
314
  key="clear"
315
315
  onPress={handleClear}
316
- variant={{ compact: true }}
316
+ variant={{ subtle: true }}
317
317
  />
318
318
  )
319
319
  }
@@ -329,7 +329,7 @@ const TextInputBase = React.forwardRef(
329
329
  icon={!showPassword ? passwordShowButtonIcon : passwordHideButtonIcon}
330
330
  key={!showPassword ? 'hide' : 'show'}
331
331
  onPress={handleShowOrHide}
332
- variant={{ compact: true, password: true, inactive: variant.inactive, size: 'large' }}
332
+ variant={{ subtle: true, inactive: variant.inactive, size: 'large' }}
333
333
  tokens={{ width: 40, height: 40 }}
334
334
  />
335
335
  )
@@ -27,3 +27,4 @@ export { default as formatImageSource } from './formatImageSource'
27
27
  export { default as getSpacingScale } from './getSpacingScale'
28
28
  export { default as useVariants } from './useVariants'
29
29
  export { default as isTouchDevice } from './isTouchDevice'
30
+ export { default as resolveContentMaxWidth } from './resolveContentMaxWidth'
@@ -0,0 +1,28 @@
1
+ const CONTENT_MAX_WIDTH = 'max'
2
+ const CONTENT_FULL_WIDTH = 'full'
3
+
4
+ /**
5
+ * Resolves the maximum width for content based on the provided value and responsive width.
6
+ *
7
+ * @param {number|string|null|undefined} contentMaxWidthValue - The maximum width value for the content.
8
+ * Can be a number, a special string constant (e.g., CONTENT_FULL_WIDTH, CONTENT_MAX_WIDTH), or null/undefined.
9
+ * @param {number} responsiveWidth - The responsive width to use when contentMaxWidthValue is CONTENT_MAX_WIDTH.
10
+ * @returns {number|string|null} The resolved maximum width value, or null if full width is desired.
11
+ */
12
+ const resolveContentMaxWidth = (contentMaxWidthValue, responsiveWidth) => {
13
+ if (!contentMaxWidthValue || contentMaxWidthValue === CONTENT_FULL_WIDTH) {
14
+ return null
15
+ }
16
+
17
+ if (Number.isFinite(contentMaxWidthValue)) {
18
+ return contentMaxWidthValue
19
+ }
20
+
21
+ if (contentMaxWidthValue === CONTENT_MAX_WIDTH) {
22
+ return responsiveWidth
23
+ }
24
+
25
+ return contentMaxWidthValue
26
+ }
27
+
28
+ export default resolveContentMaxWidth
@@ -0,0 +1,42 @@
1
+ import type { Variant } from '../../components-web/types/common'
2
+ import { ComponentType, ReactNode } from 'react'
3
+
4
+ import { IconProps } from './Icon'
5
+
6
+ export type StatusTokensProps = {
7
+ backgroundColor: string
8
+ backgroundGradient: string
9
+ borderColor: string
10
+ borderRadius: number | string
11
+ borderWidth: number | string
12
+ fontName: string
13
+ fontSize: number | string
14
+ fontWeight: number | string
15
+ icon: IconProps
16
+ iconColor: string
17
+ iconGradient: string
18
+ marginLeft: string | number
19
+ paddingBottom: string | number
20
+ paddingLeft: string | number
21
+ paddingRight: string | number
22
+ paddingTop: string | number
23
+ textColor: string
24
+ textLineHeight: string | number
25
+ iconSize: number | string
26
+ iconPaddingTop: number | string
27
+ }
28
+
29
+ export interface StatusProps {
30
+ children: ReactNode
31
+ variant?: Variant
32
+ tokens?: Partial<StatusTokensProps>
33
+ customGradient?: (
34
+ gradient: { colors: string[]; start: { x: number; y: number }; end: { x: number; y: number } },
35
+ styles: Record<string, unknown>,
36
+ content: ReactNode
37
+ ) => ReactNode
38
+ }
39
+
40
+ declare const Status: ComponentType<StatusProps>
41
+
42
+ export default Status
package/types/index.d.ts CHANGED
@@ -90,4 +90,7 @@ export { PortalProps } from './Portal'
90
90
  export { default as Listbox } from './Listbox'
91
91
  export { ListboxProps, ListboxTokens } from './Listbox'
92
92
 
93
+ export { default as Status } from './Status'
94
+ export { StatusProps, StatusTokensProps } from './Status'
95
+
93
96
  export * from './Common'