@telus-uds/components-base 3.12.2 → 3.14.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 (63) hide show
  1. package/CHANGELOG.md +37 -2
  2. package/lib/cjs/BaseProvider/index.js +4 -1
  3. package/lib/cjs/Button/ButtonDropdown.js +105 -12
  4. package/lib/cjs/Card/Card.js +23 -4
  5. package/lib/cjs/Card/CardBase.js +170 -19
  6. package/lib/cjs/Card/PressableCardBase.js +19 -5
  7. package/lib/cjs/Card/backgroundImageStylesMap.js +197 -0
  8. package/lib/cjs/ExpandCollapse/ExpandCollapse.js +3 -1
  9. package/lib/cjs/ExpandCollapseMini/ExpandCollapseMini.js +1 -1
  10. package/lib/cjs/ExpandCollapseMini/ExpandCollapseMiniControl.js +30 -6
  11. package/lib/cjs/FlexGrid/FlexGrid.js +71 -6
  12. package/lib/cjs/Icon/Icon.js +3 -1
  13. package/lib/cjs/InputLabel/InputLabel.js +1 -1
  14. package/lib/cjs/InputSupports/InputSupports.js +1 -1
  15. package/lib/cjs/Notification/Notification.js +27 -8
  16. package/lib/cjs/Tabs/Tabs.js +34 -2
  17. package/lib/cjs/Tabs/TabsDropdown.js +252 -0
  18. package/lib/cjs/Tabs/TabsItem.js +4 -2
  19. package/lib/cjs/Tabs/dictionary.js +14 -0
  20. package/lib/cjs/ViewportProvider/ViewportProvider.js +9 -3
  21. package/lib/cjs/utils/props/inputSupportsProps.js +1 -1
  22. package/lib/esm/BaseProvider/index.js +4 -1
  23. package/lib/esm/Button/ButtonDropdown.js +107 -14
  24. package/lib/esm/Card/Card.js +21 -4
  25. package/lib/esm/Card/CardBase.js +169 -19
  26. package/lib/esm/Card/PressableCardBase.js +19 -5
  27. package/lib/esm/Card/backgroundImageStylesMap.js +190 -0
  28. package/lib/esm/ExpandCollapse/ExpandCollapse.js +4 -2
  29. package/lib/esm/ExpandCollapseMini/ExpandCollapseMini.js +2 -2
  30. package/lib/esm/ExpandCollapseMini/ExpandCollapseMiniControl.js +30 -6
  31. package/lib/esm/FlexGrid/FlexGrid.js +72 -7
  32. package/lib/esm/Icon/Icon.js +3 -1
  33. package/lib/esm/InputLabel/InputLabel.js +1 -1
  34. package/lib/esm/InputSupports/InputSupports.js +1 -1
  35. package/lib/esm/Notification/Notification.js +27 -8
  36. package/lib/esm/Tabs/Tabs.js +35 -3
  37. package/lib/esm/Tabs/TabsDropdown.js +245 -0
  38. package/lib/esm/Tabs/TabsItem.js +4 -2
  39. package/lib/esm/Tabs/dictionary.js +8 -0
  40. package/lib/esm/ViewportProvider/ViewportProvider.js +9 -3
  41. package/lib/esm/utils/props/inputSupportsProps.js +1 -1
  42. package/lib/package.json +2 -2
  43. package/package.json +2 -2
  44. package/src/BaseProvider/index.jsx +4 -2
  45. package/src/Button/ButtonDropdown.jsx +109 -16
  46. package/src/Card/Card.jsx +27 -3
  47. package/src/Card/CardBase.jsx +165 -19
  48. package/src/Card/PressableCardBase.jsx +31 -4
  49. package/src/Card/backgroundImageStylesMap.js +41 -0
  50. package/src/ExpandCollapse/ExpandCollapse.jsx +5 -2
  51. package/src/ExpandCollapseMini/ExpandCollapseMini.jsx +2 -2
  52. package/src/ExpandCollapseMini/ExpandCollapseMiniControl.jsx +39 -9
  53. package/src/FlexGrid/FlexGrid.jsx +80 -7
  54. package/src/Icon/Icon.jsx +3 -1
  55. package/src/InputLabel/InputLabel.jsx +1 -1
  56. package/src/InputSupports/InputSupports.jsx +1 -1
  57. package/src/Notification/Notification.jsx +58 -9
  58. package/src/Tabs/Tabs.jsx +36 -2
  59. package/src/Tabs/TabsDropdown.jsx +265 -0
  60. package/src/Tabs/TabsItem.jsx +4 -2
  61. package/src/Tabs/dictionary.js +8 -0
  62. package/src/ViewportProvider/ViewportProvider.jsx +8 -3
  63. package/src/utils/props/inputSupportsProps.js +1 -1
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "@gorhom/portal": "^1.0.14",
13
13
  "@react-native-picker/picker": "^2.9.0",
