@telus-uds/components-base 3.19.0 → 3.21.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 (39) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/lib/cjs/Button/ButtonDropdown.js +1 -0
  3. package/lib/cjs/ExpandCollapseMini/ExpandCollapseMiniControl.js +8 -21
  4. package/lib/cjs/Link/LinkBase.js +8 -9
  5. package/lib/cjs/MultiSelectFilter/MultiSelectFilter.js +8 -8
  6. package/lib/cjs/Responsive/ResponsiveWithMediaQueryStyleSheet.js +1 -1
  7. package/lib/cjs/Spacer/Spacer.js +65 -5
  8. package/lib/cjs/StackView/StackView.js +62 -12
  9. package/lib/cjs/Tabs/TabsDropdown.js +4 -5
  10. package/lib/cjs/utils/index.js +8 -0
  11. package/lib/cjs/utils/ssr-media-query/index.js +7 -0
  12. package/lib/cjs/utils/ssr-media-query/utils/use-all-viewport-tokens.js +53 -0
  13. package/lib/cjs/utils/useMediaQuerySpacing.js +121 -0
  14. package/lib/esm/Button/ButtonDropdown.js +1 -0
  15. package/lib/esm/ExpandCollapseMini/ExpandCollapseMiniControl.js +8 -21
  16. package/lib/esm/Link/LinkBase.js +8 -9
  17. package/lib/esm/MultiSelectFilter/MultiSelectFilter.js +8 -8
  18. package/lib/esm/Responsive/ResponsiveWithMediaQueryStyleSheet.js +1 -1
  19. package/lib/esm/Spacer/Spacer.js +66 -6
  20. package/lib/esm/StackView/StackView.js +63 -13
  21. package/lib/esm/Tabs/TabsDropdown.js +4 -5
  22. package/lib/esm/utils/index.js +1 -0
  23. package/lib/esm/utils/ssr-media-query/index.js +2 -1
  24. package/lib/esm/utils/ssr-media-query/utils/use-all-viewport-tokens.js +48 -0
  25. package/lib/esm/utils/useMediaQuerySpacing.js +116 -0
  26. package/lib/package.json +5 -5
  27. package/package.json +5 -5
  28. package/src/Button/ButtonDropdown.jsx +1 -0
  29. package/src/ExpandCollapseMini/ExpandCollapseMiniControl.jsx +9 -16
  30. package/src/Link/LinkBase.jsx +11 -9
  31. package/src/MultiSelectFilter/MultiSelectFilter.jsx +9 -8
  32. package/src/Responsive/ResponsiveWithMediaQueryStyleSheet.jsx +1 -1
  33. package/src/Spacer/Spacer.jsx +54 -7
  34. package/src/StackView/StackView.jsx +62 -9
  35. package/src/Tabs/TabsDropdown.jsx +10 -9
  36. package/src/utils/index.js +1 -0
  37. package/src/utils/ssr-media-query/index.js +2 -1
  38. package/src/utils/ssr-media-query/utils/use-all-viewport-tokens.js +32 -0
  39. package/src/utils/useMediaQuerySpacing.js +124 -0
