@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.
Files changed (40) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/lib/cjs/Button/Button.js +10 -3
  3. package/lib/cjs/Button/ButtonBase.js +53 -5
  4. package/lib/cjs/Card/PressableCardBase.js +3 -1
  5. package/lib/cjs/Carousel/Carousel.js +11 -3
  6. package/lib/cjs/Responsive/ResponsiveWithMediaQueryStyleSheet.js +1 -1
  7. package/lib/cjs/StackView/StackView.js +62 -12
  8. package/lib/cjs/Tabs/TabsDropdown.js +4 -5
  9. package/lib/cjs/ThemeProvider/index.js +9 -1
  10. package/lib/cjs/ThemeProvider/useResponsiveThemeTokensCallback.js +124 -0
  11. package/lib/cjs/index.js +7 -0
  12. package/lib/cjs/utils/ssr-media-query/index.js +7 -0
  13. package/lib/cjs/utils/ssr-media-query/utils/use-all-viewport-tokens.js +53 -0
  14. package/lib/esm/Button/Button.js +11 -4
  15. package/lib/esm/Button/ButtonBase.js +54 -6
  16. package/lib/esm/Card/PressableCardBase.js +3 -1
  17. package/lib/esm/Carousel/Carousel.js +11 -3
  18. package/lib/esm/Responsive/ResponsiveWithMediaQueryStyleSheet.js +1 -1
  19. package/lib/esm/StackView/StackView.js +63 -13
  20. package/lib/esm/Tabs/TabsDropdown.js +4 -5
  21. package/lib/esm/ThemeProvider/index.js +1 -0
  22. package/lib/esm/ThemeProvider/useResponsiveThemeTokensCallback.js +117 -0
  23. package/lib/esm/index.js +1 -1
  24. package/lib/esm/utils/ssr-media-query/index.js +2 -1
  25. package/lib/esm/utils/ssr-media-query/utils/use-all-viewport-tokens.js +48 -0
  26. package/lib/package.json +4 -4
  27. package/package.json +4 -4
  28. package/src/Button/Button.jsx +24 -4
  29. package/src/Button/ButtonBase.jsx +61 -4
  30. package/src/Card/PressableCardBase.jsx +3 -1
  31. package/src/Carousel/Carousel.jsx +13 -2
  32. package/src/PriceLockup/utils/renderPrice.jsx +15 -17
  33. package/src/Responsive/ResponsiveWithMediaQueryStyleSheet.jsx +1 -1
  34. package/src/StackView/StackView.jsx +62 -9
  35. package/src/Tabs/TabsDropdown.jsx +10 -9
  36. package/src/ThemeProvider/index.js +1 -0
  37. package/src/ThemeProvider/useResponsiveThemeTokensCallback.js +129 -0
  38. package/src/index.js +2 -1
  39. package/src/utils/ssr-media-query/index.js +2 -1
  40. package/src/utils/ssr-media-query/utils/use-all-viewport-tokens.js +32 -0
@@ -16,7 +16,9 @@ import {
16
16
  viewProps,
17
17
  wrapStringsInText,
18
18
  withLinkRouter,
19
- contentfulProps
19
+ contentfulProps,
20
+ createMediaQueryStyles,
21
+ StyleSheet as StyleSheetUtils
20
22
  } from '../utils'
21
23
  import { IconText } from '../Icon'
22
24
 
@@ -221,6 +223,9 @@ const ButtonBase = React.forwardRef(
221
223
  },
222
224
  ref
