@telus-uds/components-base 1.94.0 → 1.96.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +41 -2
  2. package/lib/Autocomplete/Autocomplete.js +38 -3
  3. package/lib/Card/CardBase.js +4 -0
  4. package/lib/DownloadApp/DownloadApp.js +168 -0
  5. package/lib/DownloadApp/dictionary.js +17 -0
  6. package/lib/DownloadApp/index.js +10 -0
  7. package/lib/Icon/IconText.js +19 -2
  8. package/lib/Link/LinkBase.js +2 -2
  9. package/lib/List/ListItemBase.js +2 -1
  10. package/lib/Modal/Modal.js +34 -13
  11. package/lib/Modal/ModalContent.js +1 -1
  12. package/lib/Modal/WebModal.js +1 -0
  13. package/lib/Notification/Notification.js +5 -5
  14. package/lib/PriceLockup/PriceLockup.js +11 -3
  15. package/lib/Progress/Progress.js +5 -2
  16. package/lib/Progress/ProgressBar.js +4 -1
  17. package/lib/TabBar/TabBar.js +133 -0
  18. package/lib/TabBar/TabBarItem.js +184 -0
  19. package/lib/TabBar/index.js +10 -0
  20. package/lib/TextInput/TextInputBase.js +2 -1
  21. package/lib/Tooltip/getTooltipPosition.js +8 -9
  22. package/lib/Typography/Typography.js +42 -19
  23. package/lib/index.js +23 -0
  24. package/lib-module/Autocomplete/Autocomplete.js +38 -3
  25. package/lib-module/Card/CardBase.js +4 -0
  26. package/lib-module/DownloadApp/DownloadApp.js +160 -0
  27. package/lib-module/DownloadApp/dictionary.js +10 -0
  28. package/lib-module/DownloadApp/index.js +2 -0
  29. package/lib-module/Icon/IconText.js +19 -2
  30. package/lib-module/Link/LinkBase.js +2 -2
  31. package/lib-module/List/ListItemBase.js +2 -1
  32. package/lib-module/Modal/Modal.js +34 -13
  33. package/lib-module/Modal/ModalContent.js +1 -1
  34. package/lib-module/Modal/WebModal.js +1 -0
  35. package/lib-module/Notification/Notification.js +5 -5
  36. package/lib-module/PriceLockup/PriceLockup.js +11 -3
  37. package/lib-module/Progress/Progress.js +6 -3
  38. package/lib-module/Progress/ProgressBar.js +5 -2
  39. package/lib-module/TabBar/TabBar.js +125 -0
  40. package/lib-module/TabBar/TabBarItem.js +177 -0
  41. package/lib-module/TabBar/index.js +2 -0
  42. package/lib-module/TextInput/TextInputBase.js +2 -1
  43. package/lib-module/Tooltip/getTooltipPosition.js +8 -9
  44. package/lib-module/Typography/Typography.js +42 -19
  45. package/lib-module/index.js +3 -1
  46. package/package.json +2 -2
  47. package/src/Autocomplete/Autocomplete.jsx +43 -3
  48. package/src/Card/CardBase.jsx +6 -0
  49. package/src/DownloadApp/DownloadApp.jsx +165 -0
  50. package/src/DownloadApp/dictionary.js +10 -0
  51. package/src/DownloadApp/index.js +3 -0
  52. package/src/Icon/IconText.jsx +21 -4
  53. package/src/Link/LinkBase.jsx +2 -2
  54. package/src/List/ListItemBase.jsx +1 -1
  55. package/src/Modal/Modal.jsx +40 -14
  56. package/src/Modal/ModalContent.jsx +1 -1
  57. package/src/Modal/WebModal.jsx +1 -1
  58. package/src/Notification/Notification.jsx +5 -5
  59. package/src/PriceLockup/PriceLockup.jsx +9 -1
  60. package/src/Progress/Progress.jsx +6 -3
  61. package/src/Progress/ProgressBar.jsx +4 -2
  62. package/src/TabBar/TabBar.jsx +123 -0
  63. package/src/TabBar/TabBarItem.jsx +149 -0
  64. package/src/TabBar/index.js +3 -0
  65. package/src/TextInput/TextInputBase.jsx +1 -1
  66. package/src/Tooltip/getTooltipPosition.js +11 -12
  67. package/src/Typography/Typography.jsx +37 -13
  68. package/src/index.js +4 -1
