@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.
- package/CHANGELOG.md +37 -2
- package/lib/cjs/BaseProvider/index.js +4 -1
- package/lib/cjs/Button/ButtonDropdown.js +105 -12
- package/lib/cjs/Card/Card.js +23 -4
- package/lib/cjs/Card/CardBase.js +170 -19
- package/lib/cjs/Card/PressableCardBase.js +19 -5
- package/lib/cjs/Card/backgroundImageStylesMap.js +197 -0
- package/lib/cjs/ExpandCollapse/ExpandCollapse.js +3 -1
- package/lib/cjs/ExpandCollapseMini/ExpandCollapseMini.js +1 -1
- package/lib/cjs/ExpandCollapseMini/ExpandCollapseMiniControl.js +30 -6
- package/lib/cjs/FlexGrid/FlexGrid.js +71 -6
- package/lib/cjs/Icon/Icon.js +3 -1
- package/lib/cjs/InputLabel/InputLabel.js +1 -1
- package/lib/cjs/InputSupports/InputSupports.js +1 -1
- package/lib/cjs/Notification/Notification.js +27 -8
- package/lib/cjs/Tabs/Tabs.js +34 -2
- package/lib/cjs/Tabs/TabsDropdown.js +252 -0
- package/lib/cjs/Tabs/TabsItem.js +4 -2
- package/lib/cjs/Tabs/dictionary.js +14 -0
- package/lib/cjs/ViewportProvider/ViewportProvider.js +9 -3
- package/lib/cjs/utils/props/inputSupportsProps.js +1 -1
- package/lib/esm/BaseProvider/index.js +4 -1
- package/lib/esm/Button/ButtonDropdown.js +107 -14
- package/lib/esm/Card/Card.js +21 -4
- package/lib/esm/Card/CardBase.js +169 -19
- package/lib/esm/Card/PressableCardBase.js +19 -5
- package/lib/esm/Card/backgroundImageStylesMap.js +190 -0
- package/lib/esm/ExpandCollapse/ExpandCollapse.js +4 -2
- package/lib/esm/ExpandCollapseMini/ExpandCollapseMini.js +2 -2
- package/lib/esm/ExpandCollapseMini/ExpandCollapseMiniControl.js +30 -6
- package/lib/esm/FlexGrid/FlexGrid.js +72 -7
- package/lib/esm/Icon/Icon.js +3 -1
- package/lib/esm/InputLabel/InputLabel.js +1 -1
- package/lib/esm/InputSupports/InputSupports.js +1 -1
- package/lib/esm/Notification/Notification.js +27 -8
- package/lib/esm/Tabs/Tabs.js +35 -3
- package/lib/esm/Tabs/TabsDropdown.js +245 -0
- package/lib/esm/Tabs/TabsItem.js +4 -2
- package/lib/esm/Tabs/dictionary.js +8 -0
- package/lib/esm/ViewportProvider/ViewportProvider.js +9 -3
- package/lib/esm/utils/props/inputSupportsProps.js +1 -1
- package/lib/package.json +2 -2
- package/package.json +2 -2
- package/src/BaseProvider/index.jsx +4 -2
- package/src/Button/ButtonDropdown.jsx +109 -16
- package/src/Card/Card.jsx +27 -3
- package/src/Card/CardBase.jsx +165 -19
- package/src/Card/PressableCardBase.jsx +31 -4
- package/src/Card/backgroundImageStylesMap.js +41 -0
- package/src/ExpandCollapse/ExpandCollapse.jsx +5 -2
- package/src/ExpandCollapseMini/ExpandCollapseMini.jsx +2 -2
- package/src/ExpandCollapseMini/ExpandCollapseMiniControl.jsx +39 -9
- package/src/FlexGrid/FlexGrid.jsx +80 -7
- package/src/Icon/Icon.jsx +3 -1
- package/src/InputLabel/InputLabel.jsx +1 -1
- package/src/InputSupports/InputSupports.jsx +1 -1
- package/src/Notification/Notification.jsx +58 -9
- package/src/Tabs/Tabs.jsx +36 -2
- package/src/Tabs/TabsDropdown.jsx +265 -0
- package/src/Tabs/TabsItem.jsx +4 -2
- package/src/Tabs/dictionary.js +8 -0
- package/src/ViewportProvider/ViewportProvider.jsx +8 -3
- 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.
|
|
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.
|
|
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) =>
|
|
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 =
|
|
129
|
-
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
|
|
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 ?
|
|
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
|
|
142
|
-
|
|
143
|
-
|
|
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
|
|
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
|
/**
|
package/src/Card/CardBase.jsx
CHANGED
|
@@ -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
|
-
|
|
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
|
-
{
|
|
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
|
|
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={
|
|
162
|
+
style={staticStyles.linkContainer}
|
|
162
163
|
{...(hrefAttrs || {})}
|
|
163
164
|
>
|
|
164
|
-
<CardBase
|
|
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
|