@telus-uds/components-base 1.76.0 → 1.77.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.
@@ -1,11 +1,16 @@
1
- import React, { forwardRef } from 'react';
1
+ import React, { forwardRef, useEffect, useState } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import View from "react-native-web/dist/exports/View";
4
4
  import ScrollView from "react-native-web/dist/exports/ScrollView";
5
5
  import Platform from "react-native-web/dist/exports/Platform";
6
+ import StyleSheet from "react-native-web/dist/exports/StyleSheet";
7
+ import ImageBackground from "react-native-web/dist/exports/ImageBackground";
8
+ import Image from "react-native-web/dist/exports/Image";
6
9
  import { useThemeTokens } from '../ThemeProvider';
7
- import { a11yProps, getA11yPropsFromHtmlTag, getTokensPropType, layoutTags, selectSystemProps, spacingProps, useSpacingScale, variantProp, viewProps } from '../utils';
10
+ import { a11yProps, getA11yPropsFromHtmlTag, getTokensPropType, layoutTags, responsiveProps, selectSystemProps, spacingProps, useResponsiveProp, useSpacingScale, variantProp, viewProps } from '../utils';
11
+ import backgroundImageStylesMap from './backgroundImageStylesMap';
8
12
  import { jsx as _jsx } from "react/jsx-runtime";
13
+ import { jsxs as _jsxs } from "react/jsx-runtime";
9
14
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
10
15
 
11
16
  /**
@@ -56,6 +61,45 @@ const selectBoxStyles = (_ref, customGradient) => {
56
61
  });
57
62
  return styles;
58
63
  };
64
+ const setBackgroundImage = _ref2 => {
65
+ let {
66
+ src,
67
+ alt,
68
+ backgroundImageResizeMode,
69
+ backgroundImagePosition,
70
+ backgroundImageAlign,
71
+ backgroundImageWidth,
72
+ backgroundImageHeight,
73
+ content
74
+ } = _ref2;
75
+ if (backgroundImageResizeMode === 'contain') {
76
+ const containedViewStyle = {
77
+ ...staticStyles.containedView,
78
+ width: backgroundImageWidth,
79
+ height: backgroundImageHeight,
80
+ ...backgroundImageStylesMap[`${backgroundImagePosition}-${backgroundImageAlign}`]
81
+ };
82
+ return /*#__PURE__*/_jsxs(View, {
83
+ style: staticStyles.containedContainer,
84
+ children: [/*#__PURE__*/_jsx(View, {
85
+ style: containedViewStyle,
86
+ children: /*#__PURE__*/_jsx(Image, {
87
+ source: src,
88
+ alt: alt,
89
+ style: staticStyles.containedImage,
90
+ accessibilityIgnoresInvertColors: true
91
+ })
92
+ }), content]
93
+ });
94
+ }
95
+ return /*#__PURE__*/_jsx(ImageBackground, {
96
+ source: src,
97
+ alt: alt,
98
+ style: staticStyles.backgroundImageContainer,
99
+ resizeMode: backgroundImageResizeMode,
100
+ children: content
101
+ });
102
+ };
59
103
 
60
104
  /**
61
105
  * A layout utility component. Use Box to create space (padding) around content.
@@ -122,7 +166,7 @@ const selectBoxStyles = (_ref, customGradient) => {
122
166
  * text content is inside a scrollable box, as screens are not scrollable by default and even very
123
167
  * short text will require scrolling on small devices at the highest accessibility text scaling settings.
124
168
  */
