@telus-uds/components-base 3.29.0 → 3.30.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 +26 -1
- package/jest.config.cjs +1 -0
- package/lib/cjs/ActionCard/ActionCard.js +4 -4
- package/lib/cjs/ActivityIndicator/Dots.native.js +1 -2
- package/lib/cjs/ActivityIndicator/Spinner.native.js +1 -2
- package/lib/cjs/Box/Box.js +1 -2
- package/lib/cjs/Button/Button.js +1 -2
- package/lib/cjs/Button/ButtonBase.js +8 -8
- package/lib/cjs/Button/ButtonDropdown.js +1 -2
- package/lib/cjs/Button/ButtonGroup.js +1 -2
- package/lib/cjs/Button/ButtonLink.js +1 -2
- package/lib/cjs/Card/Card.js +1 -2
- package/lib/cjs/Card/PressableCardBase.js +1 -2
- package/lib/cjs/Card/index.js +1 -2
- package/lib/cjs/CardGroup/CardGroup.js +1 -2
- package/lib/cjs/Carousel/Carousel.js +12 -7
- package/lib/cjs/Carousel/CarouselThumbnail.js +123 -31
- package/lib/cjs/Carousel/CarouselThumbnailNavigation.js +8 -1
- package/lib/cjs/CheckboxCard/CheckboxCard.js +1 -2
- package/lib/cjs/CheckboxCardGroup/CheckboxCardGroup.js +1 -2
- package/lib/cjs/Footnote/FootnoteLink.js +18 -17
- package/lib/cjs/HorizontalScroll/index.js +1 -2
- package/lib/cjs/Icon/IconText.js +1 -2
- package/lib/cjs/Icon/index.js +1 -2
- package/lib/cjs/InputSupports/InputSupports.js +1 -2
- package/lib/cjs/InputSupports/useInputSupports.js +1 -3
- package/lib/cjs/Link/MobileIconTextContent.js +1 -2
- package/lib/cjs/Listbox/Listbox.js +1 -2
- package/lib/cjs/Modal/Modal.js +1 -1
- package/lib/cjs/Pagination/PageButton.js +1 -2
- package/lib/cjs/Pagination/Pagination.js +1 -2
- package/lib/cjs/Progress/Progress.js +30 -5
- package/lib/cjs/QuickLinks/QuickLinks.js +7 -0
- package/lib/cjs/Radio/Radio.js +1 -2
- package/lib/cjs/RadioCard/RadioCard.js +1 -2
- package/lib/cjs/RadioCard/RadioCardGroup.js +1 -2
- package/lib/cjs/Scroll/Scroll.js +466 -0
- package/lib/cjs/Scroll/ScrollContext.js +55 -0
- package/lib/cjs/Scroll/ScrollItem.js +103 -0
- package/lib/cjs/Scroll/ScrollProgress.js +99 -0
- package/lib/cjs/Scroll/dictionary.js +18 -0
- package/lib/cjs/Scroll/index.js +29 -0
- package/lib/cjs/Shortcuts/Shortcuts.js +1 -2
- package/lib/cjs/SideNav/SideNav.js +1 -2
- package/lib/cjs/StackView/StackWrapBox.js +9 -1
- package/lib/cjs/StackView/StackWrapGap.js +3 -1
- package/lib/cjs/StackView/getStackedContent.js +21 -12
- package/lib/cjs/Tabs/Tabs.js +1 -2
- package/lib/cjs/Tooltip/Tooltip.native.js +2 -2
- package/lib/cjs/index.js +16 -2
- package/lib/cjs/utils/animation/index.js +7 -0
- package/lib/cjs/utils/animation/useAnimatedValue.js +46 -0
- package/lib/cjs/utils/children.js +2 -1
- package/lib/cjs/utils/index.js +1 -2
- package/lib/esm/ActionCard/ActionCard.js +4 -4
- package/lib/esm/Button/ButtonBase.js +8 -8
- package/lib/esm/Carousel/Carousel.js +12 -7
- package/lib/esm/Carousel/CarouselThumbnail.js +124 -32
- package/lib/esm/Carousel/CarouselThumbnailNavigation.js +8 -1
- package/lib/esm/Footnote/FootnoteLink.js +18 -17
- package/lib/esm/InputSupports/InputSupports.js +1 -2
- package/lib/esm/InputSupports/useInputSupports.js +1 -3
- package/lib/esm/Modal/Modal.js +1 -1
- package/lib/esm/Progress/Progress.js +30 -5
- package/lib/esm/QuickLinks/QuickLinks.js +7 -0
- package/lib/esm/Scroll/Scroll.js +459 -0
- package/lib/esm/Scroll/ScrollContext.js +47 -0
- package/lib/esm/Scroll/ScrollItem.js +96 -0
- package/lib/esm/Scroll/ScrollProgress.js +92 -0
- package/lib/esm/Scroll/dictionary.js +12 -0
- package/lib/esm/Scroll/index.js +4 -0
- package/lib/esm/StackView/StackWrapBox.js +9 -1
- package/lib/esm/StackView/StackWrapGap.js +3 -1
- package/lib/esm/StackView/getStackedContent.js +20 -10
- package/lib/esm/Tooltip/Tooltip.native.js +2 -2
- package/lib/esm/index.js +1 -0
- package/lib/esm/utils/animation/index.js +1 -1
- package/lib/esm/utils/animation/useAnimatedValue.js +39 -0
- package/lib/esm/utils/children.js +2 -1
- package/lib/package.json +2 -2
- package/package.json +2 -2
- package/src/Carousel/Carousel.jsx +6 -1
- package/src/Carousel/CarouselThumbnail.jsx +153 -64
- package/src/Carousel/CarouselThumbnailNavigation.jsx +8 -2
- package/src/Footnote/FootnoteLink.jsx +17 -12
- package/src/InputSupports/InputSupports.jsx +1 -6
- package/src/InputSupports/useInputSupports.js +1 -1
- package/src/Modal/Modal.jsx +1 -1
- package/src/Progress/Progress.jsx +22 -3
- package/src/QuickLinks/QuickLinks.jsx +8 -0
- package/src/Scroll/Scroll.jsx +488 -0
- package/src/Scroll/ScrollContext.jsx +41 -0
- package/src/Scroll/ScrollItem.jsx +89 -0
- package/src/Scroll/ScrollProgress.jsx +75 -0
- package/src/Scroll/dictionary.js +12 -0
- package/src/Scroll/index.js +5 -0
- package/src/StackView/StackWrapBox.jsx +13 -1
- package/src/StackView/StackWrapGap.jsx +2 -1
- package/src/StackView/getStackedContent.jsx +22 -8
- package/src/index.js +1 -0
- package/src/utils/animation/index.js +1 -1
- package/src/utils/animation/useAnimatedValue.js +37 -0
- package/src/utils/children.jsx +4 -1
- package/types/Scroll.d.ts +66 -0
- package/types/index.d.ts +9 -0
|
@@ -43,6 +43,7 @@ const StackWrapBox = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
43
43
|
variant,
|
|
44
44
|
tag,
|
|
45
45
|
accessibilityRole,
|
|
46
|
+
itemAccessibilityRole,
|
|
46
47
|
...rest
|
|
47
48
|
} = _ref;
|
|
48
49
|
const viewport = useViewport();
|
|
@@ -69,7 +70,8 @@ const StackWrapBox = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
69
70
|
const content = getStackedContent(children, {
|
|
70
71
|
direction,
|
|
71
72
|
space: 0,
|
|
72
|
-
box: boxProps
|
|
73
|
+
box: boxProps,
|
|
74
|
+
itemAccessibilityRole
|
|
73
75
|
});
|
|
74
76
|
return /*#__PURE__*/_jsx(View, {
|
|
75
77
|
ref: ref,
|
|
@@ -105,6 +107,12 @@ StackWrapBox.propTypes = {
|
|
|
105
107
|
* is not defined, the accessibilityRole will default to "heading".
|
|
106
108
|
*/
|
|
107
109
|
tag: PropTypes.oneOf(layoutTags),
|
|
110
|
+
/**
|
|
111
|
+
* Optional accessibility role to apply to each item in the stack.
|
|
112
|
+
* On web, items are wrapped (or cloned) with this role, enabling correct list semantics
|
|
113
|
+
* when the container tag is "ul" or "ol".
|
|
114
|
+
*/
|
|
115
|
+
itemAccessibilityRole: PropTypes.string,
|
|
108
116
|
/**
|
|
109
117
|
* A StackWrap may take any children, but will have no effect if it is only passed one child or is passed children
|
|
110
118
|
* wrapped in a component. If necessary, children may be wrapped in one React Fragment.
|
|
@@ -28,6 +28,7 @@ const StackWrapGap = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
28
28
|
children,
|
|
29
29
|
tag,
|
|
30
30
|
accessibilityRole,
|
|
31
|
+
itemAccessibilityRole,
|
|
31
32
|
...rest
|
|
32
33
|
} = _ref;
|
|
33
34
|
const viewport = useViewport();
|
|
@@ -47,7 +48,8 @@ const StackWrapGap = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
47
48
|
};
|
|
48
49
|
const content = getStackedContent(children, {
|
|
49
50
|
direction,
|
|
50
|
-
space: 0
|
|
51
|
+
space: 0,
|
|
52
|
+
itemAccessibilityRole
|
|
51
53
|
});
|
|
52
54
|
return /*#__PURE__*/_jsx(View, {
|
|
53
55
|
ref: ref,
|
|
@@ -36,7 +36,8 @@ const getStackedContent = (children, _ref) => {
|
|
|
36
36
|
space,
|
|
37
37
|
direction = 'column',
|
|
38
38
|
box,
|
|
39
|
-
preserveFragments = false
|
|
39
|
+
preserveFragments = false,
|
|
40
|
+
itemAccessibilityRole
|
|
40
41
|
} = _ref;
|
|
41
42
|
const boxProps = box && typeof box === 'object' ? box : {
|
|
42
43
|
space
|
|
@@ -48,15 +49,24 @@ const getStackedContent = (children, _ref) => {
|
|
|
48
49
|
const validChildren = React.Children.toArray(topLevelChildren).filter(Boolean);
|
|
49
50
|
const content = validChildren.reduce((newChildren, child, index) => {
|
|
50
51
|
const boxID = `Stack-Box-${index}`;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
let item;
|
|
53
|
+
if (box) {
|
|
54
|
+
// If wrapped in Box, that Box needs a key.
|
|
55
|
+
// If possible, use an existing content key; use an index-based key only if necessary.
|
|
56
|
+
item = /*#__PURE__*/_createElement(Box, {
|
|
57
|
+
...boxProps,
|
|
58
|
+
accessibilityRole: itemAccessibilityRole,
|
|
59
|
+
key: child.key || boxID,
|
|
60
|
+
testID: boxID
|
|
61
|
+
}, child);
|
|
62
|
+
} else if (itemAccessibilityRole) {
|
|
63
|
+
item = /*#__PURE__*/React.cloneElement(child, {
|
|
64
|
+
accessibilityRole: itemAccessibilityRole,
|
|
65
|
+
key: child.key || boxID
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
item = child;
|
|
69
|
+
}
|
|
60
70
|
if (!index || !space && !divider) return [...newChildren, item];
|
|
61
71
|
const testID = `Stack-${divider ? 'Divider' : 'Spacer'}-${index}`;
|
|
62
72
|
const commonProps = {
|
|
@@ -206,7 +206,7 @@ const Tooltip = /*#__PURE__*/React.forwardRef((_ref7, ref) => {
|
|
|
206
206
|
focus: focused
|
|
207
207
|
};
|
|
208
208
|
};
|
|
209
|
-
const onTooltipLayout =
|
|
209
|
+
const onTooltipLayout = _ref0 => {
|
|
210
210
|
let {
|
|
211
211
|
nativeEvent: {
|
|
212
212
|
layout: {
|
|
@@ -214,7 +214,7 @@ const Tooltip = /*#__PURE__*/React.forwardRef((_ref7, ref) => {
|
|
|
214
214
|
height
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
|
-
} =
|
|
217
|
+
} = _ref0;
|
|
218
218
|
if (tooltipDimensions === null || tooltipDimensions.width !== width || tooltipDimensions.height !== height) {
|
|
219
219
|
setTooltipDimensions({
|
|
220
220
|
width: Platform.select({
|
package/lib/esm/index.js
CHANGED
|
@@ -45,6 +45,7 @@ export { default as QuickLinksFeature } from './QuickLinksFeature';
|
|
|
45
45
|
export { default as Radio, RadioGroup } from './Radio';
|
|
46
46
|
export { default as RadioCard, RadioCardGroup } from './RadioCard';
|
|
47
47
|
export { default as Responsive } from './Responsive';
|
|
48
|
+
export { default as Scroll, ScrollItem } from './Scroll';
|
|
48
49
|
export { default as Search } from './Search';
|
|
49
50
|
export { default as Select } from './Select';
|
|
50
51
|
export { default as Shortcuts, ShortcutsItem } from './Shortcuts';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
export { default as useAnimatedValue } from './useAnimatedValue';
|
|
2
2
|
export { default as useVerticalExpandAnimation } from './useVerticalExpandAnimation';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Animated from "react-native-web/dist/exports/Animated";
|
|
3
|
+
import Easing from "react-native-web/dist/exports/Easing";
|
|
4
|
+
const ANIMATION_DURATION = 200;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Smoothly animates a numeric value to a new target using a quadratic ease-in-out
|
|
8
|
+
* curve. Returns the current interpolated value as a plain number so
|
|
9
|
+
* consumers can use it in non-Animated style props.
|
|
10
|
+
*
|
|
11
|
+
* @param {number} targetValue - The value to animate towards.
|
|
12
|
+
* @param {number} [duration=ANIMATION_DURATION] - Animation duration in ms.
|
|
13
|
+
*/
|
|
14
|
+
const useAnimatedValue = function (targetValue) {
|
|
15
|
+
let duration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ANIMATION_DURATION;
|
|
16
|
+
const [currentValue, setCurrentValue] = React.useState(targetValue);
|
|
17
|
+
const animatedValue = React.useRef(new Animated.Value(targetValue)).current;
|
|
18
|
+
React.useEffect(() => {
|
|
19
|
+
const id = animatedValue.addListener(_ref => {
|
|
20
|
+
let {
|
|
21
|
+
value
|
|
22
|
+
} = _ref;
|
|
23
|
+
return setCurrentValue(value);
|
|
24
|
+
});
|
|
25
|
+
return () => animatedValue.removeListener(id);
|
|
26
|
+
}, [animatedValue]);
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
const animation = Animated.timing(animatedValue, {
|
|
29
|
+
toValue: targetValue,
|
|
30
|
+
duration,
|
|
31
|
+
easing: Easing.inOut(Easing.quad),
|
|
32
|
+
useNativeDriver: false
|
|
33
|
+
});
|
|
34
|
+
animation.start();
|
|
35
|
+
return () => animation.stop();
|
|
36
|
+
}, [targetValue, animatedValue, duration]);
|
|
37
|
+
return currentValue;
|
|
38
|
+
};
|
|
39
|
+
export default useAnimatedValue;
|
|
@@ -47,7 +47,8 @@ export const unpackFragment = child => {
|
|
|
47
47
|
};
|
|
48
48
|
const isStringOrNumber = child => typeof child === 'string' || typeof child === 'number';
|
|
49
49
|
// Wrap an A11yText with neighouring text strings so it doesn't split them into multiple <Text>s
|
|
50
|
-
|
|
50
|
+
// Check displayName first as it is explicitly set and reliable regardless of export style (named vs default)
|
|
51
|
+
const isWrapable = child => isStringOrNumber(child) || child.type === A11yText || child.type?.__UDS_COMPONENT_NAME__ === 'FootnoteLink';
|
|
51
52
|
const combineKeys = childrenArray => childrenArray.reduce((newKey, child) => `${newKey}${child.key || ''}`, '');
|
|
52
53
|
|
|
53
54
|
// Group wrappable children for one `<Text>` parent, merging adjacent text nodes
|
package/lib/package.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"@gorhom/portal": "^1.0.14",
|
|
13
13
|
"@react-native-picker/picker": "^2.9.0",
|
|
14
14
|
"@telus-uds/system-constants": "^3.0.0",
|
|
15
|
-
"@telus-uds/system-theme-tokens": "^4.
|
|
15
|
+
"@telus-uds/system-theme-tokens": "^4.21.0",
|
|
16
16
|
"airbnb-prop-types": "^2.16.0",
|
|
17
17
|
"css-mediaquery": "^0.1.2",
|
|
18
18
|
"expo-document-picker": "^13.0.1",
|
|
@@ -84,6 +84,6 @@
|
|
|
84
84
|
"standard-engine": {
|
|
85
85
|
"skip": true
|
|
86
86
|
},
|
|
87
|
-
"version": "3.
|
|
87
|
+
"version": "3.30.0",
|
|
88
88
|
"types": "types/index.d.ts"
|
|
89
89
|
}
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"@gorhom/portal": "^1.0.14",
|
|
13
13
|
"@react-native-picker/picker": "^2.9.0",
|
|
14
14
|
"@telus-uds/system-constants": "^3.0.0",
|
|
15
|
-
"@telus-uds/system-theme-tokens": "^4.
|
|
15
|
+
"@telus-uds/system-theme-tokens": "^4.21.0",
|
|
16
16
|
"airbnb-prop-types": "^2.16.0",
|
|
17
17
|
"css-mediaquery": "^0.1.2",
|
|
18
18
|
"expo-document-picker": "^13.0.1",
|
|
@@ -84,6 +84,6 @@
|
|
|
84
84
|
"standard-engine": {
|
|
85
85
|
"skip": true
|
|
86
86
|
},
|
|
87
|
-
"version": "3.
|
|
87
|
+
"version": "3.30.0",
|
|
88
88
|
"types": "types/index.d.ts"
|
|
89
89
|
}
|
|
@@ -1403,9 +1403,14 @@ Carousel.propTypes = {
|
|
|
1403
1403
|
*/
|
|
1404
1404
|
thumbnails: PropTypes.arrayOf(
|
|
1405
1405
|
PropTypes.shape({
|
|
1406
|
+
/** Accessibility label for the thumbnail image, used by assistive technologies. */
|
|
1406
1407
|
accessibilityLabel: PropTypes.string,
|
|
1408
|
+
/** Alternative text for the thumbnail image, displayed when the image cannot be rendered. */
|
|
1407
1409
|
alt: PropTypes.string,
|
|
1408
|
-
|
|
1410
|
+
/** When true, renders a play icon overlay on the thumbnail to indicate that the slide contains a video. */
|
|
1411
|
+
isVideo: PropTypes.bool,
|
|
1412
|
+
/** URL or path of the thumbnail image. When used with `isVideo`, this should be the video's poster/preview image. */
|
|
1413
|
+
src: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
|
1409
1414
|
})
|
|
1410
1415
|
),
|
|
1411
1416
|
/**
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
|
-
import { Pressable, Image } from 'react-native'
|
|
3
|
+
import { StyleSheet, Pressable, Image, View } from 'react-native'
|
|
4
4
|
import { useCarousel } from './CarouselContext'
|
|
5
5
|
import { useThemeTokensCallback } from '../ThemeProvider'
|
|
6
6
|
import { useViewport } from '../ViewportProvider'
|
|
7
|
+
import Icon from '../Icon'
|
|
7
8
|
|
|
8
9
|
const selectPressableTokens = ({ borderColor, borderRadius, borderWidth, margin, padding }) => ({
|
|
9
10
|
borderColor,
|
|
@@ -13,83 +14,171 @@ const selectPressableTokens = ({ borderColor, borderRadius, borderWidth, margin,
|
|
|
13
14
|
padding
|
|
14
15
|
})
|
|
15
16
|
|
|
17
|
+
// Play icon overlay appearance is consistent across all brands and is not theme-configurable.
|
|
18
|
+
// The circle occupies 55% of the thumbnail size, and the icon occupies 55% of the circle diameter.
|
|
19
|
+
const PLAY_ICON_RATIO = 0.55
|
|
20
|
+
const PLAY_ICON_BORDER_RADIUS_DIVISOR = 2
|
|
21
|
+
|
|
22
|
+
const selectImageStyles = (size) => ({
|
|
23
|
+
width: size,
|
|
24
|
+
height: size
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const selectSelectedStyles = ({ selectedBorderColor, selectedBorderWidth, padding, margin }) => ({
|
|
28
|
+
borderColor: selectedBorderColor,
|
|
29
|
+
borderWidth: selectedBorderWidth,
|
|
30
|
+
padding: padding - selectedBorderWidth,
|
|
31
|
+
marginBottom: margin + selectedBorderWidth
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const selectNonSelectedStyles = ({ borderWidth, padding, margin, selectedBorderWidth }) => ({
|
|
35
|
+
padding: padding - borderWidth,
|
|
36
|
+
marginBottom: margin + selectedBorderWidth
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const selectPlayIconCircleStyles = (thumbnailSize) => {
|
|
40
|
+
const diameter = thumbnailSize * PLAY_ICON_RATIO
|
|
41
|
+
return {
|
|
42
|
+
width: diameter,
|
|
43
|
+
height: diameter,
|
|
44
|
+
borderRadius: diameter / PLAY_ICON_BORDER_RADIUS_DIVISOR
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const selectPlayIconTokens = (thumbnailSize) => ({
|
|
49
|
+
size: thumbnailSize * PLAY_ICON_RATIO * PLAY_ICON_RATIO
|
|
50
|
+
})
|
|
51
|
+
|
|
16
52
|
/**
|
|
17
53
|
* `Carousel.Thumbnail` is used to wrap the content of an individual slide and is suppsoed to be the
|
|
18
54
|
* only top-level component passed to the `Carousel`
|
|
19
55
|
*/
|
|
20
|
-
const CarouselThumbnail = React.forwardRef(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const { borderWidth, padding, selectedBorderColor, selectedBorderWidth, size, margin } =
|
|
36
|
-
getThumbnailTokens({ viewport })
|
|
37
|
-
const styles = {
|
|
38
|
-
image: {
|
|
39
|
-
height: size,
|
|
40
|
-
width: size
|
|
41
|
-
},
|
|
42
|
-
selected: {
|
|
43
|
-
borderColor: selectedBorderColor,
|
|
44
|
-
borderWidth: selectedBorderWidth,
|
|
45
|
-
padding: padding - selectedBorderWidth,
|
|
46
|
-
marginBottom: margin + selectedBorderWidth
|
|
47
|
-
},
|
|
48
|
-
nonSelected: {
|
|
49
|
-
padding: padding - borderWidth,
|
|
50
|
-
marginBottom: margin + selectedBorderWidth
|
|
56
|
+
const CarouselThumbnail = React.forwardRef(
|
|
57
|
+
({ accessibilityLabel, alt, index, isVideo, src }, ref) => {
|
|
58
|
+
const { activeIndex, itemLabel, totalItems, getCopyWithPlaceholders, goTo } = useCarousel()
|
|
59
|
+
const getThumbnailTokens = useThemeTokensCallback('CarouselThumbnail')
|
|
60
|
+
const viewport = useViewport()
|
|
61
|
+
const thumbnailTitle =
|
|
62
|
+
alt ??
|
|
63
|
+
getCopyWithPlaceholders('stepTrackerLabel')
|
|
64
|
+
.replace(/%\{itemLabel\}/g, itemLabel)
|
|
65
|
+
.replace(/%\{stepNumber\}/g, index)
|
|
66
|
+
.replace(/%\{stepCount\}/g, totalItems)
|
|
67
|
+
const handlePress = () => goTo(index)
|
|
68
|
+
const handleKeyDown = (event) => {
|
|
69
|
+
// Allow using the spacebar for navigation
|
|
70
|
+
if (event?.key === ' ') goTo(index)
|
|
51
71
|
}
|
|
52
|
-
|
|
72
|
+
const {
|
|
73
|
+
borderWidth,
|
|
74
|
+
padding,
|
|
75
|
+
selectedBorderColor,
|
|
76
|
+
selectedBorderWidth,
|
|
77
|
+
size,
|
|
78
|
+
margin,
|
|
79
|
+
playIcon
|
|
80
|
+
} = getThumbnailTokens({ viewport })
|
|
53
81
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
return (
|
|
83
|
+
<Pressable
|
|
84
|
+
key={src}
|
|
85
|
+
onKeyDown={handleKeyDown}
|
|
86
|
+
onPress={handlePress}
|
|
87
|
+
style={({ hovered, pressed, focused }) => {
|
|
88
|
+
const pressableStyles = selectPressableTokens(
|
|
89
|
+
getThumbnailTokens({
|
|
90
|
+
hover: hovered,
|
|
91
|
+
pressed,
|
|
92
|
+
focus: focused
|
|
93
|
+
})
|
|
94
|
+
)
|
|
67
95
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
return [
|
|
97
|
+
pressableStyles,
|
|
98
|
+
index === activeIndex
|
|
99
|
+
? [
|
|
100
|
+
selectSelectedStyles({
|
|
101
|
+
selectedBorderColor,
|
|
102
|
+
selectedBorderWidth,
|
|
103
|
+
padding,
|
|
104
|
+
margin
|
|
105
|
+
}),
|
|
106
|
+
staticStyles.selectedPressableOutline
|
|
107
|
+
]
|
|
108
|
+
: selectNonSelectedStyles({ borderWidth, padding, margin, selectedBorderWidth })
|
|
109
|
+
]
|
|
110
|
+
}}
|
|
111
|
+
ref={ref}
|
|
112
|
+
>
|
|
113
|
+
<View style={[staticStyles.imageContainer, selectImageStyles(size)]}>
|
|
114
|
+
<Image
|
|
115
|
+
accessibilityIgnoresInvertColors
|
|
116
|
+
accessibilityLabel={accessibilityLabel ?? alt}
|
|
117
|
+
source={src}
|
|
118
|
+
style={selectImageStyles(size)}
|
|
119
|
+
title={thumbnailTitle}
|
|
120
|
+
/>
|
|
121
|
+
{isVideo && playIcon && (
|
|
122
|
+
<View
|
|
123
|
+
style={staticStyles.playIconOverlayContainer}
|
|
124
|
+
testID={`play-icon-overlay-${index}`}
|
|
125
|
+
>
|
|
126
|
+
<View
|
|
127
|
+
style={[selectPlayIconCircleStyles(size), staticStyles.playIconCircleBackground]}
|
|
128
|
+
>
|
|
129
|
+
<Icon
|
|
130
|
+
icon={playIcon}
|
|
131
|
+
tokens={{
|
|
132
|
+
...selectPlayIconTokens(size),
|
|
133
|
+
color: staticStyles.playIconSymbol.color
|
|
134
|
+
}}
|
|
135
|
+
/>
|
|
136
|
+
</View>
|
|
137
|
+
</View>
|
|
138
|
+
)}
|
|
139
|
+
</View>
|
|
140
|
+
</Pressable>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
)
|
|
85
144
|
|
|
86
145
|
CarouselThumbnail.displayName = 'CarouselThumbnail'
|
|
87
146
|
|
|
88
147
|
CarouselThumbnail.propTypes = {
|
|
148
|
+
/** Accessibility label for screen readers, overrides the alt text */
|
|
89
149
|
accessibilityLabel: PropTypes.string,
|
|
150
|
+
/** Alt text for the thumbnail image */
|
|
90
151
|
alt: PropTypes.string,
|
|
152
|
+
/** Zero-based index of this thumbnail within the carousel */
|
|
91
153
|
index: PropTypes.number,
|
|
92
|
-
|
|
154
|
+
/**
|
|
155
|
+
* When true, renders a play icon overlay on the thumbnail to indicate that the slide contains a video.
|
|
156
|
+
*/
|
|
157
|
+
isVideo: PropTypes.bool,
|
|
158
|
+
/** Image source URI (web) or local asset require() (native) */
|
|
159
|
+
src: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
|
93
160
|
}
|
|
94
161
|
|
|
162
|
+
const staticStyles = StyleSheet.create({
|
|
163
|
+
imageContainer: {
|
|
164
|
+
position: 'relative'
|
|
165
|
+
},
|
|
166
|
+
playIconOverlayContainer: {
|
|
167
|
+
position: 'absolute',
|
|
168
|
+
top: 0,
|
|
169
|
+
left: 0,
|
|
170
|
+
right: 0,
|
|
171
|
+
bottom: 0,
|
|
172
|
+
justifyContent: 'center',
|
|
173
|
+
alignItems: 'center'
|
|
174
|
+
},
|
|
175
|
+
selectedPressableOutline: { outlineStyle: 'none' },
|
|
176
|
+
playIconCircleBackground: {
|
|
177
|
+
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
|
178
|
+
justifyContent: 'center',
|
|
179
|
+
alignItems: 'center'
|
|
180
|
+
},
|
|
181
|
+
playIconSymbol: { color: 'rgb(255, 255, 255)' }
|
|
182
|
+
})
|
|
183
|
+
|
|
95
184
|
export default CarouselThumbnail
|
|
@@ -27,11 +27,12 @@ const CarouselThumbnailNavigation = React.forwardRef(({ thumbnails = [] }, ref)
|
|
|
27
27
|
return (
|
|
28
28
|
<View style={containerStyles}>
|
|
29
29
|
<StackWrap direction="row" tokens={stackWrapTokens} space={2} ref={ref}>
|
|
30
|
-
{thumbnails.map(({ accessibilityLabel, alt, src }, index) => (
|
|
30
|
+
{thumbnails.map(({ accessibilityLabel, alt, isVideo, src }, index) => (
|
|
31
31
|
<CarouselThumbnail
|
|
32
32
|
accessibilityLabel={accessibilityLabel}
|
|
33
33
|
alt={alt}
|
|
34
34
|
index={index}
|
|
35
|
+
isVideo={isVideo}
|
|
35
36
|
key={src}
|
|
36
37
|
src={src}
|
|
37
38
|
/>
|
|
@@ -47,9 +48,14 @@ CarouselThumbnailNavigation.propTypes = {
|
|
|
47
48
|
*/
|
|
48
49
|
thumbnails: PropTypes.arrayOf(
|
|
49
50
|
PropTypes.shape({
|
|
51
|
+
/** Accessibility label for the thumbnail image, used by assistive technologies. */
|
|
50
52
|
accessibilityLabel: PropTypes.string,
|
|
53
|
+
/** Alternative text for the thumbnail image, displayed when the image cannot be rendered. */
|
|
51
54
|
alt: PropTypes.string,
|
|
52
|
-
|
|
55
|
+
/** When true, renders a play icon overlay on the thumbnail to indicate that the slide contains a video. */
|
|
56
|
+
isVideo: PropTypes.bool,
|
|
57
|
+
/** URL or path of the thumbnail image. When used with `isVideo`, this should be the video's poster/preview image. */
|
|
58
|
+
src: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
|
53
59
|
})
|
|
54
60
|
).isRequired
|
|
55
61
|
}
|
|
@@ -14,22 +14,26 @@ import { applyTextStyles, useThemeTokens } from '../ThemeProvider'
|
|
|
14
14
|
|
|
15
15
|
const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs, viewProps])
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
const HALF_FONT_SIZE = 2
|
|
18
|
+
const QUARTER_FONT_SIZE = 4
|
|
19
|
+
const DEFAULT_LINE_HEIGHT = 1
|
|
20
|
+
const LINE_HEIGHT_MULTIPLIER = 2
|
|
21
|
+
|
|
22
|
+
const selectTextStyle = ({ fontSize, lineHeight }) =>
|
|
23
|
+
Platform.select({
|
|
24
|
+
native: {
|
|
25
|
+
fontSize: fontSize / HALF_FONT_SIZE,
|
|
26
|
+
lineHeight: fontSize,
|
|
27
|
+
position: 'relative',
|
|
28
|
+
top: -(fontSize * lineHeight) / HALF_FONT_SIZE
|
|
27
29
|
},
|
|
28
30
|
web: {
|
|
29
|
-
|
|
31
|
+
fontSize,
|
|
32
|
+
lineHeight:
|
|
33
|
+
lineHeight !== DEFAULT_LINE_HEIGHT ? lineHeight : fontSize * LINE_HEIGHT_MULTIPLIER,
|
|
34
|
+
top: -fontSize / QUARTER_FONT_SIZE
|
|
30
35
|
}
|
|
31
36
|
})
|
|
32
|
-
})
|
|
33
37
|
|
|
34
38
|
const FootnoteLink = React.forwardRef(
|
|
35
39
|
({ copy = 'en', content = [], onClick, tokens, variant = {}, ...rest }, ref) => {
|
|
@@ -70,6 +74,7 @@ const FootnoteLink = React.forwardRef(
|
|
|
70
74
|
)
|
|
71
75
|
|
|
72
76
|
FootnoteLink.displayName = 'FootnoteLink'
|
|
77
|
+
FootnoteLink.__UDS_COMPONENT_NAME__ = 'FootnoteLink'
|
|
73
78
|
|
|
74
79
|
const copyShape = PropTypes.shape({ a11yLabel: PropTypes.string.isRequired })
|
|
75
80
|
|
|
@@ -66,12 +66,7 @@ const InputSupports = React.forwardRef(
|
|
|
66
66
|
/>
|
|
67
67
|
)}
|
|
68
68
|
{typeof children === 'function'
|
|
69
|
-
? children({
|
|
70
|
-
inputId,
|
|
71
|
-
...a11yProps,
|
|
72
|
-
validation: feedbackValidation,
|
|
73
|
-
accessibilityDescribedBy: feedbackId
|
|
74
|
-
})
|
|
69
|
+
? children({ inputId, ...a11yProps, validation: feedbackValidation })
|
|
75
70
|
: children}
|
|
76
71
|
{feedback || maxCharsReachedErrorMessage ? (
|
|
77
72
|
<Feedback
|
|
@@ -31,8 +31,8 @@ const useInputSupports = ({
|
|
|
31
31
|
: undefined
|
|
32
32
|
]), // native only -> replaced with describedBy on web
|
|
33
33
|
accessibilityDescribedBy: joinDefined([
|
|
34
|
-
!hasValidationError && feedback && feedbackId, // feedback receives a11yRole=alert on error, so there's no need to include it here
|
|
35
34
|
hint && hintId,
|
|
35
|
+
(feedback && feedbackId) || undefined,
|
|
36
36
|
charactersCount
|
|
37
37
|
? getCopy('charactersRemaining').replace(/%\{charCount\}/g, charactersCount)
|
|
38
38
|
: undefined
|
package/src/Modal/Modal.jsx
CHANGED
|
@@ -262,7 +262,7 @@ const Modal = React.forwardRef(
|
|
|
262
262
|
</View>
|
|
263
263
|
{/* when a modal becomes open its first focusable element is being automatically focused */}
|
|
264
264
|
{/* and we prefer the close button over backdrop */}
|
|
265
|
-
<TouchableWithoutFeedback onPress={isBackdropClickable
|
|
265
|
+
<TouchableWithoutFeedback onPress={isBackdropClickable ? handleClose : undefined}>
|
|
266
266
|
<View style={[staticStyles.backdrop, selectBackdropStyles(themeTokens)]} />
|
|
267
267
|
</TouchableWithoutFeedback>
|
|
268
268
|
</ScrollView>
|
|
@@ -8,6 +8,9 @@ import ProgressContext from './ProgressContext'
|
|
|
8
8
|
|
|
9
9
|
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
|
|
10
10
|
|
|
11
|
+
const BORDER_SIDES = 2
|
|
12
|
+
const CENTER_DIVISOR = 2
|
|
13
|
+
|
|
11
14
|
const selectProgressStyles = ({
|
|
12
15
|
backgroundColor,
|
|
13
16
|
borderWidth,
|
|
@@ -24,6 +27,14 @@ const selectProgressStyles = ({
|
|
|
24
27
|
...applyShadowToken(shadow)
|
|
25
28
|
})
|
|
26
29
|
|
|
30
|
+
const selectBarWrapperStyles = ({ barHeight, borderRadius, borderWidth, height }) => ({
|
|
31
|
+
height: barHeight,
|
|
32
|
+
width: '100%',
|
|
33
|
+
position: 'absolute',
|
|
34
|
+
top: (height - borderWidth * BORDER_SIDES - barHeight) / CENTER_DIVISOR,
|
|
35
|
+
borderRadius
|
|
36
|
+
})
|
|
37
|
+
|
|
27
38
|
/**
|
|
28
39
|
* The `Progress` is a container for displaying one or several `ProgressBar`s.
|
|
29
40
|
*
|
|
@@ -36,10 +47,11 @@ const selectProgressStyles = ({
|
|
|
36
47
|
*
|
|
37
48
|
* - Use the following tokens to customize the appearance:
|
|
38
49
|
* - `backgroundColor` for the background color of the progress container,
|
|
50
|
+
* - `barHeight` to control the height of the progress bars displayed within the container,
|
|
39
51
|
* - `borderColor` to control the color of the border,
|
|
40
52
|
* - `borderRadius` for the rounded corners,
|
|
41
53
|
* - `borderWidth` to change the border width.
|
|
42
|
-
* - `height` to control the height of the progress
|
|
54
|
+
* - `height` to control the height of the progress bar container.
|
|
43
55
|
* - `shadow` to apply a box shadow to the progress bar container.
|
|
44
56
|
*
|
|
45
57
|
* ## Variants
|
|
@@ -62,8 +74,15 @@ const selectProgressStyles = ({
|
|
|
62
74
|
*/
|
|
63
75
|
const Progress = React.forwardRef(({ children, tokens, variant, ...rest }, ref) => {
|
|
64
76
|
const themeTokens = useThemeTokens('Progress', tokens, variant)
|
|
65
|
-
// Default to false (vertical layout) to preserve existing behavior and avoid breaking changes
|
|
66
77
|
const layers = variant?.layers ?? false
|
|
78
|
+
const { barHeight, height } = themeTokens
|
|
79
|
+
|
|
80
|
+
const content =
|
|
81
|
+
barHeight && barHeight !== height ? (
|
|
82
|
+
<View style={selectBarWrapperStyles(themeTokens)}>{children}</View>
|
|
83
|
+
) : (
|
|
84
|
+
children
|
|
85
|
+
)
|
|
67
86
|
|
|
68
87
|
return (
|
|
69
88
|
<ProgressContext.Provider value={{ layers }}>
|
|
@@ -72,7 +91,7 @@ const Progress = React.forwardRef(({ children, tokens, variant, ...rest }, ref)
|
|
|
72
91
|
style={[staticStyles.progressContainer, selectProgressStyles(themeTokens)]}
|
|
73
92
|
{...selectProps(rest)}
|
|
74
93
|
>
|
|
75
|
-
{
|
|
94
|
+
{content}
|
|
76
95
|
</View>
|
|
77
96
|
</ProgressContext.Provider>
|
|
78
97
|
)
|