@telus-uds/components-base 3.18.0 → 3.19.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 (37) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/jest.config.cjs +10 -2
  3. package/lib/cjs/Box/Box.js +114 -62
  4. package/lib/cjs/Box/backgroundImageStylesMap.js +136 -28
  5. package/lib/cjs/Carousel/Carousel.js +1 -1
  6. package/lib/cjs/MultiSelectFilter/MultiSelectFilter.js +2 -2
  7. package/lib/cjs/StepTracker/Step.js +12 -1
  8. package/lib/cjs/StepTracker/StepTracker.js +15 -4
  9. package/lib/cjs/TabBar/TabBar.js +4 -2
  10. package/lib/cjs/TabBar/index.js +2 -0
  11. package/lib/cjs/Tooltip/Backdrop.js +1 -1
  12. package/lib/cjs/utils/index.js +9 -1
  13. package/lib/cjs/utils/isTouchDevice.js +34 -0
  14. package/lib/esm/Box/Box.js +113 -63
  15. package/lib/esm/Box/backgroundImageStylesMap.js +134 -27
  16. package/lib/esm/Carousel/Carousel.js +2 -2
  17. package/lib/esm/MultiSelectFilter/MultiSelectFilter.js +2 -2
  18. package/lib/esm/StepTracker/Step.js +12 -1
  19. package/lib/esm/StepTracker/StepTracker.js +15 -4
  20. package/lib/esm/TabBar/TabBar.js +4 -2
  21. package/lib/esm/TabBar/index.js +2 -0
  22. package/lib/esm/Tooltip/Backdrop.js +1 -1
  23. package/lib/esm/utils/index.js +2 -1
  24. package/lib/esm/utils/isTouchDevice.js +27 -0
  25. package/lib/package.json +2 -2
  26. package/package.json +2 -2
  27. package/src/Box/Box.jsx +97 -55
  28. package/src/Box/backgroundImageStylesMap.js +48 -15
  29. package/src/Carousel/Carousel.jsx +3 -2
  30. package/src/MultiSelectFilter/MultiSelectFilter.jsx +2 -2
  31. package/src/StepTracker/Step.jsx +47 -27
  32. package/src/StepTracker/StepTracker.jsx +9 -1
  33. package/src/TabBar/TabBar.jsx +3 -1
  34. package/src/TabBar/index.js +3 -0
  35. package/src/Tooltip/Backdrop.jsx +1 -1
  36. package/src/utils/index.js +1 -0
  37. package/src/utils/isTouchDevice.js +34 -0
@@ -0,0 +1,27 @@
1
+ import Platform from "react-native-web/dist/exports/Platform";
2
+ /**
3
+ * Determines if the current device supports touch interactions
4
+ *
5
+ * @returns {boolean} True if the device supports touch, false otherwise
6
+ */
7
+ const isTouchDevice = () => {
8
+ if (Platform.OS !== 'web') {
9
+ return true;
10
+ }
11
+ if (typeof window !== 'undefined') {
12
+ if ('ontouchstart' in window) {
13
+ return true;
14
+ }
15
+ if (window.navigator && window.navigator.maxTouchPoints > 0) {
16
+ return true;
17
+ }
18
+ if (window.navigator && window.navigator.msMaxTouchPoints > 0) {
19
+ return true;
20
+ }
21
+ if (window.matchMedia && window.matchMedia('(pointer: coarse)').matches) {
22
+ return true;
23
+ }
24
+ }
25
+ return false;
26
+ };
27
+ export default isTouchDevice;
package/lib/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "@gorhom/portal": "^1.0.14",
13
13
  "@react-native-picker/picker": "^2.9.0",
14
14
  "@telus-uds/system-constants": "^3.0.0",
15
- "@telus-uds/system-theme-tokens": "^4.14.0",
15
+ "@telus-uds/system-theme-tokens": "^4.15.0",
16
16
  "airbnb-prop-types": "^2.16.0",
17
17
  "css-mediaquery": "^0.1.2",
18
18
  "expo-document-picker": "^13.0.1",
@@ -84,6 +84,6 @@
84
84
  "standard-engine": {
85
85
  "skip": true
86
86
  },