223
225
  ) => {
226
+ const { themeOptions } = useTheme()
227
+ const { viewport } = rawRest
228
+ const enableMediaQueryStyleSheet = themeOptions.enableMediaQueryStyleSheet && viewport
224
229
  const { onPress, ...rest } = clickProps.toPressProps(rawRest)
225
230
  const extraButtonState = { inactive, selected, iconPosition }
226
231
  const resolveButtonTokens = (pressableState) =>
@@ -228,9 +233,49 @@ const ButtonBase = React.forwardRef(
228
233
 
229
234
  const systemProps = selectProps(rest)
230
235
 
236
+ let layoutMediaQueryStyles
237
+ let flexAndWidthStylesIds
238
+
239
+ if (enableMediaQueryStyleSheet) {
240
+ const defaultPressableState = { pressed: false, hovered: false, focused: false }
241
+ const defaultTokensByViewport = resolveButtonTokens(defaultPressableState)
242
+
243
+ const layoutTokensByViewport = Object.entries(defaultTokensByViewport).reduce(
244
+ (acc, [vp, viewportTokens]) => {
245
+ const flexAndWidthStyles =
246
+ viewportTokens.width === '100%' && viewportTokens.flex === 1
247
+ ? selectFlexAndWidthStyles(viewportTokens)
248
+ : {}
249
+
250
+ acc[vp] = {
251
+ ...staticStyles.row,
252
+ ...selectWebOnlyStyles(inactive, viewportTokens, systemProps),
253
+ ...(Object.keys(flexAndWidthStyles).length > 0 ? flexAndWidthStyles : {}),
254
+ ...selectOuterSizeStyles(viewportTokens)
255
+ }
256
+ return acc
257
+ },
258
+ {}
259
+ )
260
+
261
+ const mediaQueryStyles = createMediaQueryStyles(layoutTokensByViewport)
262
+ const { ids, styles } = StyleSheetUtils.create({
263
+ layout: {
264
+ ...mediaQueryStyles
265
+ }
266
+ })
267
+
268
+ layoutMediaQueryStyles = styles.layout
269
+ flexAndWidthStylesIds = ids.layout
270
+ }
271
+
231
272
  const getPressableStyle = (pressableState) => {
273
+ if (enableMediaQueryStyleSheet) {
274
+ const themeTokens = resolveButtonTokens(pressableState)[viewport]
275
+ return [layoutMediaQueryStyles, selectOuterContainerStyles(themeTokens)]
276
+ }
277
+
232
278
  const themeTokens = resolveButtonTokens(pressableState)
233
- // 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
234
279
  const flexAndWidthStyles =
235
280
  themeTokens.width === '100%' && themeTokens.flex === 1
236
281
  ? selectFlexAndWidthStyles(themeTokens)
@@ -244,7 +289,16 @@ const ButtonBase = React.forwardRef(
244
289
  selectOuterSizeStyles(themeTokens)
245
290
  ]
246
291
  }
247
- const { themeOptions } = useTheme()
292
+
293
+ const dataSetProp =
294
+ flexAndWidthStylesIds || rawRest.dataSet
295
+ ? {
296
+ dataSet: {
297
+ ...(flexAndWidthStylesIds ? { media: flexAndWidthStylesIds } : {}),
298
+ ...rawRest.dataSet
299
+ }
300
+ }
301
+ : {}
248
302
 
249
303
  return (
250
304
  <Pressable
@@ -255,9 +309,12 @@ const ButtonBase = React.forwardRef(
255
309
  disabled={inactive}
256
310
  hrefAttrs={hrefAttrs}
257
311
  {...systemProps}
312
+ {...dataSetProp}
258
313
  >
259
314
  {(pressableState) => {
260
- const themeTokens = resolveButtonTokens(pressableState)
315
+ const themeTokens = enableMediaQueryStyleSheet
316
+ ? resolveButtonTokens(pressableState)[viewport]
317
+ : resolveButtonTokens(pressableState)
261
318
  const containerStyles = selectInnerContainerStyles(themeTokens)
262
319
  const borderStyles = selectBorderStyles(themeTokens)
263
320
  const textStyles = [
@@ -204,7 +204,9 @@ const staticStyles = StyleSheet.create({
204
204
  },
205
205
  linkContainer: {
206
206
  flex: 1,
207
- display: 'flex'
207
+ display: 'flex',
208
+ alignItems: 'stretch',
209
+ justifyContent: 'flex-start'
208
210
  }
209
211
  })
210
212
 
@@ -399,6 +399,7 @@ const Carousel = React.forwardRef(
399
399
  copy,
400
400
  slideDuration = 0,
401
401
  transitionDuration = 0,
402
+ loopDuration = transitionDuration,
402
403
  autoPlay = false,
403
404
  enablePeeking = false,
404
405
  ...rest
@@ -533,6 +534,8 @@ const Carousel = React.forwardRef(
533
534
 
534
535
  const animate = React.useCallback(
535
536
  (panToAnimate, toValue, toIndex) => {
537
+ const applicableTransitionDuration =
538
+ isLastSlide && toIndex === 0 ? loopDuration : transitionDuration
536
539
  const handleAnimationEndToIndex = (...args) => handleAnimationEnd(toIndex, ...args)
537
540
  if (reduceMotionRef.current || isSwiping.current) {
538
541
  Animated.timing(panToAnimate, { toValue, duration: 1, useNativeDriver: false }).start(
@@ -543,14 +546,14 @@ const Carousel = React.forwardRef(
543
546
  ...springConfig,
544
547
  toValue,
545
548
  useNativeDriver: false,
546
- duration: transitionDuration * 1000
549
+ duration: applicableTransitionDuration * 1000
547
550
  }).start(handleAnimationEndToIndex)
548
551
  } else if (enablePeeking || enableDisplayMultipleItemsPerSlide) {
549
552
  Animated.timing(panToAnimate, {
550
553
  ...springConfig,
551
554
  toValue,
552
555
  useNativeDriver: false,
553
- duration: transitionDuration ? transitionDuration * 1000 : 1000
556
+ duration: applicableTransitionDuration ? applicableTransitionDuration * 1000 : 1000
554
557
  }).start(handleAnimationEndToIndex)
555
558
  } else {
556
559
  Animated.spring(panToAnimate, {
@@ -564,6 +567,8 @@ const Carousel = React.forwardRef(
564
567
  springConfig,
565
568
  handleAnimationEnd,
566
569
  transitionDuration,
570
+ loopDuration,
571
+ isLastSlide,
567
572
  isAutoPlayEnabled,
568
573
  enablePeeking,
569
574
  enableDisplayMultipleItemsPerSlide
@@ -1390,6 +1395,12 @@ Carousel.propTypes = {
1390
1395
  * - `autoPlay` and `slideDuration` are required to be set for this to work
1391
1396
  */
1392
1397
  transitionDuration: PropTypes.number,
1398
+ /**
1399
+ * Time it takes in seconds to transition from last slide to first slide
1400
+ * - Default value equals `transitionDuration`'s value
1401
+ * - `autoPlay` and `transitionDuration` are required to be set for this to work
1402
+ */
1403
+ loopDuration: PropTypes.number,
1393
1404
  /**
1394
1405
  * If set to `true`, the Carousel will show the previous and next slides
1395
1406
  * - Default value is `false`
@@ -102,28 +102,26 @@ const renderPrice = (
102
102
  )}
103
103
  </Text>
104
104
  ) : null}
105
- {
106
- <Text>
107
- {renderAmount(
108
- amount,
109
- amountTypographyTokens,
105
+ <Text>
106
+ {renderAmount(
107
+ amount,
108
+ amountTypographyTokens,
109
+ strikeThrough,
110
+ a11yText,
111
+ fontColor,
112
+ themeTokens
113
+ )}
114
+ {isPriceBaseline &&
115
+ cents &&
116
+ renderAmount(
117
+ `${separator}${cents}`,
118
+ centsTypographyTokens,
110
119
  strikeThrough,
111
120
  a11yText,
112
121
  fontColor,
113
122
  themeTokens
114
123
  )}
115
- {isPriceBaseline &&
116
- cents &&
117
- renderAmount(
118
- `${separator}${cents}`,
119
- centsTypographyTokens,
120
- strikeThrough,
121
- a11yText,
122
- fontColor,
123
- themeTokens
124
- )}
125
- </Text>
126
- }
124
+ </Text>
127
125
  {cents && !isPriceBaseline
128
126
  ? renderTypography(`${separator}${cents}`, centsTypographyTokens, undefined, fontColor)
129
127
  : null}
@@ -59,7 +59,7 @@ ResponsiveWithMediaQueryStyleSheet.propTypes = {
59
59
  /**
60
60
  * To hide children of `Responsive` if the current viewport is larger than `max`
61
61
  */
62
- max: PropTypes.oneOf(['sm', 'md', 'lg', 'xl']),
62
+ max: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
63
63
  inheritedStyles: PropTypes.arrayOf(PropTypes.string),
64
64
  children: PropTypes.node.isRequired
65
65
  }
@@ -13,10 +13,12 @@ import {
13
13
  spacingProps,
14
14
  useResponsiveProp,
15
15
  variantProp,
16
- viewProps
16
+ viewProps,
17
+ StyleSheet as StyleSheetUtils,
18
+ createMediaQueryStyles,
19
+ useAllViewportTokens
17
20
  } from '../utils'
18
- import { useThemeTokens } from '../ThemeProvider'
19
- import { useViewport } from '../ViewportProvider'
21
+ import useTheme from '../ThemeProvider/useTheme'
20
22
  import getStackedContent from './getStackedContent'
21
23
  import { staticStyles, selectFlexStyles } from './common'
22
24
 
@@ -75,24 +77,70 @@ const StackView = React.forwardRef(
75
77
  tokens,
76
78
  tag,
77
79
  accessibilityRole,
80
+ dataSet,
78
81
  ...rest
79
82
  },
80
83
  ref
81
84
  ) => {
82
- const viewport = useViewport()
83
85
  const direction = useResponsiveProp(directionProp, 'column')
86
+ const {
87
+ themeOptions: { enableMediaQueryStyleSheet }
88
+ } = useTheme()
89
+
84
90
  const selectedProps = selectProps({
85
91
  accessibilityRole,
86
92
  ...getA11yPropsFromHtmlTag(tag, accessibilityRole),
87
93
  ...rest
88
94
  })
89
95
  const content = getStackedContent(children, { direction, divider, space })
90
- const themeTokens = useThemeTokens('StackView', tokens, variant, { viewport })
91
- const flexStyles = selectFlexStyles(themeTokens)
92
- const size = { width: themeTokens.width }
96
+
97
+ const allTokens = useAllViewportTokens('StackView', tokens, variant)
98
+
99
+ let stackViewStyles
100
+ let dataSetValue = dataSet
101
+
102
+ if (enableMediaQueryStyleSheet) {
103
+ const stylesByViewport = {
104
+ xs: {
105
+ ...selectFlexStyles(allTokens.xs),
106
+ width: allTokens.xs.width
107
+ },
108
+ sm: {
109
+ ...selectFlexStyles(allTokens.sm),
110
+ width: allTokens.sm.width
111
+ },
112
+ md: {
113
+ ...selectFlexStyles(allTokens.md),
114
+ width: allTokens.md.width
115
+ },
116
+ lg: {
117
+ ...selectFlexStyles(allTokens.lg),
118
+ width: allTokens.lg.width
119
+ },
120
+ xl: {
121
+ ...selectFlexStyles(allTokens.xl),
122
+ width: allTokens.xl.width
123
+ }
124
+ }
125
+
126
+ const mediaQueryStyles = createMediaQueryStyles(stylesByViewport)
127
+
128
+ const { ids, styles } = StyleSheetUtils.create({
129
+ stackView: {
130
+ ...mediaQueryStyles
131
+ }
132
+ })
133
+
134
+ stackViewStyles = [staticStyles[direction], styles.stackView]
135
+ dataSetValue = { media: ids.stackView, ...dataSet }
136
+ } else {
137
+ const flexStyles = selectFlexStyles(allTokens.current)
138
+ const size = { width: allTokens.current.width }
139
+ stackViewStyles = [flexStyles, staticStyles[direction], size]
140
+ }
93
141
 
94
142
  return (
95
- <View ref={ref} {...selectedProps} style={[flexStyles, staticStyles[direction], size]}>
143
+ <View ref={ref} {...selectedProps} style={stackViewStyles} dataSet={dataSetValue}>
96
144
  {content}
97
145
  </View>
98
146
  )
@@ -132,7 +180,12 @@ StackView.propTypes = {
132
180
  * A StackView may take any children, but will have no effect if it is only passed one child or is passed children
133
181
  * wrapped in a component. If necessary, children may be wrapped in one React Fragment.
134
182
  */
135
- children: PropTypes.node
183
+ children: PropTypes.node,
184
+ /**
185
+ * Data attributes to be applied to the element. When media query stylesheet is enabled,
186
+ * this will include media query IDs for responsive styling.
187
+ */
188
+ dataSet: PropTypes.object
136
189
  }
137
190
 
138
191
  export default StackView
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { Pressable, StyleSheet, Text, View } from 'react-native'
3
+ import { Platform, Pressable, StyleSheet, Text, View } from 'react-native'
4
4
  import { useThemeTokensCallback, applyTextStyles, useTheme } from '../ThemeProvider'
5
5
  import {
6
6
  a11yProps,
@@ -245,20 +245,21 @@ const styles = StyleSheet.create({
245
245
  position: 'relative',
246
246
  width: '100%'
247
247
  },
248
- pressable: {
249
- outlineWidth: 0,
250
- outlineStyle: 'none',
251
- outlineColor: 'transparent'
252
- },
248
+ pressable:
249
+ Platform.OS === 'web'
250
+ ? {
251
+ outlineWidth: 0,
252
+ outlineStyle: 'none',
253
+ outlineColor: 'transparent'
254
+ }
255
+ : {},
253
256
  buttonContent: {
254
257
  display: 'flex',
255
258
  flexDirection: 'row',
256
259
  alignItems: 'center',
257
260
  justifyContent: 'space-between',
258
261
  width: '100%',
259
- minHeight: 44,
260
- outline: 'none',
261
- boxSizing: 'border-box'
262
+ minHeight: 44
262
263
  }
263
264
  })
264
265
 
@@ -3,6 +3,7 @@ import ThemeProvider from './ThemeProvider'
3
3
  export { default as useTheme } from './useTheme'
4
4
  export { default as useSetTheme } from './useSetTheme'
5
5
  export { default as useResponsiveThemeTokens } from './useResponsiveThemeTokens'
6
+ export { default as useResponsiveThemeTokensCallback } from './useResponsiveThemeTokensCallback'
6
7
  export * from './useThemeTokens'
7
8
 
8
9
  export * from './utils'
@@ -0,0 +1,129 @@
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
+
6
+ const getResponsiveThemeTokens = (
7
+ { rules = [], tokens: defaultThemeTokens = {} },
8
+ tokensProp,
9
+ variants = {},
10
+ states
11
+ ) => {
12
+ const appearances = mergeAppearances(variants, states)
13
+
14
+ const tokensByViewport = Object.fromEntries(
15
+ viewports.keys.map((viewport) => [viewport, { ...defaultThemeTokens }])
16
+ )
17
+
18
+ // Go through each rule and collect them for the corresponding viewport if they apply
19
+ rules.forEach((rule) => {
20
+ if (doesRuleApply(rule, appearances)) {
21
+ // If the rule does not have a viewport specified, we collect it in all viewports
22
+ let targetViewports = rule.if.viewport || viewports.keys
23
+ if (!Array.isArray(targetViewports)) {
24
+ targetViewports = [targetViewports]
25
+ }
26
+ targetViewports.forEach((viewport) => {
27
+ tokensByViewport[viewport] = { ...tokensByViewport[viewport], ...rule.tokens }
28
+ })
29
+ }
30
+ })
31
+
32
+ Object.keys(tokensByViewport).forEach((viewport) => {
33
+ tokensByViewport[viewport] = resolveThemeTokens(
34
+ tokensByViewport[viewport],
35
+ appearances,
36
+ tokensProp
37
+ )
38
+ })
39
+
40
+ return tokensByViewport
41
+ }
42
+
43
+ const doesRuleApply = (rule, appearances) =>
44
+ Object.entries(rule.if).every((condition) => doesConditionApply(condition, appearances))
45
+
46
+ const doesConditionApply = ([key, value], appearances) => {
47
+ if (key === 'viewport') {
48
+ return true
49
+ }
50
+ // use null rather than undefined so we can serialise the value in themes
51
+ const appearanceValue = appearances[key] ?? null
52
+ return Array.isArray(value) ? value.includes(appearanceValue) : value === appearanceValue
53
+ }
54
+
55
+ /**
56
+ * @typedef {import('../utils/props/tokens.js').TokensSet} TokensSet
57
+ * @typedef {import('../utils/props/tokens.js').TokensProp} TokensProp
58
+ * @typedef {import('../utils/props/variantProp').AppearanceSet} AppearanceSet
59
+ */
60
+
61
+ /**
62
+ * Returns a memoised tokens getter function that gets responsive tokens for all viewports,
63
+ * similar to calling useResponsiveThemeTokens but with the callback pattern of useThemeTokensCallback.
64
+ *
65
+ * Scenarios where `useResponsiveThemeTokensCallback` should be used:
66
+ *
67
+ * - Where responsive tokens are to be obtained from state that is accessible only in scopes like callbacks
68
+ * and render functions, where calling useResponsiveThemeTokens directly would be disallowed by React's hook rules.
69
+ * - When using media query stylesheets and need to resolve tokens based on dynamic state (e.g., pressed, hovered)
70
+ * that changes at runtime.
71
+ * - Passing a responsive tokens getter down via a child component's `tokens` prop, applying rules using the
72
+ * child component's current state.
73
+ *
74
+ * The function returned may be called with an object of state appearances to get an object
75
+ * of tokens for each viewport, which can then be passed to createMediaQueryStyles.
76
+ *
77
+ * @example
78
+ * // Resolving responsive tokens inside Pressable's style function, based on Pressable state
79
+ * const PressMe = ({ tokens, variant, children }) => {
80
+ * const getResponsiveTokens = useResponsiveThemeTokensCallback('PressMe', tokens, variant)
81
+ * const getPressableStyle = ({ pressed }) => {
82
+ * const responsiveTokens = getResponsiveTokens({ pressed })
83
+ * const mediaQueryStyles = createMediaQueryStyles(responsiveTokens)
84
+ * return mediaQueryStyles
85
+ * }
86
+ * return <Pressable style={getPressableStyle}>{children}</Pressable>
87
+ * }
88
+ *
89
+ * @example
90
+ * // Setting the theme in a parent and resolving it in a child based on child's state
91
+ * const MenuButton = ({ tokens, variant, ...buttonProps }) => {
92
+ * // Define what theme, variant etc we want in this component...
93
+ * const getResponsiveTokens = useResponsiveThemeTokensCallback('Button', tokens, variant)
94
+ * // ...resolve them in another component based on its state (e.g. press, hover...)
95
+ * return <ButtonBase tokens={getResponsiveTokens} accessibilityRole="menuitem" {...buttonProps} />
96
+ * }
97
+ *
98
+ * @typedef {Object} ResponsiveObject
99
+ * @property {TokensSet} xs
100
+ * @property {TokensSet} sm
101
+ * @property {TokensSet} md
102
+ * @property {TokensSet} lg
103
+ * @property {TokensSet} xl
104
+ *
105
+ * @param {string} componentName - the name as defined in the theme schema of the component whose theme is to be used
106
+ * @param {TokensProp} [tokens] - every themed component should accept a `tokens` prop allowing theme tokens to be overridden
107
+ * @param {AppearanceSet} [variants] - variants passed in as props that don't change dynamically
108
+ * @returns {(states: AppearanceSet, tokenOverrides?: TokensProp) => ResponsiveObject}
109
+ * - callback function that returns an overridable responsive tokens object for current state. Only pass
110
+ * tokenOverrides in rare cases where tokens overrides are also generated outside hook scope.
111
+ */
112
+ const useResponsiveThemeTokensCallback = (componentName, tokens = {}, variants = {}) => {
113
+ const theme = useTheme()
114
+ const componentTheme = getComponentTheme(theme, componentName)
115
+ const getResponsiveThemeTokensCallback = useCallback(
116
+ (states, tokenOverrides) => {
117
+ const resolvedTokens = resolveThemeTokens(
118
+ tokens,
119
+ mergeAppearances(variants, states),
120
+ tokenOverrides
121
+ )
122
+ return getResponsiveThemeTokens(componentTheme, resolvedTokens, variants, states)
123
+ },
124
+ [componentTheme, tokens, variants]
125
+ )
126
+ return getResponsiveThemeTokensCallback
127
+ }
128
+
129
+ export default useResponsiveThemeTokensCallback
package/src/index.js CHANGED
@@ -83,7 +83,8 @@ export {
83
83
  applyOuterBorder,
84
84
  applyTextStyles,
85
85
  applyShadowToken,
86
- useResponsiveThemeTokens
86
+ useResponsiveThemeTokens,
87
+ useResponsiveThemeTokensCallback
87
88
  } from './ThemeProvider'
88
89
 
89
90
  export * from './utils'
@@ -1,8 +1,9 @@
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
 
4
5
  const StyleSheet = {
5
6
  create: createStyleSheet
6
7
  }
7
8
 
8
- export { StyleSheet, createMediaQueryStyles }
9
+ export { StyleSheet, createMediaQueryStyles, useAllViewportTokens }
@@ -0,0 +1,32 @@
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
+
22
+ const xs = useThemeTokens(componentName, tokens, variant, { viewport: 'xs' })
23
+ const sm = useThemeTokens(componentName, tokens, variant, { viewport: 'sm' })
24
+ const md = useThemeTokens(componentName, tokens, variant, { viewport: 'md' })
25
+ const lg = useThemeTokens(componentName, tokens, variant, { viewport: 'lg' })
26
+ const xl = useThemeTokens(componentName, tokens, variant, { viewport: 'xl' })
27
+ const current = useThemeTokens(componentName, tokens, variant, { viewport })
28
+
29
+ return { xs, sm, md, lg, xl, current }
30
+ }
31
+
32
+ export default useAllViewportTokens