@telus-uds/components-base 3.13.0 → 3.14.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.
- package/CHANGELOG.md +10 -2
- package/lib/cjs/BaseProvider/index.js +4 -1
- 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/FlexGrid/FlexGrid.js +28 -12
- 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/esm/BaseProvider/index.js +4 -1
- 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/FlexGrid/FlexGrid.js +28 -12
- 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/package.json +2 -2
- package/package.json +2 -2
- package/src/BaseProvider/index.jsx +4 -2
- 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/FlexGrid/FlexGrid.jsx +30 -13
- 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/lib/esm/Card/CardBase.js
CHANGED
|
@@ -3,15 +3,112 @@ import PropTypes from 'prop-types';
|
|
|
3
3
|
import View from "react-native-web/dist/exports/View";
|
|
4
4
|
import Platform from "react-native-web/dist/exports/Platform";
|
|
5
5
|
import ImageBackground from "react-native-web/dist/exports/ImageBackground";
|
|
6
|
+
import Image from "react-native-web/dist/exports/Image";
|
|
6
7
|
import StyleSheet from "react-native-web/dist/exports/StyleSheet";
|
|
7
8
|
import { applyShadowToken } from '../ThemeProvider';
|
|
8
9
|
import { getTokensPropType, responsiveProps, useResponsiveProp, formatImageSource } from '../utils';
|
|
9
10
|
import { a11yProps, viewProps, selectSystemProps } from '../utils/props';
|
|
10
|
-
import
|
|
11
|
+
import backgroundImageStylesMap from './backgroundImageStylesMap';
|
|
12
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
13
|
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
|
|
14
|
+
const setBackgroundImage = _ref => {
|
|
15
|
+
let {
|
|
16
|
+
src,
|
|
17
|
+
alt,
|
|
18
|
+
backgroundImageResizeMode,
|
|
19
|
+
backgroundImagePosition,
|
|
20
|
+
backgroundImageAlign,
|
|
21
|
+
content,
|
|
22
|
+
cardStyle
|
|
23
|
+
} = _ref;
|
|
24
|
+
const borderRadius = cardStyle?.borderRadius || 0;
|
|
25
|
+
const borderWidth = cardStyle?.borderWidth || 0;
|
|
26
|
+
const adjustedBorderRadius = Math.max(0, borderRadius - borderWidth);
|
|
27
|
+
|
|
28
|
+
// For contain mode with position and align, use CSS background properties for web
|
|
29
|
+
if (backgroundImageResizeMode === 'contain' && backgroundImagePosition && backgroundImageAlign) {
|
|
30
|
+
const positionKey = `${backgroundImagePosition}-${backgroundImageAlign}`;
|
|
31
|
+
if (Platform.OS === 'web') {
|
|
32
|
+
// Create background position based on position and align
|
|
33
|
+
let backgroundPosition;
|
|
34
|
+
switch (positionKey) {
|
|
35
|
+
case 'top-start':
|
|
36
|
+
backgroundPosition = 'left top';
|
|
37
|
+
break;
|
|
38
|
+
case 'top-center':
|
|
39
|
+
backgroundPosition = 'center top';
|
|
40
|
+
break;
|
|
41
|
+
case 'top-end':
|
|
42
|
+
backgroundPosition = 'right top';
|
|
43
|
+
break;
|
|
44
|
+
case 'bottom-start':
|
|
45
|
+
backgroundPosition = 'left bottom';
|
|
46
|
+
break;
|
|
47
|
+
case 'bottom-center':
|
|
48
|
+
backgroundPosition = 'center bottom';
|
|
49
|
+
break;
|
|
50
|
+
case 'bottom-end':
|
|
51
|
+
backgroundPosition = 'right bottom';
|
|
52
|
+
break;
|
|
53
|
+
case 'left-center':
|
|
54
|
+
backgroundPosition = 'left center';
|
|
55
|
+
break;
|
|
56
|
+
case 'right-center':
|
|
57
|
+
backgroundPosition = 'right center';
|
|
58
|
+
break;
|
|
59
|
+
default:
|
|
60
|
+
backgroundPosition = 'center center';
|
|
61
|
+
}
|
|
62
|
+
const backgroundImageStyle = {
|
|
63
|
+
backgroundImage: `url(${src})`,
|
|
64
|
+
backgroundSize: 'contain',
|
|
65
|
+
backgroundRepeat: 'no-repeat',
|
|
66
|
+
backgroundPosition,
|
|
67
|
+
borderRadius: adjustedBorderRadius
|
|
68
|
+
};
|
|
69
|
+
return /*#__PURE__*/_jsx(View, {
|
|
70
|
+
style: [staticStyles.imageBackground, backgroundImageStyle],
|
|
71
|
+
role: "img",
|
|
72
|
+
"aria-label": alt,
|
|
73
|
+
children: content
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// For React Native, apply positioning styles with full dimensions
|
|
77
|
+
const positionStyles = backgroundImageStylesMap[positionKey] || {};
|
|
78
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
79
|
+
style: [staticStyles.containContainer, {
|
|
80
|
+
borderRadius: adjustedBorderRadius
|
|
81
|
+
}],
|
|
82
|
+
children: [/*#__PURE__*/_jsx(Image, {
|
|
83
|
+
source: src,
|
|
84
|
+
resizeMode: backgroundImageResizeMode,
|
|
85
|
+
style: [staticStyles.containImage, positionStyles],
|
|
86
|
+
accessible: true,
|
|
87
|
+
accessibilityLabel: alt,
|
|
88
|
+
accessibilityIgnoresInvertColors: true
|
|
89
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
90
|
+
style: staticStyles.contentOverlay,
|
|
91
|
+
children: content
|
|
92
|
+
})]
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Use ImageBackground for all other resize modes and React Native
|
|
97
|
+
return /*#__PURE__*/_jsx(ImageBackground, {
|
|
98
|
+
source: src,
|
|
99
|
+
imageStyle: {
|
|
100
|
+
borderRadius: adjustedBorderRadius
|
|
101
|
+
},
|
|
102
|
+
resizeMode: backgroundImageResizeMode,
|
|
103
|
+
style: staticStyles.imageBackground,
|
|
104
|
+
accessible: true,
|
|
105
|
+
accessibilityLabel: alt,
|
|
106
|
+
children: content
|
|
107
|
+
});
|
|
108
|
+
};
|
|
12
109
|
|
|
13
110
|
// Ensure explicit selection of tokens
|
|
14
|
-
const selectStyles =
|
|
111
|
+
export const selectStyles = _ref2 => {
|
|
15
112
|
let {
|
|
16
113
|
flex,
|
|
17
114
|
backgroundColor,
|
|
@@ -28,7 +125,7 @@ const selectStyles = _ref => {
|
|
|
28
125
|
gradient,
|
|
29
126
|
maxHeight,
|
|
30
127
|
overflowY
|
|
31
|
-
} =
|
|
128
|
+
} = _ref2;
|
|
32
129
|
return {
|
|
33
130
|
flex,
|
|
34
131
|
backgroundColor,
|
|
@@ -61,46 +158,97 @@ const selectStyles = _ref => {
|
|
|
61
158
|
* A themeless base component for Card which components can apply theme tokens to. Not
|
|
62
159
|
* intended to be used in apps or sites directly: build themed components on top of this.
|
|
63
160
|
*/
|
|
64
|
-
const CardBase = /*#__PURE__*/React.forwardRef((
|
|
161
|
+
const CardBase = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
65
162
|
let {
|
|
66
163
|
children,
|
|
67
164
|
tokens,
|
|
68
165
|
dataSet,
|
|
69
166
|
backgroundImage,
|
|
70
167
|
...rest
|
|
71
|
-
} =
|
|
168
|
+
} = _ref3;
|
|
72
169
|
const cardStyle = selectStyles(typeof tokens === 'function' ? tokens() : tokens);
|
|
73
170
|
const props = selectProps(rest);
|
|
171
|
+
let content = children;
|
|
74
172
|
const {
|
|
75
173
|
src = '',
|
|
76
174
|
alt = '',
|
|
77
|
-
resizeMode = ''
|
|
175
|
+
resizeMode = '',
|
|
176
|
+
position = '',
|
|
177
|
+
align = ''
|
|
78
178
|
} = backgroundImage || {};
|
|
79
179
|
const backgroundImageResizeMode = useResponsiveProp(resizeMode, 'cover');
|
|
180
|
+
const backgroundImagePosition = useResponsiveProp(position);
|
|
181
|
+
const backgroundImageAlign = useResponsiveProp(align);
|
|
80
182
|
const imageSourceViewport = formatImageSource(useResponsiveProp(src));
|
|
183
|
+
if (backgroundImage && src) {
|
|
184
|
+
// When there's a background image, separate the padding from the container style
|
|
185
|
+
// so the image can fill the entire container without padding interference
|
|
186
|
+
const {
|
|
187
|
+
paddingTop,
|
|
188
|
+
paddingBottom,
|
|
189
|
+
paddingLeft,
|
|
190
|
+
paddingRight,
|
|
191
|
+
...containerStyle
|
|
192
|
+
} = cardStyle;
|
|
193
|
+
|
|
194
|
+
// Only create padding wrapper if there's actually padding defined
|
|
195
|
+
const hasPadding = paddingTop || paddingBottom || paddingLeft || paddingRight;
|
|
196
|
+
const paddedContent = hasPadding ? /*#__PURE__*/_jsx(View, {
|
|
197
|
+
style: {
|
|
198
|
+
paddingTop,
|
|
199
|
+
paddingBottom,
|
|
200
|
+
paddingLeft,
|
|
201
|
+
paddingRight
|
|
202
|
+
},
|
|
203
|
+
children: children
|
|
204
|
+
}) : children;
|
|
205
|
+
content = setBackgroundImage({
|
|
206
|
+
src: imageSourceViewport,
|
|
207
|
+
alt,
|
|
208
|
+
backgroundImageResizeMode,
|
|
209
|
+
backgroundImagePosition,
|
|
210
|
+
backgroundImageAlign,
|
|
211
|
+
content: paddedContent,
|
|
212
|
+
cardStyle: containerStyle
|
|
213
|
+
});
|
|
214
|
+
return /*#__PURE__*/_jsx(View, {
|
|
215
|
+
style: containerStyle,
|
|
216
|
+
dataSet: dataSet,
|
|
217
|
+
ref: ref,
|
|
218
|
+
...props,
|
|
219
|
+
children: content
|
|
220
|
+
});
|
|
221
|
+
}
|
|
81
222
|
return /*#__PURE__*/_jsx(View, {
|
|
82
223
|
style: cardStyle,
|
|
83
224
|
dataSet: dataSet,
|
|
84
225
|
ref: ref,
|
|
85
226
|
...props,
|
|
86
|
-
children:
|
|
87
|
-
source: imageSourceViewport,
|
|
88
|
-
imageStyle: {
|
|
89
|
-
borderRadius: cardStyle?.borderRadius - cardStyle?.borderWidth
|
|
90
|
-
},
|
|
91
|
-
resizeMode: backgroundImageResizeMode,
|
|
92
|
-
style: styles.imageBackground,
|
|
93
|
-
accessible: true,
|
|
94
|
-
accessibilityLabel: alt,
|
|
95
|
-
children: children
|
|
96
|
-
}) : children
|
|
227
|
+
children: content
|
|
97
228
|
});
|
|
98
229
|
});
|
|
99
230
|
CardBase.displayName = 'CardBase';
|
|
100
|
-
const
|
|
231
|
+
const staticStyles = StyleSheet.create({
|
|
101
232
|
imageBackground: {
|
|
102
233
|
width: '100%',
|
|
103
234
|
height: '100%'
|
|
235
|
+
},
|
|
236
|
+
contentOverlay: {
|
|
237
|
+
position: 'relative',
|
|
238
|
+
width: '100%',
|
|
239
|
+
height: '100%',
|
|
240
|
+
zIndex: 1
|
|
241
|
+
},
|
|
242
|
+
containContainer: {
|
|
243
|
+
width: '100%',
|
|
244
|
+
height: '100%',
|
|
245
|
+
overflow: 'hidden',
|
|
246
|
+
position: 'relative'
|
|
247
|
+
},
|
|
248
|
+
containImage: {
|
|
249
|
+
position: 'absolute',
|
|
250
|
+
width: '100%',
|
|
251
|
+
height: '100%'
|
|
104
252
|
}
|
|
105
253
|
});
|
|
106
254
|
CardBase.propTypes = {
|
|
@@ -115,7 +263,9 @@ CardBase.propTypes = {
|
|
|
115
263
|
// src is an object when used responsively to provide different image sources for different screen sizes
|
|
116
264
|
src: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]).isRequired,
|
|
117
265
|
alt: PropTypes.string,
|
|
118
|
-
resizeMode: responsiveProps.getTypeOptionallyByViewport(PropTypes.oneOf(['cover', 'contain', 'stretch', 'repeat', 'center']))
|
|
266
|
+
resizeMode: responsiveProps.getTypeOptionallyByViewport(PropTypes.oneOf(['cover', 'contain', 'stretch', 'repeat', 'center'])),
|
|
267
|
+
position: responsiveProps.getTypeOptionallyByViewport(PropTypes.oneOf(['bottom', 'left', 'right', 'top'])),
|
|
268
|
+
align: responsiveProps.getTypeOptionallyByViewport(PropTypes.oneOf(['start', 'end', 'center', 'stretch']))
|
|
119
269
|
})
|
|
120
270
|
};
|
|
121
271
|
export default CardBase;
|
|
@@ -31,6 +31,7 @@ const PressableCardBase = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
31
31
|
href,
|
|
32
32
|
hrefAttrs,
|
|
33
33
|
dataSet,
|
|
34
|
+
backgroundImage,
|
|
34
35
|
accessibilityRole = href ? 'link' : undefined,
|
|
35
36
|
...rawRest
|
|
36
37
|
} = _ref;
|
|
@@ -125,10 +126,7 @@ const PressableCardBase = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
125
126
|
setFocused(false);
|
|
126
127
|
setPressed(false);
|
|
127
128
|
},
|
|
128
|
-
style:
|
|
129
|
-
...staticStyles.container,
|
|
130
|
-
textDecoration: 'none'
|
|
131
|
-
},
|
|
129
|
+
style: staticStyles.linkContainer,
|
|
132
130
|
...(hrefAttrs || {}),
|
|
133
131
|
children: /*#__PURE__*/_jsx(CardBase, {
|
|
134
132
|
tokens: getCardTokens({
|
|
@@ -136,6 +134,7 @@ const PressableCardBase = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
136
134
|
focused,
|
|
137
135
|
hovered
|
|
138
136
|
}),
|
|
137
|
+
backgroundImage: backgroundImage,
|
|
139
138
|
children: typeof children === 'function' ? children(getCardState({
|
|
140
139
|
pressed,
|
|
141
140
|
focused,
|
|
@@ -159,6 +158,7 @@ const PressableCardBase = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
159
158
|
}),
|
|
160
159
|
children: pressableState => /*#__PURE__*/_jsx(CardBase, {
|
|
161
160
|
tokens: getCardTokens(pressableState),
|
|
161
|
+
backgroundImage: backgroundImage,
|
|
162
162
|
children: typeof children === 'function' ? children(getCardState(pressableState)) : children
|
|
163
163
|
})
|
|
164
164
|
});
|
|
@@ -167,6 +167,11 @@ const staticStyles = StyleSheet.create({
|
|
|
167
167
|
container: {
|
|
168
168
|
flex: 1,
|
|
169
169
|
display: 'flex'
|
|
170
|
+
},
|
|
171
|
+
linkContainer: {
|
|
172
|
+
flex: 1,
|
|
173
|
+
display: 'flex',
|
|
174
|
+
textDecoration: 'none'
|
|
170
175
|
}
|
|
171
176
|
});
|
|
172
177
|
PressableCardBase.displayName = 'PressableCardBase';
|
|
@@ -177,6 +182,15 @@ PressableCardBase.propTypes = {
|
|
|
177
182
|
partial: true,
|
|
178
183
|
allowFunction: true
|
|
179
184
|
}),
|
|
180
|
-
variant: variantProp.propType
|
|
185
|
+
variant: variantProp.propType,
|
|
186
|
+
backgroundImage: PropTypes.shape({
|
|
187
|
+
// The image src is either a URI string or a number (when a local image src is bundled in IOS or Android app)
|
|
188
|
+
// src is an object when used responsively to provide different image sources for different screen sizes
|
|
189
|
+
src: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]).isRequired,
|
|
190
|
+
alt: PropTypes.string,
|
|
191
|
+
resizeMode: PropTypes.oneOfType([PropTypes.oneOf(['cover', 'contain', 'stretch', 'repeat', 'center']), PropTypes.object]),
|
|
192
|
+
position: PropTypes.oneOfType([PropTypes.oneOf(['bottom', 'left', 'right', 'top']), PropTypes.object]),
|
|
193
|
+
align: PropTypes.oneOfType([PropTypes.oneOf(['start', 'end', 'center', 'stretch']), PropTypes.object])
|
|
194
|
+
})
|
|
181
195
|
};
|
|
182
196
|
export default withLinkRouter(PressableCardBase);
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import Platform from "react-native-web/dist/exports/Platform";
|
|
2
|
+
const webStyles = {
|
|
3
|
+
'top-start': {
|
|
4
|
+
top: 0,
|
|
5
|
+
left: 0
|
|
6
|
+
},
|
|
7
|
+
'top-center': {
|
|
8
|
+
top: 0,
|
|
9
|
+
left: '50%',
|
|
10
|
+
transform: [{
|
|
11
|
+
translateX: '-50%'
|
|
12
|
+
}]
|
|
13
|
+
},
|
|
14
|
+
'top-end': {
|
|
15
|
+
top: 0,
|
|
16
|
+
right: 0
|
|
17
|
+
},
|
|
18
|
+
'right-start': {
|
|
19
|
+
top: 0,
|
|
20
|
+
right: 0
|
|
21
|
+
},
|
|
22
|
+
'left-start': {
|
|
23
|
+
top: 0,
|
|
24
|
+
left: 0
|
|
25
|
+
},
|
|
26
|
+
'left-center': {
|
|
27
|
+
top: '50%',
|
|
28
|
+
left: 0,
|
|
29
|
+
transform: [{
|
|
30
|
+
translateY: '-50%'
|
|
31
|
+
}]
|
|
32
|
+
},
|
|
33
|
+
'right-center': {
|
|
34
|
+
top: '50%',
|
|
35
|
+
right: 0,
|
|
36
|
+
transform: [{
|
|
37
|
+
translateY: '-50%'
|
|
38
|
+
}]
|
|
39
|
+
},
|
|
40
|
+
'bottom-start': {
|
|
41
|
+
bottom: 0,
|
|
42
|
+
left: 0
|
|
43
|
+
},
|
|
44
|
+
'left-end': {
|
|
45
|
+
bottom: 0,
|
|
46
|
+
left: 0
|
|
47
|
+
},
|
|
48
|
+
'bottom-center': {
|
|
49
|
+
bottom: 0,
|
|
50
|
+
left: '50%',
|
|
51
|
+
transform: [{
|
|
52
|
+
translateX: '-50%'
|
|
53
|
+
}]
|
|
54
|
+
},
|
|
55
|
+
'bottom-end': {
|
|
56
|
+
bottom: 0,
|
|
57
|
+
right: 0
|
|
58
|
+
},
|
|
59
|
+
'right-end': {
|
|
60
|
+
bottom: 0,
|
|
61
|
+
right: 0
|
|
62
|
+
},
|
|
63
|
+
'top-stretch': {
|
|
64
|
+
top: 0,
|
|
65
|
+
left: 0,
|
|
66
|
+
right: 0,
|
|
67
|
+
width: '100%'
|
|
68
|
+
},
|
|
69
|
+
'left-stretch': {
|
|
70
|
+
top: 0,
|
|
71
|
+
bottom: 0,
|
|
72
|
+
left: 0,
|
|
73
|
+
height: '100%'
|
|
74
|
+
},
|
|
75
|
+
'right-stretch': {
|
|
76
|
+
top: 0,
|
|
77
|
+
bottom: 0,
|
|
78
|
+
right: 0,
|
|
79
|
+
height: '100%'
|
|
80
|
+
},
|
|
81
|
+
'bottom-stretch': {
|
|
82
|
+
bottom: 0,
|
|
83
|
+
left: 0,
|
|
84
|
+
right: 0,
|
|
85
|
+
width: '100%'
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const nativeStyles = {
|
|
89
|
+
'top-start': {
|
|
90
|
+
top: 0,
|
|
91
|
+
left: 0,
|
|
92
|
+
width: 150,
|
|
93
|
+
height: 200
|
|
94
|
+
},
|
|
95
|
+
'top-center': {
|
|
96
|
+
top: 0,
|
|
97
|
+
left: '50%',
|
|
98
|
+
marginLeft: -75,
|
|
99
|
+
width: 150,
|
|
100
|
+
height: 200
|
|
101
|
+
},
|
|
102
|
+
'top-end': {
|
|
103
|
+
top: 0,
|
|
104
|
+
right: 0,
|
|
105
|
+
width: 150,
|
|
106
|
+
height: 200
|
|
107
|
+
},
|
|
108
|
+
'right-start': {
|
|
109
|
+
top: 0,
|
|
110
|
+
right: 0,
|
|
111
|
+
width: 150,
|
|
112
|
+
height: 200
|
|
113
|
+
},
|
|
114
|
+
'left-start': {
|
|
115
|
+
top: 0,
|
|
116
|
+
left: 0,
|
|
117
|
+
width: 150,
|
|
118
|
+
height: 200
|
|
119
|
+
},
|
|
120
|
+
'left-center': {
|
|
121
|
+
left: 0,
|
|
122
|
+
top: '50%',
|
|
123
|
+
marginTop: -100,
|
|
124
|
+
width: 150,
|
|
125
|
+
height: 200
|
|
126
|
+
},
|
|
127
|
+
'right-center': {
|
|
128
|
+
right: 0,
|
|
129
|
+
top: '50%',
|
|
130
|
+
marginTop: -100,
|
|
131
|
+
width: 150,
|
|
132
|
+
height: 200
|
|
133
|
+
},
|
|
134
|
+
'bottom-start': {
|
|
135
|
+
bottom: 0,
|
|
136
|
+
left: 0,
|
|
137
|
+
width: 150,
|
|
138
|
+
height: 200
|
|
139
|
+
},
|
|
140
|
+
'left-end': {
|
|
141
|
+
bottom: 0,
|
|
142
|
+
left: 0,
|
|
143
|
+
width: 150,
|
|
144
|
+
height: 200
|
|
145
|
+
},
|
|
146
|
+
'bottom-center': {
|
|
147
|
+
bottom: 0,
|
|
148
|
+
left: '50%',
|
|
149
|
+
marginLeft: -75,
|
|
150
|
+
width: 150,
|
|
151
|
+
height: 200
|
|
152
|
+
},
|
|
153
|
+
'bottom-end': {
|
|
154
|
+
bottom: 0,
|
|
155
|
+
right: 0,
|
|
156
|
+
width: 150,
|
|
157
|
+
height: 200
|
|
158
|
+
},
|
|
159
|
+
'right-end': {
|
|
160
|
+
bottom: 0,
|
|
161
|
+
right: 0,
|
|
162
|
+
width: 150,
|
|
163
|
+
height: 200
|
|
164
|
+
},
|
|
165
|
+
'top-stretch': {
|
|
166
|
+
top: 0,
|
|
167
|
+
left: 0,
|
|
168
|
+
right: 0,
|
|
169
|
+
width: '100%'
|
|
170
|
+
},
|
|
171
|
+
'left-stretch': {
|
|
172
|
+
top: 0,
|
|
173
|
+
bottom: 0,
|
|
174
|
+
left: 0,
|
|
175
|
+
height: '100%'
|
|
176
|
+
},
|
|
177
|
+
'right-stretch': {
|
|
178
|
+
top: 0,
|
|
179
|
+
bottom: 0,
|
|
180
|
+
right: 0,
|
|
181
|
+
height: '100%'
|
|
182
|
+
},
|
|
183
|
+
'bottom-stretch': {
|
|
184
|
+
bottom: 0,
|
|
185
|
+
left: 0,
|
|
186
|
+
right: 0,
|
|
187
|
+
width: '100%'
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
export default Platform.OS === 'web' ? webStyles : nativeStyles;
|
|
@@ -17,17 +17,14 @@ const CONTENT_FULL_WIDTH = 'full';
|
|
|
17
17
|
* Resolves the maximum width for content based on the provided value and responsive width.
|
|
18
18
|
*
|
|
19
19
|
* @param {number|string|null|undefined} contentMinWidthValue - The minimum width value for the content.
|
|
20
|
-
* Can be a number
|
|
21
|
-
* @param {number
|
|
22
|
-
* @returns {number|string|null} The resolved maximum width value,
|
|
20
|
+
* Can be a number, a special string constant (e.g., CONTENT_FULL_WIDTH, CONTENT_MAX_WIDTH), or null/undefined.
|
|
21
|
+
* @param {number} responsiveWidth - The responsive width to use when contentMinWidthValue is CONTENT_MAX_WIDTH.
|
|
22
|
+
* @returns {number|string|null} The resolved maximum width value, or null if full width is desired.
|
|
23
23
|
*/
|
|
24
24
|
const resolveContentMaxWidth = (contentMinWidthValue, responsiveWidth) => {
|
|
25
|
-
if (!contentMinWidthValue) {
|
|
25
|
+
if (!contentMinWidthValue || contentMinWidthValue === CONTENT_FULL_WIDTH) {
|
|
26
26
|
return null;
|
|
27
27
|
}
|
|
28
|
-
if (contentMinWidthValue === CONTENT_FULL_WIDTH) {
|
|
29
|
-
return '100%';
|
|
30
|
-
}
|
|
31
28
|
if (Number.isFinite(contentMinWidthValue)) {
|
|
32
29
|
return contentMinWidthValue;
|
|
33
30
|
}
|
|
@@ -37,6 +34,25 @@ const resolveContentMaxWidth = (contentMinWidthValue, responsiveWidth) => {
|
|
|
37
34
|
return contentMinWidthValue;
|
|
38
35
|
};
|
|
39
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Calculates the maximum width for a given viewport based on limitWidth and contentMinWidth settings.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} viewportKey - The viewport key ('xs', 'sm', 'md', 'lg', 'xl')
|
|
41
|
+
* @param {boolean} limitWidth - Whether to limit the width to viewport breakpoints
|
|
42
|
+
* @param {any} contentMinWidth - The contentMinWidth prop value
|
|
43
|
+
* @param {number|string|null} maxWidth - The resolved max width value
|
|
44
|
+
* @returns {number|string|null} The calculated maximum width for the viewport
|
|
45
|
+
*/
|
|
46
|
+
const getMaxWidthForViewport = (viewportKey, limitWidth, contentMinWidth, maxWidth) => {
|
|
47
|
+
if (limitWidth) {
|
|
48
|
+
return viewports.map.get(viewportKey === 'xs' ? 'sm' : viewportKey);
|
|
49
|
+
}
|
|
50
|
+
if (contentMinWidth) {
|
|
51
|
+
return maxWidth;
|
|
52
|
+
}
|
|
53
|
+
return viewportKey === 'xl' ? viewports.map.get('xl') : null;
|
|
54
|
+
};
|
|
55
|
+
|
|
40
56
|
/**
|
|
41
57
|
* A mobile-first flexbox grid.
|
|
42
58
|
*/
|
|
@@ -73,23 +89,23 @@ const FlexGrid = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
73
89
|
const maxWidth = resolveContentMaxWidth(contentMinWidthValue, responsiveWidth);
|
|
74
90
|
const stylesByViewport = {
|
|
75
91
|
xs: {
|
|
76
|
-
maxWidth:
|
|
92
|
+
maxWidth: getMaxWidthForViewport('xs', limitWidth, contentMinWidth, maxWidth),
|
|
77
93
|
flexDirection: reverseLevel[0] ? 'column-reverse' : 'column'
|
|
78
94
|
},
|
|
79
95
|
sm: {
|
|
80
|
-
maxWidth:
|
|
96
|
+
maxWidth: getMaxWidthForViewport('sm', limitWidth, contentMinWidth, maxWidth),
|
|
81
97
|
flexDirection: reverseLevel[1] ? 'column-reverse' : 'column'
|
|
82
98
|
},
|
|
83
99
|
md: {
|
|
84
|
-
maxWidth:
|
|
100
|
+
maxWidth: getMaxWidthForViewport('md', limitWidth, contentMinWidth, maxWidth),
|
|
85
101
|
flexDirection: reverseLevel[2] ? 'column-reverse' : 'column'
|
|
86
102
|
},
|
|
87
103
|
lg: {
|
|
88
|
-
maxWidth:
|
|
104
|
+
maxWidth: getMaxWidthForViewport('lg', limitWidth, contentMinWidth, maxWidth),
|
|
89
105
|
flexDirection: reverseLevel[3] ? 'column-reverse' : 'column'
|
|
90
106
|
},
|
|
91
107
|
xl: {
|
|
92
|
-
maxWidth:
|
|
108
|
+
maxWidth: getMaxWidthForViewport('xl', limitWidth, contentMinWidth, maxWidth),
|
|
93
109
|
flexDirection: reverseLevel[4] ? 'column-reverse' : 'column'
|
|
94
110
|
}
|
|
95
111
|
};
|
package/lib/esm/Tabs/Tabs.js
CHANGED
|
@@ -4,9 +4,10 @@ import PropTypes from 'prop-types';
|
|
|
4
4
|
import ABBPropTypes from 'airbnb-prop-types';
|
|
5
5
|
import { useThemeTokens } from '../ThemeProvider';
|
|
6
6
|
import StackView from '../StackView';
|
|
7
|
-
import { a11yProps, getTokensPropType, focusHandlerProps, pressProps, selectSystemProps, useHash, useInputValue, variantProp, viewProps, withLinkRouter } from '../utils';
|
|
7
|
+
import { a11yProps, getTokensPropType, focusHandlerProps, pressProps, selectSystemProps, useHash, useInputValue, useResponsiveProp, variantProp, viewProps, withLinkRouter } from '../utils';
|
|
8
8
|
import HorizontalScroll, { horizontalScrollUtils, HorizontalScrollButton } from '../HorizontalScroll';
|
|
9
9
|
import TabsItem from './TabsItem';
|
|
10
|
+
import TabsDropdown from './TabsDropdown';
|
|
10
11
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
12
|
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
|
|
12
13
|
const [selectItemProps, selectedItemPropTypes] = selectSystemProps([a11yProps, focusHandlerProps, pressProps, viewProps]);
|
|
@@ -39,6 +40,12 @@ const getStackViewTokens = variant => {
|
|
|
39
40
|
* Tabs renders a horizontally-scrolling menu of selectable buttons which may link
|
|
40
41
|
* to a page or control what content is displayed on this page.
|
|
41
42
|
*
|
|
43
|
+
* By default, Tabs always renders as horizontal scrolling tabs regardless of viewport.
|
|
44
|
+
* To enable dropdown mode, you must explicitly pass `variant={{ dropdown: true }}`.
|
|
45
|
+
* When dropdown is enabled, it will only render as a dropdown on mobile and tablet
|
|
46
|
+
* viewports (XS and SM). On larger viewports (MD, LG, XL), it will still render as
|
|
47
|
+
* horizontal tabs even with dropdown enabled.
|
|
48
|
+
*
|
|
42
49
|
* If you are using Tabs to navigate to a new page (web-only) you should pass
|
|
43
50
|
* `navigation`as the `accessibilityRole` to te Tabs component, this will cause
|
|
44
51
|
* TabItems to default to a role of link and obtain aria-current behaviour.
|
|
@@ -84,6 +91,31 @@ const Tabs = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
84
91
|
const parentAccessibilityRole = restProps.accessibilityRole ?? 'tablist';
|
|
85
92
|
const defaultTabItemAccessibiltyRole = getDefaultTabItemAccessibilityRole(parentAccessibilityRole);
|
|
86
93
|
const stackViewTokens = getStackViewTokens(variant);
|
|
94
|
+
|
|
95
|
+
// Render dropdown only if explicitly requested via variant AND viewport is xs or sm
|
|
96
|
+
const isSmallViewport = useResponsiveProp({
|
|
97
|
+
xs: true,
|
|
98
|
+
sm: true,
|
|
99
|
+
md: false,
|
|
100
|
+
lg: false,
|
|
101
|
+
xl: false
|
|
102
|
+
}, false);
|
|
103
|
+
const shouldRenderDropdown = variant?.dropdown === true && isSmallViewport;
|
|
104
|
+
if (shouldRenderDropdown) {
|
|
105
|
+
return /*#__PURE__*/_jsx(TabsDropdown, {
|
|
106
|
+
ref: ref,
|
|
107
|
+
tokens: tokens,
|
|
108
|
+
itemTokens: itemTokens,
|
|
109
|
+
variant: variant,
|
|
110
|
+
value: currentValue,
|
|
111
|
+
onChange: setValue,
|
|
112
|
+
items: items,
|
|
113
|
+
LinkRouter: LinkRouter,
|
|
114
|
+
linkRouterProps: linkRouterProps,
|
|
115
|
+
accessibilityRole: parentAccessibilityRole === 'tablist' ? 'button' : parentAccessibilityRole,
|
|
116
|
+
...restProps
|
|
117
|
+
});
|
|
118
|
+
}
|
|
87
119
|
return /*#__PURE__*/_jsx(HorizontalScroll, {
|
|
88
120
|
ref: ref,
|
|
89
121
|
ScrollButton: HorizontalScrollButton,
|
|
@@ -143,13 +175,13 @@ const Tabs = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
143
175
|
Tabs.displayName = 'Tabs';
|
|
144
176
|
Tabs.propTypes = {
|
|
145
177
|
...selectedSystemPropTypes,
|
|
146
|
-
...withLinkRouter.
|
|
178
|
+
...withLinkRouter.propTypes,
|
|
147
179
|
/**
|
|
148
180
|
* Array of `TabsItem`s
|
|
149
181
|
*/
|
|
150
182
|
items: PropTypes.arrayOf(PropTypes.shape({
|
|
151
183
|
...selectedItemPropTypes,
|
|
152
|
-
...withLinkRouter.
|
|
184
|
+
...withLinkRouter.propTypes,
|
|
153
185
|
href: PropTypes.string,
|
|
154
186
|
label: PropTypes.string,
|
|
155
187
|
id: PropTypes.string,
|