@@ -62,36 +62,28 @@ const ExpandCollapseMiniControl = React.forwardRef(
62
62
  const iconBaselineOffset = 0
63
63
  const hoverTranslateY = 4
64
64
 
65
- // Calculate baseline alignment to vertically center icon with text
66
- // This combines font and icon metrics with adjustments for visual balance
67
- const fontBaseline = fontSize / hoverTranslateY // Quarter of font size - adjusts for text's visual center point
68
- const iconBaseline = iconSize / hoverTranslateY // Quarter of icon size - adjusts for icon's visual center point
69
- const staticOffset = hoverTranslateY // Fixed downward adjustment to fine-tune vertical alignment
70
- const sizeCompensation = -Math.abs(iconSize - fontSize) // Compensates when icon and text sizes differ significantly
65
+ const fontBaseline = fontSize / hoverTranslateY
66
+ const iconBaseline = iconSize / hoverTranslateY
67
+ const staticOffset = hoverTranslateY
68
+ const sizeCompensation = -Math.abs(iconSize - fontSize)
71
69
 
72
70
  const baselineAlignment = fontBaseline + iconBaseline - staticOffset + sizeCompensation
73
71
 
74
- if (Platform.OS !== 'web') {
75
- // For native platforms, use baseline alignment with optional offset
76
- return { iconTranslateY: baselineAlignment + iconBaselineOffset }
77
- }
72
+ const mobileAdjustment = Platform.OS !== 'web' ? -2 : 0
78
73
 
79
74
  if (isHovered) {
80
- // Apply animation offset to the baseline-aligned position
81
- // When expanded: move icon UP (1.3 the hover distance for clear movement)
82
- // When collapsed: move icon DOWN (single hover distance)
83
75
  const hoverMovementDistance = 1.3
84
76
  const animationOffset = expanded
85
77
  ? -(hoverTranslateY * hoverMovementDistance)
86
78
  : hoverTranslateY
87
79
 
88
80
  return {
89
- iconTranslateY: baselineAlignment + iconBaselineOffset + animationOffset
81
+ iconTranslateY:
82
+ baselineAlignment + iconBaselineOffset + animationOffset + mobileAdjustment
90
83
  }
91
84
  }
92
85
 
93
- // Default state uses baseline alignment with optional offset
94
- return { iconTranslateY: baselineAlignment + iconBaselineOffset }
86
+ return { iconTranslateY: baselineAlignment + iconBaselineOffset + mobileAdjustment }
95
87
  }
96
88
 
97
89
  return (
@@ -103,6 +95,7 @@ const ExpandCollapseMiniControl = React.forwardRef(
103
95
  ...linkTokens,
104
96
  ...getTokens(linkState),
105
97
  iconSize,
98
+ blockFontSize: fontSize,
106
99
  blockLineHeight: lineHeight
107
100
  })}
108
101
  ref={ref}