14
14
  "@telus-uds/system-constants": "^3.0.0",
15
- "@telus-uds/system-theme-tokens": "^4.11.0",
15
+ "@telus-uds/system-theme-tokens": "^4.13.0",
16
16
  "airbnb-prop-types": "^2.16.0",
17
17
  "css-mediaquery": "^0.1.2",
18
18
  "expo-document-picker": "^13.0.1",
@@ -84,6 +84,6 @@
84
84
  "standard-engine": {
85
85
  "skip": true
86
86
  },
87
- "version": "3.12.2",
87
+ "version": "3.14.0",
88
88
  "types": "types/index.d.ts"
89
89
  }
@@ -1,6 +1,7 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import { PortalProvider } from '@gorhom/portal'
4
+ import { viewports } from '@telus-uds/system-constants'
4
5
  import A11yInfoProvider from '../A11yInfoProvider'
5
6
  import ViewportProvider from '../ViewportProvider'
6
7
  import ThemeProvider from '../ThemeProvider'
@@ -10,7 +11,7 @@ import { HydrationProvider } from './HydrationContext'
10
11
  const BaseProvider = React.forwardRef(({ defaultTheme, children, themeOptions }, _) => (
11
12
  <HydrationProvider>
12
13
  <A11yInfoProvider>
13
- <ViewportProvider>
14
+ <ViewportProvider defaultViewport={themeOptions?.defaultViewport}>
14
15
  <ThemeProvider defaultTheme={defaultTheme} themeOptions={themeOptions}>
15
16
  <PortalProvider>{children}</PortalProvider>
16
17
  </ThemeProvider>
@@ -26,7 +27,8 @@ BaseProvider.propTypes = {
26
27
  defaultTheme: ThemeProvider.propTypes?.defaultTheme,
27
28
  themeOptions: PropTypes.shape({
28
29
  forceAbsoluteFontSizing: PropTypes.bool,
29
- forceZIndex: PropTypes.bool
30
+ forceZIndex: PropTypes.bool,
31
+ defaultViewport: PropTypes.oneOf(viewports.keys)
30
32
  })
31
33
  }
32
34
 
@@ -1,9 +1,9 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { Platform, Text, View } from 'react-native'
3
+ import { Platform, StyleSheet, Text, View } from 'react-native'
4
4
  import buttonPropTypes, { textAndA11yText } from './propTypes'
5
5
  import ButtonBase from './ButtonBase'
6
- import { useThemeTokensCallback } from '../ThemeProvider'
6
+ import { applyTextStyles, useThemeTokensCallback } from '../ThemeProvider'
7
7
  import {
8
8
  a11yProps,
9
9
  getTokensPropType,
@@ -13,9 +13,10 @@ import {
13
13
  useInputValue
14
14
  } from '../utils'
15
15
  import Icon from '../Icon'
16
- import { getStackedContent } from '../StackView'
17
16
  import { getPressHandlersWithArgs } from '../utils/pressability'
18
17
 
18
+ const FULL_WIDTH_STYLE = 'full'
19
+
19
20
  const selectIconTokens = ({
20
21
  icon,
21
22
  iconPosition,
@@ -50,6 +51,44 @@ const selectIconTokens = ({
50
51
  }
51
52
  })
52
53
 
54
+ const selectDescriptionTextStyles = (tokens) => ({
55
+ ...applyTextStyles({
56
+ fontName: tokens?.descriptionFontName,
57
+ fontSize: tokens?.descriptionFontSize,
58
+ fontWeight: tokens?.descriptionFontWeight,
59
+ fontColor: tokens?.color
60
+ }),
61
+ paddingBottom: tokens?.descriptionTextPaddingBottom
62
+ })
63
+
64
+ const selectLeadIconTokens = (tokens) => ({
65
+ color: tokens?.leadIconColor,
66
+ backgroundColor: tokens?.leadIconBackgroundColor,
67
+ size: tokens?.leadIconSize,
68
+ borderRadius: tokens?.leadIconBorderRadius,
69
+ padding: tokens?.leadIconPadding
70
+ })
71
+
72
+ const selectLeadIconContainerStyles = (tokens) => ({
73
+ paddingTop: tokens?.leadIconContainerPaddingTop,
74
+ paddingBottom: tokens?.leadIconContainerPaddingBottom,
75
+ paddingLeft: tokens?.leadIconContainerPaddingLeft,
76
+ paddingRight: tokens?.leadIconContainerPaddingRight
77
+ })
78
+
79
+ const selectTextContainerStyles = (tokens) => ({
80
+ paddingLeft: tokens?.textPaddingLeft,
81
+ paddingRight: tokens?.textPaddingRight
82
+ })
83
+
84
+ const selectStackedContentStyles = (tokens, iconPosition, isFullWidth) => ({
85
+ ...staticStyles.stackedContent,
86
+ gap: tokens?.iconSpace,
87
+ flexDirection: iconPosition === 'left' ? 'row' : 'row-reverse',
88
+ ...(Platform.OS === 'web' && { flex: 1 }),
89
+ ...(isFullWidth && { justifyContent: 'space-between', flex: 1 })
90
+ })
91
+
53
92
  const ButtonDropdown = React.forwardRef(
54
93
  (
55
94
  {
@@ -63,10 +102,14 @@ const ButtonDropdown = React.forwardRef(
63
102
  readOnly = false,
64
103
  children = null,
65
104
  accessibilityRole = 'radio',
105
+ description,
106
+ singleOption,
66
107
  ...props
67
108
  },
68
109
  ref
69
110
  ) => {
111
+ const isFullWidth = variant?.width === FULL_WIDTH_STYLE
112
+
70
113
  const { currentValue: isOpen, setValue: setIsOpen } = useInputValue(
71
114
  {
72
115
  value,
@@ -85,7 +128,11 @@ const ButtonDropdown = React.forwardRef(
85
128
 
86
129
  const getTokens = useThemeTokensCallback('ButtonDropdown', tokens, extraState)
87
130
 
88
- const getButtonTokens = (buttonState) => selectTokens('Button', getTokens(buttonState))
131
+ const getButtonTokens = (buttonState) => ({
132
+ ...selectTokens('Button', getTokens(buttonState)),
133
+ iconSpace: props?.icon ? getTokens(buttonState)?.iconSpace : 0,
134
+ ...(isFullWidth && { width: 'full' })
135
+ })
89
136
 
90
137
  // Pass an object of relevant component state as first argument for any passed-in press handlers
91
138
  const pressHandlers = getPressHandlersWithArgs(props, [{ label, open: isOpen }])
@@ -103,7 +150,7 @@ const ButtonDropdown = React.forwardRef(
103
150
  {...pressHandlers}
104
151
  onPress={handlePress}
105
152
  tokens={getButtonTokens}
106
- inactive={inactive}
153
+ inactive={singleOption || inactive}
107
154
  icon={() => null}
108
155
  accessibilityRole={accessibilityRole}
109
156
  {...props}
@@ -116,31 +163,50 @@ const ButtonDropdown = React.forwardRef(
116
163
  // - Token sets: https://github.com/telus/universal-design-system/issues/782
117
164
 
118
165
  const itemTokens = getTokens(buttonState)
166
+ const leadIcon = itemTokens?.leadIcon
119
167
 
120
168
  const {
121
169
  iconTokens,
122
170
  iconPosition,
123
- iconSpace,
124
171
  iconWrapperStyle,
125
172
  icon: IconComponent
126
173
  } = selectIconTokens(itemTokens)
127
174
 
128
- const iconContent = IconComponent ? (
129
- <View style={iconWrapperStyle}>
130
- <Icon icon={IconComponent} tokens={iconTokens} />
131
- </View>
132
- ) : null
175
+ const iconContent =
176
+ IconComponent && !singleOption ? (
177
+ <View style={iconWrapperStyle}>
178
+ <Icon icon={IconComponent} tokens={iconTokens} />
179
+ </View>
180
+ ) : null
133
181
 
134
182
  const childrenContent = () =>
135
183
  typeof children === 'function'
136
184
  ? children({ ...resolvePressableState(buttonState, extraState), textStyles })
137
185
  : children
138
186
 
139
- const content = children ? childrenContent() : <Text style={textStyles}>{label}</Text>
187
+ const content = children ? (
188
+ childrenContent()
189
+ ) : (
190
+ <View style={staticStyles.contentContainer}>
191
+ {leadIcon && (
192
+ <View style={selectLeadIconContainerStyles(itemTokens)}>
193
+ <Icon icon={leadIcon} tokens={selectLeadIconTokens(itemTokens)} />
194
+ </View>
195
+ )}
196
+ <View style={[staticStyles.textContainer, selectTextContainerStyles(itemTokens)]}>
197
+ <Text style={textStyles}>{label}</Text>
198
+ {description && (
199
+ <Text style={selectDescriptionTextStyles(itemTokens)}>{description}</Text>
200
+ )}
201
+ </View>
202
+ </View>
203
+ )
140
204
 
141
- return getStackedContent(
142
- iconPosition === 'left' ? [iconContent, content] : [content, iconContent],
143
- { space: iconSpace, direction: 'row' }
205
+ return (
206
+ <View style={selectStackedContentStyles(itemTokens, iconPosition, isFullWidth)}>
207
+ {iconContent}
208
+ {content}
209
+ </View>
144
210
  )
145
211
  }}
146
212
  </ButtonBase>
@@ -176,7 +242,34 @@ ButtonDropdown.propTypes = {
176
242
  /**
177
243
  * By default, `ButtonDropdown` is treated by accessibility tools as a radio button.
178
244
  */
179
- accessibilityRole: PropTypes.string
245
+ accessibilityRole: PropTypes.string,
246
+ /**
247
+ * The description of ButtonDropdown.
248
+ */
249
+ description: PropTypes.string,
250
+ /**
251
+ * Use this prop to render the ButtonDropdown as display only without any interaction when there is only one option.
252
+ */
253
+ singleOption: PropTypes.bool
180
254
  }
181
255
 
256
+ const staticStyles = StyleSheet.create({
257
+ textContainer: {
258
+ alignItems: 'flex-start',
259
+ flexShrink: 1,
260
+ ...(Platform.OS === 'web' && { flex: 1 })
261
+ },
262
+ contentContainer: {
263
+ flexDirection: 'row',
264
+ alignContent: 'center',
265
+ alignItems: 'center',
266
+ ...(Platform.OS === 'web' && { flex: 1 }),
267
+ flexShrink: 1
268
+ },
269
+ stackedContent: {
270
+ flexDirection: 'row',
271
+ alignItems: 'center'
272
+ }
273
+ })
274
+
182
275
  export default ButtonDropdown
package/src/Card/Card.jsx CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  responsiveProps,
18
18
  hrefAttrsProp
19
19
  } from '../utils/props'
20
- import CardBase from './CardBase'
20
+ import CardBase, { selectStyles } from './CardBase'
21
21
  import PressableCardBase from './PressableCardBase'
22
22
  import CheckboxButton from '../Checkbox/CheckboxButton'
23
23
  import RadioButton from '../Radio/RadioButton'
@@ -186,10 +186,28 @@ const Card = React.forwardRef(
186
186
  let mediaIds
187
187
 
188
188
  if (enableMediaQueryStyleSheet) {
189
- const mediaQueryStyleSheet = createMediaQueryStyles(themeTokens)
189
+ const transformedThemeTokens = Object.entries(themeTokens).reduce(
190
+ (acc, [vp, viewportTokens]) => {
191
+ const tokensToTransform = selectionType
192
+ ? selectStyles({
193
+ ...viewportTokens,
194
+ paddingTop: 0,
195
+ paddingBottom: 0,
196
+ paddingLeft: 0,
197
+ paddingRight: 0
198
+ })
199
+ : selectStyles(viewportTokens)
200
+
201
+ acc[vp] = tokensToTransform
202
+ return acc
203
+ },
204
+ {}
205
+ )
206
+
207
+ const mediaQueryStyleSheet = createMediaQueryStyles(transformedThemeTokens)
190
208
 
191
209
  const { ids, styles } = StyleSheet.create({
192
- card: mediaQueryStyleSheet
210
+ card: { ...themeTokens[viewport], ...mediaQueryStyleSheet }
193
211
  })
194
212
  cardStyles = styles.card
195
213
  mediaIds = ids.card
@@ -336,6 +354,12 @@ Card.propTypes = {
336
354
  alt: PropTypes.string,
337
355
  resizeMode: responsiveProps.getTypeOptionallyByViewport(
338
356
  PropTypes.oneOf(['cover', 'contain', 'stretch', 'repeat', 'center'])
357
+ ),
358
+ position: responsiveProps.getTypeOptionallyByViewport(
359
+ PropTypes.oneOf(['bottom', 'left', 'right', 'top'])
360
+ ),
361
+ align: responsiveProps.getTypeOptionallyByViewport(
362
+ PropTypes.oneOf(['start', 'end', 'center', 'stretch'])
339
363
  )
340
364
  }),
341
365
  /**
@@ -1,15 +1,117 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { View, Platform, ImageBackground, StyleSheet } from 'react-native'
3
+ import { View, Platform, ImageBackground, Image, StyleSheet } from 'react-native'
4
4
 
5
5
  import { applyShadowToken } from '../ThemeProvider'
6
6
  import { getTokensPropType, responsiveProps, useResponsiveProp, formatImageSource } from '../utils'
7
7
  import { a11yProps, viewProps, selectSystemProps } from '../utils/props'
8
+ import backgroundImageStylesMap from './backgroundImageStylesMap'
8
9
 
9
10
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
10
11
 
12
+ const setBackgroundImage = ({
13
+ src,
14
+ alt,
15
+ backgroundImageResizeMode,
16
+ backgroundImagePosition,
17
+ backgroundImageAlign,
18
+ content,
19
+ cardStyle
20
+ }) => {
21
+ const borderRadius = cardStyle?.borderRadius || 0
22
+ const borderWidth = cardStyle?.borderWidth || 0
23
+ const adjustedBorderRadius = Math.max(0, borderRadius - borderWidth)
24
+
25
+ // For contain mode with position and align, use CSS background properties for web
26
+ if (backgroundImageResizeMode === 'contain' && backgroundImagePosition && backgroundImageAlign) {
27
+ const positionKey = `${backgroundImagePosition}-${backgroundImageAlign}`
28
+
29
+ if (Platform.OS === 'web') {
30
+ // Create background position based on position and align
31
+ let backgroundPosition
32
+
33
+ switch (positionKey) {
34
+ case 'top-start':
35
+ backgroundPosition = 'left top'
36
+ break
37
+ case 'top-center':
38
+ backgroundPosition = 'center top'
39
+ break
40
+ case 'top-end':
41
+ backgroundPosition = 'right top'
42
+ break
43
+ case 'bottom-start':
44
+ backgroundPosition = 'left bottom'
45
+ break
46
+ case 'bottom-center':
47
+ backgroundPosition = 'center bottom'
48
+ break
49
+ case 'bottom-end':
50
+ backgroundPosition = 'right bottom'
51
+ break
52
+ case 'left-center':
53
+ backgroundPosition = 'left center'
54
+ break
55
+ case 'right-center':
56
+ backgroundPosition = 'right center'
57
+ break
58
+ default:
59
+ backgroundPosition = 'center center'
60
+ }
61
+
62
+ const backgroundImageStyle = {
63
+ backgroundImage: `url(${src})`,
64
+ backgroundSize: 'contain',
65
+ backgroundRepeat: 'no-repeat',
66
+ backgroundPosition,
67
+ borderRadius: adjustedBorderRadius
68
+ }
69
+
70
+ return (
71
+ <View
72
+ style={[staticStyles.imageBackground, backgroundImageStyle]}
73
+ role="img"
74
+ aria-label={alt}
75
+ >
76
+ {content}
77
+ </View>
78
+ )
79
+ }
80
+ // For React Native, apply positioning styles with full dimensions
81
+ const positionStyles = backgroundImageStylesMap[positionKey] || {}
82
+
83
+ return (
84
+ <View style={[staticStyles.containContainer, { borderRadius: adjustedBorderRadius }]}>
85
+ <Image
86
+ source={src}
87
+ resizeMode={backgroundImageResizeMode}
88
+ style={[staticStyles.containImage, positionStyles]}
89
+ accessible={true}
90
+ accessibilityLabel={alt}
91
+ accessibilityIgnoresInvertColors={true}
92
+ />
93
+ <View style={staticStyles.contentOverlay}>{content}</View>
94
+ </View>
95
+ )
96
+ }
97
+
98
+ // Use ImageBackground for all other resize modes and React Native
99
+ return (
100
+ <ImageBackground
101
+ source={src}
102
+ imageStyle={{ borderRadius: adjustedBorderRadius }}
103
+ resizeMode={backgroundImageResizeMode}
104
+ style={staticStyles.imageBackground}
105
+ accessible={true}
106
+ accessibilityLabel={alt}
107
+ >
108
+ {content}
109
+ </ImageBackground>
110
+ )
111
+ }
112
+
11
113
  // Ensure explicit selection of tokens
12
- const selectStyles = ({
114
+ export const selectStyles = ({
13
115
  flex,
14
116
  backgroundColor,
15
117
  borderColor,
@@ -64,34 +166,72 @@ const CardBase = React.forwardRef(
64
166
  const cardStyle = selectStyles(typeof tokens === 'function' ? tokens() : tokens)
65
167
  const props = selectProps(rest)
66
168
 
67
- const { src = '', alt = '', resizeMode = '' } = backgroundImage || {}
169
+ let content = children
170
+
171
+ const { src = '', alt = '', resizeMode = '', position = '', align = '' } = backgroundImage || {}
68
172
  const backgroundImageResizeMode = useResponsiveProp(resizeMode, 'cover')
173
+ const backgroundImagePosition = useResponsiveProp(position)
174
+ const backgroundImageAlign = useResponsiveProp(align)
69
175
  const imageSourceViewport = formatImageSource(useResponsiveProp(src))
70
176
 
177
+ if (backgroundImage && src) {
178
+ // When there's a background image, separate the padding from the container style
179
+ // so the image can fill the entire container without padding interference
180
+ const { paddingTop, paddingBottom, paddingLeft, paddingRight, ...containerStyle } = cardStyle
181
+
182
+ // Only create padding wrapper if there's actually padding defined
183
+ const hasPadding = paddingTop || paddingBottom || paddingLeft || paddingRight
184
+ const paddedContent = hasPadding ? (
185
+ <View style={{ paddingTop, paddingBottom, paddingLeft, paddingRight }}>{children}</View>
186
+ ) : (
187
+ children
188
+ )
189
+
190
+ content = setBackgroundImage({
191
+ src: imageSourceViewport,
192
+ alt,
193
+ backgroundImageResizeMode,
194
+ backgroundImagePosition,
195
+ backgroundImageAlign,
196
+ content: paddedContent,
197
+ cardStyle: containerStyle
198
+ })
199
+
200
+ return (
201
+ <View style={containerStyle} dataSet={dataSet} ref={ref} {...props}>
202
+ {content}
203
+ </View>
204
+ )
205
+ }
206
+
71
207
  return (
72
208
  <View style={cardStyle} dataSet={dataSet} ref={ref} {...props}>
73
- {src ? (
74
- <ImageBackground
75
- source={imageSourceViewport}
76
- imageStyle={{ borderRadius: cardStyle?.borderRadius - cardStyle?.borderWidth }}
77
- resizeMode={backgroundImageResizeMode}
78
- style={styles.imageBackground}
79
- accessible={true}
80
- accessibilityLabel={alt}
81
- >
82
- {children}
83
- </ImageBackground>
84
- ) : (
85
- children
86
- )}
209
+ {content}
87
210
  </View>
88
211
  )
89
212
  }
90
213
  )
91
214
  CardBase.displayName = 'CardBase'
92
215
 
93
- const styles = StyleSheet.create({
94
- imageBackground: { width: '100%', height: '100%' }
216
+ const staticStyles = StyleSheet.create({
217
+ imageBackground: { width: '100%', height: '100%' },
218
+ contentOverlay: {
219
+ position: 'relative',
220
+ width: '100%',
221
+ height: '100%',
222
+ zIndex: 1
223
+ },
224
+ containContainer: {
225
+ width: '100%',
226
+ height: '100%',
227
+ overflow: 'hidden',
228
+ position: 'relative'
229
+ },
230
+ containImage: {
231
+ position: 'absolute',
232
+ width: '100%',
233
+ height: '100%'
234
+ }
95
235
  })
96
236
 
97
237
  CardBase.propTypes = {
@@ -108,6 +248,12 @@ CardBase.propTypes = {
108
248
  alt: PropTypes.string,
109
249
  resizeMode: responsiveProps.getTypeOptionallyByViewport(
110
250
  PropTypes.oneOf(['cover', 'contain', 'stretch', 'repeat', 'center'])
251
+ ),
252
+ position: responsiveProps.getTypeOptionallyByViewport(
253
+ PropTypes.oneOf(['bottom', 'left', 'right', 'top'])
254
+ ),
255
+ align: responsiveProps.getTypeOptionallyByViewport(
256
+ PropTypes.oneOf(['start', 'end', 'center', 'stretch'])
111
257
  )
112
258
  })
113
259
  }
@@ -80,6 +80,7 @@ const PressableCardBase = React.forwardRef(
80
80
  href,
81
81
  hrefAttrs,
82
82
  dataSet,
83
+ backgroundImage,
83
84
  accessibilityRole = href ? 'link' : undefined,
84
85
  ...rawRest
85
86
  },
@@ -158,10 +159,13 @@ const PressableCardBase = React.forwardRef(
158
159
  setFocused(false)
159
160
  setPressed(false)
160
161
  }}
161
- style={{ ...staticStyles.container, textDecoration: 'none' }}
162
+ style={staticStyles.linkContainer}
162
163
  {...(hrefAttrs || {})}
163
164
  >
164
- <CardBase tokens={getCardTokens({ pressed, focused, hovered })}>
165
+ <CardBase
166
+ tokens={getCardTokens({ pressed, focused, hovered })}
167
+ backgroundImage={backgroundImage}
168
+ >
165
169
  {typeof children === 'function'
166
170
  ? children(getCardState({ pressed, focused, hovered }))
167
171
  : children}
@@ -183,7 +187,7 @@ const PressableCardBase = React.forwardRef(
183
187
  {...selectProps({ ...rest, accessibilityRole })}
184
188
  >
185
189
  {(pressableState) => (
186
- <CardBase tokens={getCardTokens(pressableState)}>
190
+ <CardBase tokens={getCardTokens(pressableState)} backgroundImage={backgroundImage}>
187
191
  {typeof children === 'function' ? children(getCardState(pressableState)) : children}
188
192
  </CardBase>
189
193
  )}
@@ -196,6 +200,11 @@ const staticStyles = StyleSheet.create({
196
200
  container: {
197
201
  flex: 1,
198
202
  display: 'flex'
203
+ },
204
+ linkContainer: {
205
+ flex: 1,
206
+ display: 'flex',
207
+ textDecoration: 'none'
199
208
  }
200
209
  })
201
210
 
@@ -205,7 +214,25 @@ PressableCardBase.propTypes = {
205
214
  ...selectedSystemPropTypes,
206
215
  children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
207
216
  tokens: getTokensSetPropType(tokenKeys, { partial: true, allowFunction: true }),
208
- variant: variantProp.propType
217
+ variant: variantProp.propType,
218
+ backgroundImage: PropTypes.shape({
219
+ // The image src is either a URI string or a number (when a local image src is bundled in IOS or Android app)
220
+ // src is an object when used responsively to provide different image sources for different screen sizes
221
+ src: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]).isRequired,
222
+ alt: PropTypes.string,
223
+ resizeMode: PropTypes.oneOfType([
224
+ PropTypes.oneOf(['cover', 'contain', 'stretch', 'repeat', 'center']),
225
+ PropTypes.object
226
+ ]),
227
+ position: PropTypes.oneOfType([
228
+ PropTypes.oneOf(['bottom', 'left', 'right', 'top']),
229
+ PropTypes.object
230
+ ]),
231
+ align: PropTypes.oneOfType([
232
+ PropTypes.oneOf(['start', 'end', 'center', 'stretch']),
233
+ PropTypes.object
234
+ ])
235
+ })
209
236
  }
210
237
 
211
238
  export default withLinkRouter(PressableCardBase)
@@ -0,0 +1,41 @@
1
+ import { Platform } from 'react-native'
2
+
3
+ const webStyles = {
4
+ 'top-start': { top: 0, left: 0 },
5
+ 'top-center': { top: 0, left: '50%', transform: [{ translateX: '-50%' }] },
6
+ 'top-end': { top: 0, right: 0 },
7
+ 'right-start': { top: 0, right: 0 },
8
+ 'left-start': { top: 0, left: 0 },
9
+ 'left-center': { top: '50%', left: 0, transform: [{ translateY: '-50%' }] },
10
+ 'right-center': { top: '50%', right: 0, transform: [{ translateY: '-50%' }] },
11
+ 'bottom-start': { bottom: 0, left: 0 },
12
+ 'left-end': { bottom: 0, left: 0 },
13
+ 'bottom-center': { bottom: 0, left: '50%', transform: [{ translateX: '-50%' }] },
14
+ 'bottom-end': { bottom: 0, right: 0 },
15
+ 'right-end': { bottom: 0, right: 0 },
16
+ 'top-stretch': { top: 0, left: 0, right: 0, width: '100%' },
17
+ 'left-stretch': { top: 0, bottom: 0, left: 0, height: '100%' },
18
+ 'right-stretch': { top: 0, bottom: 0, right: 0, height: '100%' },
19
+ 'bottom-stretch': { bottom: 0, left: 0, right: 0, width: '100%' }
20
+ }
21
+
22
+ const nativeStyles = {
23
+ 'top-start': { top: 0, left: 0, width: 150, height: 200 },
24
+ 'top-center': { top: 0, left: '50%', marginLeft: -75, width: 150, height: 200 },
25
+ 'top-end': { top: 0, right: 0, width: 150, height: 200 },
26
+ 'right-start': { top: 0, right: 0, width: 150, height: 200 },
27
+ 'left-start': { top: 0, left: 0, width: 150, height: 200 },
28
+ 'left-center': { left: 0, top: '50%', marginTop: -100, width: 150, height: 200 },
29
+ 'right-center': { right: 0, top: '50%', marginTop: -100, width: 150, height: 200 },
30
+ 'bottom-start': { bottom: 0, left: 0, width: 150, height: 200 },
31
+ 'left-end': { bottom: 0, left: 0, width: 150, height: 200 },
32
+ 'bottom-center': { bottom: 0, left: '50%', marginLeft: -75, width: 150, height: 200 },
33
+ 'bottom-end': { bottom: 0, right: 0, width: 150, height: 200 },
34
+ 'right-end': { bottom: 0, right: 0, width: 150, height: 200 },
35
+ 'top-stretch': { top: 0, left: 0, right: 0, width: '100%' },
36
+ 'left-stretch': { top: 0, bottom: 0, left: 0, height: '100%' },
37
+ 'right-stretch': { top: 0, bottom: 0, right: 0, height: '100%' },
38
+ 'bottom-stretch': { bottom: 0, left: 0, right: 0, width: '100%' }
39
+ }
40
+
41
+ export default Platform.OS === 'web' ? webStyles : nativeStyles
@@ -10,7 +10,8 @@ import {
10
10
  useMultipleInputValues,
11
11
  variantProp,
12
12
  viewProps,
13
- contentfulProps
13
+ contentfulProps,
14
+ useUniqueId
14
15
  } from '../utils'
15
16
 
16
17
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([
@@ -36,6 +37,8 @@ function selectBorderStyles(tokens) {
36
37
  */
37
38
  const ExpandCollapse = React.forwardRef(
38
39
  ({ children, tokens, variant, maxOpen, open, initialOpen, onChange, dataSet, ...rest }, ref) => {
40
+ const instanceId = useUniqueId('ExpandCollapse')
41
+
39
42
  const {
40
43
  currentValues: openIds,
41
44
  toggleOneValue: onToggle,
@@ -54,7 +57,7 @@ const ExpandCollapse = React.forwardRef(
54
57
  <View style={staticStyles.container} ref={ref} {...selectProps(rest)} dataSet={dataSet}>
55
58
  <View style={selectBorderStyles(themeTokens)}>
56
59
  {typeof children === 'function'
57
- ? children({ openIds, onToggle, resetValues, setValues, unsetValues })
60
+ ? children({ openIds, onToggle, resetValues, setValues, unsetValues, instanceId })
58
61
  : children}
59
62
  </View>
60
63
  </View>
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import ExpandCollapse from '../ExpandCollapse'
4
- import { getTokensPropType, selectSystemProps, contentfulProps } from '../utils'
4
+ import { getTokensPropType, selectSystemProps, contentfulProps, useUniqueId } from '../utils'
5
5
  import { variantProp } from '../utils/props'
6
6
  import ExpandCollapseMiniControl from './ExpandCollapseMiniControl'
7
7
 
@@ -12,7 +12,7 @@ const ExpandCollapseMini = React.forwardRef(
12
12
  { children, onToggle = () => {}, tokens = {}, nativeID, initialOpen = false, dataSet, ...rest },
13
13
  ref
14
14
  ) => {
15
- const expandCollapeMiniPanelId = 'ExpandCollapseMiniPanel'
15
+ const expandCollapeMiniPanelId = useUniqueId('ExpandCollapseMiniPanel')
16
16
  const handleChange = (openPanels, event) => {
17
17
  if (typeof onToggle === 'function') {
18
18
  const isOpen = openPanels.length > 0