@telus-uds/components-base 1.19.0 → 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 +2 -2
- 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 +2 -2
- package/lib/Button/ButtonDropdown.js +207 -0
- package/lib/Button/ButtonGroup.js +1 -1
- package/lib/Carousel/Carousel.js +2 -4
- package/lib/Carousel/CarouselContext.js +1 -1
- package/lib/Carousel/CarouselFirstFocus/CarouselFirstFocus.js +1 -1
- 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/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/Search/Search.js +1 -1
- 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 +1 -1
- 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/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 +2 -2
- package/lib-module/Button/ButtonDropdown.js +181 -0
- package/lib-module/Button/ButtonGroup.js +1 -1
- package/lib-module/Carousel/Carousel.js +2 -4
- package/lib-module/Carousel/CarouselContext.js +1 -1
- package/lib-module/Carousel/CarouselFirstFocus/CarouselFirstFocus.js +1 -1
- 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/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/Search/Search.js +1 -1
- 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 +1 -1
- 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/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 +2 -2
|
@@ -122,7 +122,7 @@ const Search = /*#__PURE__*/forwardRef((_ref3, ref) => {
|
|
|
122
122
|
|
|
123
123
|
const a11yLabelText = accessibilityLabel || getCopy('accessibilityLabel'); // Placeholder is optional and may be unset by passing an empty string
|
|
124
124
|
|
|
125
|
-
const placeholderText = placeholder
|
|
125
|
+
const placeholderText = placeholder ?? a11yLabelText;
|
|
126
126
|
const {
|
|
127
127
|
nativeID,
|
|
128
128
|
testID,
|
|
@@ -63,7 +63,7 @@ const SideNav = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
|
63
63
|
|
|
64
64
|
const renderItem = (item, index, groupId) => {
|
|
65
65
|
const {
|
|
66
|
-
itemId =
|
|
66
|
+
itemId = `item-${index}`,
|
|
67
67
|
onPress
|
|
68
68
|
} = item.props;
|
|
69
69
|
|
|
@@ -91,7 +91,7 @@ const SideNav = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
|
91
91
|
|
|
92
92
|
if (child.type === ItemsGroup) {
|
|
93
93
|
const {
|
|
94
|
-
groupId =
|
|
94
|
+
groupId = `group-${index}`
|
|
95
95
|
} = child.props;
|
|
96
96
|
const isGroupActive = active.itemId !== undefined && active.groupId === groupId;
|
|
97
97
|
|
|
@@ -124,7 +124,7 @@ const Skeleton = /*#__PURE__*/forwardRef((_ref5, ref) => {
|
|
|
124
124
|
return /*#__PURE__*/_jsx(Component, {
|
|
125
125
|
testID: "skeleton",
|
|
126
126
|
style: [selectSkeletonStyles(themeTokens), getAnimationBasedOnPlatform(), getStyledBasedOnShape()]
|
|
127
|
-
},
|
|
127
|
+
}, `skeleton-${index + 1}`);
|
|
128
128
|
};
|
|
129
129
|
|
|
130
130
|
return /*#__PURE__*/_jsx(StackView, {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ANIMATION_DURATION, DEFAULT_OPACITY, OPACITY_STOP } from './skeleton.constant';
|
|
2
2
|
export default {
|
|
3
|
-
animationDuration:
|
|
3
|
+
animationDuration: `${ANIMATION_DURATION}ms`,
|
|
4
4
|
animationTimingFunction: 'ease-in-out',
|
|
5
5
|
animationDelay: '0.5s',
|
|
6
6
|
animationIterationCount: 'infinite',
|
|
@@ -24,14 +24,12 @@ const exampleGapValue = '1px';
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
const StackWrap = /*#__PURE__*/forwardRef((props, ref) => {
|
|
27
|
-
var _props$gap;
|
|
28
|
-
|
|
29
27
|
const [canUseCSSGap, setCanUseCSSGap] = useState(false);
|
|
30
28
|
const {
|
|
31
29
|
space
|
|
32
30
|
} = props; // Don't apply separate gap if `null` or `undefined`, so can be unset in Storybook etc
|
|
33
31
|
|
|
34
|
-
const gap =
|
|
32
|
+
const gap = props.gap ?? space;
|
|
35
33
|
const gapEqualsSpace = gap === space; // If possible, use the cleaner implementation that applies CSS `gap` styles to the container,
|
|
36
34
|
// preserving direct parent-child relationships between the container and each item, which
|
|
37
35
|
// can result in clearer descriptions on some screen readers (e.g. radio "X of Y" on MacOS).
|
|
@@ -48,7 +48,7 @@ const getStackedContent = (children, _ref) => {
|
|
|
48
48
|
const topLevelChildren = preserveFragments ? children : unpackFragment(children);
|
|
49
49
|
const validChildren = Children.toArray(topLevelChildren).filter(Boolean);
|
|
50
50
|
const content = validChildren.reduce((newChildren, child, index) => {
|
|
51
|
-
const boxID =
|
|
51
|
+
const boxID = `Stack-Box-${index}`;
|
|
52
52
|
const item = box ?
|
|
53
53
|
/*#__PURE__*/
|
|
54
54
|
// If wrapped in Box, that Box needs a key.
|
|
@@ -58,7 +58,7 @@ const getStackedContent = (children, _ref) => {
|
|
|
58
58
|
testID: boxID
|
|
59
59
|
}, child) : child;
|
|
60
60
|
if (!index || !space && !divider) return [...newChildren, item];
|
|
61
|
-
const testID =
|
|
61
|
+
const testID = `Stack-${divider ? 'Divider' : 'Spacer'}-${index}`;
|
|
62
62
|
const commonProps = {
|
|
63
63
|
testID,
|
|
64
64
|
key: testID,
|
package/lib-module/Tabs/Tabs.js
CHANGED
|
@@ -37,8 +37,6 @@ const getDefaultTabItemAccessibilityRole = parentRole => {
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
const Tabs = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
40
|
-
var _restProps$accessibil;
|
|
41
|
-
|
|
42
40
|
let {
|
|
43
41
|
tokens,
|
|
44
42
|
itemTokens,
|
|
@@ -76,7 +74,7 @@ const Tabs = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
|
76
74
|
if (hashId) setTimeout(setValue(hashId, event), 500);
|
|
77
75
|
}, [items, setValue]), isPositioningReady);
|
|
78
76
|
const restProps = selectProps(rest);
|
|
79
|
-
const parentAccessibilityRole =
|
|
77
|
+
const parentAccessibilityRole = restProps.accessibilityRole ?? 'tablist';
|
|
80
78
|
const defaultTabItemAccessibiltyRole = getDefaultTabItemAccessibilityRole(parentAccessibilityRole);
|
|
81
79
|
return /*#__PURE__*/_jsx(HorizontalScroll, {
|
|
82
80
|
ref: ref,
|
|
@@ -101,7 +99,7 @@ const Tabs = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
|
101
99
|
linkRouterProps: itemLinkRouterProps,
|
|
102
100
|
...itemRest
|
|
103
101
|
} = _ref3;
|
|
104
|
-
const itemId = id
|
|
102
|
+
const itemId = id ?? label;
|
|
105
103
|
const isSelected = Boolean(currentValue && currentValue === itemId);
|
|
106
104
|
|
|
107
105
|
const handlePress = event => {
|
package/lib-module/Tags/Tags.js
CHANGED
|
@@ -108,7 +108,7 @@ const Tags = /*#__PURE__*/forwardRef((_ref2, ref) => {
|
|
|
108
108
|
const uniqueFields = ['id', 'label'];
|
|
109
109
|
|
|
110
110
|
if (!containUniqueFields(items, uniqueFields)) {
|
|
111
|
-
throw new Error(
|
|
111
|
+
throw new Error(`Tags items must have unique ${uniqueFields.join(', ')}`);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
return /*#__PURE__*/_jsx(StackWrap, {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
en: {
|
|
3
|
+
clearButtonAccessibilityLabel: 'Clear',
|
|
4
|
+
showPasswordAccessibilityLabel: 'Show Password',
|
|
5
|
+
hidePasswordAccessibilityLabel: 'Hide Password'
|
|
6
|
+
},
|
|
7
|
+
fr: {
|
|
8
|
+
clearButtonAccessibilityLabel: 'Effacer',
|
|
9
|
+
showPasswordAccessibilityLabel: 'montrer le mot de passe',
|
|
10
|
+
hidePasswordAccessibilityLabel: 'masquer le mot de passe'
|
|
11
|
+
}
|
|
12
|
+
};
|
|
@@ -28,7 +28,7 @@ export function applyTextStyles(_ref) {
|
|
|
28
28
|
|
|
29
29
|
if (fontSize) {
|
|
30
30
|
// If relative font sizes are needed, catch and calculate them here
|
|
31
|
-
styles.fontSize = Platform.OS === 'web' && !forceAbsoluteFontSizing ?
|
|
31
|
+
styles.fontSize = Platform.OS === 'web' && !forceAbsoluteFontSizing ? `${fontSize / fontBasePixels}rem` : fontSize;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
if (typeof lineHeight === 'number') {
|
|
@@ -44,7 +44,7 @@ export function applyTextStyles(_ref) {
|
|
|
44
44
|
if (fontName) {
|
|
45
45
|
// Don't set undefined font families. May need some validation here that the font is available.
|
|
46
46
|
// Android doesn't recognise font weights natively so apply custom font weights via `fontFamily`.
|
|
47
|
-
styles.fontFamily =
|
|
47
|
+
styles.fontFamily = `${fontName}${fontWeight}${fontStyle}`;
|
|
48
48
|
} else if (fontWeight) {
|
|
49
49
|
// If using system default font, apply the font weight directly.
|
|
50
50
|
// Font weight support in Android is limited to 'bold' or anything else === 'normal'.
|
|
@@ -91,7 +91,7 @@ function applyWebShadow(_ref2) {
|
|
|
91
91
|
} = _ref2;
|
|
92
92
|
const insetString = inset ? 'inset ' : '';
|
|
93
93
|
const boxShadow = {
|
|
94
|
-
boxShadow:
|
|
94
|
+
boxShadow: `${insetString}${offsetX}px ${offsetY}px ${blur}px ${spread}px ${color}`
|
|
95
95
|
};
|
|
96
96
|
return boxShadow;
|
|
97
97
|
}
|
|
@@ -17,29 +17,27 @@ export const getComponentTheme = (theme, componentName) => {
|
|
|
17
17
|
// Give clear and understandable error messages for common dev errors, for example,
|
|
18
18
|
// typo in component name, missing export or accessing old version of theme
|
|
19
19
|
if (!theme) {
|
|
20
|
-
throw new Error(
|
|
20
|
+
throw new Error(`Called useTheme's getStyle on "${componentName}" with no theme provided`);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const themeName = ((_theme$metadata = theme.metadata) === null || _theme$metadata === void 0 ? void 0 : _theme$metadata.name) || '';
|
|
24
24
|
|
|
25
25
|
if (!theme.components) {
|
|
26
|
-
throw new Error(
|
|
26
|
+
throw new Error(`Theme "${themeName}" has no components defined (looking for "${componentName}")`);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const componentTheme = theme.components[componentName];
|
|
30
30
|
|
|
31
31
|
if (!componentTheme) {
|
|
32
|
-
throw new Error(
|
|
32
|
+
throw new Error(`Theme "${themeName}" does not have styles for component "${componentName}"`);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
return componentTheme;
|
|
36
36
|
};
|
|
37
37
|
export const doesThemeConditionApply = (_ref, appearances) => {
|
|
38
|
-
var _appearances$key;
|
|
39
|
-
|
|
40
38
|
let [key, value] = _ref;
|
|
41
39
|
// use null rather than undefined so we can serialise the value in themes
|
|
42
|
-
const appearanceValue =
|
|
40
|
+
const appearanceValue = appearances[key] ?? null;
|
|
43
41
|
return Array.isArray(value) ? value.includes(appearanceValue) : value === appearanceValue;
|
|
44
42
|
};
|
|
45
43
|
export const doesThemeRuleApply = (rule, appearances) => Object.entries(rule.if).every(condition => doesThemeConditionApply(condition, appearances));
|
|
@@ -158,6 +156,10 @@ export const validateThemeTokensVersion = theme => {
|
|
|
158
156
|
const actualThemeTokensVersion = theme === null || theme === void 0 ? void 0 : (_theme$metadata2 = theme.metadata) === null || _theme$metadata2 === void 0 ? void 0 : _theme$metadata2.themeTokensVersion;
|
|
159
157
|
|
|
160
158
|
if (!semVerSatisfies(actualThemeTokensVersion, expectedThemeTokensVersion)) {
|
|
161
|
-
throw new Error(
|
|
159
|
+
throw new Error(`Invalid UDS token schema version detected.
|
|
160
|
+
|
|
161
|
+
The UDS base components ${pkg.name} v${pkg.version} are only compatible with UDS themes that are built with @telus-uds/system-theme-tokens version that is semver compatible with ${expectedThemeTokensVersion}. The current theme was built with @telus-uds/system-theme-tokens v${actualThemeTokensVersion}.
|
|
162
|
+
|
|
163
|
+
If you see this error than most likely you have attempted to install ${pkg.name} and a UDS theme manually because you are building a multi-brand application. If you are building a single brand application, consider installing the brand specific design system package such as @telus-uds/ds-allium. For more information, see https://github.com/telus/universal-design-system/blob/main/docs/docs/multi-brand-usage.md`);
|
|
162
164
|
}
|
|
163
165
|
};
|
|
@@ -141,7 +141,7 @@ const Timeline = /*#__PURE__*/forwardRef((_ref7, ref) => {
|
|
|
141
141
|
style: selectItemContentStyles(themeTokens, index + 1 === children.length),
|
|
142
142
|
children: child
|
|
143
143
|
})]
|
|
144
|
-
},
|
|
144
|
+
}, `timeline-${index}-${child.displayName}`);
|
|
145
145
|
})
|
|
146
146
|
});
|
|
147
147
|
});
|
|
@@ -136,7 +136,7 @@ const ToggleSwitch = /*#__PURE__*/forwardRef((_ref7, ref) => {
|
|
|
136
136
|
const getButtonTokens = buttonState => selectButtonTokens(getTokens(buttonState));
|
|
137
137
|
|
|
138
138
|
const uniqueId = useUniqueId('toggleSwitch');
|
|
139
|
-
const inputId = id
|
|
139
|
+
const inputId = id ?? uniqueId;
|
|
140
140
|
return /*#__PURE__*/_jsxs(StackView, {
|
|
141
141
|
space: 2,
|
|
142
142
|
direction: "row",
|
|
@@ -63,7 +63,7 @@ const ToggleSwitchGroup = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
|
63
63
|
const uniqueFields = ['id', 'label'];
|
|
64
64
|
|
|
65
65
|
if (!containUniqueFields(items, uniqueFields)) {
|
|
66
|
-
throw new Error(
|
|
66
|
+
throw new Error(`ToggleSwitchGroup items must have unique ${uniqueFields.join(', ')}`);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
const toggleSwitches = items.map((_ref2, index) => {
|
|
@@ -8,7 +8,15 @@ function createPortalNode(nodeId) {
|
|
|
8
8
|
// this way the backdrop stays in place when scrolling the window - that's why we need to
|
|
9
9
|
// position it at the scroll position when rendering
|
|
10
10
|
|
|
11
|
-
node.style.cssText =
|
|
11
|
+
node.style.cssText = `
|
|
12
|
+
position: absolute;
|
|
13
|
+
top: ${window.scrollY}px;
|
|
14
|
+
left: ${window.scrollX}px;
|
|
15
|
+
right: 0;
|
|
16
|
+
bottom: 0;
|
|
17
|
+
z-index: 9999;
|
|
18
|
+
pointer-events: none;
|
|
19
|
+
`;
|
|
12
20
|
document.body.appendChild(node);
|
|
13
21
|
return node;
|
|
14
22
|
}
|
|
@@ -29,7 +37,7 @@ function Backdrop(_ref) {
|
|
|
29
37
|
} = _ref;
|
|
30
38
|
const [portalNode, setPortalNode] = useState();
|
|
31
39
|
useEffect(() => {
|
|
32
|
-
const nodeId =
|
|
40
|
+
const nodeId = `tooltip-backdrop-${Date.now()}`;
|
|
33
41
|
const node = createPortalNode(nodeId);
|
|
34
42
|
setPortalNode(node);
|
|
35
43
|
return () => {
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import React, { forwardRef, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import Dimensions from "react-native-web/dist/exports/Dimensions";
|
|
3
|
+
import Platform from "react-native-web/dist/exports/Platform";
|
|
4
|
+
import Pressable from "react-native-web/dist/exports/Pressable";
|
|
5
|
+
import StyleSheet from "react-native-web/dist/exports/StyleSheet";
|
|
6
|
+
import Text from "react-native-web/dist/exports/Text";
|
|
7
|
+
import View from "react-native-web/dist/exports/View";
|
|
8
|
+
import propTypes from './shared';
|
|
9
|
+
import { applyShadowToken, applyTextStyles, useThemeTokens } from '../ThemeProvider';
|
|
10
|
+
import { a11yProps, selectSystemProps, selectTokens, viewProps } from '../utils';
|
|
11
|
+
import Backdrop from './Backdrop';
|
|
12
|
+
import getTooltipPosition from './getTooltipPosition';
|
|
13
|
+
import TooltipButton from '../TooltipButton';
|
|
14
|
+
import useCopy from '../utils/useCopy';
|
|
15
|
+
import dictionary from './dictionary';
|
|
16
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
17
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
18
|
+
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
|
|
19
|
+
|
|
20
|
+
const selectTooltipStyles = _ref => {
|
|
21
|
+
let {
|
|
22
|
+
backgroundColor,
|
|
23
|
+
paddingTop,
|
|
24
|
+
paddingBottom,
|
|
25
|
+
paddingLeft,
|
|
26
|
+
paddingRight,
|
|
27
|
+
borderRadius
|
|
28
|
+
} = _ref;
|
|
29
|
+
return {
|
|
30
|
+
backgroundColor,
|
|
31
|
+
paddingTop,
|
|
32
|
+
paddingBottom,
|
|
33
|
+
paddingLeft,
|
|
34
|
+
paddingRight,
|
|
35
|
+
borderRadius
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const selectTooltipShadowStyles = _ref2 => {
|
|
40
|
+
let {
|
|
41
|
+
shadow,
|
|
42
|
+
borderRadius
|
|
43
|
+
} = _ref2;
|
|
44
|
+
return {
|
|
45
|
+
borderRadius,
|
|
46
|
+
...applyShadowToken(shadow)
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const selectTooltipPositionStyles = _ref3 => {
|
|
51
|
+
let {
|
|
52
|
+
top,
|
|
53
|
+
left,
|
|
54
|
+
width
|
|
55
|
+
} = _ref3;
|
|
56
|
+
return {
|
|
57
|
+
top,
|
|
58
|
+
left,
|
|
59
|
+
width
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const selectArrowStyles = (_ref4, _ref5) => {
|
|
64
|
+
let {
|
|
65
|
+
backgroundColor,
|
|
66
|
+
arrowWidth,
|
|
67
|
+
arrowBorderRadius,
|
|
68
|
+
shadow
|
|
69
|
+
} = _ref4;
|
|
70
|
+
let {
|
|
71
|
+
position,
|
|
72
|
+
width: tooltipWidth,
|
|
73
|
+
height: tooltipHeight
|
|
74
|
+
} = _ref5;
|
|
75
|
+
// the arrow width is actually a diagonal of the rectangle that we'll use as a tip
|
|
76
|
+
const rectangleSide = Math.sqrt(arrowWidth * arrowWidth / 2); // position the arrow at the side and center of the tooltip - this happens before rotation
|
|
77
|
+
// so we use the rectangle size as basis
|
|
78
|
+
|
|
79
|
+
const verticalOffset = -1 * rectangleSide / 2;
|
|
80
|
+
const horizontalOffset = rectangleSide / 2; // percentage-based absolute positioning doesn't act well on native, so we have to
|
|
81
|
+
// calculate the pixel values
|
|
82
|
+
|
|
83
|
+
const directionalStyles = {
|
|
84
|
+
above: {
|
|
85
|
+
bottom: verticalOffset,
|
|
86
|
+
left: tooltipWidth / 2 - horizontalOffset,
|
|
87
|
+
transform: [{
|
|
88
|
+
rotateZ: '45deg'
|
|
89
|
+
}]
|
|
90
|
+
},
|
|
91
|
+
below: {
|
|
92
|
+
top: verticalOffset,
|
|
93
|
+
left: tooltipWidth / 2 - horizontalOffset,
|
|
94
|
+
transform: [{
|
|
95
|
+
rotateZ: '-135deg'
|
|
96
|
+
}]
|
|
97
|
+
},
|
|
98
|
+
left: {
|
|
99
|
+
right: verticalOffset,
|
|
100
|
+
top: tooltipHeight / 2 - horizontalOffset,
|
|
101
|
+
transform: [{
|
|
102
|
+
rotateZ: '-45deg'
|
|
103
|
+
}]
|
|
104
|
+
},
|
|
105
|
+
right: {
|
|
106
|
+
left: verticalOffset,
|
|
107
|
+
top: tooltipHeight / 2 - horizontalOffset,
|
|
108
|
+
transform: [{
|
|
109
|
+
rotateZ: '135deg'
|
|
110
|
+
}]
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
backgroundColor,
|
|
115
|
+
width: rectangleSide,
|
|
116
|
+
height: rectangleSide,
|
|
117
|
+
borderBottomRightRadius: arrowBorderRadius,
|
|
118
|
+
// this corner will be the arrow tip after rotation
|
|
119
|
+
...applyShadowToken(shadow),
|
|
120
|
+
...directionalStyles[position]
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const selectTextStyles = tokens => applyTextStyles(selectTokens('Typography', tokens));
|
|
125
|
+
|
|
126
|
+
const defaultControl = (pressableState, variant) => /*#__PURE__*/_jsx(TooltipButton, {
|
|
127
|
+
pressableState: pressableState,
|
|
128
|
+
variant: variant
|
|
129
|
+
});
|
|
130
|
+
/**
|
|
131
|
+
* Tooltip provides a descriptive and detailed explanation or instructions. It can be used next to an input label
|
|
132
|
+
* to help a user fill it in, or as a standalone component.
|
|
133
|
+
*
|
|
134
|
+
* By default the TooltipButton component will be used as a control for triggering the tooltip, but you may attach
|
|
135
|
+
* a tooltip to any other component. A render function can be used to adjust the control's styling on state changes (hover, focus, etc.).
|
|
136
|
+
*
|
|
137
|
+
* ### Positioning
|
|
138
|
+
* By default a Tooltip will be automatically positioned in a way that ensures it fits within the viewport.
|
|
139
|
+
* You may suggest a position with a prop - it will be used, unless the tooltip would end up outside the viewport.
|
|
140
|
+
*
|
|
141
|
+
* ### Usage criteria
|
|
142
|
+
* - You may use one when the information is useful only to a small percentage of users (ie. tech savvy people wouldn't need this info).
|
|
143
|
+
* - Tooltips may also be useful when vertical space is an issue.
|
|
144
|
+
*/
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
const Tooltip = /*#__PURE__*/forwardRef((_ref6, ref) => {
|
|
148
|
+
let {
|
|
149
|
+
children,
|
|
150
|
+
content,
|
|
151
|
+
position = 'auto',
|
|
152
|
+
copy = 'en',
|
|
153
|
+
tokens,
|
|
154
|
+
variant,
|
|
155
|
+
...rest
|
|
156
|
+
} = _ref6;
|
|
157
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
158
|
+
const controlRef = useRef();
|
|
159
|
+
const [controlLayout, setControlLayout] = useState(null);
|
|
160
|
+
const [tooltipDimensions, setTooltipDimensions] = useState(null);
|
|
161
|
+
const [windowDimensions, setWindowDimensions] = useState(Dimensions.get('window'));
|
|
162
|
+
const [tooltipPosition, setTooltipPosition] = useState(null);
|
|
163
|
+
const getCopy = useCopy({
|
|
164
|
+
dictionary,
|
|
165
|
+
copy
|
|
166
|
+
});
|
|
167
|
+
const themeTokens = useThemeTokens('Tooltip', tokens, variant);
|
|
168
|
+
const {
|
|
169
|
+
arrowWidth,
|
|
170
|
+
arrowOffset
|
|
171
|
+
} = themeTokens;
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
const subscription = Dimensions.addEventListener('change', _ref7 => {
|
|
174
|
+
let {
|
|
175
|
+
window
|
|
176
|
+
} = _ref7;
|
|
177
|
+
setWindowDimensions(window);
|
|
178
|
+
});
|
|
179
|
+
return () => subscription === null || subscription === void 0 ? void 0 : subscription.remove();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const toggleIsOpen = () => setIsOpen(!isOpen);
|
|
183
|
+
|
|
184
|
+
const close = () => setIsOpen(false);
|
|
185
|
+
|
|
186
|
+
const getPressableState = _ref8 => {
|
|
187
|
+
let {
|
|
188
|
+
pressed,
|
|
189
|
+
hovered,
|
|
190
|
+
focused
|
|
191
|
+
} = _ref8;
|
|
192
|
+
return {
|
|
193
|
+
pressed,
|
|
194
|
+
hover: hovered,
|
|
195
|
+
focus: focused
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const onTooltipLayout = _ref9 => {
|
|
200
|
+
let {
|
|
201
|
+
nativeEvent: {
|
|
202
|
+
layout: {
|
|
203
|
+
width,
|
|
204
|
+
height
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
} = _ref9;
|
|
208
|
+
|
|
209
|
+
if (tooltipDimensions === null || tooltipDimensions.width !== width || tooltipDimensions.height !== height) {
|
|
210
|
+
setTooltipDimensions({
|
|
211
|
+
width: Platform.select({
|
|
212
|
+
web: width + 0.3,
|
|
213
|
+
// avoids often unnecessary line breaks due to subpixel rendering of fonts
|
|
214
|
+
native: width
|
|
215
|
+
}),
|
|
216
|
+
height
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
useEffect(() => {
|
|
222
|
+
if (isOpen) {
|
|
223
|
+
controlRef.current.measureInWindow((x, y, width, height) => {
|
|
224
|
+
setControlLayout({
|
|
225
|
+
x,
|
|
226
|
+
y,
|
|
227
|
+
width,
|
|
228
|
+
height
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
} else {
|
|
232
|
+
setControlLayout(null);
|
|
233
|
+
setTooltipDimensions(null);
|
|
234
|
+
setTooltipPosition(null);
|
|
235
|
+
}
|
|
236
|
+
}, [isOpen]);
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
setIsOpen(false);
|
|
239
|
+
}, [windowDimensions]);
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
if (tooltipPosition !== null && !(tooltipPosition !== null && tooltipPosition !== void 0 && tooltipPosition.isNormalized) || !isOpen || controlLayout === null || tooltipDimensions == null) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const updatedPosition = getTooltipPosition(position, {
|
|
246
|
+
controlLayout,
|
|
247
|
+
tooltipDimensions,
|
|
248
|
+
windowDimensions,
|
|
249
|
+
arrowWidth,
|
|
250
|
+
arrowOffset
|
|
251
|
+
}); // avoid ending up in an infinite normalization loop
|
|
252
|
+
|
|
253
|
+
if (tooltipPosition !== null && tooltipPosition !== void 0 && tooltipPosition.isNormalized && updatedPosition.isNormalized) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
setTooltipPosition(updatedPosition);
|
|
258
|
+
}, [isOpen, position, tooltipDimensions, controlLayout, windowDimensions, arrowWidth, arrowOffset, tooltipPosition]);
|
|
259
|
+
const control = children !== undefined ? children : defaultControl;
|
|
260
|
+
const pressableStyles = control === defaultControl ? Platform.select({
|
|
261
|
+
web: {
|
|
262
|
+
outline: 'none'
|
|
263
|
+
}
|
|
264
|
+
}) : undefined;
|
|
265
|
+
const pressableHitSlop = control === defaultControl ? {
|
|
266
|
+
top: 10,
|
|
267
|
+
bottom: 10,
|
|
268
|
+
left: 10,
|
|
269
|
+
right: 10
|
|
270
|
+
} : undefined;
|
|
271
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
272
|
+
style: staticStyles.container,
|
|
273
|
+
...selectProps(rest),
|
|
274
|
+
children: [/*#__PURE__*/_jsx(Pressable, {
|
|
275
|
+
onPress: toggleIsOpen,
|
|
276
|
+
ref: controlRef,
|
|
277
|
+
onBlur: close,
|
|
278
|
+
style: pressableStyles,
|
|
279
|
+
hitSlop: pressableHitSlop,
|
|
280
|
+
accessibilityLabel: getCopy('a11yText'),
|
|
281
|
+
accessibilityRole: "button",
|
|
282
|
+
children: typeof control === 'function' ? pressableState => control(getPressableState(pressableState), variant) : control
|
|
283
|
+
}), isOpen && /*#__PURE__*/_jsx(Backdrop, {
|
|
284
|
+
onPress: close,
|
|
285
|
+
children: /*#__PURE__*/_jsxs(View, {
|
|
286
|
+
ref: ref,
|
|
287
|
+
style: [staticStyles.tooltip, selectTooltipShadowStyles(themeTokens), // applied separately so that it doesn't cover the arrow
|
|
288
|
+
tooltipPosition && selectTooltipPositionStyles(tooltipPosition), (tooltipPosition === null || (tooltipPosition === null || tooltipPosition === void 0 ? void 0 : tooltipPosition.isNormalized)) && staticStyles.tooltipHidden // visually hide the tooltip until we have a final measurement
|
|
289
|
+
],
|
|
290
|
+
onLayout: onTooltipLayout,
|
|
291
|
+
accessibilityRole: "alert",
|
|
292
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
293
|
+
style: [staticStyles.arrow, tooltipPosition && selectArrowStyles(themeTokens, tooltipPosition)]
|
|
294
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
295
|
+
style: selectTooltipStyles(themeTokens),
|
|
296
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
297
|
+
style: selectTextStyles(themeTokens),
|
|
298
|
+
children: content
|
|
299
|
+
})
|
|
300
|
+
})]
|
|
301
|
+
})
|
|
302
|
+
})]
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
Tooltip.displayName = 'NativeTooltip';
|
|
306
|
+
Tooltip.propTypes = { ...selectedSystemPropTypes,
|
|
307
|
+
...propTypes
|
|
308
|
+
};
|
|
309
|
+
export default Tooltip;
|
|
310
|
+
const staticStyles = StyleSheet.create({
|
|
311
|
+
container: {
|
|
312
|
+
alignItems: 'flex-start'
|
|
313
|
+
},
|
|
314
|
+
tooltip: {
|
|
315
|
+
position: 'absolute',
|
|
316
|
+
maxWidth: 240,
|
|
317
|
+
top: 0,
|
|
318
|
+
left: 0
|
|
319
|
+
},
|
|
320
|
+
tooltipHidden: {
|
|
321
|
+
opacity: 0
|
|
322
|
+
},
|
|
323
|
+
arrow: {
|
|
324
|
+
position: 'absolute'
|
|
325
|
+
}
|
|
326
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import PropTypes from 'prop-types';
|
|
2
|
+
import { getTokensPropType, variantProp } from '../utils';
|
|
3
|
+
const propTypes = {
|
|
4
|
+
/**
|
|
5
|
+
* Used to render the control (i.e. tooltip trigger). If a render function is used it will receive the
|
|
6
|
+
* pressable state and tooltip variant as an argument.
|
|
7
|
+
*/
|
|
8
|
+
children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The message. Can be raw text or text components.
|
|
12
|
+
*/
|
|
13
|
+
content: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Select English or French copy for the accessible label.
|
|
17
|
+
*/
|
|
18
|
+
copy: PropTypes.oneOf(['en', 'fr']),
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Use to place the tooltip in a specific location (only if it fits within viewport).
|
|
22
|
+
*/
|
|
23
|
+
position: PropTypes.oneOf(['auto', 'above', 'right', 'below', 'left']),
|
|
24
|
+
tokens: getTokensPropType('Tooltip'),
|
|
25
|
+
variant: variantProp.propType
|
|
26
|
+
};
|
|
27
|
+
export default propTypes;
|