@telus-uds/components-base 1.10.0 → 1.12.1

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 (117) hide show
  1. package/CHANGELOG.md +37 -3
  2. package/component-docs.json +413 -62
  3. package/lib/BaseProvider/index.js +7 -2
  4. package/lib/Button/ButtonBase.js +18 -14
  5. package/lib/Carousel/Carousel.js +92 -71
  6. package/lib/Carousel/CarouselContext.js +12 -4
  7. package/lib/Carousel/CarouselItem/CarouselItem.js +24 -9
  8. package/lib/Carousel/CarouselStepTracker/CarouselStepTracker.js +56 -0
  9. package/lib/Carousel/CarouselStepTracker/index.js +13 -0
  10. package/lib/Carousel/dictionary.js +23 -0
  11. package/lib/Checkbox/Checkbox.js +7 -3
  12. package/lib/Checkbox/CheckboxGroup.js +1 -1
  13. package/lib/Feedback/Feedback.js +18 -10
  14. package/lib/Icon/IconText.js +5 -0
  15. package/lib/InputLabel/InputLabel.js +11 -5
  16. package/lib/InputSupports/InputSupports.js +10 -3
  17. package/lib/InputSupports/useInputSupports.js +3 -2
  18. package/lib/Link/LinkBase.js +7 -3
  19. package/lib/List/ListItem.js +7 -3
  20. package/lib/Modal/Modal.js +4 -0
  21. package/lib/Notification/Notification.js +7 -2
  22. package/lib/Pagination/Pagination.js +7 -3
  23. package/lib/RadioCard/RadioCard.js +6 -1
  24. package/lib/Select/Select.js +7 -3
  25. package/lib/Skeleton/Skeleton.js +1 -0
  26. package/lib/StepTracker/Step.js +8 -4
  27. package/lib/StepTracker/StepTracker.js +17 -13
  28. package/lib/Tabs/TabsItem.js +4 -0
  29. package/lib/TextInput/TextInput.js +3 -1
  30. package/lib/TextInput/TextInputBase.js +7 -3
  31. package/lib/ThemeProvider/ThemeProvider.js +20 -3
  32. package/lib/ThemeProvider/utils/styles.js +8 -1
  33. package/lib/ThemeProvider/utils/theme-tokens.js +1 -1
  34. package/lib/Typography/Typography.js +6 -2
  35. package/lib/index.js +9 -0
  36. package/lib/utils/index.js +9 -0
  37. package/lib/utils/props/clickProps.js +2 -2
  38. package/lib/utils/props/handlerProps.js +77 -31
  39. package/lib/utils/useScrollBlocking.js +66 -0
  40. package/lib/utils/useScrollBlocking.native.js +11 -0
  41. package/lib-module/BaseProvider/index.js +7 -2
  42. package/lib-module/Button/ButtonBase.js +7 -3
  43. package/lib-module/Carousel/Carousel.js +85 -70
  44. package/lib-module/Carousel/CarouselContext.js +11 -4
  45. package/lib-module/Carousel/CarouselItem/CarouselItem.js +25 -10
  46. package/lib-module/Carousel/CarouselStepTracker/CarouselStepTracker.js +42 -0
  47. package/lib-module/Carousel/CarouselStepTracker/index.js +2 -0
  48. package/lib-module/Carousel/dictionary.js +16 -0
  49. package/lib-module/Checkbox/Checkbox.js +8 -4
  50. package/lib-module/Checkbox/CheckboxGroup.js +1 -1
  51. package/lib-module/Feedback/Feedback.js +19 -11
  52. package/lib-module/Icon/IconText.js +5 -0
  53. package/lib-module/InputLabel/InputLabel.js +12 -6
  54. package/lib-module/InputSupports/InputSupports.js +10 -3
  55. package/lib-module/InputSupports/useInputSupports.js +3 -2
  56. package/lib-module/Link/LinkBase.js +8 -4
  57. package/lib-module/List/ListItem.js +8 -4
  58. package/lib-module/Modal/Modal.js +3 -0
  59. package/lib-module/Notification/Notification.js +8 -3
  60. package/lib-module/Pagination/Pagination.js +8 -4
  61. package/lib-module/RadioCard/RadioCard.js +7 -2
  62. package/lib-module/Select/Select.js +8 -4
  63. package/lib-module/Skeleton/Skeleton.js +1 -0
  64. package/lib-module/StepTracker/Step.js +9 -5
  65. package/lib-module/StepTracker/StepTracker.js +17 -14
  66. package/lib-module/Tabs/TabsItem.js +5 -1
  67. package/lib-module/TextInput/TextInput.js +3 -1
  68. package/lib-module/TextInput/TextInputBase.js +8 -4
  69. package/lib-module/ThemeProvider/ThemeProvider.js +20 -3
  70. package/lib-module/ThemeProvider/utils/styles.js +8 -1
  71. package/lib-module/ThemeProvider/utils/theme-tokens.js +1 -1
  72. package/lib-module/Typography/Typography.js +7 -3
  73. package/lib-module/index.js +1 -0
  74. package/lib-module/utils/index.js +1 -0
  75. package/lib-module/utils/props/clickProps.js +2 -2
  76. package/lib-module/utils/props/handlerProps.js +78 -31
  77. package/lib-module/utils/useScrollBlocking.js +58 -0
  78. package/lib-module/utils/useScrollBlocking.native.js +2 -0
  79. package/package.json +3 -3
  80. package/src/BaseProvider/index.jsx +6 -3
  81. package/src/Button/ButtonBase.jsx +8 -3
  82. package/src/Carousel/Carousel.jsx +106 -74
  83. package/src/Carousel/CarouselContext.jsx +15 -4
  84. package/src/Carousel/CarouselItem/CarouselItem.jsx +26 -8
  85. package/src/Carousel/CarouselStepTracker/CarouselStepTracker.jsx +36 -0
  86. package/src/Carousel/CarouselStepTracker/index.js +3 -0
  87. package/src/Carousel/dictionary.js +16 -0
  88. package/src/Checkbox/Checkbox.jsx +14 -11
  89. package/src/Checkbox/CheckboxGroup.jsx +1 -1
  90. package/src/Feedback/Feedback.jsx +14 -7
  91. package/src/Icon/IconText.jsx +2 -0
  92. package/src/InputLabel/InputLabel.jsx +13 -12
  93. package/src/InputSupports/InputSupports.jsx +18 -3
  94. package/src/InputSupports/useInputSupports.js +2 -2
  95. package/src/Link/LinkBase.jsx +10 -4
  96. package/src/List/ListItem.jsx +9 -4
  97. package/src/Modal/Modal.jsx +3 -1
  98. package/src/Notification/Notification.jsx +5 -3
  99. package/src/Pagination/Pagination.jsx +6 -4
  100. package/src/RadioCard/RadioCard.jsx +3 -2
  101. package/src/Select/Select.jsx +12 -3
  102. package/src/Skeleton/Skeleton.jsx +1 -0
  103. package/src/StepTracker/Step.jsx +12 -4
  104. package/src/StepTracker/StepTracker.jsx +20 -13
  105. package/src/Tabs/TabsItem.jsx +3 -2
  106. package/src/TextInput/TextInput.jsx +1 -1
  107. package/src/TextInput/TextInputBase.jsx +11 -3
  108. package/src/ThemeProvider/ThemeProvider.jsx +16 -3
  109. package/src/ThemeProvider/utils/styles.js +9 -1
  110. package/src/ThemeProvider/utils/theme-tokens.js +1 -1
  111. package/src/Typography/Typography.jsx +11 -12
  112. package/src/index.js +1 -0
  113. package/src/utils/index.js +1 -0
  114. package/src/utils/props/clickProps.js +2 -2
  115. package/src/utils/props/handlerProps.js +64 -16
  116. package/src/utils/useScrollBlocking.js +57 -0
  117. package/src/utils/useScrollBlocking.native.js +2 -0
