@telus-uds/components-base 1.72.0 → 1.73.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 (53) hide show
  1. package/CHANGELOG.md +20 -2
  2. package/lib/Box/Box.js +17 -6
  3. package/lib/FlexGrid/Col/Col.js +42 -19
  4. package/lib/FlexGrid/FlexGrid.js +40 -17
  5. package/lib/FlexGrid/Row/Row.js +45 -22
  6. package/lib/Listbox/ListboxGroup.js +7 -1
  7. package/lib/MultiSelectFilter/MultiSelectFilter.js +1 -0
  8. package/lib/Notification/Notification.js +13 -5
  9. package/lib/OrderedList/ItemBase.js +7 -1
  10. package/lib/Responsive/Responsive.js +24 -14
  11. package/lib/Responsive/ResponsiveProp.js +46 -0
  12. package/lib/Responsive/ResponsiveWithMediaQueryStyleSheet.js +72 -0
  13. package/lib/ThemeProvider/ThemeProvider.js +5 -2
  14. package/lib/ThemeProvider/index.js +9 -1
  15. package/lib/ThemeProvider/useResponsiveThemeTokens.js +89 -0
  16. package/lib/Typography/Typography.js +48 -22
  17. package/lib/server.js +40 -0
  18. package/lib/utils/ssr-media-query/utils/create-media-query-styles.js +39 -6
  19. package/lib-module/Box/Box.js +17 -6
  20. package/lib-module/FlexGrid/Col/Col.js +42 -19
  21. package/lib-module/FlexGrid/FlexGrid.js +40 -17
  22. package/lib-module/FlexGrid/Row/Row.js +45 -22
  23. package/lib-module/Listbox/ListboxGroup.js +7 -1
  24. package/lib-module/MultiSelectFilter/MultiSelectFilter.js +1 -0
  25. package/lib-module/Notification/Notification.js +13 -5
  26. package/lib-module/OrderedList/ItemBase.js +7 -1
  27. package/lib-module/Responsive/Responsive.js +24 -15
  28. package/lib-module/Responsive/ResponsiveProp.js +39 -0
  29. package/lib-module/Responsive/ResponsiveWithMediaQueryStyleSheet.js +64 -0
  30. package/lib-module/ThemeProvider/ThemeProvider.js +5 -2
  31. package/lib-module/ThemeProvider/index.js +1 -0
  32. package/lib-module/ThemeProvider/useResponsiveThemeTokens.js +81 -0
  33. package/lib-module/Typography/Typography.js +50 -24
  34. package/lib-module/server.js +4 -0
  35. package/lib-module/utils/ssr-media-query/utils/create-media-query-styles.js +36 -6
  36. package/package.json +13 -2
  37. package/src/Box/Box.jsx +35 -17
  38. package/src/FlexGrid/Col/Col.jsx +42 -13
  39. package/src/FlexGrid/FlexGrid.jsx +40 -11
  40. package/src/FlexGrid/Row/Row.jsx +40 -16
  41. package/src/Listbox/ListboxGroup.jsx +9 -2
  42. package/src/MultiSelectFilter/MultiSelectFilter.jsx +2 -0
  43. package/src/Notification/Notification.jsx +15 -3
  44. package/src/OrderedList/ItemBase.jsx +14 -2
  45. package/src/Responsive/Responsive.jsx +24 -11
  46. package/src/Responsive/ResponsiveProp.jsx +33 -0
  47. package/src/Responsive/ResponsiveWithMediaQueryStyleSheet.jsx +58 -0
  48. package/src/ThemeProvider/ThemeProvider.jsx +5 -2
  49. package/src/ThemeProvider/index.js +1 -0
  50. package/src/ThemeProvider/useResponsiveThemeTokens.js +85 -0
  51. package/src/Typography/Typography.jsx +72 -24
  52. package/src/server.js +4 -0
  53. package/src/utils/ssr-media-query/utils/create-media-query-styles.js +21 -6
