@telus-uds/components-base 0.0.2-prerelease.6 → 0.0.2-prerelease.7
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/.ultra.cache.json +1 -1
- package/CHANGELOG.md +20 -0
- package/__fixtures__/testTheme.js +424 -37
- package/__tests__/Button/ButtonBase.test.jsx +2 -31
- package/__tests__/Checkbox/Checkbox.test.jsx +94 -0
- package/__tests__/InputSupports/InputSupports.test.jsx +50 -0
- package/__tests__/List/List.test.jsx +60 -0
- package/__tests__/Radio/Radio.test.jsx +87 -0
- package/__tests__/Select/Select.test.jsx +93 -0
- package/__tests__/Skeleton/Skeleton.test.jsx +61 -0
- package/__tests__/Tags/Tags.test.jsx +328 -0
- package/__tests__/TextInput/TextArea.test.jsx +34 -0
- package/__tests__/TextInput/{TextInput.test.jsx → TextInputBase.test.jsx} +20 -46
- package/jest.config.js +3 -1
- package/lib/Button/Button.js +10 -3
- package/lib/Button/ButtonBase.js +73 -59
- package/lib/Button/ButtonGroup.js +11 -27
- package/lib/Button/ButtonLink.js +5 -0
- package/lib/Checkbox/Checkbox.js +308 -0
- package/lib/Checkbox/CheckboxInput.native.js +6 -0
- package/lib/Checkbox/CheckboxInput.web.js +57 -0
- package/lib/Checkbox/index.js +2 -0
- package/lib/Feedback/Feedback.js +20 -3
- package/lib/Icon/Icon.js +8 -5
- package/lib/Icon/IconText.js +72 -0
- package/lib/Icon/index.js +2 -1
- package/lib/InputSupports/InputSupports.js +90 -0
- package/lib/InputSupports/index.js +2 -0
- package/lib/InputSupports/propTypes.js +55 -0
- package/lib/Link/ChevronLink.js +23 -20
- package/lib/Link/InlinePressable.native.js +78 -0
- package/lib/Link/InlinePressable.web.js +32 -0
- package/lib/Link/Link.js +11 -10
- package/lib/Link/LinkBase.js +62 -123
- package/lib/Link/TextButton.js +20 -9
- package/lib/Link/index.js +2 -1
- package/lib/List/List.js +52 -0
- package/lib/List/ListItem.js +207 -0
- package/lib/List/index.js +2 -0
- package/lib/Pagination/PageButton.js +2 -25
- package/lib/Pagination/SideButton.js +27 -37
- package/lib/Radio/Radio.js +291 -0
- package/lib/Radio/RadioInput.native.js +6 -0
- package/lib/Radio/RadioInput.web.js +59 -0
- package/lib/Radio/index.js +2 -0
- package/lib/Select/Group.native.js +14 -0
- package/lib/Select/Group.web.js +18 -0
- package/lib/Select/Item.native.js +9 -0
- package/lib/Select/Item.web.js +15 -0
- package/lib/Select/Picker.native.js +87 -0
- package/lib/Select/Picker.web.js +63 -0
- package/lib/Select/Select.js +272 -0
- package/lib/Select/index.js +6 -0
- package/lib/Skeleton/Skeleton.js +119 -0
- package/lib/Skeleton/index.js +2 -0
- package/lib/Tags/Tags.js +217 -0
- package/lib/Tags/index.js +2 -0
- package/lib/TextInput/TextArea.js +82 -0
- package/lib/TextInput/TextInput.js +23 -304
- package/lib/TextInput/TextInputBase.js +229 -0
- package/lib/TextInput/index.js +2 -1
- package/lib/TextInput/propTypes.js +31 -0
- package/lib/ThemeProvider/useThemeTokens.js +54 -3
- package/lib/ToggleSwitch/ToggleSwitch.js +1 -1
- package/lib/Typography/Typography.js +4 -19
- package/lib/index.js +8 -1
- package/lib/utils/a11y/index.js +1 -0
- package/lib/utils/a11y/textSize.js +33 -0
- package/lib/utils/index.js +3 -0
- package/lib/utils/info/index.js +7 -0
- package/lib/utils/info/platform/index.js +11 -0
- package/lib/utils/info/platform/platform.android.js +1 -0
- package/lib/utils/info/platform/platform.ios.js +1 -0
- package/lib/utils/info/platform/platform.native.js +4 -0
- package/lib/utils/info/platform/platform.web.js +1 -0
- package/lib/utils/info/versions.js +5 -0
- package/lib/utils/pressability.js +92 -0
- package/lib/utils/propTypes.js +78 -17
- package/package.json +5 -4
- package/release-context.json +4 -4
- package/src/Button/Button.jsx +6 -3
- package/src/Button/ButtonBase.jsx +66 -57
- package/src/Button/ButtonGroup.jsx +9 -22
- package/src/Button/ButtonLink.jsx +11 -2
- package/src/Checkbox/Checkbox.jsx +275 -0
- package/src/Checkbox/CheckboxInput.native.jsx +6 -0
- package/src/Checkbox/CheckboxInput.web.jsx +55 -0
- package/src/Checkbox/index.js +3 -0
- package/src/Feedback/Feedback.jsx +13 -4
- package/src/Icon/Icon.jsx +9 -5
- package/src/Icon/IconText.jsx +63 -0
- package/src/Icon/index.js +2 -1
- package/src/InputSupports/InputSupports.jsx +86 -0
- package/src/InputSupports/index.js +3 -0
- package/src/InputSupports/propTypes.js +44 -0
- package/src/Link/ChevronLink.jsx +20 -17
- package/src/Link/InlinePressable.native.jsx +73 -0
- package/src/Link/InlinePressable.web.jsx +37 -0
- package/src/Link/Link.jsx +17 -13
- package/src/Link/LinkBase.jsx +57 -140
- package/src/Link/TextButton.jsx +25 -11
- package/src/Link/index.js +2 -1
- package/src/List/List.jsx +47 -0
- package/src/List/ListItem.jsx +187 -0
- package/src/List/index.js +3 -0
- package/src/Pagination/PageButton.jsx +2 -16
- package/src/Pagination/SideButton.jsx +23 -34
- package/src/Radio/Radio.jsx +270 -0
- package/src/Radio/RadioInput.native.jsx +6 -0
- package/src/Radio/RadioInput.web.jsx +57 -0
- package/src/Radio/index.js +3 -0
- package/src/Select/Group.native.jsx +14 -0
- package/src/Select/Group.web.jsx +15 -0
- package/src/Select/Item.native.jsx +10 -0
- package/src/Select/Item.web.jsx +11 -0
- package/src/Select/Picker.native.jsx +95 -0
- package/src/Select/Picker.web.jsx +67 -0
- package/src/Select/Select.jsx +265 -0
- package/src/Select/index.js +8 -0
- package/src/Skeleton/Skeleton.jsx +101 -0
- package/src/Skeleton/index.js +3 -0
- package/src/Tags/Tags.jsx +206 -0
- package/src/Tags/index.js +3 -0
- package/src/TextInput/TextArea.jsx +78 -0
- package/src/TextInput/TextInput.jsx +17 -284
- package/src/TextInput/TextInputBase.jsx +220 -0
- package/src/TextInput/index.js +2 -1
- package/src/TextInput/propTypes.js +29 -0
- package/src/ThemeProvider/useThemeTokens.js +54 -3
- package/src/ToggleSwitch/ToggleSwitch.jsx +1 -1
- package/src/Typography/Typography.jsx +4 -15
- package/src/index.js +8 -1
- package/src/utils/a11y/index.js +1 -0
- package/src/utils/a11y/textSize.js +30 -0
- package/src/utils/index.js +3 -0
- package/src/utils/info/index.js +8 -0
- package/src/utils/info/platform/index.js +11 -0
- package/src/utils/info/platform/platform.android.js +1 -0
- package/src/utils/info/platform/platform.ios.js +1 -0
- package/src/utils/info/platform/platform.native.js +4 -0
- package/src/utils/info/platform/platform.web.js +1 -0
- package/src/utils/info/versions.js +6 -0
- package/src/utils/pressability.js +92 -0
- package/src/utils/propTypes.js +97 -22
- package/stories/Button/Button.stories.jsx +5 -0
- package/stories/Checkbox/Checkbox.stories.jsx +71 -0
- package/stories/Feedback/Feedback.stories.jsx +5 -6
- package/stories/Link/Link.stories.jsx +15 -1
- package/stories/List/List.stories.jsx +117 -0
- package/stories/Radio/Radio.stories.jsx +113 -0
- package/stories/Select/Select.stories.jsx +55 -0
- package/stories/Skeleton/Skeleton.stories.jsx +36 -0
- package/stories/Tags/Tags.stories.jsx +69 -0
- package/stories/TextInput/TextArea.stories.jsx +100 -0
- package/stories/supports.jsx +1 -1
package/lib/Button/ButtonBase.js
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Pressable, Text, View, StyleSheet, Platform } from 'react-native';
|
|
3
|
-
import { useThemeTokensCallback } from '../ThemeProvider';
|
|
4
3
|
import { applyTextStyles, applyShadowToken } from '../ThemeProvider/utils';
|
|
5
4
|
import buttonPropTypes from './propTypes';
|
|
6
|
-
import { a11yProps, hrefAttrsProp, linkProps } from '../utils
|
|
7
|
-
|
|
8
|
-
const getCursorStyle = (inactive, accessibilityRole) => {
|
|
9
|
-
if (inactive) return 'not-allowed'; // These roles should result in cursor: pointer but don't in current RNW releases
|
|
10
|
-
|
|
11
|
-
if (['checkbox', 'radio', 'switch'].includes(accessibilityRole)) return 'pointer'; // For everything else, let React Native Web figure it out internally
|
|
12
|
-
|
|
13
|
-
return undefined;
|
|
14
|
-
};
|
|
5
|
+
import { a11yProps, getCursorStyle, hrefAttrsProp, linkProps, resolvePressableState, resolvePressableTokens } from '../utils';
|
|
15
6
|
|
|
16
7
|
const getOuterBorderOffset = ({
|
|
17
8
|
outerBorderGap = 0,
|
|
@@ -94,7 +85,8 @@ const selectInnerContainerStyles = ({
|
|
|
94
85
|
paddingTop,
|
|
95
86
|
paddingBottom,
|
|
96
87
|
shadow,
|
|
97
|
-
borderWidth
|
|
88
|
+
borderWidth,
|
|
89
|
+
minWidth
|
|
98
90
|
}) => {
|
|
99
91
|
// Subtract border width from padding so overall button width/height doesn't
|
|
100
92
|
// jump around if the border width changes (avoiding NaN and negative padding)
|
|
@@ -106,6 +98,7 @@ const selectInnerContainerStyles = ({
|
|
|
106
98
|
paddingTop: offsetBorder(paddingTop),
|
|
107
99
|
paddingBottom: offsetBorder(paddingBottom),
|
|
108
100
|
backgroundColor,
|
|
101
|
+
minWidth,
|
|
109
102
|
...applyShadowToken(shadow)
|
|
110
103
|
};
|
|
111
104
|
};
|
|
@@ -138,76 +131,83 @@ const selectTextStyles = ({
|
|
|
138
131
|
|
|
139
132
|
const selectWebOnlyStyles = (inactive, themeTokens, {
|
|
140
133
|
accessibilityRole
|
|
141
|
-
}) =>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
134
|
+
}) => {
|
|
135
|
+
return Platform.select({
|
|
136
|
+
web: {
|
|
137
|
+
// if it would overflow the container, wraps instead
|
|
138
|
+
maxWidth: `calc(100% + ${getOuterBorderOffset(themeTokens) * 2}px)`,
|
|
139
|
+
outline: 'none',
|
|
140
|
+
// removes the default browser :focus outline
|
|
141
|
+
...getCursorStyle(inactive, accessibilityRole)
|
|
142
|
+
},
|
|
143
|
+
default: {}
|
|
144
|
+
});
|
|
145
|
+
}; // TODO: see if this can be made into a generalised utility, ideally when
|
|
146
|
+
// there is a stable, generic, generalised approach to within-component text
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
const resolveChildren = (children, {
|
|
150
|
+
textStyles,
|
|
151
|
+
state
|
|
152
|
+
}) => {
|
|
153
|
+
switch (typeof children) {
|
|
154
|
+
case 'function':
|
|
155
|
+
return children(state);
|
|
146
156
|
|
|
147
|
-
|
|
157
|
+
case 'string':
|
|
158
|
+
return /*#__PURE__*/React.createElement(Text, {
|
|
159
|
+
style: textStyles
|
|
160
|
+
}, children);
|
|
161
|
+
|
|
162
|
+
default:
|
|
163
|
+
return children;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
148
166
|
|
|
149
167
|
const ButtonBase = ({
|
|
150
168
|
href,
|
|
151
169
|
hrefAttrs,
|
|
152
170
|
children,
|
|
153
|
-
variant,
|
|
154
171
|
onPress,
|
|
155
|
-
tokens,
|
|
172
|
+
tokens = {},
|
|
156
173
|
disabled = false,
|
|
157
174
|
// alias for inactive
|
|
158
175
|
inactive = disabled,
|
|
159
176
|
selected = false,
|
|
160
177
|
...rest
|
|
161
178
|
}) => {
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
const getButtonState = ({
|
|
165
|
-
pressed,
|
|
166
|
-
focused,
|
|
167
|
-
hovered
|
|
168
|
-
}) => ({
|
|
169
|
-
pressed,
|
|
170
|
-
focus: focused,
|
|
171
|
-
hover: hovered,
|
|
179
|
+
const extraButtonState = {
|
|
172
180
|
inactive,
|
|
173
181
|
selected
|
|
174
|
-
}
|
|
182
|
+
};
|
|
175
183
|
|
|
176
|
-
const
|
|
184
|
+
const resolveTokens = pressableState => resolvePressableTokens(tokens, pressableState, extraButtonState);
|
|
177
185
|
|
|
178
186
|
const a11y = a11yProps.select(rest);
|
|
179
187
|
|
|
180
188
|
const getPressableStyle = pressableState => {
|
|
181
|
-
const themeTokens =
|
|
182
|
-
return [staticStyles.
|
|
189
|
+
const themeTokens = resolveTokens(pressableState);
|
|
190
|
+
return [staticStyles.row, selectWebOnlyStyles(inactive, themeTokens, a11y), selectOuterContainerStyles(themeTokens), selectOuterWidthStyles(themeTokens)];
|
|
183
191
|
};
|
|
184
192
|
|
|
185
|
-
const handlePress = linkProps.handleHref({
|
|
186
|
-
href,
|
|
187
|
-
onPress
|
|
188
|
-
});
|
|
189
193
|
return /*#__PURE__*/React.createElement(Pressable, Object.assign({
|
|
190
|
-
|
|
194
|
+
href: href,
|
|
195
|
+
onPress: linkProps.handleHref({
|
|
196
|
+
href,
|
|
197
|
+
onPress
|
|
198
|
+
}),
|
|
191
199
|
style: getPressableStyle,
|
|
192
|
-
disabled: inactive
|
|
193
|
-
href: href
|
|
200
|
+
disabled: inactive
|
|
194
201
|
}, hrefAttrsProp.spread(hrefAttrs), a11y), pressableState => {
|
|
195
|
-
const themeTokens =
|
|
202
|
+
const themeTokens = resolveTokens(pressableState);
|
|
196
203
|
const containerStyles = selectInnerContainerStyles(themeTokens);
|
|
197
204
|
const borderStyles = selectBorderStyles(themeTokens);
|
|
198
|
-
const textStyles =
|
|
199
|
-
...Platform.select({
|
|
200
|
-
// TODO: https://github.com/telus/universal-design-system/issues/487
|
|
201
|
-
web: {
|
|
202
|
-
transition: 'color 200ms'
|
|
203
|
-
}
|
|
204
|
-
})
|
|
205
|
-
}; // If the container has a width set, fill it instead of sizing from content.
|
|
205
|
+
const textStyles = [selectTextStyles(themeTokens), staticStyles.text]; // If the container has a width set, fill it instead of sizing from content.
|
|
206
206
|
// If in future we support text alignments other than center, add here.
|
|
207
207
|
|
|
208
|
-
const stretchStyles =
|
|
208
|
+
const stretchStyles = themeTokens.width ? staticStyles.stretch : staticStyles.align;
|
|
209
209
|
return /*#__PURE__*/React.createElement(View, {
|
|
210
|
-
style: [containerStyles, borderStyles, stretchStyles, Platform.select({
|
|
210
|
+
style: [containerStyles, borderStyles, stretchStyles, staticStyles.row, Platform.select({
|
|
211
211
|
web: {
|
|
212
212
|
maxWidth: '100%',
|
|
213
213
|
// ensure overflowing content wraps
|
|
@@ -215,11 +215,12 @@ const ButtonBase = ({
|
|
|
215
215
|
transition: 'background-color 200ms, border-color 200ms'
|
|
216
216
|
}
|
|
217
217
|
})]
|
|
218
|
-
},
|
|
218
|
+
}, resolveChildren(children, {
|
|
219
|
+
state: { ...resolvePressableState(pressableState, extraButtonState),
|
|
220
|
+
textStyles
|
|
221
|
+
},
|
|
219
222
|
textStyles
|
|
220
|
-
})
|
|
221
|
-
style: textStyles
|
|
222
|
-
}, children));
|
|
223
|
+
}));
|
|
223
224
|
});
|
|
224
225
|
};
|
|
225
226
|
|
|
@@ -228,11 +229,24 @@ ButtonBase.propTypes = { ...a11yProps.types,
|
|
|
228
229
|
...linkProps.types
|
|
229
230
|
};
|
|
230
231
|
const staticStyles = StyleSheet.create({
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
232
|
+
row: {
|
|
233
|
+
// Apply all button alignment horizontally; no vertical stacking within a button
|
|
234
|
+
flexDirection: 'row'
|
|
235
|
+
},
|
|
236
|
+
text: {
|
|
237
|
+
flexGrow: 1,
|
|
238
|
+
// On native but not web, flexShrink here wraps text prematurely
|
|
239
|
+
...Platform.select({
|
|
240
|
+
// TODO: https://github.com/telus/universal-design-system/issues/487
|
|
241
|
+
web: {
|
|
242
|
+
transition: 'color 200ms'
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
},
|
|
246
|
+
align: {
|
|
247
|
+
alignItems: 'center'
|
|
234
248
|
},
|
|
235
|
-
|
|
249
|
+
stretch: {
|
|
236
250
|
flex: 1,
|
|
237
251
|
alignItems: 'center',
|
|
238
252
|
justifyContent: 'center'
|
|
@@ -4,13 +4,13 @@ import { Platform } from 'react-native';
|
|
|
4
4
|
import ButtonBase from './ButtonBase';
|
|
5
5
|
import { StackWrap } from '../StackView';
|
|
6
6
|
import { useViewport } from '../ViewportProvider';
|
|
7
|
-
import { useThemeTokens } from '../ThemeProvider';
|
|
7
|
+
import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider';
|
|
8
8
|
import { a11yProps, pressProps, variantProp, getTokensPropType, selectTokens } from '../utils/propTypes';
|
|
9
9
|
import { useMultipleInputValues } from '../utils/input';
|
|
10
|
+
import { getPressHandlersWithArgs } from '../utils/pressability';
|
|
10
11
|
|
|
11
12
|
const ButtonGroup = ({
|
|
12
13
|
variant,
|
|
13
|
-
buttonVariant = {},
|
|
14
14
|
tokens,
|
|
15
15
|
items = [],
|
|
16
16
|
values,
|
|
@@ -35,6 +35,7 @@ const ButtonGroup = ({
|
|
|
35
35
|
direction,
|
|
36
36
|
space
|
|
37
37
|
} = themeTokens;
|
|
38
|
+
const getButtonTokens = useThemeTokensCallback('ButtonGroupItem', tokens, variant);
|
|
38
39
|
const {
|
|
39
40
|
currentValues,
|
|
40
41
|
toggleOneValue
|
|
@@ -59,18 +60,13 @@ const ButtonGroup = ({
|
|
|
59
60
|
id = label,
|
|
60
61
|
accessibilityLabel
|
|
61
62
|
}, index) => {
|
|
62
|
-
const isSelected = currentValues.includes(id); //
|
|
63
|
+
const isSelected = currentValues.includes(id); // Pass an object of relevant component state as first argument for any passed-in press handlers
|
|
63
64
|
|
|
64
|
-
const pressHandlers =
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
label,
|
|
70
|
-
currentValues
|
|
71
|
-
}, ...args);
|
|
72
|
-
}
|
|
73
|
-
})));
|
|
65
|
+
const pressHandlers = getPressHandlersWithArgs(rest, [{
|
|
66
|
+
id,
|
|
67
|
+
label,
|
|
68
|
+
currentValues
|
|
69
|
+
}]);
|
|
74
70
|
|
|
75
71
|
const handlePress = () => {
|
|
76
72
|
if (pressHandlers.onPress) pressHandlers.onPress();
|
|
@@ -83,15 +79,7 @@ const ButtonGroup = ({
|
|
|
83
79
|
},
|
|
84
80
|
accessibilityRole: itemA11yRole,
|
|
85
81
|
accessibilityLabel,
|
|
86
|
-
...
|
|
87
|
-
web: {
|
|
88
|
-
// accessibilityPosInSet etc exists in React Native Web main branch
|
|
89
|
-
// but not in a release compatible with Expo etc; just use `aria-*`
|
|
90
|
-
'aria-setsize': items.length,
|
|
91
|
-
'aria-posinset': index + 1
|
|
92
|
-
},
|
|
93
|
-
default: {}
|
|
94
|
-
})
|
|
82
|
+
...a11yProps.getPositionInSet(items.length, index)
|
|
95
83
|
}; // Ensure button is direct child of group as MacOS voiceover only applies "X of Y" to
|
|
96
84
|
// "radio" if it's a direct child of "radiogroup", even if aria-posinset etc exists
|
|
97
85
|
|
|
@@ -99,10 +87,7 @@ const ButtonGroup = ({
|
|
|
99
87
|
key: id
|
|
100
88
|
}, pressHandlers, {
|
|
101
89
|
onPress: handlePress,
|
|
102
|
-
|
|
103
|
-
component: 'ButtonGroup',
|
|
104
|
-
...buttonVariant
|
|
105
|
-
},
|
|
90
|
+
tokens: getButtonTokens,
|
|
106
91
|
selected: isSelected,
|
|
107
92
|
inactive: inactive
|
|
108
93
|
}, itemA11y), label);
|
|
@@ -113,7 +98,6 @@ ButtonGroup.propTypes = { ...a11yProps.propTypes,
|
|
|
113
98
|
...pressProps.propTypes,
|
|
114
99
|
tokens: getTokensPropType('ButtonGroup'),
|
|
115
100
|
variant: variantProp.propType,
|
|
116
|
-
buttonVariant: variantProp.propType,
|
|
117
101
|
|
|
118
102
|
/**
|
|
119
103
|
* The maximum number of items a user may select at once. Defaults to 1 and behaves
|
package/lib/Button/ButtonLink.js
CHANGED
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import ButtonBase from './ButtonBase';
|
|
3
3
|
import buttonPropTypes from './propTypes';
|
|
4
4
|
import { a11yProps, hrefAttrsProp, linkProps } from '../utils/propTypes';
|
|
5
|
+
import { useThemeTokensCallback } from '../ThemeProvider';
|
|
5
6
|
/**
|
|
6
7
|
* `ButtonLink` is a component with the semantics and behaviour of a link, but with the visual appearance of a button.
|
|
7
8
|
* ButtonLink is a block-level component and should not be used inline.
|
|
@@ -9,14 +10,18 @@ import { a11yProps, hrefAttrsProp, linkProps } from '../utils/propTypes';
|
|
|
9
10
|
|
|
10
11
|
const ButtonLink = ({
|
|
11
12
|
accessibilityRole = 'link',
|
|
13
|
+
tokens,
|
|
14
|
+
variant,
|
|
12
15
|
...props
|
|
13
16
|
}) => {
|
|
14
17
|
const {
|
|
15
18
|
hrefAttrs,
|
|
16
19
|
rest
|
|
17
20
|
} = hrefAttrsProp.bundle(props);
|
|
21
|
+
const getTokens = useThemeTokensCallback('Button', tokens, variant);
|
|
18
22
|
return /*#__PURE__*/React.createElement(ButtonBase, Object.assign({
|
|
19
23
|
accessibilityRole: accessibilityRole,
|
|
24
|
+
tokens: getTokens,
|
|
20
25
|
hrefAttrs: hrefAttrs
|
|
21
26
|
}, rest));
|
|
22
27
|
};
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
|
4
|
+
import CheckboxInput from './CheckboxInput'; // @todo move `LabelContent` outside of the `InputLabel` and fix
|
|
5
|
+
// the issue with the cursor not being pointer on Web
|
|
6
|
+
|
|
7
|
+
import CheckboxLabel from '../InputLabel/LabelContent';
|
|
8
|
+
import Feedback from '../Feedback';
|
|
9
|
+
import StackView from '../StackView';
|
|
10
|
+
import { applyShadowToken, applyTextStyles, useThemeTokensCallback } from '../ThemeProvider';
|
|
11
|
+
import { getTokensPropType, useInputValue, variantProp } from '../utils';
|
|
12
|
+
import { a11yProps } from '../utils/propTypes';
|
|
13
|
+
import useUniqueId from '../utils/useUniqueId';
|
|
14
|
+
|
|
15
|
+
const selectInputStyles = ({
|
|
16
|
+
iconBackgroundColor,
|
|
17
|
+
inputBorderColor,
|
|
18
|
+
inputBorderRadius,
|
|
19
|
+
inputBorderWidth,
|
|
20
|
+
inputBackgroundColor,
|
|
21
|
+
inputHeight,
|
|
22
|
+
inputOutlineColor,
|
|
23
|
+
inputOutlineWidth,
|
|
24
|
+
inputShadow,
|
|
25
|
+
inputWidth
|
|
26
|
+
}, isChecked) => ({
|
|
27
|
+
borderColor: inputBorderColor,
|
|
28
|
+
borderWidth: inputBorderWidth,
|
|
29
|
+
borderRadius: inputBorderRadius,
|
|
30
|
+
backgroundColor: isChecked ? iconBackgroundColor : inputBackgroundColor,
|
|
31
|
+
outlineStyle: 'solid',
|
|
32
|
+
outlineColor: inputOutlineColor,
|
|
33
|
+
outlineWidth: inputOutlineWidth,
|
|
34
|
+
height: inputHeight,
|
|
35
|
+
width: inputWidth,
|
|
36
|
+
...applyShadowToken(inputShadow)
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const selectLabelStyles = ({
|
|
40
|
+
labelColor,
|
|
41
|
+
labelFontName,
|
|
42
|
+
labelFontSize,
|
|
43
|
+
labelFontWeight,
|
|
44
|
+
labelMarginLeft,
|
|
45
|
+
labelLineHeight
|
|
46
|
+
}) => ({
|
|
47
|
+
marginLeft: labelMarginLeft,
|
|
48
|
+
...applyTextStyles({
|
|
49
|
+
color: labelColor,
|
|
50
|
+
fontName: labelFontName,
|
|
51
|
+
fontWeight: labelFontWeight,
|
|
52
|
+
fontSize: labelFontSize,
|
|
53
|
+
lineHeight: labelLineHeight
|
|
54
|
+
})
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const selectContainerStyles = ({
|
|
58
|
+
containerMarginBottom
|
|
59
|
+
}) => ({
|
|
60
|
+
marginBottom: containerMarginBottom
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const selectIconTokens = ({
|
|
64
|
+
iconColor,
|
|
65
|
+
iconSize
|
|
66
|
+
}) => ({
|
|
67
|
+
color: iconColor,
|
|
68
|
+
size: iconSize
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const selectFeedbackTokens = ({
|
|
72
|
+
feedbackMarginBottom,
|
|
73
|
+
feedbackMarginTop,
|
|
74
|
+
feedbackPosition
|
|
75
|
+
}) => ({
|
|
76
|
+
feedbackPosition,
|
|
77
|
+
feedbackMarginBottom,
|
|
78
|
+
feedbackMarginTop
|
|
79
|
+
});
|
|
80
|
+
/**
|
|
81
|
+
* Basic Checkbox component.
|
|
82
|
+
*
|
|
83
|
+
* ## Component API
|
|
84
|
+
*
|
|
85
|
+
* Use `label` prop to provide a label for the checkbox.
|
|
86
|
+
* For a disabled `Checkbox` set the `inactive` prop to `true`.
|
|
87
|
+
*
|
|
88
|
+
* ### Controlled version
|
|
89
|
+
*
|
|
90
|
+
* If the checkbox is controlled from outside, it needs to receive `checked` and `onChange` props.
|
|
91
|
+
*
|
|
92
|
+
* ### Uncontrolled version
|
|
93
|
+
*
|
|
94
|
+
* In case of uncontrolled checkbox you can use `defaultChecked` prop to provide the initial value.
|
|
95
|
+
* Whenever the checkbox gets toggled, it calls the `onChange` callback with the new value (boolean).
|
|
96
|
+
*
|
|
97
|
+
* ### Using within forms
|
|
98
|
+
*
|
|
99
|
+
* You can pass `name` and `value` props if you need a particular checkbox to be a part of a group of
|
|
100
|
+
* checkboxes on a form.
|
|
101
|
+
*
|
|
102
|
+
* ### Validation and feedback
|
|
103
|
+
*
|
|
104
|
+
* You can use the `feedback` prop to provide a comment related to the checkbox element. If the comment
|
|
105
|
+
* is related to a validation error, the checkbox and the feedback can be styled accordingly by setting
|
|
106
|
+
* the `error` prop to `true` (can also be done without feedback).
|
|
107
|
+
*
|
|
108
|
+
* ## A11y guidelines
|
|
109
|
+
*
|
|
110
|
+
* Checkbox component accepts all the common accessibility props, but also sets some defaults, including
|
|
111
|
+
* accessibility role `'checkbox'` and accessibility state that depends on the other props (`checked`, `inactive`)
|
|
112
|
+
* or the internal state in case of uncontrolled checkbox.
|
|
113
|
+
*
|
|
114
|
+
*/
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
const Checkbox = ({
|
|
118
|
+
checked,
|
|
119
|
+
defaultChecked,
|
|
120
|
+
error = false,
|
|
121
|
+
feedback,
|
|
122
|
+
id,
|
|
123
|
+
inactive,
|
|
124
|
+
label,
|
|
125
|
+
name,
|
|
126
|
+
onChange,
|
|
127
|
+
tokens,
|
|
128
|
+
value,
|
|
129
|
+
variant,
|
|
130
|
+
...rest
|
|
131
|
+
}) => {
|
|
132
|
+
const {
|
|
133
|
+
currentValue: isChecked,
|
|
134
|
+
setValue: setIsChecked,
|
|
135
|
+
isControlled
|
|
136
|
+
} = useInputValue({
|
|
137
|
+
value: checked,
|
|
138
|
+
initialValue: defaultChecked,
|
|
139
|
+
onChange
|
|
140
|
+
}, 'useCheckboxValue');
|
|
141
|
+
const getTokens = useThemeTokensCallback('Checkbox', tokens, {
|
|
142
|
+
checked: isChecked,
|
|
143
|
+
inactive,
|
|
144
|
+
error,
|
|
145
|
+
...variant
|
|
146
|
+
});
|
|
147
|
+
const defaultTokens = getTokens();
|
|
148
|
+
const {
|
|
149
|
+
feedbackMarginBottom,
|
|
150
|
+
feedbackMarginTop,
|
|
151
|
+
feedbackPosition
|
|
152
|
+
} = selectFeedbackTokens(defaultTokens);
|
|
153
|
+
const styles = StyleSheet.create({
|
|
154
|
+
feedbackContainer: {
|
|
155
|
+
marginTop: feedbackMarginTop,
|
|
156
|
+
marginBottom: feedbackMarginBottom
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const handleChange = () => {
|
|
161
|
+
if (!inactive) {
|
|
162
|
+
setIsChecked(!isChecked);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const accessibilityProps = a11yProps.select(rest);
|
|
167
|
+
const uniqueId = useUniqueId('checkbox');
|
|
168
|
+
const inputId = id ?? uniqueId; // @todo our current version of React Native Web doesn't include
|
|
169
|
+
// keyboard support on `Pressable` (which starts with 0.15.3), so
|
|
170
|
+
// the complete accessibility of the `Checkbox` component on Web
|
|
171
|
+
// (namely, change on key pressed) is pending RNW upgrade
|
|
172
|
+
// (see https://github.com/necolas/react-native-web/issues/1950)
|
|
173
|
+
|
|
174
|
+
return /*#__PURE__*/React.createElement(View, {
|
|
175
|
+
style: staticStyles.wrapper
|
|
176
|
+
}, /*#__PURE__*/React.createElement(StackView, {
|
|
177
|
+
direction: feedbackPosition === 'top' ? 'column-reverse' : 'column',
|
|
178
|
+
space: 0
|
|
179
|
+
}, /*#__PURE__*/React.createElement(Pressable, Object.assign({
|
|
180
|
+
disabled: inactive,
|
|
181
|
+
onPress: handleChange,
|
|
182
|
+
accessibilityRole: "checkbox",
|
|
183
|
+
accessibilityState: {
|
|
184
|
+
checked: isChecked,
|
|
185
|
+
disabled: inactive
|
|
186
|
+
}
|
|
187
|
+
}, accessibilityProps), ({
|
|
188
|
+
focused: focus,
|
|
189
|
+
hovered: hover,
|
|
190
|
+
pressed
|
|
191
|
+
}) => {
|
|
192
|
+
const {
|
|
193
|
+
icon: IconComponent,
|
|
194
|
+
...stateTokens
|
|
195
|
+
} = getTokens({
|
|
196
|
+
focus,
|
|
197
|
+
hover,
|
|
198
|
+
pressed
|
|
199
|
+
});
|
|
200
|
+
const iconTokens = selectIconTokens(stateTokens);
|
|
201
|
+
return /*#__PURE__*/React.createElement(View, {
|
|
202
|
+
style: [staticStyles.container, selectContainerStyles(stateTokens)]
|
|
203
|
+
}, /*#__PURE__*/React.createElement(View, {
|
|
204
|
+
style: StyleSheet.flatten([staticStyles.defaultInputStyles, selectInputStyles(stateTokens, isChecked)]),
|
|
205
|
+
testID: "Checkbox-Input"
|
|
206
|
+
}, /*#__PURE__*/React.createElement(CheckboxInput, {
|
|
207
|
+
checked: isChecked,
|
|
208
|
+
defaultChecked: defaultChecked,
|
|
209
|
+
disabled: inactive,
|
|
210
|
+
id: inputId,
|
|
211
|
+
isControlled: isControlled,
|
|
212
|
+
name: name,
|
|
213
|
+
value: value
|
|
214
|
+
}), isChecked && IconComponent && /*#__PURE__*/React.createElement(IconComponent, {
|
|
215
|
+
tokens: iconTokens,
|
|
216
|
+
testID: "Checkbox-Icon"
|
|
217
|
+
})), Boolean(label) && /*#__PURE__*/React.createElement(Text, {
|
|
218
|
+
style: selectLabelStyles(stateTokens)
|
|
219
|
+
}, /*#__PURE__*/React.createElement(CheckboxLabel, {
|
|
220
|
+
forId: inputId
|
|
221
|
+
}, label)));
|
|
222
|
+
}), Boolean(feedback) && /*#__PURE__*/React.createElement(View, {
|
|
223
|
+
style: styles.feedbackContainer
|
|
224
|
+
}, /*#__PURE__*/React.createElement(Feedback, {
|
|
225
|
+
title: feedback,
|
|
226
|
+
variant: {
|
|
227
|
+
icon: error === true
|
|
228
|
+
},
|
|
229
|
+
validation: error === true ? 'error' : undefined
|
|
230
|
+
}))));
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
Checkbox.propTypes = { ...a11yProps.propTypes,
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Use `checked` for controlled Checkbox. For uncontrolled Checkbox, use the `defaultChecked` prop.
|
|
237
|
+
*/
|
|
238
|
+
checked: PropTypes.bool,
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Use `defaultChecked` to provide the initial value for an uncontrolled Checkbox.
|
|
242
|
+
*/
|
|
243
|
+
defaultChecked: PropTypes.bool,
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* A detailed description of related validation error or additional instructions (depending on the `error` prop).
|
|
247
|
+
*/
|
|
248
|
+
feedback: PropTypes.string,
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Checkbox ID.
|
|
252
|
+
*/
|
|
253
|
+
id: PropTypes.string,
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Whether the corresponding input is disabled or active.
|
|
257
|
+
*/
|
|
258
|
+
inactive: PropTypes.bool,
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* The label.
|
|
262
|
+
*/
|
|
263
|
+
label: PropTypes.string,
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Associate this checkbox with a group (set as the name attribute).
|
|
267
|
+
*/
|
|
268
|
+
name: PropTypes.string,
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Whether the underlying input triggered a validation error or not.
|
|
272
|
+
*/
|
|
273
|
+
error: PropTypes.bool,
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* The value. Must be unique within the group.
|
|
277
|
+
*/
|
|
278
|
+
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Callback called when a controlled checkbox gets interacted with.
|
|
282
|
+
*/
|
|
283
|
+
onChange: PropTypes.func,
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Checkbox tokens.
|
|
287
|
+
*/
|
|
288
|
+
tokens: getTokensPropType('Checkbox'),
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Checkbox variant.
|
|
292
|
+
*/
|
|
293
|
+
variant: variantProp.propType
|
|
294
|
+
};
|
|
295
|
+
export default Checkbox;
|
|
296
|
+
const staticStyles = StyleSheet.create({
|
|
297
|
+
wrapper: {
|
|
298
|
+
backgroundColor: 'transparent'
|
|
299
|
+
},
|
|
300
|
+
container: {
|
|
301
|
+
flexDirection: 'row',
|
|
302
|
+
alignItems: 'center'
|
|
303
|
+
},
|
|
304
|
+
defaultInputStyles: {
|
|
305
|
+
alignItems: 'center',
|
|
306
|
+
justifyContent: 'center'
|
|
307
|
+
}
|
|
308
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
/**
|
|
4
|
+
* On Web we need to include an actual input but hide it.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const CheckboxInput = /*#__PURE__*/forwardRef(({
|
|
8
|
+
checked,
|
|
9
|
+
defaultChecked,
|
|
10
|
+
disabled,
|
|
11
|
+
id,
|
|
12
|
+
isControlled,
|
|
13
|
+
name,
|
|
14
|
+
onChange,
|
|
15
|
+
value
|
|
16
|
+
}, ref) => {
|
|
17
|
+
const handleClick = event => {
|
|
18
|
+
// Cancel the click dispatched via the label tag, since it's already wrapped
|
|
19
|
+
// in <Pressable>
|
|
20
|
+
event.preventDefault();
|
|
21
|
+
event.stopPropagation();
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return /*#__PURE__*/React.createElement("input", {
|
|
25
|
+
checked: isControlled ? checked : undefined,
|
|
26
|
+
defaultChecked: isControlled ? undefined : defaultChecked,
|
|
27
|
+
disabled: disabled,
|
|
28
|
+
hidden: true,
|
|
29
|
+
id: id,
|
|
30
|
+
name: name,
|
|
31
|
+
onChange: onChange,
|
|
32
|
+
onClick: handleClick,
|
|
33
|
+
ref: ref,
|
|
34
|
+
type: "checkbox",
|
|
35
|
+
value: value
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
CheckboxInput.displayName = 'CheckboxInput';
|
|
39
|
+
CheckboxInput.propTypes = {
|
|
40
|
+
checked: PropTypes.bool,
|
|
41
|
+
defaultChecked: PropTypes.bool,
|
|
42
|
+
disabled: PropTypes.bool,
|
|
43
|
+
id: PropTypes.string.isRequired,
|
|
44
|
+
isControlled: PropTypes.bool.isRequired,
|
|
45
|
+
name: PropTypes.string,
|
|
46
|
+
onChange: PropTypes.func,
|
|
47
|
+
value: PropTypes.string
|
|
48
|
+
};
|
|
49
|
+
CheckboxInput.defaultProps = {
|
|
50
|
+
checked: undefined,
|
|
51
|
+
defaultChecked: undefined,
|
|
52
|
+
disabled: false,
|
|
53
|
+
name: undefined,
|
|
54
|
+
onChange: () => {},
|
|
55
|
+
value: undefined
|
|
56
|
+
};
|
|
57
|
+
export default CheckboxInput;
|