@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
@@ -1,26 +1,18 @@
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
4
  import Platform from "react-native-web/dist/exports/Platform";
5
+ import View from "react-native-web/dist/exports/View";
6
+ import StyleSheet from "react-native-web/dist/exports/StyleSheet";
5
7
  import ButtonBase from './ButtonBase';
6
- import { StackWrap } from '../StackView';
7
8
  import Fieldset from '../Fieldset';
8
9
  import { useViewport } from '../ViewportProvider';
9
10
  import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider';
10
- import { a11yProps, containUniqueFields, focusHandlerProps, pressProps, getTokensPropType, selectSystemProps, selectTokens, useMultipleInputValues, variantProp, viewProps } from '../utils';
11
+ import { useSpacingScale, a11yProps, containUniqueFields, focusHandlerProps, pressProps, getTokensPropType, selectSystemProps, useMultipleInputValues, variantProp, viewProps } from '../utils';
11
12
  import { getPressHandlersWithArgs } from '../utils/pressability';
12
13
  import { jsx as _jsx } from "react/jsx-runtime";
13
14
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
14
15
  const [selectItemProps, selectedItemPropTypes] = selectSystemProps([a11yProps, focusHandlerProps, pressProps, viewProps]);
15
- const getStackWrapTokens = variant => {
16
- return Platform.select({
17
- web: {
18
- justifyContent: variant?.width === 'equal' ? 'space-evenly' : 'flex-start',
19
- width: variant?.width === 'equal' ? '100%' : 'auto'
20
- },
21
- default: {}
22
- });
23
- };
24
16
  const ButtonGroup = /*#__PURE__*/React.forwardRef((_ref, ref) => {
25
17
  let {
26
18
  variant,
@@ -53,12 +45,6 @@ const ButtonGroup = /*#__PURE__*/React.forwardRef((_ref, ref) => {
53
45
  const themeTokens = useThemeTokens('ButtonGroup', tokens, variant, {
54
46
  viewport
55
47
  });
56
- const themeStackTokens = selectTokens('StackView', themeTokens);
57
- const variantStackTokens = getStackWrapTokens(variant);
58
- const stackTokens = {
59
- ...themeStackTokens,
60
- ...variantStackTokens
61
- };
62
48
  const {
63
49
  direction,
64
50
  space,
@@ -68,15 +54,34 @@ const ButtonGroup = /*#__PURE__*/React.forwardRef((_ref, ref) => {
68
54
  padding,
69
55
  gap
70
56
  } = themeTokens;
57
+ const isMobileNonContained = Platform.OS !== 'web' && (!variant || variant?.style !== 'contained');
71
58
  const themeButtonTokensCallback = useThemeTokensCallback('ButtonGroupItem', tokens, variant);
72
- const getButtonTokens = state => {
59
+ const gapValue = useSpacingScale(gap || space);
60
+ const getButtonTokens = useCallback(state => {
73
61
  const themeButtonTokens = themeButtonTokensCallback(state);
62
+ const shouldUseTransparentBackground = isMobileNonContained && !state.selected && !state.pressed && !state.hover && !state.focus;
74
63
  return {
75
64
  ...themeButtonTokens,
76
- width: variant?.width === 'equal' ? '100%' : 'auto',
77
- flex: variant?.width === 'equal' ? 1 : undefined
65
+ ...(variant?.width === 'equal' && staticStyles.equalWidth),
66
+ ...(shouldUseTransparentBackground && {
67
+ backgroundColor: 'transparent'
68
+ }),
69
+ alignSelf: themeButtonTokens.width ? 'flex-start' : 'center'
78
70
  };
79
- };
71
+ }, [themeButtonTokensCallback, isMobileNonContained, variant?.width]);
72
+ const fieldsetStyles = useMemo(() => ({
73
+ ...staticStyles.fieldsetBase,
74
+ borderRadius,
75
+ backgroundColor: isMobileNonContained ? 'transparent' : backgroundColor || 'transparent',
76
+ padding,
77
+ width: variant?.width === 'equal' ? '100%' : 'auto'
78
+ }), [borderRadius, backgroundColor, padding, variant?.width, isMobileNonContained]);
79
+ const viewStyles = useMemo(() => ({
80
+ ...staticStyles.viewBase,
81
+ flexDirection: direction === 'column' ? 'column' : 'row',
82
+ gap: gapValue || 0,
83
+ justifyContent: variant?.width === 'equal' ? 'space-evenly' : 'flex-start'
84
+ }), [direction, gapValue, variant?.width]);
80
85
  const {
81
86
  currentValues,
82
87
  toggleOneValue
@@ -113,25 +118,11 @@ const ButtonGroup = /*#__PURE__*/React.forwardRef((_ref, ref) => {
113
118
  inactive: inactive,
114
119
  validation: validation,
115
120
  accessibilityRole: accessibilityRole,
116
- style: {
117
- borderRadius,
118
- backgroundColor,
119
- padding,
120
- ...(Platform.OS === 'web' ? {
121
- gap,
122
- width: variant?.width === 'equal' ? '100%' : 'auto'
123
- } : {
124
- alignSelf: 'flex-start'
125
- })
126
- },
121
+ style: fieldsetStyles,
127
122
  ...selectProps(rest),
128
- children: /*#__PURE__*/_jsx(StackWrap, {
123
+ children: /*#__PURE__*/_jsx(View, {
129
124
  accessibilityRole: innerRole,
130
- space: space,
131
- direction: direction,
132
- tokens: stackTokens,
133
- gap: gap,
134
- ref: ref,
125
+ style: viewStyles,
135
126
  children: items.map((_ref2, index) => {
136
127
  let {
137
128
  label,
@@ -291,4 +282,18 @@ ButtonGroup.propTypes = {
291
282
  */
292
283
  copy: PropTypes.oneOf(['en', 'fr'])
293
284
  };
285
+ const staticStyles = StyleSheet.create({
286
+ fieldsetBase: {
287
+ alignSelf: 'flex-start',
288
+ display: 'inline'
289
+ },
290
+ viewBase: {
291
+ flexWrap: 'wrap',
292
+ alignItems: 'center'
293
+ },
294
+ equalWidth: {
295
+ width: '100%',
296
+ flex: 1
297
+ }
298
+ });
294
299
  export default ButtonGroup;
@@ -6,6 +6,12 @@ import { iconComponentPropTypes } from '../Icon';
6
6
  export const textAndA11yText = ABBPropTypes.childrenOf(PropTypes.oneOfType([ABBPropTypes.elementType(A11yText), PropTypes.string]));
7
7
  const buttonPropTypes = {
8
8
  tokens: getTokensPropType('Button'),
9
+ /**
10
+ * If true, the button will honor the align-items value from its parent flex container.
11
+ * If false, the button will always align to 'flex-start' in a flex container.
12
+ * 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`
13
+ */
14
+ heightFull: PropTypes.bool,
9
15
  /**
10
16
  * If true, prevents the button from being pressed, changes the cursor (on web) and accessibility
11
17
  * attributes to communicate this to the user, and applies `inactive: true` appearances from the theme
@@ -173,7 +173,9 @@ const staticStyles = StyleSheet.create({
173
173
  },
174
174
  linkContainer: {
175
175
  flex: 1,
176
- display: 'flex'
176
+ display: 'flex',
177
+ alignItems: 'stretch',
178
+ justifyContent: 'flex-start'
177
179
  }
178
180
  });
179
181
  PressableCardBase.displayName = 'PressableCardBase';
@@ -351,11 +351,13 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
351
351
  copy,
352
352
  slideDuration = 0,
353
353
  transitionDuration = 0,
354
+ loopDuration = transitionDuration,
354
355
  autoPlay = false,
355
356
  enablePeeking = false,
356
357
  ...rest
357
358
  } = _ref3;
358
359
  let childrenArray = unpackFragment(children);
360
+ const isTransitioningRef = React.useRef(false);
359
361
  const viewport = useViewport();
360
362
  const totalItems = getTotalItems(enableDisplayMultipleItemsPerSlide, childrenArray, viewport);
361
363
  const autoPlayFeatureEnabled = autoPlay && slideDuration > 0 && transitionDuration > 0 && totalItems > 1;
@@ -426,9 +428,14 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
426
428
  setIsAnimating(true);
427
429
  }, [onAnimationStart]);
428
430
  const handleAnimationEnd = React.useCallback(function () {
429
- if (typeof onAnimationEnd === 'function') onAnimationEnd(...arguments);
430
- setIsAnimating(false);
431
- }, [onAnimationEnd]);
431
+ var _ref4;
432
+ const result = (_ref4 = arguments.length - 1, _ref4 < 0 || arguments.length <= _ref4 ? undefined : arguments[_ref4]);
433
+ if (result?.finished) {
434
+ if (typeof onAnimationEnd === 'function') onAnimationEnd(...arguments);
435
+ setIsAnimating(false);
436
+ isTransitioningRef.current = false;
437
+ }
438
+ }, [onAnimationEnd, isTransitioningRef]);
432
439
  const updateOffset = React.useCallback(() => {
433
440
  if (enablePeeking) {
434
441
  const {
@@ -470,6 +477,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
470
477
  }
471
478
  }, [pan, animatedX, heroPan, heroAnimatedX, enableHero, viewport, enablePeeking]);
472
479
  const animate = React.useCallback((panToAnimate, toValue, toIndex) => {
480
+ const applicableTransitionDuration = isLastSlide && toIndex === 0 ? loopDuration : transitionDuration;
473
481
  const handleAnimationEndToIndex = function () {
474
482
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
475
483
  args[_key] = arguments[_key];
@@ -487,14 +495,14 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
487
495
  ...springConfig,
488
496
  toValue,
489
497
  useNativeDriver: false,
490
- duration: transitionDuration * 1000
498
+ duration: applicableTransitionDuration * 1000
491
499
  }).start(handleAnimationEndToIndex);
492
500
  } else if (enablePeeking || enableDisplayMultipleItemsPerSlide) {
493
501
  Animated.timing(panToAnimate, {
494
502
  ...springConfig,
495
503
  toValue,
496
504
  useNativeDriver: false,
497
- duration: transitionDuration ? transitionDuration * 1000 : 1000
505
+ duration: applicableTransitionDuration ? applicableTransitionDuration * 1000 : 1000
498
506
  }).start(handleAnimationEndToIndex);
499
507
  } else {
500
508
  Animated.spring(panToAnimate, {
@@ -503,7 +511,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
503
511
  useNativeDriver: false
504
512
  }).start(handleAnimationEndToIndex);
505
513
  }
506
- }, [springConfig, handleAnimationEnd, transitionDuration, isAutoPlayEnabled, enablePeeking, enableDisplayMultipleItemsPerSlide]);
514
+ }, [springConfig, handleAnimationEnd, transitionDuration, loopDuration, isLastSlide, isAutoPlayEnabled, enablePeeking, enableDisplayMultipleItemsPerSlide]);
507
515
  const stopAutoplay = React.useCallback(() => {
508
516
  if (autoPlayRef?.current) {
509
517
  clearTimeout(autoPlayRef?.current);
@@ -535,6 +543,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
535
543
  }
536
544
  const index = activeIndexRef.current + calcDelta;
537
545
  if (skipChanges) {
546
+ isTransitioningRef.current = true;
538
547
  animate(pan, toValue, index);
539
548
  if (enableHero) {
540
549
  animate(heroPan, toValue, index);
@@ -550,6 +559,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
550
559
  y: 0
551
560
  };
552
561
  heroToValue.x = heroContainerLayoutRef.current.width * -1 * calcDelta;
562
+ isTransitioningRef.current = true;
553
563
  animate(pan, toValue, index);
554
564
  if (enableHero) {
555
565
  animate(heroPan, heroToValue, index);
@@ -605,29 +615,29 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
605
615
  heroContainerLayoutRef.current = heroContainerLayout;
606
616
  }, [heroContainerLayout]);
607
617
  React.useEffect(() => {
608
- pan.x.addListener(_ref4 => {
618
+ pan.x.addListener(_ref5 => {
609
619
  let {
610
620
  value
611
- } = _ref4;
621
+ } = _ref5;
612
622
  animatedX.current = value;
613
623
  });
614
- pan.y.addListener(_ref5 => {
624
+ pan.y.addListener(_ref6 => {
615
625
  let {
616
626
  value
617
- } = _ref5;
627
+ } = _ref6;
618
628
  animatedY.current = value;
619
629
  });
620
630
  if (enableHero) {
621
- heroPan.x.addListener(_ref6 => {
631
+ heroPan.x.addListener(_ref7 => {
622
632
  let {
623
633
  value
624
- } = _ref6;
634
+ } = _ref7;
625
635
  heroAnimatedX.current = value;
626
636
  });
627
- heroPan.y.addListener(_ref7 => {
637
+ heroPan.y.addListener(_ref8 => {
628
638
  let {
629
639
  value
630
- } = _ref7;
640
+ } = _ref8;
631
641
  heroAnimatedY.current = value;
632
642
  });
633
643
  }
@@ -664,7 +674,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
664
674
  stopAutoplay();
665
675
  };
666
676
  }, [stopAutoplay]);
667
- const onContainerLayout = _ref8 => {
677
+ const onContainerLayout = _ref9 => {
668
678
  let {
669
679
  nativeEvent: {
670
680
  layout: {
@@ -674,7 +684,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
674
684
  height
675
685
  }
676
686
  }
677
- } = _ref8;
687
+ } = _ref9;
678
688
  return setContainerLayout(prevState => ({
679
689
  ...prevState,
680
690
  x,
@@ -683,7 +693,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
683
693
  height
684
694
  }));
685
695
  };
686
- const onHeroContainerLayout = _ref9 => {
696
+ const onHeroContainerLayout = _ref10 => {
687
697
  let {
688
698
  nativeEvent: {
689
699
  layout: {
@@ -693,7 +703,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
693
703
  height
694
704
  }
695
705
  }
696
- } = _ref9;
706
+ } = _ref10;
697
707
  return setHeroContainerLayout(prevState => ({
698
708
  ...prevState,
699
709
  x,
@@ -702,14 +712,14 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
702
712
  height
703
713
  }));
704
714
  };
705
- const onPreviousNextNavigationButtonLayout = _ref10 => {
715
+ const onPreviousNextNavigationButtonLayout = _ref11 => {
706
716
  let {
707
717
  nativeEvent: {
708
718
  layout: {
709
719
  width
710
720
  }
711
721
  }
712
- } = _ref10;
722
+ } = _ref11;
713
723
  return setPreviousNextNavigationButtonWidth(width);
714
724
  };
715
725
  const isSwipeAllowed = React.useCallback(() => {
@@ -871,6 +881,11 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
871
881
  }
872
882
  setisCarouselPlaying(prevState => !prevState);
873
883
  }, [isCarouselPlaying, stopAutoplay, startAutoplay]);
884
+ const handleKeyDown = React.useCallback(event => {
885
+ if (isTransitioningRef.current && event.key === 'Tab') {
886
+ event.preventDefault();
887
+ }
888
+ }, [isTransitioningRef]);
874
889
  return /*#__PURE__*/_jsxs(View, {
875
890
  style: selectRootContainerStyles(enableHero, viewport),
876
891
  children: [/*#__PURE__*/_jsx(View, {
@@ -896,6 +911,9 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
896
911
  ref: ref,
897
912
  ...systemProps,
898
913
  ...containerProps,
914
+ ...(Platform.OS === 'web' ? {
915
+ onKeyDown: handleKeyDown
916
+ } : {}),
899
917
  children: [isAutoPlayEnabled ? /*#__PURE__*/_jsx(View, {
900
918
  style: [staticStyles.animationControlButton, selectControlButtonPositionStyles({
901
919
  positionVariant: previousNextNavigationPosition,
@@ -950,10 +968,27 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
950
968
  // This is a known Voiceover bug: https://github.com/phetsims/a11y-research/issues/132
951
969
  accessibilityLiveRegion: accessibilityLiveRegion,
952
970
  children: childrenArray.map((element, index) => {
953
- const hidden = !isAnimating && index !== activeIndex;
971
+ let hidden = !isAnimating && index !== activeIndex;
972
+ if (enablePeeking && !isAnimating) {
973
+ if (enableDisplayMultipleItemsPerSlide) {
974
+ const maxItemsForSlide = getMaximumItemsForSlide(enableDisplayMultipleItemsPerSlide, viewport);
975
+ if (index >= activeIndex * maxItemsForSlide - 1 && index < activeIndex * maxItemsForSlide + maxItemsForSlide + 1) {
976
+ hidden = false;
977
+ } else {
978
+ hidden = true;
979
+ }
980
+ } else if (index >= activeIndex - 1 && index <= activeIndex + 1) {
981
+ hidden = false;
982
+ }
983
+ } else if (!enablePeeking && enableDisplayMultipleItemsPerSlide && !isAnimating) {
984
+ const maxItemsForSlide = getMaximumItemsForSlide(enableDisplayMultipleItemsPerSlide, viewport);
985
+ if (index >= activeIndex * maxItemsForSlide && index < activeIndex * maxItemsForSlide + maxItemsForSlide) {
986
+ hidden = false;
987
+ }
988
+ }
954
989
  const clonedElement = /*#__PURE__*/React.cloneElement(element, {
955
990
  elementIndex: index,
956
- hidden: enablePeeking || enableDisplayMultipleItemsPerSlide ? false : hidden,
991
+ hidden,
957
992
  enablePeeking,
958
993
  peekingProps: getPeekingProps(viewport),
959
994
  enableDisplayMultipleItemsPerSlide,
@@ -1197,6 +1232,12 @@ Carousel.propTypes = {
1197
1232
  * - `autoPlay` and `slideDuration` are required to be set for this to work
1198
1233
  */
1199
1234
  transitionDuration: PropTypes.number,
1235
+ /**
1236
+ * Time it takes in seconds to transition from last slide to first slide
1237
+ * - Default value equals `transitionDuration`'s value
1238
+ * - `autoPlay` and `transitionDuration` are required to be set for this to work
1239
+ */
1240
+ loopDuration: PropTypes.number,
1200
1241
  /**
1201
1242
  * If set to `true`, the Carousel will show the previous and next slides
1202
1243
  * - Default value is `false`
@@ -97,13 +97,33 @@ const CarouselItem = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
97
97
  });
98
98
  const focusabilityProps = activeIndex === elementIndex || enablePeeking ? {} : a11yProps.nonFocusableProps;
99
99
  const handleFocus = React.useCallback(event => {
100
- if (Platform.OS === 'web' && elementIndex >= maximumItemsForSlide * (activeIndex + 1)) {
101
- goTo(activeIndex + 1);
100
+ if (Platform.OS === 'web') {
101
+ if (enablePeeking) {
102
+ if (enableDisplayMultipleItemsPerSlide) {
103
+ const startIndex = maximumItemsForSlide * activeIndex;
104
+ const endIndex = startIndex + maximumItemsForSlide - 1;
105
+ if (elementIndex < startIndex) {
106
+ if (activeIndex - 1 < 0) {
107
+ goTo(0);
108
+ } else {
109
+ goTo(activeIndex - 1);
110
+ }
111
+ } else if (elementIndex > endIndex) {
112
+ goTo(activeIndex + 1);
113
+ }
114
+ } else if (elementIndex !== activeIndex) {
115
+ if (elementIndex > activeIndex) {
116
+ goTo(activeIndex + 1);
117
+ } else if (elementIndex < activeIndex) {
118
+ goTo(activeIndex - 1);
119
+ }
120
+ }
121
+ }
102
122
  }
103
123
  if (rest.onFocus) {
104
124
  rest.onFocus(event);
105
125
  }
106
- }, [elementIndex, activeIndex, goTo, maximumItemsForSlide, rest]);
126
+ }, [elementIndex, activeIndex, goTo, maximumItemsForSlide, rest, enablePeeking, enableDisplayMultipleItemsPerSlide]);
107
127
  return /*#__PURE__*/_jsx(View, {
108
128
  style: selectContainerStyle({
109
129
  width,
@@ -36,17 +36,14 @@ const Icon = /*#__PURE__*/React.forwardRef((_ref, ref) => {
36
36
  padding: themeTokens.padding
37
37
  };
38
38
  const getIconContentForMobile = () => {
39
- if (Object.keys(paddingStyles).length) {
40
- return /*#__PURE__*/_jsx(View, {
41
- style: {
42
- backgroundColor: themeTokens.backgroundColor,
43
- borderRadius: themeTokens.borderRadius,
44
- ...paddingStyles
45
- },
46
- children: iconContent
47
- });
48
- }
49
- return iconContent;
39
+ return /*#__PURE__*/_jsx(View, {
40
+ style: {
41
+ backgroundColor: themeTokens.backgroundColor,
42
+ borderRadius: themeTokens.borderRadius,
43
+ ...paddingStyles
44
+ },
45
+ children: iconContent
46
+ });
50
47
  };
51
48
  return Platform.OS === 'web' ? /*#__PURE__*/_jsx(View, {
52
49
  ref: ref
@@ -100,7 +100,6 @@ IconText.propTypes = {
100
100
  * `<Typography>` component, or a component that renders `<Text>`.
101
101
  */
102
102
  children: PropTypes.node
103
- /* eslint-enable react/no-unused-prop-types */
104
103
  };
105
104
  const staticStyles = StyleSheet.create({
106
105
  adjustedContainer: {
@@ -1,15 +1,15 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import View from "react-native-web/dist/exports/View";
4
- import StyleSheet from "react-native-web/dist/exports/StyleSheet";
5
4
  import Text from "react-native-web/dist/exports/Text";
5
+ import StyleSheet from "react-native-web/dist/exports/StyleSheet";
6
6
  import { useThemeTokens } from '../ThemeProvider';
7
7
  import Icon from '../Icon';
8
8
  import Spacer from '../Spacer';
9
9
  import { useListboxContext } from './ListboxContext';
10
10
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  const styles = StyleSheet.create({
12
- controlWrapper: {
12
+ container: {
13
13
  width: '100%',
14
14
  flex: 1,
15
15
  alignItems: 'center',
@@ -18,6 +18,34 @@ const styles = StyleSheet.create({
18
18
  boxSizing: 'border-box'
19
19
  }
20
20
  });
21
+ const selectTextStyles = tokens => ({
22
+ color: tokens.groupColor,
23
+ fontFamily: `${tokens.groupFontName}${tokens.groupFontWeight}normal`,
24
+ fontSize: tokens.groupFontSize,
25
+ fontWeight: tokens.groupFontWeight
26
+ });
27
+ const selectContainerStyles = tokens => ({
28
+ fontFamily: `${tokens.groupFontName}${tokens.groupFontWeight}normal`,
29
+ fontSize: tokens.groupFontSize,
30
+ color: tokens.groupColor,
31
+ textDecoration: tokens.itemTextDecoration,
32
+ backgroundColor: tokens.groupBackgroundColor,
33
+ outline: tokens.itemOutline,
34
+ minHeight: tokens.groupHeight,
35
+ borderRadius: tokens.groupBorderRadius,
36
+ paddingLeft: tokens.groupPaddingLeft - tokens.groupBorderLeftWidth,
37
+ paddingRight: tokens.groupPaddingRight - tokens.groupBorderRightWidth,
38
+ paddingTop: tokens.groupPaddingTop - tokens.groupBorderTopWidth,
39
+ paddingBottom: tokens.groupPaddingBottom - tokens.groupBorderBottomWidth,
40
+ borderLeftWidth: tokens.groupBorderLeftWidth,
41
+ borderLeftColor: tokens.groupBorderLeftColor,
42
+ borderRightWidth: tokens.groupBorderRightWidth,
43
+ borderRightColor: tokens.groupBorderRightColor,
44
+ borderTopWidth: tokens.groupBorderTopWidth,
45
+ borderTopColor: tokens.groupBorderTopColor,
46
+ borderBottomWidth: tokens.groupBorderBottomWidth,
47
+ borderBottomColor: tokens.groupBorderBottomColor
48
+ });
21
49
  const GroupControl = /*#__PURE__*/React.forwardRef((_ref, ref) => {
22
50
  let {
23
51
  expanded,
@@ -38,51 +66,17 @@ const GroupControl = /*#__PURE__*/React.forwardRef((_ref, ref) => {
38
66
  current: selectedId === id && id !== undefined,
39
67
  focus
40
68
  });
41
- const {
42
- groupFontName,
43
- groupFontWeight,
44
- groupFontSize,
45
- groupColor,
46
- groupBackgroundColor,
47
- groupBorderColor,
48
- groupBorderWidth,
49
- groupBorderRadius,
50
- groupPaddingLeft,
51
- groupPaddingRight,
52
- groupPaddingTop,
53
- groupPaddingBottom,
54
- itemTextDecoration,
55
- itemOutline,
56
- groupHeight
57
- } = tokens;
58
- const getTextStyles = () => ({
59
- color: groupColor
60
- });
61
69
  return /*#__PURE__*/_jsxs(View, {
62
70
  onPress: () => setSelectedId(id),
63
- style: [styles.controlWrapper, {
64
- fontFamily: `${groupFontName}${groupFontWeight}normal`,
65
- fontSize: groupFontSize,
66
- color: groupColor,
67
- textDecoration: itemTextDecoration,
68
- backgroundColor: groupBackgroundColor,
69
- outline: itemOutline,
70
- height: groupHeight,
71
- border: `${groupBorderWidth}px solid ${groupBorderColor}`,
72
- borderRadius: groupBorderRadius,
73
- paddingLeft: groupPaddingLeft - groupBorderWidth,
74
- paddingRight: groupPaddingRight - groupBorderWidth,
75
- paddingTop: groupPaddingTop - groupBorderWidth,
76
- paddingBottom: groupPaddingBottom - groupBorderWidth
77
- }],
71
+ style: [styles.container, selectContainerStyles(tokens)],
78
72
  ref: ref,
79
73
  children: [/*#__PURE__*/_jsx(Text, {
80
- style: getTextStyles(),
74
+ style: selectTextStyles(tokens),
81
75
  children: label
82
76
  }), /*#__PURE__*/_jsx(Spacer, {
83
77
  space: 1,
84
78
  direction: "row"
85
- }), /*#__PURE__*/_jsx(Icon, {
79
+ }), tokens.groupIcon && /*#__PURE__*/_jsx(Icon, {
86
80
  icon: tokens.groupIcon,
87
81
  tokens: {
88
82
  color: tokens.groupColor
@@ -1,10 +1,10 @@
1
1
  import React, { createElement as _createElement } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import View from "react-native-web/dist/exports/View";
4
- import StyleSheet from "react-native-web/dist/exports/StyleSheet";
5
4
  import Platform from "react-native-web/dist/exports/Platform";
5
+ import StyleSheet from "react-native-web/dist/exports/StyleSheet";
6
6
  import { useThemeTokens } from '../ThemeProvider';
7
- import { withLinkRouter, getTokensPropType } from '../utils';
7
+ import { withLinkRouter, getTokensPropType, variantProp } from '../utils';
8
8
  import ExpandCollapse from '../ExpandCollapse';
9
9
  import ListboxGroup from './ListboxGroup';
10
10
  import ListboxItem from './ListboxItem';
@@ -12,11 +12,16 @@ import { ListboxContext } from './ListboxContext';
12
12
  import DropdownOverlay from './ListboxOverlay';
13
13
  import { jsx as _jsx } from "react/jsx-runtime";
14
14
  const styles = StyleSheet.create({
15
- list: {
15
+ container: {
16
16
  padding: 0,
17
17
  margin: 0
18
18
  }
19
19
  });
20
+ const selectContainerStyles = tokens => ({
21
+ minHeight: tokens.minHeight,
22
+ minWidth: tokens.minWidth,
23
+ backgroundColor: tokens.containerBackgroundColor
24
+ });
20
25
  const getInitialOpen = (items, selectedId) => items.filter(item => item.items && item.items.some(nestedItem => (nestedItem.id ?? nestedItem.label) === selectedId)).map(item => item.id ?? item.label);
21
26
  const Listbox = /*#__PURE__*/React.forwardRef((_ref, ref) => {
22
27
  let {
@@ -30,14 +35,12 @@ const Listbox = /*#__PURE__*/React.forwardRef((_ref, ref) => {
30
35
  itemRouterProps,
31
36
  onClose,
32
37
  variant,
33
- tokens
38
+ tokens,
39
+ testID
34
40
  } = _ref;
35
41
  const initialOpen = getInitialOpen(items, defaultSelectedId);
36
42
  const [selectedId, setSelectedId] = React.useState(defaultSelectedId);
37
- const {
38
- minHeight,
39
- minWidth
40
- } = useThemeTokens('Listbox', variant, tokens);
43
+ const listboxTokens = useThemeTokens('Listbox', tokens, variant);
41
44
 
42
45
  // We need to keep track of each item's ref in order to be able to
43
46
  // focus on a specific item via keyboard navigation
@@ -96,11 +99,9 @@ const Listbox = /*#__PURE__*/React.forwardRef((_ref, ref) => {
96
99
  maxOpen: 1,
97
100
  ref: ref,
98
101
  children: expandProps => /*#__PURE__*/_jsx(View, {
99
- style: [styles.list, {
100
- minHeight,
101
- minWidth
102
- }],
103
- role: "listbox",
102
+ style: [styles.container, selectContainerStyles(listboxTokens)],
103
+ accessibilityRole: "combobox",
104
+ testID: testID,
104
105
  children: items.map((item, index) => {
105
106
  const {
106
107
  id,
@@ -162,7 +163,15 @@ Listbox.propTypes = {
162
163
  /**
163
164
  * onClose event
164
165
  */
165
- onClose: PropTypes.func
166
+ onClose: PropTypes.func,
167
+ /**
168
+ * Test ID for testing
169
+ */
170
+ testID: PropTypes.string,
171
+ /**
172
+ * Listbox variant
173
+ */
174
+ variant: variantProp.propType
166
175
  };
167
176
  Listbox.Overlay = DropdownOverlay;
168
177
  export default Listbox;
@@ -77,7 +77,8 @@ const ListboxGroup = /*#__PURE__*/React.forwardRef((_ref, ref) => {
77
77
  borderColor: 'transparent',
78
78
  borderRadius: 0,
79
79
  borderWidth: 0,
80
- marginBottom: 0
80
+ marginBottom: 0,
81
+ contentPanelBackgroundColor: 'transparent'
81
82
  },
82
83
  controlRef: ref,
83
84
  children: /*#__PURE__*/_jsx(View, {