@@ -0,0 +1,85 @@
1
+ import { viewports } from '@telus-uds/system-constants'
2
+ import useTheme from './useTheme'
3
+ import { getComponentTheme, mergeAppearances, resolveThemeTokens } from './utils'
4
+
5
+ const getResponsiveThemeTokens = (
6
+ { rules = [], tokens: defaultThemeTokens = {} },
7
+ tokensProp,
8
+ variants = {},
9
+ states
10
+ ) => {
11
+ const appearances = mergeAppearances(variants, states)
12
+
13
+ const tokensByViewport = Object.fromEntries(
14
+ viewports.keys.map((viewport) => [viewport, { ...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] = { ...tokensByViewport[viewport], ...rule.tokens }
27
+ })
28
+ }
29
+ })
30
+
31
+ Object.keys(tokensByViewport).forEach((viewport) => {
32
+ tokensByViewport[viewport] = resolveThemeTokens(
33
+ tokensByViewport[viewport],
34
+ appearances,
35
+ tokensProp
36
+ )
37
+ })
38
+
39
+ return tokensByViewport
40
+ }
41
+
42
+ const doesRuleApply = (rule, appearances) =>
43
+ Object.entries(rule.if).every((condition) => doesConditionApply(condition, appearances))
44
+
45
+ const doesConditionApply = ([key, value], appearances) => {
46
+ if (key === 'viewport') {
47
+ return true
48
+ }
49
+ // use null rather than undefined so we can serialise the value in themes
50
+ const appearanceValue = appearances[key] ?? null
51
+ return Array.isArray(value) ? value.includes(appearanceValue) : value === appearanceValue
52
+ }
53
+
54
+ /**
55
+ * @typedef {import('../utils/props/tokens.js').TokensSet} TokensSet
56
+ * @typedef {import('../utils/props/tokens.js').TokensProp} TokensProp
57
+ * @typedef {import('../utils/props/variantProp').AppearanceSet} AppearanceSet
58
+ *
59
+ * Returns a complete set of tokens for a component for each viewport based on which of the
60
+ * component's theme rules apply to the current set of theme appearances.
61
+ * Pass the returned output to createMediaQueryStyles to generate media query styles for use inside
62
+ * the media query stylesheet from './utils/ssr-media-query'.
63
+ *
64
+ * @typedef {Object} ResponsiveObject
65
+ * @property {TokensSet} xs
66
+ * @property {TokensSet} sm
67
+ * @property {TokensSet} md
68
+ * @property {TokensSet} lg
69
+ * @property {TokensSet} xl
70
+ *
71
+ * @param { string } componentName
72
+ * @param { TokensProp } tokens
73
+ * @param { AppearanceSet } variants
74
+ * @param { AppearanceSet } states
75
+ * @returns { ResponsiveObject }
76
+ */
77
+
78
+ const useResponsiveThemeTokens = (componentName, tokens = {}, variants = {}, states = {}) => {
79
+ const theme = useTheme()
80
+ const componentTheme = getComponentTheme(theme, componentName)
81
+ const themeTokens = getResponsiveThemeTokens(componentTheme, tokens, variants, states)
82
+ return themeTokens
83
+ }
84
+
85
+ export default useResponsiveThemeTokens
@@ -2,8 +2,7 @@ import React, { forwardRef } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import { Text, View } from 'react-native'
4
4
 
5
- import { useTheme, useThemeTokens } from '../ThemeProvider'
6
- import { useViewport } from '../ViewportProvider'
5
+ import { useResponsiveThemeTokens, useTheme, useThemeTokens } from '../ThemeProvider'
7
6
  import { applyTextStyles } from '../ThemeProvider/utils'