87
- "version": "3.18.0",
87
+ "version": "3.19.0",
88
88
  "types": "types/index.d.ts"
89
89
  }
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "@gorhom/portal": "^1.0.14",
13
13
  "@react-native-picker/picker": "^2.9.0",
14
14
  "@telus-uds/system-constants": "^3.0.0",
15
- "@telus-uds/system-theme-tokens": "^4.14.0",
15
+ "@telus-uds/system-theme-tokens": "^4.15.0",
16
16
  "airbnb-prop-types": "^2.16.0",
17
17
  "css-mediaquery": "^0.1.2",
18
18
  "expo-document-picker": "^13.0.1",
@@ -84,6 +84,6 @@
84
84
  "standard-engine": {
85
85
  "skip": true
86
86
  },
87
- "version": "3.18.0",
87
+ "version": "3.19.0",
88
88
  "types": "types/index.d.ts"
89
89
  }
package/src/Box/Box.jsx CHANGED
@@ -21,9 +21,10 @@ import {
21
21
  variantProp,
22
22
  viewProps,
23
23
  StyleSheet as RNMQStyleSheet,
24
- getSpacingScale
24
+ getSpacingScale,
25
+ formatImageSource
25
26
  } from '../utils'
26
- import backgroundImageStylesMap from './backgroundImageStylesMap'
27
+ import backgroundImageStylesMap, { backgroundPositions } from './backgroundImageStylesMap'
27
28
  import { useViewport } from '../ViewportProvider'
28
29
 
