@telus-uds/components-base 3.18.0 → 3.20.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 (52) hide show
  1. package/CHANGELOG.md +32 -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/Button/ButtonDropdown.js +1 -0
  6. package/lib/cjs/Carousel/Carousel.js +1 -1
  7. package/lib/cjs/ExpandCollapseMini/ExpandCollapseMiniControl.js +8 -21
  8. package/lib/cjs/Link/LinkBase.js +8 -9
  9. package/lib/cjs/MultiSelectFilter/MultiSelectFilter.js +10 -10
  10. package/lib/cjs/Spacer/Spacer.js +65 -5
  11. package/lib/cjs/StepTracker/Step.js +12 -1
  12. package/lib/cjs/StepTracker/StepTracker.js +15 -4
  13. package/lib/cjs/TabBar/TabBar.js +4 -2
  14. package/lib/cjs/TabBar/index.js +2 -0
  15. package/lib/cjs/Tooltip/Backdrop.js +1 -1
  16. package/lib/cjs/utils/index.js +17 -1
  17. package/lib/cjs/utils/isTouchDevice.js +34 -0
  18. package/lib/cjs/utils/useMediaQuerySpacing.js +121 -0
  19. package/lib/esm/Box/Box.js +113 -63
  20. package/lib/esm/Box/backgroundImageStylesMap.js +134 -27
  21. package/lib/esm/Button/ButtonDropdown.js +1 -0
  22. package/lib/esm/Carousel/Carousel.js +2 -2
  23. package/lib/esm/ExpandCollapseMini/ExpandCollapseMiniControl.js +8 -21
  24. package/lib/esm/Link/LinkBase.js +8 -9
  25. package/lib/esm/MultiSelectFilter/MultiSelectFilter.js +10 -10
  26. package/lib/esm/Spacer/Spacer.js +66 -6
  27. package/lib/esm/StepTracker/Step.js +12 -1
  28. package/lib/esm/StepTracker/StepTracker.js +15 -4
  29. package/lib/esm/TabBar/TabBar.js +4 -2
  30. package/lib/esm/TabBar/index.js +2 -0
  31. package/lib/esm/Tooltip/Backdrop.js +1 -1
  32. package/lib/esm/utils/index.js +3 -1
  33. package/lib/esm/utils/isTouchDevice.js +27 -0
  34. package/lib/esm/utils/useMediaQuerySpacing.js +116 -0
  35. package/lib/package.json +2 -2
  36. package/package.json +2 -2
  37. package/src/Box/Box.jsx +97 -55
  38. package/src/Box/backgroundImageStylesMap.js +48 -15
  39. package/src/Button/ButtonDropdown.jsx +1 -0
  40. package/src/Carousel/Carousel.jsx +3 -2
  41. package/src/ExpandCollapseMini/ExpandCollapseMiniControl.jsx +9 -16
  42. package/src/Link/LinkBase.jsx +11 -9
  43. package/src/MultiSelectFilter/MultiSelectFilter.jsx +11 -10
  44. package/src/Spacer/Spacer.jsx +54 -7
  45. package/src/StepTracker/Step.jsx +47 -27
  46. package/src/StepTracker/StepTracker.jsx +9 -1
  47. package/src/TabBar/TabBar.jsx +3 -1
  48. package/src/TabBar/index.js +3 -0
  49. package/src/Tooltip/Backdrop.jsx +1 -1
  50. package/src/utils/index.js +2 -0
  51. package/src/utils/isTouchDevice.js +34 -0
  52. package/src/utils/useMediaQuerySpacing.js +124 -0
@@ -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
@@ -56,6 +56,7 @@ const selectDescriptionTextStyles = (tokens) => ({
56
56
  fontName: tokens?.descriptionFontName,
57
57
  fontSize: tokens?.descriptionFontSize,
58
58
  fontWeight: tokens?.descriptionFontWeight,
59
+ lineHeight: tokens?.descriptionLineHeight,
59
60
  fontColor: tokens?.color
60
61
  }),
61
62
  paddingBottom: tokens?.descriptionTextPaddingBottom