8
7
  import {
9
8
  a11yProps,
@@ -15,17 +14,29 @@ import {
15
14
  textTags,
16
15
  textProps,
17
16
  viewProps,
18
- getA11yPropsFromHtmlTag
17
+ getA11yPropsFromHtmlTag,
18
+ StyleSheet,
19
+ createMediaQueryStyles
19
20
  } from '../utils'
21
+ import { useViewport } from '../ViewportProvider'
20
22
  /**
21
23
  * @typedef {import('../utils/a11y/semantics').TextTag} TextTag
22
24
  */
23
25
 
24
26
  const [selectContainerProps, selectedContainerPropTypes] = selectSystemProps([a11yProps, viewProps])
25
27
  const [selectTextProps, selectedTextPropTypes] = selectSystemProps([textProps])
26
-
27
28
  const selectTextStyles = (
28
- { fontWeight, fontSize, color, lineHeight, fontName, textAlign, textTransform, letterSpacing },
29
+ {
30
+ fontWeight,
31
+ fontSize,
32
+ color,
33
+ lineHeight,
34
+ fontName,
35
+ textAlign,
36
+ textTransform,
37
+ letterSpacing,
38
+ textDecorationLine
39
+ },
29
40
  themeOptions
30
41
  ) =>
31
42
  applyTextStyles({
@@ -37,7 +48,8 @@ const selectTextStyles = (
37
48
  themeOptions,
38
49
  textAlign,
39
50
  textTransform,
40
- letterSpacing
51
+ letterSpacing,
52
+ textDecorationLine
41
53
  })
42
54
 
43
55
  // General-purpose flexible theme-neutral base component for text
@@ -59,19 +71,56 @@ const Typography = forwardRef(
59
71
  ref
60
72
  ) => {
61
73
  const viewport = useViewport()
62
- const { superScriptFontSize, ...themeTokens } = useThemeTokens('Typography', tokens, variant, {
63
- viewport
64
- })
65
74
  const { themeOptions } = useTheme()
66
75
 
76
+ const { enableMediaQueryStyleSheet } = themeOptions
77
+
78
+ const useTokens = enableMediaQueryStyleSheet ? useResponsiveThemeTokens : useThemeTokens
79
+ const themeTokens = useTokens('Typography', tokens, variant)
80
+ const maxFontSizeMultiplier = enableMediaQueryStyleSheet
81
+ ? getMaxFontMultiplier(themeTokens[viewport])
82
+ : getMaxFontMultiplier(themeTokens)
83
+ const textDecorationLine = strikeThrough ? 'line-through' : 'none'
84
+
85
+ let textStyles
86
+ let mediaIds
87
+
88
+ if (enableMediaQueryStyleSheet) {
89
+ const transformedThemeTokens = Object.entries(themeTokens).reduce(
90
+ (acc, [vp, viewportTokens]) => {
91
+ acc[vp] = selectTextStyles(
92
+ {
93
+ textAlign: align,
94
+ textDecorationLine,
95
+ ...viewportTokens
96
+ },
97
+ themeOptions
98
+ )
99
+ return acc
100
+ },
101
+ {}
102
+ )
103
+ const mediaQueryStyles = createMediaQueryStyles(transformedThemeTokens)
104
+ const { ids, styles } = StyleSheet.create({
105
+ text: mediaQueryStyles
106
+ })
107
+ textStyles = styles.text
108
+ mediaIds = ids.text
109
+ } else {
110
+ textStyles = selectTextStyles(
111
+ {
112
+ textAlign: align,
113
+ textDecorationLine,
114
+ ...themeTokens
115
+ },
116
+ themeOptions
117
+ )
118
+ }
119
+
67
120
  const resolvedTextProps = {
68
121
  ...selectTextProps(rest),
69
- style: selectTextStyles(
70
- align ? { ...themeTokens, textAlign: align } : themeTokens,
71
- themeOptions
72
- ),
73
122
  dataSet,
74
- maxFontSizeMultiplier: getMaxFontMultiplier(themeTokens)
123
+ maxFontSizeMultiplier
75
124
  }
76
125
 
77
126
  const containerProps = {
@@ -83,7 +132,7 @@ const Typography = forwardRef(
83
132
  const resetTagStyling = (child) => {
84
133
  if (typeof child === 'object' && (child?.type === 'sub' || child?.type === 'sup')) {
85
134
  const childStyles = child?.props?.style || {}
86
- const supFontSize = childStyles.fontSize ?? superScriptFontSize
135
+ const supFontSize = childStyles.fontSize ?? themeTokens.superScriptFontSize
87
136
  const sanitizedChild = React.cloneElement(child, {
88
137
  style: {
89
138
  ...childStyles,
@@ -91,10 +140,8 @@ const Typography = forwardRef(
91
140
  lineHeight: 0
92
141
  }
93
142
  })
94
-
95
143
  return sanitizedChild
96
144
  }
97
-
98
145
  return child
99
146
  }
100
147
 
@@ -106,19 +153,20 @@ const Typography = forwardRef(
106
153
  return resetTagStyling(children)
107
154
  }
108
155
 
109
- const textDecorationLine = strikeThrough ? 'line-through' : 'none'
110
- const textStyles = resolvedTextProps.style
111
- ? { ...resolvedTextProps.style, textDecorationLine }
112
- : { textDecorationLine }
113
-
114
156
  return block ? (
115
157
  <View ref={ref} {...containerProps}>
116
- <Text {...resolvedTextProps} style={textStyles}>
158
+ <Text {...resolvedTextProps} style={textStyles} dataSet={mediaIds && { media: mediaIds }}>
117
159
  {sanitizeChildren(children)}
118
160
  </Text>
119
161
  </View>
120
162
  ) : (
121
- <Text ref={ref} {...containerProps} {...resolvedTextProps} style={textStyles}>
163
+ <Text
164
+ ref={ref}
165
+ {...containerProps}
166
+ {...resolvedTextProps}
167
+ style={textStyles}
168
+ dataSet={mediaIds && { media: mediaIds }}
169
+ >
122
170
  {sanitizeChildren(children)}
123
171
  </Text>
124
172
  )
package/src/server.js ADDED
@@ -0,0 +1,4 @@
1
+ export { default as selectSystemProps } from './utils/props/selectSystemProps'
2
+ export { getTokensPropType } from './utils/props/tokens'
3
+ export { default as htmlAttrs } from './utils/htmlAttrs'
4
+ export { getComponentTheme, getThemeTokens } from './ThemeProvider/utils/theme-tokens'
@@ -1,20 +1,35 @@
1
1
  import { viewports } from '@telus-uds/system-constants'
2
2
 
3
+ const inherit = ({ xs, sm = xs, md = sm, lg = md, xl = lg }) => ({ xs, sm, md, lg, xl })
4
+
3
5
  /**
4
6
  * @typedef { Object } CssStyles
5
- * @typedef { Record<"xs" | "sm" | "md" | "lg" | "xl", CssStyles> } ViewportStyles
7
+ */
8
+
9
+ /**
10
+ * @typedef { Object } ViewportStyles
11
+ * @property { CssStyles } xs - The CSS styles for the "xs" viewport (required).
12
+ * @property { CssStyles } [sm] - Optional styles for the "sm" viewport. Inherits from "xs" if not specified.
13
+ * @property { CssStyles } [md] - Optional styles for the "md" viewport. Inherits from "sm" if not specified.
14
+ * @property { CssStyles } [lg] - Optional styles for the "lg" viewport. Inherits from "md" if not specified.
15
+ * @property { CssStyles } [xl] - Optional styles for the "xl" viewport. Inherits from "lg" if not specified.
16
+ */
17
+
18
+ /**
6
19
  * @typedef { Record<String, CssStyles> } MediaQueryStyles
7
20
  */
8
21
 
9
22
  /**
10
23
  * Generates media query styles based on specified viewport styles.
11
- * @param {ViewportStyles} viewportStyles
12
- * @returns { MediaQueryStyles }
24
+ * @param { Object } viewportStyles - The styles for different viewports.
25
+ * @param { boolean } [shouldInherit=true] - Flag indicating whether to inherit styles.
26
+ * @returns { Object } - The media query styles.
13
27
  */
14
28
 
15
- function createMediaQueryStyles(viewportStyles) {
16
- const viewportsArray = Object.keys(viewportStyles)
17
- const mediaQueries = Object.entries(viewportStyles).reduce((acc, [viewport, styles]) => {
29
+ function createMediaQueryStyles(viewportStyles, shouldInherit = true) {
30
+ const effectiveStyles = shouldInherit ? inherit(viewportStyles) : viewportStyles
31
+ const viewportsArray = viewports.keys
32
+ const mediaQueries = Object.entries(effectiveStyles).reduce((acc, [viewport, styles]) => {
18
33
  const minWidth = viewports.map.get(viewport)
19
34
  const nextViewport = viewportsArray[viewportsArray.indexOf(viewport) + 1]
20
35
  const maxWidth = viewports.map.get(nextViewport)