@telus-uds/components-base 3.21.0 → 3.23.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 (64) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/lib/cjs/Button/Button.js +12 -3
  3. package/lib/cjs/Button/ButtonBase.js +63 -10
  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/PressableCardBase.js +3 -1
  8. package/lib/cjs/Carousel/Carousel.js +63 -22
  9. package/lib/cjs/Carousel/CarouselItem/CarouselItem.js +23 -3
  10. package/lib/cjs/Icon/Icon.js +8 -11
  11. package/lib/cjs/Icon/IconText.js +0 -1
  12. package/lib/cjs/Listbox/GroupControl.js +33 -39
  13. package/lib/cjs/Listbox/Listbox.js +22 -13
  14. package/lib/cjs/Listbox/ListboxGroup.js +2 -1
  15. package/lib/cjs/Listbox/ListboxOverlay.js +5 -2
  16. package/lib/cjs/Listbox/PressableItem.js +8 -4
  17. package/lib/cjs/TextInput/TextInputBase.js +5 -1
  18. package/lib/cjs/ThemeProvider/index.js +9 -1
  19. package/lib/cjs/ThemeProvider/useResponsiveThemeTokensCallback.js +124 -0
  20. package/lib/cjs/Validator/Validator.js +171 -135
  21. package/lib/cjs/index.js +7 -0
  22. package/lib/esm/Button/Button.js +13 -4
  23. package/lib/esm/Button/ButtonBase.js +64 -11
  24. package/lib/esm/Button/ButtonDropdown.js +2 -0
  25. package/lib/esm/Button/ButtonGroup.js +44 -39
  26. package/lib/esm/Button/propTypes.js +6 -0
  27. package/lib/esm/Card/PressableCardBase.js +3 -1
  28. package/lib/esm/Carousel/Carousel.js +63 -22
  29. package/lib/esm/Carousel/CarouselItem/CarouselItem.js +23 -3
  30. package/lib/esm/Icon/Icon.js +8 -11
  31. package/lib/esm/Icon/IconText.js +0 -1
  32. package/lib/esm/Listbox/GroupControl.js +33 -39
  33. package/lib/esm/Listbox/Listbox.js +23 -14
  34. package/lib/esm/Listbox/ListboxGroup.js +2 -1
  35. package/lib/esm/Listbox/ListboxOverlay.js +5 -2
  36. package/lib/esm/Listbox/PressableItem.js +8 -4
  37. package/lib/esm/TextInput/TextInputBase.js +5 -1
  38. package/lib/esm/ThemeProvider/index.js +1 -0
  39. package/lib/esm/ThemeProvider/useResponsiveThemeTokensCallback.js +117 -0
  40. package/lib/esm/Validator/Validator.js +171 -135
  41. package/lib/esm/index.js +1 -1
  42. package/lib/package.json +2 -2
  43. package/package.json +2 -2
  44. package/src/Button/Button.jsx +26 -5
  45. package/src/Button/ButtonBase.jsx +79 -16
  46. package/src/Button/ButtonDropdown.jsx +2 -0
  47. package/src/Button/ButtonGroup.jsx +62 -45
  48. package/src/Button/propTypes.js +6 -0
  49. package/src/Card/PressableCardBase.jsx +3 -1
  50. package/src/Carousel/Carousel.jsx +71 -7
  51. package/src/Carousel/CarouselItem/CarouselItem.jsx +31 -3
  52. package/src/Icon/Icon.jsx +11 -14
  53. package/src/Icon/IconText.jsx +0 -1
  54. package/src/Listbox/GroupControl.jsx +41 -47
  55. package/src/Listbox/Listbox.jsx +26 -9
  56. package/src/Listbox/ListboxGroup.jsx +2 -1
  57. package/src/Listbox/ListboxOverlay.jsx +7 -2
  58. package/src/Listbox/PressableItem.jsx +8 -4
  59. package/src/PriceLockup/utils/renderPrice.jsx +15 -17
  60. package/src/TextInput/TextInputBase.jsx +5 -1
  61. package/src/ThemeProvider/index.js +1 -0
  62. package/src/ThemeProvider/useResponsiveThemeTokensCallback.js +129 -0
  63. package/src/Validator/Validator.jsx +180 -159
  64. package/src/index.js +2 -1