@@ -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])
@@ -62,36 +62,28 @@ const ExpandCollapseMiniControl = React.forwardRef(
62
62
  const iconBaselineOffset = 0
63
63
  const hoverTranslateY = 4
64
64
 
65
- // Calculate baseline alignment to vertically center icon with text
66
- // This combines font and icon metrics with adjustments for visual balance
67
- const fontBaseline = fontSize / hoverTranslateY // Quarter of font size - adjusts for text's visual center point
68
- const iconBaseline = iconSize / hoverTranslateY // Quarter of icon size - adjusts for icon's visual center point
69
- const staticOffset = hoverTranslateY // Fixed downward adjustment to fine-tune vertical alignment
70
- const sizeCompensation = -Math.abs(iconSize - fontSize) // Compensates when icon and text sizes differ significantly
65
+ const fontBaseline = fontSize / hoverTranslateY
66
+ const iconBaseline = iconSize / hoverTranslateY
67
+ const staticOffset = hoverTranslateY
68
+ const sizeCompensation = -Math.abs(iconSize - fontSize)
71
69
 
72
70
  const baselineAlignment = fontBaseline + iconBaseline - staticOffset + sizeCompensation
73
71
 
74
- if (Platform.OS !== 'web') {
75
- // For native platforms, use baseline alignment with optional offset
76
- return { iconTranslateY: baselineAlignment + iconBaselineOffset }
77
- }
72
+ const mobileAdjustment = Platform.OS !== 'web' ? -2 : 0
78
73
 
79
74
  if (isHovered) {
80
- // Apply animation offset to the baseline-aligned position
81
- // When expanded: move icon UP (1.3 the hover distance for clear movement)
82
- // When collapsed: move icon DOWN (single hover distance)
83
75
  const hoverMovementDistance = 1.3
84
76
  const animationOffset = expanded
85
77
  ? -(hoverTranslateY * hoverMovementDistance)
86
78
  : hoverTranslateY
87
79
 
88
80
  return {
89
- iconTranslateY: baselineAlignment + iconBaselineOffset + animationOffset
81
+ iconTranslateY:
82
+ baselineAlignment + iconBaselineOffset + animationOffset + mobileAdjustment
90
83
  }
91
84
  }
92
85
 
93
- // Default state uses baseline alignment with optional offset
94
- return { iconTranslateY: baselineAlignment + iconBaselineOffset }
86
+ return { iconTranslateY: baselineAlignment + iconBaselineOffset + mobileAdjustment }
95
87
  }
96
88
 
97
89
  return (
@@ -103,6 +95,7 @@ const ExpandCollapseMiniControl = React.forwardRef(
103
95
  ...linkTokens,
104
96
  ...getTokens(linkState),
105
97
  iconSize,
98
+ blockFontSize: fontSize,
106
99
  blockLineHeight: lineHeight
107
100
  })}
108
101
  ref={ref}