@@ -3,13 +3,22 @@ import { View, Animated, PanResponder, StyleSheet, Platform } from 'react-native
3
3
  import PropTypes from 'prop-types'
4
4
  import { useThemeTokens } from '../ThemeProvider'
5
5
  import { useViewport } from '../ViewportProvider'
6
- import { getTokensPropType, variantProp, selectSystemProps, a11yProps, viewProps } from '../utils'
6
+ import {
7
+ getTokensPropType,
8
+ getA11yPropsFromHtmlTag,
9
+ layoutTags,
10
+ variantProp,
11
+ selectSystemProps,
12
+ a11yProps,
13
+ viewProps,
14
+ useCopy
15
+ } from '../utils'
7
16
  import { useA11yInfo } from '../A11yInfoProvider'
8
17
  import { CarouselProvider } from './CarouselContext'
9
18
  import CarouselItem from './CarouselItem'
10
- import StepTracker from '../StepTracker'
11
- import StackView from '../StackView'
12
19
  import IconButton from '../IconButton'
20
+ import CarouselStepTracker from './CarouselStepTracker/CarouselStepTracker'
21
+ import dictionary from './dictionary'
13
22
 
14
23
  const staticStyles = StyleSheet.create({
15
24
  root: {
@@ -22,19 +31,6 @@ const staticStyles = StyleSheet.create({
22
31
  }
23
32
  })
24
33
 
25
- const staticTokens = {
26
- stackView: {
27
- justifyContent: 'center'
28
- },
29
- stepTracker: {
30
- showStepLabel: false,
31
- showStepTrackerLabel: true,
32
- knobCompletedBackgroundColor: 'none',
33
- connectorCompletedColor: 'none',
34
- connectorColor: 'none'
35
- }
36
- }
37
-
38
34
  const selectContainerStyles = (width) => ({
39
35
  backgroundColor: 'transparent',
40
36
  overflow: 'hidden',
@@ -79,15 +75,6 @@ const selectPreviousNextNavigationButtonStyles = (
79
75
  return styles
80
76
  }
81
77
 
82
- const defaultPanelNavigationDictionary = {
83
- en: {
84
- stepTrackerLabel: 'Showing %{stepNumber} of %{stepCount}'
85
- },
86
- fr: {
87
- stepTrackerLabel: 'Étape %{stepNumber} sur %{stepCount}: %{stepLabel}'
88
- }
89
- }
90
-
91
78
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
92
79
 
93
80
  /**
@@ -150,6 +137,7 @@ const Carousel = React.forwardRef(
150
137
  tokens,
151
138
  variant,
152
139
  children,
140
+ itemLabel = 'item',
153
141
  previousNextNavigationPosition = 'inside',
154
142
  previousNextIconSize = 'default',
155
143
  minDistanceToCapture = 5,
@@ -158,26 +146,46 @@ const Carousel = React.forwardRef(
158
146
  onAnimationEnd,
159
147
  onIndexChanged,
160
148
  springConfig = undefined,
161
- onRenderPanelNavigation,
162
- panelNavigationTextDictionary = defaultPanelNavigationDictionary,
149
+ panelNavigation = <CarouselStepTracker />,
150
+ tag = 'ul',
163
151
  accessibilityRole = 'adjustable',
164
152
  accessibilityLabel = 'carousel',
153
+ copy,
165
154
  ...rest
166
155
  },
167
156
  ref
168
157
  ) => {
169
158
  const viewport = useViewport()
159
+ const themeTokens = useThemeTokens('Carousel', tokens, variant, {
160
+ viewport
161
+ })
170
162
  const {
171
163
  previousIcon,
172
164
  nextIcon,
173
165
  showPreviousNextNavigation,
174
166
  showPanelNavigation,
175
- spaceBetweenSlideAndPreviousNextNavigation,
176
- spaceBetweenSlideAndPanelNavigation
177
- } = useThemeTokens('Carousel', tokens, variant, {
178
- viewport
179
- })
167
+ spaceBetweenSlideAndPreviousNextNavigation
168
+ } = themeTokens
180
169
  const [activeIndex, setActiveIndex] = React.useState(0)
170
+
171
+ const [isAnimating, setIsAnimating] = React.useState(false)
172
+ const handleAnimationStart = React.useCallback(
173
+ (...args) => {
174
+ if (typeof onAnimationStart === 'function') onAnimationStart(...args)
175
+ setIsAnimating(true)
176
+ },
177
+ [onAnimationStart]
178
+ )
179
+ const handleAnimationEnd = React.useCallback(
180
+ (...args) => {
181
+ if (typeof onAnimationEnd === 'function') onAnimationEnd(...args)
182
+ setIsAnimating(false)
183
+ },
184
+ [onAnimationEnd]
185
+ )
186
+
187
+ const getCopy = useCopy({ dictionary, copy })
188
+
181
189
  const childrenArray = React.Children.toArray(children)
182
190
  const systemProps = selectProps({
183
191
  ...rest,
@@ -202,10 +210,6 @@ const Carousel = React.forwardRef(
202
210
  const animatedY = React.useRef(0)
203
211
  const isFirstSlide = !activeIndex
204
212
  const isLastSlide = activeIndex + 1 >= children.length
205
- const panelNavigationTokens = {
206
- ...staticTokens.stepTracker,
207
- containerPaddingTop: spaceBetweenSlideAndPanelNavigation
208
- }
209
213
 
210
214
  const onContainerLayout = ({
211
215
  nativeEvent: {
@@ -230,18 +234,21 @@ const Carousel = React.forwardRef(
230
234
  }, [activeIndex, containerLayout.width, pan, animatedX])
231
235
 
232
236
  const animate = React.useCallback(
233
- (toValue) => {
237
+ (toValue, toIndex) => {
238
+ const handleAnimationEndToIndex = (...args) => handleAnimationEnd(toIndex, ...args)
234
239
  if (reduceMotionEnabled) {
235
- Animated.timing(pan, { toValue, duration: 1, useNativeDriver: false }).start()
240
+ Animated.timing(pan, { toValue, duration: 1, useNativeDriver: false }).start(
241
+ handleAnimationEndToIndex
242
+ )
236
243
  } else {
237
244
  Animated.spring(pan, {
238
245
  ...springConfig,
239
246
  toValue,
240
247
  useNativeDriver: false
241
- }).start()
248
+ }).start(handleAnimationEndToIndex)
242
249
  }
243
250
  },
244
- [pan, springConfig, reduceMotionEnabled]
251
+ [pan, springConfig, reduceMotionEnabled, handleAnimationEnd]
245
252
  )
246
253
 
247
254
  const updateIndex = React.useCallback(
@@ -258,32 +265,32 @@ const Carousel = React.forwardRef(
258
265
  calcDelta = -1 * activeIndex + delta - 1
259
266
  }
260
267
 
268
+ const index = activeIndex + calcDelta
269
+
261
270
  if (skipChanges) {
262
- animate(toValue)
271
+ animate(toValue, index)
263
272
  return calcDelta
264
273
  }
265
274
 
266
- const index = activeIndex + calcDelta
267
275
  setActiveIndex(index)
268
276
 
269
277
  toValue.x = containerLayout.width * -1 * calcDelta
270
278
 
271
- animate(toValue)
279
+ animate(toValue, index)
272
280
 
273
281
  if (onIndexChanged) onIndexChanged(calcDelta)
274
- if (onAnimationEnd) onAnimationEnd(index)
275
282
  return calcDelta
276
283
  },
277
- [containerLayout.width, activeIndex, animate, children.length, onIndexChanged, onAnimationEnd]
284
+ [containerLayout.width, activeIndex, animate, children.length, onIndexChanged]
278
285
  )
279
286
 
280
287
  const fixOffsetAndGo = React.useCallback(
281
288
  (delta) => {
282
289
  updateOffset()
283
- if (onAnimationStart) onAnimationStart(activeIndex)
290
+ handleAnimationStart(activeIndex)
284
291
  updateIndex(delta)
285
292
  },
286
- [updateIndex, updateOffset, activeIndex, onAnimationStart]
293
+ [updateIndex, updateOffset, activeIndex, handleAnimationStart]
287
294
  )
288
295
 
289
296
  const goToNeighboring = React.useCallback(
@@ -310,7 +317,7 @@ const Carousel = React.forwardRef(
310
317
  return false
311
318
  }
312
319
 
313
- if (onAnimationStart) onAnimationStart(activeIndex)
320
+ handleAnimationStart(activeIndex)
314
321
 
315
322
  return Math.abs(gestureState.dx) > minDistanceToCapture
316
323
  },
@@ -322,7 +329,7 @@ const Carousel = React.forwardRef(
322
329
  const correction = gesture.moveX - gesture.x0
323
330
 
324
331
  if (Math.abs(correction) < containerLayout.width * minDistanceForAction) {
325
- animate({ x: 0, y: 0 })
332
+ animate({ x: 0, y: 0 }, 0)
326
333
  } else {
327
334
  const delta = correction > 0 ? -1 : 1
328
335
  updateIndex(delta)
@@ -337,7 +344,7 @@ const Carousel = React.forwardRef(
337
344
  isSwipeAllowed,
338
345
  activeIndex,
339
346
  minDistanceForAction,
340
- onAnimationStart,
347
+ handleAnimationStart,
341
348
  minDistanceToCapture,
342
349
  pan.x
343
350
  ]
@@ -379,12 +386,27 @@ const Carousel = React.forwardRef(
379
386
  // Related discussion - https://github.com/telus/universal-design-system/issues/1549
380
387
  const previousNextIconButtonVariants = { size: previousNextIconSize, raised: true }
381
388
 
389
+ const getCopyWithPlaceholders = React.useCallback(
390
+ (copyKey) => {
391
+ const copyText = getCopy(copyKey)
392
+ .replace(/%\{itemLabel\}/g, itemLabel)
393
+ .replace(/%\{stepNumber\}/g, activeIndex + 1)
394
+ .replace(/%\{stepCount\}/g, childrenArray.length)
395
+
396
+ // First word might be a lowercase placeholder: capitalize the first letter
397
+ return `${copyText[0].toUpperCase()}${copyText.slice(1)}`
398
+ },
399
+ [activeIndex, childrenArray.length, itemLabel, getCopy]
400
+ )
401
+
382
402
  return (
383
403
  <CarouselProvider
384
404
  activeIndex={activeIndex}
385
405
  totalItems={childrenArray.length}
386
406
  width={containerLayout.width}
387
407
  goTo={goTo}
408
+ getCopyWithPlaceholders={getCopyWithPlaceholders}
409
+ themeTokens={themeTokens}
388
410
  >
389
411
  <View style={staticStyles.root} onLayout={onContainerLayout} ref={ref} {...systemProps}>
390
412
  {showPreviousNextNavigation && (
@@ -404,7 +426,10 @@ const Carousel = React.forwardRef(
404
426
  icon={previousIcon}
405
427
  onPress={goToPrev}
406
428
  variant={previousNextIconButtonVariants}
407
- accessibilityLabel="previous-button"
429
+ accessibilityLabel={getCopyWithPlaceholders('iconButtonLabel').replace(
430
+ '%{targetStep}',
431
+ activeIndex
432
+ )}
408
433
  />
409
434
  </View>
410
435
  )}
@@ -417,9 +442,11 @@ const Carousel = React.forwardRef(
417
442
  }
418
443
  ])}
419
444
  {...panResponder.panHandlers}
445
+ {...getA11yPropsFromHtmlTag(tag)}
420
446
  >
421
447
  {childrenArray.map((element, index) => {
422
- const clonedElement = React.cloneElement(element, { elementIndex: index })
448
+ const hidden = !isAnimating && index !== activeIndex
449
+ const clonedElement = React.cloneElement(element, { elementIndex: index, hidden })
423
450
  return <React.Fragment key={index.toFixed(2)}>{clonedElement}</React.Fragment>
424
451
  })}
425
452
  </Animated.View>
@@ -441,25 +468,15 @@ const Carousel = React.forwardRef(
441
468
  icon={nextIcon}
442
469
  onPress={goToNext}
443
470
  variant={previousNextIconButtonVariants}
444
- accessibilityLabel="next-button"
471
+ accessibilityLabel={getCopyWithPlaceholders('iconButtonLabel').replace(
472
+ '%{targetStep}',
473
+ activeIndex + 2
474
+ )}
445
475
  />
446
476
  </View>
447
477
  )}
448
478
  </View>
449
- {showPanelNavigation ? (
450
- <StackView direction="row" tokens={staticTokens.stackView}>
451
- {onRenderPanelNavigation ? (
452
- onRenderPanelNavigation({ activeIndex, totalItems: childrenArray.length })
453
- ) : (
454
- <StepTracker
455
- current={activeIndex}
456
- steps={childrenArray.map((_, index) => String(index))}
457
- dictionary={panelNavigationTextDictionary}
458
- tokens={panelNavigationTokens}
459
- />
460
- )}
461
- </StackView>
462
- ) : null}
479
+ {showPanelNavigation ? panelNavigation : null}
463
480
  </CarouselProvider>
464
481
  )
465
482
  }
@@ -473,6 +490,13 @@ Carousel.propTypes = {
473
490
  * Slides to render in Carousel. Wrap individual slides in `Carousel.Item`
474
491
  */
475
492
  children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
493
+ /**
494
+ * Lowercase language-appropriate user-facing description of what each Carousel slide represents.
495
+ * This is used when generating item labels. For example, if a carousel contains offers,
496
+ * pass itemLabel="summer offer" (or copy="fr" and an appropriate French translation) to genereate
497
+ * accessible labels such as "Summer offer 1 of 3" and "Show summer offer 2 of 3".
498
+ */
499
+ itemLabel: PropTypes.string,
476
500
  /**
477
501
  * `inside` renders the previous and next buttons inside the slide
478
502
  * `outside` renders the previous and next buttons outside the slide
@@ -514,20 +538,20 @@ Carousel.propTypes = {
514
538
  */
515
539
  onIndexChanged: PropTypes.func,
516
540
  /**
517
- * Use this to render a custom panel navigation element instead of dots navigation
518
- * This function is also provided with an object with the following properties
519
- * activeIndex: index of current slide
520
- * totalItems: total number of slides
541
+ * Use this to render a custom panel navigation element instead of the default StepTracker's based navigation
542
+ * You can make use of `useCarousel` within your custom panel navigation component to hook into various Carousel states such as:
543
+ * - activeIndex: index of current slide
544
+ * - totalItems: total number of slides
521
545
  * Use it as follows:
522
546
  * ```js
523
547
  * <Carousel
524
- * onRenderPanelNavigation={({ totalItems, activeIndex }) => <Text>Showing {activeIndex + 1}</Text>}
548
+ * panelNavigation={<CustomPanelNavigation />}
525
549
  * >
526
550
  * <Carousel.Item>First Slide</Carousel.Item>
527
551
  * </Carousel>
528
552
  * ```
529
553
  */
530
- onRenderPanelNavigation: PropTypes.func,
554
+ panelNavigation: PropTypes.element,
531
555
  /**
532
556
  * When slide animation start
533
557
  * This function is also provided with a parameter indicating the current slide index before animation starts
@@ -576,7 +600,15 @@ Carousel.propTypes = {
576
600
  /**
577
601
  * Provide custom accessibilityLabel for Carousel container
578
602
  */
579
- accessibilityLabel: PropTypes.string
603
+ accessibilityLabel: PropTypes.string,
604
+ /**
605
+ * HTML tag to use for the Carousel item's immediate parent. Defaults to `'ul'` so that
606
+ * assistive technology tools know to intepret the carousel as a list.
607
+ *
608
+ * Note that if the immediate Carousel children do not all render as `'li'` elements,
609
+ * this should be changed (e.g. pass tag="div") because only 'li' is a valid child of 'ul'.
610
+ */
611
+ tag: PropTypes.oneOf(layoutTags)
580
612
  }
581
613
 
582
614
  Carousel.Item = CarouselItem
@@ -1,12 +1,21 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
+ import { getTokensPropType } from '../utils'
3
4
 
4
5
  const CarouselContext = React.createContext()
5
6
 
6
- const CarouselProvider = ({ children, activeIndex, totalItems, width, goTo }) => {
7
+ const CarouselProvider = ({
8
+ children,
9
+ activeIndex,
10
+ totalItems,
11
+ width,
12
+ goTo,
13
+ getCopyWithPlaceholders,
14
+ themeTokens
15
+ }) => {
7
16
  const value = React.useMemo(
8
- () => ({ activeIndex, totalItems, width, goTo }),
9
- [activeIndex, totalItems, width, goTo]
17
+ () => ({ activeIndex, totalItems, width, goTo, getCopyWithPlaceholders, themeTokens }),
18
+ [activeIndex, totalItems, width, goTo, getCopyWithPlaceholders, themeTokens]
10
19
  )
11
20
  return <CarouselContext.Provider value={value}>{children}</CarouselContext.Provider>
12
21
  }
@@ -24,7 +33,9 @@ CarouselProvider.propTypes = {
24
33
  activeIndex: PropTypes.number.isRequired,
25
34
  totalItems: PropTypes.number.isRequired,
26
35
  width: PropTypes.number.isRequired,
27
- goTo: PropTypes.func.isRequired
36
+ goTo: PropTypes.func.isRequired,
37
+ getCopyWithPlaceholders: PropTypes.func.isRequired,
38
+ themeTokens: getTokensPropType('Carousel')
28
39
  }
29
40
 
30
41
  export { CarouselProvider, useCarousel }
@@ -1,7 +1,13 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import { View, Platform } from 'react-native'
4
- import { selectSystemProps, a11yProps, viewProps } from '../../utils'
4
+ import {
5
+ layoutTags,
6
+ getA11yPropsFromHtmlTag,
7
+ selectSystemProps,
8
+ a11yProps,
9
+ viewProps
10
+ } from '../../utils'
5
11
  import { useCarousel } from '../CarouselContext'
6
12
 
7
13
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
@@ -10,17 +16,21 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, vie
10
16
  * `Carousel.Item` is used to wrap the content of an individual slide and is suppsoed to be the
11
17
  * only top-level component passed to the `Carousel`
12
18
  */
13
- const CarouselItem = ({ children, elementIndex, ...rest }) => {
14
- const { width, activeIndex, totalItems } = useCarousel()
19
+ const CarouselItem = ({ children, elementIndex, tag = 'li', hidden, ...rest }) => {
20
+ const { width, activeIndex } = useCarousel()
15
21
  const selectedProps = selectProps({
16
22
  ...rest,
17
- // `group` role crashes the app on Android so setting it to `none` for Android
18
- accessibilityRole: Platform.OS === 'android' ? 'none' : 'group',
19
- accessibilityLabel: `Showing ${elementIndex + 1} of ${totalItems}`
23
+ ...getA11yPropsFromHtmlTag(tag, rest.accessibilityRole)
20
24
  })
25
+
21
26
  const focusabilityProps = activeIndex === elementIndex ? {} : a11yProps.nonFocusableProps
27
+ const style = { width }
28
+ if (hidden && Platform.OS === 'web') {
29
+ // On web, visibility: hidden makes all children non-focusable. It doesn't exist on native.
30
+ style.visibility = 'hidden'
31
+ }
22
32
  return (
23
- <View style={{ width }} {...selectedProps} {...focusabilityProps}>
33
+ <View style={style} {...selectedProps} {...focusabilityProps}>
24
34
  {children}
25
35
  </View>
26
36
  )
@@ -40,7 +50,15 @@ CarouselItem.propTypes = {
40
50
  /**
41
51
  * Content of the slide
42
52
  */
43
- children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired
53
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
54
+ /**
55
+ * Sets the HTML tag of the outer container. By default `'li'` so that assistive technology sees
56
+ * the Carousel as a list of items.
57
+ *
58
+ * Carousel's innermost container defaults to `'ul'` which can be overridden. If the tag of either
59
+ * `Carousel` or `Carousel.Item` is overriden, the other should be too, to avoid producing invalid HTML.
60
+ */
61
+ tag: PropTypes.oneOf(layoutTags)
44
62
  }
45
63
 
46
64
  CarouselItem.displayName = 'Carousel.Item'
@@ -0,0 +1,36 @@
1
+ import React from 'react'
2
+ import { useCarousel } from '../CarouselContext'
3
+ import StepTracker from '../../StepTracker'
4
+ import StackView from '../../StackView'
5
+
6
+ const CarouselStepTracker = () => {
7
+ const { activeIndex, totalItems, getCopyWithPlaceholders, themeTokens } = useCarousel()
8
+ const stackViewTokens = {
9
+ justifyContent: 'center'
10
+ }
11
+ const stepTrackerTokens = {
12
+ showStepLabel: false,
13
+ showStepTrackerLabel: true,
14
+ knobCompletedBackgroundColor: 'none',
15
+ connectorCompletedColor: 'none',
16
+ connectorColor: 'none',
17
+ containerPaddingTop: themeTokens.spaceBetweenSlideAndPanelNavigation
18
+ }
19
+ const steps = Array.from(Array(totalItems)).map((_, index) => String(index))
20
+ return (
21
+ <StackView direction="row" tokens={stackViewTokens}>
22
+ <StepTracker
23
+ current={activeIndex}
24
+ steps={steps}
25
+ copy={{
26
+ // Give StepTracker copy from Carousel's language and dictionary
27
+ stepLabel: getCopyWithPlaceholders('stepLabel'),
28
+ stepTrackerLabel: getCopyWithPlaceholders('stepTrackerLabel')
29
+ }}
30
+ tokens={stepTrackerTokens}
31
+ />
32
+ </StackView>
33
+ )
34
+ }
35
+
36
+ export default CarouselStepTracker
@@ -0,0 +1,3 @@
1
+ import CarouselStepTracker from './CarouselStepTracker'
2
+
3
+ export default CarouselStepTracker
@@ -0,0 +1,16 @@
1
+ // 'stepLabel' and 'stepTrackerLabel' are passed down to StepTracker
2
+ export default {
3
+ en: {
4
+ carouselLabel: '%{stepCount} items',
5
+ iconButtonLabel: 'Show %{itemLabel} %{targetStep} of %{stepCount}',
6
+ stepLabel: '%{itemLabel} %{stepNumber}',
7
+ stepTrackerLabel: '%{itemLabel} %{stepNumber} of %{stepCount}'
8
+ },
9
+ fr: {
10
+ // TODO: French translations here
11
+ carouselLabel: '(fr) %{stepCount} items',
12
+ iconButtonLabel: '(fr) Show %{itemLabel} %{targetStep} of %{stepCount}',
13
+ stepLabel: '(fr) %{itemLabel} %{stepNumber}',
14
+ stepTrackerLabel: '(fr) %{itemLabel} %{stepNumber} of %{stepCount}'
15
+ }
16
+ }
@@ -6,7 +6,12 @@ import CheckboxInput from './CheckboxInput'
6
6
  import CheckboxLabel from '../InputLabel/LabelContent'
7
7
  import Feedback from '../Feedback'
8
8
  import StackView from '../StackView'
9
- import { applyShadowToken, applyTextStyles, useThemeTokensCallback } from '../ThemeProvider'
9
+ import {
10
+ applyShadowToken,
11
+ applyTextStyles,
12
+ useTheme,
13
+ useThemeTokensCallback
14
+ } from '../ThemeProvider'
10
15
  import {
11
16
  a11yProps,
12
17
  focusHandlerProps,
@@ -54,21 +59,18 @@ const selectInputStyles = (
54
59
  }
55
60
  })
56
61
  })
57
- const selectLabelStyles = ({
58
- labelColor,
59
- labelFontName,
60
- labelFontSize,
61
- labelFontWeight,
62
- labelMarginLeft,
63
- labelLineHeight
64
- }) => ({
62
+ const selectLabelStyles = (
63
+ { labelColor, labelFontName, labelFontSize, labelFontWeight, labelMarginLeft, labelLineHeight },
64
+ themeOptions
65
+ ) => ({
65
66
  marginLeft: labelMarginLeft,
66
67
  ...applyTextStyles({
67
68
  color: labelColor,
68
69
  fontName: labelFontName,
69
70
  fontWeight: labelFontWeight,
70
71
  fontSize: labelFontSize,
71
- lineHeight: labelLineHeight
72
+ lineHeight: labelLineHeight,
73
+ themeOptions
72
74
  })
73
75
  })
74
76
  const selectIconTokens = ({ icon, iconColor, iconSize }) => ({
@@ -172,6 +174,7 @@ const Checkbox = forwardRef(
172
174
  }
173
175
  const uniqueId = useUniqueId('checkbox')
174
176
  const inputId = id ?? uniqueId
177
+ const { themeOptions } = useTheme()
175
178
 
176
179
  return (
177
180
  <View style={staticStyles.wrapper} ref={ref}>
@@ -187,7 +190,7 @@ const Checkbox = forwardRef(
187
190
  {({ focused: focus, hovered: hover, pressed }) => {
188
191
  const { icon: IconComponent, ...stateTokens } = getTokens({ focus, hover, pressed })
189
192
  const iconTokens = selectIconTokens(stateTokens)
190
- const labelStyles = selectLabelStyles(stateTokens)
193
+ const labelStyles = selectLabelStyles(stateTokens, themeOptions)
191
194
  const alignWithLabel = label
192
195
  ? [staticStyles.alignWithLabel, { height: labelStyles.lineHeight }]
193
196
  : null
@@ -61,7 +61,7 @@ const [selectItemProps, selectedItemPropTypes] = selectSystemProps([
61
61
  * @example
62
62
  * ```jsx
63
63
  * <CheckboxGroup
64
- * initialCheckedId="check1"
64
+ * initialCheckedIds="check1"
65
65
  * items={[
66
66
  * { label: 'Checkbox 1', id: 'check1' },
67
67
  * { label: 'Checkbox 2', id: 'check2' },
@@ -2,7 +2,7 @@ import React, { forwardRef } from 'react'
2
2
  import { StyleSheet, Text, View } from 'react-native'
3
3
  import PropTypes from 'prop-types'
4
4
 
5
- import { applyTextStyles, useThemeTokens } from '../ThemeProvider'
5
+ import { applyTextStyles, useTheme, useThemeTokens } from '../ThemeProvider'
6
6
  import {
7
7
  a11yProps,
8
8
  getTokensPropType,
@@ -17,10 +17,16 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, vie
17
17
 
18
18
  const selectStyles = (tokens) => selectTokens('Feedback', tokens)
19
19
 
20
- const selectTitleTextStyles = ({ titleFontSize, ...tokens }) =>
21
- applyTextStyles(selectTokens('Typography', { ...tokens, fontSize: titleFontSize }))
22
- const selectContentTextStyles = ({ contentFontSize, ...tokens }) =>
23
- applyTextStyles(selectTokens('Typography', { ...tokens, fontSize: contentFontSize }))
20
+ const selectTitleTextStyles = ({ titleFontSize, ...tokens }, themeOptions) =>
21
+ applyTextStyles({
22
+ ...selectTokens('Typography', { ...tokens, fontSize: titleFontSize, themeOptions }),
23
+ themeOptions
24
+ })
25
+ const selectContentTextStyles = ({ contentFontSize, ...tokens }, themeOptions) =>
26
+ applyTextStyles({
27
+ ...selectTokens('Typography', { ...tokens, fontSize: contentFontSize }),
28
+ themeOptions
29
+ })
24
30
 
25
31
  const selectIconTokens = ({ iconSize, iconColor }) => ({
26
32
  size: iconSize,
@@ -56,8 +62,9 @@ const Feedback = forwardRef(
56
62
 
57
63
  const { icon: IconComponent } = themeTokens
58
64
 
59
- const titleTextStyles = selectTitleTextStyles(themeTokens)
60
- const contentTextStyles = selectContentTextStyles(themeTokens)
65
+ const { themeOptions } = useTheme()
66
+ const titleTextStyles = selectTitleTextStyles(themeTokens, themeOptions)
67
+ const contentTextStyles = selectContentTextStyles(themeTokens, themeOptions)
61
68
 
62
69
  const content =
63
70
  typeof children === 'string' ? <Text style={contentTextStyles}>{children}</Text> : children
@@ -39,6 +39,7 @@ const IconText = forwardRef(
39
39
  IconText.displayName = 'IconText'
40
40
 
41
41
  IconText.propTypes = {
42
+ /* eslint-disable react/no-unused-prop-types */ // eslint is having hard time seeing these props through forwardRef
42
43
  /**
43
44
  * Amount of space to separate the text content and icon. Uses the themes's spacing scale
44
45
  * (see useSpacingScale for more info).
@@ -63,6 +64,7 @@ IconText.propTypes = {
63
64
  * `<Typography>` component, or a component that renders `<Text>`.
64
65
  */
65
66
  children: PropTypes.node
67
+ /* eslint-enable react/no-unused-prop-types */
66
68
  }
67
69
 
68
70
  export default IconText