@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.
Files changed (134) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/lib/ActivityIndicator/Spinner.js +7 -7
  3. package/lib/ActivityIndicator/Spinner.native.js +2 -2
  4. package/lib/BaseProvider/HydrationContext.js +1 -1
  5. package/lib/BaseProvider/TamaguiProvider.js +30 -0
  6. package/lib/Button/ButtonBase.js +2 -2
  7. package/lib/Button/ButtonDropdown.js +207 -0
  8. package/lib/Button/ButtonGroup.js +1 -1
  9. package/lib/Carousel/Carousel.js +2 -4
  10. package/lib/Carousel/CarouselContext.js +1 -1
  11. package/lib/Carousel/CarouselFirstFocus/CarouselFirstFocus.js +1 -1
  12. package/lib/Carousel/CarouselThumbnail.js +2 -2
  13. package/lib/Checkbox/Checkbox.js +1 -1
  14. package/lib/Checkbox/CheckboxGroup.js +2 -2
  15. package/lib/Divider/Divider.js +2 -2
  16. package/lib/FlexGrid/Col/Col.js +1 -1
  17. package/lib/Icon/Icon.js +1 -1
  18. package/lib/MultiSelectFilter/ModalOverlay.js +136 -0
  19. package/lib/MultiSelectFilter/MultiSelectFilter.js +314 -0
  20. package/lib/MultiSelectFilter/dictionary.js +19 -0
  21. package/lib/MultiSelectFilter/index.js +13 -0
  22. package/lib/Pagination/PageButton.js +2 -2
  23. package/lib/Pagination/Pagination.js +3 -5
  24. package/lib/Pagination/usePagination.js +2 -2
  25. package/lib/Progress/ProgressBar.js +3 -3
  26. package/lib/Progress/ProgressBarBackground.js +3 -3
  27. package/lib/QuickLinksFeature/QuickLinksFeature.js +91 -0
  28. package/lib/QuickLinksFeature/QuickLinksFeatureItem.js +157 -0
  29. package/lib/QuickLinksFeature/index.js +16 -0
  30. package/lib/Radio/Radio.js +2 -2
  31. package/lib/Radio/RadioGroup.js +2 -2
  32. package/lib/RadioCard/RadioCard.js +1 -1
  33. package/lib/RadioCard/RadioCardGroup.js +2 -2
  34. package/lib/Search/Search.js +1 -1
  35. package/lib/Select/constants.js +15 -0
  36. package/lib/SideNav/SideNav.js +2 -2
  37. package/lib/Skeleton/Skeleton.js +1 -1
  38. package/lib/Skeleton/skeletonWebAnimation.js +1 -1
  39. package/lib/StackView/StackWrap.js +1 -3
  40. package/lib/StackView/getStackedContent.js +2 -2
  41. package/lib/Tabs/Tabs.js +2 -4
  42. package/lib/Tags/Tags.js +1 -1
  43. package/lib/TextInput/dictionary.js +19 -0
  44. package/lib/ThemeProvider/utils/styles.js +3 -3
  45. package/lib/ThemeProvider/utils/theme-tokens.js +9 -7
  46. package/lib/Timeline/Timeline.js +1 -1
  47. package/lib/ToggleSwitch/ToggleSwitch.js +1 -1
  48. package/lib/ToggleSwitch/ToggleSwitchGroup.js +1 -1
  49. package/lib/Tooltip/Backdrop.js +10 -2
  50. package/lib/Tooltip/Tooltip.native.js +357 -0
  51. package/lib/Tooltip/shared.js +39 -0
  52. package/lib/Validator/Validator.js +271 -0
  53. package/lib/Validator/index.js +13 -0
  54. package/lib/utils/BaseView/BaseView.js +64 -0
  55. package/lib/utils/BaseView/BaseView.native.js +16 -0
  56. package/lib/utils/BaseView/index.js +13 -0
  57. package/lib/utils/animation/useVerticalExpandAnimation.js +1 -1
  58. package/lib/utils/children.js +2 -2
  59. package/lib/utils/floating-ui/index.js +43 -0
  60. package/lib/utils/floating-ui/index.native.js +43 -0
  61. package/lib/utils/input.js +12 -6
  62. package/lib/utils/props/componentPropType.js +3 -3
  63. package/lib/utils/props/selectSystemProps.js +2 -2
  64. package/lib/utils/props/tokens.js +2 -2
  65. package/lib/utils/useOverlaidPosition.js +243 -0
  66. package/lib/utils/useSpacingScale.js +1 -3
  67. package/lib/utils/useUniqueId.js +1 -1
  68. package/lib-module/ActivityIndicator/Spinner.js +7 -7
  69. package/lib-module/ActivityIndicator/Spinner.native.js +2 -2
  70. package/lib-module/BaseProvider/HydrationContext.js +1 -1
  71. package/lib-module/BaseProvider/TamaguiProvider.js +22 -0
  72. package/lib-module/Button/ButtonBase.js +2 -2
  73. package/lib-module/Button/ButtonDropdown.js +181 -0
  74. package/lib-module/Button/ButtonGroup.js +1 -1
  75. package/lib-module/Carousel/Carousel.js +2 -4
  76. package/lib-module/Carousel/CarouselContext.js +1 -1
  77. package/lib-module/Carousel/CarouselFirstFocus/CarouselFirstFocus.js +1 -1
  78. package/lib-module/Carousel/CarouselThumbnail.js +2 -2
  79. package/lib-module/Checkbox/Checkbox.js +1 -1
  80. package/lib-module/Checkbox/CheckboxGroup.js +2 -2
  81. package/lib-module/Divider/Divider.js +2 -2
  82. package/lib-module/FlexGrid/Col/Col.js +1 -1
  83. package/lib-module/Icon/Icon.js +1 -1
  84. package/lib-module/MultiSelectFilter/ModalOverlay.js +112 -0
  85. package/lib-module/MultiSelectFilter/MultiSelectFilter.js +286 -0
  86. package/lib-module/MultiSelectFilter/dictionary.js +12 -0
  87. package/lib-module/MultiSelectFilter/index.js +2 -0
  88. package/lib-module/Pagination/PageButton.js +2 -2
  89. package/lib-module/Pagination/Pagination.js +3 -5
  90. package/lib-module/Pagination/usePagination.js +2 -2
  91. package/lib-module/Progress/ProgressBar.js +3 -3
  92. package/lib-module/Progress/ProgressBarBackground.js +3 -3
  93. package/lib-module/QuickLinksFeature/QuickLinksFeature.js +69 -0
  94. package/lib-module/QuickLinksFeature/QuickLinksFeatureItem.js +130 -0
  95. package/lib-module/QuickLinksFeature/index.js +4 -0
  96. package/lib-module/Radio/Radio.js +2 -2
  97. package/lib-module/Radio/RadioGroup.js +2 -2
  98. package/lib-module/RadioCard/RadioCard.js +1 -1
  99. package/lib-module/RadioCard/RadioCardGroup.js +2 -2
  100. package/lib-module/Search/Search.js +1 -1
  101. package/lib-module/Select/constants.js +5 -0
  102. package/lib-module/SideNav/SideNav.js +2 -2
  103. package/lib-module/Skeleton/Skeleton.js +1 -1
  104. package/lib-module/Skeleton/skeletonWebAnimation.js +1 -1
  105. package/lib-module/StackView/StackWrap.js +1 -3
  106. package/lib-module/StackView/getStackedContent.js +2 -2
  107. package/lib-module/Tabs/Tabs.js +2 -4
  108. package/lib-module/Tags/Tags.js +1 -1
  109. package/lib-module/TextInput/dictionary.js +12 -0
  110. package/lib-module/ThemeProvider/utils/styles.js +3 -3
  111. package/lib-module/ThemeProvider/utils/theme-tokens.js +9 -7
  112. package/lib-module/Timeline/Timeline.js +1 -1
  113. package/lib-module/ToggleSwitch/ToggleSwitch.js +1 -1
  114. package/lib-module/ToggleSwitch/ToggleSwitchGroup.js +1 -1
  115. package/lib-module/Tooltip/Backdrop.js +10 -2
  116. package/lib-module/Tooltip/Tooltip.native.js +326 -0
  117. package/lib-module/Tooltip/shared.js +27 -0
  118. package/lib-module/Validator/Validator.js +245 -0
  119. package/lib-module/Validator/index.js +2 -0
  120. package/lib-module/utils/BaseView/BaseView.js +43 -0
  121. package/lib-module/utils/BaseView/BaseView.native.js +6 -0
  122. package/lib-module/utils/BaseView/index.js +2 -0
  123. package/lib-module/utils/animation/useVerticalExpandAnimation.js +1 -1
  124. package/lib-module/utils/children.js +2 -2
  125. package/lib-module/utils/floating-ui/index.js +1 -0
  126. package/lib-module/utils/floating-ui/index.native.js +1 -0
  127. package/lib-module/utils/input.js +12 -6
  128. package/lib-module/utils/props/componentPropType.js +3 -3
  129. package/lib-module/utils/props/selectSystemProps.js +2 -2
  130. package/lib-module/utils/props/tokens.js +2 -2
  131. package/lib-module/utils/useOverlaidPosition.js +232 -0
  132. package/lib-module/utils/useSpacingScale.js +1 -3
  133. package/lib-module/utils/useUniqueId.js +1 -1
  134. 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 !== null && placeholder !== void 0 ? placeholder : a11yLabelText;