@@ -171,9 +171,12 @@ const LinkBase = React.forwardRef(
171
171
  const themeTokens = resolveLinkTokens(linkState)
172
172
  const outerBorderStyles = selectOuterBorderStyles(themeTokens)
173
173
  const decorationStyles = selectDecorationStyles(themeTokens)
174
+
175
+ const mobileCompensation = null
176
+
174
177
  return [
175
178
  outerBorderStyles,
176
- staticStyles.outerBorderStyles,
179
+ mobileCompensation,
177
180
  blockLeftStyle,
178
181
  decorationStyles,
179
182
  hasIcon && staticStyles.rowContainer
@@ -191,11 +194,14 @@ const LinkBase = React.forwardRef(
191
194
  const IconComponent = icon || themeTokens.icon
192
195
  const { iconSpace } = themeTokens
193
196
 
197
+ const isTextOnlyLink = !IconComponent && !icon && accessibilityRole === 'link'
198
+ const adjustedIconSpace = Platform.OS !== 'web' && isTextOnlyLink ? 0 : iconSpace
199
+
194
200
  return (
195
201
  <IconText
196
202
  icon={IconComponent}
197
203
  iconPosition={iconPosition}
198
- space={iconSpace}
204
+ space={adjustedIconSpace}
199
205
  iconProps={{
200
206
  ...iconProps,
201
207
  tokens: iconTokens,
@@ -274,15 +280,11 @@ const staticStyles = StyleSheet.create({
274
280
  }
275
281
  })
276
282
  },
277
- outerBorderStyles: {
283
+ outerBorderCompensation: {
278
284
  ...(Platform.OS !== 'web' && {
279
- margin: 0,
280
285
  marginHorizontal: 2,
281
- padding: 0
282
- }),
283
- ...(Platform.OS === 'android' && {
284
- paddingHorizontal: 2,
285
- paddingTop: 2
286
+ paddingHorizontal: Platform.OS === 'android' ? 2 : 0,
287
+ paddingTop: Platform.OS === 'android' ? 2 : 0
286
288
  })
287
289
  }
288
290
  })
@@ -65,7 +65,6 @@ const selectContainerStyle = (windowHeight, windowWidth) => ({
65
65
  })
66
66
 
67
67
  const TOTAL_COLUMNS = 12
68
- const MAX_ITEMS_THRESHOLD = 12
69
68
 
70
69
  const MultiSelectFilter = React.forwardRef(
71
70
  (
@@ -174,13 +173,15 @@ const MultiSelectFilter = React.forwardRef(
174
173
  const getCopy = useCopy({ dictionary, copy })
175
174
  const colSizeNotMobile = items.length > rowLimit ? 2 : 1
176
175
  const colSize = viewport !== 'xs' ? colSizeNotMobile : 1
177
- const itemsLengthNotMobile = items.length > 24 ? items.length / 2 : rowLimit
178
- const rowLength = viewport !== 'xs' ? itemsLengthNotMobile : items.length
176
+
177
+ let rowLength = items.length
178
+ if (viewport !== 'xs' && colSize === 2) {
179
+ rowLength = Math.ceil(items.length / 2)
180
+ }
179
181
 
180
182
  React.useEffect(() => {
181
- if (colSize === 1) return setMaxWidth(false)
182
- return colSize === 2 && setMaxWidth(true)
183
- }, [colSize])
183
+ setMaxWidth(items.length >= rowLimit)
184
+ }, [items.length, rowLimit])
184
185
 
185
186
  React.useEffect(() => setCheckedIds(currentValues ?? []), [currentValues])
186
187
 
@@ -414,14 +415,14 @@ const MultiSelectFilter = React.forwardRef(
414
415
  dismissWhenPressedOutside={dismissWhenPressedOutside}
415
416
  onClose={onClose}
416
417
  overlaidPosition={overlaidPosition}
417
- maxHeight={items.length > MAX_ITEMS_THRESHOLD ? true : maxHeight}
418
+ maxHeight={items.length >= rowLimit ? true : maxHeight}
418
419
  maxHeightSize={maxHeightSize}
419
420
  maxWidthSize={maxWidthSize}
420
421
  minHeight={minHeight}
421
422
  minWidth={minWidth}
422
423
  tokens={{
423
424
  ...tokens,
424
- maxWidth: items.length > MAX_ITEMS_THRESHOLD ? true : maxWidth,
425
+ maxWidth: items.length >= rowLimit ? true : maxWidth,
425
426
  borderColor: containerBorderColor
426
427
  }}
427
428
  copy={copy}
@@ -479,9 +480,9 @@ MultiSelectFilter.propTypes = {
479
480
  */
480
481
  label: PropTypes.string.isRequired,
481
482
  /**
482
- * The text for the subtitle
483
+ * The text for the subtitle. Can also be JSX.
483
484
  */
484
- subtitle: PropTypes.string,
485
+ subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
485
486
  /**
486
487
  * An optional unique string may be provided to identify the ButtonDropdown.
487
488
  * If not provided, the label is used.
@@ -1,7 +1,17 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { StyleSheet, View } from 'react-native'
4
- import { a11yProps, selectSystemProps, spacingProps, useSpacingScale, viewProps } from '../utils'
3
+ import { View, StyleSheet } from 'react-native'
4
+ import {
5
+ a11yProps,
6
+ selectSystemProps,
7
+ spacingProps,
8
+ useSpacingScale,
9
+ viewProps,
10
+ StyleSheet as StyleSheetUtils,
11
+ createMediaQueryStyles
12
+ } from '../utils'
13
+ import useMediaQuerySpacing from '../utils/useMediaQuerySpacing'
14
+ import useTheme from '../ThemeProvider/useTheme'
5
15
 
6
16
  /**
7
17
  * @typedef {import('../utils/props/spacingProps.js').SpacingValue} SpacingValue
@@ -56,11 +66,43 @@ const selectSizeStyle = (size, direction) => ({
56
66
  * Spacer has no content and is ignored by tools such as screen readers. Use `Divider` for
57
67
  * separations between elements that may be treated as semantically meaningful on web.
58
68
  */
59
- const Spacer = React.forwardRef(({ space = 1, direction = 'column', ...rest }, ref) => {
60
- const size = useSpacingScale(space)
61
- const sizeStyle = selectSizeStyle(size, direction)
69
+ const Spacer = React.forwardRef(({ space = 1, direction = 'column', dataSet, ...rest }, ref) => {
70
+ const {
71
+ themeOptions: { enableMediaQueryStyleSheet }
72
+ } = useTheme()
62
73
 
63
- return <View ref={ref} style={[staticStyles.stretch, sizeStyle]} {...selectProps(rest)} />
74
+ const { sizeByViewport } = useMediaQuerySpacing(space)
75
+
76
+ const fallbackSize = useSpacingScale(space)
77
+ const sizeStyle = selectSizeStyle(fallbackSize, direction)
78
+
79
+ let spacerStyles
80
+ let dataSetValue = dataSet
81
+
82
+ if (enableMediaQueryStyleSheet) {
83
+ const sizeKey = direction === 'row' ? 'width' : 'height'
84
+ const stylesByViewport = {
85
+ xs: { [sizeKey]: sizeByViewport.xs, ...staticStyles.stretch },
86
+ sm: { [sizeKey]: sizeByViewport.sm, ...staticStyles.stretch },
87
+ md: { [sizeKey]: sizeByViewport.md, ...staticStyles.stretch },
88
+ lg: { [sizeKey]: sizeByViewport.lg, ...staticStyles.stretch },
89
+ xl: { [sizeKey]: sizeByViewport.xl, ...staticStyles.stretch }
90
+ }
91
+ const mediaQueryStyles = createMediaQueryStyles(stylesByViewport)
92
+
93
+ const { ids, styles } = StyleSheetUtils.create({
94
+ spacer: {
95
+ ...mediaQueryStyles
96
+ }
97
+ })
98
+
99
+ spacerStyles = styles.spacer
100
+ dataSetValue = { media: ids.spacer, ...dataSet }
101
+ } else {
102
+ spacerStyles = [staticStyles.stretch, sizeStyle]
103
+ }
104
+
105
+ return <View ref={ref} style={spacerStyles} dataSet={dataSetValue} {...selectProps(rest)} />
64
106
  })
65
107
  Spacer.displayName = 'Spacer'
66
108
 
@@ -78,7 +120,12 @@ Spacer.propTypes = {
78
120
  * - `'column'` (default) applies space vertically; has a fixed height and not width.
79
121
  * - `'row'` applies space horizontally; has a fixed width and not height.
80
122
  */
81
- direction: PropTypes.oneOf(['column', 'row'])
123
+ direction: PropTypes.oneOf(['column', 'row']),
124
+ /**
125
+ * Data attributes to be applied to the element. When media query stylesheet is enabled,
126
+ * this will include media query IDs for responsive styling.
127
+ */
128
+ dataSet: PropTypes.object
82
129
  }
83
130
 
84
131
  const staticStyles = StyleSheet.create({
@@ -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
 
@@ -9,6 +9,7 @@ export { default as info } from './info'
9
9
  export { default as useCopy } from './useCopy'
10
10
  export { default as useHash } from './useHash'
11
11
  export { default as useSpacingScale } from './useSpacingScale'
12
+ export { default as useMediaQuerySpacing } from './useMediaQuerySpacing'
12
13
  export { default as useResponsiveProp } from './useResponsiveProp'
13
14
  export { default as useOverlaidPosition } from './useOverlaidPosition'
14
15
  export { default as useSafeLayoutEffect } from './useSafeLayoutEffect'
@@ -25,3 +26,4 @@ export { default as convertFromMegaByteToByte } from './convertFromMegaByteToByt
25
26
  export { default as formatImageSource } from './formatImageSource'
26
27
  export { default as getSpacingScale } from './getSpacingScale'
27
28
  export { default as useVariants } from './useVariants'
29
+ 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