@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { viewports } from '@telus-uds/system-constants';
|
|
4
|
-
import { a11yProps, viewProps, getA11yPropsFromHtmlTag, layoutTags, selectSystemProps, BaseView, StyleSheet, createMediaQueryStyles } from '../utils';
|
|
4
|
+
import { a11yProps, viewProps, getA11yPropsFromHtmlTag, layoutTags, selectSystemProps, BaseView, StyleSheet, createMediaQueryStyles, useResponsiveProp } from '../utils';
|
|
5
5
|
import Row from './Row';
|
|
6
6
|
import Col from './Col';
|
|
7
7
|
import GutterContext from './providers/GutterContext';
|
|
@@ -10,6 +10,48 @@ import { useTheme } from '../ThemeProvider';
|
|
|
10
10
|
import { useViewport } from '../ViewportProvider';
|
|
11
11
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
12
12
|
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
|
|
13
|
+
const CONTENT_MAX_WIDTH = 'max';
|
|
14
|
+
const CONTENT_FULL_WIDTH = 'full';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resolves the maximum width for content based on the provided value and responsive width.
|
|
18
|
+
*
|
|
19
|
+
* @param {number|string|null|undefined} contentMinWidthValue - The minimum width value for the content.
|
|
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
|
+
*/
|
|
24
|
+
const resolveContentMaxWidth = (contentMinWidthValue, responsiveWidth) => {
|
|
25
|
+
if (!contentMinWidthValue || contentMinWidthValue === CONTENT_FULL_WIDTH) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
if (Number.isFinite(contentMinWidthValue)) {
|
|
29
|
+
return contentMinWidthValue;
|
|
30
|
+
}
|
|
31
|
+
if (contentMinWidthValue === CONTENT_MAX_WIDTH) {
|
|
32
|
+
return responsiveWidth;
|
|
33
|
+
}
|
|
34
|
+
return contentMinWidthValue;
|
|
35
|
+
};
|
|
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
|
+
};
|
|
13
55
|
|
|
14
56
|
/**
|
|
15
57
|
* A mobile-first flexbox grid.
|
|
@@ -17,7 +59,7 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, vie
|
|
|
17
59
|
|
|
18
60
|
const FlexGrid = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
19
61
|
let {
|
|
20
|
-
limitWidth =
|
|
62
|
+
limitWidth = false,
|
|
21
63
|
gutter = true,
|
|
22
64
|
outsideGutter = true,
|
|
23
65
|
xsReverse,
|
|
@@ -29,35 +71,41 @@ const FlexGrid = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
29
71
|
accessibilityRole,
|
|
30
72
|
children,
|
|
31
73
|
dataSet,
|
|
74
|
+
contentMinWidth,
|
|
32
75
|
...rest
|
|
33
76
|
} = _ref;
|
|
34
77
|
const reverseLevel = applyInheritance([xsReverse, smReverse, mdReverse, lgReverse, xlReverse]);
|
|
35
78
|
const viewport = useViewport();
|
|
36
79
|
const {
|
|
80
|
+
themeOptions,
|
|
37
81
|
themeOptions: {
|
|
38
82
|
enableMediaQueryStyleSheet
|
|
39
83
|
}
|
|
40
84
|
} = useTheme();
|
|
41
85
|
let flexgridStyles;
|
|
42
86
|
let mediaIds;
|
|
87
|
+
const contentMinWidthValue = useResponsiveProp(contentMinWidth);
|
|
88
|
+
const responsiveWidth = useResponsiveProp(themeOptions?.contentMaxWidth);
|
|
89
|
+
const maxWidth = resolveContentMaxWidth(contentMinWidthValue, responsiveWidth);
|
|
43
90
|
const stylesByViewport = {
|
|
44
91
|
xs: {
|
|
92
|
+
maxWidth: getMaxWidthForViewport('xs', limitWidth, contentMinWidth, maxWidth),
|
|
45
93
|
flexDirection: reverseLevel[0] ? 'column-reverse' : 'column'
|
|
46
94
|
},
|
|
47
95
|
sm: {
|
|
48
|
-
maxWidth:
|
|
96
|
+
maxWidth: getMaxWidthForViewport('sm', limitWidth, contentMinWidth, maxWidth),
|
|
49
97
|
flexDirection: reverseLevel[1] ? 'column-reverse' : 'column'
|
|
50
98
|
},
|
|
51
99
|
md: {
|
|
52
|
-
maxWidth:
|
|
100
|
+
maxWidth: getMaxWidthForViewport('md', limitWidth, contentMinWidth, maxWidth),
|
|
53
101
|
flexDirection: reverseLevel[2] ? 'column-reverse' : 'column'
|
|
54
102
|
},
|
|
55
103
|
lg: {
|
|
56
|
-
maxWidth:
|
|
104
|
+
maxWidth: getMaxWidthForViewport('lg', limitWidth, contentMinWidth, maxWidth),
|
|
57
105
|
flexDirection: reverseLevel[3] ? 'column-reverse' : 'column'
|
|
58
106
|
},
|
|
59
107
|
xl: {
|
|
60
|
-
maxWidth:
|
|
108
|
+
maxWidth: getMaxWidthForViewport('xl', limitWidth, contentMinWidth, maxWidth),
|
|
61
109
|
flexDirection: reverseLevel[4] ? 'column-reverse' : 'column'
|
|
62
110
|
}
|
|
63
111
|
};
|
|
@@ -151,7 +199,24 @@ FlexGrid.propTypes = {
|
|
|
151
199
|
/**
|
|
152
200
|
* The rows and columns of the Grid. Will typically be `FlexGrid.Row` and `FlexGrid.Col` components.
|
|
153
201
|
*/
|
|
154
|
-
children: PropTypes.node.isRequired
|
|
202
|
+
children: PropTypes.node.isRequired,
|
|
203
|
+
/**
|
|
204
|
+
* The minimum width of the content in the FlexGrid.
|
|
205
|
+
* This prop accepts responsive values for different viewports. If a number is provided,
|
|
206
|
+
* it will be the max content width for the desired viewport.
|
|
207
|
+
* - `xs`: 'max' | 'full' | <number>
|
|
208
|
+
* - `sm`: 'max' | 'full' | <number>
|
|
209
|
+
* - `md`: 'max' | 'full' | <number>
|
|
210
|
+
* - `lg`: 'max' | 'full' | <number>
|
|
211
|
+
* - `xl`: 'max' | 'full' | <number>
|
|
212
|
+
*/
|
|
213
|
+
contentMinWidth: PropTypes.shape({
|
|
214
|
+
xl: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
|
|
215
|
+
lg: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
|
|
216
|
+
md: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
|
|
217
|
+
sm: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
|
|
218
|
+
xs: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number])
|
|
219
|
+
})
|
|
155
220
|
};
|
|
156
221
|
FlexGrid.Row = Row;
|
|
157
222
|
FlexGrid.Col = Col;
|
package/lib/esm/Icon/Icon.js
CHANGED
|
@@ -32,7 +32,9 @@ const Icon = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
32
32
|
width: themeTokens.size + themeTokens.width * 2,
|
|
33
33
|
// sets the diameter of the circle which is the size of the icon plus twice the general padding established to obtain a perfect circle
|
|
34
34
|
height: themeTokens.size + themeTokens.width * 2
|
|
35
|
-
} : {
|
|
35
|
+
} : {
|
|
36
|
+
padding: themeTokens.padding
|
|
37
|
+
};
|
|
36
38
|
const getIconContentForMobile = () => {
|
|
37
39
|
if (Object.keys(paddingStyles).length) {
|
|
38
40
|
return /*#__PURE__*/_jsx(View, {
|
|
@@ -142,7 +142,7 @@ InputLabel.propTypes = {
|
|
|
142
142
|
/**
|
|
143
143
|
* Content of an optional `Tooltip`. If set, a tooltip button will be shown next to the label.
|
|
144
144
|
*/
|
|
145
|
-
tooltip: PropTypes.oneOfType([tooltipPropTypes, PropTypes.string]),
|
|
145
|
+
tooltip: PropTypes.oneOfType([PropTypes.shape(tooltipPropTypes), PropTypes.string]),
|
|
146
146
|
/**
|
|
147
147
|
* Current number of characterts of an input text.
|
|
148
148
|
*/
|
|
@@ -116,7 +116,7 @@ InputSupports.propTypes = {
|
|
|
116
116
|
* 1. `tooltip` as a string - The content of the tooltip.
|
|
117
117
|
* 2. `tooltip` as an object - Tooltip component props to be passed.
|
|
118
118
|
*/
|
|
119
|
-
tooltip: PropTypes.oneOfType([tooltipPropTypes, PropTypes.string]),
|
|
119
|
+
tooltip: PropTypes.oneOfType([PropTypes.shape(tooltipPropTypes), PropTypes.string]),
|
|
120
120
|
/**
|
|
121
121
|
* Use to visually mark an input as valid or invalid.
|
|
122
122
|
*/
|
|
@@ -9,6 +9,7 @@ import useCopy from '../utils/useCopy';
|
|
|
9
9
|
import dictionary from './dictionary';
|
|
10
10
|
import { useViewport } from '../ViewportProvider';
|
|
11
11
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
12
|
+
const CONTENT_MAX_WIDTH = 'max';
|
|
12
13
|
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
|
|
13
14
|
const selectContainerStyles = tokens => ({
|
|
14
15
|
...tokens
|
|
@@ -63,13 +64,13 @@ const selectDismissButtonContainerStyles = _ref4 => {
|
|
|
63
64
|
placeContent: 'start'
|
|
64
65
|
};
|
|
65
66
|
};
|
|
66
|
-
const selectContentContainerStyle = (themeTokens, maxWidth, system, viewport) => ({
|
|
67
|
-
maxWidth: system && viewport === 'xl' ? maxWidth : '100%',
|
|
67
|
+
const selectContentContainerStyle = (themeTokens, maxWidth, system, viewport, useContentMaxWidth) => ({
|
|
68
|
+
maxWidth: system && (useContentMaxWidth || viewport === 'xl') ? maxWidth : '100%',
|
|
68
69
|
width: '100%',
|
|
69
70
|
paddingRight: themeTokens?.containerPaddingRight,
|
|
70
71
|
paddingLeft: themeTokens?.containerPaddingLeft
|
|
71
72
|
});
|
|
72
|
-
const getMediaQueryStyles = (themeTokens, themeOptions, maxWidth, mediaIdsRef, dismissible, viewport, system) => {
|
|
73
|
+
const getMediaQueryStyles = (themeTokens, themeOptions, maxWidth, mediaIdsRef, dismissible, viewport, system, useContentMaxWidth) => {
|
|
73
74
|
const transformedSelectContainerStyles = Object.entries(themeTokens).reduce((acc, _ref5) => {
|
|
74
75
|
let [vp, viewportTokens] = _ref5;
|
|
75
76
|
acc[vp] = {
|
|
@@ -92,7 +93,7 @@ const getMediaQueryStyles = (themeTokens, themeOptions, maxWidth, mediaIdsRef, d
|
|
|
92
93
|
const transformedSelectContentContainerStyles = Object.entries(themeTokens).reduce((acc, _ref6) => {
|
|
93
94
|
let [vp, viewportTokens] = _ref6;
|
|
94
95
|
acc[vp] = {
|
|
95
|
-
...selectContentContainerStyle(viewportTokens, maxWidth, system, vp),
|
|
96
|
+
...selectContentContainerStyle(viewportTokens, maxWidth, system, vp, useContentMaxWidth),
|
|
96
97
|
flexDirection: 'row',
|
|
97
98
|
flexShrink: 1,
|
|
98
99
|
justifyContent: 'space-between',
|
|
@@ -169,7 +170,7 @@ const getMediaQueryStyles = (themeTokens, themeOptions, maxWidth, mediaIdsRef, d
|
|
|
169
170
|
selectDismissIconPropsStyles
|
|
170
171
|
};
|
|
171
172
|
};
|
|
172
|
-
const getDefaultStyles = (themeTokens, themeOptions, maxWidth, dismissible, viewport, system) => ({
|
|
173
|
+
const getDefaultStyles = (themeTokens, themeOptions, maxWidth, dismissible, viewport, system, useContentMaxWidth) => ({
|
|
173
174
|
containerStyles: {
|
|
174
175
|
container: {
|
|
175
176
|
flexDirection: 'column',
|
|
@@ -181,7 +182,7 @@ const getDefaultStyles = (themeTokens, themeOptions, maxWidth, dismissible, view
|
|
|
181
182
|
flexDirection: 'row',
|
|
182
183
|
flexShrink: 1,
|
|
183
184
|
justifyContent: 'space-between',
|
|
184
|
-
...selectContentContainerStyle(themeTokens, maxWidth, system, viewport),
|
|
185
|
+
...selectContentContainerStyle(themeTokens, maxWidth, system, viewport, useContentMaxWidth),
|
|
185
186
|
...(system && {
|
|
186
187
|
alignSelf: 'center'
|
|
187
188
|
})
|
|
@@ -280,6 +281,7 @@ const Notification = /*#__PURE__*/React.forwardRef((_ref7, ref) => {
|
|
|
280
281
|
tokens,
|
|
281
282
|
variant,
|
|
282
283
|
onDismiss,
|
|
284
|
+
contentMinWidth,
|
|
283
285
|
...rest
|
|
284
286
|
} = _ref7;
|
|
285
287
|
const [isDismissed, setIsDismissed] = React.useState(false);
|
|
@@ -302,6 +304,7 @@ const Notification = /*#__PURE__*/React.forwardRef((_ref7, ref) => {
|
|
|
302
304
|
system: isSystemEnabled,
|
|
303
305
|
viewport
|
|
304
306
|
});
|
|
307
|
+
const useContentMaxWidth = useResponsiveProp(contentMinWidth) === CONTENT_MAX_WIDTH;
|
|
305
308
|
const maxWidth = useResponsiveProp(themeOptions?.contentMaxWidth, viewports.map.get(viewports.xl));
|
|
306
309
|
const notificationComponentRef = React.useRef({
|
|
307
310
|
containerStyles: {},
|
|
@@ -324,9 +327,9 @@ const Notification = /*#__PURE__*/React.forwardRef((_ref7, ref) => {
|
|
|
324
327
|
selectDismissIconPropsIds: {}
|
|
325
328
|
});
|
|
326
329
|
if (enableMediaQueryStyleSheet) {
|
|
327
|
-
notificationComponentRef.current = getMediaQueryStyles(themeTokens, themeOptions, maxWidth, mediaIdsRef, dismissible, viewport, isSystemEnabled);
|
|
330
|
+
notificationComponentRef.current = getMediaQueryStyles(themeTokens, themeOptions, maxWidth, mediaIdsRef, dismissible, viewport, isSystemEnabled, useContentMaxWidth);
|
|
328
331
|
} else {
|
|
329
|
-
notificationComponentRef.current = getDefaultStyles(themeTokens, themeOptions, maxWidth, dismissible, viewport, isSystemEnabled);
|
|
332
|
+
notificationComponentRef.current = getDefaultStyles(themeTokens, themeOptions, maxWidth, dismissible, viewport, isSystemEnabled, useContentMaxWidth);
|
|
330
333
|
}
|
|
331
334
|
if (isDismissed) {
|
|
332
335
|
return null;
|
|
@@ -423,6 +426,22 @@ Notification.propTypes = {
|
|
|
423
426
|
* Callback function called when the dismiss button is clicked
|
|
424
427
|
*/
|
|
425
428
|
onDismiss: PropTypes.func,
|
|
429
|
+
/**
|
|
430
|
+
* The minimum width of the content in the Notification when using the system variant.
|
|
431
|
+
* This prop accepts responsive values for different viewports.
|
|
432
|
+
* - `xs`: 'max' | 'full'
|
|
433
|
+
* - `sm`: 'max' | 'full'
|
|
434
|
+
* - `md`: 'max' | 'full'
|
|
435
|
+
* - `lg`: 'max' | 'full'
|
|
436
|
+
* - `xl`: 'max' | 'full'
|
|
437
|
+
*/
|
|
438
|
+
contentMinWidth: PropTypes.shape({
|
|
439
|
+
xl: PropTypes.oneOf(['max', 'full']),
|
|
440
|
+
lg: PropTypes.oneOf(['max', 'full']),
|
|
441
|
+
md: PropTypes.oneOf(['max', 'full']),
|
|
442
|
+
sm: PropTypes.oneOf(['max', 'full']),
|
|
443
|
+
xs: PropTypes.oneOf(['max', 'full'])
|
|
444
|
+
}),
|
|
426
445
|
tokens: getTokensPropType('Notification'),
|
|
427
446
|
variant: variantProp.propType
|
|
428
447
|
};
|
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,
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import Pressable from "react-native-web/dist/exports/Pressable";
|
|
4
|
+
import StyleSheet from "react-native-web/dist/exports/StyleSheet";
|
|
5
|
+
import Text from "react-native-web/dist/exports/Text";
|
|
6
|
+
import View from "react-native-web/dist/exports/View";
|
|
7
|
+
import { useThemeTokensCallback, applyTextStyles, useTheme } from '../ThemeProvider';
|
|
8
|
+
import { a11yProps, getTokensPropType, resolvePressableTokens, selectSystemProps, selectTokens, useOverlaidPosition, useCopy, variantProp, viewProps, withLinkRouter } from '../utils';
|
|
9
|
+
import { useViewport } from '../ViewportProvider';
|
|
10
|
+
import Icon from '../Icon';
|
|
11
|
+
import Listbox from '../Listbox';
|
|
12
|
+
import dictionary from './dictionary';
|
|
13
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
14
|
+
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
|
|
15
|
+
const selectButtonContentStyles = _ref => {
|
|
16
|
+
let {
|
|
17
|
+
backgroundColor,
|
|
18
|
+
borderColor,
|
|
19
|
+
borderWidth,
|
|
20
|
+
borderRadius,
|
|
21
|
+
paddingHorizontal,
|
|
22
|
+
paddingVertical,
|
|
23
|
+
marginHorizontal,
|
|
24
|
+
marginVertical
|
|
25
|
+
} = _ref;
|
|
26
|
+
return {
|
|
27
|
+
backgroundColor,
|
|
28
|
+
borderColor,
|
|
29
|
+
borderWidth,
|
|
30
|
+
borderRadius,
|
|
31
|
+
paddingHorizontal,
|
|
32
|
+
paddingVertical,
|
|
33
|
+
marginLeft: marginHorizontal,
|
|
34
|
+
marginRight: marginHorizontal,
|
|
35
|
+
marginTop: marginVertical,
|
|
36
|
+
marginBottom: marginVertical
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* TabsDropdown renders a dropdown version of tabs for mobile/tablet viewports.
|
|
42
|
+
* It shows the currently selected tab as a button that opens a dropdown menu
|
|
43
|
+
* containing all available tabs.
|
|
44
|
+
*
|
|
45
|
+
* This is rendered automatically by `Tabs` on mobile viewports and when variant
|
|
46
|
+
* is dropdown and isn't intended to be used directly.
|
|
47
|
+
*/
|
|
48
|
+
const TabsDropdown = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
|
|
49
|
+
let {
|
|
50
|
+
itemTokens,
|
|
51
|
+
variant,
|
|
52
|
+
value,
|
|
53
|
+
onChange,
|
|
54
|
+
items = [],
|
|
55
|
+
LinkRouter,
|
|
56
|
+
linkRouterProps,
|
|
57
|
+
accessibilityRole = 'button',
|
|
58
|
+
copy = 'en',
|
|
59
|
+
dictionary: customDictionary = dictionary,
|
|
60
|
+
...rest
|
|
61
|
+
} = _ref2;
|
|
62
|
+
const {
|
|
63
|
+
themeOptions
|
|
64
|
+
} = useTheme();
|
|
65
|
+
const viewport = useViewport();
|
|
66
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
67
|
+
const getTokens = useThemeTokensCallback('TabsItem', itemTokens, {
|
|
68
|
+
viewport,
|
|
69
|
+
...variant
|
|
70
|
+
});
|
|
71
|
+
const selectedItem = items.find(item => {
|
|
72
|
+
const itemId = item.id ?? item.label;
|
|
73
|
+
return value === itemId;
|
|
74
|
+
}) || items[0];
|
|
75
|
+
const {
|
|
76
|
+
overlaidPosition,
|
|
77
|
+
sourceRef,
|
|
78
|
+
targetRef,
|
|
79
|
+
onTargetLayout,
|
|
80
|
+
isReady
|
|
81
|
+
} = useOverlaidPosition({
|
|
82
|
+
isShown: isOpen,
|
|
83
|
+
offsets: {
|
|
84
|
+
vertical: 4
|
|
85
|
+
},
|
|
86
|
+
align: {
|
|
87
|
+
top: 'bottom',
|
|
88
|
+
left: 'left'
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
const handleToggle = () => setIsOpen(prev => !prev);
|
|
92
|
+
const handleClose = () => setIsOpen(false);
|
|
93
|
+
const handleItemSelect = (item, event) => {
|
|
94
|
+
const itemId = item.id ?? item.label;
|
|
95
|
+
setIsOpen(false);
|
|
96
|
+
if (onChange) onChange(itemId, event);
|
|
97
|
+
if (item.onPress) item.onPress(event);
|
|
98
|
+
};
|
|
99
|
+
const listboxItems = items.map(item => ({
|
|
100
|
+
...item,
|
|
101
|
+
onPress: event => handleItemSelect(item, event)
|
|
102
|
+
}));
|
|
103
|
+
const isSelected = Boolean(selectedItem && value);
|
|
104
|
+
const getCopy = useCopy({
|
|
105
|
+
dictionary: customDictionary,
|
|
106
|
+
copy
|
|
107
|
+
});
|
|
108
|
+
const selectedProps = selectProps({
|
|
109
|
+
accessibilityRole,
|
|
110
|
+
...rest
|
|
111
|
+
});
|
|
112
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
113
|
+
ref: ref,
|
|
114
|
+
style: styles.container,
|
|
115
|
+
children: [/*#__PURE__*/_jsx(Pressable, {
|
|
116
|
+
ref: sourceRef,
|
|
117
|
+
onPress: handleToggle,
|
|
118
|
+
...selectedProps,
|
|
119
|
+
style: styles.pressable,
|
|
120
|
+
children: pressableState => {
|
|
121
|
+
// Use resolvePressableTokens like TabBarItem does for proper state handling
|
|
122
|
+
const resolvedTokens = resolvePressableTokens(getTokens, pressableState, {
|
|
123
|
+
viewport,
|
|
124
|
+
expanded: isOpen,
|
|
125
|
+
selected: isSelected
|
|
126
|
+
});
|
|
127
|
+
const textStyles = applyTextStyles({
|
|
128
|
+
...selectTokens('Typography', resolvedTokens),
|
|
129
|
+
themeOptions
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Get dropdown icons from resolved tokens
|
|
133
|
+
const dropdownIcon = isOpen ? resolvedTokens.dropdownIconExpanded : resolvedTokens.dropdownIcon;
|
|
134
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
135
|
+
style: [styles.buttonContent, selectButtonContentStyles(resolvedTokens)],
|
|
136
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
137
|
+
style: textStyles,
|
|
138
|
+
children: selectedItem?.label || getCopy('selectTab')
|
|
139
|
+
}), dropdownIcon && /*#__PURE__*/_jsx(Icon, {
|
|
140
|
+
icon: dropdownIcon,
|
|
141
|
+
variant: {
|
|
142
|
+
size: 'micro'
|
|
143
|
+
},
|
|
144
|
+
tokens: {
|
|
145
|
+
color: textStyles.color
|
|
146
|
+
}
|
|
147
|
+
})]
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}), isOpen && /*#__PURE__*/_jsx(Listbox.Overlay, {
|
|
151
|
+
overlaidPosition: overlaidPosition,
|
|
152
|
+
maxWidth: 400,
|
|
153
|
+
minWidth: 200,
|
|
154
|
+
isReady: isReady,
|
|
155
|
+
onLayout: onTargetLayout,
|
|
156
|
+
children: /*#__PURE__*/_jsx(Listbox, {
|
|
157
|
+
items: listboxItems,
|
|
158
|
+
firstItemRef: targetRef,
|
|
159
|
+
parentRef: sourceRef,
|
|
160
|
+
selectedId: value,
|
|
161
|
+
onClose: handleClose,
|
|
162
|
+
LinkRouter: LinkRouter,
|
|
163
|
+
linkRouterProps: linkRouterProps
|
|
164
|
+
})
|
|
165
|
+
})]
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
TabsDropdown.displayName = 'TabsDropdown';
|
|
169
|
+
const dictionaryContentShape = PropTypes.shape({
|
|
170
|
+
selectTab: PropTypes.string.isRequired
|
|
171
|
+
});
|
|
172
|
+
TabsDropdown.propTypes = {
|
|
173
|
+
...selectedSystemPropTypes,
|
|
174
|
+
...withLinkRouter.propTypes,
|
|
175
|
+
/**
|
|
176
|
+
* Array of tab items
|
|
177
|
+
*/
|
|
178
|
+
items: PropTypes.arrayOf(PropTypes.shape({
|
|
179
|
+
...withLinkRouter.propTypes,
|
|
180
|
+
/** URL to navigate to when the tab is pressed */
|
|
181
|
+
href: PropTypes.string,
|
|
182
|
+
/** Display text for the tab */
|
|
183
|
+
label: PropTypes.string,
|
|
184
|
+
/** Unique identifier for the tab */
|
|
185
|
+
id: PropTypes.string,
|
|
186
|
+
/** Reference to the tab element */
|
|
187
|
+
ref: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
|
|
188
|
+
/** Custom render function for the tab content */
|
|
189
|
+
render: PropTypes.func
|
|
190
|
+
})),
|
|
191
|
+
/**
|
|
192
|
+
* Current selected tab id
|
|
193
|
+
*/
|
|
194
|
+
value: PropTypes.string,
|
|
195
|
+
/**
|
|
196
|
+
* Callback for when the selected tab changes
|
|
197
|
+
*/
|
|
198
|
+
onChange: PropTypes.func,
|
|
199
|
+
/**
|
|
200
|
+
* Custom tokens for the main Tabs container
|
|
201
|
+
*/
|
|
202
|
+
tokens: getTokensPropType('Tabs'),
|
|
203
|
+
/**
|
|
204
|
+
* Custom tokens for `TabsItem`
|
|
205
|
+
*/
|
|
206
|
+
itemTokens: getTokensPropType('TabsItem'),
|
|
207
|
+
/**
|
|
208
|
+
* Visual and behavioral variants for the tabs dropdown
|
|
209
|
+
*/
|
|
210
|
+
variant: variantProp.propType,
|
|
211
|
+
/**
|
|
212
|
+
* Select English or French copy for the accessible labels.
|
|
213
|
+
* You may also pass in a custom dictionary object.
|
|
214
|
+
*/
|
|
215
|
+
copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr']), dictionaryContentShape]),
|
|
216
|
+
/**
|
|
217
|
+
* Override the default dictionary, by passing the complete dictionary object for `en` and `fr`
|
|
218
|
+
*/
|
|
219
|
+
dictionary: PropTypes.shape({
|
|
220
|
+
en: dictionaryContentShape,
|
|
221
|
+
fr: dictionaryContentShape
|
|
222
|
+
})
|
|
223
|
+
};
|
|
224
|
+
const styles = StyleSheet.create({
|
|
225
|
+
container: {
|
|
226
|
+
position: 'relative',
|
|
227
|
+
width: '100%'
|
|
228
|
+
},
|
|
229
|
+
pressable: {
|
|
230
|
+
outlineWidth: 0,
|
|
231
|
+
outlineStyle: 'none',
|
|
232
|
+
outlineColor: 'transparent'
|
|
233
|
+
},
|
|
234
|
+
buttonContent: {
|
|
235
|
+
display: 'flex',
|
|
236
|
+
flexDirection: 'row',
|
|
237
|
+
alignItems: 'center',
|
|
238
|
+
justifyContent: 'space-between',
|
|
239
|
+
width: '100%',
|
|
240
|
+
minHeight: 44,
|
|
241
|
+
outline: 'none',
|
|
242
|
+
boxSizing: 'border-box'
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
export default TabsDropdown;
|
package/lib/esm/Tabs/TabsItem.js
CHANGED
|
@@ -72,8 +72,10 @@ const selectContainerStyles = _ref3 => {
|
|
|
72
72
|
borderRadius,
|
|
73
73
|
paddingHorizontal: paddingHorizontal - borderWidth,
|
|
74
74
|
paddingVertical: paddingVertical - borderWidth,
|
|
75
|
-
marginHorizontal,
|
|
76
|
-
|
|
75
|
+
marginLeft: marginHorizontal,
|
|
76
|
+
marginRight: marginHorizontal,
|
|
77
|
+
marginTop: marginVertical,
|
|
78
|
+
marginBottom: marginVertical
|
|
77
79
|
};
|
|
78
80
|
};
|
|
79
81
|
|
|
@@ -6,15 +6,20 @@ import useViewportListener from './useViewportListener';
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Provides an up-to-date viewport value from system-constants, available via the `useViewport` hook
|
|
9
|
+
*
|
|
10
|
+
* @param {React.ReactNode} children - Child components that will have access to viewport context
|
|
11
|
+
* @param {string} [defaultViewport] - Default viewport to use during server-side rendering.
|
|
12
|
+
* Must be one of the viewport keys from system-constants. If not provided, defaults to the smallest viewport.
|
|
9
13
|
*/
|
|
10
14
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
15
|
const ViewportProvider = _ref => {
|
|
12
16
|
let {
|
|
13
|
-
children
|
|
17
|
+
children,
|
|
18
|
+
defaultViewport
|
|
14
19
|
} = _ref;
|
|
15
20
|
// Default to the smallest viewport for mobile-first SSR. On client side, this is updated
|
|
16
21
|
// by useViewportListener in a layout effect before anything is shown to the user.
|
|
17
|
-
const [viewport, setViewport] = React.useState(viewports.keys[0]);
|
|
22
|
+
const [viewport, setViewport] = React.useState(defaultViewport || viewports.keys[0]);
|
|
18
23
|
useViewportListener(setViewport);
|
|
19
24
|
return /*#__PURE__*/_jsx(ViewportContext.Provider, {
|
|
20
25
|
value: viewport,
|
|
@@ -22,6 +27,7 @@ const ViewportProvider = _ref => {
|
|
|
22
27
|
});
|
|
23
28
|
};
|
|
24
29
|
ViewportProvider.propTypes = {
|
|
25
|
-
children: PropTypes.node.isRequired
|
|
30
|
+
children: PropTypes.node.isRequired,
|
|
31
|
+
defaultViewport: PropTypes.oneOf(viewports.keys)
|
|
26
32
|
};
|
|
27
33
|
export default ViewportProvider;
|
|
@@ -37,7 +37,7 @@ export default {
|
|
|
37
37
|
* 1. `tooltip` as a string - The content of the tooltip.
|
|
38
38
|
* 2. `tooltip` as an object - Tooltip component props to be passed.
|
|
39
39
|
*/
|
|
40
|
-
tooltip: PropTypes.oneOfType([tooltipPropTypes, PropTypes.string]),
|
|
40
|
+
tooltip: PropTypes.oneOfType([PropTypes.shape(tooltipPropTypes), PropTypes.string]),
|
|
41
41
|
/**
|
|
42
42
|
* Use to visually mark an input as valid or invalid.
|
|
43
43
|
*/
|
package/lib/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
|
}
|