@telus-uds/components-base 3.22.0 → 3.24.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 (84) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/lib/cjs/Button/Button.js +2 -0
  3. package/lib/cjs/Button/ButtonBase.js +10 -5
  4. package/lib/cjs/Button/ButtonDropdown.js +2 -0
  5. package/lib/cjs/Button/ButtonGroup.js +45 -38
  6. package/lib/cjs/Button/propTypes.js +6 -0
  7. package/lib/cjs/Card/CardBase.js +97 -17
  8. package/lib/cjs/Card/PressableCardBase.js +12 -8
  9. package/lib/cjs/Carousel/Carousel.js +52 -19
  10. package/lib/cjs/Carousel/CarouselItem/CarouselItem.js +23 -3
  11. package/lib/cjs/HorizontalScroll/HorizontalScroll.js +5 -2
  12. package/lib/cjs/Icon/Icon.js +11 -11
  13. package/lib/cjs/Icon/IconText.js +0 -1
  14. package/lib/cjs/Listbox/GroupControl.js +44 -44
  15. package/lib/cjs/Listbox/Listbox.js +63 -20
  16. package/lib/cjs/Listbox/ListboxGroup.js +141 -9
  17. package/lib/cjs/Listbox/ListboxOverlay.js +13 -5
  18. package/lib/cjs/Listbox/PressableItem.js +8 -4
  19. package/lib/cjs/Listbox/SecondLevelHeader.js +201 -0
  20. package/lib/cjs/Listbox/dictionary.js +14 -0
  21. package/lib/cjs/Shortcuts/Shortcuts.js +169 -0
  22. package/lib/cjs/Shortcuts/ShortcutsItem.js +280 -0
  23. package/lib/cjs/Shortcuts/index.js +16 -0
  24. package/lib/cjs/TextInput/TextInputBase.js +5 -1
  25. package/lib/cjs/Tooltip/Tooltip.native.js +2 -0
  26. package/lib/cjs/Validator/Validator.js +171 -135
  27. package/lib/cjs/index.js +15 -0
  28. package/lib/esm/Button/Button.js +2 -0
  29. package/lib/esm/Button/ButtonBase.js +10 -5
  30. package/lib/esm/Button/ButtonDropdown.js +2 -0
  31. package/lib/esm/Button/ButtonGroup.js +44 -39
  32. package/lib/esm/Button/propTypes.js +6 -0
  33. package/lib/esm/Card/CardBase.js +97 -17
  34. package/lib/esm/Card/PressableCardBase.js +10 -8
  35. package/lib/esm/Carousel/Carousel.js +52 -19
  36. package/lib/esm/Carousel/CarouselItem/CarouselItem.js +23 -3
  37. package/lib/esm/HorizontalScroll/HorizontalScroll.js +6 -3
  38. package/lib/esm/Icon/Icon.js +11 -11
  39. package/lib/esm/Icon/IconText.js +0 -1
  40. package/lib/esm/Listbox/GroupControl.js +44 -44
  41. package/lib/esm/Listbox/Listbox.js +64 -21
  42. package/lib/esm/Listbox/ListboxGroup.js +143 -11
  43. package/lib/esm/Listbox/ListboxOverlay.js +13 -5
  44. package/lib/esm/Listbox/PressableItem.js +8 -4
  45. package/lib/esm/Listbox/SecondLevelHeader.js +194 -0
  46. package/lib/esm/Listbox/dictionary.js +8 -0
  47. package/lib/esm/Shortcuts/Shortcuts.js +160 -0
  48. package/lib/esm/Shortcuts/ShortcutsItem.js +273 -0
  49. package/lib/esm/Shortcuts/index.js +3 -0
  50. package/lib/esm/TextInput/TextInputBase.js +5 -1
  51. package/lib/esm/Tooltip/Tooltip.native.js +2 -0
  52. package/lib/esm/Validator/Validator.js +171 -135
  53. package/lib/esm/index.js +1 -0
  54. package/lib/package.json +2 -2
  55. package/package.json +2 -2
  56. package/src/Button/Button.jsx +2 -1
  57. package/src/Button/ButtonBase.jsx +18 -12
  58. package/src/Button/ButtonDropdown.jsx +2 -0
  59. package/src/Button/ButtonGroup.jsx +62 -45
  60. package/src/Button/propTypes.js +6 -0
  61. package/src/Card/CardBase.jsx +113 -14
  62. package/src/Card/PressableCardBase.jsx +17 -5
  63. package/src/Carousel/Carousel.jsx +58 -5
  64. package/src/Carousel/CarouselItem/CarouselItem.jsx +31 -3
  65. package/src/HorizontalScroll/HorizontalScroll.jsx +6 -3
  66. package/src/Icon/Icon.jsx +14 -14
  67. package/src/Icon/IconText.jsx +0 -1
  68. package/src/Listbox/GroupControl.jsx +72 -70
  69. package/src/Listbox/Listbox.jsx +67 -11
  70. package/src/Listbox/ListboxGroup.jsx +160 -27
  71. package/src/Listbox/ListboxOverlay.jsx +23 -5
  72. package/src/Listbox/PressableItem.jsx +8 -4
  73. package/src/Listbox/SecondLevelHeader.jsx +182 -0
  74. package/src/Listbox/dictionary.js +8 -0
  75. package/src/Shortcuts/Shortcuts.jsx +174 -0
  76. package/src/Shortcuts/ShortcutsItem.jsx +297 -0
  77. package/src/Shortcuts/index.js +4 -0
  78. package/src/TextInput/TextInputBase.jsx +5 -1
  79. package/src/Tooltip/Tooltip.native.jsx +2 -1
  80. package/src/Validator/Validator.jsx +180 -159
  81. package/src/index.js +1 -0
  82. package/types/Listbox.d.ts +24 -0
  83. package/types/Shortcuts.d.ts +136 -0
  84. package/types/index.d.ts +12 -0
