@telus-uds/components-base 0.0.2-prerelease.4 → 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 +17 -0
- package/__fixtures__/testTheme.js +83 -11
- package/__tests__/Divider/Divider.test.jsx +26 -5
- package/__tests__/Feedback/Feedback.test.jsx +42 -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__/utils/useUniqueId.test.js +31 -0
- package/lib/Box/Box.js +7 -2
- package/lib/Button/ButtonBase.js +6 -16
- package/lib/Button/ButtonGroup.js +13 -22
- package/lib/Divider/Divider.js +40 -2
- package/lib/Feedback/Feedback.js +110 -0
- package/lib/Feedback/index.js +2 -0
- 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/LinkBase.js +9 -3
- 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/Typography/Typography.js +0 -4
- package/lib/index.js +6 -1
- package/lib/utils/input.js +3 -1
- package/lib/utils/propTypes.js +9 -1
- package/lib/utils/useUniqueId.js +12 -0
- package/package.json +2 -2
- package/release-context.json +4 -4
- package/src/Box/Box.jsx +4 -2
- package/src/Button/ButtonBase.jsx +6 -18
- package/src/Button/ButtonGroup.jsx +13 -17
- package/src/Divider/Divider.jsx +38 -3
- package/src/Feedback/Feedback.jsx +99 -0
- package/src/Feedback/index.js +3 -0
- 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/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/Typography/Typography.jsx +0 -4
- package/src/index.js +6 -1
- package/src/utils/input.js +2 -1
- package/src/utils/propTypes.js +9 -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 +29 -2
- 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 +13 -1
- 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 -2
- package/stories/Link/TextButton.stories.jsx +24 -2
- package/stories/Pagination/Pagination.stories.jsx +28 -14
- 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 +109 -13
package/src/Box/Box.jsx
CHANGED
|
@@ -99,6 +99,7 @@ const Box = ({
|
|
|
99
99
|
variant,
|
|
100
100
|
tokens,
|
|
101
101
|
scroll,
|
|
102
|
+
testID,
|
|
102
103
|
...rest
|
|
103
104
|
}) => {
|
|
104
105
|
const a11y = a11yProps.select(rest)
|
|
@@ -116,13 +117,13 @@ const Box = ({
|
|
|
116
117
|
const scrollProps = typeof scroll === 'object' ? scroll : {}
|
|
117
118
|
scrollProps.contentContainerStyle = [styles, scrollProps.contentContainerStyle]
|
|
118
119
|
return (
|
|
119
|
-
<ScrollView {...scrollProps} {...a11y}>
|
|
120
|
+
<ScrollView {...scrollProps} {...a11y} testID={testID}>
|
|
120
121
|
{children}
|
|
121
122
|
</ScrollView>
|
|
122
123
|
)
|
|
123
124
|
}
|
|
124
125
|
return (
|
|
125
|
-
<View {...a11y} style={styles}>
|
|
126
|
+
<View {...a11y} style={styles} testID={testID}>
|
|
126
127
|
{children}
|
|
127
128
|
</View>
|
|
128
129
|
)
|
|
@@ -142,6 +143,7 @@ Box.propTypes = {
|
|
|
142
143
|
]),
|
|
143
144
|
variant: variantProp.propType,
|
|
144
145
|
tokens: getTokensPropType('Box'),
|
|
146
|
+
testID: PropTypes.string,
|
|
145
147
|
children: PropTypes.node.isRequired
|
|
146
148
|
}
|
|
147
149
|
|
|
@@ -18,6 +18,7 @@ const getOuterBorderOffset = ({ outerBorderGap = 0, outerBorderWidth = 0 }) =>
|
|
|
18
18
|
outerBorderGap + outerBorderWidth
|
|
19
19
|
|
|
20
20
|
const selectOuterContainerStyles = ({
|
|
21
|
+
alignSelf,
|
|
21
22
|
opacity,
|
|
22
23
|
outerBorderColor,
|
|
23
24
|
outerBorderWidth,
|
|
@@ -25,6 +26,7 @@ const selectOuterContainerStyles = ({
|
|
|
25
26
|
outerBorderRadius = 0,
|
|
26
27
|
outerBackgroundColor
|
|
27
28
|
}) => ({
|
|
29
|
+
alignSelf,
|
|
28
30
|
padding: outerBorderGap,
|
|
29
31
|
borderWidth: outerBorderWidth,
|
|
30
32
|
borderColor: outerBorderColor,
|
|
@@ -33,25 +35,12 @@ const selectOuterContainerStyles = ({
|
|
|
33
35
|
opacity
|
|
34
36
|
})
|
|
35
37
|
|
|
36
|
-
const selectOuterWidthStyles = ({
|
|
37
|
-
outerBorderGap,
|
|
38
|
-
outerBorderWidth,
|
|
39
|
-
width,
|
|
40
|
-
// TODO: make margin the responsibility of a parent
|
|
41
|
-
// https://github.com/telus/universal-design-system/issues/525
|
|
42
|
-
marginTop = 0,
|
|
43
|
-
marginBottom = 0,
|
|
44
|
-
marginLeft = 0,
|
|
45
|
-
marginRight = 0
|
|
46
|
-
}) => {
|
|
38
|
+
const selectOuterWidthStyles = ({ outerBorderGap, outerBorderWidth, width }) => {
|
|
47
39
|
// The inner container's bounding box is the bounding box of the button overall
|
|
48
40
|
// so this many device pixels will sit outside of the overall bounding box
|
|
49
41
|
const outerBorderOffset = getOuterBorderOffset({ outerBorderGap, outerBorderWidth })
|
|
50
42
|
const widthStyles = {
|
|
51
|
-
|
|
52
|
-
marginBottom: marginBottom - outerBorderOffset,
|
|
53
|
-
marginLeft: marginLeft - outerBorderOffset,
|
|
54
|
-
marginRight: marginRight - outerBorderOffset
|
|
43
|
+
margin: 0 - outerBorderOffset
|
|
55
44
|
}
|
|
56
45
|
|
|
57
46
|
if (!width) {
|
|
@@ -59,9 +48,8 @@ const selectOuterWidthStyles = ({
|
|
|
59
48
|
...widthStyles,
|
|
60
49
|
// Wrap content, stopping a flex parent's default align-items: stretch stretching focus ring beyond content
|
|
61
50
|
...Platform.select({
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
native: { alignSelf: 'flex-start' }
|
|
51
|
+
// width: fit-content isn't supported on Firefox; can't cascade props like CSS `width: fit-content; width: --moz-fit-content;`
|
|
52
|
+
web: { display: 'inline-flex' }
|
|
65
53
|
})
|
|
66
54
|
}
|
|
67
55
|
}
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
|
-
import {
|
|
3
|
+
import { Platform } from 'react-native'
|
|
4
4
|
|
|
5
5
|
import ButtonBase from './ButtonBase'
|
|
6
|
+
import { StackWrap } from '../StackView'
|
|
6
7
|
import { useViewport } from '../ViewportProvider'
|
|
7
8
|
import { useThemeTokens } from '../ThemeProvider'
|
|
8
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
a11yProps,
|
|
11
|
+
pressProps,
|
|
12
|
+
variantProp,
|
|
13
|
+
getTokensPropType,
|
|
14
|
+
selectTokens
|
|
15
|
+
} from '../utils/propTypes'
|
|
9
16
|
import { useMultipleInputValues } from '../utils/input'
|
|
10
17
|
|
|
11
|
-
const selectContainerStyles = ({ direction }) => ({
|
|
12
|
-
flexDirection: direction
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
const selectItemTokens = ({ gap }, index) => ({
|
|
16
|
-
marginLeft: index && gap
|
|
17
|
-
})
|
|
18
|
-
|
|
19
18
|
const ButtonGroup = ({
|
|
20
19
|
variant,
|
|
21
20
|
buttonVariant = {},
|
|
@@ -34,7 +33,8 @@ const ButtonGroup = ({
|
|
|
34
33
|
}) => {
|
|
35
34
|
const viewport = useViewport()
|
|
36
35
|
const themeTokens = useThemeTokens('ButtonGroup', tokens, variant, { viewport })
|
|
37
|
-
const
|
|
36
|
+
const stackTokens = selectTokens('StackView', themeTokens)
|
|
37
|
+
const { direction, space } = themeTokens
|
|
38
38
|
|
|
39
39
|
const { currentValues, toggleOneValue } = useMultipleInputValues({
|
|
40
40
|
initialValues,
|
|
@@ -51,9 +51,8 @@ const ButtonGroup = ({
|
|
|
51
51
|
const itemA11yRole = a11y.accessibilityRole === 'radioGroup' ? 'radio' : 'checkbox'
|
|
52
52
|
|
|
53
53
|
return (
|
|
54
|
-
<
|
|
54
|
+
<StackWrap {...a11y} space={space} direction={direction} tokens={stackTokens}>
|
|
55
55
|
{items.map(({ label, id = label, accessibilityLabel }, index) => {
|
|
56
|
-
const itemTokens = selectItemTokens(themeTokens, index)
|
|
57
56
|
const isSelected = currentValues.includes(id)
|
|
58
57
|
|
|
59
58
|
// Allow handlers to be passed down for blur, hover, focus, pressIn, etc
|
|
@@ -87,15 +86,12 @@ const ButtonGroup = ({
|
|
|
87
86
|
|
|
88
87
|
// Ensure button is direct child of group as MacOS voiceover only applies "X of Y" to
|
|
89
88
|
// "radio" if it's a direct child of "radiogroup", even if aria-posinset etc exists
|
|
90
|
-
// See also: TODO: make margin the responsibility of a parent
|
|
91
|
-
// https://github.com/telus/universal-design-system/issues/525
|
|
92
89
|
return (
|
|
93
90
|
<ButtonBase
|
|
94
91
|
key={id}
|
|
95
92
|
{...pressHandlers}
|
|
96
93
|
onPress={handlePress}
|
|
97
94
|
variant={{ component: 'ButtonGroup', ...buttonVariant }}
|
|
98
|
-
tokens={itemTokens}
|
|
99
95
|
selected={isSelected}
|
|
100
96
|
inactive={inactive}
|
|
101
97
|
{...itemA11y}
|
|
@@ -104,7 +100,7 @@ const ButtonGroup = ({
|
|
|
104
100
|
</ButtonBase>
|
|
105
101
|
)
|
|
106
102
|
})}
|
|
107
|
-
</
|
|
103
|
+
</StackWrap>
|
|
108
104
|
)
|
|
109
105
|
}
|
|
110
106
|
|
package/src/Divider/Divider.jsx
CHANGED
|
@@ -2,7 +2,12 @@ import React from 'react'
|
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
3
|
import { View, StyleSheet, Platform } from 'react-native'
|
|
4
4
|
import { useThemeTokens } from '../ThemeProvider'
|
|
5
|
-
import
|
|
5
|
+
import Spacer from '../Spacer'
|
|
6
|
+
import { getTokensPropType, variantProp, spacingProps } from '../utils'
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {import('../utils/propTypes.js').SpacingIndex} SpacingIndex
|
|
9
|
+
* @typedef {import('../utils/propTypes.js').SpacingObject} SpacingObject
|
|
10
|
+
*/
|
|
6
11
|
|
|
7
12
|
/**
|
|
8
13
|
* A basic divider component, horizontal by default. Color and thickness can be controlled by theme.
|
|
@@ -16,11 +21,31 @@ import { getTokensPropType, variantProp } from '../utils/propTypes'
|
|
|
16
21
|
*
|
|
17
22
|
* In a flexbox row, vertical dividers will automatically size to their parent height.
|
|
18
23
|
*
|
|
24
|
+
* ## Space
|
|
25
|
+
*
|
|
26
|
+
* Use this to create **space either side of the divider**. For simple cases, pass a number referring to
|
|
27
|
+
* a position on the theme's spacing scale; for example, this will put a Spacer of the smallest supported
|
|
28
|
+
* size either side of the divider:
|
|
29
|
+
*
|
|
30
|
+
* ```jsx
|
|
31
|
+
* <Divider space={1} />
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* `space` prop uses `useSpacingScale` and may accept a {@link SpacingObject} or a {@link SpacingIndex} number.
|
|
35
|
+
*
|
|
36
|
+
* To **reduce the length of a divider** as well as creating space between it and its neighbours, wrap it in
|
|
37
|
+
* a `Box` component. For example, this will have the second-smallest theme-supported spacing value between
|
|
38
|
+
* the dividing line and its neighbours, and will shorten the line at either end by the same amount:
|
|
39
|
+
*
|
|
40
|
+
* ```jsx
|
|
41
|
+
* <Box space={2}><Divider /></Box>
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
19
44
|
* ## Accessibility
|
|
20
45
|
*
|
|
21
46
|
* For accessibility purposes a divider component will be described with ARIA attributes, i.e. `role="separator"` and `aria-orientation="vertical/horizontal"`.
|
|
22
47
|
*/
|
|
23
|
-
const Divider = ({ variant, vertical = false, tokens, testID }) => {
|
|
48
|
+
const Divider = ({ variant, vertical = false, space, tokens, testID }) => {
|
|
24
49
|
const { color, width } = useThemeTokens('Divider', tokens, variant)
|
|
25
50
|
|
|
26
51
|
const borderStyles = vertical
|
|
@@ -43,11 +68,21 @@ const Divider = ({ variant, vertical = false, tokens, testID }) => {
|
|
|
43
68
|
: // There are no such equivalent attributes for native
|
|
44
69
|
{}
|
|
45
70
|
|
|
46
|
-
|
|
71
|
+
const divider = <View style={[styles.divider, borderStyles]} testID={testID} {...a11y} />
|
|
72
|
+
if (!space) return divider
|
|
73
|
+
const spacerProps = { space, direction: vertical ? 'row' : 'column' }
|
|
74
|
+
return (
|
|
75
|
+
<>
|
|
76
|
+
<Spacer {...spacerProps} testID={testID ? `${testID}-Spacer-before` : undefined} />
|
|
77
|
+
{divider}
|
|
78
|
+
<Spacer {...spacerProps} testID={testID ? `${testID}-Spacer-after` : undefined} />
|
|
79
|
+
</>
|
|
80
|
+
)
|
|
47
81
|
}
|
|
48
82
|
|
|
49
83
|
Divider.propTypes = {
|
|
50
84
|
tokens: getTokensPropType('Divider'),
|
|
85
|
+
space: spacingProps.types.spacingValue,
|
|
51
86
|
variant: variantProp.propType,
|
|
52
87
|
/**
|
|
53
88
|
* By default, the divider is a horizontal line the full width of its parent.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Text, View, StyleSheet } from 'react-native'
|
|
3
|
+
import PropTypes from 'prop-types'
|
|
4
|
+
|
|
5
|
+
import { applyTextStyles, useThemeTokens } from '../ThemeProvider'
|
|
6
|
+
import { a11yProps, getTokensPropType, selectTokens, variantProp } from '../utils'
|
|
7
|
+
|
|
8
|
+
const selectStyles = (tokens) => selectTokens('Feedback', tokens)
|
|
9
|
+
|
|
10
|
+
const selectTitleTextStyles = ({ titleFontSize, ...tokens }) =>
|
|
11
|
+
applyTextStyles(selectTokens('Typography', { ...tokens, fontSize: titleFontSize }))
|
|
12
|
+
const selectContentTextStyles = ({ contentFontSize, ...tokens }) =>
|
|
13
|
+
applyTextStyles(selectTokens('Typography', { ...tokens, fontSize: contentFontSize }))
|
|
14
|
+
|
|
15
|
+
const selectIconTokens = ({ iconSize, iconColor }) => ({
|
|
16
|
+
size: iconSize,
|
|
17
|
+
color: iconColor
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const selectIconContainerStyles = ({ iconGap }) => ({
|
|
21
|
+
paddingRight: iconGap
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A feedback box commonly used with form fields.
|
|
26
|
+
*
|
|
27
|
+
* ### Standalone usage
|
|
28
|
+
* While its primary use is to facilitate feedback states for other form components such as `TextInput`,
|
|
29
|
+
* you may use it standalone.
|
|
30
|
+
*
|
|
31
|
+
* ### Complex content
|
|
32
|
+
* You may pass any React tree as the children of this component, bear in mind that a render function
|
|
33
|
+
* is better suited for styling children based on Feedback's variant.
|
|
34
|
+
*
|
|
35
|
+
* ### Using a render function
|
|
36
|
+
* When a function is passed for rendering content, it will receive the feedback text styles and
|
|
37
|
+
* variant as arguments.
|
|
38
|
+
*
|
|
39
|
+
* ### Accessibility
|
|
40
|
+
* All accessibility props set on this component will be applied to the outer container.
|
|
41
|
+
*/
|
|
42
|
+
const Feedback = ({ title, children, tokens, variant, ...rest }) => {
|
|
43
|
+
const themeTokens = useThemeTokens('Feedback', tokens, variant)
|
|
44
|
+
|
|
45
|
+
const { icon: IconComponent } = themeTokens
|
|
46
|
+
|
|
47
|
+
const titleTextStyles = selectTitleTextStyles(themeTokens)
|
|
48
|
+
const contentTextStyles = selectContentTextStyles(themeTokens)
|
|
49
|
+
|
|
50
|
+
const content =
|
|
51
|
+
typeof children === 'string' ? <Text style={contentTextStyles}>{children}</Text> : children
|
|
52
|
+
|
|
53
|
+
const accessibilityProps = a11yProps.select(rest)
|
|
54
|
+
|
|
55
|
+
// TODO: use Stack to separate the title from content (space 2)
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<View style={selectStyles(themeTokens)} {...accessibilityProps}>
|
|
59
|
+
{title !== undefined && (
|
|
60
|
+
<View style={staticStyles.title}>
|
|
61
|
+
{IconComponent && (
|
|
62
|
+
<View style={selectIconContainerStyles(themeTokens)}>
|
|
63
|
+
<IconComponent tokens={selectIconTokens(themeTokens)} />
|
|
64
|
+
</View>
|
|
65
|
+
)}
|
|
66
|
+
<Text style={titleTextStyles}>{title}</Text>
|
|
67
|
+
</View>
|
|
68
|
+
)}
|
|
69
|
+
{content && typeof content === 'function' ? (
|
|
70
|
+
content({ textStyles: contentTextStyles, variant })
|
|
71
|
+
) : (
|
|
72
|
+
<View>{content}</View>
|
|
73
|
+
)}
|
|
74
|
+
</View>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
Feedback.propTypes = {
|
|
79
|
+
/**
|
|
80
|
+
* Emphasized summary of the feedback. If an icon is set, it is rendered next to the title.
|
|
81
|
+
*/
|
|
82
|
+
title: PropTypes.string,
|
|
83
|
+
/**
|
|
84
|
+
* Feedback content rendered below the title. A render function `({textStyles, variant}) => {}` is supported.
|
|
85
|
+
*/
|
|
86
|
+
children: PropTypes.oneOfType([PropTypes.string, PropTypes.node, PropTypes.func]),
|
|
87
|
+
tokens: getTokensPropType('Feedback'),
|
|
88
|
+
variant: variantProp.propType
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export default Feedback
|
|
92
|
+
|
|
93
|
+
const staticStyles = StyleSheet.create({
|
|
94
|
+
title: {
|
|
95
|
+
display: 'flex',
|
|
96
|
+
flexDirection: 'row',
|
|
97
|
+
alignItems: 'center'
|
|
98
|
+
}
|
|
99
|
+
})
|
package/src/Icon/Icon.jsx
CHANGED
|
@@ -19,7 +19,8 @@ const Icon = ({ IconSvg, variant, label, titleId, tokens = {} }) => {
|
|
|
19
19
|
transition: 'transform 200ms',
|
|
20
20
|
transform: [
|
|
21
21
|
themeTokens.scale ? `scale(${themeTokens.scale})` : '',
|
|
22
|
-
themeTokens.translateX ? `translateX(${themeTokens.translateX}px)` : ''
|
|
22
|
+
themeTokens.translateX ? `translateX(${themeTokens.translateX}px)` : '',
|
|
23
|
+
themeTokens.translateY ? `translateY(${themeTokens.translateY}px)` : ''
|
|
23
24
|
]
|
|
24
25
|
.filter((exists) => exists)
|
|
25
26
|
.join(' ')
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, Text, StyleSheet } from 'react-native'
|
|
3
|
+
import PropTypes from 'prop-types'
|
|
4
|
+
|
|
5
|
+
import { applyTextStyles, useThemeTokens } from '../ThemeProvider'
|
|
6
|
+
import { getTokensPropType, selectTokens, variantProp } from '../utils'
|
|
7
|
+
|
|
8
|
+
import LabelContent from './LabelContent'
|
|
9
|
+
|
|
10
|
+
const selectLabelStyles = (tokens) => applyTextStyles(selectTokens('Typography', tokens))
|
|
11
|
+
|
|
12
|
+
const selectHintStyles = ({
|
|
13
|
+
hintColor,
|
|
14
|
+
hintFontName,
|
|
15
|
+
hintFontSize,
|
|
16
|
+
hintFontWeight,
|
|
17
|
+
hintLineHeight
|
|
18
|
+
}) =>
|
|
19
|
+
applyTextStyles({
|
|
20
|
+
color: hintColor,
|
|
21
|
+
fontName: hintFontName,
|
|
22
|
+
fontSize: hintFontSize,
|
|
23
|
+
fontWeight: hintFontWeight,
|
|
24
|
+
lineHeight: hintLineHeight
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const selectGapStyles = ({ gap }) => ({ marginRight: gap })
|
|
28
|
+
|
|
29
|
+
function InputLabel({
|
|
30
|
+
label,
|
|
31
|
+
forId,
|
|
32
|
+
hint,
|
|
33
|
+
hintPosition = 'inline',
|
|
34
|
+
hintId,
|
|
35
|
+
tooltip,
|
|
36
|
+
tokens,
|
|
37
|
+
variant
|
|
38
|
+
}) {
|
|
39
|
+
const themeTokens = useThemeTokens('InputLabel', tokens, variant)
|
|
40
|
+
|
|
41
|
+
// TODO: use the actual Tooltip component here
|
|
42
|
+
const hasTooltip = tooltip !== undefined
|
|
43
|
+
const isHintInline = hintPosition === 'inline'
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<View style={[staticStyles.container, !isHintInline && staticStyles.containerWithHintBelow]}>
|
|
47
|
+
<Text
|
|
48
|
+
style={[selectLabelStyles(themeTokens), selectGapStyles(themeTokens), staticStyles.label]}
|
|
49
|
+
>
|
|
50
|
+
<LabelContent forId={forId}>{label}</LabelContent>
|
|
51
|
+
</Text>
|
|
52
|
+
{hint && isHintInline && (
|
|
53
|
+
<Text
|
|
54
|
+
style={[selectHintStyles(themeTokens), hasTooltip && selectGapStyles(themeTokens)]}
|
|
55
|
+
nativeID={hintId}
|
|
56
|
+
>
|
|
57
|
+
{hint}
|
|
58
|
+
</Text>
|
|
59
|
+
)}
|
|
60
|
+
{tooltip && <Text>?</Text>}
|
|
61
|
+
{hint && !isHintInline && (
|
|
62
|
+
<Text style={[selectHintStyles(themeTokens), staticStyles.hintBelow]} nativeID={hintId}>
|
|
63
|
+
{hint}
|
|
64
|
+
</Text>
|
|
65
|
+
)}
|
|
66
|
+
</View>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
InputLabel.propTypes = {
|
|
71
|
+
label: PropTypes.string.isRequired,
|
|
72
|
+
forId: PropTypes.string,
|
|
73
|
+
hint: PropTypes.string,
|
|
74
|
+
hintPosition: PropTypes.oneOf(['inline', 'below']),
|
|
75
|
+
hintId: PropTypes.string,
|
|
76
|
+
tooltip: PropTypes.string,
|
|
77
|
+
tokens: getTokensPropType('InputLabel'),
|
|
78
|
+
variant: variantProp.propType
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default InputLabel
|
|
82
|
+
|
|
83
|
+
const staticStyles = StyleSheet.create({
|
|
84
|
+
container: {
|
|
85
|
+
display: 'flex',
|
|
86
|
+
flexDirection: 'row',
|
|
87
|
+
alignItems: 'center'
|
|
88
|
+
},
|
|
89
|
+
containerWithHintBelow: {
|
|
90
|
+
flexWrap: 'wrap'
|
|
91
|
+
},
|
|
92
|
+
label: {
|
|
93
|
+
flexShrink: 0
|
|
94
|
+
},
|
|
95
|
+
hintBelow: {
|
|
96
|
+
flexBasis: '100%',
|
|
97
|
+
flexShrink: 0
|
|
98
|
+
}
|
|
99
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
|
|
4
|
+
function LabelContent({ children, forId }) {
|
|
5
|
+
return <label htmlFor={forId}>{children}</label>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default LabelContent
|
|
9
|
+
|
|
10
|
+
LabelContent.propTypes = {
|
|
11
|
+
children: PropTypes.string,
|
|
12
|
+
forId: PropTypes.string
|
|
13
|
+
}
|
package/src/Link/LinkBase.jsx
CHANGED
|
@@ -40,7 +40,10 @@ const selectOuterBorderStyles = ({
|
|
|
40
40
|
outline: outerBorderOutline,
|
|
41
41
|
borderWidth: outerBorderWidth,
|
|
42
42
|
borderColor: outerBorderColor,
|
|
43
|
-
borderRadius: outerBorderRadius
|
|
43
|
+
borderRadius: outerBorderRadius,
|
|
44
|
+
// Stops focus ring stretching horizontally if parent has display: block
|
|
45
|
+
// width: fit-content isn't supported on Firefox; can't cascade props like CSS `width: fit-content; width: --moz-fit-content;`
|
|
46
|
+
display: 'inline-flex'
|
|
44
47
|
}
|
|
45
48
|
: {}
|
|
46
49
|
|
|
@@ -62,10 +65,12 @@ const selectIconStyles = ({
|
|
|
62
65
|
iconGapBefore,
|
|
63
66
|
iconGapAfter,
|
|
64
67
|
iconScale,
|
|
65
|
-
iconTranslateX
|
|
68
|
+
iconTranslateX,
|
|
69
|
+
iconTranslateY
|
|
66
70
|
}) => ({
|
|
67
71
|
scale: iconScale,
|
|
68
72
|
translateX: iconTranslateX,
|
|
73
|
+
translateY: iconTranslateY,
|
|
69
74
|
size: iconSize,
|
|
70
75
|
gapBefore: iconGapBefore,
|
|
71
76
|
gapAfter: iconGapAfter
|
|
@@ -181,7 +186,8 @@ const LinkBase = ({
|
|
|
181
186
|
size: iconStyles.size ? iconStyles.size * iconScale : undefined,
|
|
182
187
|
color: contentStyles.color ?? undefined,
|
|
183
188
|
scale: iconStyles.scale ?? undefined,
|
|
184
|
-
translateX: iconStyles.translateX ?? undefined
|
|
189
|
+
translateX: iconStyles.translateX ?? undefined,
|
|
190
|
+
translateY: iconStyles.translateY ?? undefined
|
|
185
191
|
}
|
|
186
192
|
|
|
187
193
|
const iconContent = <IconComponent tokens={iconTokens} variant={iconVariant} />
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import { StyleSheet, View } from 'react-native'
|
|
4
|
+
|
|
5
|
+
import { useSpacingScale, spacingProps } from '../utils'
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {import('../utils/propTypes.js').SpacingValue} SpacingValue
|
|
8
|
+
* @typedef {import('../utils/propTypes.js').SpacingObject} SpacingObject
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const selectSizeStyle = (size, direction) => ({
|
|
12
|
+
// Only apply space in one direction at a time, else large spacers will increase the
|
|
13
|
+
// 'across' size of the parent and mess up siblings with styles like `alignItems: stretch`.
|
|
14
|
+
[direction === 'row' ? 'width' : 'height']: size
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* An empty element used to create a gap between components that is sized according to the theme spacing scale,
|
|
19
|
+
* passing its `space` prop ({@link SpacingValue}) to `useSpacingScale`.
|
|
20
|
+
*
|
|
21
|
+
* ## Simple spacing
|
|
22
|
+
*
|
|
23
|
+
* For most simple uses, pass a number to Spacer's `space` prop referring to an index in the theme's
|
|
24
|
+
* spacing scale. For example, for a spacer of the theme's smallest non-zero spacing size:
|
|
25
|
+
*
|
|
26
|
+
* ```jsx
|
|
27
|
+
* <Spacer space={1} />
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* ## Responsive spacing
|
|
31
|
+
*
|
|
32
|
+
* Different spacing scale sizes may be chosen for different viewports by passing `space` a {@link SpacingObject}
|
|
33
|
+
* using keys for the viewports at and above which certain spacing indexes should apply.
|
|
34
|
+
*
|
|
35
|
+
* This can be useful when a Spacer is inside a layout with a shape that changes between viewports.
|
|
36
|
+
*
|
|
37
|
+
* For example, a spacer might need no width below 'md' viewport because the items it separates will be on separate lines,
|
|
38
|
+
* but may need a visible size above that viewport, and a wider size at the highest viewport:
|
|
39
|
+
*
|
|
40
|
+
* ```jsx
|
|
41
|
+
* <Spacer space={{ xs: 0, md: 2, xl: 3 }} />
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* Note that themes may also define viewport-specific behaviour for indexes on the spacing scale. These viewport
|
|
45
|
+
* props should only be used where necessary to achieve a certain responsive layoutm and shouldn't be used to replace
|
|
46
|
+
* or deviate from responsive spacing behaviour in a theme that is intended to apply universally.
|
|
47
|
+
*
|
|
48
|
+
* ## More options
|
|
49
|
+
*
|
|
50
|
+
* See `useSpacingScale` hook for more options that may be used with the `space` prop.
|
|
51
|
+
*
|
|
52
|
+
* ## Accessibility
|
|
53
|
+
*
|
|
54
|
+
* Spacer has no content and is ignored by tools such as screen readers. Use `Divider` for
|
|
55
|
+
* separations between elements that may be treated as semantically meaningful on web.
|
|
56
|
+
*/
|
|
57
|
+
const Spacer = ({ space = 1, direction = 'column', testID }) => {
|
|
58
|
+
const size = useSpacingScale(space)
|
|
59
|
+
const sizeStyle = selectSizeStyle(size, direction)
|
|
60
|
+
return <View testID={testID} style={[staticStyles.stretch, sizeStyle]} />
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
Spacer.propTypes = {
|
|
64
|
+
/**
|
|
65
|
+
* The size of the spacer according to the theme's spacing scale.
|
|
66
|
+
* Either a number corresponding to a position on the theme's spacing scale (1 is smallest, 2 is second smallest, etc),
|
|
67
|
+
* or, a SpacingObject with viewport keys and options (see `useSpacingScale` for details).
|
|
68
|
+
*/
|
|
69
|
+
space: spacingProps.types.spacingValue,
|
|
70
|
+
/**
|
|
71
|
+
* The spacer applies space in only one direction, which is controlled by the `direction` prop:
|
|
72
|
+
*
|
|
73
|
+
* - `'column'` (default) applies space vertically; has a fixed height and not width.
|
|
74
|
+
* - `'row'` applies space horizontally; has a fixed width and not height.
|
|
75
|
+
*/
|
|
76
|
+
direction: PropTypes.oneOf(['column', 'row']),
|
|
77
|
+
/**
|
|
78
|
+
* @ignore
|
|
79
|
+
* In tests, the only way to select Spacers is with testID prop and getByTestId, since they have no content,
|
|
80
|
+
* no accessibility role, and there is no equivalent of nextSibling in React Native Testing Library.
|
|
81
|
+
*/
|
|
82
|
+
testID: PropTypes.string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const staticStyles = StyleSheet.create({
|
|
86
|
+
stretch: {
|
|
87
|
+
alignSelf: 'stretch'
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
export default Spacer
|