29
30
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
@@ -87,38 +88,60 @@ const setBackgroundImage = ({
87
88
  backgroundImageResizeMode,
88
89
  backgroundImagePosition,
89
90
  backgroundImageAlign,
90
- backgroundImageWidth,
91
- backgroundImageHeight,
92
- content
91
+ content,
92
+ testID
93
93
  }) => {
94
- if (backgroundImageResizeMode === 'contain') {
95
- const containedViewStyle = {
96
- ...staticStyles.containedView,
97
- width: backgroundImageWidth,
98
- height: backgroundImageHeight,
99
- ...backgroundImageStylesMap[`${backgroundImagePosition}-${backgroundImageAlign}`]
94
+ const backgroundImageTestID = testID ? `${testID}-background-image` : undefined
95
+
96
+ if (backgroundImageResizeMode === 'contain' && backgroundImagePosition && backgroundImageAlign) {
97
+ const positionKey = `${backgroundImagePosition}-${backgroundImageAlign}`
98
+
99
+ if (Platform.OS === 'web') {
100
+ const backgroundPosition = backgroundPositions[positionKey] || 'center center'
101
+
102
+ const backgroundImageStyle = {
103
+ backgroundImage: `url(${src})`,
104
+ backgroundSize: 'contain',
105
+ backgroundRepeat: 'no-repeat',
106
+ backgroundPosition
107
+ }
108
+
109
+ return (
110
+ <View
111
+ style={[staticStyles.imageBackground, backgroundImageStyle]}
112
+ aria-label={alt}
113
+ testID={backgroundImageTestID}
114
+ >
115
+ {content}
116
+ </View>
117
+ )
100
118
  }
119
+ const positionStyles = backgroundImageStylesMap[positionKey] || {}
101
120
 
102
121
  return (
103
- <View style={staticStyles.containedContainer}>
104
- <View style={containedViewStyle}>
105
- <Image
106
- source={{ uri: src }}
107
- alt={alt}
108
- style={staticStyles.containedImage}
109
- accessibilityIgnoresInvertColors
110
- />
111
- </View>
112
- {content}
122
+ <View style={staticStyles.containContainer}>
123
+ <Image
124
+ source={src}
125
+ resizeMode={backgroundImageResizeMode}
126
+ style={[staticStyles.containImage, positionStyles]}
127
+ accessible={true}
128
+ accessibilityLabel={alt}
129
+ accessibilityIgnoresInvertColors={true}
130
+ testID={backgroundImageTestID}
131
+ />
132
+ <View style={staticStyles.contentOverlay}>{content}</View>
113
133
  </View>
114
134
  )
115
135
  }
136
+
116
137
  return (
117
138
  <ImageBackground
118
- source={{ uri: src }}
119
- alt={alt}
120
- style={staticStyles.backgroundImageContainer}
139
+ source={src}
121
140
  resizeMode={backgroundImageResizeMode}
141
+ style={staticStyles.imageBackground}
142
+ accessible={true}
143
+ accessibilityLabel={alt}
144
+ testID={backgroundImageTestID}
122
145
  >
123
146
  {content}
124
147
  </ImageBackground>
@@ -279,33 +302,47 @@ const Box = React.forwardRef(
279
302
 
280
303
  const { src = '', alt = '', resizeMode = '', position = '', align = '' } = backgroundImage || {}
281
304
  const backgroundImageResizeMode = useResponsiveProp(resizeMode, 'cover')
282
- const backgroundImagePosition = useResponsiveProp(position, 'none')
283
- const backgroundImageAlign = useResponsiveProp(align, 'stretch')
284
- const [backgroundImageWidth, setBackgroundImageWidth] = React.useState(0)
285
- const [backgroundImageHeight, setBackgroundImageHeight] = React.useState(0)
286
- if (backgroundImage)
305
+ const backgroundImagePosition = useResponsiveProp(position)
306
+ const backgroundImageAlign = useResponsiveProp(align)
307
+ const imageSourceViewport = formatImageSource(useResponsiveProp(src))
308
+
309
+ if (backgroundImage && src) {
310
+ const { paddingTop, paddingBottom, paddingLeft, paddingRight, ...containerStyle } = boxStyles
311
+
312
+ const hasPadding = paddingTop || paddingBottom || paddingLeft || paddingRight
313
+ const paddedContent = hasPadding ? (
314
+ <View style={{ paddingTop, paddingBottom, paddingLeft, paddingRight }}>{children}</View>
315
+ ) : (
316
+ children
317
+ )
318
+
287
319
  content = setBackgroundImage({
288
- src,
320
+ src: imageSourceViewport,
289
321
  alt,
290
322
  backgroundImageResizeMode,
291
323
  backgroundImagePosition,
292
324
  backgroundImageAlign,
293
- backgroundImageWidth,
294
- backgroundImageHeight,
295
- content
325
+ content: paddedContent,
326
+ testID
296
327
  })
297
328
 
298
- React.useEffect(() => {
299
- if (backgroundImage && backgroundImageWidth === 0 && backgroundImageHeight === 0) {
300
- Image.getSize(src, (width, height) => {
301
- // Only update the state if the size has changed
302
- if (width !== backgroundImageWidth || height !== backgroundImageHeight) {
303
- setBackgroundImageWidth(width)
304
- setBackgroundImageHeight(height)
305
- }
306
- })
329
+ const dataSetValue = boxMediaIds ? { media: boxMediaIds, ...dataSet } : dataSet
330
+
331
+ if (scroll) {
332
+ const scrollProps = typeof scroll === 'object' ? scroll : {}
333
+ scrollProps.contentContainerStyle = [containerStyle, scrollProps.contentContainerStyle]
334
+ return (
335
+ <ScrollView {...scrollProps} {...props} testID={testID} dataSet={dataSetValue} ref={ref}>
336
+ {content}
337
+ </ScrollView>
338
+ )
307
339
  }
308
- }, [backgroundImage, backgroundImageWidth, backgroundImageHeight, src])
340
+ return (
341
+ <View {...props} style={containerStyle} testID={testID} dataSet={dataSetValue} ref={ref}>
342
+ {content}
343
+ </View>
344
+ )
345
+ }
309
346
 
310
347
  const dataSetValue = boxMediaIds ? { media: boxMediaIds, ...dataSet } : dataSet
311
348
 
@@ -416,10 +453,12 @@ Box.propTypes = {
416
453
  */
417
454
  customGradient: PropTypes.func,
418
455
  /**
419
- * Use this prop to add a background image to the box.
456
+ * Apply background image to the box.
420
457
  */
421
458
  backgroundImage: PropTypes.shape({
422
- src: PropTypes.string.isRequired,
459
+ // The image src is either a URI string or a number (when a local image src is bundled in IOS or Android app)
460
+ // src is an object when used responsively to provide different image sources for different screen sizes
461
+ src: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]).isRequired,
423
462
  alt: PropTypes.string,
424
463
  resizeMode: responsiveProps.getTypeOptionallyByViewport(
425
464
  PropTypes.oneOf(['cover', 'contain', 'stretch', 'repeat', 'center'])
@@ -436,18 +475,21 @@ Box.propTypes = {
436
475
  export default Box
437
476
 
438
477
  const staticStyles = StyleSheet.create({
439
- backgroundImageContainer: {
440
- flex: 1
441
- },
442
- containedContainer: {
443
- flex: 1,
444
- overflow: 'hidden'
478
+ imageBackground: { width: '100%', height: '100%' },
479
+ contentOverlay: {
480
+ position: 'relative',
481
+ width: '100%',
482
+ height: '100%',
483
+ zIndex: 1
445
484
  },
446
- containedView: {
447
- zIndex: -1,
448
- position: 'absolute'
485
+ containContainer: {
486
+ width: '100%',
487
+ height: '100%',
488
+ overflow: 'hidden',
489
+ position: 'relative'
449
490
  },
450
- containedImage: {
491
+ containImage: {
492
+ position: 'absolute',
451
493
  width: '100%',
452
494
  height: '100%'
453
495
  }
@@ -1,21 +1,54 @@
1
- export default {
2
- 'top-start': { top: 0 },
3
- 'top-center': { left: 0, right: 0, marginHorizontal: 'auto' },
1
+ import { Platform } from 'react-native'
2
+
3
+ const webStyles = {
4
+ 'top-start': { top: 0, left: 0 },
5
+ 'top-center': { top: 0, left: '50%', transform: [{ translateX: '-50%' }] },
4
6
  'top-end': { top: 0, right: 0 },
5
7
  'right-start': { top: 0, right: 0 },
6
- 'left-start': { top: 0 },
7
- 'left-center': { top: 0, bottom: 0, marginVertical: 'auto' },
8
- 'none-start': { top: 0, bottom: 0, marginVertical: 'auto' },
9
- 'none-center': { top: 0, bottom: 0, left: 0, right: 0, margin: 'auto' },
10
- 'right-center': { top: 0, bottom: 0, right: 0, marginVertical: 'auto' },
11
- 'none-end': { top: 0, bottom: 0, right: 0, marginVertical: 'auto' },
8
+ 'left-start': { top: 0, left: 0 },
9
+ 'left-center': { top: '50%', left: 0, transform: [{ translateY: '-50%' }] },
10
+ 'right-center': { top: '50%', right: 0, transform: [{ translateY: '-50%' }] },
12
11
  'bottom-start': { bottom: 0, left: 0 },
13
12
  'left-end': { bottom: 0, left: 0 },
14
- 'bottom-center': { left: 0, right: 0, bottom: 0, marginHorizontal: 'auto' },
15
- 'bottom-end': { right: 0, bottom: 0 },
16
- 'right-end': { right: 0, bottom: 0 },
17
- 'top-stretch': { left: 0, right: 0, width: '100%' },
18
- 'left-stretch': { top: 0, bottom: 0, height: '100%' },
13
+ 'bottom-center': { bottom: 0, left: '50%', transform: [{ translateX: '-50%' }] },
14
+ 'bottom-end': { bottom: 0, right: 0 },
15
+ 'right-end': { bottom: 0, right: 0 },
16
+ 'top-stretch': { top: 0, left: 0, right: 0, width: '100%' },
17
+ 'left-stretch': { top: 0, bottom: 0, left: 0, height: '100%' },
19
18
  'right-stretch': { top: 0, bottom: 0, right: 0, height: '100%' },
20
- 'bottom-stretch': { left: 0, right: 0, bottom: 0, width: '100%' }
19
+ 'bottom-stretch': { bottom: 0, left: 0, right: 0, width: '100%' }
21
20
  }
21
+
22
+ const webBackgroundPositions = {
23
+ 'top-start': 'left top',
24
+ 'top-center': 'center top',
25
+ 'top-end': 'right top',
26
+ 'bottom-start': 'left bottom',
27
+ 'bottom-center': 'center bottom',
28
+ 'bottom-end': 'right bottom',
29
+ 'left-center': 'left center',
30
+ 'right-center': 'right center'
31
+ }
32
+
33
+ const nativeStyles = {
34
+ 'top-start': { top: 0, left: 0, width: 150, height: 200 },
35
+ 'top-center': { top: 0, left: '50%', marginLeft: -75, width: 150, height: 200 },
36
+ 'top-end': { top: 0, right: 0, width: 150, height: 200 },
37
+ 'right-start': { top: 0, right: 0, width: 150, height: 200 },
38
+ 'left-start': { top: 0, left: 0, width: 150, height: 200 },
39
+ 'left-center': { left: 0, top: '50%', marginTop: -100, width: 150, height: 200 },
40
+ 'right-center': { right: 0, top: '50%', marginTop: -100, width: 150, height: 200 },
41
+ 'bottom-start': { bottom: 0, left: 0, width: 150, height: 200 },
42
+ 'left-end': { bottom: 0, left: 0, width: 150, height: 200 },
43
+ 'bottom-center': { bottom: 0, left: '50%', marginLeft: -75, width: 150, height: 200 },
44
+ 'bottom-end': { bottom: 0, right: 0, width: 150, height: 200 },
45
+ 'right-end': { bottom: 0, right: 0, width: 150, height: 200 },
46
+ 'top-stretch': { top: 0, left: 0, right: 0, width: '100%' },
47
+ 'left-stretch': { top: 0, bottom: 0, left: 0, height: '100%' },
48
+ 'right-stretch': { top: 0, bottom: 0, right: 0, height: '100%' },
49
+ 'bottom-stretch': { bottom: 0, left: 0, right: 0, width: '100%' }
50
+ }
51
+
52
+ export const backgroundPositions = Platform.OS === 'web' ? webBackgroundPositions : {}
53
+
54
+ export default Platform.OS === 'web' ? webStyles : nativeStyles
@@ -12,7 +12,8 @@ import {
12
12
  a11yProps,
13
13
  viewProps,
14
14
  useCopy,
15
- unpackFragment
15
+ unpackFragment,
16
+ isTouchDevice
16
17
  } from '../utils'
17
18
  import { useA11yInfo } from '../A11yInfoProvider'
18
19
  import { CarouselProvider } from './CarouselContext'
@@ -799,7 +800,7 @@ const Carousel = React.forwardRef(
799
800
  return false
800
801
  }
801
802
  if (Platform.OS === 'web') {
802
- return !!(viewport === 'xs' || viewport === 'sm')
803
+ return !!(viewport === 'xs' || viewport === 'sm' || (viewport === 'md' && isTouchDevice()))
803
804
  }
804
805
  return true
805
806
  }, [viewport, totalItems])
@@ -479,9 +479,9 @@ MultiSelectFilter.propTypes = {
479
479
  */
480
480
  label: PropTypes.string.isRequired,
481
481
  /**
482
- * The text for the subtitle
482
+ * The text for the subtitle. Can also be JSX.
483
483
  */
484
- subtitle: PropTypes.string,
484
+ subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
485
485
  /**
486
486
  * An optional unique string may be provided to identify the ButtonDropdown.
487
487
  * If not provided, the label is used.
@@ -137,12 +137,24 @@ const getStepTestID = (isCompleted, isCurrent) => {
137
137
 
138
138
  return testID
139
139
  }
140
+ const selectBarContainerStyles = (themeTokens, isCompleted, isCurrent) => ({
141
+ backgroundColor: isCompleted
142
+ ? themeTokens.barCompletedBackgroundColor
143
+ : themeTokens.barBackgroundColor,
144
+ height: themeTokens.barHeight,
145
+ ...(isCurrent && {
146
+ backgroundColor: themeTokens.barCurrentBackgroundColor
147
+ })
148
+ })
140
149
 
141
150
  /**
142
151
  * A single step of a StepTracker.
143
152
  */
144
153
  const Step = React.forwardRef(
145
- ({ label, name, status = 0, stepCount = 0, stepIndex = 0, tokens, ...rest }, ref) => {
154
+ (
155
+ { label, name, status = 0, stepCount = 0, stepIndex = 0, tokens, isBarVariant, ...rest },
156
+ ref
157
+ ) => {
146
158
  const { completedIcon, showStepLabel, showStepName, textStepTrackerLabel, ...themeTokens } =
147
159
  tokens
148
160
  const isFirst = stepIndex === 0
@@ -162,35 +174,43 @@ const Step = React.forwardRef(
162
174
  ref={ref}
163
175
  {...selectProps(rest)}
164
176
  >
165
- <StackView
166
- direction="row"
167
- space={0}
168
- tokens={{ alignItems: 'center', flexGrow: 0, justifyContent: 'center' }}
169
- >
177
+ {isBarVariant && (
170
178
  <View
171
- style={[
172
- staticStyles.connector,
173
- !isFirst && selectConnectorStyles(themeTokens, isActive)
174
- ]}
175
- />
176
- <View
177
- style={[staticStyles.knob, selectKnobStyles(themeTokens, isCompleted, isCurrent)]}
179
+ style={selectBarContainerStyles(themeTokens, isCompleted, isCurrent)}
178
180
  testID={getStepTestID(isCompleted, isCurrent)}
179
- >
180
- {isCompleted && completedIcon && (
181
- <Icon icon={completedIcon} tokens={selectCompletedIconTokens(themeTokens)} />
182
- )}
183
- {((isCurrent && completedIcon) || (isCurrent && !completedIcon)) && (
184
- <View style={selectCurrentInnerStyles(themeTokens)} />
185
- )}
186
- </View>
187
- <View
188
- style={[
189
- staticStyles.connector,
190
- !isLast && selectConnectorStyles(themeTokens, isCompleted)
191
- ]}
192
181
  />
193
- </StackView>
182
+ )}
183
+ {!isBarVariant && (
184
+ <StackView
185
+ direction="row"
186
+ space={0}
187
+ tokens={{ alignItems: 'center', flexGrow: 0, justifyContent: 'center' }}
188
+ >
189
+ <View
190
+ style={[
191
+ staticStyles.connector,
192
+ !isFirst && selectConnectorStyles(themeTokens, isActive)
193
+ ]}
194
+ />
195
+ <View
196
+ style={[staticStyles.knob, selectKnobStyles(themeTokens, isCompleted, isCurrent)]}
197
+ testID={getStepTestID(isCompleted, isCurrent)}
198
+ >
199
+ {isCompleted && completedIcon && (
200
+ <Icon icon={completedIcon} tokens={selectCompletedIconTokens(themeTokens)} />
201
+ )}
202
+ {((isCurrent && completedIcon) || (isCurrent && !completedIcon)) && (
203
+ <View style={selectCurrentInnerStyles(themeTokens)} />
204
+ )}
205
+ </View>
206
+ <View
207
+ style={[
208
+ staticStyles.connector,
209
+ !isLast && selectConnectorStyles(themeTokens, isCompleted)
210
+ ]}
211
+ />
212
+ </StackView>
213
+ )}
194
214
  {showStepLabel && (
195
215
  <View style={[staticStyles.stepLabelContainer, selectLabelContainerStyles(tokens)]}>
196
216
  {showStepName && (
@@ -9,6 +9,8 @@ import useCopy from '../utils/useCopy'
9
9
  import Step from './Step'
10
10
  import defaultDictionary from './dictionary'
11
11
 
12
+ const STYLE_BAR_VARIANT = 'bar'
13
+
12
14
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
13
15
 
14
16
  const selectContainerStyles = ({
@@ -38,6 +40,10 @@ const selectStepTrackerLabelStyles = (
38
40
  themeOptions
39
41
  })
40
42
 
43
+ const selectStepsContainerStyles = ({ barGap }) => ({
44
+ gap: barGap
45
+ })
46
+
41
47
  /**
42
48
  * StepTracker component shows the current position in a sequence of steps.
43
49
  *
@@ -83,6 +89,7 @@ const StepTracker = React.forwardRef(
83
89
  },
84
90
  ref
85
91
  ) => {
92
+ const isBarVariant = variant?.style === STYLE_BAR_VARIANT
86
93
  const viewport = useViewport()
87
94
  const { showStepTrackerLabel, ...themeTokens } = useThemeTokens(
88
95
  'StepTracker',
@@ -135,7 +142,7 @@ const StepTracker = React.forwardRef(
135
142
  <View ref={ref} style={selectContainerStyles(themeTokens)} {...selectedProps}>
136
143
  <StackView space={0}>
137
144
  <View
138
- style={staticStyles.stepsContainer}
145
+ style={[staticStyles.stepsContainer, selectStepsContainerStyles(themeTokens)]}
139
146
  accessibilityRole={stepsContainerAccessibilityRole}
140
147
  >
141
148
  {steps.map((label, index) => {
@@ -150,6 +157,7 @@ const StepTracker = React.forwardRef(
150
157
  tokens={themeTokens}
151
158
  accessibilityRole={stepAccessibilityRole}
152
159
  accessibilityCurrent={current === index && Platform.OS === 'web' && 'step'}
160
+ isBarVariant={isBarVariant}
153
161
  />
154
162
  )
155
163
  })}
@@ -74,6 +74,7 @@ const TabBar = React.forwardRef(
74
74
  onPress={() => handlePress(item.id)}
75
75
  id={`tab-item-${index}`}
76
76
  accessibilityRole="tablist"
77
+ tokens={item.tokens}
77
78
  />
78
79
  ))}
79
80
  </View>
@@ -93,7 +94,8 @@ TabBar.propTypes = {
93
94
  icon: PropTypes.node,
94
95
  iconActive: PropTypes.node,
95
96
  label: PropTypes.string.isRequired,
96
- href: PropTypes.string
97
+ href: PropTypes.string,
98
+ tokens: getTokensPropType('TabBarItem')
97
99
  })
98
100
  ).isRequired,
99
101
  /** Id of the initially selected item. */
@@ -1,3 +1,6 @@
1
1
  import TabBar from './TabBar'
2
+ import TabBarItem from './TabBarItem'
3
+
4
+ TabBar.Item = TabBarItem
2
5
 
3
6
  export default TabBar
@@ -16,7 +16,7 @@ function createPortalNode(nodeId) {
16
16
  left: ${window.scrollX}px;
17
17
  right: 0;
18
18
  bottom: 0;
19
- z-index: 9999;
19
+ z-index: 100000;
20
20
  pointer-events: none;
21
21
  `
22
22
 
@@ -25,3 +25,4 @@ export { default as convertFromMegaByteToByte } from './convertFromMegaByteToByt
25
25
  export { default as formatImageSource } from './formatImageSource'
26
26
  export { default as getSpacingScale } from './getSpacingScale'
27
27
  export { default as useVariants } from './useVariants'
28
+ export { default as isTouchDevice } from './isTouchDevice'
@@ -0,0 +1,34 @@
1
+ import { Platform } from 'react-native'
2
+
3
+ /**
4
+ * Determines if the current device supports touch interactions
5
+ *
6
+ * @returns {boolean} True if the device supports touch, false otherwise
7
+ */
8
+ const isTouchDevice = () => {
9
+ if (Platform.OS !== 'web') {
10
+ return true
11
+ }
12
+
13
+ if (typeof window !== 'undefined') {
14
+ if ('ontouchstart' in window) {
15
+ return true
16
+ }
17
+
18
+ if (window.navigator && window.navigator.maxTouchPoints > 0) {
19
+ return true
20
+ }
21
+
22
+ if (window.navigator && window.navigator.msMaxTouchPoints > 0) {
23
+ return true
24
+ }
25
+
26
+ if (window.matchMedia && window.matchMedia('(pointer: coarse)').matches) {
27
+ return true
28
+ }
29
+ }
30
+
31
+ return false
32
+ }
33
+
34
+ export default isTouchDevice