@@ -0,0 +1,177 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import Pressable from "react-native-web/dist/exports/Pressable";
4
+ import View from "react-native-web/dist/exports/View";
5
+ import StyleSheet from "react-native-web/dist/exports/StyleSheet";
6
+ import { getTokensPropType, resolvePressableTokens, variantProp } from '../utils';
7
+ import { useThemeTokensCallback } from '../ThemeProvider';
8
+ import Typography from '../Typography';
9
+ import Spacer from '../Spacer';
10
+ import { jsx as _jsx } from "react/jsx-runtime";
11
+ import { jsxs as _jsxs } from "react/jsx-runtime";
12
+ const selectTypographyStyles = (_ref, isSelected) => {
13
+ let {
14
+ fontWeight,
15
+ color,
16
+ lineHeight,
17
+ fontName,
18
+ activeColor
19
+ } = _ref;
20
+ return {
21
+ fontWeight,
22
+ color: isSelected ? activeColor : color,
23
+ lineHeight,
24
+ fontName
25
+ };
26
+ };
27
+ const selectIconContainerStyles = _ref2 => {
28
+ let {
29
+ paddingTop,
30
+ paddingBottom
31
+ } = _ref2;
32
+ return {
33
+ paddingTop,
34
+ paddingBottom,
35
+ alignItems: 'center'
36
+ };
37
+ };
38
+ const selectIconStyles = (_ref3, isSelected) => {
39
+ let {
40
+ iconSize,
41
+ iconColor,
42
+ activeColor
43
+ } = _ref3;
44
+ return {
45
+ size: iconSize,
46
+ color: isSelected ? activeColor : iconColor
47
+ };
48
+ };
49
+ const selectContainerStyles = _ref4 => {
50
+ let {
51
+ borderRadius,
52
+ backgroundColor
53
+ } = _ref4;
54
+ return {
55
+ borderRadius,
56
+ backgroundColor,
57
+ alignSelf: 'center',
58
+ alignItems: 'center'
59
+ };
60
+ };
61
+ const TabBarItem = /*#__PURE__*/React.forwardRef((_ref5, ref) => {
62
+ let {
63
+ href,
64
+ variant,
65
+ tokens,
66
+ label,
67
+ isSelected,
68
+ id,
69
+ onPress,
70
+ icon: IconComponent,
71
+ iconActive: IconActiveComponent,
72
+ accessibilityRole = 'tab'
73
+ } = _ref5;
74
+ const getTokens = useThemeTokensCallback('TabBarItem', tokens, variant);
75
+ const getPressableStyle = _ref6 => {
76
+ let {
77
+ pressed,
78
+ focused,
79
+ hovered
80
+ } = _ref6;
81
+ const resolvedTokens = resolvePressableTokens(getTokens, {
82
+ pressed,
83
+ focused,
84
+ hovered
85
+ }, {
86
+ isSelected
87
+ });
88
+ return {
89
+ ...resolvedTokens,
90
+ outline: 'none' // removes the default browser :focus outline
91
+ };
92
+ };
93
+
94
+ return /*#__PURE__*/_jsx(Pressable, {
95
+ ref: ref,
96
+ href: href,
97
+ nativeID: id,
98
+ onPress: onPress,
99
+ style: _ref7 => {
100
+ let {
101
+ pressed,
102
+ focused,
103
+ hovered
104
+ } = _ref7;
105
+ return [styles.flexContainer, getPressableStyle({
106
+ pressed,
107
+ focused,
108
+ hovered
109
+ })];
110
+ },
111
+ accessibilityRole: accessibilityRole,
112
+ testID: id,
113
+ children: pressableState => {
114
+ const resolvedTokens = getPressableStyle(pressableState);
115
+ return /*#__PURE__*/_jsxs(View, {
116
+ style: selectContainerStyles(resolvedTokens),
117
+ children: [/*#__PURE__*/_jsx(View, {
118
+ style: [selectIconContainerStyles(resolvedTokens), styles.iconContainer],
119
+ children: isSelected ? IconActiveComponent && /*#__PURE__*/_jsx(IconActiveComponent, {
120
+ ...selectIconStyles(resolvedTokens, isSelected)
121
+ }) : IconComponent && /*#__PURE__*/_jsx(IconComponent, {
122
+ ...selectIconStyles(resolvedTokens)
123
+ })
124
+ }), /*#__PURE__*/_jsx(Spacer, {
125
+ space: 1
126
+ }), /*#__PURE__*/_jsx(Typography, {
127
+ variant: {
128
+ size: 'micro'
129
+ },
130
+ tokens: selectTypographyStyles(resolvedTokens, isSelected),
131
+ align: "center",
132
+ children: label
133
+ }), /*#__PURE__*/_jsx(Spacer, {
134
+ space: 1
135
+ })]
136
+ });
137
+ }
138
+ });
139
+ });
140
+ TabBarItem.displayName = 'TabBarItem';
141
+ TabBarItem.propTypes = {
142
+ /** The icon to be displayed when the item is not selected. */
143
+ icon: PropTypes.elementType,
144
+ /** The icon to be displayed when the item is selected. */
145
+ iconActive: PropTypes.elementType,
146
+ /** Tokens for theming and styling the TabBarItem. */
147
+ tokens: getTokensPropType('TabBarItem'),
148
+ /** Variant of the TabBarItem for styling purposes. */
149
+ variant: variantProp.propType,
150
+ /** Callback function to handle press events. */
151
+ onPress: PropTypes.func,
152
+ /** URL to navigate to when the item is pressed. */
153
+ href: PropTypes.string,
154
+ /** Indicates whether the item is selected. */
155
+ isSelected: PropTypes.bool,
156
+ /** Unique identifier for the item. */
157
+ id: PropTypes.string,
158
+ /** Label text for the item. */
159
+ label: PropTypes.string.isRequired,
160
+ /** Accessibility role for the item. */
161
+ accessibilityRole: PropTypes.string
162
+ };
163
+ const styles = StyleSheet.create({
164
+ flexContainer: {
165
+ flex: 1,
166
+ alignItems: 'center'
167
+ },
168
+ iconContainer: {
169
+ flex: 1,
170
+ justifyContent: 'center',
171
+ alignItems: 'center',
172
+ minWidth: 44,
173
+ minHeight: 40,
174
+ aspectRatio: 1.1
175
+ }
176
+ });
177
+ export default TabBarItem;
@@ -0,0 +1,2 @@
1
+ import TabBar from './TabBar';
2
+ export default TabBar;
@@ -42,7 +42,8 @@ const selectInputStyles = function (_ref, themeOptions, inactive, type) {
42
42
  // jump around if the border width changes (avoiding NaN and negative padding)
43
43
  const offsetBorder = value => typeof value === 'number' ? Math.max(0, value - borderWidth) : value;
44
44
  const textStyles = applyTextStyles({
45
- fontName,
45
+ fontName: isPassword ? undefined : fontName,
46
+ // In this case, we don't want to apply the fontName to the input if it's a password because Monotype don't have support for the masked characters in mobile.
46
47
  fontSize,
47
48
  lineHeight,
48
49
  fontWeight,
@@ -1,4 +1,4 @@
1
- function normalizePosition(position) {
1
+ function getAbsolutePosition(position) {
2
2
  const {
3
3
  left,
4
4
  right,
@@ -9,7 +9,7 @@ function normalizePosition(position) {
9
9
  } = position;
10
10
 
11
11
  // adjust the coordinates so that it fits within the window
12
- const normalized = {
12
+ const finalPosition = {
13
13
  left: Math.max(0, left),
14
14
  right: Math.max(0, right),
15
15
  top: Math.max(0, top),
@@ -19,15 +19,14 @@ function normalizePosition(position) {
19
19
  const getAbsoluteDiff = (value1, value2) => Math.abs(Math.abs(value1) - Math.abs(value2));
20
20
 
21
21
  // adjust the width by whatever has been subtracted from left or right
22
- normalized.width = width - Math.abs(getAbsoluteDiff(left, normalized.left) - getAbsoluteDiff(right, normalized.right));
23
- if (normalized.top !== top) {
24
- normalized.bottom += normalized.top - top;
22
+ finalPosition.width = width - Math.abs(getAbsoluteDiff(left, finalPosition.left) - getAbsoluteDiff(right, finalPosition.right));
23
+ if (finalPosition.top !== top) {
24
+ finalPosition.bottom += finalPosition.top - top;
25
25
  }
26
- const isNormalized = normalized.right !== right || normalized.left !== left || normalized.top !== top;
27
26
  return {
28
- ...normalized,
27
+ ...finalPosition,
29
28
  ...rest,
30
- isNormalized
29
+ isNormalized: false
31
30
  };
32
31
  }
33
32
  function invertPosition(position) {
@@ -166,6 +165,6 @@ function getTooltipPosition(position, _ref2) {
166
165
 
167
166
  // prefer 'below' over 'above', since we can always expand the document downwards,
168
167
  // and 'above' might cause issues on small viewports with large tooltips
169
- return normalizePosition(leastOverflowing.position === 'above' ? findRectByPosition('below', boundingRects) : leastOverflowing);
168
+ return getAbsolutePosition(leastOverflowing.position === 'above' ? findRectByPosition('below', boundingRects) : leastOverflowing);
170
169
  }
171
170
  export default getTooltipPosition;
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import Text from "react-native-web/dist/exports/Text";
4
4
  import View from "react-native-web/dist/exports/View";
5
+ import Platform from "react-native-web/dist/exports/Platform";
5
6
  import { useResponsiveThemeTokens, useTheme, useThemeTokens } from '../ThemeProvider';
6
7
  import { applyTextStyles } from '../ThemeProvider/utils';
7
8
  import { a11yProps, variantProp, getTokensPropType, getMaxFontMultiplier, headingTags, selectSystemProps, textTags, textProps, viewProps, getA11yPropsFromHtmlTag, StyleSheet, createMediaQueryStyles } from '../utils';
@@ -39,9 +40,22 @@ const selectTextStyles = (_ref, themeOptions) => {
39
40
  gradient
40
41
  });
41
42
  };
43
+ const HALF_FONT_SIZE = 2;
44
+ const QUARTER_FONT_SIZE = 4;
45
+ const selectMobileSubSupStyles = (_ref2, type) => {
46
+ let {
47
+ fontSize
48
+ } = _ref2;
49
+ return {
50
+ fontSize: fontSize / HALF_FONT_SIZE,
51
+ lineHeight: fontSize,
52
+ position: 'relative',
53
+ top: type === 'sub' ? fontSize / QUARTER_FONT_SIZE : -fontSize / QUARTER_FONT_SIZE
54
+ };
55
+ };
42
56
 
43
57
  // General-purpose flexible theme-neutral base component for text
44
- const Typography = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
58
+ const Typography = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
45
59
  let {
46
60
  children,
47
61
  variant,
@@ -54,7 +68,7 @@ const Typography = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
54
68
  dataSet,
55
69
  strikeThrough = false,
56
70
  ...rest
57
- } = _ref2;
71
+ } = _ref3;
58
72
  const viewport = useViewport();
59
73
  const {
60
74
  themeOptions
@@ -101,8 +115,8 @@ const Typography = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
101
115
  let textStyles;
102
116
  let mediaIds;
103
117
  if (enableMediaQueryStyleSheet) {
104
- const transformedThemeTokens = Object.entries(themeTokens).reduce((acc, _ref3) => {
105
- let [vp, viewportTokens] = _ref3;
118
+ const transformedThemeTokens = Object.entries(themeTokens).reduce((acc, _ref4) => {
119
+ let [vp, viewportTokens] = _ref4;
106
120
  acc[vp] = selectTextStyles({
107
121
  textAlign: align,
108
122
  textDecorationLine,
@@ -136,22 +150,31 @@ const Typography = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
136
150
  ...selectContainerProps(rest)
137
151
  };
138
152
  const resetTagStyling = child => {
139
- if (typeof child === 'object' && ((child === null || child === void 0 ? void 0 : child.type) === 'sub' || (child === null || child === void 0 ? void 0 : child.type) === 'sup')) {
140
- var _child$props;
141
- const childStyles = (child === null || child === void 0 ? void 0 : (_child$props = child.props) === null || _child$props === void 0 ? void 0 : _child$props.style) || {};
142
- const supFontSize = childStyles.fontSize ?? themeTokens.superScriptFontSize;
143
- const sanitizedChild = /*#__PURE__*/React.cloneElement(child, {
144
- style: {
145
- ...childStyles,
146
- ...(supFontSize ? {
147
- fontSize: supFontSize
148
- } : {}),
149
- lineHeight: 0
150
- }
151
- });
152
- return sanitizedChild;
153
+ var _child$props;
154
+ if (typeof child !== 'object' || !((child === null || child === void 0 ? void 0 : child.type) === 'sub' || (child === null || child === void 0 ? void 0 : child.type) === 'sup')) {
155
+ return child;
153
156
  }
154
- return child;
157
+ const childStyles = (child === null || child === void 0 ? void 0 : (_child$props = child.props) === null || _child$props === void 0 ? void 0 : _child$props.style) || {};
158
+ const supFontSize = childStyles.fontSize ?? themeTokens.superScriptFontSize;
159
+ const isMobile = Platform.OS === 'ios' || Platform.OS === 'android';
160
+ const isSubSup = (child === null || child === void 0 ? void 0 : child.type) === 'sub' || (child === null || child === void 0 ? void 0 : child.type) === 'sup';
161
+ const mobileStyles = isMobile && isSubSup ? selectMobileSubSupStyles(themeTokens, child === null || child === void 0 ? void 0 : child.type) : {};
162
+ const defaultStyles = !isMobile && isSubSup ? {
163
+ fontSize: supFontSize,
164
+ lineHeight: 0
165
+ } : {};
166
+ const sanitizedChild = /*#__PURE__*/React.cloneElement(isMobile && isSubSup ? /*#__PURE__*/_jsx(View, {
167
+ children: /*#__PURE__*/_jsx(Text, {
168
+ style: [childStyles, mobileStyles],
169
+ children: child.props.children
170
+ })
171
+ }) : child, {
172
+ style: {
173
+ ...childStyles,
174
+ ...defaultStyles
175
+ }
176
+ });
177
+ return sanitizedChild;
155
178
  };
156
179
  const sanitizeChildren = () => {
157
180
  if (Array.isArray(children)) {
@@ -15,6 +15,7 @@ export * from './Checkbox';
15
15
  export { default as CheckboxCard } from './CheckboxCard';
16
16
  export { default as CheckboxCardGroup } from './CheckboxCardGroup';
17
17
  export { default as ColourToggle } from './ColourToggle';
18
+ export { default as DownloadApp } from './DownloadApp';
18
19
  export { default as Divider } from './Divider';
19
20
  export { default as ExpandCollapse, Accordion } from './ExpandCollapse';
20
21
  export { default as Feedback } from './Feedback';
@@ -57,6 +58,7 @@ export * from './StackView';
57
58
  export { default as Status } from './Status';
58
59
  export { default as StepTracker } from './StepTracker';
59
60
  export { default as Tabs } from './Tabs';
61
+ export { default as TabBar } from './TabBar';
60
62
  export { default as Tags } from './Tags';
61
63
  export * from './TextInput';
62
64
  export { default as Timeline } from './Timeline';
@@ -69,6 +71,6 @@ export { default as BaseProvider } from './BaseProvider';
69
71
  export { useHydrationContext } from './BaseProvider/HydrationContext';
70
72
  export { default as Validator } from './Validator';
71
73
  export { default as ViewportProvider, useViewport, ViewportContext } from './ViewportProvider';
72
- export { default as ThemeProvider, useTheme, useSetTheme, useThemeTokens, useThemeTokensCallback, getThemeTokens, applyOuterBorder, applyTextStyles, applyShadowToken } from './ThemeProvider';
74
+ export { default as ThemeProvider, useTheme, useSetTheme, useThemeTokens, useThemeTokensCallback, getThemeTokens, applyOuterBorder, applyTextStyles, applyShadowToken, useResponsiveThemeTokens } from './ThemeProvider';
73
75
  export * from './utils';
74
76
  export { default as Portal } from './Portal';
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "@floating-ui/react-native": "^0.8.1",
12
12
  "@gorhom/portal": "^1.0.14",
13
13
  "@telus-uds/system-constants": "^1.3.0",
14
- "@telus-uds/system-theme-tokens": "^2.63.0",
14
+ "@telus-uds/system-theme-tokens": "^2.65.0",
15
15
  "airbnb-prop-types": "^2.16.0",
16
16
  "css-mediaquery": "^0.1.2",
17
17
  "expo-linear-gradient": "^12.5.0",
@@ -86,6 +86,6 @@
86
86
  "standard-engine": {
87
87
  "skip": true
88
88
  },
89
- "version": "1.94.0",
89
+ "version": "1.96.0",
90
90
  "types": "types/index.d.ts"
91
91
  }
@@ -102,6 +102,7 @@ const Autocomplete = React.forwardRef(
102
102
  validation,
103
103
  value,
104
104
  helpText = '',
105
+ loadingLabel,
105
106
  ...rest
106
107
  },
107
108
  ref
@@ -134,6 +135,8 @@ const Autocomplete = React.forwardRef(
134
135
  // When it's nested, selected value
135
136
  const [nestedSelectedValue, setNestedSelectedValue] = React.useState(null)
136
137
 
138
+ const [isInputVisible, setIsInputVisible] = React.useState(true)
139
+
137
140
  const { supportsProps, ...selectedProps } = selectProps(rest)
138
141
  const { hint, label: inputLabel } = supportsProps
139
142
  const hintExpansionEnabled = isFocused && helpText && !currentValue
@@ -243,6 +246,32 @@ const Autocomplete = React.forwardRef(
243
246
  }
244
247
  }, [nestedSelectedValue, items])
245
248
 
249
+ React.useEffect(() => {
250
+ if (Platform.OS === 'ios' || Platform.OS === 'android') {
251
+ return undefined
252
+ }
253
+
254
+ const observer = new IntersectionObserver((entries) => {
255
+ const [entry] = entries
256
+ setIsInputVisible(entry.isIntersecting)
257
+ if (!entry.isIntersecting) {
258
+ setIsExpanded(false)
259
+ }
260
+ })
261
+
262
+ const currentInputRef = inputRef.current
263
+
264
+ if (currentInputRef) {
265
+ observer.observe(currentInputRef)
266
+ }
267
+
268
+ return () => {
269
+ if (currentInputRef) {
270
+ observer.unobserve(currentInputRef)
271
+ }
272
+ }
273
+ }, [inputRef])
274
+
246
275
  const handleClose = (event) => {
247
276
  if (
248
277
  (event.type === 'keydown' && (event.key === 'Escape' || event.key === '27')) ||
@@ -312,7 +341,18 @@ const Autocomplete = React.forwardRef(
312
341
  readOnly={readOnly}
313
342
  ref={inputRef}
314
343
  {...(Platform.OS !== 'web'
315
- ? { onLayout: (event) => setSourceLayout(event.nativeEvent.layout) }
344
+ ? {
345
+ onLayout: (event) => {
346
+ setSourceLayout(event.nativeEvent.layout)
347
+ const { y, height } = event.nativeEvent.layout
348
+ if (y >= 0 && height > 0) {
349
+ setIsInputVisible(true)
350
+ } else {
351
+ setIsInputVisible(false)
352
+ setIsExpanded(false)
353
+ }
354
+ }
355
+ }
316
356
  : {})}
317
357
  tokens={inputTokens}
318
358
  validation={validation}
@@ -323,7 +363,7 @@ const Autocomplete = React.forwardRef(
323
363
  )
324
364
  }}
325
365
  </InputSupports>
326
- {(isExpanded || hintExpansionEnabled) && (
366
+ {(isExpanded || hintExpansionEnabled) && isInputVisible && (
327
367
  <>
328
368
  <Listbox.Overlay
329
369
  overlaidPosition={overlaidPosition}
@@ -334,7 +374,7 @@ const Autocomplete = React.forwardRef(
334
374
  ref={openOverlayRef}
335
375
  >
336
376
  {isLoading ? (
337
- <Loading label={getCopy('loading')} />
377
+ <Loading label={loadingLabel ?? getCopy('loading')} />
338
378
  ) : (
339
379
  <Suggestions
340
380
  hasResults={getCopy('hasResults')}
@@ -21,6 +21,7 @@ const selectStyles = ({
21
21
  paddingTop,
22
22
  minWidth,
23
23
  shadow,
24
+ backgroundGradient,
24
25
  gradient
25
26
  }) => {
26
27
  return {
@@ -42,6 +43,11 @@ const selectStyles = ({
42
43
  boxShadow: `inset 0 1000px white`,
43
44
  border: `${borderWidth}px solid transparent`
44
45
  }
46
+ : {}),
47
+ ...(backgroundGradient && Platform.OS === 'web'
48
+ ? {
49
+ backgroundImage: `linear-gradient(${backgroundGradient.angle}deg, ${backgroundGradient.stops[0].color}, ${backgroundGradient.stops[1].color})`
50
+ }
45
51
  : {})
46
52
  }
47
53
  }
@@ -0,0 +1,165 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { Pressable, Platform, StyleSheet } from 'react-native'
4
+ import {
5
+ a11yProps,
6
+ linkProps,
7
+ hrefAttrsProp,
8
+ viewProps,
9
+ getTokensPropType,
10
+ resolvePressableTokens,
11
+ variantProp,
12
+ copyPropTypes,
13
+ selectSystemProps,
14
+ useCopy
15
+ } from '../utils'
16
+ import { useThemeTokensCallback } from '../ThemeProvider'
17
+ import defaultDictionary from './dictionary'
18
+
19
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, linkProps, viewProps])
20
+
21
+ const selectOuterStyles = ({ borderColor, borderWidth, borderGap, borderRadius, padding }) => ({
22
+ outline: 'none',
23
+ borderColor,
24
+ borderWidth,
25
+ borderGap,
26
+ borderRadius,
27
+ padding
28
+ })
29
+
30
+ const DownloadApp = React.forwardRef(
31
+ (
32
+ {
33
+ copy = 'en',
34
+ dictionary = defaultDictionary,
35
+ type = 'ios',
36
+ href,
37
+ onPress,
38
+ tokens = {},
39
+ variant = {},
40
+ ...props
41
+ },
42
+ ref
43
+ ) => {
44
+ const getCopy = useCopy({ dictionary, copy })
45
+ const { hrefAttrs, rest } = hrefAttrsProp.bundle(props)
46
+
47
+ const selectedProps = selectProps({
48
+ accessibilityRole: href ? 'link' : 'button',
49
+ href,
50
+ onPress: linkProps.handleHref({ href, onPress }),
51
+ hrefAttrs,
52
+ ...rest
53
+ })
54
+
55
+ const getDownloadAppTokens = useThemeTokensCallback('DownloadApp', tokens, variant)
56
+ const resolveDownloadAppTokens = (pressableState) => {
57
+ const themeTokens = resolvePressableTokens(getDownloadAppTokens, pressableState, {})
58
+ return selectOuterStyles(themeTokens)
59
+ }
60
+
61
+ const { androidENIcon, androidFRIcon, iosENIcon, iosFRIcon } = getDownloadAppTokens()
62
+
63
+ const imageResources = {
64
+ en: {
65
+ android: androidENIcon,
66
+ ios: iosENIcon
67
+ },
68
+ fr: {
69
+ android: androidFRIcon,
70
+ ios: iosFRIcon
71
+ }
72
+ }
73
+ const IconComponent = imageResources[copy][type]
74
+
75
+ return (
76
+ <Pressable
77
+ ref={ref}
78
+ style={(pressState) => [resolveDownloadAppTokens(pressState), staticStyles.row]}
79
+ {...selectedProps}
80
+ >
81
+ <IconComponent
82
+ style={staticImageSizes[type][copy]}
83
+ alt={type === 'ios' ? getCopy('altTextIos') : getCopy('altTextAndroid')}
84
+ />
85
+ </Pressable>
86
+ )
87
+ }
88
+ )
89
+ DownloadApp.displayName = 'DownloadApp'
90
+
91
+ const dictionaryContentShape = PropTypes.shape({
92
+ altTextAndroid: PropTypes.string.isRequired,
93
+ altTextIos: PropTypes.string.isRequired
94
+ })
95
+
96
+ DownloadApp.propTypes = {
97
+ ...selectedSystemPropTypes,
98
+ copy: copyPropTypes,
99
+ /**
100
+ * Override the default dictionary, by passing the complete dictionary object for `en` and `fr`
101
+ */
102
+ dictionary: PropTypes.shape({
103
+ en: dictionaryContentShape,
104
+ fr: dictionaryContentShape
105
+ }),
106
+ /**
107
+ * Select the type of image to show.
108
+ */
109
+ type: PropTypes.oneOf(['android', 'ios']),
110
+ /**
111
+ * It's a simple link that opens the Download Button instead of the onPress function.
112
+ */
113
+ href: PropTypes.string,
114
+ /**
115
+ * Function called when the button is pressed. Required unless the button has a href.
116
+ */
117
+ onPress: PropTypes.func,
118
+ /**
119
+ * DownloadApp tokens.
120
+ */
121
+ tokens: getTokensPropType('DownloadApp'),
122
+ /**
123
+ * DownloadApp variant.
124
+ */
125
+ variant: variantProp.propType
126
+ }
127
+
128
+ const staticStyles = StyleSheet.create({
129
+ row: {
130
+ ...Platform.select({
131
+ web: {
132
+ display: 'inline-flex',
133
+ width: 'fit-content'
134
+ },
135
+ default: {
136
+ alignSelf: 'flex-start'
137
+ }
138
+ })
139
+ }
140
+ })
141
+
142
+ const staticImageSizes = {
143
+ android: {
144
+ en: {
145
+ width: 162,
146
+ height: 48
147
+ },
148
+ fr: {
149
+ width: 162,
150
+ height: 48
151
+ }
152
+ },
153
+ ios: {
154
+ en: {
155
+ width: 144,
156
+ height: 48
157
+ },
158
+ fr: {
159
+ width: 152,
160
+ height: 48
161
+ }
162
+ }
163
+ }
164
+
165
+ export default DownloadApp
@@ -0,0 +1,10 @@
1
+ export default {
2
+ en: {
3
+ altTextAndroid: 'Get it on Google Play',
4
+ altTextIos: 'Download on the App Store'
5
+ },
6
+ fr: {
7
+ altTextAndroid: 'Disponible sur Google Play',
8
+ altTextIos: 'Télécharger dans l’App Store'
9
+ }
10
+ }
@@ -0,0 +1,3 @@
1
+ import DownloadApp from './DownloadApp'
2
+
3
+ export default DownloadApp