@telus-uds/components-base 1.12.1 → 1.13.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.
- package/CHANGELOG.md +20 -2
- package/component-docs.json +740 -33
- package/lib/Button/ButtonGroup.js +7 -0
- package/lib/Carousel/Carousel.js +69 -12
- package/lib/Carousel/CarouselContext.js +17 -11
- package/lib/Carousel/CarouselFirstFocus/CarouselFirstFocus.js +73 -0
- package/lib/Carousel/CarouselTabs/CarouselTabs.js +70 -0
- package/lib/Carousel/CarouselTabs/CarouselTabsPanel.js +95 -0
- package/lib/Carousel/CarouselTabs/CarouselTabsPanelItem.js +148 -0
- package/lib/Carousel/CarouselTabs/index.js +13 -0
- package/lib/Carousel/CarouselThumbnail.js +99 -0
- package/lib/Carousel/CarouselThumbnailNavigation.js +87 -0
- package/lib/Carousel/dictionary.js +4 -2
- package/lib/Carousel/index.js +10 -1
- package/lib/Checkbox/CheckboxGroup.js +7 -0
- package/lib/Link/InlinePressable.js +1 -8
- package/lib/Link/LinkBase.js +5 -6
- package/lib/Radio/RadioGroup.js +8 -0
- package/lib/RadioCard/RadioCardGroup.js +7 -0
- package/lib/SkipLink/SkipLink.js +216 -0
- package/lib/SkipLink/index.js +13 -0
- package/lib/ThemeProvider/ThemeProvider.js +6 -1
- package/lib/ToggleSwitch/ToggleSwitchGroup.js +7 -0
- package/lib/index.js +9 -0
- package/lib-module/Button/ButtonGroup.js +7 -0
- package/lib-module/Carousel/Carousel.js +66 -11
- package/lib-module/Carousel/CarouselContext.js +17 -11
- package/lib-module/Carousel/CarouselFirstFocus/CarouselFirstFocus.js +51 -0
- package/lib-module/Carousel/CarouselTabs/CarouselTabs.js +50 -0
- package/lib-module/Carousel/CarouselTabs/CarouselTabsPanel.js +76 -0
- package/lib-module/Carousel/CarouselTabs/CarouselTabsPanelItem.js +126 -0
- package/lib-module/Carousel/CarouselTabs/index.js +2 -0
- package/lib-module/Carousel/CarouselThumbnail.js +85 -0
- package/lib-module/Carousel/CarouselThumbnailNavigation.js +66 -0
- package/lib-module/Carousel/dictionary.js +4 -2
- package/lib-module/Carousel/index.js +2 -1
- package/lib-module/Checkbox/CheckboxGroup.js +7 -0
- package/lib-module/Link/InlinePressable.js +1 -8
- package/lib-module/Link/LinkBase.js +5 -6
- package/lib-module/Radio/RadioGroup.js +8 -0
- package/lib-module/RadioCard/RadioCardGroup.js +7 -0
- package/lib-module/SkipLink/SkipLink.js +188 -0
- package/lib-module/SkipLink/index.js +2 -0
- package/lib-module/ThemeProvider/ThemeProvider.js +5 -1
- package/lib-module/ToggleSwitch/ToggleSwitchGroup.js +7 -0
- package/lib-module/index.js +1 -0
- package/package.json +46 -47
- package/src/Button/ButtonGroup.jsx +6 -0
- package/src/Carousel/Carousel.jsx +68 -10
- package/src/Carousel/CarouselContext.jsx +22 -9
- package/src/Carousel/CarouselFirstFocus/CarouselFirstFocus.jsx +49 -0
- package/src/Carousel/CarouselTabs/CarouselTabs.jsx +37 -0
- package/src/Carousel/CarouselTabs/CarouselTabsPanel.jsx +69 -0
- package/src/Carousel/CarouselTabs/CarouselTabsPanelItem.jsx +119 -0
- package/src/Carousel/CarouselTabs/index.js +3 -0
- package/src/Carousel/CarouselThumbnail.jsx +77 -0
- package/src/Carousel/CarouselThumbnailNavigation.jsx +53 -0
- package/src/Carousel/dictionary.js +4 -2
- package/src/Carousel/index.js +1 -0
- package/src/Checkbox/CheckboxGroup.jsx +7 -0
- package/src/Link/InlinePressable.jsx +2 -8
- package/src/Link/LinkBase.jsx +7 -16
- package/src/Radio/RadioGroup.jsx +7 -0
- package/src/RadioCard/RadioCardGroup.jsx +6 -0
- package/src/SkipLink/SkipLink.jsx +179 -0
- package/src/SkipLink/index.js +3 -0
- package/src/ThemeProvider/ThemeProvider.jsx +7 -1
- package/src/ToggleSwitch/ToggleSwitchGroup.jsx +6 -0
- package/src/index.js +1 -0
package/lib/index.js
CHANGED
|
@@ -34,6 +34,7 @@ var _exportNames = {
|
|
|
34
34
|
Select: true,
|
|
35
35
|
SideNav: true,
|
|
36
36
|
Skeleton: true,
|
|
37
|
+
SkipLink: true,
|
|
37
38
|
Spacer: true,
|
|
38
39
|
StackView: true,
|
|
39
40
|
StepTracker: true,
|
|
@@ -249,6 +250,12 @@ Object.defineProperty(exports, "Skeleton", {
|
|
|
249
250
|
return _Skeleton.default;
|
|
250
251
|
}
|
|
251
252
|
});
|
|
253
|
+
Object.defineProperty(exports, "SkipLink", {
|
|
254
|
+
enumerable: true,
|
|
255
|
+
get: function () {
|
|
256
|
+
return _SkipLink.default;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
252
259
|
Object.defineProperty(exports, "Spacer", {
|
|
253
260
|
enumerable: true,
|
|
254
261
|
get: function () {
|
|
@@ -524,6 +531,8 @@ var _SideNav = _interopRequireDefault(require("./SideNav"));
|
|
|
524
531
|
|
|
525
532
|
var _Skeleton = _interopRequireDefault(require("./Skeleton"));
|
|
526
533
|
|
|
534
|
+
var _SkipLink = _interopRequireDefault(require("./SkipLink"));
|
|
535
|
+
|
|
527
536
|
var _Spacer = _interopRequireDefault(require("./Spacer"));
|
|
528
537
|
|
|
529
538
|
var _StackView = _interopRequireWildcard(require("./StackView"));
|
|
@@ -26,6 +26,7 @@ const ButtonGroup = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
|
26
26
|
legend,
|
|
27
27
|
tooltip,
|
|
28
28
|
hint,
|
|
29
|
+
hintPosition = 'inline',
|
|
29
30
|
validation,
|
|
30
31
|
feedback,
|
|
31
32
|
name: inputGroupName,
|
|
@@ -77,6 +78,7 @@ const ButtonGroup = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
|
77
78
|
legend: legend,
|
|
78
79
|
tooltip: tooltip,
|
|
79
80
|
hint: hint,
|
|
81
|
+
hintPosition: hintPosition,
|
|
80
82
|
space: fieldSpace,
|
|
81
83
|
feedback: feedback,
|
|
82
84
|
readOnly: readOnly,
|
|
@@ -208,6 +210,11 @@ ButtonGroup.propTypes = { ...selectedSystemPropTypes,
|
|
|
208
210
|
*/
|
|
209
211
|
hint: PropTypes.string,
|
|
210
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Position of the hint relative to label. Use `below` to display a larger hint below the label.
|
|
215
|
+
*/
|
|
216
|
+
hintPosition: PropTypes.oneOf(['inline', 'below']),
|
|
217
|
+
|
|
211
218
|
/**
|
|
212
219
|
* Optional tooltip text content to include alongside the legend and hint.
|
|
213
220
|
*/
|
|
@@ -7,12 +7,15 @@ import Platform from "react-native-web/dist/exports/Platform";
|
|
|
7
7
|
import PropTypes from 'prop-types';
|
|
8
8
|
import { useThemeTokens } from '../ThemeProvider';
|
|
9
9
|
import { useViewport } from '../ViewportProvider';
|
|
10
|
-
import { getTokensPropType, getA11yPropsFromHtmlTag, layoutTags, variantProp, selectSystemProps, a11yProps, viewProps, useCopy } from '../utils';
|
|
10
|
+
import { getTokensPropType, getA11yPropsFromHtmlTag, layoutTags, variantProp, selectSystemProps, a11yProps, viewProps, useCopy, unpackFragment } from '../utils';
|
|
11
11
|
import { useA11yInfo } from '../A11yInfoProvider';
|
|
12
12
|
import { CarouselProvider } from './CarouselContext';
|
|
13
13
|
import CarouselItem from './CarouselItem';
|
|
14
14
|
import IconButton from '../IconButton';
|
|
15
|
-
import
|
|
15
|
+
import SkipLink from '../SkipLink';
|
|
16
|
+
import A11yText from '../A11yText';
|
|
17
|
+
import CarouselStepTracker from './CarouselStepTracker';
|
|
18
|
+
import CarouselThumbnailNavigation from './CarouselThumbnailNavigation';
|
|
16
19
|
import dictionary from './dictionary';
|
|
17
20
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
18
21
|
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
@@ -134,11 +137,18 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
134
137
|
onAnimationStart,
|
|
135
138
|
onAnimationEnd,
|
|
136
139
|
onIndexChanged,
|
|
140
|
+
skipLinkHref,
|
|
141
|
+
refocus,
|
|
142
|
+
title = 'carousel',
|
|
137
143
|
springConfig = undefined,
|
|
138
|
-
|
|
144
|
+
thumbnails = undefined,
|
|
145
|
+
panelNavigation = thumbnails ? /*#__PURE__*/_jsx(CarouselThumbnailNavigation, {
|
|
146
|
+
thumbnails: thumbnails
|
|
147
|
+
}) : /*#__PURE__*/_jsx(CarouselStepTracker, {}),
|
|
139
148
|
tag = 'ul',
|
|
140
|
-
accessibilityRole
|
|
141
|
-
accessibilityLabel =
|
|
149
|
+
accessibilityRole,
|
|
150
|
+
accessibilityLabel = title,
|
|
151
|
+
accessibilityLiveRegion = 'polite',
|
|
142
152
|
copy,
|
|
143
153
|
...rest
|
|
144
154
|
} = _ref;
|
|
@@ -167,7 +177,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
167
177
|
dictionary,
|
|
168
178
|
copy
|
|
169
179
|
});
|
|
170
|
-
const childrenArray =
|
|
180
|
+
const childrenArray = unpackFragment(children);
|
|
171
181
|
const systemProps = selectProps({ ...rest,
|
|
172
182
|
accessibilityRole,
|
|
173
183
|
accessibilityLabel,
|
|
@@ -186,6 +196,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
186
196
|
width: 0
|
|
187
197
|
});
|
|
188
198
|
const [previousNextNavigationButtonWidth, setPreviousNextNavigationButtonWidth] = React.useState(0);
|
|
199
|
+
const firstFocusRef = React.useRef(null);
|
|
189
200
|
const pan = React.useRef(new Animated.ValueXY()).current;
|
|
190
201
|
const animatedX = React.useRef(0);
|
|
191
202
|
const animatedY = React.useRef(0);
|
|
@@ -285,10 +296,13 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
285
296
|
return calcDelta;
|
|
286
297
|
}, [containerLayout.width, activeIndex, animate, children.length, onIndexChanged]);
|
|
287
298
|
const fixOffsetAndGo = React.useCallback(delta => {
|
|
299
|
+
var _firstFocusRef$curren;
|
|
300
|
+
|
|
288
301
|
updateOffset();
|
|
289
302
|
handleAnimationStart(activeIndex);
|
|
290
303
|
updateIndex(delta);
|
|
291
|
-
|
|
304
|
+
if (refocus) (_firstFocusRef$curren = firstFocusRef.current) === null || _firstFocusRef$curren === void 0 ? void 0 : _firstFocusRef$curren.focus();
|
|
305
|
+
}, [updateIndex, updateOffset, activeIndex, handleAnimationStart, refocus]);
|
|
292
306
|
const goToNeighboring = React.useCallback(function () {
|
|
293
307
|
let toPrev = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
294
308
|
fixOffsetAndGo(toPrev ? -1 : 1);
|
|
@@ -371,17 +385,20 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
371
385
|
raised: true
|
|
372
386
|
};
|
|
373
387
|
const getCopyWithPlaceholders = React.useCallback(copyKey => {
|
|
374
|
-
const copyText = getCopy(copyKey).replace(/%\{itemLabel\}/g, itemLabel).replace(/%\{stepNumber\}/g, activeIndex + 1).replace(/%\{stepCount\}/g, childrenArray.length); // First word might be a lowercase placeholder: capitalize the first letter
|
|
388
|
+
const copyText = getCopy(copyKey).replace(/%\{title\}/g, title).replace(/%\{itemLabel\}/g, itemLabel).replace(/%\{stepNumber\}/g, activeIndex + 1).replace(/%\{stepCount\}/g, childrenArray.length); // First word might be a lowercase placeholder: capitalize the first letter
|
|
375
389
|
|
|
376
390
|
return "".concat(copyText[0].toUpperCase()).concat(copyText.slice(1));
|
|
377
|
-
}, [activeIndex, childrenArray.length, itemLabel, getCopy]);
|
|
391
|
+
}, [activeIndex, childrenArray.length, itemLabel, getCopy, title]);
|
|
378
392
|
return /*#__PURE__*/_jsxs(CarouselProvider, {
|
|
379
393
|
activeIndex: activeIndex,
|
|
380
|
-
totalItems: childrenArray.length,
|
|
381
|
-
width: containerLayout.width,
|
|
382
394
|
goTo: goTo,
|
|
383
395
|
getCopyWithPlaceholders: getCopyWithPlaceholders,
|
|
396
|
+
itemLabel: itemLabel,
|
|
397
|
+
totalItems: childrenArray.length,
|
|
384
398
|
themeTokens: themeTokens,
|
|
399
|
+
firstFocusRef: firstFocusRef,
|
|
400
|
+
refocus: refocus,
|
|
401
|
+
width: containerLayout.width,
|
|
385
402
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
386
403
|
style: staticStyles.root,
|
|
387
404
|
onLayout: onContainerLayout,
|
|
@@ -397,6 +414,17 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
397
414
|
variant: previousNextIconButtonVariants,
|
|
398
415
|
accessibilityLabel: getCopyWithPlaceholders('iconButtonLabel').replace('%{targetStep}', activeIndex)
|
|
399
416
|
})
|
|
417
|
+
}), Boolean(skipLinkHref) && /*#__PURE__*/_jsx(SkipLink, {
|
|
418
|
+
ref: firstFocusRef,
|
|
419
|
+
href: skipLinkHref,
|
|
420
|
+
children: getCopyWithPlaceholders('skipLink')
|
|
421
|
+
}), /*#__PURE__*/_jsx(A11yText // Read the current slide position to screen readers on slide.
|
|
422
|
+
// If it's set to refocus and doesn't have a SkipLink to focus to, focus this.
|
|
423
|
+
, {
|
|
424
|
+
ref: !skipLinkHref && refocus ? firstFocusRef : null,
|
|
425
|
+
accessibilityLiveRegion: !skipLinkHref && refocus ? undefined : 'polite',
|
|
426
|
+
focusable: !skipLinkHref && refocus,
|
|
427
|
+
text: getCopyWithPlaceholders('stepTrackerLabel')
|
|
400
428
|
}), /*#__PURE__*/_jsx(View, {
|
|
401
429
|
style: selectContainerStyles(containerLayout.width),
|
|
402
430
|
children: /*#__PURE__*/_jsx(Animated.View, {
|
|
@@ -409,6 +437,9 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
409
437
|
}]),
|
|
410
438
|
...panResponder.panHandlers,
|
|
411
439
|
...getA11yPropsFromHtmlTag(tag),
|
|
440
|
+
// In iframes on Mac (e.g. in Storybook), this content may be misread or read twice.
|
|
441
|
+
// This is a known Voiceover bug: https://github.com/phetsims/a11y-research/issues/132
|
|
442
|
+
accessibilityLiveRegion: accessibilityLiveRegion,
|
|
412
443
|
children: childrenArray.map((element, index) => {
|
|
413
444
|
const hidden = !isAnimating && index !== activeIndex;
|
|
414
445
|
const clonedElement = /*#__PURE__*/React.cloneElement(element, {
|
|
@@ -468,6 +499,15 @@ Carousel.propTypes = { ...selectedSystemPropTypes,
|
|
|
468
499
|
*/
|
|
469
500
|
springConfig: PropTypes.object,
|
|
470
501
|
|
|
502
|
+
/**
|
|
503
|
+
* An array of objects containing information on the thumbnails to be rendered as navigation panel
|
|
504
|
+
*/
|
|
505
|
+
thumbnails: PropTypes.arrayOf(PropTypes.shape({
|
|
506
|
+
accessibilityLabel: PropTypes.string,
|
|
507
|
+
alt: PropTypes.string,
|
|
508
|
+
src: PropTypes.string
|
|
509
|
+
})),
|
|
510
|
+
|
|
471
511
|
/**
|
|
472
512
|
* Minimal part of slide width must be swiped for changing index.
|
|
473
513
|
* Otherwise animation restore current slide. Default value 0.2 means that 20% must be swiped for change index
|
|
@@ -497,6 +537,21 @@ Carousel.propTypes = { ...selectedSystemPropTypes,
|
|
|
497
537
|
*/
|
|
498
538
|
onIndexChanged: PropTypes.func,
|
|
499
539
|
|
|
540
|
+
/**
|
|
541
|
+
* If this is a complex carousel with a lot of focusable content, pass a href for a skip link. Typically, this will be an anchor link
|
|
542
|
+
* with the ID of a focusable element immediately after the Carousel, e.g. `'#section-2-heading'`.
|
|
543
|
+
*/
|
|
544
|
+
skipLinkHref: PropTypes.string,
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* If true, whenever a new slide comes into view, the focus of the Carousel switches to the start.
|
|
548
|
+
*
|
|
549
|
+
* Pass this as true when using carousel items that contain interactive content, so a user can easily tab into that content.
|
|
550
|
+
*
|
|
551
|
+
* If skipLinkHref is passed, the focus target will be the SkipLink; if not, it'll be an empty element before the slide content.
|
|
552
|
+
*/
|
|
553
|
+
refocus: PropTypes.bool,
|
|
554
|
+
|
|
500
555
|
/**
|
|
501
556
|
* Use this to render a custom panel navigation element instead of the default StepTracker's based navigation
|
|
502
557
|
* You can make use of `useCarousel` within your custom panel navigation component to hook into various Carousel states such as:
|
|
@@ -6,22 +6,26 @@ const CarouselContext = /*#__PURE__*/React.createContext();
|
|
|
6
6
|
|
|
7
7
|
const CarouselProvider = _ref => {
|
|
8
8
|
let {
|
|
9
|
-
children,
|
|
10
9
|
activeIndex,
|
|
11
|
-
|
|
12
|
-
width,
|
|
10
|
+
children,
|
|
13
11
|
goTo,
|
|
14
12
|
getCopyWithPlaceholders,
|
|
15
|
-
|
|
13
|
+
itemLabel,
|
|
14
|
+
refocus = false,
|
|
15
|
+
themeTokens,
|
|
16
|
+
totalItems,
|
|
17
|
+
width
|
|
16
18
|
} = _ref;
|
|
17
19
|
const value = React.useMemo(() => ({
|
|
18
20
|
activeIndex,
|
|
19
|
-
totalItems,
|
|
20
|
-
width,
|
|
21
21
|
goTo,
|
|
22
22
|
getCopyWithPlaceholders,
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
itemLabel,
|
|
24
|
+
refocus,
|
|
25
|
+
themeTokens,
|
|
26
|
+
totalItems,
|
|
27
|
+
width
|
|
28
|
+
}), [activeIndex, goTo, getCopyWithPlaceholders, itemLabel, refocus, totalItems, themeTokens, width]);
|
|
25
29
|
return /*#__PURE__*/_jsx(CarouselContext.Provider, {
|
|
26
30
|
value: value,
|
|
27
31
|
children: children
|
|
@@ -41,10 +45,12 @@ function useCarousel() {
|
|
|
41
45
|
CarouselProvider.propTypes = {
|
|
42
46
|
children: PropTypes.arrayOf(PropTypes.element).isRequired,
|
|
43
47
|
activeIndex: PropTypes.number.isRequired,
|
|
44
|
-
totalItems: PropTypes.number.isRequired,
|
|
45
|
-
width: PropTypes.number.isRequired,
|
|
46
48
|
goTo: PropTypes.func.isRequired,
|
|
47
49
|
getCopyWithPlaceholders: PropTypes.func.isRequired,
|
|
48
|
-
|
|
50
|
+
itemLabel: PropTypes.string.isRequired,
|
|
51
|
+
refocus: PropTypes.bool,
|
|
52
|
+
themeTokens: getTokensPropType('Carousel'),
|
|
53
|
+
totalItems: PropTypes.number.isRequired,
|
|
54
|
+
width: PropTypes.number.isRequired
|
|
49
55
|
};
|
|
50
56
|
export { CarouselProvider, useCarousel };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
|
+
import Pressable from "react-native-web/dist/exports/Pressable";
|
|
3
|
+
import Platform from "react-native-web/dist/exports/Platform";
|
|
4
|
+
import StyleSheet from "react-native-web/dist/exports/StyleSheet";
|
|
5
|
+
import { PropTypes } from 'prop-types';
|
|
6
|
+
import { useCarousel } from '../CarouselContext';
|
|
7
|
+
/**
|
|
8
|
+
* Focus target so that when a new slide is shown, the user can tab into
|
|
9
|
+
* its content using the keyboard.
|
|
10
|
+
*
|
|
11
|
+
* @TODO rework this after integrating with SkipLink when available.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
15
|
+
const CarouselFirstFocus = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
16
|
+
let {
|
|
17
|
+
title
|
|
18
|
+
} = _ref;
|
|
19
|
+
const {
|
|
20
|
+
getCopyWithPlaceholders
|
|
21
|
+
} = useCarousel(); // TODO: integrate skip link description if behaving as skip link.
|
|
22
|
+
// Consider moving this content to aria-live area while only the skip link is focused.
|
|
23
|
+
|
|
24
|
+
const accessibilityLabel = "".concat(title, ", ").concat(getCopyWithPlaceholders('stepTrackerLabel'));
|
|
25
|
+
const accessibilityRole = Platform.select({
|
|
26
|
+
web: 'link',
|
|
27
|
+
// The focused item will ultimately be a skip link.
|
|
28
|
+
default: 'button' // 'link' role usually denotes opening browser on Native.
|
|
29
|
+
|
|
30
|
+
});
|
|
31
|
+
return /*#__PURE__*/_jsx(Pressable // TODO: integrate skip link functionality, jump focus to after Carousel
|
|
32
|
+
, {
|
|
33
|
+
onPress: undefined,
|
|
34
|
+
ref: ref,
|
|
35
|
+
accessibilityLabel: accessibilityLabel,
|
|
36
|
+
accessibilityRole: accessibilityRole,
|
|
37
|
+
style: StyleSheet.absoluteFill,
|
|
38
|
+
focusable: true
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
CarouselFirstFocus.displayName = 'CarouselFirstFocus';
|
|
42
|
+
CarouselFirstFocus.propTypes = {
|
|
43
|
+
/**
|
|
44
|
+
* Simple description of this carousel for screenreaders, to be read before
|
|
45
|
+
* "{itemLabel} {index} of {count}.
|
|
46
|
+
*
|
|
47
|
+
* For example, "Summer offers" in "Summer offers, offer 1 of 3"
|
|
48
|
+
*/
|
|
49
|
+
title: PropTypes.string
|
|
50
|
+
};
|
|
51
|
+
export default CarouselFirstFocus;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
var _CarouselTabs$propTyp, _CarouselTabs$propTyp2;
|
|
2
|
+
|
|
3
|
+
import React, { forwardRef } from 'react';
|
|
4
|
+
import PropTypes from 'prop-types';
|
|
5
|
+
import { useResponsiveProp } from '../../utils';
|
|
6
|
+
import Carousel from '../Carousel';
|
|
7
|
+
import CarouselTabsPanel from './CarouselTabsPanel';
|
|
8
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
9
|
+
const CarouselTabs = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
10
|
+
let {
|
|
11
|
+
items,
|
|
12
|
+
refocus = true,
|
|
13
|
+
...carouselProps
|
|
14
|
+
} = _ref;
|
|
15
|
+
const panelNavigation = useResponsiveProp({
|
|
16
|
+
md: /*#__PURE__*/_jsx(CarouselTabsPanel, {
|
|
17
|
+
items: items
|
|
18
|
+
})
|
|
19
|
+
});
|
|
20
|
+
return /*#__PURE__*/_jsx(Carousel, {
|
|
21
|
+
refocus: refocus,
|
|
22
|
+
...carouselProps,
|
|
23
|
+
ref: ref,
|
|
24
|
+
panelNavigation: panelNavigation,
|
|
25
|
+
children: items.map(_ref2 => {
|
|
26
|
+
let {
|
|
27
|
+
title,
|
|
28
|
+
content
|
|
29
|
+
} = _ref2;
|
|
30
|
+
return /*#__PURE__*/_jsx(React.Fragment, {
|
|
31
|
+
children: content
|
|
32
|
+
}, title);
|
|
33
|
+
})
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
CarouselTabs.displayName = 'CarouselTabs';
|
|
37
|
+
CarouselTabs.propTypes = {
|
|
38
|
+
/**
|
|
39
|
+
* An array of objects where title is the string shown in the slide's tab and content is the JSX for the slide itself
|
|
40
|
+
*/
|
|
41
|
+
items: PropTypes.arrayOf(PropTypes.shape({
|
|
42
|
+
title: PropTypes.string.isRequired,
|
|
43
|
+
content: PropTypes.node.isRequired
|
|
44
|
+
})),
|
|
45
|
+
...Carousel.propTypes
|
|
46
|
+
}; // CarouselTabs doesn't require `children` prop, it uses `items` instead.
|
|
47
|
+
// eslint-disable-next-line react/forbid-foreign-prop-types
|
|
48
|
+
|
|
49
|
+
if ((_CarouselTabs$propTyp = CarouselTabs.propTypes) !== null && _CarouselTabs$propTyp !== void 0 && _CarouselTabs$propTyp.children) (_CarouselTabs$propTyp2 = CarouselTabs.propTypes) === null || _CarouselTabs$propTyp2 === void 0 ? true : delete _CarouselTabs$propTyp2.children;
|
|
50
|
+
export default CarouselTabs;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import React, { forwardRef, useRef } from 'react';
|
|
2
|
+
import View from "react-native-web/dist/exports/View";
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import StackView from '../../StackView';
|
|
5
|
+
import { useCarousel } from '../CarouselContext';
|
|
6
|
+
import CarouselTabsPanelItem from './CarouselTabsPanelItem';
|
|
7
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
8
|
+
import { Fragment as _Fragment } from "react/jsx-runtime";
|
|
9
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
10
|
+
const CarouselTabsPanel = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
11
|
+
let {
|
|
12
|
+
items
|
|
13
|
+
} = _ref;
|
|
14
|
+
const {
|
|
15
|
+
activeIndex,
|
|
16
|
+
goTo
|
|
17
|
+
} = useCarousel();
|
|
18
|
+
const nextFocusRef = useRef();
|
|
19
|
+
const firstTabRef = useRef(); // TODO: figure out a better cross-brand way to specify subcomponent variants.
|
|
20
|
+
// For now, this picks an Allium variant, and does nothing in brands that lack it.
|
|
21
|
+
// See similar comment in Carousel and https://github.com/telus/universal-design-system/issues/1549
|
|
22
|
+
|
|
23
|
+
const dividerVariant = {
|
|
24
|
+
decorative: true
|
|
25
|
+
};
|
|
26
|
+
const lastTabSelected = activeIndex === items.length - 1;
|
|
27
|
+
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
28
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
29
|
+
focusable: true,
|
|
30
|
+
accessible: true,
|
|
31
|
+
onFocus: event => {
|
|
32
|
+
// When user forward-tabs into this section, focus the next tab; if they backwards-tab
|
|
33
|
+
// (shift-tab) back into the carousel content, don't interfere.
|
|
34
|
+
const previousWebFocus = event.relatedTarget;
|
|
35
|
+
if (previousWebFocus !== firstTabRef.current) nextFocusRef.current.focus();
|
|
36
|
+
}
|
|
37
|
+
}), /*#__PURE__*/_jsx(StackView, {
|
|
38
|
+
direction: "row",
|
|
39
|
+
space: 3,
|
|
40
|
+
divider: {
|
|
41
|
+
variant: dividerVariant
|
|
42
|
+
},
|
|
43
|
+
ref: ref,
|
|
44
|
+
children: items.map((_ref2, index) => {
|
|
45
|
+
let {
|
|
46
|
+
title,
|
|
47
|
+
onPress,
|
|
48
|
+
...panelItemProps
|
|
49
|
+
} = _ref2;
|
|
50
|
+
const selected = index === activeIndex;
|
|
51
|
+
const isNext = index === activeIndex + 1; // Selected item should be always unfocusable and unpressable
|
|
52
|
+
|
|
53
|
+
const handlePress = selected ? undefined : event => {
|
|
54
|
+
if (typeof onPress === 'function') onPress(event, index);
|
|
55
|
+
goTo(index);
|
|
56
|
+
};
|
|
57
|
+
return /*#__PURE__*/_jsx(CarouselTabsPanelItem, {
|
|
58
|
+
ref: isNext && nextFocusRef || index === 0 && firstTabRef || null,
|
|
59
|
+
title: title,
|
|
60
|
+
selected: selected,
|
|
61
|
+
onPress: handlePress,
|
|
62
|
+
...panelItemProps
|
|
63
|
+
}, title);
|
|
64
|
+
})
|
|
65
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
66
|
+
focusable: true,
|
|
67
|
+
accessible: true,
|
|
68
|
+
ref: lastTabSelected ? nextFocusRef : null
|
|
69
|
+
})]
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
CarouselTabsPanel.displayName = 'CarouselTabsPanel';
|
|
73
|
+
CarouselTabsPanel.propTypes = {
|
|
74
|
+
items: PropTypes.arrayOf(PropTypes.shape(CarouselTabsPanelItem.propTypes || {}))
|
|
75
|
+
};
|
|
76
|
+
export default CarouselTabsPanel;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import React, { forwardRef, useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import Pressable from "react-native-web/dist/exports/Pressable";
|
|
4
|
+
import Text from "react-native-web/dist/exports/Text";
|
|
5
|
+
import Platform from "react-native-web/dist/exports/Platform";
|
|
6
|
+
import { applyTextStyles, useThemeTokensCallback } from '../../ThemeProvider';
|
|
7
|
+
import { selectSystemProps, a11yProps, pressProps, viewProps, textProps, getTokensPropType, variantProp, resolvePressableState } from '../../utils';
|
|
8
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
9
|
+
const [selectPressProps, pressPropTypes] = selectSystemProps([a11yProps, pressProps, viewProps]);
|
|
10
|
+
const [selectTextProps, textPropTypes] = selectSystemProps([textProps]);
|
|
11
|
+
|
|
12
|
+
const selectContainerStyles = _ref => {
|
|
13
|
+
let {
|
|
14
|
+
paddingLeft,
|
|
15
|
+
paddingRight,
|
|
16
|
+
paddingTop,
|
|
17
|
+
paddingBottom = 0,
|
|
18
|
+
borderBottomColor,
|
|
19
|
+
borderBottomWidth = 0,
|
|
20
|
+
borderBottomStyle,
|
|
21
|
+
flex,
|
|
22
|
+
alignItems,
|
|
23
|
+
justifyContent
|
|
24
|
+
} = _ref;
|
|
25
|
+
return {
|
|
26
|
+
paddingLeft,
|
|
27
|
+
paddingRight,
|
|
28
|
+
paddingTop,
|
|
29
|
+
paddingBottom: paddingBottom - borderBottomWidth,
|
|
30
|
+
borderBottomColor,
|
|
31
|
+
borderBottomWidth,
|
|
32
|
+
borderBottomStyle,
|
|
33
|
+
flex,
|
|
34
|
+
alignItems,
|
|
35
|
+
justifyContent,
|
|
36
|
+
...Platform.select({
|
|
37
|
+
// Removes the default browser :focus outline
|
|
38
|
+
web: {
|
|
39
|
+
outline: 'none'
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const selectTextStyles = _ref2 => {
|
|
46
|
+
let {
|
|
47
|
+
fontSize,
|
|
48
|
+
fontScaleCap,
|
|
49
|
+
lineHeight,
|
|
50
|
+
letterSpacing,
|
|
51
|
+
fontWeight,
|
|
52
|
+
fontName,
|
|
53
|
+
color
|
|
54
|
+
} = _ref2;
|
|
55
|
+
return applyTextStyles({
|
|
56
|
+
fontSize,
|
|
57
|
+
fontScaleCap,
|
|
58
|
+
lineHeight,
|
|
59
|
+
letterSpacing,
|
|
60
|
+
fontWeight,
|
|
61
|
+
fontName,
|
|
62
|
+
color
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const CarouselTabsPanelItem = /*#__PURE__*/forwardRef((_ref3, ref) => {
|
|
67
|
+
let {
|
|
68
|
+
title,
|
|
69
|
+
selected,
|
|
70
|
+
inactive,
|
|
71
|
+
variant,
|
|
72
|
+
tokens,
|
|
73
|
+
accessibilityRole = 'tab',
|
|
74
|
+
...rest
|
|
75
|
+
} = _ref3;
|
|
76
|
+
// Workaround for React Native Web https://github.com/necolas/react-native-web/issues/2357
|
|
77
|
+
// Don't allow disabled to be set while focus is true else focus state gets locked `true`
|
|
78
|
+
// (must refocus _after_ calling `goTo`, else focus target content is not up to date)
|
|
79
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
80
|
+
const disabled = (inactive || selected) && !isFocused;
|
|
81
|
+
const getTokens = useThemeTokensCallback('CarouselTabsPanelItem', tokens, variant);
|
|
82
|
+
|
|
83
|
+
const resolveTokens = pressState => getTokens(resolvePressableState(pressState, {
|
|
84
|
+
selected
|
|
85
|
+
}));
|
|
86
|
+
|
|
87
|
+
const getContainerStyle = pressState => selectContainerStyles(resolveTokens(pressState));
|
|
88
|
+
|
|
89
|
+
const getTextStyle = pressState => selectTextStyles(resolveTokens(pressState));
|
|
90
|
+
|
|
91
|
+
const {
|
|
92
|
+
onPress,
|
|
93
|
+
...selectedPressProps
|
|
94
|
+
} = selectPressProps(rest);
|
|
95
|
+
|
|
96
|
+
const handleKeyDown = event => {
|
|
97
|
+
// Allow using the spacebar for navigation
|
|
98
|
+
if ((event === null || event === void 0 ? void 0 : event.key) === ' ') onPress(event);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return /*#__PURE__*/_jsx(Pressable, {
|
|
102
|
+
onPress: onPress,
|
|
103
|
+
style: getContainerStyle,
|
|
104
|
+
accessibilityRole: accessibilityRole,
|
|
105
|
+
ref: ref,
|
|
106
|
+
onKeyDown: handleKeyDown,
|
|
107
|
+
onFocus: () => setIsFocused(true),
|
|
108
|
+
onBlur: () => setIsFocused(false),
|
|
109
|
+
disabled: disabled,
|
|
110
|
+
...selectedPressProps,
|
|
111
|
+
children: pressState => /*#__PURE__*/_jsx(Text, {
|
|
112
|
+
style: getTextStyle(pressState),
|
|
113
|
+
...selectTextProps(rest),
|
|
114
|
+
children: title
|
|
115
|
+
})
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
CarouselTabsPanelItem.displayName = 'CarouselTabsPanelItem';
|
|
119
|
+
CarouselTabsPanelItem.propTypes = { ...pressPropTypes,
|
|
120
|
+
...textPropTypes,
|
|
121
|
+
title: PropTypes.string.isRequired,
|
|
122
|
+
selected: PropTypes.bool,
|
|
123
|
+
tokens: getTokensPropType('CarouselTabsPanelItem'),
|
|
124
|
+
variant: variantProp.propType
|
|
125
|
+
};
|
|
126
|
+
export default CarouselTabsPanelItem;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import Pressable from "react-native-web/dist/exports/Pressable";
|
|
4
|
+
import Image from "react-native-web/dist/exports/Image";
|
|
5
|
+
import { useCarousel } from './CarouselContext';
|
|
6
|
+
/**
|
|
7
|
+
* `Carousel.Thumbnail` is used to wrap the content of an individual slide and is suppsoed to be the
|
|
8
|
+
* only top-level component passed to the `Carousel`
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
12
|
+
|
|
13
|
+
const CarouselThumbnail = _ref => {
|
|
14
|
+
let {
|
|
15
|
+
accessibilityLabel,
|
|
16
|
+
alt,
|
|
17
|
+
index,
|
|
18
|
+
src
|
|
19
|
+
} = _ref;
|
|
20
|
+
const {
|
|
21
|
+
activeIndex,
|
|
22
|
+
itemLabel,
|
|
23
|
+
totalItems,
|
|
24
|
+
getCopyWithPlaceholders,
|
|
25
|
+
goTo,
|
|
26
|
+
themeTokens
|
|
27
|
+
} = useCarousel();
|
|
28
|
+
const thumbnailTitle = alt !== null && alt !== void 0 ? alt : getCopyWithPlaceholders('stepTrackerLabel').replace(/%\{itemLabel\}/g, itemLabel).replace(/%\{stepNumber\}/g, index).replace(/%\{stepCount\}/g, totalItems);
|
|
29
|
+
|
|
30
|
+
const handlePress = () => goTo(index);
|
|
31
|
+
|
|
32
|
+
const handleKeyDown = event => {
|
|
33
|
+
// Allow using the spacebar for navigation
|
|
34
|
+
if ((event === null || event === void 0 ? void 0 : event.key) === ' ') goTo(index);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
thumbnailBorderColor,
|
|
39
|
+
thumbnailBorderRadius,
|
|
40
|
+
thumbnailBorderWidth,
|
|
41
|
+
thumbnailMargin,
|
|
42
|
+
thumbnailPadding,
|
|
43
|
+
thumbnailSelectedBorderColor,
|
|
44
|
+
thumbnailSelectedBorderWidth,
|
|
45
|
+
thumbnailSize
|
|
46
|
+
} = themeTokens;
|
|
47
|
+
const styles = {
|
|
48
|
+
pressable: {
|
|
49
|
+
borderColor: thumbnailBorderColor,
|
|
50
|
+
borderRadius: thumbnailBorderRadius,
|
|
51
|
+
borderWidth: thumbnailBorderWidth,
|
|
52
|
+
margin: thumbnailMargin,
|
|
53
|
+
padding: thumbnailPadding
|
|
54
|
+
},
|
|
55
|
+
image: {
|
|
56
|
+
height: thumbnailSize,
|
|
57
|
+
width: thumbnailSize
|
|
58
|
+
},
|
|
59
|
+
selected: {
|
|
60
|
+
borderColor: thumbnailSelectedBorderColor,
|
|
61
|
+
borderWidth: thumbnailSelectedBorderWidth,
|
|
62
|
+
padding: thumbnailPadding - thumbnailSelectedBorderWidth + thumbnailBorderWidth
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return /*#__PURE__*/_jsx(Pressable, {
|
|
66
|
+
onKeyDown: handleKeyDown,
|
|
67
|
+
onPress: handlePress,
|
|
68
|
+
style: [styles.pressable, index === activeIndex && styles.selected],
|
|
69
|
+
children: /*#__PURE__*/_jsx(Image, {
|
|
70
|
+
accessibilityIgnoresInvertColors: true,
|
|
71
|
+
accessibilityLabel: accessibilityLabel !== null && accessibilityLabel !== void 0 ? accessibilityLabel : alt,
|
|
72
|
+
source: src,
|
|
73
|
+
style: styles.image,
|
|
74
|
+
title: thumbnailTitle
|
|
75
|
+
})
|
|
76
|
+
}, src);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
CarouselThumbnail.propTypes = {
|
|
80
|
+
accessibilityLabel: PropTypes.string,
|
|
81
|
+
alt: PropTypes.string,
|
|
82
|
+
index: PropTypes.number,
|
|
83
|
+
src: PropTypes.string
|
|
84
|
+
};
|
|
85
|
+
export default CarouselThumbnail;
|