@telus-uds/components-base 0.0.2-prerelease.1 → 0.0.2-prerelease.5
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 +36 -0
- package/__fixtures__/testTheme.js +264 -84
- package/__tests__/Box/Box.test.jsx +81 -58
- package/__tests__/Card/Card.test.jsx +63 -0
- package/__tests__/Divider/Divider.test.jsx +26 -5
- package/__tests__/Feedback/Feedback.test.jsx +42 -0
- package/__tests__/FlexGrid/Col.test.jsx +5 -0
- package/__tests__/Pagination/Pagination.test.jsx +160 -0
- package/__tests__/Spacer/Spacer.test.jsx +63 -0
- package/__tests__/StackView/StackView.test.jsx +242 -0
- package/__tests__/StackView/StackWrap.test.jsx +47 -0
- package/__tests__/StackView/getStackedContent.test.jsx +295 -0
- package/__tests__/TextInput/TextInput.test.jsx +146 -0
- package/__tests__/ThemeProvider/useThemeTokens.test.jsx +5 -3
- package/__tests__/utils/spacing.test.jsx +273 -0
- package/__tests__/utils/useUniqueId.test.js +31 -0
- package/babel.config.json +8 -0
- package/jest.config.js +7 -6
- package/lib/A11yInfoProvider/index.js +2 -2
- package/lib/A11yText/index.js +1 -3
- package/lib/ActivityIndicator/Spinner.web.js +3 -5
- package/lib/Box/Box.js +117 -82
- package/lib/Button/Button.js +1 -3
- package/lib/Button/ButtonBase.js +9 -21
- package/lib/Button/ButtonGroup.js +14 -25
- package/lib/Button/ButtonLink.js +1 -3
- package/lib/Card/Card.js +103 -0
- package/lib/Card/index.js +2 -0
- package/lib/Divider/Divider.js +40 -4
- package/lib/ExpandCollapse/Accordion.js +1 -3
- package/lib/ExpandCollapse/Control.js +3 -5
- package/lib/ExpandCollapse/Panel.js +2 -4
- package/lib/Feedback/Feedback.js +110 -0
- package/lib/Feedback/index.js +2 -0
- package/lib/FlexGrid/Col/Col.js +3 -5
- package/lib/FlexGrid/FlexGrid.js +1 -3
- package/lib/FlexGrid/Row/Row.js +1 -3
- package/lib/FlexGrid/providers/GutterContext.js +1 -1
- package/lib/Icon/Icon.js +1 -1
- package/lib/InputLabel/InputLabel.js +86 -0
- package/lib/InputLabel/LabelContent.native.js +8 -0
- package/lib/InputLabel/LabelContent.web.js +17 -0
- package/lib/InputLabel/index.js +2 -0
- package/lib/Link/ChevronLink.js +1 -3
- package/lib/Link/Link.js +1 -3
- package/lib/Link/LinkBase.js +11 -7
- package/lib/Link/TextButton.js +1 -3
- package/lib/Pagination/PageButton.js +85 -0
- package/lib/Pagination/Pagination.js +118 -0
- package/lib/Pagination/SideButton.js +108 -0
- package/lib/Pagination/dictionary.js +18 -0
- package/lib/Pagination/index.js +2 -0
- package/lib/Pagination/useCopy.js +10 -0
- package/lib/Pagination/usePagination.js +70 -0
- package/lib/SideNav/Item.js +4 -6
- package/lib/SideNav/ItemsGroup.js +11 -11
- package/lib/SideNav/SideNav.js +2 -4
- package/lib/Spacer/Spacer.js +98 -0
- package/lib/Spacer/index.js +2 -0
- package/lib/StackView/StackView.js +105 -0
- package/lib/StackView/StackWrap.js +32 -0
- package/lib/StackView/StackWrap.native.js +3 -0
- package/lib/StackView/StackWrapBox.js +85 -0
- package/lib/StackView/StackWrapGap.js +45 -0
- package/lib/StackView/common.js +30 -0
- package/lib/StackView/getStackedContent.js +111 -0
- package/lib/StackView/index.js +5 -0
- package/lib/TextInput/TextInput.js +337 -0
- package/lib/TextInput/index.js +2 -0
- package/lib/ThemeProvider/ThemeProvider.js +2 -2
- package/lib/ThemeProvider/useThemeTokens.js +34 -6
- package/lib/ThemeProvider/utils/theme-tokens.js +37 -9
- package/lib/ToggleSwitch/ToggleSwitch.js +17 -47
- package/lib/Typography/Typography.js +1 -7
- package/lib/ViewportProvider/index.js +1 -1
- package/lib/index.js +8 -1
- package/lib/utils/index.js +2 -1
- package/lib/utils/input.js +3 -1
- package/lib/utils/propTypes.js +103 -8
- package/lib/utils/spacing/index.js +2 -0
- package/lib/utils/spacing/useSpacingScale.js +102 -0
- package/lib/utils/spacing/utils.js +32 -0
- package/lib/utils/useUniqueId.js +12 -0
- package/package.json +6 -9
- package/release-context.json +4 -4
- package/src/Box/Box.jsx +117 -80
- package/src/Button/ButtonBase.jsx +8 -21
- package/src/Button/ButtonGroup.jsx +13 -17
- package/src/Card/Card.jsx +101 -0
- package/src/Card/index.js +3 -0
- package/src/Divider/Divider.jsx +38 -3
- package/src/ExpandCollapse/Control.jsx +2 -3
- package/src/Feedback/Feedback.jsx +99 -0
- package/src/Feedback/index.js +3 -0
- package/src/FlexGrid/Col/Col.jsx +4 -2
- package/src/Icon/Icon.jsx +2 -1
- package/src/InputLabel/InputLabel.jsx +99 -0
- package/src/InputLabel/LabelContent.native.jsx +6 -0
- package/src/InputLabel/LabelContent.web.jsx +13 -0
- package/src/InputLabel/index.js +3 -0
- package/src/Link/LinkBase.jsx +9 -3
- package/src/Pagination/PageButton.jsx +80 -0
- package/src/Pagination/Pagination.jsx +135 -0
- package/src/Pagination/SideButton.jsx +93 -0
- package/src/Pagination/dictionary.js +18 -0
- package/src/Pagination/index.js +3 -0
- package/src/Pagination/useCopy.js +7 -0
- package/src/Pagination/usePagination.js +69 -0
- package/src/SideNav/Item.jsx +3 -3
- package/src/SideNav/ItemsGroup.jsx +11 -13
- package/src/Spacer/Spacer.jsx +91 -0
- package/src/Spacer/index.js +3 -0
- package/src/StackView/StackView.jsx +103 -0
- package/src/StackView/StackWrap.jsx +33 -0
- package/src/StackView/StackWrap.native.jsx +4 -0
- package/src/StackView/StackWrapBox.jsx +82 -0
- package/src/StackView/StackWrapGap.jsx +39 -0
- package/src/StackView/common.jsx +28 -0
- package/src/StackView/getStackedContent.jsx +106 -0
- package/src/StackView/index.js +6 -0
- package/src/TextInput/TextInput.jsx +325 -0
- package/src/TextInput/index.js +3 -0
- package/src/ThemeProvider/useThemeTokens.js +34 -7
- package/src/ThemeProvider/utils/theme-tokens.js +37 -8
- package/src/ToggleSwitch/ToggleSwitch.jsx +23 -43
- package/src/Typography/Typography.jsx +0 -4
- package/src/index.js +8 -1
- package/src/utils/index.js +1 -0
- package/src/utils/input.js +2 -1
- package/src/utils/propTypes.js +105 -16
- package/src/utils/spacing/index.js +3 -0
- package/src/utils/spacing/useSpacingScale.js +93 -0
- package/src/utils/spacing/utils.js +28 -0
- package/src/utils/useUniqueId.js +14 -0
- package/stories/A11yText/A11yText.stories.jsx +11 -5
- package/stories/ActivityIndicator/ActivityIndicator.stories.jsx +11 -2
- package/stories/Box/Box.stories.jsx +46 -17
- package/stories/Button/Button.stories.jsx +17 -21
- package/stories/Button/ButtonGroup.stories.jsx +2 -1
- package/stories/Button/ButtonLink.stories.jsx +6 -4
- package/stories/Card/Card.stories.jsx +62 -0
- package/stories/Divider/Divider.stories.jsx +26 -2
- package/stories/ExpandCollapse/ExpandCollapse.stories.jsx +74 -79
- package/stories/Feedback/Feedback.stories.jsx +97 -0
- package/stories/FlexGrid/01 FlexGrid.stories.jsx +20 -7
- package/stories/Icon/Icon.stories.jsx +11 -3
- package/stories/InputLabel/InputLabel.stories.jsx +37 -0
- package/stories/Link/ChevronLink.stories.jsx +20 -4
- package/stories/Link/Link.stories.jsx +24 -3
- package/stories/Link/TextButton.stories.jsx +24 -3
- package/stories/Pagination/Pagination.stories.jsx +64 -0
- package/stories/SideNav/SideNav.stories.jsx +17 -2
- package/stories/Spacer/Spacer.stories.jsx +33 -0
- package/stories/StackView/StackView.stories.jsx +65 -0
- package/stories/StackView/StackWrap.stories.jsx +52 -0
- package/stories/TextInput/TextInput.stories.jsx +103 -0
- package/stories/ToggleSwitch/ToggleSwitch.stories.jsx +16 -3
- package/stories/Typography/Typography.stories.jsx +12 -3
- package/stories/platform-supports.web.jsx +1 -1
- package/stories/supports.jsx +113 -13
- package/babel.config.js +0 -3
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import { useCallback } from 'react'
|
|
2
2
|
import useTheme from './useTheme'
|
|
3
|
-
import { getComponentTheme, getThemeTokens } from './utils'
|
|
3
|
+
import { getComponentTheme, getThemeTokens, resolveTokens, mergeAppearances } from './utils'
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import('../utils/propTypes.js').AppearanceSet} AppearanceSet
|
|
6
|
+
* @typedef {import('../utils/propTypes.js').TokensProp} TokensProp
|
|
7
|
+
* @typedef {import('../utils/propTypes.js').TokensSet} TokensSet
|
|
8
|
+
*/
|
|
4
9
|
|
|
5
10
|
/**
|
|
6
11
|
* Returns a complete set of theme tokens for a component based on which of the
|
|
7
12
|
* component's theme rules apply to the current set of theme appearances.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} componentName - the name as defined in the theme schema of the component whose theme is to be used
|
|
15
|
+
* @param {TokensProp} [tokens] - every themed component should accept an optional `tokens` prop allowing theme tokens to be overridden
|
|
16
|
+
* @param {AppearanceSet} [variants] - every themed component should accept an optional `variants` prop specifying theme variants
|
|
17
|
+
* @param {AppearanceSet} [states] - optional object containing current theme appearances dictated by user action or context
|
|
18
|
+
* @returns {TokensSet} - the currently-applicable resolved set of theme tokens to apply
|
|
8
19
|
*/
|
|
9
20
|
export const useThemeTokens = (componentName, tokens = {}, variants = {}, states = {}) => {
|
|
10
21
|
const theme = useTheme()
|
|
@@ -14,16 +25,32 @@ export const useThemeTokens = (componentName, tokens = {}, variants = {}, states
|
|
|
14
25
|
}
|
|
15
26
|
|
|
16
27
|
/**
|
|
17
|
-
* Returns a memoised function that
|
|
18
|
-
*
|
|
19
|
-
*
|
|
28
|
+
* Returns a memoised tokens getter function that gets tokens similar to calling useThemeTokens.
|
|
29
|
+
* Scenarios where useThemeTokensCallback should be used instead of useThemeTokens include:
|
|
30
|
+
*
|
|
31
|
+
* - Where tokens to be obtained from state accessible only in scopes like callbacks and render functions,
|
|
32
|
+
* where calling useThemeTokens directly would be disallowed by React's hook rules.
|
|
33
|
+
* - Passing a tokens getter down via a child component's `tokens` prop, applying rules using the
|
|
34
|
+
* child component's current state. Consider wrapping the returned tokens in `selectTokens()`.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} componentName - the name as defined in the theme schema of the component whose theme is to be used
|
|
37
|
+
* @param {TokensProp} [tokens] - every themed component should accept a `tokens` prop allowing theme tokens to be overridden
|
|
38
|
+
* @param {AppearanceSet} [variants] - variants passed in as props that don't change dynamically
|
|
39
|
+
* @returns {(states: AppearanceSet, tokenOverrides?: TokensProp) => TokensSet}
|
|
40
|
+
* - callback function that returning an overridable tokens set for current state. Only pass
|
|
41
|
+
* tokenOverrides in rare cases where tokens overrides are also generated outside hook scope,
|
|
42
|
+
* e.g. if one theme tokens callback needs to pass certain token overrides to another.
|
|
20
43
|
*/
|
|
21
|
-
export const useThemeTokensCallback = (componentName) => {
|
|
44
|
+
export const useThemeTokensCallback = (componentName, tokens = {}, variants = {}) => {
|
|
22
45
|
const theme = useTheme()
|
|
23
46
|
const componentTheme = getComponentTheme(theme, componentName)
|
|
24
47
|
const getThemeTokensCallback = useCallback(
|
|
25
|
-
(
|
|
26
|
-
|
|
48
|
+
(states, tokenOverrides) => {
|
|
49
|
+
const appearances = mergeAppearances(variants, states)
|
|
50
|
+
const resolvedTokens = resolveTokens(tokens, tokenOverrides, appearances)
|
|
51
|
+
return getThemeTokens(componentTheme, resolvedTokens, appearances)
|
|
52
|
+
},
|
|
53
|
+
[componentTheme, tokens, variants]
|
|
27
54
|
)
|
|
28
55
|
return getThemeTokensCallback
|
|
29
56
|
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('../../utils/propTypes.js').AppearanceSet} AppearanceSet
|
|
3
|
+
* @typedef {import('../../utils/propTypes.js').TokensProp} TokensProp
|
|
4
|
+
* @typedef {import('../../utils/propTypes.js').TokensSet} TokensSet
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
/**
|
|
2
8
|
* General utilities around working with theme tokens
|
|
3
9
|
*/
|
|
@@ -30,24 +36,48 @@ export const doesThemeConditionApply = ([key, value], appearances) => {
|
|
|
30
36
|
export const doesThemeRuleApply = (rule, appearances) =>
|
|
31
37
|
Object.entries(rule.if).every((condition) => doesThemeConditionApply(condition, appearances))
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Turns a tokens prop and an optional tokens override prop (either or both of which may be a tokens getter function)
|
|
41
|
+
* into one tokens set object where overrides are applied over the resolved default tokens.
|
|
42
|
+
*
|
|
43
|
+
* @param {TokensProp} defaultTokens - a set of tokens or tokens getter function which may be overridden
|
|
44
|
+
* @param {TokensProp} [tokenOverrides] - optional set of tokens or tokens getter function to override the default
|
|
45
|
+
* @param {AppearanceSet} [appearances] - optional appearance set to pass to tokens getter functions
|
|
46
|
+
* @returns {TokensSet} - object containing resolved tokens with overrides applied
|
|
47
|
+
*/
|
|
48
|
+
export const resolveTokens = (defaultTokens, tokenOverrides, appearances = {}) => {
|
|
49
|
+
const resolve = (tokens) => (typeof tokens === 'function' ? tokens(appearances) : tokens)
|
|
50
|
+
if (!tokenOverrides) return resolve(defaultTokens)
|
|
51
|
+
|
|
52
|
+
return Object.entries(resolve(tokenOverrides)).reduce(
|
|
36
53
|
(mergedTokens, [tokenName, tokenValue]) =>
|
|
37
54
|
tokenValue === undefined ? mergedTokens : { ...mergedTokens, [tokenName]: tokenValue },
|
|
38
|
-
|
|
55
|
+
resolve(defaultTokens)
|
|
39
56
|
)
|
|
40
57
|
}
|
|
41
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Merges variants over states. Must be merged in that order to allow static showcases of a state,
|
|
61
|
+
* e.g. `<Button variant={{ pressed: true }} />` where button's pressed state is `false` by default.
|
|
62
|
+
* Returns an empty object if both variants and states are undefined.
|
|
63
|
+
*
|
|
64
|
+
* @param {AppearanceSet} [variants]
|
|
65
|
+
* @param {AppearanceSet} [states]
|
|
66
|
+
* @returns {AppearanceSet}
|
|
67
|
+
*/
|
|
68
|
+
export const mergeAppearances = (variants = {}, states) =>
|
|
69
|
+
states ? { ...states, ...variants } : variants
|
|
70
|
+
|
|
42
71
|
export const getThemeTokens = (
|
|
43
72
|
{ rules = [], tokens: defaultThemeTokens = {} },
|
|
44
73
|
tokensProp,
|
|
45
74
|
variants = {},
|
|
46
|
-
states
|
|
75
|
+
states
|
|
47
76
|
) => {
|
|
48
|
-
const appearances =
|
|
77
|
+
const appearances = mergeAppearances(variants, states)
|
|
49
78
|
// TODO: if in dev mode, validate the appearances and provided propTokens
|
|
50
79
|
|
|
80
|
+
// Get the theme's default tokens set and merge tokens from applicable theme rules over it
|
|
51
81
|
const themeTokens = rules.reduce(
|
|
52
82
|
(mergedTokens, rule) =>
|
|
53
83
|
doesThemeRuleApply(rule, appearances)
|
|
@@ -58,8 +88,7 @@ export const getThemeTokens = (
|
|
|
58
88
|
: mergedTokens,
|
|
59
89
|
defaultThemeTokens
|
|
60
90
|
)
|
|
61
|
-
|
|
62
|
-
return tokensProp ? mergeTokens(tokensProp, themeTokens, appearances) : themeTokens
|
|
91
|
+
return resolveTokens(themeTokens, tokensProp, appearances)
|
|
63
92
|
}
|
|
64
93
|
|
|
65
94
|
export const toArray = (strOrArr) => (Array.isArray(strOrArr) ? strOrArr : [strOrArr])
|
|
@@ -4,43 +4,29 @@ import { Platform, View, StyleSheet } from 'react-native'
|
|
|
4
4
|
|
|
5
5
|
import ButtonBase from '../Button/ButtonBase'
|
|
6
6
|
import { useThemeTokensCallback, applyShadowToken } from '../ThemeProvider'
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
a11yProps,
|
|
9
|
+
pressProps,
|
|
10
|
+
variantProp,
|
|
11
|
+
getTokensPropType,
|
|
12
|
+
selectTokens
|
|
13
|
+
} from '../utils/propTypes'
|
|
8
14
|
import { useInputValue } from '../utils/input'
|
|
9
15
|
|
|
10
|
-
const selectButtonTokens = (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
paddingRight,
|
|
23
|
-
paddingTop,
|
|
24
|
-
paddingBottom,
|
|
25
|
-
shadow
|
|
26
|
-
}) => ({
|
|
27
|
-
borderColor,
|
|
28
|
-
borderWidth,
|
|
29
|
-
borderRadius,
|
|
30
|
-
outerBorderColor,
|
|
31
|
-
outerBorderWidth,
|
|
32
|
-
outerBorderGap,
|
|
33
|
-
outerBorderRadius,
|
|
34
|
-
outerBackgroundColor,
|
|
35
|
-
backgroundColor,
|
|
36
|
-
opacity,
|
|
37
|
-
paddingLeft,
|
|
38
|
-
paddingRight,
|
|
39
|
-
paddingTop,
|
|
40
|
-
paddingBottom,
|
|
41
|
-
shadow,
|
|
42
|
-
width: null // make it wrap around our track width
|
|
16
|
+
const selectButtonTokens = (tokens) =>
|
|
17
|
+
selectTokens('Button', {
|
|
18
|
+
...tokens,
|
|
19
|
+
// Width tokens are applied to our inner track. Disable Button width token so it wraps our track width.
|
|
20
|
+
width: null
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// Map and rename icon-specific tokens to name used within Icon
|
|
24
|
+
const selectIconTokens = ({ iconSize, iconColor, iconOpacity }) => ({
|
|
25
|
+
opacity: iconOpacity,
|
|
26
|
+
size: iconSize,
|
|
27
|
+
color: iconColor
|
|
43
28
|
})
|
|
29
|
+
|
|
44
30
|
const selectTrackStyles = ({ trackBorderWidth, trackBorderColor, trackBorderRadius, width }) => ({
|
|
45
31
|
borderWidth: trackBorderWidth,
|
|
46
32
|
borderColor: trackBorderColor,
|
|
@@ -68,11 +54,6 @@ const selectSwitchStyles = ({
|
|
|
68
54
|
web: { transition: 'transform 200ms' }
|
|
69
55
|
})
|
|
70
56
|
})
|
|
71
|
-
const selectIconTokens = ({ iconSize, iconColor, iconOpacity }) => ({
|
|
72
|
-
opacity: iconOpacity,
|
|
73
|
-
size: iconSize,
|
|
74
|
-
color: iconColor
|
|
75
|
-
})
|
|
76
57
|
|
|
77
58
|
const ToggleSwitch = ({
|
|
78
59
|
value,
|
|
@@ -83,7 +64,7 @@ const ToggleSwitch = ({
|
|
|
83
64
|
variant,
|
|
84
65
|
accessibilityRole = 'switch'
|
|
85
66
|
}) => {
|
|
86
|
-
const getTokens = useThemeTokensCallback('ToggleSwitch')
|
|
67
|
+
const getTokens = useThemeTokensCallback('ToggleSwitch', tokens, variant)
|
|
87
68
|
|
|
88
69
|
const { currentValue, setValue } = useInputValue({
|
|
89
70
|
value,
|
|
@@ -93,8 +74,7 @@ const ToggleSwitch = ({
|
|
|
93
74
|
|
|
94
75
|
const handlePress = () => setValue(!currentValue)
|
|
95
76
|
|
|
96
|
-
const getButtonTokens = (buttonState) =>
|
|
97
|
-
selectButtonTokens(getTokens(tokens, variant, buttonState))
|
|
77
|
+
const getButtonTokens = (buttonState) => selectButtonTokens(getTokens(buttonState))
|
|
98
78
|
|
|
99
79
|
return (
|
|
100
80
|
<ButtonBase
|
|
@@ -106,7 +86,7 @@ const ToggleSwitch = ({
|
|
|
106
86
|
onPress={handlePress}
|
|
107
87
|
>
|
|
108
88
|
{(buttonState) => {
|
|
109
|
-
const themeTokens = getTokens(
|
|
89
|
+
const themeTokens = getTokens(buttonState)
|
|
110
90
|
const IconComponent = themeTokens.icon
|
|
111
91
|
const switchStyles = selectSwitchStyles(themeTokens)
|
|
112
92
|
const trackStyles = selectTrackStyles(themeTokens)
|
|
@@ -33,8 +33,6 @@ const selectTextStyles = ({
|
|
|
33
33
|
color,
|
|
34
34
|
lineHeight,
|
|
35
35
|
fontName,
|
|
36
|
-
marginTop,
|
|
37
|
-
marginBottom,
|
|
38
36
|
textAlign,
|
|
39
37
|
textTransform
|
|
40
38
|
}) =>
|
|
@@ -44,8 +42,6 @@ const selectTextStyles = ({
|
|
|
44
42
|
color,
|
|
45
43
|
lineHeight,
|
|
46
44
|
fontName,
|
|
47
|
-
marginTop,
|
|
48
|
-
marginBottom,
|
|
49
45
|
textAlign,
|
|
50
46
|
textTransform
|
|
51
47
|
})
|
package/src/index.js
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
export { default as ActivityIndicator } from './ActivityIndicator'
|
|
2
2
|
export { default as Box } from './Box'
|
|
3
3
|
export * from './Button'
|
|
4
|
+
export { default as Card } from './Card'
|
|
4
5
|
export { default as Divider } from './Divider'
|
|
5
6
|
export { default as ExpandCollapse, Accordion } from './ExpandCollapse'
|
|
7
|
+
export { default as Feedback } from './Feedback'
|
|
6
8
|
export { default as FlexGrid } from './FlexGrid'
|
|
7
9
|
export { default as Icon } from './Icon'
|
|
8
10
|
export * from './Icon'
|
|
9
11
|
export * from './Link'
|
|
12
|
+
export { default as Pagination } from './Pagination'
|
|
10
13
|
export { default as SideNav } from './SideNav'
|
|
14
|
+
export { default as Spacer } from './Spacer'
|
|
15
|
+
export { default as StackView } from './StackView'
|
|
16
|
+
export * from './StackView'
|
|
17
|
+
export { default as TextInput } from './TextInput'
|
|
11
18
|
export { default as ToggleSwitch } from './ToggleSwitch'
|
|
12
19
|
export { default as Typography } from './Typography'
|
|
13
20
|
|
|
14
21
|
export { default as A11yInfoProvider, useA11yInfo } from './A11yInfoProvider'
|
|
15
22
|
export { default as BaseProvider } from './BaseProvider'
|
|
16
23
|
export { default as ViewportProvider, useViewport } from './ViewportProvider'
|
|
17
|
-
export { default as ThemeProvider, useTheme, useSetTheme } from './ThemeProvider'
|
|
24
|
+
export { default as ThemeProvider, useTheme, useSetTheme, useThemeTokens } from './ThemeProvider'
|
|
18
25
|
|
|
19
26
|
export * from './utils'
|
package/src/utils/index.js
CHANGED
package/src/utils/input.js
CHANGED
|
@@ -55,6 +55,7 @@ Consumers of this hook must be one of:
|
|
|
55
55
|
* currentValue: any
|
|
56
56
|
* setValue: (value: any) => void
|
|
57
57
|
* resetValue: () => void
|
|
58
|
+
* isControlled: bool
|
|
58
59
|
* }}
|
|
59
60
|
*/
|
|
60
61
|
|
|
@@ -81,7 +82,7 @@ export const useInputValue = (props = {}, hookName = 'useInputValue') => {
|
|
|
81
82
|
)
|
|
82
83
|
const resetValue = useCallback(() => setValue(initializedValue), [initializedValue, setValue])
|
|
83
84
|
|
|
84
|
-
return { currentValue, setValue, resetValue }
|
|
85
|
+
return { currentValue, setValue, resetValue, isControlled }
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
/**
|
package/src/utils/propTypes.js
CHANGED
|
@@ -2,6 +2,10 @@ import PropTypes from 'prop-types'
|
|
|
2
2
|
import { Linking, Platform } from 'react-native'
|
|
3
3
|
import { tokenKeys } from '@telus-uds/tools-theme'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {{[key: string]: string|number|boolean}} AppearanceSet
|
|
7
|
+
* @typedef {AppearanceSet} VariantProp
|
|
8
|
+
*/
|
|
5
9
|
export const variantProp = {
|
|
6
10
|
/**
|
|
7
11
|
* 'variant' is an optional object prop on all themable components.
|
|
@@ -17,7 +21,10 @@ export const variantProp = {
|
|
|
17
21
|
)
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
// Tokens can be primitive values (e.g. `'rgba(0,0,0,0'`, `12`), or objects of such values
|
|
25
|
+
// such as tokens that describe shadow (e.g. shadow: { inset: true, color: 'rgba(...)' ... })
|
|
26
|
+
const tokenValue = PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool])
|
|
27
|
+
const tokenValueType = PropTypes.oneOfType([tokenValue, PropTypes.objectOf(tokenValue)])
|
|
21
28
|
|
|
22
29
|
const getTokenNames = (componentName) => {
|
|
23
30
|
const componentTokenNames = tokenKeys[componentName]
|
|
@@ -40,15 +47,18 @@ export const selectTokens = (componentName, tokens) => {
|
|
|
40
47
|
return filteredTokens
|
|
41
48
|
}
|
|
42
49
|
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {string|number|boolean|{[key: string]:string|number|boolean}} TokenValue
|
|
52
|
+
* @typedef {{[key: string]: TokenValue}} TokensSet
|
|
53
|
+
* @typedef {(AppearanceSet) => TokensSet} TokensGetter
|
|
54
|
+
* @typedef {TokensSet|TokensGetter} TokensProp
|
|
55
|
+
*/
|
|
43
56
|
/**
|
|
44
57
|
* 'tokens' is an optional object prop on all themable components. Its keys must match the
|
|
45
58
|
* token keys in the component's theme schema.
|
|
46
59
|
*
|
|
47
60
|
* This prop is intended to be used as an 'escape hatch' for difficult or exceptional cases
|
|
48
61
|
* where the main theming system doesn't apply. It is intentionally permissive about values.
|
|
49
|
-
*
|
|
50
|
-
* Be careful about passing a token key with value `undefined`, as this will override any tokens from
|
|
51
|
-
* the theme. If a key is set on a `tokens` prop object, tokens from the theme will be overridden.
|
|
52
62
|
*/
|
|
53
63
|
export const getTokensPropType = (componentName) =>
|
|
54
64
|
PropTypes.oneOfType([
|
|
@@ -251,19 +261,87 @@ export const linkProps = {
|
|
|
251
261
|
}
|
|
252
262
|
}
|
|
253
263
|
|
|
254
|
-
|
|
264
|
+
const getByViewport = (propType) => ({
|
|
265
|
+
xs: propType,
|
|
266
|
+
sm: propType,
|
|
267
|
+
md: propType,
|
|
268
|
+
lg: propType,
|
|
269
|
+
xl: propType
|
|
270
|
+
})
|
|
271
|
+
/**
|
|
272
|
+
* Utilities for props that allow different values to be applied at different viewports.
|
|
273
|
+
*
|
|
274
|
+
* These should apply viewport inheritance such that if a viewport is undefined, the value is
|
|
275
|
+
* taken from the next smallest viewport for which a value is available. For example, a
|
|
276
|
+
* responsive prop { xs: 2, lg: 3 } should apply 2 at sizes sm and md, and 3 at size xl.
|
|
277
|
+
*
|
|
278
|
+
* @property {Function} getByViewport - returns an object where each each viewport has a key
|
|
279
|
+
* containing the provided argument.
|
|
280
|
+
* @property {Function} getTypeByViewport - returns a PropTypes shape validator for an object where
|
|
281
|
+
* each viewport has a key containing the provided proptype.
|
|
282
|
+
* @property {Function} getTypeOptionallyByViewport - returns a PropTypes validator that accepts
|
|
283
|
+
* either the provided proptype on its own, or the output of getTypeByViewport
|
|
284
|
+
*/
|
|
285
|
+
export const responsiveProps = {
|
|
286
|
+
getByViewport,
|
|
287
|
+
getTypeByViewport: (propType) => PropTypes.shape(getByViewport(propType)),
|
|
288
|
+
getTypeOptionallyByViewport: (propType) =>
|
|
289
|
+
PropTypes.oneOfType([propType, PropTypes.shape(getByViewport(propType))])
|
|
290
|
+
}
|
|
255
291
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
292
|
+
/**
|
|
293
|
+
* @typedef {0|1|2|3|4|5|6|7|8|9|10|11} SpacingIndex - value used to select a size on the spacing scale
|
|
294
|
+
*
|
|
295
|
+
* @typedef SpacingOptions
|
|
296
|
+
* @property {VariantProp} [SpacingOptions.variant] - optional theme scale variants e.g. compact, wide
|
|
297
|
+
* @property {number} [SpacingOptions.size] - optional override to force a particular pixel size
|
|
298
|
+
* @property {number} [SpacingOptions.subtract] - optional number to subtract from final pixel size
|
|
299
|
+
*
|
|
300
|
+
* @typedef SpacingObject
|
|
301
|
+
* @property {SpacingIndex} [SpacingObject.xs] - space scale index to use above xs viewport
|
|
302
|
+
* @property {SpacingIndex} [SpacingObject.sm] - space scale index to use above sm viewport
|
|
303
|
+
* @property {SpacingIndex} [SpacingObject.md] - space scale index to use above md viewport
|
|
304
|
+
* @property {SpacingIndex} [SpacingObject.lg] - space scale index to use above lg viewport
|
|
305
|
+
* @property {SpacingIndex} [SpacingObject.xl] - space scale index to use above xl viewport
|
|
306
|
+
* @property {SpacingIndex} [SpacingObject.space] - space scale index to use at all viewports
|
|
307
|
+
* @property {SpacingOptions} [SpacingObject.options] - optional options for this spacing
|
|
308
|
+
*
|
|
309
|
+
* @typedef {SpacingIndex|SpacingObject} SpacingValue
|
|
310
|
+
*/
|
|
311
|
+
const spacingScale = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
|
|
312
|
+
const spacingIndexPropType = PropTypes.oneOf(spacingScale)
|
|
313
|
+
const spacingObjectPropType = PropTypes.shape({
|
|
314
|
+
...responsiveProps.getByViewport(spacingIndexPropType),
|
|
315
|
+
space: spacingIndexPropType,
|
|
316
|
+
options: PropTypes.shape({
|
|
317
|
+
variant: variantProp.propType,
|
|
318
|
+
size: PropTypes.number
|
|
319
|
+
})
|
|
320
|
+
})
|
|
321
|
+
/**
|
|
322
|
+
* Components and utilities that assign fixed space between components use a theme-defined spacing scale.
|
|
323
|
+
*
|
|
324
|
+
* They typically take one or more props of the {@link SpacingValue} type and turn it into a pixel size value
|
|
325
|
+
* using the hook `useSpacingScale`, which resolves any options or responsive behaviour and returns the
|
|
326
|
+
* appropriate value from the theme spacing scale.
|
|
327
|
+
*
|
|
328
|
+
* - see /ADRs/inter-component-spacing/README.md - ADR on this structure
|
|
329
|
+
* - see /src/utils/spacing/useSpacingScale.js - hook that processes spacing values
|
|
330
|
+
* - @see {@link SpacingIndex} - themes provide spacing scales of up to 12 sizes with optional theme rules.
|
|
331
|
+
* - @see {@link SpacingValue} - either a simple number referencing an index position on the spacing
|
|
332
|
+
* scale, or an object with an optional `options` key and one or more spacing indexes keyed either by
|
|
333
|
+
* viewports or `space` to apply at all viewports.
|
|
334
|
+
*/
|
|
335
|
+
export const spacingProps = {
|
|
336
|
+
scale: spacingScale,
|
|
337
|
+
types: {
|
|
338
|
+
spacingIndex: spacingIndexPropType,
|
|
339
|
+
spacingObject: spacingObjectPropType,
|
|
340
|
+
|
|
341
|
+
// Most spacing components and utilities take this prop / arg type:
|
|
342
|
+
spacingValue: PropTypes.oneOfType([spacingIndexPropType, spacingObjectPropType])
|
|
343
|
+
}
|
|
344
|
+
}
|
|
267
345
|
|
|
268
346
|
/**
|
|
269
347
|
* Returns a prop type validator which checks whether a prop is either a component or an array of components of a given
|
|
@@ -347,3 +425,14 @@ export const componentPropType = (passedName, checkDisplayName = false) => {
|
|
|
347
425
|
|
|
348
426
|
return validate
|
|
349
427
|
}
|
|
428
|
+
|
|
429
|
+
export const copyPropTypes = PropTypes.oneOf(['en', 'fr'])
|
|
430
|
+
|
|
431
|
+
export const paddingProp = {
|
|
432
|
+
propType: PropTypes.shape({
|
|
433
|
+
paddingBottom: PropTypes.number,
|
|
434
|
+
paddingLeft: PropTypes.number,
|
|
435
|
+
paddingRight: PropTypes.number,
|
|
436
|
+
paddingTop: PropTypes.number
|
|
437
|
+
})
|
|
438
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useViewport } from '../../ViewportProvider'
|
|
2
|
+
import { useThemeTokens } from '../../ThemeProvider'
|
|
3
|
+
import { resolveSpacingValue, resolveSpacingOptions } from './utils'
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import('@telus-uds/system-constants/viewports').Viewport} Viewport
|
|
6
|
+
* @typedef {import('../propTypes.js').SpacingValue} SpacingValue
|
|
7
|
+
* @typedef {import('../propTypes.js').SpacingIndex} SpacingIndex
|
|
8
|
+
* @typedef {import('../propTypes.js').SpacingObject} SpacingObject
|
|
9
|
+
* @typedef {import('../propTypes.js').SpacingOptions} SpacingOptions
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Pass a {@link SpacingValue}, which is one of:
|
|
14
|
+
*
|
|
15
|
+
* - A number 0-11 ({@link SpacingIndex}) pointing to an index on the theme's spacing scale
|
|
16
|
+
* - Or, an object ({@link SpacingObject}), with optional properties:
|
|
17
|
+
* - `xs`, `sm`, `md`, `lg`, `xl`: {@link SpacingIndex} to apply at or above these specified {@link Viewport}
|
|
18
|
+
* - `options`: an optional {@link SpacingOptions} object, see below
|
|
19
|
+
* - `space`: a {@link SpacingIndex} to apply to all viewports (can be used alongside `options`)
|
|
20
|
+
*
|
|
21
|
+
* ## Example
|
|
22
|
+
*
|
|
23
|
+
* If the theme's spacing scale is `[0, 4, 8, 12, 16, 24, 48]` with no theme rules:
|
|
24
|
+
*
|
|
25
|
+
* - `useSpacingScale(0)` returns `0`
|
|
26
|
+
* - `useSpacingScale(2)` returns `8`
|
|
27
|
+
* - `useSpacingScale({ xs: 3, lg: 4 })` returns `12` at 'xs', 'sm' or 'md' viewports, and `16` at 'lg' or 'xl'.
|
|
28
|
+
*
|
|
29
|
+
* These viewport properties are intended to support case-specific responsive layout changes, for example, where a
|
|
30
|
+
* grid item drops or adds spacing on a particular side at viewports where the parent changes the layout shape.
|
|
31
|
+
*
|
|
32
|
+
* ## Theming
|
|
33
|
+
*
|
|
34
|
+
* A theme's `'spacingScale'` has theme rules and appearances same as components, and may support `viewport`.
|
|
35
|
+
* For example, a theme with the following rule would change index [2] above from `8` to `12` on large viewports:
|
|
36
|
+
*
|
|
37
|
+
* ```json
|
|
38
|
+
* { if: { space: 2, viewport: ['lg', 'xl'] }, tokens: { size: 12 }}
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* Setting responsive spacing in the theme is the preferred way to adapt the aesthetic tightness or looseness of
|
|
42
|
+
* a theme to the available space without changing the shape of the layout itself.
|
|
43
|
+
*
|
|
44
|
+
* ## Options
|
|
45
|
+
*
|
|
46
|
+
* Space values passed as objects may have an `options` key, with the following optional properties:
|
|
47
|
+
*
|
|
48
|
+
* - `variant`: Themes may choose to have spacing scale variants, same as variants in component themes.
|
|
49
|
+
* For example, if a theme rule contains `{ if: { space: 2, compact: true }, tokens: { size: 6 }}`,
|
|
50
|
+
* this tighter spacing can be accessed with:
|
|
51
|
+
*
|
|
52
|
+
* ```jsx
|
|
53
|
+
* const compactSize = useSpacingScale({ space: 2, options: { variant: { compact: true }}})`
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* - `subtract`: Sometimes on-brand spacing needs to be reduced by another value to achieve an on-brand result.
|
|
57
|
+
* For example, a component with a variable border may want to subtract its border width from its padding so
|
|
58
|
+
* the total distance from content edge to bounding box is a valid theme value, regardless of border width:
|
|
59
|
+
*
|
|
60
|
+
* ```jsx
|
|
61
|
+
* const padding = useSpacingScale({ space: 2, options: { subtract: themeTokens.borderWidth }})
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* - `size`: In exceptional cases, the theme's spacing scale may be bypassed by passing a `size` option.
|
|
65
|
+
* This can result in layouts that may be rejected for being off-brand so should only be used as a
|
|
66
|
+
* last resort for fixing layout problems (e.g. when working around non-branded embedded content).
|
|
67
|
+
* Where possible, fixing layout issues using a spacing scale value and the `subtract` option is preferred.
|
|
68
|
+
*
|
|
69
|
+
* ```jsx
|
|
70
|
+
* // Comments should be included when `size` is used, stating why this off-brand size is needed
|
|
71
|
+
* const iframeOffset = useSpacingScale({ options: { size: 13 }})`
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* ## References
|
|
75
|
+
*
|
|
76
|
+
* `/ADRs/inter-component-spacing/README.md` - ADR on this structure
|
|
77
|
+
*
|
|
78
|
+
* @param {SpacingValue} [spaceValue] - a {@link SpacingIndex} number, or a {@link SpacingObject}
|
|
79
|
+
* @returns {number}
|
|
80
|
+
*/
|
|
81
|
+
const useSpacingScale = (spaceValue) => {
|
|
82
|
+
// In future, may need to consider window height as well as width, particularly for native apps,
|
|
83
|
+
// e.g. to ensure designs don't look lost on large, tall, not-so-wide portrait screens.
|
|
84
|
+
const viewport = useViewport()
|
|
85
|
+
|
|
86
|
+
const { tokens, variant, overridden, subtract = 0 } = resolveSpacingOptions(spaceValue)
|
|
87
|
+
const space = overridden ? null : resolveSpacingValue(spaceValue, viewport)
|
|
88
|
+
|
|
89
|
+
const { size } = useThemeTokens('spacingScale', tokens, variant, { space, viewport })
|
|
90
|
+
return Math.max(size - subtract, 0)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default useSpacingScale
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { viewports as systemViewports } from '@telus-uds/system-constants'
|
|
2
|
+
|
|
3
|
+
export const resolveSpacingValue = (space, viewport) => {
|
|
4
|
+
// If spacing value has been passed as undefined or nullish, get the 0-index
|
|
5
|
+
if (!space) return 0
|
|
6
|
+
|
|
7
|
+
// Pass through simple non-responsive numbers (which may be in objects alongside options)
|
|
8
|
+
if (typeof space === 'number') return space
|
|
9
|
+
if (typeof space.space === 'number') return space.space
|
|
10
|
+
|
|
11
|
+
// Get the appropriate space value for the current viewport.
|
|
12
|
+
// If no viewports available (e.g. SSR), default to the smallest.
|
|
13
|
+
// If no values are found (e.g. empty space object), default to 0.
|
|
14
|
+
return (viewport && systemViewports.inherit(space)[viewport]) ?? space.xs ?? 0
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const resolveSpacingOptions = (space) => {
|
|
18
|
+
if (!space?.options) return {}
|
|
19
|
+
|
|
20
|
+
const { size, variant, subtract = 0 } = space.options
|
|
21
|
+
const overridden = typeof size === 'number'
|
|
22
|
+
|
|
23
|
+
// Might need an option that adapts the size value by current user's system font scale, so that
|
|
24
|
+
// meaningful spacing between items doesn't disappear on the highest a11y font scale settings.
|
|
25
|
+
// https://github.com/telus/universal-design-system/issues/583
|
|
26
|
+
|
|
27
|
+
return { tokens: { size }, variant, overridden, subtract }
|
|
28
|
+
}
|
|
@@ -4,6 +4,7 @@ import { StyleSheet, Text, View } from 'react-native'
|
|
|
4
4
|
|
|
5
5
|
import A11yText from '../../lib/A11yText'
|
|
6
6
|
import { Button, Typography } from '../../lib'
|
|
7
|
+
import { EachParentType, parentTypesParams } from '../supports'
|
|
7
8
|
|
|
8
9
|
const defaultArgs = {
|
|
9
10
|
text: 'This text is for screen readers,',
|
|
@@ -31,6 +32,10 @@ const Template = (args) => (
|
|
|
31
32
|
</>
|
|
32
33
|
)
|
|
33
34
|
|
|
35
|
+
export const Default = (args) => <A11yText {...args} />
|
|
36
|
+
Default.storyName = 'A11yText'
|
|
37
|
+
Default.args = defaultArgs
|
|
38
|
+
|
|
34
39
|
export const A11yTextInText = Template.bind({})
|
|
35
40
|
A11yTextInText.args = defaultArgs
|
|
36
41
|
|
|
@@ -50,12 +55,13 @@ export const A11yTextInButton = (args) => (
|
|
|
50
55
|
)
|
|
51
56
|
A11yTextInButton.args = defaultArgs
|
|
52
57
|
|
|
53
|
-
export const
|
|
54
|
-
<
|
|
55
|
-
<Template {...args} />
|
|
56
|
-
</
|
|
58
|
+
export const ParentTypes = (args) => (
|
|
59
|
+
<EachParentType {...args}>
|
|
60
|
+
{({ label }) => <Template {...args} key={label} text={label} />}
|
|
61
|
+
</EachParentType>
|
|
57
62
|
)
|
|
58
|
-
|
|
63
|
+
ParentTypes.args = defaultArgs
|
|
64
|
+
ParentTypes.parameters = parentTypesParams
|
|
59
65
|
|
|
60
66
|
const styles = StyleSheet.create({
|
|
61
67
|
// Use borders so any hairline gaps created by A11yText are visible
|