125
- const Box = /*#__PURE__*/forwardRef((_ref2, ref) => {
169
+ const Box = /*#__PURE__*/forwardRef((_ref3, ref) => {
126
170
  let {
127
171
  space,
128
172
  horizontal = space,
@@ -141,8 +185,9 @@ const Box = /*#__PURE__*/forwardRef((_ref2, ref) => {
141
185
  testID,
142
186
  dataSet,
143
187
  customGradient,
188
+ backgroundImage,
144
189
  ...rest
145
- } = _ref2;
190
+ } = _ref3;
146
191
  const props = {
147
192
  accessibilityRole,
148
193
  ...getA11yPropsFromHtmlTag(tag, accessibilityRole),
@@ -157,7 +202,41 @@ const Box = /*#__PURE__*/forwardRef((_ref2, ref) => {
157
202
  paddingBottom: useSpacingScale(bottom),
158
203
  ...selectBoxStyles(themeTokens, customGradient)
159
204
  };
160
- const childrenToRender = typeof customGradient === 'function' ? customGradient(styles.colors, styles)(children) : children;
205
+ let content = children;
206
+ if (typeof customGradient === 'function') content = customGradient(styles.colors, styles)(children);
207
+ const {
208
+ src = '',
209
+ alt = '',
210
+ resizeMode = '',
211
+ position = '',
212
+ align = ''
213
+ } = backgroundImage || {};
214
+ const backgroundImageResizeMode = useResponsiveProp(resizeMode, 'cover');
215
+ const backgroundImagePosition = useResponsiveProp(position, 'none');
216
+ const backgroundImageAlign = useResponsiveProp(align, 'stretch');
217
+ const [backgroundImageWidth, setBackgroundImageWidth] = useState(0);
218
+ const [backgroundImageHeight, setBackgroundImageHeight] = useState(0);
219
+ if (backgroundImage) content = setBackgroundImage({
220
+ src,
221
+ alt,
222
+ backgroundImageResizeMode,
223
+ backgroundImagePosition,
224
+ backgroundImageAlign,
225
+ backgroundImageWidth,
226
+ backgroundImageHeight,
227
+ content
228
+ });
229
+ useEffect(() => {
230
+ if (backgroundImage && backgroundImageWidth === 0 && backgroundImageHeight === 0) {
231
+ Image.getSize(src, (width, height) => {
232
+ // Only update the state if the size has changed
233
+ if (width !== backgroundImageWidth || height !== backgroundImageHeight) {
234
+ setBackgroundImageWidth(width);
235
+ setBackgroundImageHeight(height);
236
+ }
237
+ });
238
+ }
239
+ }, [backgroundImage, backgroundImageWidth, backgroundImageHeight, src]);
161
240
  if (scroll) {
162
241
  const scrollProps = typeof scroll === 'object' ? scroll : {};
163
242
  scrollProps.contentContainerStyle = [styles, scrollProps.contentContainerStyle];
@@ -167,7 +246,7 @@ const Box = /*#__PURE__*/forwardRef((_ref2, ref) => {
167
246
  testID: testID,
168
247
  dataSet: dataSet,
169
248
  ref: ref,
170
- children: childrenToRender
249
+ children: content
171
250
  });
172
251
  }
173
252
  return /*#__PURE__*/_jsx(View, {
@@ -176,7 +255,7 @@ const Box = /*#__PURE__*/forwardRef((_ref2, ref) => {
176
255
  testID: testID,
177
256
  dataSet: dataSet,
178
257
  ref: ref,
179
- children: childrenToRender
258
+ children: content
180
259
  });
181
260
  });
182
261
  Box.displayName = 'Box';
@@ -264,6 +343,33 @@ Box.propTypes = {
264
343
  /**
265
344
  Use this prop if need to add a custom gradient for mobile
266
345
  */
267
- customGradient: PropTypes.func
346
+ customGradient: PropTypes.func,
347
+ /**
348
+ * Use this prop to add a background image to the box.
349
+ */
350
+ backgroundImage: PropTypes.shape({
351
+ src: PropTypes.string.isRequired,
352
+ alt: PropTypes.string,
353
+ resizeMode: responsiveProps.getTypeOptionallyByViewport(PropTypes.oneOf(['cover', 'contain', 'stretch', 'repeat', 'center'])),
354
+ position: responsiveProps.getTypeOptionallyByViewport(PropTypes.oneOf(['none', 'bottom', 'left', 'right', 'top'])),
355
+ align: responsiveProps.getTypeOptionallyByViewport(PropTypes.oneOf(['start', 'end', 'center', 'stretch']))
356
+ })
268
357
  };
269
- export default Box;
358
+ export default Box;
359
+ const staticStyles = StyleSheet.create({
360
+ backgroundImageContainer: {
361
+ flex: 1
362
+ },
363
+ containedContainer: {
364
+ flex: 1,
365
+ overflow: 'hidden'
366
+ },
367
+ containedView: {
368
+ zIndex: -1,
369
+ position: 'absolute'
370
+ },
371
+ containedImage: {
372
+ width: '100%',
373
+ height: '100%'
374
+ }
375
+ });
@@ -0,0 +1,94 @@
1
+ export default {
2
+ 'top-start': {
3
+ top: 0
4
+ },
5
+ 'top-center': {
6
+ left: 0,
7
+ right: 0,
8
+ marginHorizontal: 'auto'
9
+ },
10
+ 'top-end': {
11
+ top: 0,
12
+ right: 0
13
+ },
14
+ 'right-start': {
15
+ top: 0,
16
+ right: 0
17
+ },
18
+ 'left-start': {
19
+ top: 0
20
+ },
21
+ 'left-center': {
22
+ top: 0,
23
+ bottom: 0,
24
+ marginVertical: 'auto'
25
+ },
26
+ 'none-start': {
27
+ top: 0,
28
+ bottom: 0,
29
+ marginVertical: 'auto'
30
+ },
31
+ 'none-center': {
32
+ top: 0,
33
+ bottom: 0,
34
+ left: 0,
35
+ right: 0,
36
+ margin: 'auto'
37
+ },
38
+ 'right-center': {
39
+ top: 0,
40
+ bottom: 0,
41
+ right: 0,
42
+ marginVertical: 'auto'
43
+ },
44
+ 'none-end': {
45
+ top: 0,
46
+ bottom: 0,
47
+ right: 0,
48
+ marginVertical: 'auto'
49
+ },
50
+ 'bottom-start': {
51
+ bottom: 0,
52
+ left: 0
53
+ },
54
+ 'left-end': {
55
+ bottom: 0,
56
+ left: 0
57
+ },
58
+ 'bottom-center': {
59
+ left: 0,
60
+ right: 0,
61
+ bottom: 0,
62
+ marginHorizontal: 'auto'
63
+ },
64
+ 'bottom-end': {
65
+ right: 0,
66
+ bottom: 0
67
+ },
68
+ 'right-end': {
69
+ right: 0,
70
+ bottom: 0
71
+ },
72
+ 'top-stretch': {
73
+ left: 0,
74
+ right: 0,
75
+ width: '100%'
76
+ },
77
+ 'left-stretch': {
78
+ top: 0,
79
+ bottom: 0,
80
+ height: '100%'
81
+ },
82
+ 'right-stretch': {
83
+ top: 0,
84
+ bottom: 0,
85
+ right: 0,
86
+ height: '100%'
87
+ },
88
+ 'bottom-stretch': {
89
+ left: 0,
90
+ right: 0,
91
+ bottom: 0,
92
+ width: '100%'
93
+ }
94
+ };
@@ -68,6 +68,14 @@ const selectContentPanelStyles = _ref3 => {
68
68
  marginBottom
69
69
  };
70
70
  };
71
+ const selectControlPanelStyles = _ref4 => {
72
+ let {
73
+ contentPanelBackgroundColor
74
+ } = _ref4;
75
+ return {
76
+ backgroundColor: contentPanelBackgroundColor
77
+ };
78
+ };
71
79
 
72
80
  /**
73
81
  * An item in an `ExpandCollapse` which contains collapsible `children` and a `control` that opens
@@ -79,7 +87,7 @@ const selectContentPanelStyles = _ref3 => {
79
87
  * The panel does not need to be a direct child of the `<ExpandCollapse>` (unless this is required
80
88
  * by the chosen accessibility props for a particular accessibility tools).
81
89
  */
82
- const ExpandCollapsePanel = /*#__PURE__*/forwardRef((_ref4, ref) => {
90
+ const ExpandCollapsePanel = /*#__PURE__*/forwardRef((_ref5, ref) => {
83
91
  let {
84
92
  openIds = [],
85
93
  panelId,
@@ -93,7 +101,7 @@ const ExpandCollapsePanel = /*#__PURE__*/forwardRef((_ref4, ref) => {
93
101
  controlRef,
94
102
  content,
95
103
  ...rest
96
- } = _ref4;
104
+ } = _ref5;
97
105
  const [containerHeight, setContainerHeight] = useState(null);
98
106
  const isExpanded = openIds.includes(panelId);
99
107
  const selectedProps = selectProps({
@@ -135,14 +143,17 @@ const ExpandCollapsePanel = /*#__PURE__*/forwardRef((_ref4, ref) => {
135
143
  }) : /*#__PURE__*/_jsxs(View, {
136
144
  ref: ref,
137
145
  style: themeTokens,
138
- children: [/*#__PURE__*/_jsx(ExpandCollapseControl, {
139
- ...selectedProps,
140
- isExpanded: isExpanded,
141
- tokens: controlTokens,
142
- variant: variant,
143
- onPress: handleControlPress,
144
- ref: controlRef,
145
- children: control
146
+ children: [/*#__PURE__*/_jsx(View, {
147
+ style: selectControlPanelStyles(themeTokens),
148
+ children: /*#__PURE__*/_jsx(ExpandCollapseControl, {
149
+ ...selectedProps,
150
+ isExpanded: isExpanded,
151
+ tokens: controlTokens,
152
+ variant: variant,
153
+ onPress: handleControlPress,
154
+ ref: controlRef,
155
+ children: control
156
+ })
146
157
  }), isExpanded && /*#__PURE__*/_jsx(View, {
147
158
  style: {
148
159
  borderTopColor: themeTokens.expandDividerColor,
@@ -30,6 +30,7 @@ const InlinePressable = /*#__PURE__*/forwardRef((_ref, ref) => {
30
30
  }
31
31
  }, [onPress]);
32
32
  const handleKeyPress = useCallback(e => {
33
+ e.preventDefault();
33
34
  if (e.key === 'Enter' || e.key === ' ') {
34
35
  handlePress();
35
36
  }
@@ -5,7 +5,7 @@ import { useThemeTokensCallback } from '../ThemeProvider';
5
5
  import { a11yProps, copyPropTypes, getTokensPropType, hrefAttrsProp, linkProps, selectTokens, variantProp, withLinkRouter } from '../utils';
6
6
  import useCopy from '../utils/useCopy';
7
7
  import dictionary from './dictionary';
8
- import { jsx as _jsx } from "react/jsx-runtime";
8
+ import { createElement as _createElement } from "react";
9
9
  const PageButton = /*#__PURE__*/forwardRef((_ref, ref) => {
10
10
  let {
11
11
  label,
@@ -25,11 +25,11 @@ const PageButton = /*#__PURE__*/forwardRef((_ref, ref) => {
25
25
  const getButtonTokens = buttonState => selectTokens('Button', getTokens(buttonState));
26
26
  const activeProps = isActive ? {
27
27
  selected: true,
28
- ...a11yProps.nonFocusableProps,
28
+ ...a11yProps.nonFocusableProps
29
29
  // a brute fix for the focus state being stuck on an active item since it becomes non-focusable
30
30
  // (see https://github.com/telus/universal-design-system/pull/577#issuecomment-931344107)
31
- key: 'active-item'
32
31
  } : {};
32
+ const key = isActive ? 'active-item' : undefined;
33
33
  const accessibilityRole = href !== undefined ? 'link' : 'button';
34
34
  const activeLabel = isActive ? ` ${getCopy('currentLabel')}` : '';
35
35
  const accessibilityLabel = `${getCopy('goToLabel')} ${label}${activeLabel}`;
@@ -45,13 +45,13 @@ const PageButton = /*#__PURE__*/forwardRef((_ref, ref) => {
45
45
  hrefAttrs,
46
46
  ...rest
47
47
  };
48
- return /*#__PURE__*/_jsx(ButtonBase, {
48
+ return /*#__PURE__*/_createElement(ButtonBase, {
49
49
  ref: ref,
50
50
  ...buttonProps,
51
51
  tokens: getButtonTokens,
52
- ...activeProps,
53
- children: label
54
- });
52
+ key: key,
53
+ ...activeProps
54
+ }, label);
55
55
  });
56
56
  PageButton.displayName = 'PageButton';
57
57
  PageButton.propTypes = {
@@ -25,11 +25,13 @@ const selectSkeletonStyles = _ref => {
25
25
  const selectLineStyles = _ref2 => {
26
26
  let {
27
27
  skeletonHeight,
28
- lineWidth
28
+ lineWidth,
29
+ radius
29
30
  } = _ref2;
30
31
  return {
31
32
  width: lineWidth,
32
- height: skeletonHeight
33
+ height: skeletonHeight,
34
+ borderRadius: radius
33
35
  };
34
36
  };
35
37
  const selectShapeStyles = _ref3 => {
@@ -112,7 +114,8 @@ const Skeleton = /*#__PURE__*/forwardRef((_ref5, ref) => {
112
114
  }
113
115
  return selectLineStyles({
114
116
  skeletonHeight,
115
- lineWidth: getLineWidth()
117
+ lineWidth: getLineWidth(),
118
+ radius: themeTokens.lineRadius
116
119
  });
117
120
  };
118
121
  const renderSkeleton = function () {
@@ -62,17 +62,16 @@ const getStackedContent = (children, _ref) => {
62
62
  const testID = `Stack-${divider ? 'Divider' : 'Spacer'}-${index}`;
63
63
  const commonProps = {
64
64
  testID,
65
- key: testID,
66
65
  space
67
66
  };
68
67
  const separator = divider ? /*#__PURE__*/_jsx(Divider, {
69
68
  vertical: direction.startsWith('row'),
70
69
  ...dividerProps,
71
70
  ...commonProps
72
- }) : /*#__PURE__*/_jsx(Spacer, {
71
+ }, testID) : /*#__PURE__*/_jsx(Spacer, {
73
72
  direction: direction.startsWith('row') ? 'row' : 'column',
74
73
  ...commonProps
75
- });
74
+ }, testID);
76
75
  return [...newChildren, separator, item];
77
76
  }, []);
78
77
  return content;
@@ -66,7 +66,7 @@ const selectStepTrackerLabelStyles = (_ref3, themeOptions) => {
66
66
  * ## Usability and A11y guidelines
67
67
  *
68
68
  * Keep in mind that in its current implementation this is not an interactive
69
- * component and cant be used to navigate between steps. The application
69
+ * component and can't be used to navigate between steps. The application
70
70
  * must provide its own navigation mechanism and state control. That is the
71
71
  * main reason the component assumes the `progressbar` role in terms of
72
72
  * accessibility. This also makes it extremely important to make sure you
@@ -110,7 +110,13 @@ const StepTracker = /*#__PURE__*/forwardRef((_ref4, ref) => {
110
110
  copy
111
111
  });
112
112
  const stepTrackerLabel = showStepTrackerLabel ? (typeof copy === 'string' ? getCopy(textStepTrackerLabel ?? 1).stepTrackerLabel : getCopy('stepTrackerLabel')).replace('%{stepNumber}', current < steps.length ? current + 1 : steps.length).replace('%{stepCount}', steps.length).replace('%{stepLabel}', current < steps.length ? steps[current] : steps[steps.length - 1]) : '';
113
- const getStepLabel = index => themeTokens.showStepLabel ? (typeof copy === 'string' ? getCopy(textStepTrackerLabel ?? 1).stepLabel : getCopy('stepLabel')).replace('%{stepNumber}', index + 1) : '';
113
+ const getStepLabel = index => {
114
+ if (themeTokens.showStepLabel) {
115
+ var _getCopy;
116
+ return (_getCopy = getCopy(index + 1)) === null || _getCopy === void 0 ? void 0 : _getCopy.stepLabel.replace('%{stepNumber}', index + 1);
117
+ }
118
+ return '';
119
+ };
114
120
  const {
115
121
  themeOptions
116
122
  } = useTheme();
@@ -5,7 +5,7 @@ export default {
5
5
  stepTrackerLabel: 'Step %{stepNumber} of %{stepCount}: %{stepLabel}'
6
6
  },
7
7
  2: {
8
- stepLabel: '%{stepNumber}.',
8
+ stepLabel: 'Step %{stepNumber}',
9
9
  stepTrackerLabel: 'Step %{stepNumber} of %{stepCount}: %{stepLabel}'
10
10
  },
11
11
  3: {
@@ -19,7 +19,7 @@ export default {
19
19
  stepTrackerLabel: 'Étape %{stepNumber} sur %{stepCount}: %{stepLabel}'
20
20
  },
21
21
  2: {
22
- stepLabel: '%{stepNumber}.',
22
+ stepLabel: 'Étape %{stepNumber}',
23
23
  stepTrackerLabel: 'Étape %{stepNumber} sur %{stepCount}: %{stepLabel}'
24
24
  },
25
25
  3: {
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.50.1",
14
+ "@telus-uds/system-theme-tokens": "^2.51.0",
15
15
  "airbnb-prop-types": "^2.16.0",
16
16
  "css-mediaquery": "^0.1.2",
17
17
  "expo-linear-gradient": "^12.5.0",
@@ -85,5 +85,6 @@
85
85
  "standard-engine": {
86
86
  "skip": true
87
87
  },
88
- "version": "1.76.0"
88
+ "version": "1.77.1",
89
+ "types": "types/index.d.ts"
89
90
  }
package/src/Box/Box.jsx CHANGED
@@ -1,18 +1,21 @@
1
- import React, { forwardRef } from 'react'
1
+ import React, { forwardRef, useEffect, useState } from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { View, ScrollView, Platform } from 'react-native'
3
+ import { View, ScrollView, Platform, StyleSheet, ImageBackground, Image } from 'react-native'
4
4
  import { useThemeTokens } from '../ThemeProvider'
5
5
  import {
6
6
  a11yProps,
7
7
  getA11yPropsFromHtmlTag,
8
8
  getTokensPropType,
9
9
  layoutTags,
10
+ responsiveProps,
10
11
  selectSystemProps,
11
12
  spacingProps,
13
+ useResponsiveProp,
12
14
  useSpacingScale,
13
15
  variantProp,
14
16
  viewProps
15
17
  } from '../utils'
18
+ import backgroundImageStylesMap from './backgroundImageStylesMap'
16
19
 
17
20
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
18
21
 
@@ -69,6 +72,50 @@ const selectBoxStyles = (
69
72
  return styles
70
73
  }
71
74
 
75
+ const setBackgroundImage = ({
76
+ src,
77
+ alt,
78
+ backgroundImageResizeMode,
79
+ backgroundImagePosition,
80
+ backgroundImageAlign,
81
+ backgroundImageWidth,
82
+ backgroundImageHeight,
83
+ content
84
+ }) => {
85
+ if (backgroundImageResizeMode === 'contain') {
86
+ const containedViewStyle = {
87
+ ...staticStyles.containedView,
88
+ width: backgroundImageWidth,
89
+ height: backgroundImageHeight,
90
+ ...backgroundImageStylesMap[`${backgroundImagePosition}-${backgroundImageAlign}`]
91
+ }
92
+
93
+ return (
94
+ <View style={staticStyles.containedContainer}>
95
+ <View style={containedViewStyle}>
96
+ <Image
97
+ source={src}
98
+ alt={alt}
99
+ style={staticStyles.containedImage}
100
+ accessibilityIgnoresInvertColors
101
+ />
102
+ </View>
103
+ {content}
104
+ </View>
105
+ )
106
+ }
107
+ return (
108
+ <ImageBackground
109
+ source={src}
110
+ alt={alt}
111
+ style={staticStyles.backgroundImageContainer}
112
+ resizeMode={backgroundImageResizeMode}
113
+ >
114
+ {content}
115
+ </ImageBackground>
116
+ )
117
+ }
118
+
72
119
  /**
73
120
  * A layout utility component. Use Box to create space (padding) around content.
74
121
  *
@@ -154,6 +201,7 @@ const Box = forwardRef(
154
201
  testID,
155
202
  dataSet,
156
203
  customGradient,
204
+ backgroundImage,
157
205
  ...rest
158
206
  },
159
207
  ref
@@ -174,23 +222,52 @@ const Box = forwardRef(
174
222
  ...selectBoxStyles(themeTokens, customGradient)
175
223
  }
176
224
 
177
- const childrenToRender =
178
- typeof customGradient === 'function'
179
- ? customGradient(styles.colors, styles)(children)
180
- : children
225
+ let content = children
226
+ if (typeof customGradient === 'function')
227
+ content = customGradient(styles.colors, styles)(children)
228
+
229
+ const { src = '', alt = '', resizeMode = '', position = '', align = '' } = backgroundImage || {}
230
+ const backgroundImageResizeMode = useResponsiveProp(resizeMode, 'cover')
231
+ const backgroundImagePosition = useResponsiveProp(position, 'none')
232
+ const backgroundImageAlign = useResponsiveProp(align, 'stretch')
233
+ const [backgroundImageWidth, setBackgroundImageWidth] = useState(0)
234
+ const [backgroundImageHeight, setBackgroundImageHeight] = useState(0)
235
+ if (backgroundImage)
236
+ content = setBackgroundImage({
237
+ src,
238
+ alt,
239
+ backgroundImageResizeMode,
240
+ backgroundImagePosition,
241
+ backgroundImageAlign,
242
+ backgroundImageWidth,
243
+ backgroundImageHeight,
244
+ content
245
+ })
246
+
247
+ useEffect(() => {
248
+ if (backgroundImage && backgroundImageWidth === 0 && backgroundImageHeight === 0) {
249
+ Image.getSize(src, (width, height) => {
250
+ // Only update the state if the size has changed
251
+ if (width !== backgroundImageWidth || height !== backgroundImageHeight) {
252
+ setBackgroundImageWidth(width)
253
+ setBackgroundImageHeight(height)
254
+ }
255
+ })
256
+ }
257
+ }, [backgroundImage, backgroundImageWidth, backgroundImageHeight, src])
181
258
 
182
259
  if (scroll) {
183
260
  const scrollProps = typeof scroll === 'object' ? scroll : {}
184
261
  scrollProps.contentContainerStyle = [styles, scrollProps.contentContainerStyle]
185
262
  return (
186
263
  <ScrollView {...scrollProps} {...props} testID={testID} dataSet={dataSet} ref={ref}>
187
- {childrenToRender}
264
+ {content}
188
265
  </ScrollView>
189
266
  )
190
267
  }
191
268
  return (
192
269
  <View {...props} style={styles} testID={testID} dataSet={dataSet} ref={ref}>
193
- {childrenToRender}
270
+ {content}
194
271
  </View>
195
272
  )
196
273
  }
@@ -284,7 +361,41 @@ Box.propTypes = {
284
361
  /**
285
362
  Use this prop if need to add a custom gradient for mobile
286
363
  */
287
- customGradient: PropTypes.func
364
+ customGradient: PropTypes.func,
365
+ /**
366
+ * Use this prop to add a background image to the box.
367
+ */
368
+ backgroundImage: PropTypes.shape({
369
+ src: PropTypes.string.isRequired,
370
+ alt: PropTypes.string,
371
+ resizeMode: responsiveProps.getTypeOptionallyByViewport(
372
+ PropTypes.oneOf(['cover', 'contain', 'stretch', 'repeat', 'center'])
373
+ ),
374
+ position: responsiveProps.getTypeOptionallyByViewport(
375
+ PropTypes.oneOf(['none', 'bottom', 'left', 'right', 'top'])
376
+ ),
377
+ align: responsiveProps.getTypeOptionallyByViewport(
378
+ PropTypes.oneOf(['start', 'end', 'center', 'stretch'])
379
+ )
380
+ })
288
381
  }
289
382
 
290
383
  export default Box
384
+
385
+ const staticStyles = StyleSheet.create({
386
+ backgroundImageContainer: {
387
+ flex: 1
388
+ },
389
+ containedContainer: {
390
+ flex: 1,
391
+ overflow: 'hidden'
392
+ },
393
+ containedView: {
394
+ zIndex: -1,
395
+ position: 'absolute'
396
+ },
397
+ containedImage: {
398
+ width: '100%',
399
+ height: '100%'
400
+ }
401
+ })