125
+ const placeholderText = placeholder ?? a11yLabelText;
126
126
  const {
127
127
  nativeID,
128
128
  testID,
@@ -0,0 +1,5 @@
1
+ // Because Android
2
+ export const ANDROID_VALIDATION_ICON_CONTAINER_OFFSET = 5;
3
+ export const ANDROID_HEIGHT_OFFSET = 12;
4
+ export const ANDROID_HORIZONTAL_PADDING_OFFSET = 8;
5
+ export const ANDROID_DEFAULT_PADDING = 0;
@@ -63,7 +63,7 @@ const SideNav = /*#__PURE__*/forwardRef((_ref, ref) => {
63
63
 
64
64
  const renderItem = (item, index, groupId) => {
65
65
  const {
66
- itemId = "item-".concat(index),
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 = "group-".concat(index)
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
- }, "skeleton-".concat(index + 1));
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: "".concat(ANIMATION_DURATION, "ms"),
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 = (_props$gap = props.gap) !== null && _props$gap !== void 0 ? _props$gap : space;
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 = "Stack-Box-".concat(index);
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 = "Stack-".concat(divider ? 'Divider' : 'Spacer', "-").concat(index);
61
+ const testID = `Stack-${divider ? 'Divider' : 'Spacer'}-${index}`;
62
62
  const commonProps = {
63
63
  testID,
64
64
  key: testID,
@@ -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 = (_restProps$accessibil = restProps.accessibilityRole) !== null && _restProps$accessibil !== void 0 ? _restProps$accessibil : 'tablist';
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 !== null && id !== void 0 ? id : label;
102
+ const itemId = id ?? label;
105
103
  const isSelected = Boolean(currentValue && currentValue === itemId);
106
104
 
107
105
  const handlePress = event => {
@@ -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("Tags items must have unique ".concat(uniqueFields.join(', ')));
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 ? "".concat(fontSize / fontBasePixels, "rem") : fontSize;
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 = "".concat(fontName).concat(fontWeight).concat(fontStyle);
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: "".concat(insetString).concat(offsetX, "px ").concat(offsetY, "px ").concat(blur, "px ").concat(spread, "px ").concat(color)
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("Called useTheme's getStyle on \"".concat(componentName, "\" with no theme provided"));
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("Theme \"".concat(themeName, "\" has no components defined (looking for \"").concat(componentName, "\")"));
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("Theme \"".concat(themeName, "\" does not have styles for component \"").concat(componentName, "\""));
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 = (_appearances$key = appearances[key]) !== null && _appearances$key !== void 0 ? _appearances$key : null;
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("Invalid UDS token schema version detected.\n\nThe UDS base components ".concat(pkg.name, " v").concat(pkg.version, " are only compatible with UDS themes that are built with @telus-uds/system-theme-tokens version that is semver compatible with ").concat(expectedThemeTokensVersion, ". The current theme was built with @telus-uds/system-theme-tokens v").concat(actualThemeTokensVersion, ".\n\nIf you see this error than most likely you have attempted to install ").concat(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"));
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
- }, "timeline-".concat(index, "-").concat(child.displayName));
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 !== null && id !== void 0 ? id : uniqueId;
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("ToggleSwitchGroup items must have unique ".concat(uniqueFields.join(', ')));
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 = "\n position: absolute; \n top: ".concat(window.scrollY, "px;\n left: ").concat(window.scrollX, "px; \n right: 0; \n bottom: 0; \n z-index: 9999; \n pointer-events: none;\n ");
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 = "tooltip-backdrop-".concat(Date.now());
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;