@telus-uds/components-base 1.18.1 → 1.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -2
- package/component-docs.json +111 -16
- package/jest.config-android.js +17 -0
- package/jest.config-ios.js +18 -0
- package/jest.config-web.js +31 -0
- package/lib/ActivityIndicator/Spinner.js +7 -7
- package/lib/ActivityIndicator/Spinner.native.js +2 -2
- package/lib/BaseProvider/HydrationContext.js +1 -1
- package/lib/BaseProvider/TamaguiProvider.js +30 -0
- package/lib/Button/ButtonBase.js +8 -4
- package/lib/Button/ButtonDropdown.js +207 -0
- package/lib/Button/ButtonGroup.js +1 -1
- package/lib/Carousel/Carousel.js +31 -5
- package/lib/Carousel/CarouselContext.js +1 -1
- package/lib/Carousel/CarouselFirstFocus/CarouselFirstFocus.js +1 -1
- package/lib/Carousel/CarouselTabs/CarouselTabsPanel.js +1 -10
- package/lib/Carousel/CarouselThumbnail.js +2 -2
- package/lib/Checkbox/Checkbox.js +1 -1
- package/lib/Checkbox/CheckboxGroup.js +2 -2
- package/lib/Divider/Divider.js +2 -2
- package/lib/FlexGrid/Col/Col.js +1 -1
- package/lib/Icon/Icon.js +1 -1
- package/lib/MultiSelectFilter/ModalOverlay.js +136 -0
- package/lib/MultiSelectFilter/MultiSelectFilter.js +314 -0
- package/lib/MultiSelectFilter/dictionary.js +19 -0
- package/lib/MultiSelectFilter/index.js +13 -0
- package/lib/Pagination/PageButton.js +2 -2
- package/lib/Pagination/Pagination.js +3 -5
- package/lib/Pagination/SideButton.js +6 -4
- package/lib/Pagination/usePagination.js +2 -2
- package/lib/Progress/ProgressBar.js +3 -3
- package/lib/Progress/ProgressBarBackground.js +3 -3
- package/lib/QuickLinksFeature/QuickLinksFeature.js +91 -0
- package/lib/QuickLinksFeature/QuickLinksFeatureItem.js +157 -0
- package/lib/QuickLinksFeature/index.js +16 -0
- package/lib/Radio/Radio.js +2 -2
- package/lib/Radio/RadioGroup.js +2 -2
- package/lib/RadioCard/RadioCard.js +1 -1
- package/lib/RadioCard/RadioCardGroup.js +2 -2
- package/lib/Responsive/Responsive.js +58 -0
- package/lib/Responsive/index.js +13 -0
- package/lib/Search/Search.js +30 -63
- package/lib/Select/constants.js +15 -0
- package/lib/SideNav/SideNav.js +2 -2
- package/lib/Skeleton/Skeleton.js +1 -1
- package/lib/Skeleton/skeletonWebAnimation.js +1 -1
- package/lib/StackView/StackWrap.js +1 -3
- package/lib/StackView/getStackedContent.js +2 -2
- package/lib/Tabs/Tabs.js +2 -4
- package/lib/Tags/Tags.js +11 -5
- package/lib/TextInput/TextInputBase.js +53 -19
- package/lib/TextInput/dictionary.js +19 -0
- package/lib/ThemeProvider/utils/styles.js +3 -3
- package/lib/ThemeProvider/utils/theme-tokens.js +9 -7
- package/lib/Timeline/Timeline.js +1 -1
- package/lib/ToggleSwitch/ToggleSwitch.js +1 -1
- package/lib/ToggleSwitch/ToggleSwitchGroup.js +1 -1
- package/lib/Tooltip/Backdrop.js +10 -2
- package/lib/Tooltip/Tooltip.native.js +357 -0
- package/lib/Tooltip/shared.js +39 -0
- package/lib/Validator/Validator.js +271 -0
- package/lib/Validator/index.js +13 -0
- package/lib/index.js +9 -0
- package/lib/utils/BaseView/BaseView.js +64 -0
- package/lib/utils/BaseView/BaseView.native.js +16 -0
- package/lib/utils/BaseView/index.js +13 -0
- package/lib/utils/animation/useVerticalExpandAnimation.js +1 -1
- package/lib/utils/children.js +2 -2
- package/lib/utils/floating-ui/index.js +43 -0
- package/lib/utils/floating-ui/index.native.js +43 -0
- package/lib/utils/input.js +12 -6
- package/lib/utils/props/componentPropType.js +3 -3
- package/lib/utils/props/selectSystemProps.js +2 -2
- package/lib/utils/props/tokens.js +2 -2
- package/lib/utils/useOverlaidPosition.js +243 -0
- package/lib/utils/useSpacingScale.js +1 -3
- package/lib/utils/useUniqueId.js +1 -1
- package/lib-module/ActivityIndicator/Spinner.js +7 -7
- package/lib-module/ActivityIndicator/Spinner.native.js +2 -2
- package/lib-module/BaseProvider/HydrationContext.js +1 -1
- package/lib-module/BaseProvider/TamaguiProvider.js +22 -0
- package/lib-module/Button/ButtonBase.js +8 -4
- package/lib-module/Button/ButtonDropdown.js +181 -0
- package/lib-module/Button/ButtonGroup.js +1 -1
- package/lib-module/Carousel/Carousel.js +31 -5
- package/lib-module/Carousel/CarouselContext.js +1 -1
- package/lib-module/Carousel/CarouselFirstFocus/CarouselFirstFocus.js +1 -1
- package/lib-module/Carousel/CarouselTabs/CarouselTabsPanel.js +1 -10
- package/lib-module/Carousel/CarouselThumbnail.js +2 -2
- package/lib-module/Checkbox/Checkbox.js +1 -1
- package/lib-module/Checkbox/CheckboxGroup.js +2 -2
- package/lib-module/Divider/Divider.js +2 -2
- package/lib-module/FlexGrid/Col/Col.js +1 -1
- package/lib-module/Icon/Icon.js +1 -1
- package/lib-module/MultiSelectFilter/ModalOverlay.js +112 -0
- package/lib-module/MultiSelectFilter/MultiSelectFilter.js +286 -0
- package/lib-module/MultiSelectFilter/dictionary.js +12 -0
- package/lib-module/MultiSelectFilter/index.js +2 -0
- package/lib-module/Pagination/PageButton.js +2 -2
- package/lib-module/Pagination/Pagination.js +3 -5
- package/lib-module/Pagination/SideButton.js +6 -4
- package/lib-module/Pagination/usePagination.js +2 -2
- package/lib-module/Progress/ProgressBar.js +3 -3
- package/lib-module/Progress/ProgressBarBackground.js +3 -3
- package/lib-module/QuickLinksFeature/QuickLinksFeature.js +69 -0
- package/lib-module/QuickLinksFeature/QuickLinksFeatureItem.js +130 -0
- package/lib-module/QuickLinksFeature/index.js +4 -0
- package/lib-module/Radio/Radio.js +2 -2
- package/lib-module/Radio/RadioGroup.js +2 -2
- package/lib-module/RadioCard/RadioCard.js +1 -1
- package/lib-module/RadioCard/RadioCardGroup.js +2 -2
- package/lib-module/Responsive/Responsive.js +45 -0
- package/lib-module/Responsive/index.js +2 -0
- package/lib-module/Search/Search.js +30 -61
- package/lib-module/Select/constants.js +5 -0
- package/lib-module/SideNav/SideNav.js +2 -2
- package/lib-module/Skeleton/Skeleton.js +1 -1
- package/lib-module/Skeleton/skeletonWebAnimation.js +1 -1
- package/lib-module/StackView/StackWrap.js +1 -3
- package/lib-module/StackView/getStackedContent.js +2 -2
- package/lib-module/Tabs/Tabs.js +2 -4
- package/lib-module/Tags/Tags.js +11 -5
- package/lib-module/TextInput/TextInputBase.js +52 -19
- package/lib-module/TextInput/dictionary.js +12 -0
- package/lib-module/ThemeProvider/utils/styles.js +3 -3
- package/lib-module/ThemeProvider/utils/theme-tokens.js +9 -7
- package/lib-module/Timeline/Timeline.js +1 -1
- package/lib-module/ToggleSwitch/ToggleSwitch.js +1 -1
- package/lib-module/ToggleSwitch/ToggleSwitchGroup.js +1 -1
- package/lib-module/Tooltip/Backdrop.js +10 -2
- package/lib-module/Tooltip/Tooltip.native.js +326 -0
- package/lib-module/Tooltip/shared.js +27 -0
- package/lib-module/Validator/Validator.js +245 -0
- package/lib-module/Validator/index.js +2 -0
- package/lib-module/index.js +1 -0
- package/lib-module/utils/BaseView/BaseView.js +43 -0
- package/lib-module/utils/BaseView/BaseView.native.js +6 -0
- package/lib-module/utils/BaseView/index.js +2 -0
- package/lib-module/utils/animation/useVerticalExpandAnimation.js +1 -1
- package/lib-module/utils/children.js +2 -2
- package/lib-module/utils/floating-ui/index.js +1 -0
- package/lib-module/utils/floating-ui/index.native.js +1 -0
- package/lib-module/utils/input.js +12 -6
- package/lib-module/utils/props/componentPropType.js +3 -3
- package/lib-module/utils/props/selectSystemProps.js +2 -2
- package/lib-module/utils/props/tokens.js +2 -2
- package/lib-module/utils/useOverlaidPosition.js +232 -0
- package/lib-module/utils/useSpacingScale.js +1 -3
- package/lib-module/utils/useUniqueId.js +1 -1
- package/package.json +7 -4
- package/src/Button/ButtonBase.jsx +4 -2
- package/src/Carousel/Carousel.jsx +42 -10
- package/src/Carousel/CarouselTabs/CarouselTabsPanel.jsx +0 -10
- package/src/Pagination/SideButton.jsx +5 -5
- package/src/Responsive/Responsive.jsx +33 -0
- package/src/Responsive/index.js +3 -0
- package/src/Search/Search.jsx +17 -32
- package/src/Tags/Tags.jsx +46 -33
- package/src/TextInput/TextInputBase.jsx +46 -16
- package/src/index.js +1 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
|
+
import NativeView from "react-native-web/dist/exports/View";
|
|
3
|
+
import StyleSheet from "react-native-web/dist/exports/StyleSheet";
|
|
4
|
+
import PropTypes from 'prop-types';
|
|
5
|
+
import { useTheme } from '../../ThemeProvider';
|
|
6
|
+
/**
|
|
7
|
+
* Identical to React Native's View and supporting all the same props, but with:
|
|
8
|
+
* - a zIndex: 'auto' style added to prevent unexpectedly causing children to overlap other elements from other stacking contexts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
12
|
+
const BaseView = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
13
|
+
let {
|
|
14
|
+
children,
|
|
15
|
+
style,
|
|
16
|
+
...rest
|
|
17
|
+
} = _ref;
|
|
18
|
+
const {
|
|
19
|
+
themeOptions
|
|
20
|
+
} = useTheme();
|
|
21
|
+
const styleProp = Array.isArray(style) ? [...style] : [style];
|
|
22
|
+
|
|
23
|
+
if (!themeOptions.forceZIndex) {
|
|
24
|
+
styleProp.unshift(styles.resetZIndex);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return /*#__PURE__*/_jsx(NativeView, { ...rest,
|
|
28
|
+
style: styleProp,
|
|
29
|
+
ref: ref,
|
|
30
|
+
children: children
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
BaseView.displayName = 'BaseView';
|
|
34
|
+
const styles = StyleSheet.create({
|
|
35
|
+
resetZIndex: {
|
|
36
|
+
zIndex: 'auto'
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
BaseView.propTypes = {
|
|
40
|
+
children: PropTypes.node,
|
|
41
|
+
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array])
|
|
42
|
+
};
|
|
43
|
+
export default BaseView;
|
|
@@ -72,7 +72,7 @@ function useVerticalExpandAnimation(_ref) {
|
|
|
72
72
|
}
|
|
73
73
|
} else if (Platform.OS === 'web') {
|
|
74
74
|
const transitionDuration = isExpanded ? expandDuration : collapseDuration;
|
|
75
|
-
containerStyles.transition =
|
|
75
|
+
containerStyles.transition = `height ${transitionDuration}ms ease-in-out`;
|
|
76
76
|
containerStyles.height = isExpanded ? containerHeight : 0;
|
|
77
77
|
} else if (Platform.OS === 'ios' && isExpanded && !isAnimating && !expandStateChanged && typeof containerHeight === 'number') {
|
|
78
78
|
// iOS reflows text while the height animation is in progress. Sometimes, it hits a timing bug where
|
|
@@ -55,7 +55,7 @@ const isWrapable = child => {
|
|
|
55
55
|
return isStringOrNumber(child) || child.type === A11yText || ((_child$type = child.type) === null || _child$type === void 0 ? void 0 : _child$type.name) === 'FootnoteLink';
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
const combineKeys = childrenArray => childrenArray.reduce((newKey, child) =>
|
|
58
|
+
const combineKeys = childrenArray => childrenArray.reduce((newKey, child) => `${newKey}${child.key || ''}`, ''); // Group wrappable children for one `<Text>` parent, merging adjacent text nodes
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
const wrapChild = (child, wrappedText) => {
|
|
@@ -64,7 +64,7 @@ const wrapChild = (child, wrappedText) => {
|
|
|
64
64
|
|
|
65
65
|
if (lastIndex >= 0 && isStringOrNumber(child) && isStringOrNumber(wrappedText[lastIndex])) {
|
|
66
66
|
/* eslint-disable-next-line no-param-reassign */
|
|
67
|
-
wrappedText[lastIndex] =
|
|
67
|
+
wrappedText[lastIndex] = `${wrappedText[lastIndex]}${child}`;
|
|
68
68
|
} else {
|
|
69
69
|
wrappedText.push(child);
|
|
70
70
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useFloating, arrow, offset, shift, flip, autoPlacement } from '@floating-ui/react-dom';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useFloating, arrow, offset, shift, flip, autoPlacement } from '@floating-ui/react-native';
|
|
@@ -21,23 +21,29 @@ const validateProps = (_ref, _ref2, hookName) => {
|
|
|
21
21
|
const usageError = error => {
|
|
22
22
|
// Errors inside hooks in React Native get incomplete stack traces pointing at parent component only.
|
|
23
23
|
// Help devs out by telling them exactly which hook threw the error as well as why.
|
|
24
|
-
throw new Error(
|
|
24
|
+
throw new Error(`${hookName} ${error}.
|
|
25
|
+
|
|
26
|
+
Consumers of this hook must be one of:
|
|
27
|
+
1. An "uncontrolled" component responding directly to user actions, with an optional \`initialValue${s}\` but no \`value${s}\` prop,
|
|
28
|
+
2. A "controlled" component where an always-defined \`value${s}\` prop is managed by an \`onChange\` handler, with no \`initialValue${s}\`,
|
|
29
|
+
3. A "read-only" component, with \`readOnly\` prop set as \`true\`.
|
|
30
|
+
`);
|
|
25
31
|
};
|
|
26
32
|
|
|
27
33
|
if (value && !onChange && !readOnly) {
|
|
28
|
-
usageError(
|
|
34
|
+
usageError(`has \`value${s}\` prop without \`onChange\` or \`readOnly\``);
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
if (initialValue && value) {
|
|
32
|
-
usageError(
|
|
38
|
+
usageError(`has both \`initialValue${s}\` and \`value${s}\``);
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
if (isControlled && !isCurrentlyControlled) {
|
|
36
|
-
usageError(
|
|
42
|
+
usageError(`stopped receiving \`value${s}\` from parent after delegating state management`);
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
if (!isControlled && isCurrentlyControlled) {
|
|
40
|
-
usageError(
|
|
46
|
+
usageError(`started receiving \`value${s}\` from parent after starting managing own state`);
|
|
41
47
|
}
|
|
42
48
|
};
|
|
43
49
|
/**
|
|
@@ -148,7 +154,7 @@ export const useMultipleInputValues = function () {
|
|
|
148
154
|
onChange,
|
|
149
155
|
value: values,
|
|
150
156
|
// if we're controlling our own state, always start with an array
|
|
151
|
-
initialValue: initialValues
|
|
157
|
+
initialValue: initialValues ?? (values === undefined ? [] : undefined)
|
|
152
158
|
}, 'useMultipleInputValues');
|
|
153
159
|
const enforceMaxValues = useCallback(newValues => {
|
|
154
160
|
if (!maxValues) return newValues;
|
|
@@ -26,8 +26,8 @@ export default function componentPropType(passedName) {
|
|
|
26
26
|
const nameInProp = ((_props$propName$type = props[propName].type) === null || _props$propName$type === void 0 ? void 0 : _props$propName$type.displayName) || ((_props$propName$type2 = props[propName].type) === null || _props$propName$type2 === void 0 ? void 0 : _props$propName$type2.name);
|
|
27
27
|
|
|
28
28
|
if (!nameInProp || !passedNames.includes(nameInProp)) {
|
|
29
|
-
const propDescription = nameInProp ?
|
|
30
|
-
return new Error(
|
|
29
|
+
const propDescription = nameInProp ? `Component ${nameInProp}` : typeof props[propName];
|
|
30
|
+
return new Error(`${componentName}: ${propDescription} was passed to \`${propName}\` prop; should be ${passedNames.join(' or ')}`);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
return undefined;
|
|
@@ -35,7 +35,7 @@ export default function componentPropType(passedName) {
|
|
|
35
35
|
|
|
36
36
|
const checkRequired = (props, propName, componentName) => {
|
|
37
37
|
if (props[propName] === undefined) {
|
|
38
|
-
return new Error(
|
|
38
|
+
return new Error(`The prop \`${propName}\` is marked as required in \`${componentName}\`, but its value is ${props[propName]}.`);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
return undefined;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
export default function selectSystemProps(systemPropHelpers) {
|
|
4
4
|
const selectProps = props => systemPropHelpers.reduce((acc, propHelper) => {
|
|
5
5
|
if (typeof (propHelper === null || propHelper === void 0 ? void 0 : propHelper.select) !== 'function') {
|
|
6
|
-
throw new Error(
|
|
6
|
+
throw new Error(`An invalid system prop helper has been passed to 'selectSystemProps': prop selector ('.select') is missing in ${propHelper}`);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
return { ...acc,
|
|
@@ -13,7 +13,7 @@ export default function selectSystemProps(systemPropHelpers) {
|
|
|
13
13
|
|
|
14
14
|
const selectedPropTypes = systemPropHelpers.reduce((acc, propHelper) => {
|
|
15
15
|
if (!(propHelper !== null && propHelper !== void 0 && propHelper.types)) {
|
|
16
|
-
throw new Error(
|
|
16
|
+
throw new Error(`An invalid system prop helper has been passed to 'selectSystemProps': types selector ('.types') is missing in ${propHelper}`);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
return { ...acc,
|
|
@@ -9,7 +9,7 @@ export const getTokenNames = componentName => {
|
|
|
9
9
|
const componentTokenSchema = tokenKeys[componentName];
|
|
10
10
|
|
|
11
11
|
if (!componentTokenSchema) {
|
|
12
|
-
throw new Error(
|
|
12
|
+
throw new Error(`No "${componentName}" tokenKeys in @telus-uds/system-theme-tokens`);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
return Object.keys(componentTokenSchema);
|
|
@@ -47,7 +47,7 @@ export const getTokenNames = componentName => {
|
|
|
47
47
|
export const selectTokens = (specifier, tokens, prefix) => {
|
|
48
48
|
const tokenNames = typeof specifier === 'string' ? getTokenNames(specifier) : specifier;
|
|
49
49
|
const filteredTokens = tokenNames.reduce((validTokens, key) => {
|
|
50
|
-
const prefixedKey = prefix ?
|
|
50
|
+
const prefixedKey = prefix ? `${prefix}${key[0].toUpperCase()}${key.slice(1)}` : key;
|
|
51
51
|
const token = tokens[prefixedKey];
|
|
52
52
|
return token !== undefined ? { ...validTokens,
|
|
53
53
|
[key]: token
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import Dimensions from "react-native-web/dist/exports/Dimensions";
|
|
3
|
+
|
|
4
|
+
const adjustHorizontalToFit = (initialOffset, windowWidth, sourceWidth) => {
|
|
5
|
+
const offset = Math.max(0, initialOffset);
|
|
6
|
+
const otherEdgeOverflow = Math.max(0, offset + sourceWidth - windowWidth);
|
|
7
|
+
const tooWideBy = Math.max(0, otherEdgeOverflow - offset);
|
|
8
|
+
const adjusted = {
|
|
9
|
+
offset: Math.max(0, offset - otherEdgeOverflow)
|
|
10
|
+
};
|
|
11
|
+
if (tooWideBy) adjusted.width = Math.max(0, sourceWidth - tooWideBy);
|
|
12
|
+
return adjusted;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const getPosition = _ref => {
|
|
16
|
+
let {
|
|
17
|
+
edge,
|
|
18
|
+
fromEdge,
|
|
19
|
+
sourceSize
|
|
20
|
+
} = _ref;
|
|
21
|
+
|
|
22
|
+
switch (edge) {
|
|
23
|
+
case 'near':
|
|
24
|
+
return fromEdge;
|
|
25
|
+
|
|
26
|
+
case 'mid':
|
|
27
|
+
return fromEdge + sourceSize / 2;
|
|
28
|
+
|
|
29
|
+
case 'far':
|
|
30
|
+
return fromEdge + sourceSize;
|
|
31
|
+
|
|
32
|
+
default:
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const getEdgeType = (align, alignSide) => {
|
|
38
|
+
const alignTo = align[alignSide];
|
|
39
|
+
const edge = ['center', 'middle'].includes(alignTo) && 'mid' || (alignSide === alignTo ? 'near' : 'far');
|
|
40
|
+
return edge;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Based on UDS's private getTooltipPosition but generalised.
|
|
44
|
+
*
|
|
45
|
+
* Used for absolute positioning of the tooltip. Since the tooltip is always centered relatively
|
|
46
|
+
* to the source (button) and we have a limited set of positions, an easy and consistent way
|
|
47
|
+
* of positioning it is to check all of the possible positions and pick one that will be rendered
|
|
48
|
+
* within the window bounds. This way we can also rely on the tooltip being actually rendered
|
|
49
|
+
* before it is shown, which makes it account for the width being limiting in styles, custom font
|
|
50
|
+
* rendering, etc.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
function getOverlaidPosition(_ref2) {
|
|
55
|
+
let {
|
|
56
|
+
sourceLayout,
|
|
57
|
+
targetDimensions,
|
|
58
|
+
windowDimensions,
|
|
59
|
+
offsets = {},
|
|
60
|
+
align
|
|
61
|
+
} = _ref2;
|
|
62
|
+
// Web-only: this will be difficult to mimic on native because there's no global scroll position.
|
|
63
|
+
// TODO: wire something in e.g. a scroll ref accessible from a provider included in Allium provider
|
|
64
|
+
// that can be passed to the appropriate ScrollView?
|
|
65
|
+
const {
|
|
66
|
+
scrollX = 0,
|
|
67
|
+
scrollY = 0
|
|
68
|
+
} = typeof window === 'object' ? window : {}; // Will have top, bottom, left and/or right offsets depending on `align`
|
|
69
|
+
|
|
70
|
+
const positioning = {};
|
|
71
|
+
if (align.top) positioning.top = getPosition({
|
|
72
|
+
edge: getEdgeType(align, 'top'),
|
|
73
|
+
fromEdge: sourceLayout.y + scrollY + (offsets.vertical ?? 0),
|
|
74
|
+
sourceSize: sourceLayout.height
|
|
75
|
+
});
|
|
76
|
+
if (align.middle) positioning.top = getPosition({
|
|
77
|
+
edge: getEdgeType(align, 'middle'),
|
|
78
|
+
fromEdge: sourceLayout.y + scrollY + (offsets.vertical ?? 0) - targetDimensions.height / 2,
|
|
79
|
+
sourceSize: sourceLayout.height
|
|
80
|
+
});
|
|
81
|
+
if (align.bottom) positioning.bottom = getPosition({
|
|
82
|
+
edge: getEdgeType(align, 'bottom'),
|
|
83
|
+
fromEdge: windowDimensions.height - (sourceLayout.y + scrollY + sourceLayout.height - (offsets.vertical ?? 0)),
|
|
84
|
+
sourceSize: sourceLayout.height
|
|
85
|
+
});
|
|
86
|
+
if (align.left) positioning.left = getPosition({
|
|
87
|
+
edge: getEdgeType(align, 'left'),
|
|
88
|
+
fromEdge: sourceLayout.x + scrollX + (offsets.horizontal ?? 0),
|
|
89
|
+
sourceSize: sourceLayout.width
|
|
90
|
+
});
|
|
91
|
+
if (align.center) positioning.left = getPosition({
|
|
92
|
+
edge: getEdgeType(align, 'center'),
|
|
93
|
+
fromEdge: sourceLayout.x + scrollX + (offsets.horizontal ?? 0) - targetDimensions.width / 2,
|
|
94
|
+
sourceSize: sourceLayout.width
|
|
95
|
+
});
|
|
96
|
+
if (align.right) positioning.right = getPosition({
|
|
97
|
+
edge: getEdgeType(align, 'right'),
|
|
98
|
+
fromEdge: windowDimensions.width - (sourceLayout.x + scrollX + sourceLayout.width - (offsets.horizontal ?? 0)),
|
|
99
|
+
sourceSize: sourceLayout.width
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (!(align.left && align.right)) {
|
|
103
|
+
// Check if the position and/or width need adjusting to fit on the screen
|
|
104
|
+
const side = align.right ? 'right' : 'left';
|
|
105
|
+
const adjusted = adjustHorizontalToFit(positioning[side], windowDimensions.width, sourceLayout.width);
|
|
106
|
+
if (typeof adjusted.width === 'number') positioning.width = adjusted.width;
|
|
107
|
+
|
|
108
|
+
if (typeof adjusted.offset === 'number') {
|
|
109
|
+
positioning[side] = adjusted.offset;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return positioning;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Positions an element in a modal or portal so that it appears tooltip-like below the
|
|
117
|
+
* target element.
|
|
118
|
+
*
|
|
119
|
+
* @TODO - add support for positioning other than 'below' like UDS's tooltip (this is not
|
|
120
|
+
* a small task because UDS's tooltip logic only really works for short text - it might be
|
|
121
|
+
* better to use a third-party library).
|
|
122
|
+
*/
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
const useOverlaidPosition = _ref3 => {
|
|
126
|
+
let {
|
|
127
|
+
isShown = false,
|
|
128
|
+
offsets,
|
|
129
|
+
// By default, align the overlaid target's `top` to the bottom of the source, and center horizontally.
|
|
130
|
+
align = {
|
|
131
|
+
center: 'center',
|
|
132
|
+
top: 'bottom'
|
|
133
|
+
}
|
|
134
|
+
} = _ref3;
|
|
135
|
+
// Element in main document flow that the targetRef element is positioned around
|
|
136
|
+
const sourceRef = useRef(null);
|
|
137
|
+
const [sourceLayout, setSourceLayout] = useState(null); // Element in a modal or portal overlay positioned to appear adjacent to sourceRef
|
|
138
|
+
|
|
139
|
+
const targetRef = useRef(null);
|
|
140
|
+
const [targetDimensions, setTargetDimensions] = useState(null);
|
|
141
|
+
const [windowDimensions, setWindowDimensions] = useState(null);
|
|
142
|
+
const onTargetLayout = useCallback(_ref4 => {
|
|
143
|
+
let {
|
|
144
|
+
nativeEvent: {
|
|
145
|
+
layout: {
|
|
146
|
+
width,
|
|
147
|
+
height
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} = _ref4;
|
|
151
|
+
// NOTE: UDS's Tooltip logic injects some additional width to allow for antialiasing etc of text,
|
|
152
|
+
// avoiding adding unnecessary line breaks to text that is slightly wider than it thinks it is.
|
|
153
|
+
// That is probably something specific to text tooltips that doesn't belong in a generic hook.
|
|
154
|
+
setTargetDimensions(previousDimensions => {
|
|
155
|
+
// Re-render on first non-zero width / height: avoid infinite loops on changes, or mispositioning
|
|
156
|
+
// if user scrolls while a slidedown animation is changing the height and recalculating position.
|
|
157
|
+
if (!previousDimensions && width && height) {
|
|
158
|
+
return {
|
|
159
|
+
width,
|
|
160
|
+
height
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return previousDimensions;
|
|
165
|
+
});
|
|
166
|
+
}, []);
|
|
167
|
+
const readyToShow = Boolean(isShown && sourceRef.current);
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
const handleDimensionsChange = _ref5 => {
|
|
170
|
+
var _sourceRef$current;
|
|
171
|
+
|
|
172
|
+
let {
|
|
173
|
+
window
|
|
174
|
+
} = _ref5;
|
|
175
|
+
(_sourceRef$current = sourceRef.current) === null || _sourceRef$current === void 0 ? void 0 : _sourceRef$current.measureInWindow((x, y, width, height) => {
|
|
176
|
+
// Could add a debouncer here if there's too many rerenders during gradual resizes
|
|
177
|
+
setWindowDimensions(window);
|
|
178
|
+
setSourceLayout({
|
|
179
|
+
x,
|
|
180
|
+
y,
|
|
181
|
+
width,
|
|
182
|
+
height
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
let subscription;
|
|
188
|
+
|
|
189
|
+
const unsubscribe = () => {
|
|
190
|
+
var _subscription;
|
|
191
|
+
|
|
192
|
+
if (typeof ((_subscription = subscription) === null || _subscription === void 0 ? void 0 : _subscription.remove) === 'function') {
|
|
193
|
+
// React Native >=0.65.0
|
|
194
|
+
subscription.remove();
|
|
195
|
+
} else if (typeof Dimensions.removeEventListener === 'function') {
|
|
196
|
+
// React Native <0.65.0
|
|
197
|
+
Dimensions.removeEventListener('change', handleDimensionsChange);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
setSourceLayout(null);
|
|
201
|
+
setTargetDimensions(null);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
if (readyToShow) {
|
|
205
|
+
subscription = Dimensions.addEventListener('change', handleDimensionsChange);
|
|
206
|
+
handleDimensionsChange({
|
|
207
|
+
window: Dimensions.get('window')
|
|
208
|
+
});
|
|
209
|
+
} else {
|
|
210
|
+
unsubscribe();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return unsubscribe;
|
|
214
|
+
}, [readyToShow]);
|
|
215
|
+
const isReady = Boolean(isShown && sourceLayout && windowDimensions && targetDimensions);
|
|
216
|
+
const overlaidPosition = isReady ? getOverlaidPosition({
|
|
217
|
+
sourceLayout,
|
|
218
|
+
targetDimensions,
|
|
219
|
+
windowDimensions,
|
|
220
|
+
offsets,
|
|
221
|
+
align
|
|
222
|
+
}) : {};
|
|
223
|
+
return {
|
|
224
|
+
overlaidPosition,
|
|
225
|
+
sourceRef,
|
|
226
|
+
targetRef,
|
|
227
|
+
onTargetLayout,
|
|
228
|
+
isReady
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export default useOverlaidPosition;
|
|
@@ -101,8 +101,6 @@ const resolveSpacingOptions = space => {
|
|
|
101
101
|
|
|
102
102
|
|
|
103
103
|
const useSpacingScale = spaceValue => {
|
|
104
|
-
var _spaceValue$space;
|
|
105
|
-
|
|
106
104
|
// In future, may need to consider window height as well as width, particularly for native apps,
|
|
107
105
|
// e.g. to ensure designs don't look lost on large, tall, not-so-wide portrait screens.
|
|
108
106
|
const viewport = useViewport();
|
|
@@ -112,7 +110,7 @@ const useSpacingScale = spaceValue => {
|
|
|
112
110
|
overridden,
|
|
113
111
|
subtract = 0
|
|
114
112
|
} = resolveSpacingOptions(spaceValue);
|
|
115
|
-
const space = !overridden && ((
|
|
113
|
+
const space = !overridden && ((spaceValue === null || spaceValue === void 0 ? void 0 : spaceValue.space) ?? resolveResponsiveProp(spaceValue, viewport, 0));
|
|
116
114
|
const {
|
|
117
115
|
size
|
|
118
116
|
} = useThemeTokens('spacingScale', tokens, variant, {
|
package/package.json
CHANGED
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@gorhom/portal": "^1.0.14",
|
|
11
|
-
"@telus-uds/system-constants": "^1.0
|
|
12
|
-
"@telus-uds/system-theme-tokens": "^2.
|
|
11
|
+
"@telus-uds/system-constants": "^1.1.0",
|
|
12
|
+
"@telus-uds/system-theme-tokens": "^2.7.0",
|
|
13
13
|
"airbnb-prop-types": "^2.16.0",
|
|
14
14
|
"lodash.debounce": "^4.0.8",
|
|
15
15
|
"lodash.merge": "^4.6.2",
|
|
16
16
|
"prop-types": "^15.7.2",
|
|
17
17
|
"react-native-picker-select": "^8.0.4",
|
|
18
|
-
"semver": "
|
|
18
|
+
"semver": "7.3.5"
|
|
19
19
|
},
|
|
20
20
|
"description": "Base components",
|
|
21
21
|
"devDependencies": {
|
|
@@ -54,6 +54,9 @@
|
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"test": "jest",
|
|
57
|
+
"test:web": "jest --config jest.config-web.js",
|
|
58
|
+
"test:ios": "jest --config jest.config-ios.js",
|
|
59
|
+
"test:android": "jest --config jest.config-android.js",
|
|
57
60
|
"lint": "npm run --prefix ../.. lint:path -- --color packages/components-base",
|
|
58
61
|
"lint:fix": "npm run --prefix ../.. lint:path -- --fix packages/components-base",
|
|
59
62
|
"format": "prettier --write .",
|
|
@@ -67,5 +70,5 @@
|
|
|
67
70
|
"standard-engine": {
|
|
68
71
|
"skip": true
|
|
69
72
|
},
|
|
70
|
-
"version": "1.
|
|
73
|
+
"version": "1.19.1"
|
|
71
74
|
}
|
|
@@ -123,7 +123,7 @@ const selectBorderStyles = ({ borderColor, borderWidth, borderRadius }) => ({
|
|
|
123
123
|
})
|
|
124
124
|
|
|
125
125
|
const selectTextStyles = (
|
|
126
|
-
{ fontSize, color, lineHeight, fontName, fontWeight, textAlign },
|
|
126
|
+
{ fontSize, color, lineHeight, fontName, fontWeight, textAlign, textLine, textLineStyle },
|
|
127
127
|
themeOptions
|
|
128
128
|
) =>
|
|
129
129
|
applyTextStyles({
|
|
@@ -133,7 +133,9 @@ const selectTextStyles = (
|
|
|
133
133
|
fontName,
|
|
134
134
|
fontWeight,
|
|
135
135
|
themeOptions,
|
|
136
|
-
textAlign
|
|
136
|
+
textAlign,
|
|
137
|
+
textDecorationLine: textLine,
|
|
138
|
+
textDecorationStyle: textLineStyle
|
|
137
139
|
})
|
|
138
140
|
|
|
139
141
|
const selectWebOnlyStyles = (inactive, themeTokens, { accessibilityRole }) => {
|
|
@@ -164,7 +164,7 @@ const Carousel = React.forwardRef(
|
|
|
164
164
|
),
|
|
165
165
|
tag = 'ul',
|
|
166
166
|
accessibilityRole,
|
|
167
|
-
accessibilityLabel
|
|
167
|
+
accessibilityLabel,
|
|
168
168
|
accessibilityLiveRegion = 'polite',
|
|
169
169
|
copy,
|
|
170
170
|
...rest
|
|
@@ -422,6 +422,32 @@ const Carousel = React.forwardRef(
|
|
|
422
422
|
const activePanelNavigation =
|
|
423
423
|
tabs && showPanelTabs ? <CarouselTabsPanel items={tabs} /> : panelNavigation
|
|
424
424
|
|
|
425
|
+
const isFirstFocusContainer = Boolean(refocus && !skipLinkHref)
|
|
426
|
+
const containerRef = (element) => {
|
|
427
|
+
// Apply both firstFocusRef to the container
|
|
428
|
+
firstFocusRef.current = element
|
|
429
|
+
// Also apply forwarded ref if there is one (which could be a function ref)
|
|
430
|
+
if (ref) {
|
|
431
|
+
if (typeof ref === 'object') {
|
|
432
|
+
// eslint-disable-next-line no-param-reassign
|
|
433
|
+
ref.current = element
|
|
434
|
+
} else if (typeof ref === 'function') {
|
|
435
|
+
ref(element)
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// If container isn't used for focus, give it a label of title if none is passed in,
|
|
440
|
+
// otherwise read the current position on focus
|
|
441
|
+
const containerAccessibilityLabel =
|
|
442
|
+
systemProps.accessibilityLabel ?? isFirstFocusContainer
|
|
443
|
+
? `${title ? `${title} ` : ''}${getCopyWithPlaceholders('stepTrackerLabel')}`
|
|
444
|
+
: title
|
|
445
|
+
const containerProps = {
|
|
446
|
+
accessibilityLabel: containerAccessibilityLabel,
|
|
447
|
+
// If used for focus, attach the ref and draw a focus box around the whole carousel
|
|
448
|
+
...(isFirstFocusContainer && { ref: containerRef, focusable: true })
|
|
449
|
+
}
|
|
450
|
+
|
|
425
451
|
return (
|
|
426
452
|
<CarouselProvider
|
|
427
453
|
activeIndex={activeIndex}
|
|
@@ -434,7 +460,13 @@ const Carousel = React.forwardRef(
|
|
|
434
460
|
refocus={refocus}
|
|
435
461
|
width={containerLayout.width}
|
|
436
462
|
>
|
|
437
|
-
<View
|
|
463
|
+
<View
|
|
464
|
+
style={staticStyles.root}
|
|
465
|
+
onLayout={onContainerLayout}
|
|
466
|
+
ref={ref}
|
|
467
|
+
{...systemProps}
|
|
468
|
+
{...containerProps}
|
|
469
|
+
>
|
|
438
470
|
{showPreviousNextNavigation && (
|
|
439
471
|
<View
|
|
440
472
|
style={selectPreviousNextNavigationButtonStyles(
|
|
@@ -464,14 +496,14 @@ const Carousel = React.forwardRef(
|
|
|
464
496
|
{getCopyWithPlaceholders('skipLink')}
|
|
465
497
|
</SkipLink>
|
|
466
498
|
)}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
499
|
+
{!isFirstFocusContainer && (
|
|
500
|
+
<A11yText
|
|
501
|
+
// Read the current slide position to screen readers on slide.
|
|
502
|
+
// If it's set to refocus and doesn't have a SkipLink to focus to, focus this.
|
|
503
|
+
accessibilityLiveRegion={!skipLinkHref && refocus ? undefined : 'polite'}
|
|
504
|
+
text={getCopyWithPlaceholders('stepTrackerLabel')}
|
|
505
|
+
/>
|
|
506
|
+
)}
|
|
475
507
|
<View style={selectContainerStyles(containerLayout.width)}>
|
|
476
508
|
<Animated.View
|
|
477
509
|
style={StyleSheet.flatten([
|
|
@@ -21,16 +21,6 @@ const CarouselTabsPanel = forwardRef(({ items }, ref) => {
|
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
23
|
<>
|
|
24
|
-
<View
|
|
25
|
-
focusable
|
|
26
|
-
accessible
|
|
27
|
-
onFocus={(event) => {
|
|
28
|
-
// When user forward-tabs into this section, focus the next tab; if they backwards-tab
|
|
29
|
-
// (shift-tab) back into the carousel content, don't interfere.
|
|
30
|
-
const previousWebFocus = event.relatedTarget
|
|
31
|
-
if (previousWebFocus !== firstTabRef.current) nextFocusRef.current.focus()
|
|
32
|
-
}}
|
|
33
|
-
/>
|
|
34
24
|
<StackView direction="row" space={3} divider={{ variant: dividerVariant }} ref={ref}>
|
|
35
25
|
{items.map(({ title, onPress, ...panelItemProps }, index) => {
|
|
36
26
|
const selected = index === activeIndex
|
|
@@ -13,8 +13,9 @@ import dictionary from './dictionary'
|
|
|
13
13
|
import useCopy from '../utils/useCopy'
|
|
14
14
|
|
|
15
15
|
// We need to drop the icon here since it gets rendered via children and not
|
|
16
|
-
// `ButtonBase` in order to tap into the state of the button
|
|
17
|
-
|
|
16
|
+
// `ButtonBase` in order to tap into the state of the button; `displayLabel` flag
|
|
17
|
+
// is also not needed
|
|
18
|
+
const selectButtonTokens = ({ icon: _, displayLabel: __, ...rest }) => selectTokens('Button', rest)
|
|
18
19
|
const selectIconTokens = ({ color, iconSize, iconDisplace }, direction) => {
|
|
19
20
|
return {
|
|
20
21
|
color,
|
|
@@ -36,13 +37,12 @@ const SideButton = forwardRef(
|
|
|
36
37
|
|
|
37
38
|
const getCopy = useCopy({ dictionary, copy })
|
|
38
39
|
|
|
39
|
-
const { icon } = getTokens(tokens, buttonVariant)
|
|
40
|
+
const { icon, displayLabel } = getTokens(tokens, buttonVariant)
|
|
40
41
|
|
|
41
42
|
const getButtonTokens = (buttonState) => selectButtonTokens(getTokens(buttonState))
|
|
42
43
|
const getIconTokens = (buttonState) => selectIconTokens(getTokens(buttonState), direction)
|
|
43
44
|
|
|
44
45
|
const label = direction === 'previous' ? getCopy('previousText') : getCopy('nextText')
|
|
45
|
-
const showLabel = viewport !== 'sm' && viewport !== 'xs'
|
|
46
46
|
|
|
47
47
|
const accessibilityLabel =
|
|
48
48
|
direction === 'previous' ? getCopy('previousLabel') : getCopy('nextLabel')
|
|
@@ -69,7 +69,7 @@ const SideButton = forwardRef(
|
|
|
69
69
|
iconPosition={directionToSide[direction]}
|
|
70
70
|
iconProps={iconProps}
|
|
71
71
|
>
|
|
72
|
-
{
|
|
72
|
+
{displayLabel && <Text style={textStyles}>{label}</Text>}
|
|
73
73
|
</IconText>
|
|
74
74
|
)
|
|
75
75
|
}}
|