@telus-uds/components-base 3.20.0 → 3.22.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 +27 -1
- package/lib/cjs/Button/Button.js +10 -3
- package/lib/cjs/Button/ButtonBase.js +53 -5
- package/lib/cjs/Card/PressableCardBase.js +3 -1
- package/lib/cjs/Carousel/Carousel.js +11 -3
- package/lib/cjs/Responsive/ResponsiveWithMediaQueryStyleSheet.js +1 -1
- package/lib/cjs/StackView/StackView.js +62 -12
- package/lib/cjs/Tabs/TabsDropdown.js +4 -5
- package/lib/cjs/ThemeProvider/index.js +9 -1
- package/lib/cjs/ThemeProvider/useResponsiveThemeTokensCallback.js +124 -0
- package/lib/cjs/index.js +7 -0
- package/lib/cjs/utils/ssr-media-query/index.js +7 -0
- package/lib/cjs/utils/ssr-media-query/utils/use-all-viewport-tokens.js +53 -0
- package/lib/esm/Button/Button.js +11 -4
- package/lib/esm/Button/ButtonBase.js +54 -6
- package/lib/esm/Card/PressableCardBase.js +3 -1
- package/lib/esm/Carousel/Carousel.js +11 -3
- package/lib/esm/Responsive/ResponsiveWithMediaQueryStyleSheet.js +1 -1
- package/lib/esm/StackView/StackView.js +63 -13
- package/lib/esm/Tabs/TabsDropdown.js +4 -5
- package/lib/esm/ThemeProvider/index.js +1 -0
- package/lib/esm/ThemeProvider/useResponsiveThemeTokensCallback.js +117 -0
- package/lib/esm/index.js +1 -1
- package/lib/esm/utils/ssr-media-query/index.js +2 -1
- package/lib/esm/utils/ssr-media-query/utils/use-all-viewport-tokens.js +48 -0
- package/lib/package.json +4 -4
- package/package.json +4 -4
- package/src/Button/Button.jsx +24 -4
- package/src/Button/ButtonBase.jsx +61 -4
- package/src/Card/PressableCardBase.jsx +3 -1
- package/src/Carousel/Carousel.jsx +13 -2
- package/src/PriceLockup/utils/renderPrice.jsx +15 -17
- package/src/Responsive/ResponsiveWithMediaQueryStyleSheet.jsx +1 -1
- package/src/StackView/StackView.jsx +62 -9
- package/src/Tabs/TabsDropdown.jsx +10 -9
- package/src/ThemeProvider/index.js +1 -0
- package/src/ThemeProvider/useResponsiveThemeTokensCallback.js +129 -0
- package/src/index.js +2 -1
- package/src/utils/ssr-media-query/index.js +2 -1
- package/src/utils/ssr-media-query/utils/use-all-viewport-tokens.js +32 -0
|
@@ -6,7 +6,7 @@ import StyleSheet from "react-native-web/dist/exports/StyleSheet";
|
|
|
6
6
|
import Platform from "react-native-web/dist/exports/Platform";
|
|
7
7
|
import { applyTextStyles, applyShadowToken, applyOuterBorder, useTheme } from '../ThemeProvider';
|
|
8
8
|
import buttonPropTypes from './propTypes';
|
|
9
|
-
import { a11yProps, clickProps, focusHandlerProps, getCursorStyle, linkProps, resolvePressableState, resolvePressableTokens, selectSystemProps, viewProps, wrapStringsInText, withLinkRouter, contentfulProps } from '../utils';
|
|
9
|
+
import { a11yProps, clickProps, focusHandlerProps, getCursorStyle, linkProps, resolvePressableState, resolvePressableTokens, selectSystemProps, viewProps, wrapStringsInText, withLinkRouter, contentfulProps, createMediaQueryStyles, StyleSheet as StyleSheetUtils } from '../utils';
|
|
10
10
|
import { IconText } from '../Icon';
|
|
11
11
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
12
12
|
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, focusHandlerProps, linkProps, viewProps, contentfulProps]);
|
|
@@ -250,6 +250,13 @@ const ButtonBase = /*#__PURE__*/React.forwardRef((_ref12, ref) => {
|
|
|
250
250
|
iconProps,
|
|
251
251
|
...rawRest
|
|
252
252
|
} = _ref12;
|
|
253
|
+
const {
|
|
254
|
+
themeOptions
|
|
255
|
+
} = useTheme();
|
|
256
|
+
const {
|
|
257
|
+
viewport
|
|
258
|
+
} = rawRest;
|
|
259
|
+
const enableMediaQueryStyleSheet = themeOptions.enableMediaQueryStyleSheet && viewport;
|
|
253
260
|
const {
|
|
254
261
|
onPress,
|
|
255
262
|
...rest
|
|
@@ -261,15 +268,55 @@ const ButtonBase = /*#__PURE__*/React.forwardRef((_ref12, ref) => {
|
|
|
261
268
|
};
|
|
262
269
|
const resolveButtonTokens = pressableState => resolvePressableTokens(tokens, pressableState, extraButtonState);
|
|
263
270
|
const systemProps = selectProps(rest);
|
|
271
|
+
let layoutMediaQueryStyles;
|
|
272
|
+
let flexAndWidthStylesIds;
|
|
273
|
+
if (enableMediaQueryStyleSheet) {
|
|
274
|
+
const defaultPressableState = {
|
|
275
|
+
pressed: false,
|
|
276
|
+
hovered: false,
|
|
277
|
+
focused: false
|
|
278
|
+
};
|
|
279
|
+
const defaultTokensByViewport = resolveButtonTokens(defaultPressableState);
|
|
280
|
+
const layoutTokensByViewport = Object.entries(defaultTokensByViewport).reduce((acc, _ref13) => {
|
|
281
|
+
let [vp, viewportTokens] = _ref13;
|
|
282
|
+
const flexAndWidthStyles = viewportTokens.width === '100%' && viewportTokens.flex === 1 ? selectFlexAndWidthStyles(viewportTokens) : {};
|
|
283
|
+
acc[vp] = {
|
|
284
|
+
...staticStyles.row,
|
|
285
|
+
...selectWebOnlyStyles(inactive, viewportTokens, systemProps),
|
|
286
|
+
...(Object.keys(flexAndWidthStyles).length > 0 ? flexAndWidthStyles : {}),
|
|
287
|
+
...selectOuterSizeStyles(viewportTokens)
|
|
288
|
+
};
|
|
289
|
+
return acc;
|
|
290
|
+
}, {});
|
|
291
|
+
const mediaQueryStyles = createMediaQueryStyles(layoutTokensByViewport);
|
|
292
|
+
const {
|
|
293
|
+
ids,
|
|
294
|
+
styles
|
|
295
|
+
} = StyleSheetUtils.create({
|
|
296
|
+
layout: {
|
|
297
|
+
...mediaQueryStyles
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
layoutMediaQueryStyles = styles.layout;
|
|
301
|
+
flexAndWidthStylesIds = ids.layout;
|
|
302
|
+
}
|
|
264
303
|
const getPressableStyle = pressableState => {
|
|
304
|
+
if (enableMediaQueryStyleSheet) {
|
|
305
|
+
const themeTokens = resolveButtonTokens(pressableState)[viewport];
|
|
306
|
+
return [layoutMediaQueryStyles, selectOuterContainerStyles(themeTokens)];
|
|
307
|
+
}
|
|
265
308
|
const themeTokens = resolveButtonTokens(pressableState);
|
|
266
|
-
// Only apply flex and width styles when they are explicitly set (e.g., from ButtonGroup with width: 'equal') to not to affect other use cases
|
|
267
309
|
const flexAndWidthStyles = themeTokens.width === '100%' && themeTokens.flex === 1 ? selectFlexAndWidthStyles(themeTokens) : {};
|
|
268
310
|
return [staticStyles.row, selectWebOnlyStyles(inactive, themeTokens, systemProps), selectOuterContainerStyles(themeTokens), ...(Object.keys(flexAndWidthStyles).length > 0 ? [flexAndWidthStyles] : []), selectOuterSizeStyles(themeTokens)];
|
|
269
311
|
};
|
|
270
|
-
const {
|
|
271
|
-
|
|
272
|
-
|
|
312
|
+
const dataSetProp = flexAndWidthStylesIds || rawRest.dataSet ? {
|
|
313
|
+
dataSet: {
|
|
314
|
+
...(flexAndWidthStylesIds ? {
|
|
315
|
+
media: flexAndWidthStylesIds
|
|
316
|
+
} : {}),
|
|
317
|
+
...rawRest.dataSet
|
|
318
|
+
}
|
|
319
|
+
} : {};
|
|
273
320
|
return /*#__PURE__*/_jsx(Pressable, {
|
|
274
321
|
ref: ref,
|
|
275
322
|
href: href,
|
|
@@ -281,8 +328,9 @@ const ButtonBase = /*#__PURE__*/React.forwardRef((_ref12, ref) => {
|
|
|
281
328
|
disabled: inactive,
|
|
282
329
|
hrefAttrs: hrefAttrs,
|
|
283
330
|
...systemProps,
|
|
331
|
+
...dataSetProp,
|
|
284
332
|
children: pressableState => {
|
|
285
|
-
const themeTokens = resolveButtonTokens(pressableState);
|
|
333
|
+
const themeTokens = enableMediaQueryStyleSheet ? resolveButtonTokens(pressableState)[viewport] : resolveButtonTokens(pressableState);
|
|
286
334
|
const containerStyles = selectInnerContainerStyles(themeTokens);
|
|
287
335
|
const borderStyles = selectBorderStyles(themeTokens);
|
|
288
336
|
const textStyles = [selectTextStyles(themeTokens, themeOptions), staticStyles.text, Platform.select({
|
|
@@ -173,7 +173,9 @@ const staticStyles = StyleSheet.create({
|
|
|
173
173
|
},
|
|
174
174
|
linkContainer: {
|
|
175
175
|
flex: 1,
|
|
176
|
-
display: 'flex'
|
|
176
|
+
display: 'flex',
|
|
177
|
+
alignItems: 'stretch',
|
|
178
|
+
justifyContent: 'flex-start'
|
|
177
179
|
}
|
|
178
180
|
});
|
|
179
181
|
PressableCardBase.displayName = 'PressableCardBase';
|
|
@@ -351,6 +351,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
|
351
351
|
copy,
|
|
352
352
|
slideDuration = 0,
|
|
353
353
|
transitionDuration = 0,
|
|
354
|
+
loopDuration = transitionDuration,
|
|
354
355
|
autoPlay = false,
|
|
355
356
|
enablePeeking = false,
|
|
356
357
|
...rest
|
|
@@ -470,6 +471,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
|
470
471
|
}
|
|
471
472
|
}, [pan, animatedX, heroPan, heroAnimatedX, enableHero, viewport, enablePeeking]);
|
|
472
473
|
const animate = React.useCallback((panToAnimate, toValue, toIndex) => {
|
|
474
|
+
const applicableTransitionDuration = isLastSlide && toIndex === 0 ? loopDuration : transitionDuration;
|
|
473
475
|
const handleAnimationEndToIndex = function () {
|
|
474
476
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
475
477
|
args[_key] = arguments[_key];
|
|
@@ -487,14 +489,14 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
|
487
489
|
...springConfig,
|
|
488
490
|
toValue,
|
|
489
491
|
useNativeDriver: false,
|
|
490
|
-
duration:
|
|
492
|
+
duration: applicableTransitionDuration * 1000
|
|
491
493
|
}).start(handleAnimationEndToIndex);
|
|
492
494
|
} else if (enablePeeking || enableDisplayMultipleItemsPerSlide) {
|
|
493
495
|
Animated.timing(panToAnimate, {
|
|
494
496
|
...springConfig,
|
|
495
497
|
toValue,
|
|
496
498
|
useNativeDriver: false,
|
|
497
|
-
duration:
|
|
499
|
+
duration: applicableTransitionDuration ? applicableTransitionDuration * 1000 : 1000
|
|
498
500
|
}).start(handleAnimationEndToIndex);
|
|
499
501
|
} else {
|
|
500
502
|
Animated.spring(panToAnimate, {
|
|
@@ -503,7 +505,7 @@ const Carousel = /*#__PURE__*/React.forwardRef((_ref3, ref) => {
|
|
|
503
505
|
useNativeDriver: false
|
|
504
506
|
}).start(handleAnimationEndToIndex);
|
|
505
507
|
}
|
|
506
|
-
}, [springConfig, handleAnimationEnd, transitionDuration, isAutoPlayEnabled, enablePeeking, enableDisplayMultipleItemsPerSlide]);
|
|
508
|
+
}, [springConfig, handleAnimationEnd, transitionDuration, loopDuration, isLastSlide, isAutoPlayEnabled, enablePeeking, enableDisplayMultipleItemsPerSlide]);
|
|
507
509
|
const stopAutoplay = React.useCallback(() => {
|
|
508
510
|
if (autoPlayRef?.current) {
|
|
509
511
|
clearTimeout(autoPlayRef?.current);
|
|
@@ -1197,6 +1199,12 @@ Carousel.propTypes = {
|
|
|
1197
1199
|
* - `autoPlay` and `slideDuration` are required to be set for this to work
|
|
1198
1200
|
*/
|
|
1199
1201
|
transitionDuration: PropTypes.number,
|
|
1202
|
+
/**
|
|
1203
|
+
* Time it takes in seconds to transition from last slide to first slide
|
|
1204
|
+
* - Default value equals `transitionDuration`'s value
|
|
1205
|
+
* - `autoPlay` and `transitionDuration` are required to be set for this to work
|
|
1206
|
+
*/
|
|
1207
|
+
loopDuration: PropTypes.number,
|
|
1200
1208
|
/**
|
|
1201
1209
|
* If set to `true`, the Carousel will show the previous and next slides
|
|
1202
1210
|
* - Default value is `false`
|
|
@@ -61,7 +61,7 @@ ResponsiveWithMediaQueryStyleSheet.propTypes = {
|
|
|
61
61
|
/**
|
|
62
62
|
* To hide children of `Responsive` if the current viewport is larger than `max`
|
|
63
63
|
*/
|
|
64
|
-
max: PropTypes.oneOf(['sm', 'md', 'lg', 'xl']),
|
|
64
|
+
max: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
|
|
65
65
|
inheritedStyles: PropTypes.arrayOf(PropTypes.string),
|
|
66
66
|
children: PropTypes.node.isRequired
|
|
67
67
|
};
|
|
@@ -2,9 +2,8 @@ import React from 'react';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import View from "react-native-web/dist/exports/View";
|
|
4
4
|
import Divider from '../Divider';
|
|
5
|
-
import { a11yProps, getA11yPropsFromHtmlTag, getTokensPropType, layoutTags, responsiveProps, selectSystemProps, spacingProps, useResponsiveProp, variantProp, viewProps } from '../utils';
|
|
6
|
-
import
|
|
7
|
-
import { useViewport } from '../ViewportProvider';
|
|
5
|
+
import { a11yProps, getA11yPropsFromHtmlTag, getTokensPropType, layoutTags, responsiveProps, selectSystemProps, spacingProps, useResponsiveProp, variantProp, viewProps, StyleSheet as StyleSheetUtils, createMediaQueryStyles, useAllViewportTokens } from '../utils';
|
|
6
|
+
import useTheme from '../ThemeProvider/useTheme';
|
|
8
7
|
import getStackedContent from './getStackedContent';
|
|
9
8
|
import { staticStyles, selectFlexStyles } from './common';
|
|
10
9
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
@@ -62,10 +61,15 @@ const StackView = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
62
61
|
tokens,
|
|
63
62
|
tag,
|
|
64
63
|
accessibilityRole,
|
|
64
|
+
dataSet,
|
|
65
65
|
...rest
|
|
66
66
|
} = _ref;
|
|
67
|
-
const viewport = useViewport();
|
|
68
67
|
const direction = useResponsiveProp(directionProp, 'column');
|
|
68
|
+
const {
|
|
69
|
+
themeOptions: {
|
|
70
|
+
enableMediaQueryStyleSheet
|
|
71
|
+
}
|
|
72
|
+
} = useTheme();
|
|
69
73
|
const selectedProps = selectProps({
|
|
70
74
|
accessibilityRole,
|
|
71
75
|
...getA11yPropsFromHtmlTag(tag, accessibilityRole),
|
|
@@ -76,17 +80,58 @@ const StackView = /*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
|
76
80
|
divider,
|
|
77
81
|
space
|
|
78
82
|
});
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
const allTokens = useAllViewportTokens('StackView', tokens, variant);
|
|
84
|
+
let stackViewStyles;
|
|
85
|
+
let dataSetValue = dataSet;
|
|
86
|
+
if (enableMediaQueryStyleSheet) {
|
|
87
|
+
const stylesByViewport = {
|
|
88
|
+
xs: {
|
|
89
|
+
...selectFlexStyles(allTokens.xs),
|
|
90
|
+
width: allTokens.xs.width
|
|
91
|
+
},
|
|
92
|
+
sm: {
|
|
93
|
+
...selectFlexStyles(allTokens.sm),
|
|
94
|
+
width: allTokens.sm.width
|
|
95
|
+
},
|
|
96
|
+
md: {
|
|
97
|
+
...selectFlexStyles(allTokens.md),
|
|
98
|
+
width: allTokens.md.width
|
|
99
|
+
},
|
|
100
|
+
lg: {
|
|
101
|
+
...selectFlexStyles(allTokens.lg),
|
|
102
|
+
width: allTokens.lg.width
|
|
103
|
+
},
|
|
104
|
+
xl: {
|
|
105
|
+
...selectFlexStyles(allTokens.xl),
|
|
106
|
+
width: allTokens.xl.width
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const mediaQueryStyles = createMediaQueryStyles(stylesByViewport);
|
|
110
|
+
const {
|
|
111
|
+
ids,
|
|
112
|
+
styles
|
|
113
|
+
} = StyleSheetUtils.create({
|
|
114
|
+
stackView: {
|
|
115
|
+
...mediaQueryStyles
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
stackViewStyles = [staticStyles[direction], styles.stackView];
|
|
119
|
+
dataSetValue = {
|
|
120
|
+
media: ids.stackView,
|
|
121
|
+
...dataSet
|
|
122
|
+
};
|
|
123
|
+
} else {
|
|
124
|
+
const flexStyles = selectFlexStyles(allTokens.current);
|
|
125
|
+
const size = {
|
|
126
|
+
width: allTokens.current.width
|
|
127
|
+
};
|
|
128
|
+
stackViewStyles = [flexStyles, staticStyles[direction], size];
|
|
129
|
+
}
|
|
86
130
|
return /*#__PURE__*/_jsx(View, {
|
|
87
131
|
ref: ref,
|
|
88
132
|
...selectedProps,
|
|
89
|
-
style:
|
|
133
|
+
style: stackViewStyles,
|
|
134
|
+
dataSet: dataSetValue,
|
|
90
135
|
children: content
|
|
91
136
|
});
|
|
92
137
|
});
|
|
@@ -121,6 +166,11 @@ StackView.propTypes = {
|
|
|
121
166
|
* A StackView may take any children, but will have no effect if it is only passed one child or is passed children
|
|
122
167
|
* wrapped in a component. If necessary, children may be wrapped in one React Fragment.
|
|
123
168
|
*/
|
|
124
|
-
children: PropTypes.node
|
|
169
|
+
children: PropTypes.node,
|
|
170
|
+
/**
|
|
171
|
+
* Data attributes to be applied to the element. When media query stylesheet is enabled,
|
|
172
|
+
* this will include media query IDs for responsive styling.
|
|
173
|
+
*/
|
|
174
|
+
dataSet: PropTypes.object
|
|
125
175
|
};
|
|
126
176
|
export default StackView;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
+
import Platform from "react-native-web/dist/exports/Platform";
|
|
3
4
|
import Pressable from "react-native-web/dist/exports/Pressable";
|
|
4
5
|
import StyleSheet from "react-native-web/dist/exports/StyleSheet";
|
|
5
6
|
import Text from "react-native-web/dist/exports/Text";
|
|
@@ -226,20 +227,18 @@ const styles = StyleSheet.create({
|
|
|
226
227
|
position: 'relative',
|
|
227
228
|
width: '100%'
|
|
228
229
|
},
|
|
229
|
-
pressable: {
|
|
230
|
+
pressable: Platform.OS === 'web' ? {
|
|
230
231
|
outlineWidth: 0,
|
|
231
232
|
outlineStyle: 'none',
|
|
232
233
|
outlineColor: 'transparent'
|
|
233
|
-
},
|
|
234
|
+
} : {},
|
|
234
235
|
buttonContent: {
|
|
235
236
|
display: 'flex',
|
|
236
237
|
flexDirection: 'row',
|
|
237
238
|
alignItems: 'center',
|
|
238
239
|
justifyContent: 'space-between',
|
|
239
240
|
width: '100%',
|
|
240
|
-
minHeight: 44
|
|
241
|
-
outline: 'none',
|
|
242
|
-
boxSizing: 'border-box'
|
|
241
|
+
minHeight: 44
|
|
243
242
|
}
|
|
244
243
|
});
|
|
245
244
|
export default TabsDropdown;
|
|
@@ -2,6 +2,7 @@ import ThemeProvider from './ThemeProvider';
|
|
|
2
2
|
export { default as useTheme } from './useTheme';
|
|
3
3
|
export { default as useSetTheme } from './useSetTheme';
|
|
4
4
|
export { default as useResponsiveThemeTokens } from './useResponsiveThemeTokens';
|
|
5
|
+
export { default as useResponsiveThemeTokensCallback } from './useResponsiveThemeTokensCallback';
|
|
5
6
|
export * from './useThemeTokens';
|
|
6
7
|
export * from './utils';
|
|
7
8
|
export default ThemeProvider;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { viewports } from '@telus-uds/system-constants';
|
|
3
|
+
import useTheme from './useTheme';
|
|
4
|
+
import { getComponentTheme, mergeAppearances, resolveThemeTokens } from './utils';
|
|
5
|
+
const getResponsiveThemeTokens = function (_ref, tokensProp) {
|
|
6
|
+
let {
|
|
7
|
+
rules = [],
|
|
8
|
+
tokens: defaultThemeTokens = {}
|
|
9
|
+
} = _ref;
|
|
10
|
+
let variants = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
11
|
+
let states = arguments.length > 3 ? arguments[3] : undefined;
|
|
12
|
+
const appearances = mergeAppearances(variants, states);
|
|
13
|
+
const tokensByViewport = Object.fromEntries(viewports.keys.map(viewport => [viewport, {
|
|
14
|
+
...defaultThemeTokens
|
|
15
|
+
}]));
|
|
16
|
+
|
|
17
|
+
// Go through each rule and collect them for the corresponding viewport if they apply
|
|
18
|
+
rules.forEach(rule => {
|
|
19
|
+
if (doesRuleApply(rule, appearances)) {
|
|
20
|
+
// If the rule does not have a viewport specified, we collect it in all viewports
|
|
21
|
+
let targetViewports = rule.if.viewport || viewports.keys;
|
|
22
|
+
if (!Array.isArray(targetViewports)) {
|
|
23
|
+
targetViewports = [targetViewports];
|
|
24
|
+
}
|
|
25
|
+
targetViewports.forEach(viewport => {
|
|
26
|
+
tokensByViewport[viewport] = {
|
|
27
|
+
...tokensByViewport[viewport],
|
|
28
|
+
...rule.tokens
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
Object.keys(tokensByViewport).forEach(viewport => {
|
|
34
|
+
tokensByViewport[viewport] = resolveThemeTokens(tokensByViewport[viewport], appearances, tokensProp);
|
|
35
|
+
});
|
|
36
|
+
return tokensByViewport;
|
|
37
|
+
};
|
|
38
|
+
const doesRuleApply = (rule, appearances) => Object.entries(rule.if).every(condition => doesConditionApply(condition, appearances));
|
|
39
|
+
const doesConditionApply = (_ref2, appearances) => {
|
|
40
|
+
let [key, value] = _ref2;
|
|
41
|
+
if (key === 'viewport') {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
// use null rather than undefined so we can serialise the value in themes
|
|
45
|
+
const appearanceValue = appearances[key] ?? null;
|
|
46
|
+
return Array.isArray(value) ? value.includes(appearanceValue) : value === appearanceValue;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @typedef {import('../utils/props/tokens.js').TokensSet} TokensSet
|
|
51
|
+
* @typedef {import('../utils/props/tokens.js').TokensProp} TokensProp
|
|
52
|
+
* @typedef {import('../utils/props/variantProp').AppearanceSet} AppearanceSet
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Returns a memoised tokens getter function that gets responsive tokens for all viewports,
|
|
57
|
+
* similar to calling useResponsiveThemeTokens but with the callback pattern of useThemeTokensCallback.
|
|
58
|
+
*
|
|
59
|
+
* Scenarios where `useResponsiveThemeTokensCallback` should be used:
|
|
60
|
+
*
|
|
61
|
+
* - Where responsive tokens are to be obtained from state that is accessible only in scopes like callbacks
|
|
62
|
+
* and render functions, where calling useResponsiveThemeTokens directly would be disallowed by React's hook rules.
|
|
63
|
+
* - When using media query stylesheets and need to resolve tokens based on dynamic state (e.g., pressed, hovered)
|
|
64
|
+
* that changes at runtime.
|
|
65
|
+
* - Passing a responsive tokens getter down via a child component's `tokens` prop, applying rules using the
|
|
66
|
+
* child component's current state.
|
|
67
|
+
*
|
|
68
|
+
* The function returned may be called with an object of state appearances to get an object
|
|
69
|
+
* of tokens for each viewport, which can then be passed to createMediaQueryStyles.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // Resolving responsive tokens inside Pressable's style function, based on Pressable state
|
|
73
|
+
* const PressMe = ({ tokens, variant, children }) => {
|
|
74
|
+
* const getResponsiveTokens = useResponsiveThemeTokensCallback('PressMe', tokens, variant)
|
|
75
|
+
* const getPressableStyle = ({ pressed }) => {
|
|
76
|
+
* const responsiveTokens = getResponsiveTokens({ pressed })
|
|
77
|
+
* const mediaQueryStyles = createMediaQueryStyles(responsiveTokens)
|
|
78
|
+
* return mediaQueryStyles
|
|
79
|
+
* }
|
|
80
|
+
* return <Pressable style={getPressableStyle}>{children}</Pressable>
|
|
81
|
+
* }
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* // Setting the theme in a parent and resolving it in a child based on child's state
|
|
85
|
+
* const MenuButton = ({ tokens, variant, ...buttonProps }) => {
|
|
86
|
+
* // Define what theme, variant etc we want in this component...
|
|
87
|
+
* const getResponsiveTokens = useResponsiveThemeTokensCallback('Button', tokens, variant)
|
|
88
|
+
* // ...resolve them in another component based on its state (e.g. press, hover...)
|
|
89
|
+
* return <ButtonBase tokens={getResponsiveTokens} accessibilityRole="menuitem" {...buttonProps} />
|
|
90
|
+
* }
|
|
91
|
+
*
|
|
92
|
+
* @typedef {Object} ResponsiveObject
|
|
93
|
+
* @property {TokensSet} xs
|
|
94
|
+
* @property {TokensSet} sm
|
|
95
|
+
* @property {TokensSet} md
|
|
96
|
+
* @property {TokensSet} lg
|
|
97
|
+
* @property {TokensSet} xl
|
|
98
|
+
*
|
|
99
|
+
* @param {string} componentName - the name as defined in the theme schema of the component whose theme is to be used
|
|
100
|
+
* @param {TokensProp} [tokens] - every themed component should accept a `tokens` prop allowing theme tokens to be overridden
|
|
101
|
+
* @param {AppearanceSet} [variants] - variants passed in as props that don't change dynamically
|
|
102
|
+
* @returns {(states: AppearanceSet, tokenOverrides?: TokensProp) => ResponsiveObject}
|
|
103
|
+
* - callback function that returns an overridable responsive tokens object for current state. Only pass
|
|
104
|
+
* tokenOverrides in rare cases where tokens overrides are also generated outside hook scope.
|
|
105
|
+
*/
|
|
106
|
+
const useResponsiveThemeTokensCallback = function (componentName) {
|
|
107
|
+
let tokens = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
108
|
+
let variants = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
109
|
+
const theme = useTheme();
|
|
110
|
+
const componentTheme = getComponentTheme(theme, componentName);
|
|
111
|
+
const getResponsiveThemeTokensCallback = useCallback((states, tokenOverrides) => {
|
|
112
|
+
const resolvedTokens = resolveThemeTokens(tokens, mergeAppearances(variants, states), tokenOverrides);
|
|
113
|
+
return getResponsiveThemeTokens(componentTheme, resolvedTokens, variants, states);
|
|
114
|
+
}, [componentTheme, tokens, variants]);
|
|
115
|
+
return getResponsiveThemeTokensCallback;
|
|
116
|
+
};
|
|
117
|
+
export default useResponsiveThemeTokensCallback;
|
package/lib/esm/index.js
CHANGED
|
@@ -68,6 +68,6 @@ export { default as BaseProvider } from './BaseProvider';
|
|
|
68
68
|
export { useHydrationContext } from './BaseProvider/HydrationContext';
|
|
69
69
|
export { default as Validator } from './Validator';
|
|
70
70
|
export { default as ViewportProvider, useViewport, ViewportContext } from './ViewportProvider';
|
|
71
|
-
export { default as ThemeProvider, useTheme, useSetTheme, useThemeTokens, useThemeTokensCallback, getThemeTokens, applyOuterBorder, applyTextStyles, applyShadowToken, useResponsiveThemeTokens } from './ThemeProvider';
|
|
71
|
+
export { default as ThemeProvider, useTheme, useSetTheme, useThemeTokens, useThemeTokensCallback, getThemeTokens, applyOuterBorder, applyTextStyles, applyShadowToken, useResponsiveThemeTokens, useResponsiveThemeTokensCallback } from './ThemeProvider';
|
|
72
72
|
export * from './utils';
|
|
73
73
|
export { default as Portal } from './Portal';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import createStyleSheet from './create-stylesheet';
|
|
2
2
|
import createMediaQueryStyles from './utils/create-media-query-styles';
|
|
3
|
+
import useAllViewportTokens from './utils/use-all-viewport-tokens';
|
|
3
4
|
const StyleSheet = {
|
|
4
5
|
create: createStyleSheet
|
|
5
6
|
};
|
|
6
|
-
export { StyleSheet, createMediaQueryStyles };
|
|
7
|
+
export { StyleSheet, createMediaQueryStyles, useAllViewportTokens };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useThemeTokens } from '../../../ThemeProvider';
|
|
2
|
+
import { useViewport } from '../../../ViewportProvider';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook to get theme tokens for all viewports at once.
|
|
6
|
+
* This is useful for components that need to support React Native Media Queries (RNMQ).
|
|
7
|
+
*
|
|
8
|
+
* All hooks are called unconditionally to comply with React's Rules of Hooks.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} componentName - The name of the component to get tokens for
|
|
11
|
+
* @param {object|function} tokens - Custom tokens or token function
|
|
12
|
+
* @param {object} variant - Variant configuration
|
|
13
|
+
* @returns {object} Object with tokens for each viewport (xs, sm, md, lg, xl, current)
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const allTokens = useAllViewportTokens('StackView', tokens, variant)
|
|
17
|
+
* // Returns: { xs: {...}, sm: {...}, md: {...}, lg: {...}, xl: {...}, current: {...} }
|
|
18
|
+
*/
|
|
19
|
+
const useAllViewportTokens = (componentName, tokens, variant) => {
|
|
20
|
+
const viewport = useViewport();
|
|
21
|
+
const xs = useThemeTokens(componentName, tokens, variant, {
|
|
22
|
+
viewport: 'xs'
|
|
23
|
+
});
|
|
24
|
+
const sm = useThemeTokens(componentName, tokens, variant, {
|
|
25
|
+
viewport: 'sm'
|
|
26
|
+
});
|
|
27
|
+
const md = useThemeTokens(componentName, tokens, variant, {
|
|
28
|
+
viewport: 'md'
|
|
29
|
+
});
|
|
30
|
+
const lg = useThemeTokens(componentName, tokens, variant, {
|
|
31
|
+
viewport: 'lg'
|
|
32
|
+
});
|
|
33
|
+
const xl = useThemeTokens(componentName, tokens, variant, {
|
|
34
|
+
viewport: 'xl'
|
|
35
|
+
});
|
|
36
|
+
const current = useThemeTokens(componentName, tokens, variant, {
|
|
37
|
+
viewport
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
xs,
|
|
41
|
+
sm,
|
|
42
|
+
md,
|
|
43
|
+
lg,
|
|
44
|
+
xl,
|
|
45
|
+
current
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
export default useAllViewportTokens;
|
package/lib/package.json
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@testing-library/react": "^13.3.0",
|
|
35
35
|
"@testing-library/react-hooks": "~7.0.1",
|
|
36
36
|
"@testing-library/react-native": "11.0.0",
|
|
37
|
-
"react-test-renderer": "
|
|
37
|
+
"react-test-renderer": "^18.0.0",
|
|
38
38
|
"webpack": "5.x"
|
|
39
39
|
},
|
|
40
40
|
"exports": {
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"react": ">=18.2.0 <19.0.0",
|
|
60
60
|
"react-dom": ">=18.2.0 <19.0.0",
|
|
61
61
|
"react-native": "^0.74.5",
|
|
62
|
-
"react-native-
|
|
63
|
-
"react-native-
|
|
62
|
+
"react-native-svg": "15.7.1",
|
|
63
|
+
"react-native-web": "^0.19.10"
|
|
64
64
|
},
|
|
65
65
|
"react-native": "src/index.js",
|
|
66
66
|
"repository": {
|
|
@@ -84,6 +84,6 @@
|
|
|
84
84
|
"standard-engine": {
|
|
85
85
|
"skip": true
|
|
86
86
|
},
|
|
87
|
-
"version": "3.
|
|
87
|
+
"version": "3.22.0",
|
|
88
88
|
"types": "types/index.d.ts"
|
|
89
89
|
}
|
package/package.json
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@testing-library/react": "^13.3.0",
|
|
35
35
|
"@testing-library/react-hooks": "~7.0.1",
|
|
36
36
|
"@testing-library/react-native": "11.0.0",
|
|
37
|
-
"react-test-renderer": "
|
|
37
|
+
"react-test-renderer": "^18.0.0",
|
|
38
38
|
"webpack": "5.x"
|
|
39
39
|
},
|
|
40
40
|
"exports": {
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"react": ">=18.2.0 <19.0.0",
|
|
60
60
|
"react-dom": ">=18.2.0 <19.0.0",
|
|
61
61
|
"react-native": "^0.74.5",
|
|
62
|
-
"react-native-
|
|
63
|
-
"react-native-
|
|
62
|
+
"react-native-svg": "15.7.1",
|
|
63
|
+
"react-native-web": "^0.19.10"
|
|
64
64
|
},
|
|
65
65
|
"react-native": "src/index.js",
|
|
66
66
|
"repository": {
|
|
@@ -84,6 +84,6 @@
|
|
|
84
84
|
"standard-engine": {
|
|
85
85
|
"skip": true
|
|
86
86
|
},
|
|
87
|
-
"version": "3.
|
|
87
|
+
"version": "3.22.0",
|
|
88
88
|
"types": "types/index.d.ts"
|
|
89
89
|
}
|
package/src/Button/Button.jsx
CHANGED
|
@@ -2,17 +2,37 @@ import React from 'react'
|
|
|
2
2
|
|
|
3
3
|
import ButtonBase from './ButtonBase'
|
|
4
4
|
import buttonPropTypes, { textAndA11yText } from './propTypes'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
useThemeTokensCallback,
|
|
7
|
+
useResponsiveThemeTokensCallback,
|
|
8
|
+
useTheme
|
|
9
|
+
} from '../ThemeProvider'
|
|
6
10
|
import { a11yProps } from '../utils/props'
|
|
7
11
|
import { useViewport } from '../ViewportProvider'
|
|
8
12
|
|
|
9
13
|
const Button = React.forwardRef(
|
|
10
14
|
({ accessibilityRole = 'button', tokens, variant, ...props }, ref) => {
|
|
11
15
|
const viewport = useViewport()
|
|
12
|
-
const
|
|
13
|
-
|
|
16
|
+
const {
|
|
17
|
+
themeOptions: { enableMediaQueryStyleSheet }
|
|
18
|
+
} = useTheme()
|
|
19
|
+
|
|
20
|
+
const buttonVariant = enableMediaQueryStyleSheet ? variant : { viewport, ...variant }
|
|
21
|
+
|
|
22
|
+
const useTokens = enableMediaQueryStyleSheet
|
|
23
|
+
? useResponsiveThemeTokensCallback
|
|
24
|
+
: useThemeTokensCallback
|
|
25
|
+
|
|
26
|
+
const getTokens = useTokens('Button', tokens, buttonVariant)
|
|
27
|
+
|
|
14
28
|
return (
|
|
15
|
-
<ButtonBase
|
|
29
|
+
<ButtonBase
|
|
30
|
+
{...props}
|
|
31
|
+
tokens={getTokens}
|
|
32
|
+
accessibilityRole={accessibilityRole}
|
|
33
|
+
ref={ref}
|
|
34
|
+
viewport={viewport}
|
|
35
|
+
/>
|
|
16
36
|
)
|
|
17
37
|
}
|
|
18
38
|
)
|