@telus-uds/components-base 1.72.0 → 1.74.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 +35 -2
- package/lib/Box/Box.js +17 -6
- package/lib/ExpandCollapse/Panel.js +1 -1
- package/lib/FlexGrid/Col/Col.js +42 -19
- package/lib/FlexGrid/FlexGrid.js +40 -17
- package/lib/FlexGrid/Row/Row.js +45 -22
- package/lib/Footnote/Footnote.js +328 -0
- package/lib/Footnote/FootnoteLink.js +108 -0
- package/lib/Footnote/dictionary.js +19 -0
- package/lib/Footnote/index.js +12 -0
- package/lib/Listbox/ListboxGroup.js +7 -1
- package/lib/MultiSelectFilter/MultiSelectFilter.js +1 -0
- package/lib/Notification/Notification.js +13 -5
- package/lib/OrderedList/ItemBase.js +7 -1
- package/lib/Responsive/Responsive.js +32 -14
- package/lib/Responsive/ResponsiveProp.js +46 -0
- package/lib/Responsive/ResponsiveWithMediaQueryStyleSheet.js +75 -0
- package/lib/ThemeProvider/ThemeProvider.js +5 -2
- package/lib/ThemeProvider/index.js +9 -1
- package/lib/ThemeProvider/useResponsiveThemeTokens.js +89 -0
- package/lib/Typography/Typography.js +50 -22
- package/lib/index.js +8 -0
- package/lib/server.js +40 -0
- package/lib/utils/ssr-media-query/utils/create-media-query-styles.js +39 -6
- package/lib-module/Box/Box.js +17 -6
- package/lib-module/ExpandCollapse/Panel.js +1 -1
- package/lib-module/FlexGrid/Col/Col.js +42 -19
- package/lib-module/FlexGrid/FlexGrid.js +40 -17
- package/lib-module/FlexGrid/Row/Row.js +45 -22
- package/lib-module/Footnote/Footnote.js +319 -0
- package/lib-module/Footnote/FootnoteLink.js +101 -0
- package/lib-module/Footnote/dictionary.js +12 -0
- package/lib-module/Footnote/index.js +4 -0
- package/lib-module/Listbox/ListboxGroup.js +7 -1
- package/lib-module/MultiSelectFilter/MultiSelectFilter.js +1 -0
- package/lib-module/Notification/Notification.js +13 -5
- package/lib-module/OrderedList/ItemBase.js +7 -1
- package/lib-module/Responsive/Responsive.js +32 -15
- package/lib-module/Responsive/ResponsiveProp.js +39 -0
- package/lib-module/Responsive/ResponsiveWithMediaQueryStyleSheet.js +67 -0
- package/lib-module/ThemeProvider/ThemeProvider.js +5 -2
- package/lib-module/ThemeProvider/index.js +1 -0
- package/lib-module/ThemeProvider/useResponsiveThemeTokens.js +81 -0
- package/lib-module/Typography/Typography.js +52 -24
- package/lib-module/index.js +1 -0
- package/lib-module/server.js +4 -0
- package/lib-module/utils/ssr-media-query/utils/create-media-query-styles.js +36 -6
- package/package.json +13 -2
- package/src/Box/Box.jsx +35 -17
- package/src/ExpandCollapse/Panel.jsx +1 -1
- package/src/FlexGrid/Col/Col.jsx +42 -13
- package/src/FlexGrid/FlexGrid.jsx +40 -11
- package/src/FlexGrid/Row/Row.jsx +40 -16
- package/src/Footnote/Footnote.jsx +316 -0
- package/src/Footnote/FootnoteLink.jsx +95 -0
- package/src/Footnote/dictionary.js +12 -0
- package/src/Footnote/index.js +6 -0
- package/src/Listbox/ListboxGroup.jsx +9 -2
- package/src/MultiSelectFilter/MultiSelectFilter.jsx +2 -0
- package/src/Notification/Notification.jsx +15 -3
- package/src/OrderedList/ItemBase.jsx +14 -2
- package/src/Responsive/Responsive.jsx +31 -12
- package/src/Responsive/ResponsiveProp.jsx +33 -0
- package/src/Responsive/ResponsiveWithMediaQueryStyleSheet.jsx +60 -0
- package/src/ThemeProvider/ThemeProvider.jsx +5 -2
- package/src/ThemeProvider/index.js +1 -0
- package/src/ThemeProvider/useResponsiveThemeTokens.js +85 -0
- package/src/Typography/Typography.jsx +77 -24
- package/src/index.js +1 -0
- package/src/server.js +4 -0
- package/src/utils/ssr-media-query/utils/create-media-query-styles.js +21 -6
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import { View, StyleSheet, Platform, TouchableWithoutFeedback } from 'react-native'
|
|
4
|
+
import dictionary from './dictionary'
|
|
5
|
+
import { htmlAttrs, selectSystemProps, useCopy, viewProps, wrapStringsInText } from '../utils'
|
|
6
|
+
import { applyTextStyles, useThemeTokens } from '../ThemeProvider'
|
|
7
|
+
|
|
8
|
+
const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs, viewProps])
|
|
9
|
+
|
|
10
|
+
// The top property varies between devices due to how devices render the viewport.
|
|
11
|
+
const selectTextStyle = ({ fontSize, lineHeight }) => ({
|
|
12
|
+
fontSize,
|
|
13
|
+
lineHeight: lineHeight !== 1 ? lineHeight : fontSize * 2,
|
|
14
|
+
...Platform.select({
|
|
15
|
+
ios: {
|
|
16
|
+
top: -fontSize / 2
|
|
17
|
+
},
|
|
18
|
+
android: {
|
|
19
|
+
top: fontSize / 4
|
|
20
|
+
},
|
|
21
|
+
web: {
|
|
22
|
+
top: -fontSize / 2.8
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const FootnoteLink = ({ copy = 'en', content = [], onClick, tokens, variant = {}, ...rest }) => {
|
|
28
|
+
const themeTokens = useThemeTokens('FootnoteLink', tokens, variant)
|
|
29
|
+
const textStyles = applyTextStyles(themeTokens)
|
|
30
|
+
|
|
31
|
+
const numbers = Array.isArray(content) ? content : [content]
|
|
32
|
+
const refs = numbers.map(() => React.createRef())
|
|
33
|
+
|
|
34
|
+
const handleOnClick = (index) => {
|
|
35
|
+
onClick(numbers[index], refs[index])
|
|
36
|
+
}
|
|
37
|
+
const getCopy = useCopy({ dictionary, copy })
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<>
|
|
41
|
+
{numbers.map((num, index) => (
|
|
42
|
+
<View
|
|
43
|
+
accessibilityLabel={getCopy('a11yLabel')}
|
|
44
|
+
key={num}
|
|
45
|
+
ref={refs[index]}
|
|
46
|
+
{...selectProps(rest)}
|
|
47
|
+
>
|
|
48
|
+
<TouchableWithoutFeedback onPress={handleOnClick} accessibilityRole="button">
|
|
49
|
+
{wrapStringsInText(`${num}${index !== numbers.length - 1 ? ',' : ''}`, {
|
|
50
|
+
style: {
|
|
51
|
+
...textStyles,
|
|
52
|
+
...staticStyles.text,
|
|
53
|
+
...selectTextStyle(themeTokens)
|
|
54
|
+
}
|
|
55
|
+
})}
|
|
56
|
+
</TouchableWithoutFeedback>
|
|
57
|
+
</View>
|
|
58
|
+
))}
|
|
59
|
+
</>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const copyShape = PropTypes.shape({ a11yLabel: PropTypes.string.isRequired })
|
|
64
|
+
|
|
65
|
+
FootnoteLink.propTypes = {
|
|
66
|
+
...selectedSystemPropTypes,
|
|
67
|
+
/**
|
|
68
|
+
* Use the `copy` prop to either select provided English or French copy by passing 'en' or 'fr' respectively.
|
|
69
|
+
* To provide your own, pass a JSON object with the key `a11yLabel`.
|
|
70
|
+
*/
|
|
71
|
+
copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr']), copyShape]),
|
|
72
|
+
/**
|
|
73
|
+
* The footnote number, or multiple numbers if passed as an array.
|
|
74
|
+
* If using an array, a comma-separated group of numbers will be rendered as superscript.
|
|
75
|
+
*/
|
|
76
|
+
content: PropTypes.oneOfType([
|
|
77
|
+
PropTypes.number,
|
|
78
|
+
PropTypes.arrayOf(PropTypes.number),
|
|
79
|
+
PropTypes.string,
|
|
80
|
+
PropTypes.arrayOf(PropTypes.string)
|
|
81
|
+
]).isRequired,
|
|
82
|
+
/**
|
|
83
|
+
* A callback function to handle the click of a FootnoteLink.
|
|
84
|
+
*/
|
|
85
|
+
onClick: PropTypes.func.isRequired
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default FootnoteLink
|
|
89
|
+
|
|
90
|
+
const staticStyles = StyleSheet.create({
|
|
91
|
+
text: {
|
|
92
|
+
position: 'relative',
|
|
93
|
+
textDecorationLine: 'underline'
|
|
94
|
+
}
|
|
95
|
+
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable react-native-a11y/has-valid-accessibility-role */
|
|
2
2
|
import React, { forwardRef } from 'react'
|
|
3
3
|
import PropTypes from 'prop-types'
|
|
4
|
-
import { View, StyleSheet } from 'react-native'
|
|
4
|
+
import { View, StyleSheet, Platform } from 'react-native'
|
|
5
5
|
import { withLinkRouter } from '../utils'
|
|
6
6
|
import ExpandCollapse from '../ExpandCollapse'
|
|
7
7
|
import ListboxItem from './ListboxItem'
|
|
@@ -20,6 +20,13 @@ const styles = StyleSheet.create({
|
|
|
20
20
|
}
|
|
21
21
|
})
|
|
22
22
|
|
|
23
|
+
const getAccessibilityRole = () =>
|
|
24
|
+
Platform.select({
|
|
25
|
+
ios: 'listitem',
|
|
26
|
+
android: 'none',
|
|
27
|
+
web: 'listitem'
|
|
28
|
+
})
|
|
29
|
+
|
|
23
30
|
const ListboxGroup = forwardRef(
|
|
24
31
|
(
|
|
25
32
|
{
|
|
@@ -39,7 +46,7 @@ const ListboxGroup = forwardRef(
|
|
|
39
46
|
|
|
40
47
|
// TODO: implement keyboard navigation via refs for grouped items separately here
|
|
41
48
|
return (
|
|
42
|
-
<View id="test" style={styles.groupWrapper} accessibilityRole=
|
|
49
|
+
<View id="test" style={styles.groupWrapper} accessibilityRole={getAccessibilityRole()}>
|
|
43
50
|
<ExpandCollapse.Panel
|
|
44
51
|
panelId={id ?? label}
|
|
45
52
|
controlTokens={{
|
|
@@ -22,8 +22,20 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, vie
|
|
|
22
22
|
|
|
23
23
|
const selectContainerStyles = (tokens) => ({ ...tokens })
|
|
24
24
|
|
|
25
|
-
const selectTextStyles = (tokens, themeOptions) =>
|
|
26
|
-
|
|
25
|
+
const selectTextStyles = (tokens, themeOptions, isDismissible) => {
|
|
26
|
+
const textTokens = selectTokens('Typography', tokens)
|
|
27
|
+
const styles = {
|
|
28
|
+
...textTokens,
|
|
29
|
+
themeOptions,
|
|
30
|
+
overflow: 'hidden'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!isDismissible) {
|
|
34
|
+
styles.flexShrink = 1
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return applyTextStyles(styles)
|
|
38
|
+
}
|
|
27
39
|
|
|
28
40
|
const selectIconProps = ({ iconSize, iconColor }) => ({
|
|
29
41
|
size: iconSize,
|
|
@@ -113,7 +125,7 @@ const Notification = forwardRef(
|
|
|
113
125
|
return null
|
|
114
126
|
}
|
|
115
127
|
|
|
116
|
-
const textStyles = selectTextStyles(themeTokens, themeOptions)
|
|
128
|
+
const textStyles = selectTextStyles(themeTokens, themeOptions, dismissible)
|
|
117
129
|
|
|
118
130
|
const content = wrapStringsInText(
|
|
119
131
|
typeof children === 'function' ? children({ textStyles, variant }) : children,
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
/* eslint-disable react-native-a11y/has-valid-accessibility-role */
|
|
2
2
|
import React, { forwardRef } from 'react'
|
|
3
3
|
import PropTypes from 'prop-types'
|
|
4
|
-
import { View, StyleSheet } from 'react-native'
|
|
4
|
+
import { View, StyleSheet, Platform } from 'react-native'
|
|
5
|
+
|
|
6
|
+
const getAccessibilityRole = () =>
|
|
7
|
+
Platform.select({
|
|
8
|
+
ios: 'listitem',
|
|
9
|
+
android: 'none',
|
|
10
|
+
web: 'listitem'
|
|
11
|
+
})
|
|
5
12
|
|
|
6
13
|
const Item = forwardRef(({ children, style, ...rest }, ref) => (
|
|
7
|
-
<View
|
|
14
|
+
<View
|
|
15
|
+
accessibilityRole={getAccessibilityRole()}
|
|
16
|
+
ref={ref}
|
|
17
|
+
style={[staticStyles.container, style]}
|
|
18
|
+
{...rest}
|
|
19
|
+
>
|
|
8
20
|
{children}
|
|
9
21
|
</View>
|
|
10
22
|
))
|
|
@@ -1,29 +1,42 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
3
|
+
import { useTheme } from '../ThemeProvider'
|
|
4
|
+
import ResponsiveProp from './ResponsiveProp'
|
|
5
|
+
import ResponsiveWithMediaQueryStyleSheet from './ResponsiveWithMediaQueryStyleSheet'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Responsive conditionally renders children based on whether the viewport matches the provided
|
|
8
9
|
* min and max viewports.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
+
* If enableMediaQueryStyleSheet themeOption is set to false in ThemeProvider, then in SSR,
|
|
12
|
+
* like other viewport utilities, it treats the viewport as `xs` both in SSR itself and
|
|
11
13
|
* during first hydration on the client side; then if the viewport is not `xs`, it re-renders
|
|
12
14
|
* after hydration. This may cause a layout shift on devices other than the narrowest.
|
|
15
|
+
*
|
|
16
|
+
* If enableMediaQueryStyleSheet themeOption is set to true in ThemeProvider, then media query stylesheet
|
|
17
|
+
* is used to hide and show children of `Responsive` within a View.
|
|
13
18
|
*/
|
|
14
19
|
|
|
15
|
-
const Responsive = ({ min = 'xs', max, children }) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const Responsive = ({ min = 'xs', max, inheritedStyles = [], children }) => {
|
|
21
|
+
const {
|
|
22
|
+
themeOptions: { enableMediaQueryStyleSheet }
|
|
23
|
+
} = useTheme()
|
|
24
|
+
if (enableMediaQueryStyleSheet) {
|
|
25
|
+
return (
|
|
26
|
+
<ResponsiveWithMediaQueryStyleSheet inheritedStyles={inheritedStyles} min={min} max={max}>
|
|
27
|
+
{children}
|
|
28
|
+
</ResponsiveWithMediaQueryStyleSheet>
|
|
29
|
+
)
|
|
23
30
|
}
|
|
24
|
-
return
|
|
31
|
+
return (
|
|
32
|
+
<ResponsiveProp min={min} max={max}>
|
|
33
|
+
{children}
|
|
34
|
+
</ResponsiveProp>
|
|
35
|
+
)
|
|
25
36
|
}
|
|
26
37
|
|
|
38
|
+
Responsive.displayName = 'Responsive'
|
|
39
|
+
|
|
27
40
|
Responsive.propTypes = {
|
|
28
41
|
/**
|
|
29
42
|
* To hide children of `Responsive` if the current viewport is smaller than `min`
|
|
@@ -33,6 +46,12 @@ Responsive.propTypes = {
|
|
|
33
46
|
* To hide children of `Responsive` if the current viewport is larger than `max`
|
|
34
47
|
*/
|
|
35
48
|
max: PropTypes.oneOf(['sm', 'md', 'lg', 'xl']),
|
|
49
|
+
/**
|
|
50
|
+
* Styles to be inherited by `Responsive`.
|
|
51
|
+
* It should be an array of style property names.
|
|
52
|
+
* Note: This prop is only applicable when `enableMediaQueryStylesheet` is set to true in the `ThemeProvider`.
|
|
53
|
+
*/
|
|
54
|
+
inheritedStyles: PropTypes.arrayOf(PropTypes.string),
|
|
36
55
|
children: PropTypes.node.isRequired
|
|
37
56
|
}
|
|
38
57
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
|
|
4
|
+
import { viewports } from '@telus-uds/system-constants'
|
|
5
|
+
import { useResponsiveProp } from '../utils'
|
|
6
|
+
|
|
7
|
+
const ResponsiveProp = ({ min = 'xs', max, children }) => {
|
|
8
|
+
const byViewports = { [min]: children }
|
|
9
|
+
if (max && max !== 'xl') {
|
|
10
|
+
// Stop returning children at the viewport one above 'max' or greater
|
|
11
|
+
const maxIndex = viewports.keys.indexOf(max)
|
|
12
|
+
const maxPlusOne = maxIndex >= 0 ? viewports.keys[maxIndex + 1] : null
|
|
13
|
+
if (maxPlusOne) byViewports[maxPlusOne] = null
|
|
14
|
+
}
|
|
15
|
+
const responsiveProp = useResponsiveProp(byViewports, null)
|
|
16
|
+
return <>{responsiveProp}</>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
ResponsiveProp.displayName = 'Responsive'
|
|
20
|
+
|
|
21
|
+
ResponsiveProp.propTypes = {
|
|
22
|
+
/**
|
|
23
|
+
* To hide children of `Responsive` if the current viewport is smaller than `min`
|
|
24
|
+
*/
|
|
25
|
+
min: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
|
|
26
|
+
/**
|
|
27
|
+
* To hide children of `Responsive` if the current viewport is larger than `max`
|
|
28
|
+
*/
|
|
29
|
+
max: PropTypes.oneOf(['sm', 'md', 'lg', 'xl']),
|
|
30
|
+
children: PropTypes.node.isRequired
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default ResponsiveProp
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import { viewports } from '@telus-uds/system-constants'
|
|
4
|
+
import { BaseView, StyleSheet, createMediaQueryStyles } from '../utils'
|
|
5
|
+
|
|
6
|
+
function generateResponsiveStyles(min, max) {
|
|
7
|
+
const viewportsArray = viewports.keys
|
|
8
|
+
const minIndex = viewportsArray.indexOf(min)
|
|
9
|
+
const maxIndex = viewportsArray.indexOf(max)
|
|
10
|
+
let hiddenViewports = []
|
|
11
|
+
|
|
12
|
+
if (minIndex !== -1) {
|
|
13
|
+
hiddenViewports = viewportsArray.slice(0, minIndex)
|
|
14
|
+
}
|
|
15
|
+
if (maxIndex !== -1) {
|
|
16
|
+
hiddenViewports = hiddenViewports.concat(viewportsArray.slice(maxIndex + 1))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const styles = {}
|
|
20
|
+
|
|
21
|
+
viewportsArray.forEach((viewport) => {
|
|
22
|
+
if (hiddenViewports.includes(viewport)) {
|
|
23
|
+
styles[viewport] = { display: 'none' }
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
return createMediaQueryStyles(styles, false)
|
|
27
|
+
}
|
|
28
|
+
const ResponsiveWithMediaQueryStyleSheet = ({ min, max, inheritedStyles = [], children }) => {
|
|
29
|
+
const { ids, styles } = StyleSheet.create({
|
|
30
|
+
responsive: {
|
|
31
|
+
...inheritedStyles.reduce((acc, prop) => {
|
|
32
|
+
acc[prop] = 'inherit'
|
|
33
|
+
return acc
|
|
34
|
+
}, {}),
|
|
35
|
+
...generateResponsiveStyles(min, max)
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
return (
|
|
39
|
+
<BaseView style={styles.responsive} dataSet={ids.responsive && { media: ids.responsive }}>
|
|
40
|
+
{children}
|
|
41
|
+
</BaseView>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ResponsiveWithMediaQueryStyleSheet.displayName = 'Responsive'
|
|
46
|
+
|
|
47
|
+
ResponsiveWithMediaQueryStyleSheet.propTypes = {
|
|
48
|
+
/**
|
|
49
|
+
* To hide children of `Responsive` if the current viewport is smaller than `min`
|
|
50
|
+
*/
|
|
51
|
+
min: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
|
|
52
|
+
/**
|
|
53
|
+
* To hide children of `Responsive` if the current viewport is larger than `max`
|
|
54
|
+
*/
|
|
55
|
+
max: PropTypes.oneOf(['sm', 'md', 'lg', 'xl']),
|
|
56
|
+
inheritedStyles: PropTypes.arrayOf(PropTypes.string),
|
|
57
|
+
children: PropTypes.node.isRequired
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default ResponsiveWithMediaQueryStyleSheet
|
|
@@ -16,7 +16,8 @@ const defaultThemeOptions = {
|
|
|
16
16
|
// TODO: switch `forceZIndex` to be false by default in the next major version
|
|
17
17
|
forceZIndex: true,
|
|
18
18
|
// TODO: switch `enableHelmetSSR` to be false by default in the next major version
|
|
19
|
-
enableHelmetSSR: true
|
|
19
|
+
enableHelmetSSR: true,
|
|
20
|
+
enableMediaQueryStyleSheet: false
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const ThemeProvider = ({ children, defaultTheme, themeOptions = {} }) => {
|
|
@@ -65,12 +66,14 @@ ThemeProvider.propTypes = {
|
|
|
65
66
|
* - `enableHelmetSSR`: on Web SSR, allows React Helmet to run during server-side rendering. This should be
|
|
66
67
|
* disabled unless a web app has been specifically configured to stop React Helmet accumulating
|
|
67
68
|
* instances (which may cause a memory leak). See React Helmet's docs: https://github.com/nfl/react-helmet
|
|
69
|
+
* - `enableMediaQueryStyleSheet`: enables the use of Media Query StyleSheet.
|
|
68
70
|
*/
|
|
69
71
|
themeOptions: PropTypes.shape({
|
|
70
72
|
forceAbsoluteFontSizing: PropTypes.bool,
|
|
71
73
|
forceZIndex: PropTypes.bool,
|
|
72
74
|
enableHelmetSSR: PropTypes.bool,
|
|
73
|
-
contentMaxWidth: responsiveProps.getTypeOptionallyByViewport(PropTypes.number)
|
|
75
|
+
contentMaxWidth: responsiveProps.getTypeOptionallyByViewport(PropTypes.number),
|
|
76
|
+
enableMediaQueryStyleSheet: PropTypes.bool
|
|
74
77
|
})
|
|
75
78
|
}
|
|
76
79
|
|
|
@@ -2,6 +2,7 @@ import ThemeProvider from './ThemeProvider'
|
|
|
2
2
|
|
|
3
3
|
export { default as useTheme } from './useTheme'
|
|
4
4
|
export { default as useSetTheme } from './useSetTheme'
|
|
5
|
+
export { default as useResponsiveThemeTokens } from './useResponsiveThemeTokens'
|
|
5
6
|
export * from './useThemeTokens'
|
|
6
7
|
|
|
7
8
|
export * from './utils'
|
|
@@ -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
|
-
{
|
|
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,61 @@ 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(
|
|
80
|
+
'Typography',
|
|
81
|
+
tokens,
|
|
82
|
+
variant,
|
|
83
|
+
!enableMediaQueryStyleSheet && { viewport }
|
|
84
|
+
)
|
|
85
|
+
const maxFontSizeMultiplier = enableMediaQueryStyleSheet
|
|
86
|
+
? getMaxFontMultiplier(themeTokens[viewport])
|
|
87
|
+
: getMaxFontMultiplier(themeTokens)
|
|
88
|
+
const textDecorationLine = strikeThrough ? 'line-through' : 'none'
|
|
89
|
+
|
|
90
|
+
let textStyles
|
|
91
|
+
let mediaIds
|
|
92
|
+
|
|
93
|
+
if (enableMediaQueryStyleSheet) {
|
|
94
|
+
const transformedThemeTokens = Object.entries(themeTokens).reduce(
|
|
95
|
+
(acc, [vp, viewportTokens]) => {
|
|
96
|
+
acc[vp] = selectTextStyles(
|
|
97
|
+
{
|
|
98
|
+
textAlign: align,
|
|
99
|
+
textDecorationLine,
|
|
100
|
+
...viewportTokens
|
|
101
|
+
},
|
|
102
|
+
themeOptions
|
|
103
|
+
)
|
|
104
|
+
return acc
|
|
105
|
+
},
|
|
106
|
+
{}
|
|
107
|
+
)
|
|
108
|
+
const mediaQueryStyles = createMediaQueryStyles(transformedThemeTokens)
|
|
109
|
+
const { ids, styles } = StyleSheet.create({
|
|
110
|
+
text: mediaQueryStyles
|
|
111
|
+
})
|
|
112
|
+
textStyles = styles.text
|
|
113
|
+
mediaIds = ids.text
|
|
114
|
+
} else {
|
|
115
|
+
textStyles = selectTextStyles(
|
|
116
|
+
{
|
|
117
|
+
textAlign: align,
|
|
118
|
+
textDecorationLine,
|
|
119
|
+
...themeTokens
|
|
120
|
+
},
|
|
121
|
+
themeOptions
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
67
125
|
const resolvedTextProps = {
|
|
68
126
|
...selectTextProps(rest),
|
|
69
|
-
style: selectTextStyles(
|
|
70
|
-
align ? { ...themeTokens, textAlign: align } : themeTokens,
|
|
71
|
-
themeOptions
|
|
72
|
-
),
|
|
73
127
|
dataSet,
|
|
74
|
-
maxFontSizeMultiplier
|
|
128
|
+
maxFontSizeMultiplier
|
|
75
129
|
}
|
|
76
130
|
|
|
77
131
|
const containerProps = {
|
|
@@ -83,7 +137,7 @@ const Typography = forwardRef(
|
|
|
83
137
|
const resetTagStyling = (child) => {
|
|
84
138
|
if (typeof child === 'object' && (child?.type === 'sub' || child?.type === 'sup')) {
|
|
85
139
|
const childStyles = child?.props?.style || {}
|
|
86
|
-
const supFontSize = childStyles.fontSize ?? superScriptFontSize
|
|
140
|
+
const supFontSize = childStyles.fontSize ?? themeTokens.superScriptFontSize
|
|
87
141
|
const sanitizedChild = React.cloneElement(child, {
|
|
88
142
|
style: {
|
|
89
143
|
...childStyles,
|
|
@@ -91,10 +145,8 @@ const Typography = forwardRef(
|
|
|
91
145
|
lineHeight: 0
|
|
92
146
|
}
|
|
93
147
|
})
|
|
94
|
-
|
|
95
148
|
return sanitizedChild
|
|
96
149
|
}
|
|
97
|
-
|
|
98
150
|
return child
|
|
99
151
|
}
|
|
100
152
|
|
|
@@ -106,19 +158,20 @@ const Typography = forwardRef(
|
|
|
106
158
|
return resetTagStyling(children)
|
|
107
159
|
}
|
|
108
160
|
|
|
109
|
-
const textDecorationLine = strikeThrough ? 'line-through' : 'none'
|
|
110
|
-
const textStyles = resolvedTextProps.style
|
|
111
|
-
? { ...resolvedTextProps.style, textDecorationLine }
|
|
112
|
-
: { textDecorationLine }
|
|
113
|
-
|
|
114
161
|
return block ? (
|
|
115
162
|
<View ref={ref} {...containerProps}>
|
|
116
|
-
<Text {...resolvedTextProps} style={textStyles}>
|
|
163
|
+
<Text {...resolvedTextProps} style={textStyles} dataSet={mediaIds && { media: mediaIds }}>
|
|
117
164
|
{sanitizeChildren(children)}
|
|
118
165
|
</Text>
|
|
119
166
|
</View>
|
|
120
167
|
) : (
|
|
121
|
-
<Text
|
|
168
|
+
<Text
|
|
169
|
+
ref={ref}
|
|
170
|
+
{...containerProps}
|
|
171
|
+
{...resolvedTextProps}
|
|
172
|
+
style={textStyles}
|
|
173
|
+
dataSet={mediaIds && { media: mediaIds }}
|
|
174
|
+
>
|
|
122
175
|
{sanitizeChildren(children)}
|
|
123
176
|
</Text>
|
|
124
177
|
)
|
package/src/index.js
CHANGED
|
@@ -17,6 +17,7 @@ export { default as ExpandCollapse, Accordion } from './ExpandCollapse'
|
|
|
17
17
|
export { default as Feedback } from './Feedback'
|
|
18
18
|
export { default as Fieldset } from './Fieldset'
|
|
19
19
|
export { default as FlexGrid } from './FlexGrid'
|
|
20
|
+
export { default as Footnote } from './Footnote'
|
|
20
21
|
export { default as HorizontalScroll } from './HorizontalScroll'
|
|
21
22
|
export * from './HorizontalScroll'
|
|
22
23
|
export { default as Icon } from './Icon'
|
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'
|