@telus-uds/components-base 1.12.0 → 1.14.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 +41 -2
- package/component-docs.json +933 -55
- package/lib/BaseProvider/index.js +7 -2
- package/lib/Button/ButtonBase.js +52 -19
- package/lib/Button/ButtonGroup.js +7 -0
- package/lib/Button/propTypes.js +18 -0
- package/lib/Carousel/Carousel.js +83 -58
- package/lib/Carousel/CarouselContext.js +22 -8
- package/lib/Carousel/CarouselFirstFocus/CarouselFirstFocus.js +73 -0
- package/lib/Carousel/CarouselStepTracker/CarouselStepTracker.js +56 -0
- package/lib/Carousel/CarouselStepTracker/index.js +13 -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/Checkbox.js +7 -3
- package/lib/Checkbox/CheckboxGroup.js +8 -1
- package/lib/Feedback/Feedback.js +18 -10
- package/lib/Icon/IconText.js +6 -1
- package/lib/InputLabel/InputLabel.js +11 -5
- package/lib/Link/InlinePressable.js +1 -8
- package/lib/Link/LinkBase.js +13 -10
- package/lib/List/ListItem.js +8 -4
- package/lib/Notification/Notification.js +44 -24
- package/lib/Pagination/Pagination.js +7 -3
- package/lib/Radio/RadioGroup.js +8 -0
- package/lib/RadioCard/RadioCard.js +6 -1
- package/lib/RadioCard/RadioCardGroup.js +7 -0
- package/lib/Select/Select.js +7 -3
- package/lib/SkipLink/SkipLink.js +216 -0
- package/lib/SkipLink/index.js +13 -0
- package/lib/StepTracker/Step.js +8 -4
- package/lib/StepTracker/StepTracker.js +7 -3
- package/lib/Tabs/TabsItem.js +4 -0
- package/lib/TextInput/TextInputBase.js +7 -3
- package/lib/ThemeProvider/ThemeProvider.js +25 -3
- package/lib/ThemeProvider/utils/styles.js +8 -1
- package/lib/ThemeProvider/utils/theme-tokens.js +1 -1
- package/lib/ToggleSwitch/ToggleSwitchGroup.js +7 -0
- package/lib/Typography/Typography.js +6 -2
- package/lib/index.js +9 -0
- package/lib-module/BaseProvider/index.js +7 -2
- package/lib-module/Button/ButtonBase.js +41 -9
- package/lib-module/Button/ButtonGroup.js +7 -0
- package/lib-module/Button/propTypes.js +17 -0
- package/lib-module/Carousel/Carousel.js +80 -57
- package/lib-module/Carousel/CarouselContext.js +21 -8
- package/lib-module/Carousel/CarouselFirstFocus/CarouselFirstFocus.js +51 -0
- package/lib-module/Carousel/CarouselStepTracker/CarouselStepTracker.js +42 -0
- package/lib-module/Carousel/CarouselStepTracker/index.js +2 -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/Checkbox.js +8 -4
- package/lib-module/Checkbox/CheckboxGroup.js +8 -1
- package/lib-module/Feedback/Feedback.js +19 -11
- package/lib-module/Icon/IconText.js +6 -1
- package/lib-module/InputLabel/InputLabel.js +12 -6
- package/lib-module/Link/InlinePressable.js +1 -8
- package/lib-module/Link/LinkBase.js +14 -11
- package/lib-module/List/ListItem.js +9 -5
- package/lib-module/Notification/Notification.js +46 -26
- package/lib-module/Pagination/Pagination.js +8 -4
- package/lib-module/Radio/RadioGroup.js +8 -0
- package/lib-module/RadioCard/RadioCard.js +7 -2
- package/lib-module/RadioCard/RadioCardGroup.js +7 -0
- package/lib-module/Select/Select.js +8 -4
- package/lib-module/SkipLink/SkipLink.js +188 -0
- package/lib-module/SkipLink/index.js +2 -0
- package/lib-module/StepTracker/Step.js +9 -5
- package/lib-module/StepTracker/StepTracker.js +8 -4
- package/lib-module/Tabs/TabsItem.js +5 -1
- package/lib-module/TextInput/TextInputBase.js +8 -4
- package/lib-module/ThemeProvider/ThemeProvider.js +24 -3
- package/lib-module/ThemeProvider/utils/styles.js +8 -1
- package/lib-module/ThemeProvider/utils/theme-tokens.js +1 -1
- package/lib-module/ToggleSwitch/ToggleSwitchGroup.js +7 -0
- package/lib-module/Typography/Typography.js +7 -3
- package/lib-module/index.js +1 -0
- package/package.json +46 -47
- package/src/BaseProvider/index.jsx +6 -3
- package/src/Button/ButtonBase.jsx +36 -12
- package/src/Button/ButtonGroup.jsx +6 -0
- package/src/Button/propTypes.js +14 -0
- package/src/Carousel/Carousel.jsx +91 -64
- package/src/Carousel/CarouselContext.jsx +29 -5
- package/src/Carousel/CarouselFirstFocus/CarouselFirstFocus.jsx +49 -0
- package/src/Carousel/CarouselStepTracker/CarouselStepTracker.jsx +36 -0
- package/src/Carousel/CarouselStepTracker/index.js +3 -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/Checkbox.jsx +14 -11
- package/src/Checkbox/CheckboxGroup.jsx +8 -1
- package/src/Feedback/Feedback.jsx +14 -7
- package/src/Icon/IconText.jsx +3 -1
- package/src/InputLabel/InputLabel.jsx +13 -12
- package/src/Link/InlinePressable.jsx +2 -8
- package/src/Link/LinkBase.jsx +18 -21
- package/src/List/ListItem.jsx +10 -5
- package/src/Notification/Notification.jsx +40 -23
- package/src/Pagination/Pagination.jsx +6 -4
- package/src/Radio/RadioGroup.jsx +7 -0
- package/src/RadioCard/RadioCard.jsx +3 -2
- package/src/RadioCard/RadioCardGroup.jsx +6 -0
- package/src/Select/Select.jsx +12 -3
- package/src/SkipLink/SkipLink.jsx +179 -0
- package/src/SkipLink/index.js +3 -0
- package/src/StepTracker/Step.jsx +12 -4
- package/src/StepTracker/StepTracker.jsx +11 -10
- package/src/Tabs/TabsItem.jsx +3 -2
- package/src/TextInput/TextInputBase.jsx +11 -3
- package/src/ThemeProvider/ThemeProvider.jsx +22 -3
- package/src/ThemeProvider/utils/styles.js +9 -1
- package/src/ThemeProvider/utils/theme-tokens.js +1 -1
- package/src/ToggleSwitch/ToggleSwitchGroup.jsx +6 -0
- package/src/Typography/Typography.jsx +11 -12
- package/src/index.js +1 -0
|
@@ -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
|
*/
|
|
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
|
|
2
2
|
import ABBPropTypes from 'airbnb-prop-types';
|
|
3
3
|
import { variantProp, getTokensPropType } from '../utils/props';
|
|
4
4
|
import A11yText from '../A11yText';
|
|
5
|
+
import { iconComponentPropTypes } from '../Icon';
|
|
5
6
|
export const textAndA11yText = ABBPropTypes.childrenOf(PropTypes.oneOfType([ABBPropTypes.elementType(A11yText), PropTypes.string]));
|
|
6
7
|
const buttonPropTypes = {
|
|
7
8
|
tokens: getTokensPropType('Button'),
|
|
@@ -31,6 +32,22 @@ const buttonPropTypes = {
|
|
|
31
32
|
* Function called when the button is pressed. Required unless the button has a href.
|
|
32
33
|
*/
|
|
33
34
|
onPress: PropTypes.func,
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Optional variant that may be passed down to the link's icon if there is one
|
|
38
|
+
*/
|
|
39
|
+
iconProps: PropTypes.exact(iconComponentPropTypes),
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* When `icon` is provided, use `iconPosition` to place the Icon to the left or right side of the button.
|
|
43
|
+
*/
|
|
44
|
+
iconPosition: PropTypes.oneOf(['left', 'right']),
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A function component for an SVG icon to render inside the link. Inherits size and color from
|
|
48
|
+
* the link and any Typography the link is nested inside.
|
|
49
|
+
*/
|
|
50
|
+
icon: PropTypes.func,
|
|
34
51
|
variant: variantProp.propType
|
|
35
52
|
};
|
|
36
53
|
export default buttonPropTypes;
|
|
@@ -7,13 +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
|
-
import StepTracker from '../StepTracker';
|
|
15
|
-
import StackView from '../StackView';
|
|
16
14
|
import IconButton from '../IconButton';
|
|
15
|
+
import SkipLink from '../SkipLink';
|
|
16
|
+
import A11yText from '../A11yText';
|
|
17
|
+
import CarouselStepTracker from './CarouselStepTracker';
|
|
18
|
+
import CarouselThumbnailNavigation from './CarouselThumbnailNavigation';
|
|
17
19
|
import dictionary from './dictionary';
|
|
18
20
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
19
21
|
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
@@ -27,18 +29,6 @@ const staticStyles = StyleSheet.create({
|
|
|
27
29
|
left: 0
|
|
28
30
|
}
|
|
29
31
|
});
|
|
30
|
-
const staticTokens = {
|
|
31
|
-
stackView: {
|
|
32
|
-
justifyContent: 'center'
|
|
33
|
-
},
|
|
34
|
-
stepTracker: {
|
|
35
|
-
showStepLabel: false,
|
|
36
|
-
showStepTrackerLabel: true,
|
|
37
|
-
knobCompletedBackgroundColor: 'none',
|
|
38
|
-
connectorCompletedColor: 'none',
|
|
39
|
-
connectorColor: 'none'
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
32
|
|
|
43
33
|
const selectContainerStyles = width => ({
|
|
44
34
|
backgroundColor: 'transparent',
|
|
@@ -147,25 +137,32 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
147
137
|
onAnimationStart,
|
|
148
138
|
onAnimationEnd,
|
|
149
139
|
onIndexChanged,
|
|
140
|
+
skipLinkHref,
|
|
141
|
+
refocus,
|
|
142
|
+
title = 'carousel',
|
|
150
143
|
springConfig = undefined,
|
|
151
|
-
|
|
144
|
+
thumbnails = undefined,
|
|
145
|
+
panelNavigation = thumbnails ? /*#__PURE__*/_jsx(CarouselThumbnailNavigation, {
|
|
146
|
+
thumbnails: thumbnails
|
|
147
|
+
}) : /*#__PURE__*/_jsx(CarouselStepTracker, {}),
|
|
152
148
|
tag = 'ul',
|
|
153
|
-
accessibilityRole
|
|
154
|
-
accessibilityLabel =
|
|
149
|
+
accessibilityRole,
|
|
150
|
+
accessibilityLabel = title,
|
|
151
|
+
accessibilityLiveRegion = 'polite',
|
|
155
152
|
copy,
|
|
156
153
|
...rest
|
|
157
154
|
} = _ref;
|
|
158
155
|
const viewport = useViewport();
|
|
156
|
+
const themeTokens = useThemeTokens('Carousel', tokens, variant, {
|
|
157
|
+
viewport
|
|
158
|
+
});
|
|
159
159
|
const {
|
|
160
160
|
previousIcon,
|
|
161
161
|
nextIcon,
|
|
162
162
|
showPreviousNextNavigation,
|
|
163
163
|
showPanelNavigation,
|
|
164
|
-
spaceBetweenSlideAndPreviousNextNavigation
|
|
165
|
-
|
|
166
|
-
} = useThemeTokens('Carousel', tokens, variant, {
|
|
167
|
-
viewport
|
|
168
|
-
});
|
|
164
|
+
spaceBetweenSlideAndPreviousNextNavigation
|
|
165
|
+
} = themeTokens;
|
|
169
166
|
const [activeIndex, setActiveIndex] = React.useState(0);
|
|
170
167
|
const [isAnimating, setIsAnimating] = React.useState(false);
|
|
171
168
|
const handleAnimationStart = React.useCallback(function () {
|
|
@@ -180,7 +177,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
180
177
|
dictionary,
|
|
181
178
|
copy
|
|
182
179
|
});
|
|
183
|
-
const childrenArray =
|
|
180
|
+
const childrenArray = unpackFragment(children);
|
|
184
181
|
const systemProps = selectProps({ ...rest,
|
|
185
182
|
accessibilityRole,
|
|
186
183
|
accessibilityLabel,
|
|
@@ -199,14 +196,12 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
199
196
|
width: 0
|
|
200
197
|
});
|
|
201
198
|
const [previousNextNavigationButtonWidth, setPreviousNextNavigationButtonWidth] = React.useState(0);
|
|
199
|
+
const firstFocusRef = React.useRef(null);
|
|
202
200
|
const pan = React.useRef(new Animated.ValueXY()).current;
|
|
203
201
|
const animatedX = React.useRef(0);
|
|
204
202
|
const animatedY = React.useRef(0);
|
|
205
203
|
const isFirstSlide = !activeIndex;
|
|
206
204
|
const isLastSlide = activeIndex + 1 >= children.length;
|
|
207
|
-
const panelNavigationTokens = { ...staticTokens.stepTracker,
|
|
208
|
-
containerPaddingTop: spaceBetweenSlideAndPanelNavigation
|
|
209
|
-
};
|
|
210
205
|
|
|
211
206
|
const onContainerLayout = _ref2 => {
|
|
212
207
|
let {
|
|
@@ -301,10 +296,13 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
301
296
|
return calcDelta;
|
|
302
297
|
}, [containerLayout.width, activeIndex, animate, children.length, onIndexChanged]);
|
|
303
298
|
const fixOffsetAndGo = React.useCallback(delta => {
|
|
299
|
+
var _firstFocusRef$curren;
|
|
300
|
+
|
|
304
301
|
updateOffset();
|
|
305
302
|
handleAnimationStart(activeIndex);
|
|
306
303
|
updateIndex(delta);
|
|
307
|
-
|
|
304
|
+
if (refocus) (_firstFocusRef$curren = firstFocusRef.current) === null || _firstFocusRef$curren === void 0 ? void 0 : _firstFocusRef$curren.focus();
|
|
305
|
+
}, [updateIndex, updateOffset, activeIndex, handleAnimationStart, refocus]);
|
|
308
306
|
const goToNeighboring = React.useCallback(function () {
|
|
309
307
|
let toPrev = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
310
308
|
fixOffsetAndGo(toPrev ? -1 : 1);
|
|
@@ -386,18 +384,21 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
386
384
|
size: previousNextIconSize,
|
|
387
385
|
raised: true
|
|
388
386
|
};
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
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
|
|
387
|
+
const getCopyWithPlaceholders = React.useCallback(copyKey => {
|
|
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
|
|
392
389
|
|
|
393
390
|
return "".concat(copyText[0].toUpperCase()).concat(copyText.slice(1));
|
|
394
|
-
};
|
|
395
|
-
|
|
391
|
+
}, [activeIndex, childrenArray.length, itemLabel, getCopy, title]);
|
|
396
392
|
return /*#__PURE__*/_jsxs(CarouselProvider, {
|
|
397
393
|
activeIndex: activeIndex,
|
|
394
|
+
goTo: goTo,
|
|
395
|
+
getCopyWithPlaceholders: getCopyWithPlaceholders,
|
|
396
|
+
itemLabel: itemLabel,
|
|
398
397
|
totalItems: childrenArray.length,
|
|
398
|
+
themeTokens: themeTokens,
|
|
399
|
+
firstFocusRef: firstFocusRef,
|
|
400
|
+
refocus: refocus,
|
|
399
401
|
width: containerLayout.width,
|
|
400
|
-
goTo: goTo,
|
|
401
402
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
402
403
|
style: staticStyles.root,
|
|
403
404
|
onLayout: onContainerLayout,
|
|
@@ -413,6 +414,17 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
413
414
|
variant: previousNextIconButtonVariants,
|
|
414
415
|
accessibilityLabel: getCopyWithPlaceholders('iconButtonLabel').replace('%{targetStep}', activeIndex)
|
|
415
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')
|
|
416
428
|
}), /*#__PURE__*/_jsx(View, {
|
|
417
429
|
style: selectContainerStyles(containerLayout.width),
|
|
418
430
|
children: /*#__PURE__*/_jsx(Animated.View, {
|
|
@@ -425,6 +437,9 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
425
437
|
}]),
|
|
426
438
|
...panResponder.panHandlers,
|
|
427
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,
|
|
428
443
|
children: childrenArray.map((element, index) => {
|
|
429
444
|
const hidden = !isAnimating && index !== activeIndex;
|
|
430
445
|
const clonedElement = /*#__PURE__*/React.cloneElement(element, {
|
|
@@ -447,23 +462,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
447
462
|
accessibilityLabel: getCopyWithPlaceholders('iconButtonLabel').replace('%{targetStep}', activeIndex + 2)
|
|
448
463
|
})
|
|
449
464
|
})]
|
|
450
|
-
}), showPanelNavigation ?
|
|
451
|
-
direction: "row",
|
|
452
|
-
tokens: staticTokens.stackView,
|
|
453
|
-
children: onRenderPanelNavigation ? onRenderPanelNavigation({
|
|
454
|
-
activeIndex,
|
|
455
|
-
totalItems: childrenArray.length
|
|
456
|
-
}) : /*#__PURE__*/_jsx(StepTracker, {
|
|
457
|
-
current: activeIndex,
|
|
458
|
-
steps: childrenArray.map((_, index) => String(index)),
|
|
459
|
-
copy: {
|
|
460
|
-
// Give StepTracker copy from Carousel's language and dictionary
|
|
461
|
-
stepLabel: getCopyWithPlaceholders('stepLabel'),
|
|
462
|
-
stepTrackerLabel: getCopyWithPlaceholders('stepTrackerLabel')
|
|
463
|
-
},
|
|
464
|
-
tokens: panelNavigationTokens
|
|
465
|
-
})
|
|
466
|
-
}) : null]
|
|
465
|
+
}), showPanelNavigation ? panelNavigation : null]
|
|
467
466
|
});
|
|
468
467
|
});
|
|
469
468
|
Carousel.propTypes = { ...selectedSystemPropTypes,
|
|
@@ -500,6 +499,15 @@ Carousel.propTypes = { ...selectedSystemPropTypes,
|
|
|
500
499
|
*/
|
|
501
500
|
springConfig: PropTypes.object,
|
|
502
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
|
+
|
|
503
511
|
/**
|
|
504
512
|
* Minimal part of slide width must be swiped for changing index.
|
|
505
513
|
* Otherwise animation restore current slide. Default value 0.2 means that 20% must be swiped for change index
|
|
@@ -530,20 +538,35 @@ Carousel.propTypes = { ...selectedSystemPropTypes,
|
|
|
530
538
|
onIndexChanged: PropTypes.func,
|
|
531
539
|
|
|
532
540
|
/**
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
|
|
536
|
-
|
|
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
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Use this to render a custom panel navigation element instead of the default StepTracker's based navigation
|
|
557
|
+
* You can make use of `useCarousel` within your custom panel navigation component to hook into various Carousel states such as:
|
|
558
|
+
* - activeIndex: index of current slide
|
|
559
|
+
* - totalItems: total number of slides
|
|
537
560
|
* Use it as follows:
|
|
538
561
|
* ```js
|
|
539
562
|
* <Carousel
|
|
540
|
-
*
|
|
563
|
+
* panelNavigation={<CustomPanelNavigation />}
|
|
541
564
|
* >
|
|
542
565
|
* <Carousel.Item>First Slide</Carousel.Item>
|
|
543
566
|
* </Carousel>
|
|
544
567
|
* ```
|
|
545
568
|
*/
|
|
546
|
-
|
|
569
|
+
panelNavigation: PropTypes.element,
|
|
547
570
|
|
|
548
571
|
/**
|
|
549
572
|
* When slide animation start
|
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
+
import { getTokensPropType } from '../utils';
|
|
3
4
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
4
5
|
const CarouselContext = /*#__PURE__*/React.createContext();
|
|
5
6
|
|
|
6
7
|
const CarouselProvider = _ref => {
|
|
7
8
|
let {
|
|
8
|
-
children,
|
|
9
9
|
activeIndex,
|
|
10
|
+
children,
|
|
11
|
+
goTo,
|
|
12
|
+
getCopyWithPlaceholders,
|
|
13
|
+
itemLabel,
|
|
14
|
+
refocus = false,
|
|
15
|
+
themeTokens,
|
|
10
16
|
totalItems,
|
|
11
|
-
width
|
|
12
|
-
goTo
|
|
17
|
+
width
|
|
13
18
|
} = _ref;
|
|
14
19
|
const value = React.useMemo(() => ({
|
|
15
20
|
activeIndex,
|
|
21
|
+
goTo,
|
|
22
|
+
getCopyWithPlaceholders,
|
|
23
|
+
itemLabel,
|
|
24
|
+
refocus,
|
|
25
|
+
themeTokens,
|
|
16
26
|
totalItems,
|
|
17
|
-
width
|
|
18
|
-
|
|
19
|
-
}), [activeIndex, totalItems, width, goTo]);
|
|
27
|
+
width
|
|
28
|
+
}), [activeIndex, goTo, getCopyWithPlaceholders, itemLabel, refocus, totalItems, themeTokens, width]);
|
|
20
29
|
return /*#__PURE__*/_jsx(CarouselContext.Provider, {
|
|
21
30
|
value: value,
|
|
22
31
|
children: children
|
|
@@ -36,8 +45,12 @@ function useCarousel() {
|
|
|
36
45
|
CarouselProvider.propTypes = {
|
|
37
46
|
children: PropTypes.arrayOf(PropTypes.element).isRequired,
|
|
38
47
|
activeIndex: PropTypes.number.isRequired,
|
|
48
|
+
goTo: PropTypes.func.isRequired,
|
|
49
|
+
getCopyWithPlaceholders: PropTypes.func.isRequired,
|
|
50
|
+
itemLabel: PropTypes.string.isRequired,
|
|
51
|
+
refocus: PropTypes.bool,
|
|
52
|
+
themeTokens: getTokensPropType('Carousel'),
|
|
39
53
|
totalItems: PropTypes.number.isRequired,
|
|
40
|
-
width: PropTypes.number.isRequired
|
|
41
|
-
goTo: PropTypes.func.isRequired
|
|
54
|
+
width: PropTypes.number.isRequired
|
|
42
55
|
};
|
|
43
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,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useCarousel } from '../CarouselContext';
|
|
3
|
+
import StepTracker from '../../StepTracker';
|
|
4
|
+
import StackView from '../../StackView';
|
|
5
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
|
+
|
|
7
|
+
const CarouselStepTracker = () => {
|
|
8
|
+
const {
|
|
9
|
+
activeIndex,
|
|
10
|
+
totalItems,
|
|
11
|
+
getCopyWithPlaceholders,
|
|
12
|
+
themeTokens
|
|
13
|
+
} = useCarousel();
|
|
14
|
+
const stackViewTokens = {
|
|
15
|
+
justifyContent: 'center'
|
|
16
|
+
};
|
|
17
|
+
const stepTrackerTokens = {
|
|
18
|
+
showStepLabel: false,
|
|
19
|
+
showStepTrackerLabel: true,
|
|
20
|
+
knobCompletedBackgroundColor: 'none',
|
|
21
|
+
connectorCompletedColor: 'none',
|
|
22
|
+
connectorColor: 'none',
|
|
23
|
+
containerPaddingTop: themeTokens.spaceBetweenSlideAndPanelNavigation
|
|
24
|
+
};
|
|
25
|
+
const steps = Array.from(Array(totalItems)).map((_, index) => String(index));
|
|
26
|
+
return /*#__PURE__*/_jsx(StackView, {
|
|
27
|
+
direction: "row",
|
|
28
|
+
tokens: stackViewTokens,
|
|
29
|
+
children: /*#__PURE__*/_jsx(StepTracker, {
|
|
30
|
+
current: activeIndex,
|
|
31
|
+
steps: steps,
|
|
32
|
+
copy: {
|
|
33
|
+
// Give StepTracker copy from Carousel's language and dictionary
|
|
34
|
+
stepLabel: getCopyWithPlaceholders('stepLabel'),
|
|
35
|
+
stepTrackerLabel: getCopyWithPlaceholders('stepTrackerLabel')
|
|
36
|
+
},
|
|
37
|
+
tokens: stepTrackerTokens
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default CarouselStepTracker;
|
|
@@ -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;
|