@@ -10,6 +10,12 @@ export const textAndA11yText = ABBPropTypes.childrenOf(
10
10
 
11
11
  const buttonPropTypes = {
12
12
  tokens: getTokensPropType('Button'),
13
+ /**
14
+ * If true, the button will honor the align-items value from its parent flex container.
15
+ * If false, the button will always align to 'flex-start' in a flex container.
16
+ * Currently, `heightFull`'s default behaviour will expand to the full height of its parent's flex container. In an upcoming major release, the default behaviour will be changed to expand based on the content. To maintain the expected behaviour, you must explicitly set `heightFull: true`
17
+ */
18
+ heightFull: PropTypes.bool,
13
19
  /**
14
20
  * If true, prevents the button from being pressed, changes the cursor (on web) and accessibility
15
21
  * attributes to communicate this to the user, and applies `inactive: true` appearances from the theme
@@ -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)
@@ -407,6 +407,7 @@ const Carousel = React.forwardRef(
407
407
  ref
408
408
  ) => {
409
409
  let childrenArray = unpackFragment(children)
410
+ const isTransitioningRef = React.useRef(false)
410
411
  const viewport = useViewport()
411
412
  const totalItems = getTotalItems(enableDisplayMultipleItemsPerSlide, childrenArray, viewport)
412
413
  const autoPlayFeatureEnabled =
@@ -482,10 +483,14 @@ const Carousel = React.forwardRef(
482
483
  )
483
484
  const handleAnimationEnd = React.useCallback(
484
485
  (...args) => {
485
- if (typeof onAnimationEnd === 'function') onAnimationEnd(...args)
486
- setIsAnimating(false)
486
+ const result = args[args.length - 1]
487
+ if (result?.finished) {
488
+ if (typeof onAnimationEnd === 'function') onAnimationEnd(...args)
489
+ setIsAnimating(false)
490
+ isTransitioningRef.current = false
491
+ }
487
492
  },
488
- [onAnimationEnd]
493
+ [onAnimationEnd, isTransitioningRef]
489
494
  )
490
495
 
491
496
  const updateOffset = React.useCallback(() => {
@@ -602,7 +607,9 @@ const Carousel = React.forwardRef(
602
607
 
603
608
  const index = activeIndexRef.current + calcDelta
604
609
  if (skipChanges) {
610
+ isTransitioningRef.current = true
605
611
  animate(pan, toValue, index)
612
+
606
613
  if (enableHero) {
607
614
  animate(heroPan, toValue, index)
608
615
  }
@@ -622,6 +629,7 @@ const Carousel = React.forwardRef(
622
629
  toValue.x = finalWidth * -1 * calcDelta
623
630
  const heroToValue = { x: 0, y: 0 }
624
631
  heroToValue.x = heroContainerLayoutRef.current.width * -1 * calcDelta
632
+ isTransitioningRef.current = true
625
633
  animate(pan, toValue, index)
626
634
  if (enableHero) {
627
635
  animate(heroPan, heroToValue, index)
@@ -1012,6 +1020,15 @@ const Carousel = React.forwardRef(
1012
1020
  setisCarouselPlaying((prevState) => !prevState)
1013
1021
  }, [isCarouselPlaying, stopAutoplay, startAutoplay])
1014
1022
 
1023
+ const handleKeyDown = React.useCallback(
1024
+ (event) => {
1025
+ if (isTransitioningRef.current && event.key === 'Tab') {
1026
+ event.preventDefault()
1027
+ }
1028
+ },
1029
+ [isTransitioningRef]
1030
+ )
1031
+
1015
1032
  return (
1016
1033
  <View style={selectRootContainerStyles(enableHero, viewport)}>
1017
1034
  <View style={selectMainContainerStyles(enableHero, viewport)}>
@@ -1041,6 +1058,7 @@ const Carousel = React.forwardRef(
1041
1058
  ref={ref}
1042
1059
  {...systemProps}
1043
1060
  {...containerProps}
1061
+ {...(Platform.OS === 'web' ? { onKeyDown: handleKeyDown } : {})}
1044
1062
  >
1045
1063
  {isAutoPlayEnabled ? (
1046
1064
  <View
@@ -1130,10 +1148,45 @@ const Carousel = React.forwardRef(
1130
1148
  accessibilityLiveRegion={accessibilityLiveRegion}
1131
1149
  >
1132
1150
  {childrenArray.map((element, index) => {
1133
- const hidden = !isAnimating && index !== activeIndex
1151
+ let hidden = !isAnimating && index !== activeIndex
1152
+
1153
+ if (enablePeeking && !isAnimating) {
1154
+ if (enableDisplayMultipleItemsPerSlide) {
1155
+ const maxItemsForSlide = getMaximumItemsForSlide(
1156
+ enableDisplayMultipleItemsPerSlide,
1157
+ viewport
1158
+ )
1159
+ if (
1160
+ index >= activeIndex * maxItemsForSlide - 1 &&
1161
+ index < activeIndex * maxItemsForSlide + maxItemsForSlide + 1
1162
+ ) {
1163
+ hidden = false
1164
+ } else {
1165
+ hidden = true
1166
+ }
1167
+ } else if (index >= activeIndex - 1 && index <= activeIndex + 1) {
1168
+ hidden = false
1169
+ }
1170
+ } else if (
1171
+ !enablePeeking &&
1172
+ enableDisplayMultipleItemsPerSlide &&
1173
+ !isAnimating
1174
+ ) {
1175
+ const maxItemsForSlide = getMaximumItemsForSlide(
1176
+ enableDisplayMultipleItemsPerSlide,
1177
+ viewport
1178
+ )
1179
+ if (
1180
+ index >= activeIndex * maxItemsForSlide &&
1181
+ index < activeIndex * maxItemsForSlide + maxItemsForSlide
1182
+ ) {
1183
+ hidden = false
1184
+ }
1185
+ }
1186
+
1134
1187
  const clonedElement = React.cloneElement(element, {
1135
1188
  elementIndex: index,
1136
- hidden: enablePeeking || enableDisplayMultipleItemsPerSlide ? false : hidden,
1189
+ hidden,
1137
1190
  enablePeeking,
1138
1191
  peekingProps: getPeekingProps(viewport),
1139
1192
  enableDisplayMultipleItemsPerSlide,
@@ -114,15 +114,43 @@ const CarouselItem = React.forwardRef(
114
114
 
115
115
  const handleFocus = React.useCallback(
116
116
  (event) => {
117
- if (Platform.OS === 'web' && elementIndex >= maximumItemsForSlide * (activeIndex + 1)) {
118
- goTo(activeIndex + 1)
117
+ if (Platform.OS === 'web') {
118
+ if (enablePeeking) {
119
+ if (enableDisplayMultipleItemsPerSlide) {
120
+ const startIndex = maximumItemsForSlide * activeIndex
121
+ const endIndex = startIndex + maximumItemsForSlide - 1
122
+ if (elementIndex < startIndex) {
123
+ if (activeIndex - 1 < 0) {
124
+ goTo(0)
125
+ } else {
126
+ goTo(activeIndex - 1)
127
+ }
128
+ } else if (elementIndex > endIndex) {
129
+ goTo(activeIndex + 1)
130
+ }
131
+ } else if (elementIndex !== activeIndex) {
132
+ if (elementIndex > activeIndex) {
133
+ goTo(activeIndex + 1)
134
+ } else if (elementIndex < activeIndex) {
135
+ goTo(activeIndex - 1)
136
+ }
137
+ }
138
+ }
119
139
  }
120
140
 
121
141
  if (rest.onFocus) {
122
142
  rest.onFocus(event)
123
143
  }
124
144
  },
125
- [elementIndex, activeIndex, goTo, maximumItemsForSlide, rest]
145
+ [
146
+ elementIndex,
147
+ activeIndex,
148
+ goTo,
149
+ maximumItemsForSlide,
150
+ rest,
151
+ enablePeeking,
152
+ enableDisplayMultipleItemsPerSlide
153
+ ]
126
154
  )
127
155
 
128
156
  return (
@@ -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
@@ -43,24 +44,23 @@ const Icon = React.forwardRef(
43
44
  }
44
45
 
45
46
  const getIconContentForMobile = () => {
46
- if (Object.keys(paddingStyles).length) {
47
- return (
48
- <View
49
- style={{
50
- backgroundColor: themeTokens.backgroundColor,
51
- borderRadius: themeTokens.borderRadius,
52
- ...paddingStyles
53
- }}
54
- >
55
- {iconContent}
56
- </View>
57
- )
58
- }
59
- return iconContent
47
+ return (
48
+ <View
49
+ testID={testID}
50
+ style={{
51
+ backgroundColor: themeTokens.backgroundColor,
52
+ borderRadius: themeTokens.borderRadius,
53
+ ...paddingStyles
54
+ }}
55
+ >
56
+ {iconContent}
57
+ </View>
58
+ )
60
59
  }
61
60
 
62
61
  return Platform.OS === 'web' ? (
63
62
  <View
63
+ testID={testID}
64
64
  ref={ref}
65
65
  // eslint-disable-next-line react-native/no-inline-styles
66
66
  style={{
@@ -90,7 +90,6 @@ IconText.propTypes = {
90
90
  * `<Typography>` component, or a component that renders `<Text>`.
91
91
  */
92
92
  children: PropTypes.node
93
- /* eslint-enable react/no-unused-prop-types */
94
93
  }
95
94
 
96
95
  const staticStyles = StyleSheet.create({