@@ -16,7 +16,9 @@ import {
16
16
  viewProps,
17
17
  wrapStringsInText,
18
18
  withLinkRouter,
19
- contentfulProps
19
+ contentfulProps,
20
+ createMediaQueryStyles,
21
+ StyleSheet as StyleSheetUtils
20
22
  } from '../utils'
21
23
  import { IconText } from '../Icon'
22
24
 
@@ -47,21 +49,26 @@ const selectFlexAndWidthStyles = ({ width, flex }) => {
47
49
  return styles
48
50
  }
49
51
 
50
- const selectOuterContainerStyles = ({
52
+ const selectOuterContainerStyles = (
53
+ {
54
+ opacity,
55
+ outerBorderColor,
56
+ outerBorderWidth,
57
+ outerBorderGap,
58
+ borderRadius,
59
+ outerBackgroundColor,
60
+ alignSelf
61
+ },
62
+ heightFull
63
+ ) => ({
64
+ backgroundColor: outerBackgroundColor,
51
65
  opacity,
52
- outerBorderColor,
53
- outerBorderWidth,
54
- outerBorderGap,
55
- borderRadius,
56
- outerBackgroundColor
57
- }) => ({
58
66
  ...Platform.select({
59
67
  native: {
60
- alignSelf: 'flex-start'
61
- }
68
+ alignSelf: alignSelf !== undefined ? alignSelf : 'flex-start'
69
+ },
70
+ web: heightFull ? {} : { alignSelf: 'flex-start' }
62
71
  }),
63
- backgroundColor: outerBackgroundColor,
64
- opacity,
65
72
  ...applyOuterBorder({
66
73
  outerBorderGap,
67
74
  outerBorderWidth,
@@ -217,10 +224,14 @@ const ButtonBase = React.forwardRef(
217
224
  icon,
218
225
  iconPosition = icon ? 'left' : undefined,
219
226
  iconProps,
227
+ heightFull = true,
220
228
  ...rawRest
221
229
  },
222
230
  ref
223
231
  ) => {
232
+ const { themeOptions } = useTheme()
233
+ const { viewport } = rawRest
234
+ const enableMediaQueryStyleSheet = themeOptions.enableMediaQueryStyleSheet && viewport
224
235
  const { onPress, ...rest } = clickProps.toPressProps(rawRest)
225
236
  const extraButtonState = { inactive, selected, iconPosition }
226
237
  const resolveButtonTokens = (pressableState) =>
@@ -228,9 +239,49 @@ const ButtonBase = React.forwardRef(
228
239
 
229
240
  const systemProps = selectProps(rest)
230
241
 
242
+ let layoutMediaQueryStyles
243
+ let flexAndWidthStylesIds
244
+
245
+ if (enableMediaQueryStyleSheet) {
246
+ const defaultPressableState = { pressed: false, hovered: false, focused: false }
247
+ const defaultTokensByViewport = resolveButtonTokens(defaultPressableState)
248
+
249
+ const layoutTokensByViewport = Object.entries(defaultTokensByViewport).reduce(
250
+ (acc, [vp, viewportTokens]) => {
251
+ const flexAndWidthStyles =
252
+ viewportTokens.width === '100%' && viewportTokens.flex === 1
253
+ ? selectFlexAndWidthStyles(viewportTokens)
254
+ : {}
255
+
256
+ acc[vp] = {
257
+ ...staticStyles.row,
258
+ ...selectWebOnlyStyles(inactive, viewportTokens, systemProps),
259
+ ...(Object.keys(flexAndWidthStyles).length > 0 ? flexAndWidthStyles : {}),
260
+ ...selectOuterSizeStyles(viewportTokens)
261
+ }
262
+ return acc
263
+ },
264
+ {}
265
+ )
266
+
267
+ const mediaQueryStyles = createMediaQueryStyles(layoutTokensByViewport)
268
+ const { ids, styles } = StyleSheetUtils.create({
269
+ layout: {
270
+ ...mediaQueryStyles
271
+ }
272
+ })
273
+
274
+ layoutMediaQueryStyles = styles.layout
275
+ flexAndWidthStylesIds = ids.layout
276
+ }
277
+
231
278
  const getPressableStyle = (pressableState) => {
279
+ if (enableMediaQueryStyleSheet) {
280
+ const themeTokens = resolveButtonTokens(pressableState)[viewport]
281
+ return [layoutMediaQueryStyles, selectOuterContainerStyles(themeTokens)]
282
+ }
283
+
232
284
  const themeTokens = resolveButtonTokens(pressableState)
233
- // Only apply flex and width styles when they are explicitly set (e.g., from ButtonGroup with width: 'equal') to not to affect other use cases
234
285
  const flexAndWidthStyles =
235
286
  themeTokens.width === '100%' && themeTokens.flex === 1
236
287
  ? selectFlexAndWidthStyles(themeTokens)
@@ -239,12 +290,21 @@ const ButtonBase = React.forwardRef(
239
290
  return [
240
291
  staticStyles.row,
241
292
  selectWebOnlyStyles(inactive, themeTokens, systemProps),
242
- selectOuterContainerStyles(themeTokens),
293
+ selectOuterContainerStyles(themeTokens, heightFull),
243
294
  ...(Object.keys(flexAndWidthStyles).length > 0 ? [flexAndWidthStyles] : []),
244
295
  selectOuterSizeStyles(themeTokens)
245
296
  ]
246
297
  }
247
- const { themeOptions } = useTheme()
298
+
299
+ const dataSetProp =
300
+ flexAndWidthStylesIds || rawRest.dataSet
301
+ ? {
302
+ dataSet: {
303
+ ...(flexAndWidthStylesIds ? { media: flexAndWidthStylesIds } : {}),
304
+ ...rawRest.dataSet
305
+ }
306
+ }
307
+ : {}
248
308
 
249
309
  return (
250
310
  <Pressable
@@ -255,9 +315,12 @@ const ButtonBase = React.forwardRef(
255
315
  disabled={inactive}
256
316
  hrefAttrs={hrefAttrs}
257
317
  {...systemProps}
318
+ {...dataSetProp}
258
319
  >
259
320
  {(pressableState) => {
260
- const themeTokens = resolveButtonTokens(pressableState)
321
+ const themeTokens = enableMediaQueryStyleSheet
322
+ ? resolveButtonTokens(pressableState)[viewport]
323
+ : resolveButtonTokens(pressableState)
261
324
  const containerStyles = selectInnerContainerStyles(themeTokens)
262
325
  const borderStyles = selectBorderStyles(themeTokens)
263
326
  const textStyles = [
@@ -105,6 +105,7 @@ const ButtonDropdown = React.forwardRef(
105
105
  accessibilityRole = 'radio',
106
106
  description,
107
107
  singleOption,
108
+ heightFull = true,
108
109
  ...props
109
110
  },
110
111
  ref
@@ -151,6 +152,7 @@ const ButtonDropdown = React.forwardRef(
151
152
  {...pressHandlers}
152
153
  onPress={handlePress}
153
154
  tokens={getButtonTokens}
155
+ heightFull={heightFull}
154
156
  inactive={singleOption || inactive}
155
157
  icon={() => null}
156
158
  accessibilityRole={accessibilityRole}
@@ -1,21 +1,20 @@
1
- import React from 'react'
1
+ import React, { useCallback, useMemo } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import ABBPropTypes from 'airbnb-prop-types'
4
- import { Platform } from 'react-native'
4
+ import { Platform, View, StyleSheet } from 'react-native'
5
5
 
6
6
  import ButtonBase from './ButtonBase'
7
- import { StackWrap } from '../StackView'
8
7
  import Fieldset from '../Fieldset'
9
8
  import { useViewport } from '../ViewportProvider'
10
9
  import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider'
11
10
  import {
11
+ useSpacingScale,
12
12
  a11yProps,
13
13
  containUniqueFields,
14
14
  focusHandlerProps,
15
15
  pressProps,
16
16
  getTokensPropType,
17
17
  selectSystemProps,
18
- selectTokens,
19
18
  useMultipleInputValues,
20
19
  variantProp,
21
20
  viewProps
@@ -30,16 +29,6 @@ const [selectItemProps, selectedItemPropTypes] = selectSystemProps([
30
29
  viewProps
31
30
  ])
32
31
 
33
- const getStackWrapTokens = (variant) => {
34
- return Platform.select({
35
- web: {
36
- justifyContent: variant?.width === 'equal' ? 'space-evenly' : 'flex-start',
37
- width: variant?.width === 'equal' ? '100%' : 'auto'
38
- },
39
- default: {}
40
- })
41
- }
42
-
43
32
  const ButtonGroup = React.forwardRef(
44
33
  (
45
34
  {
@@ -72,25 +61,52 @@ const ButtonGroup = React.forwardRef(
72
61
  ) => {
73
62
  const viewport = useViewport()
74
63
  const themeTokens = useThemeTokens('ButtonGroup', tokens, variant, { viewport })
75
- const themeStackTokens = selectTokens('StackView', themeTokens)
76
- const variantStackTokens = getStackWrapTokens(variant)
77
- const stackTokens = {
78
- ...themeStackTokens,
79
- ...variantStackTokens
80
- }
81
64
  const { direction, space, fieldSpace, borderRadius, backgroundColor, padding, gap } =
82
65
  themeTokens
83
66
 
67
+ const isMobileNonContained =
68
+ Platform.OS !== 'web' && (!variant || variant?.style !== 'contained')
69
+
84
70
  const themeButtonTokensCallback = useThemeTokensCallback('ButtonGroupItem', tokens, variant)
71
+ const gapValue = useSpacingScale(gap || space)
85
72
 
86
- const getButtonTokens = (state) => {
87
- const themeButtonTokens = themeButtonTokensCallback(state)
88
- return {
89
- ...themeButtonTokens,
90
- width: variant?.width === 'equal' ? '100%' : 'auto',
91
- flex: variant?.width === 'equal' ? 1 : undefined
92
- }
93
- }
73
+ const getButtonTokens = useCallback(
74
+ (state) => {
75
+ const themeButtonTokens = themeButtonTokensCallback(state)
76
+
77
+ const shouldUseTransparentBackground =
78
+ isMobileNonContained && !state.selected && !state.pressed && !state.hover && !state.focus
79
+
80
+ return {
81
+ ...themeButtonTokens,
82
+ ...(variant?.width === 'equal' && staticStyles.equalWidth),
83
+ ...(shouldUseTransparentBackground && { backgroundColor: 'transparent' }),
84
+ alignSelf: themeButtonTokens.width ? 'flex-start' : 'center'
85
+ }
86
+ },
87
+ [themeButtonTokensCallback, isMobileNonContained, variant?.width]
88
+ )
89
+
90
+ const fieldsetStyles = useMemo(
91
+ () => ({
92
+ ...staticStyles.fieldsetBase,
93
+ borderRadius,
94
+ backgroundColor: isMobileNonContained ? 'transparent' : backgroundColor || 'transparent',
95
+ padding,
96
+ width: variant?.width === 'equal' ? '100%' : 'auto'
97
+ }),
98
+ [borderRadius, backgroundColor, padding, variant?.width, isMobileNonContained]
99
+ )
100
+
101
+ const viewStyles = useMemo(
102
+ () => ({
103
+ ...staticStyles.viewBase,
104
+ flexDirection: direction === 'column' ? 'column' : 'row',
105
+ gap: gapValue || 0,
106
+ justifyContent: variant?.width === 'equal' ? 'space-evenly' : 'flex-start'
107
+ }),
108
+ [direction, gapValue, variant?.width]
109
+ )
94
110
 
95
111
  const { currentValues, toggleOneValue } = useMultipleInputValues({
96
112
  initialValues,
@@ -130,24 +146,10 @@ const ButtonGroup = React.forwardRef(
130
146
  inactive={inactive}
131
147
  validation={validation}
132
148
  accessibilityRole={accessibilityRole}
133
- style={{
134
- borderRadius,
135
- backgroundColor,
136
- padding,
137
- ...(Platform.OS === 'web'
138
- ? { gap, width: variant?.width === 'equal' ? '100%' : 'auto' }
139
- : { alignSelf: 'flex-start' })
140
- }}
149
+ style={fieldsetStyles}
141
150
  {...selectProps(rest)}
142
151
  >
143
- <StackWrap
144
- accessibilityRole={innerRole}
145
- space={space}
146
- direction={direction}
147
- tokens={stackTokens}
148
- gap={gap}
149
- ref={ref}
150
- >
152
+ <View accessibilityRole={innerRole} style={viewStyles}>
151
153
  {items.map(
152
154
  ({ label, id = label, accessibilityLabel, ref: itemRef, ...itemRest }, index) => {
153
155
  const isSelected = currentValues.includes(id)
@@ -193,7 +195,7 @@ const ButtonGroup = React.forwardRef(
193
195
  )
194
196
  }
195
197
  )}
196
- </StackWrap>
198
+ </View>
197
199
  </Fieldset>
198
200
  )
199
201
  }
@@ -309,4 +311,19 @@ ButtonGroup.propTypes = {
309
311
  copy: PropTypes.oneOf(['en', 'fr'])
310
312
  }
311
313
 
314
+ const staticStyles = StyleSheet.create({
315
+ fieldsetBase: {
316
+ alignSelf: 'flex-start',
317
+ display: 'inline'
318
+ },
319
+ viewBase: {
320
+ flexWrap: 'wrap',
321
+ alignItems: 'center'
322
+ },
323
+ equalWidth: {
324
+ width: '100%',
325
+ flex: 1
326
+ }
327
+ })
328
+
312
329
  export default ButtonGroup
@@ -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
@@ -204,7 +204,9 @@ const staticStyles = StyleSheet.create({
204
204
  },
205
205
  linkContainer: {
206
206
  flex: 1,
207
- display: 'flex'
207
+ display: 'flex',
208
+ alignItems: 'stretch',
209
+ justifyContent: 'flex-start'
208
210
  }
209
211
  })
210
212
 
@@ -399,6 +399,7 @@ const Carousel = React.forwardRef(
399
399
  copy,
400
400
  slideDuration = 0,
401
401
  transitionDuration = 0,
402
+ loopDuration = transitionDuration,
402
403
  autoPlay = false,
403
404
  enablePeeking = false,
404
405
  ...rest
@@ -406,6 +407,7 @@ const Carousel = React.forwardRef(
406
407
  ref
407
408
  ) => {
408
409
  let childrenArray = unpackFragment(children)
410
+ const isTransitioningRef = React.useRef(false)
409
411
  const viewport = useViewport()
410
412
  const totalItems = getTotalItems(enableDisplayMultipleItemsPerSlide, childrenArray, viewport)
411
413
  const autoPlayFeatureEnabled =
@@ -481,10 +483,14 @@ const Carousel = React.forwardRef(
481
483
  )
482
484
  const handleAnimationEnd = React.useCallback(
483
485
  (...args) => {
484
- if (typeof onAnimationEnd === 'function') onAnimationEnd(...args)
485
- 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
+ }
486
492
  },
487
- [onAnimationEnd]
493
+ [onAnimationEnd, isTransitioningRef]
488
494
  )
489
495
 
490
496
  const updateOffset = React.useCallback(() => {
@@ -533,6 +539,8 @@ const Carousel = React.forwardRef(
533
539
 
534
540
  const animate = React.useCallback(
535
541
  (panToAnimate, toValue, toIndex) => {
542
+ const applicableTransitionDuration =
543
+ isLastSlide && toIndex === 0 ? loopDuration : transitionDuration
536
544
  const handleAnimationEndToIndex = (...args) => handleAnimationEnd(toIndex, ...args)
537
545
  if (reduceMotionRef.current || isSwiping.current) {
538
546
  Animated.timing(panToAnimate, { toValue, duration: 1, useNativeDriver: false }).start(
@@ -543,14 +551,14 @@ const Carousel = React.forwardRef(
543
551
  ...springConfig,
544
552
  toValue,
545
553
  useNativeDriver: false,
546
- duration: transitionDuration * 1000
554
+ duration: applicableTransitionDuration * 1000
547
555
  }).start(handleAnimationEndToIndex)
548
556
  } else if (enablePeeking || enableDisplayMultipleItemsPerSlide) {
549
557
  Animated.timing(panToAnimate, {
550
558
  ...springConfig,
551
559
  toValue,
552
560
  useNativeDriver: false,
553
- duration: transitionDuration ? transitionDuration * 1000 : 1000
561
+ duration: applicableTransitionDuration ? applicableTransitionDuration * 1000 : 1000
554
562
  }).start(handleAnimationEndToIndex)
555
563
  } else {
556
564
  Animated.spring(panToAnimate, {
@@ -564,6 +572,8 @@ const Carousel = React.forwardRef(
564
572
  springConfig,
565
573
  handleAnimationEnd,
566
574
  transitionDuration,
575
+ loopDuration,
576
+ isLastSlide,
567
577
  isAutoPlayEnabled,
568
578
  enablePeeking,
569
579
  enableDisplayMultipleItemsPerSlide
@@ -597,7 +607,9 @@ const Carousel = React.forwardRef(
597
607
 
598
608
  const index = activeIndexRef.current + calcDelta
599
609
  if (skipChanges) {
610
+ isTransitioningRef.current = true
600
611
  animate(pan, toValue, index)
612
+
601
613
  if (enableHero) {
602
614
  animate(heroPan, toValue, index)
603
615
  }
@@ -617,6 +629,7 @@ const Carousel = React.forwardRef(
617
629
  toValue.x = finalWidth * -1 * calcDelta
618
630
  const heroToValue = { x: 0, y: 0 }
619
631
  heroToValue.x = heroContainerLayoutRef.current.width * -1 * calcDelta
632
+ isTransitioningRef.current = true
620
633
  animate(pan, toValue, index)
621
634
  if (enableHero) {
622
635
  animate(heroPan, heroToValue, index)
@@ -1007,6 +1020,15 @@ const Carousel = React.forwardRef(
1007
1020
  setisCarouselPlaying((prevState) => !prevState)
1008
1021
  }, [isCarouselPlaying, stopAutoplay, startAutoplay])
1009
1022
 
1023
+ const handleKeyDown = React.useCallback(
1024
+ (event) => {
1025
+ if (isTransitioningRef.current && event.key === 'Tab') {
1026
+ event.preventDefault()
1027
+ }
1028
+ },
1029
+ [isTransitioningRef]
1030
+ )
1031
+
1010
1032
  return (
1011
1033
  <View style={selectRootContainerStyles(enableHero, viewport)}>
1012
1034
  <View style={selectMainContainerStyles(enableHero, viewport)}>
@@ -1036,6 +1058,7 @@ const Carousel = React.forwardRef(
1036
1058
  ref={ref}
1037
1059
  {...systemProps}
1038
1060
  {...containerProps}
1061
+ {...(Platform.OS === 'web' ? { onKeyDown: handleKeyDown } : {})}
1039
1062
  >
1040
1063
  {isAutoPlayEnabled ? (
1041
1064
  <View
@@ -1125,10 +1148,45 @@ const Carousel = React.forwardRef(
1125
1148
  accessibilityLiveRegion={accessibilityLiveRegion}
1126
1149
  >
1127
1150
  {childrenArray.map((element, index) => {
1128
- 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
+
1129
1187
  const clonedElement = React.cloneElement(element, {
1130
1188
  elementIndex: index,
1131
- hidden: enablePeeking || enableDisplayMultipleItemsPerSlide ? false : hidden,
1189
+ hidden,
1132
1190
  enablePeeking,
1133
1191
  peekingProps: getPeekingProps(viewport),
1134
1192
  enableDisplayMultipleItemsPerSlide,
@@ -1390,6 +1448,12 @@ Carousel.propTypes = {
1390
1448
  * - `autoPlay` and `slideDuration` are required to be set for this to work
1391
1449
  */
1392
1450
  transitionDuration: PropTypes.number,
1451
+ /**
1452
+ * Time it takes in seconds to transition from last slide to first slide
1453
+ * - Default value equals `transitionDuration`'s value
1454
+ * - `autoPlay` and `transitionDuration` are required to be set for this to work
1455
+ */
1456
+ loopDuration: PropTypes.number,
1393
1457
  /**
1394
1458
  * If set to `true`, the Carousel will show the previous and next slides
1395
1459
  * - Default value is `false`
@@ -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 (
package/src/Icon/Icon.jsx CHANGED
@@ -43,20 +43,17 @@ const Icon = React.forwardRef(
43
43
  }
44
44
 
45
45
  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
46
+ return (
47
+ <View
48
+ style={{
49
+ backgroundColor: themeTokens.backgroundColor,
50
+ borderRadius: themeTokens.borderRadius,
51
+ ...paddingStyles
52
+ }}
53
+ >
54
+ {iconContent}
55
+ </View>
56
+ )
60
57
  }
61
58
 
62
59
  return Platform.OS === 'web' ? (
@@ -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({