@@ -171,9 +171,12 @@ const LinkBase = React.forwardRef(
171
171
  const themeTokens = resolveLinkTokens(linkState)
172
172
  const outerBorderStyles = selectOuterBorderStyles(themeTokens)
173
173
  const decorationStyles = selectDecorationStyles(themeTokens)
174
+
175
+ const mobileCompensation = null
176
+
174
177
  return [
175
178
  outerBorderStyles,
176
- staticStyles.outerBorderStyles,
179
+ mobileCompensation,
177
180
  blockLeftStyle,
178
181
  decorationStyles,
179
182
  hasIcon && staticStyles.rowContainer
@@ -191,11 +194,14 @@ const LinkBase = React.forwardRef(
191
194
  const IconComponent = icon || themeTokens.icon
192
195
  const { iconSpace } = themeTokens
193
196
 
197
+ const isTextOnlyLink = !IconComponent && !icon && accessibilityRole === 'link'
198
+ const adjustedIconSpace = Platform.OS !== 'web' && isTextOnlyLink ? 0 : iconSpace
199
+
194
200
  return (
195
201
  <IconText
196
202
  icon={IconComponent}
197
203
  iconPosition={iconPosition}
198
- space={iconSpace}
204
+ space={adjustedIconSpace}
199
205
  iconProps={{
200
206
  ...iconProps,
201
207
  tokens: iconTokens,
@@ -274,15 +280,11 @@ const staticStyles = StyleSheet.create({
274
280
  }
275
281
  })
276
282
  },
277
- outerBorderStyles: {
283
+ outerBorderCompensation: {
278
284
  ...(Platform.OS !== 'web' && {
279
- margin: 0,
280
285
  marginHorizontal: 2,
281
- padding: 0
282
- }),
283
- ...(Platform.OS === 'android' && {
284
- paddingHorizontal: 2,
285
- paddingTop: 2
286
+ paddingHorizontal: Platform.OS === 'android' ? 2 : 0,
287
+ paddingTop: Platform.OS === 'android' ? 2 : 0
286
288
  })
287
289
  }
288
290
  })
@@ -65,7 +65,6 @@ const selectContainerStyle = (windowHeight, windowWidth) => ({
65
65
  })
66
66
 
67
67
  const TOTAL_COLUMNS = 12
68
- const MAX_ITEMS_THRESHOLD = 12
69
68
 
70
69
  const MultiSelectFilter = React.forwardRef(
71
70
  (
@@ -174,13 +173,15 @@ const MultiSelectFilter = React.forwardRef(
174
173
  const getCopy = useCopy({ dictionary, copy })
175
174
  const colSizeNotMobile = items.length > rowLimit ? 2 : 1
176
175
  const colSize = viewport !== 'xs' ? colSizeNotMobile : 1
177
- const itemsLengthNotMobile = items.length > 24 ? items.length / 2 : rowLimit
178
- const rowLength = viewport !== 'xs' ? itemsLengthNotMobile : items.length
176
+
177
+ let rowLength = items.length
178
+ if (viewport !== 'xs' && colSize === 2) {
179
+ rowLength = Math.ceil(items.length / 2)
180
+ }
179
181
 
180
182
  React.useEffect(() => {
181
- if (colSize === 1) return setMaxWidth(false)
182
- return colSize === 2 && setMaxWidth(true)
183
- }, [colSize])
183
+ setMaxWidth(items.length >= rowLimit)
184
+ }, [items.length, rowLimit])
184
185
 
185
186
  React.useEffect(() => setCheckedIds(currentValues ?? []), [currentValues])
186
187
 
@@ -414,14 +415,14 @@ const MultiSelectFilter = React.forwardRef(
414
415
  dismissWhenPressedOutside={dismissWhenPressedOutside}
415
416
  onClose={onClose}
416
417
  overlaidPosition={overlaidPosition}
417
- maxHeight={items.length > MAX_ITEMS_THRESHOLD ? true : maxHeight}
418
+ maxHeight={items.length >= rowLimit ? true : maxHeight}
418
419
  maxHeightSize={maxHeightSize}
419
420
  maxWidthSize={maxWidthSize}
420
421
  minHeight={minHeight}
421
422
  minWidth={minWidth}
422
423
  tokens={{
423
424
  ...tokens,
424
- maxWidth: items.length > MAX_ITEMS_THRESHOLD ? true : maxWidth,
425
+ maxWidth: items.length >= rowLimit ? true : maxWidth,
425
426
  borderColor: containerBorderColor
426
427
  }}
427
428
  copy={copy}
@@ -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
  }
@@ -1,7 +1,17 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { StyleSheet, View } from 'react-native'
4
- import { a11yProps, selectSystemProps, spacingProps, useSpacingScale, viewProps } from '../utils'
3
+ import { View, StyleSheet } from 'react-native'
4
+ import {
5
+ a11yProps,
6
+ selectSystemProps,
7
+ spacingProps,
8
+ useSpacingScale,
9
+ viewProps,
10
+ StyleSheet as StyleSheetUtils,
11
+ createMediaQueryStyles
12
+ } from '../utils'
13
+ import useMediaQuerySpacing from '../utils/useMediaQuerySpacing'
14
+ import useTheme from '../ThemeProvider/useTheme'
5
15
 
6
16
  /**
7
17
  * @typedef {import('../utils/props/spacingProps.js').SpacingValue} SpacingValue
@@ -56,11 +66,43 @@ const selectSizeStyle = (size, direction) => ({
56
66
  * Spacer has no content and is ignored by tools such as screen readers. Use `Divider` for
57
67
  * separations between elements that may be treated as semantically meaningful on web.
58
68
  */
59
- const Spacer = React.forwardRef(({ space = 1, direction = 'column', ...rest }, ref) => {
60
- const size = useSpacingScale(space)
61
- const sizeStyle = selectSizeStyle(size, direction)
69
+ const Spacer = React.forwardRef(({ space = 1, direction = 'column', dataSet, ...rest }, ref) => {
70
+ const {
71
+ themeOptions: { enableMediaQueryStyleSheet }
72
+ } = useTheme()
62
73
 
63
- return <View ref={ref} style={[staticStyles.stretch, sizeStyle]} {...selectProps(rest)} />
74
+ const { sizeByViewport } = useMediaQuerySpacing(space)
75
+
76
+ const fallbackSize = useSpacingScale(space)
77
+ const sizeStyle = selectSizeStyle(fallbackSize, direction)
78
+
79
+ let spacerStyles
80
+ let dataSetValue = dataSet
81
+
82
+ if (enableMediaQueryStyleSheet) {
83
+ const sizeKey = direction === 'row' ? 'width' : 'height'
84
+ const stylesByViewport = {
85
+ xs: { [sizeKey]: sizeByViewport.xs, ...staticStyles.stretch },
86
+ sm: { [sizeKey]: sizeByViewport.sm, ...staticStyles.stretch },
87
+ md: { [sizeKey]: sizeByViewport.md, ...staticStyles.stretch },
88
+ lg: { [sizeKey]: sizeByViewport.lg, ...staticStyles.stretch },
89
+ xl: { [sizeKey]: sizeByViewport.xl, ...staticStyles.stretch }
90
+ }
91
+ const mediaQueryStyles = createMediaQueryStyles(stylesByViewport)
92
+
93
+ const { ids, styles } = StyleSheetUtils.create({
94
+ spacer: {
95
+ ...mediaQueryStyles
96
+ }
97
+ })
98
+
99
+ spacerStyles = styles.spacer
100
+ dataSetValue = { media: ids.spacer, ...dataSet }
101
+ } else {
102
+ spacerStyles = [staticStyles.stretch, sizeStyle]
103
+ }
104
+
105
+ return <View ref={ref} style={spacerStyles} dataSet={dataSetValue} {...selectProps(rest)} />
64
106
  })
65
107
  Spacer.displayName = 'Spacer'
66
108
 
@@ -78,7 +120,12 @@ Spacer.propTypes = {
78
120
  * - `'column'` (default) applies space vertically; has a fixed height and not width.
79
121
  * - `'row'` applies space horizontally; has a fixed width and not height.
80
122
  */
81
- direction: PropTypes.oneOf(['column', 'row'])
123
+ direction: PropTypes.oneOf(['column', 'row']),
124
+ /**
125
+ * Data attributes to be applied to the element. When media query stylesheet is enabled,
126
+ * this will include media query IDs for responsive styling.
127
+ */
128
+ dataSet: PropTypes.object
82
129
  }
83
130
 
84
131
  const staticStyles = StyleSheet.create({
@@ -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
 
@@ -9,6 +9,7 @@ export { default as info } from './info'
9
9
  export { default as useCopy } from './useCopy'
10
10
  export { default as useHash } from './useHash'
11
11
  export { default as useSpacingScale } from './useSpacingScale'
12
+ export { default as useMediaQuerySpacing } from './useMediaQuerySpacing'
12
13
  export { default as useResponsiveProp } from './useResponsiveProp'
13
14
  export { default as useOverlaidPosition } from './useOverlaidPosition'
14
15
  export { default as useSafeLayoutEffect } from './useSafeLayoutEffect'
@@ -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
@@ -0,0 +1,124 @@
1
+ import { useThemeTokens } from '../ThemeProvider'
2
+ import { resolveResponsiveProp } from './useResponsiveProp'
3
+
4
+ /**
5
+ * @typedef {import('@telus-uds/system-constants/viewports').Viewport} Viewport
6
+ * @typedef {import('./props/spacingProps.js').SpacingValue} SpacingValue
7
+ * @typedef {import('./props/spacingProps.js').SpacingIndex} SpacingIndex
8
+ * @typedef {import('./props/spacingProps.js').SpacingObject} SpacingObject
9
+ */
10
+
11
+ /**
12
+ * A utility hook that simplifies implementing media query-based responsive spacing.
13
+ *
14
+ * This hook handles the complexity of:
15
+ * - Detecting if a space value is responsive (has viewport keys)
16
+ * - Fetching theme tokens for each viewport
17
+ * - Resolving the correct space index for each viewport
18
+ * - Extracting actual pixel values from theme tokens
19
+ *
20
+ * ## Usage
21
+ *
22
+ * ```jsx
23
+ * const { sizeByViewport } = useMediaQuerySpacing(space, 'spacingScale')
24
+ *
25
+ * // Use sizeByViewport to create media query styles
26
+ * const stylesByViewport = {
27
+ * xs: { padding: sizeByViewport.xs },
28
+ * sm: { padding: sizeByViewport.sm },
29
+ * md: { padding: sizeByViewport.md },
30
+ * lg: { padding: sizeByViewport.lg },
31
+ * xl: { padding: sizeByViewport.xl }
32
+ * }
33
+ * const mediaQueryStyles = createMediaQueryStyles(stylesByViewport)
34
+ * ```
35
+ *
36
+ * ## Parameters
37
+ *
38
+ * @param {SpacingValue} spaceValue - A spacing value (number or responsive object with viewport keys)
39
+ * @param {string} tokenKey - The theme token key to use (e.g., 'spacingScale', 'Typography')
40
+ * @param {object} [tokens={}] - Additional tokens to pass to useThemeTokens
41
+ * @param {object} [variant={}] - Variant to pass to useThemeTokens
42
+ *
43
+ * ## Returns
44
+ *
45
+ * @returns {{
46
+ * spaceIndexByViewport: { xs: number, sm: number, md: number, lg: number, xl: number },
47
+ * sizeByViewport: { xs: number, sm: number, md: number, lg: number, xl: number },
48
+ * tokensByViewport: { xs: object, sm: object, md: object, lg: object, xl: object }
49
+ * }}
50
+ *
51
+ * - `spaceIndexByViewport`: The resolved space index for each viewport
52
+ * - `sizeByViewport`: The actual pixel/number values for each viewport
53
+ * - `tokensByViewport`: The full theme tokens for each viewport (for advanced use cases)
54
+ */
55
+ const useMediaQuerySpacing = (spaceValue, tokenKey = 'spacingScale', tokens = {}, variant = {}) => {
56
+ const isResponsive =
57
+ typeof spaceValue === 'object' &&
58
+ spaceValue !== null &&
59
+ !spaceValue.space &&
60
+ !spaceValue.options
61
+
62
+ const getSpaceIndex = (viewport) => {
63
+ if (isResponsive) {
64
+ return resolveResponsiveProp(spaceValue, viewport)
65
+ }
66
+ if (typeof spaceValue === 'number') {
67
+ return spaceValue
68
+ }
69
+ return spaceValue?.space ?? 1
70
+ }
71
+
72
+ const spaceIndexByViewport = {
73
+ xs: getSpaceIndex('xs'),
74
+ sm: getSpaceIndex('sm'),
75
+ md: getSpaceIndex('md'),
76
+ lg: getSpaceIndex('lg'),
77
+ xl: getSpaceIndex('xl')
78
+ }
79
+
80
+ const tokensXs = useThemeTokens(tokenKey, tokens, variant, {
81
+ space: spaceIndexByViewport.xs,
82
+ viewport: 'xs'
83
+ })
84
+ const tokensSm = useThemeTokens(tokenKey, tokens, variant, {
85
+ space: spaceIndexByViewport.sm,
86
+ viewport: 'sm'
87
+ })
88
+ const tokensMd = useThemeTokens(tokenKey, tokens, variant, {
89
+ space: spaceIndexByViewport.md,
90
+ viewport: 'md'
91
+ })
92
+ const tokensLg = useThemeTokens(tokenKey, tokens, variant, {
93
+ space: spaceIndexByViewport.lg,
94
+ viewport: 'lg'
95
+ })
96
+ const tokensXl = useThemeTokens(tokenKey, tokens, variant, {
97
+ space: spaceIndexByViewport.xl,
98
+ viewport: 'xl'
99
+ })
100
+
101
+ const sizeByViewport = {
102
+ xs: tokensXs.size ?? 0,
103
+ sm: tokensSm.size ?? 0,
104
+ md: tokensMd.size ?? 0,
105
+ lg: tokensLg.size ?? 0,
106
+ xl: tokensXl.size ?? 0
107
+ }
108
+
109
+ const tokensByViewport = {
110
+ xs: tokensXs,
111
+ sm: tokensSm,
112
+ md: tokensMd,
113
+ lg: tokensLg,
114
+ xl: tokensXl
115
+ }
116
+
117
+ return {
118
+ spaceIndexByViewport,
119
+ sizeByViewport,
120
+ tokensByViewport
121
+ }
122
+ }
123
+
124
+ export default useMediaQuerySpacing