@telus-uds/components-base 1.18.1 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -2
- package/__tests17__/ThemeProvider/ThemeProvider.test.jsx +2 -1
- package/component-docs.json +1035 -231
- package/jest.config-android.js +17 -0
- package/jest.config-ios.js +18 -0
- package/jest.config-web.js +31 -0
- package/lib/BaseProvider/index.js +2 -1
- package/lib/Box/Box.js +14 -1
- package/lib/Button/ButtonBase.js +6 -2
- package/lib/Button/ButtonDropdown.js +207 -0
- package/lib/Button/index.js +8 -0
- package/lib/Carousel/Carousel.js +34 -6
- package/lib/Carousel/CarouselItem/CarouselItem.js +7 -1
- package/lib/Carousel/CarouselTabs/CarouselTabsPanel.js +22 -14
- package/lib/FlexGrid/Col/Col.js +1 -3
- package/lib/FlexGrid/FlexGrid.js +3 -5
- package/lib/FlexGrid/Row/Row.js +3 -3
- package/lib/IconButton/IconButton.js +12 -4
- package/lib/MultiSelectFilter/MultiSelectFilter.js +276 -0
- package/lib/MultiSelectFilter/dictionary.js +19 -0
- package/lib/MultiSelectFilter/index.js +13 -0
- package/lib/Pagination/SideButton.js +6 -4
- package/lib/Responsive/Responsive.js +58 -0
- package/lib/Responsive/index.js +13 -0
- package/lib/Search/Search.js +33 -63
- package/lib/Select/Picker.native.js +16 -13
- package/lib/Select/Select.js +7 -1
- package/lib/Select/constants.js +15 -0
- package/lib/StepTracker/Step.js +2 -1
- package/lib/Tags/Tags.js +10 -4
- package/lib/TextInput/TextInput.js +9 -2
- package/lib/TextInput/TextInputBase.js +98 -20
- package/lib/TextInput/dictionary.js +15 -0
- package/lib/ThemeProvider/ThemeProvider.js +6 -1
- package/lib/index.js +18 -0
- package/lib/utils/BaseView/BaseView.js +64 -0
- package/lib/utils/BaseView/BaseView.native.js +16 -0
- package/lib/utils/BaseView/index.js +13 -0
- package/lib/utils/index.js +10 -1
- package/lib/utils/input.js +11 -3
- package/lib/utils/props/handlerProps.js +5 -0
- package/lib-module/BaseProvider/index.js +2 -1
- package/lib-module/Box/Box.js +14 -1
- package/lib-module/Button/ButtonBase.js +6 -2
- package/lib-module/Button/ButtonDropdown.js +181 -0
- package/lib-module/Button/index.js +2 -1
- package/lib-module/Carousel/Carousel.js +34 -6
- package/lib-module/Carousel/CarouselItem/CarouselItem.js +8 -2
- package/lib-module/Carousel/CarouselTabs/CarouselTabsPanel.js +24 -16
- package/lib-module/FlexGrid/Col/Col.js +2 -3
- package/lib-module/FlexGrid/FlexGrid.js +2 -3
- package/lib-module/FlexGrid/Row/Row.js +2 -2
- package/lib-module/IconButton/IconButton.js +14 -4
- package/lib-module/MultiSelectFilter/MultiSelectFilter.js +248 -0
- package/lib-module/MultiSelectFilter/dictionary.js +12 -0
- package/lib-module/MultiSelectFilter/index.js +2 -0
- package/lib-module/Pagination/SideButton.js +6 -4
- package/lib-module/Responsive/Responsive.js +45 -0
- package/lib-module/Responsive/index.js +2 -0
- package/lib-module/Search/Search.js +33 -61
- package/lib-module/Select/Picker.native.js +15 -13
- package/lib-module/Select/Select.js +6 -1
- package/lib-module/Select/constants.js +5 -0
- package/lib-module/StepTracker/Step.js +2 -1
- package/lib-module/Tags/Tags.js +10 -4
- package/lib-module/TextInput/TextInput.js +6 -0
- package/lib-module/TextInput/TextInputBase.js +96 -21
- package/lib-module/TextInput/dictionary.js +8 -0
- package/lib-module/ThemeProvider/ThemeProvider.js +6 -1
- package/lib-module/index.js +2 -0
- package/lib-module/utils/BaseView/BaseView.js +43 -0
- package/lib-module/utils/BaseView/BaseView.native.js +6 -0
- package/lib-module/utils/BaseView/index.js +2 -0
- package/lib-module/utils/index.js +2 -1
- package/lib-module/utils/input.js +11 -3
- package/lib-module/utils/props/handlerProps.js +5 -0
- package/package.json +6 -3
- package/src/BaseProvider/index.jsx +4 -1
- package/src/Box/Box.jsx +14 -1
- package/src/Button/ButtonBase.jsx +4 -2
- package/src/Button/ButtonDropdown.jsx +179 -0
- package/src/Button/index.js +2 -1
- package/src/Carousel/Carousel.jsx +48 -13
- package/src/Carousel/CarouselItem/CarouselItem.jsx +9 -2
- package/src/Carousel/CarouselTabs/CarouselTabsPanel.jsx +19 -15
- package/src/FlexGrid/Col/Col.jsx +4 -4
- package/src/FlexGrid/FlexGrid.jsx +11 -10
- package/src/FlexGrid/Row/Row.jsx +4 -3
- package/src/IconButton/IconButton.jsx +3 -1
- package/src/MultiSelectFilter/MultiSelectFilter.jsx +227 -0
- package/src/MultiSelectFilter/dictionary.js +12 -0
- package/src/MultiSelectFilter/index.js +3 -0
- package/src/Pagination/SideButton.jsx +5 -5
- package/src/Responsive/Responsive.jsx +33 -0
- package/src/Responsive/index.js +3 -0
- package/src/Search/Search.jsx +19 -33
- package/src/Select/Picker.native.jsx +29 -14
- package/src/Select/Select.jsx +7 -1
- package/src/Select/constants.js +5 -0
- package/src/StepTracker/Step.jsx +5 -1
- package/src/Tags/Tags.jsx +46 -33
- package/src/TextInput/TextInput.jsx +5 -0
- package/src/TextInput/TextInputBase.jsx +85 -20
- package/src/TextInput/dictionary.js +8 -0
- package/src/ThemeProvider/ThemeProvider.jsx +5 -1
- package/src/index.js +2 -0
- package/src/utils/BaseView/BaseView.jsx +38 -0
- package/src/utils/BaseView/BaseView.native.jsx +6 -0
- package/src/utils/BaseView/index.js +3 -0
- package/src/utils/index.js +1 -0
- package/src/utils/input.js +9 -4
- package/src/utils/props/handlerProps.js +4 -0
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import React, { forwardRef, useEffect, useState } from 'react';
|
|
1
|
+
import React, { forwardRef, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
2
3
|
import Platform from "react-native-web/dist/exports/Platform";
|
|
3
4
|
import StyleSheet from "react-native-web/dist/exports/StyleSheet";
|
|
4
5
|
import NativeTextInput from "react-native-web/dist/exports/TextInput";
|
|
5
6
|
import View from "react-native-web/dist/exports/View";
|
|
6
|
-
import PropTypes from 'prop-types';
|
|
7
7
|
import { applyTextStyles, useTheme, useThemeTokens, applyOuterBorder } from '../ThemeProvider';
|
|
8
|
-
import
|
|
8
|
+
import StackView from '../StackView';
|
|
9
|
+
import IconButton from '../IconButton';
|
|
10
|
+
import { a11yProps, getTokensPropType, selectSystemProps, textInputHandlerProps, textInputProps, useCopy, useInputValue, useSpacingScale, variantProp, viewProps } from '../utils';
|
|
11
|
+
import dictionary from './dictionary';
|
|
9
12
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
10
13
|
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
14
|
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, textInputHandlerProps, textInputProps, viewProps]);
|
|
@@ -110,35 +113,49 @@ const selectIconTokens = _ref3 => {
|
|
|
110
113
|
};
|
|
111
114
|
};
|
|
112
115
|
|
|
113
|
-
const selectIconContainerStyles = _ref4 => {
|
|
116
|
+
const selectIconContainerStyles = (_ref4, buttonCount) => {
|
|
114
117
|
let {
|
|
118
|
+
buttonSize,
|
|
119
|
+
buttonsGapSize,
|
|
115
120
|
paddingRight,
|
|
116
121
|
paddingBottom
|
|
117
122
|
} = _ref4;
|
|
118
123
|
return {
|
|
119
|
-
paddingRight,
|
|
124
|
+
paddingRight: paddingRight + buttonCount * (buttonSize + buttonsGapSize),
|
|
120
125
|
paddingBottom
|
|
121
126
|
};
|
|
122
127
|
};
|
|
123
128
|
|
|
124
|
-
const
|
|
129
|
+
const selectButtonsContainerStyle = _ref5 => {
|
|
125
130
|
let {
|
|
126
|
-
|
|
131
|
+
buttonsPaddingRight
|
|
132
|
+
} = _ref5;
|
|
133
|
+
return {
|
|
134
|
+
paddingRight: buttonsPaddingRight
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const TextInputBase = /*#__PURE__*/forwardRef((_ref6, ref) => {
|
|
139
|
+
let {
|
|
140
|
+
buttons = [],
|
|
141
|
+
copy = 'en',
|
|
127
142
|
height,
|
|
128
|
-
initialValue,
|
|
129
143
|
inactive,
|
|
130
|
-
|
|
144
|
+
initialValue,
|
|
145
|
+
onBlur,
|
|
131
146
|
onChange,
|
|
132
147
|
onChangeText,
|
|
148
|
+
onClear,
|
|
133
149
|
onFocus,
|
|
134
|
-
onBlur,
|
|
135
|
-
onMouseOver,
|
|
136
150
|
onMouseOut,
|
|
151
|
+
onMouseOver,
|
|
137
152
|
pattern,
|
|
153
|
+
readOnly,
|
|
138
154
|
tokens,
|
|
155
|
+
value,
|
|
139
156
|
variant = {},
|
|
140
157
|
...rest
|
|
141
|
-
} =
|
|
158
|
+
} = _ref6;
|
|
142
159
|
const [isFocused, setIsFocused] = useState(false);
|
|
143
160
|
|
|
144
161
|
const handleFocus = event => {
|
|
@@ -163,17 +180,22 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref5, ref) => {
|
|
|
163
180
|
if (typeof onMouseOut === 'function') onMouseOut(event);
|
|
164
181
|
};
|
|
165
182
|
|
|
183
|
+
const defaultRef = useRef();
|
|
184
|
+
const inputRef = ref !== null && ref !== void 0 ? ref : defaultRef;
|
|
166
185
|
const {
|
|
167
186
|
currentValue,
|
|
187
|
+
resetValue,
|
|
168
188
|
setValue,
|
|
169
|
-
isControlled
|
|
189
|
+
isControlled,
|
|
190
|
+
isDirty
|
|
170
191
|
} = useInputValue({
|
|
171
192
|
value,
|
|
172
193
|
initialValue,
|
|
194
|
+
inputRef,
|
|
173
195
|
onChange,
|
|
174
196
|
readOnly
|
|
175
197
|
});
|
|
176
|
-
const element =
|
|
198
|
+
const element = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current;
|
|
177
199
|
useEffect(() => {
|
|
178
200
|
if (Platform.OS === 'web' && pattern && element) {
|
|
179
201
|
// React Native Web doesn't support `pattern`, so we have to attach it via a ref,
|
|
@@ -197,8 +219,35 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref5, ref) => {
|
|
|
197
219
|
};
|
|
198
220
|
const themeTokens = useThemeTokens('TextInput', tokens, variant, states);
|
|
199
221
|
const {
|
|
222
|
+
buttonsGap,
|
|
223
|
+
clearButtonIcon: ClearButtonIcon,
|
|
200
224
|
icon: IconComponent
|
|
201
225
|
} = themeTokens;
|
|
226
|
+
const buttonsGapSize = useSpacingScale(buttonsGap);
|
|
227
|
+
const getCopy = useCopy({
|
|
228
|
+
dictionary,
|
|
229
|
+
copy
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
if (onClear && isDirty) {
|
|
233
|
+
const handleClear = event => {
|
|
234
|
+
var _inputRef$current;
|
|
235
|
+
|
|
236
|
+
onClear === null || onClear === void 0 ? void 0 : onClear(event);
|
|
237
|
+
resetValue(event);
|
|
238
|
+
inputRef === null || inputRef === void 0 ? void 0 : (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
buttons === null || buttons === void 0 ? void 0 : buttons.unshift( /*#__PURE__*/_jsx(IconButton, {
|
|
242
|
+
accessibilityLabel: getCopy('clearButtonAccessibilityLabel'),
|
|
243
|
+
icon: ClearButtonIcon,
|
|
244
|
+
onPress: handleClear,
|
|
245
|
+
variant: {
|
|
246
|
+
compact: true
|
|
247
|
+
}
|
|
248
|
+
}, "clear"));
|
|
249
|
+
}
|
|
250
|
+
|
|
202
251
|
const inputProps = { ...selectProps(rest),
|
|
203
252
|
editable: !inactive,
|
|
204
253
|
onFocus: handleFocus,
|
|
@@ -220,36 +269,62 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref5, ref) => {
|
|
|
220
269
|
return /*#__PURE__*/_jsxs(View, {
|
|
221
270
|
style: selectOuterBorderStyles(themeTokens),
|
|
222
271
|
children: [/*#__PURE__*/_jsx(NativeTextInput, {
|
|
223
|
-
ref:
|
|
272
|
+
ref: inputRef,
|
|
224
273
|
style: nativeInputStyle,
|
|
225
274
|
...inputProps
|
|
226
275
|
}), IconComponent && /*#__PURE__*/_jsx(View, {
|
|
227
276
|
pointerEvents: "none" // avoid hijacking input press events
|
|
228
277
|
,
|
|
229
|
-
style: [staticStyles.iconContainer, selectIconContainerStyles(themeTokens
|
|
278
|
+
style: [staticStyles.iconContainer, selectIconContainerStyles({ ...themeTokens,
|
|
279
|
+
buttonsGapSize
|
|
280
|
+
}, buttons === null || buttons === void 0 ? void 0 : buttons.length)],
|
|
230
281
|
children: /*#__PURE__*/_jsx(IconComponent, { ...selectIconTokens(themeTokens)
|
|
231
282
|
})
|
|
283
|
+
}), (buttons === null || buttons === void 0 ? void 0 : buttons.length) > 0 && /*#__PURE__*/_jsx(View, {
|
|
284
|
+
style: [staticStyles.buttonsContainer, selectButtonsContainerStyle(themeTokens)],
|
|
285
|
+
children: /*#__PURE__*/_jsx(StackView, {
|
|
286
|
+
direction: "row",
|
|
287
|
+
space: buttonsGap,
|
|
288
|
+
children: buttons
|
|
289
|
+
})
|
|
232
290
|
})]
|
|
233
291
|
});
|
|
234
292
|
});
|
|
235
293
|
TextInputBase.displayName = 'TextInputBase';
|
|
236
294
|
TextInputBase.propTypes = { ...selectedSystemPropTypes,
|
|
237
|
-
|
|
295
|
+
buttons: PropTypes.arrayOf(PropTypes.node),
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Select English or French copy for the accessible labels.
|
|
299
|
+
* You may also pass in a custom dictionary object.
|
|
300
|
+
*/
|
|
301
|
+
copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr']), PropTypes.shape({
|
|
302
|
+
clearButtonAccessibilityLabel: PropTypes.string
|
|
303
|
+
})]),
|
|
238
304
|
height: PropTypes.number,
|
|
239
|
-
initialValue: PropTypes.string,
|
|
240
305
|
inactive: PropTypes.bool,
|
|
241
|
-
|
|
306
|
+
initialValue: PropTypes.string,
|
|
307
|
+
onBlur: PropTypes.func,
|
|
242
308
|
onChange: PropTypes.func,
|
|
243
309
|
onChangeText: PropTypes.func,
|
|
310
|
+
onClear: PropTypes.func,
|
|
244
311
|
onFocus: PropTypes.func,
|
|
245
|
-
onBlur: PropTypes.func,
|
|
246
|
-
onMouseOver: PropTypes.func,
|
|
247
312
|
onMouseOut: PropTypes.func,
|
|
313
|
+
onMouseOver: PropTypes.func,
|
|
314
|
+
readOnly: PropTypes.bool,
|
|
248
315
|
tokens: getTokensPropType('TextInput', 'TextArea'),
|
|
316
|
+
value: PropTypes.string,
|
|
249
317
|
variant: variantProp.propType
|
|
250
318
|
};
|
|
251
319
|
export default TextInputBase;
|
|
252
320
|
const staticStyles = StyleSheet.create({
|
|
321
|
+
buttonsContainer: {
|
|
322
|
+
position: 'absolute',
|
|
323
|
+
right: 0,
|
|
324
|
+
top: 0,
|
|
325
|
+
bottom: 0,
|
|
326
|
+
justifyContent: 'center'
|
|
327
|
+
},
|
|
253
328
|
iconContainer: {
|
|
254
329
|
position: 'absolute',
|
|
255
330
|
right: 0,
|
|
@@ -12,8 +12,10 @@ const ThemeProvider = _ref => {
|
|
|
12
12
|
children,
|
|
13
13
|
defaultTheme,
|
|
14
14
|
// TODO: switch `forceAbsoluteFontSizing` to be false by default in the next major version
|
|
15
|
+
// TODO: switch `forceZIndex` to be false by default in the next major version
|
|
15
16
|
themeOptions = {
|
|
16
|
-
forceAbsoluteFontSizing: true
|
|
17
|
+
forceAbsoluteFontSizing: true,
|
|
18
|
+
forceZIndex: true
|
|
17
19
|
}
|
|
18
20
|
} = _ref;
|
|
19
21
|
const [theme, setTheme] = useState(defaultTheme); // Validate the theme tokens version on every render.
|
|
@@ -48,9 +50,12 @@ ThemeProvider.propTypes = {
|
|
|
48
50
|
* relative sizing (in `rem`, scales depending on the browser settings)
|
|
49
51
|
* - `contentMaxWidth`: allows configuration of the content max width to be used in components
|
|
50
52
|
* such as Footnote and Notification to avoid content to stretch width more then the page's width
|
|
53
|
+
* - `forceZIndex`: available on web only, when set to false, sets zIndex on `View` to be `auto`
|
|
54
|
+
* and when true, sets zIndex to be `0` (the default from `react-native-web`)
|
|
51
55
|
*/
|
|
52
56
|
themeOptions: PropTypes.shape({
|
|
53
57
|
forceAbsoluteFontSizing: PropTypes.bool,
|
|
58
|
+
forceZIndex: PropTypes.bool,
|
|
54
59
|
contentMaxWidth: responsiveProps.getTypeOptionallyByViewport(PropTypes.number)
|
|
55
60
|
})
|
|
56
61
|
};
|
package/lib-module/index.js
CHANGED
|
@@ -21,6 +21,7 @@ export { default as InputSupports } from './InputSupports';
|
|
|
21
21
|
export * from './Link';
|
|
22
22
|
export { default as List, ListItem, ListBase } from './List';
|
|
23
23
|
export { default as Modal } from './Modal';
|
|
24
|
+
export { default as MultiSelectFilter } from './MultiSelectFilter';
|
|
24
25
|
export { default as Notification } from './Notification';
|
|
25
26
|
export { default as Pagination } from './Pagination';
|
|
26
27
|
export { default as Progress } from './Progress';
|
|
@@ -29,6 +30,7 @@ export { default as Radio } from './Radio';
|
|
|
29
30
|
export * from './Radio';
|
|
30
31
|
export { default as RadioCard } from './RadioCard';
|
|
31
32
|
export * from './RadioCard';
|
|
33
|
+
export { default as Responsive } from './Responsive';
|
|
32
34
|
export { default as Search } from './Search';
|
|
33
35
|
export { default as Select } from './Select';
|
|
34
36
|
export { default as SideNav } from './SideNav';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
|
+
import NativeView from "react-native-web/dist/exports/View";
|
|
3
|
+
import StyleSheet from "react-native-web/dist/exports/StyleSheet";
|
|
4
|
+
import PropTypes from 'prop-types';
|
|
5
|
+
import { useTheme } from '../../ThemeProvider';
|
|
6
|
+
/**
|
|
7
|
+
* Identical to React Native's View and supporting all the same props, but with:
|
|
8
|
+
* - a zIndex: 'auto' style added to prevent unexpectedly causing children to overlap other elements from other stacking contexts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
12
|
+
const BaseView = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
13
|
+
let {
|
|
14
|
+
children,
|
|
15
|
+
style,
|
|
16
|
+
...rest
|
|
17
|
+
} = _ref;
|
|
18
|
+
const {
|
|
19
|
+
themeOptions
|
|
20
|
+
} = useTheme();
|
|
21
|
+
const styleProp = Array.isArray(style) ? [...style] : [style];
|
|
22
|
+
|
|
23
|
+
if (!themeOptions.forceZIndex) {
|
|
24
|
+
styleProp.unshift(styles.resetZIndex);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return /*#__PURE__*/_jsx(NativeView, { ...rest,
|
|
28
|
+
style: styleProp,
|
|
29
|
+
ref: ref,
|
|
30
|
+
children: children
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
BaseView.displayName = 'BaseView';
|
|
34
|
+
const styles = StyleSheet.create({
|
|
35
|
+
resetZIndex: {
|
|
36
|
+
zIndex: 'auto'
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
BaseView.propTypes = {
|
|
40
|
+
children: PropTypes.node,
|
|
41
|
+
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array])
|
|
42
|
+
};
|
|
43
|
+
export default BaseView;
|
|
@@ -15,4 +15,5 @@ export * from './useResponsiveProp';
|
|
|
15
15
|
export { default as useUniqueId } from './useUniqueId';
|
|
16
16
|
export { default as withLinkRouter } from './withLinkRouter';
|
|
17
17
|
export * from './ssr';
|
|
18
|
-
export { default as containUniqueFields } from './containUniqueFields';
|
|
18
|
+
export { default as containUniqueFields } from './containUniqueFields';
|
|
19
|
+
export { default as BaseView } from './BaseView';
|
|
@@ -77,6 +77,7 @@ export const useInputValue = function () {
|
|
|
77
77
|
const {
|
|
78
78
|
value,
|
|
79
79
|
initialValue,
|
|
80
|
+
inputRef,
|
|
80
81
|
onChange,
|
|
81
82
|
readOnly = false
|
|
82
83
|
} = props;
|
|
@@ -90,19 +91,26 @@ export const useInputValue = function () {
|
|
|
90
91
|
}); // Make current value accessible inside useCallback without rememoizing every time the value changes
|
|
91
92
|
|
|
92
93
|
valueRef.current.value = currentValue;
|
|
94
|
+
const isDirty = currentValue !== valueRef.current.initial;
|
|
93
95
|
const setValue = useCallback((arg, event) => {
|
|
94
96
|
if (readOnly) return;
|
|
95
97
|
const newValue = typeof arg === 'function' ? arg(valueRef.current.value) : arg;
|
|
96
|
-
|
|
98
|
+
|
|
99
|
+
if (!isControlled) {
|
|
100
|
+
setOwnValue(newValue);
|
|
101
|
+
if (inputRef !== null && inputRef !== void 0 && inputRef.current) inputRef.current.value = newValue !== null && newValue !== void 0 ? newValue : '';
|
|
102
|
+
} // Call onChange handler if there's something for it to handle (event or a changed value)
|
|
103
|
+
|
|
97
104
|
|
|
98
105
|
if (onChange && (event || valueRef.current.value !== newValue)) onChange(newValue, event);
|
|
99
|
-
}, [isControlled, onChange, readOnly]);
|
|
106
|
+
}, [inputRef, isControlled, onChange, readOnly]);
|
|
100
107
|
const resetValue = useCallback(event => setValue(valueRef.current.initial, event), [setValue]);
|
|
101
108
|
return {
|
|
102
109
|
currentValue,
|
|
103
110
|
setValue,
|
|
104
111
|
resetValue,
|
|
105
|
-
isControlled
|
|
112
|
+
isControlled,
|
|
113
|
+
isDirty
|
|
106
114
|
};
|
|
107
115
|
};
|
|
108
116
|
/**
|
package/package.json
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@gorhom/portal": "^1.0.14",
|
|
11
|
-
"@telus-uds/system-constants": "^1.0
|
|
12
|
-
"@telus-uds/system-theme-tokens": "^2.
|
|
11
|
+
"@telus-uds/system-constants": "^1.2.0",
|
|
12
|
+
"@telus-uds/system-theme-tokens": "^2.8.0",
|
|
13
13
|
"airbnb-prop-types": "^2.16.0",
|
|
14
14
|
"lodash.debounce": "^4.0.8",
|
|
15
15
|
"lodash.merge": "^4.6.2",
|
|
@@ -54,6 +54,9 @@
|
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"test": "jest",
|
|
57
|
+
"test:web": "jest --config jest.config-web.js",
|
|
58
|
+
"test:ios": "jest --config jest.config-ios.js",
|
|
59
|
+
"test:android": "jest --config jest.config-android.js",
|
|
57
60
|
"lint": "npm run --prefix ../.. lint:path -- --color packages/components-base",
|
|
58
61
|
"lint:fix": "npm run --prefix ../.. lint:path -- --fix packages/components-base",
|
|
59
62
|
"format": "prettier --write .",
|
|
@@ -67,5 +70,5 @@
|
|
|
67
70
|
"standard-engine": {
|
|
68
71
|
"skip": true
|
|
69
72
|
},
|
|
70
|
-
"version": "1.
|
|
73
|
+
"version": "1.20.0"
|
|
71
74
|
}
|
|
@@ -21,7 +21,10 @@ const BaseProvider = ({ defaultTheme, children, themeOptions }) => (
|
|
|
21
21
|
BaseProvider.propTypes = {
|
|
22
22
|
children: PropTypes.node.isRequired,
|
|
23
23
|
defaultTheme: ThemeProvider.propTypes?.defaultTheme,
|
|
24
|
-
themeOptions: PropTypes.shape({
|
|
24
|
+
themeOptions: PropTypes.shape({
|
|
25
|
+
forceAbsoluteFontSizing: PropTypes.bool,
|
|
26
|
+
forceZIndex: PropTypes.bool
|
|
27
|
+
})
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
export default BaseProvider
|
package/src/Box/Box.jsx
CHANGED
|
@@ -24,7 +24,20 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, vie
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
const selectBoxStyles = (tokens) => {
|
|
27
|
-
|
|
27
|
+
let styles = { backgroundColor: tokens.backgroundColor }
|
|
28
|
+
if (tokens.gradient) {
|
|
29
|
+
const {
|
|
30
|
+
gradient: {
|
|
31
|
+
angle,
|
|
32
|
+
stops: [stopOne, stopTwo]
|
|
33
|
+
}
|
|
34
|
+
} = tokens
|
|
35
|
+
styles = {
|
|
36
|
+
...styles,
|
|
37
|
+
backgroundImage: `linear-gradient(${angle}deg, ${stopOne.color}, 75% , ${stopTwo.color})`
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
28
41
|
const paddings = ['paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom']
|
|
29
42
|
// Only set on styles if token provided because we spread this object after the spacing scale values
|
|
30
43
|
paddings.forEach((side) => {
|
|
@@ -123,7 +123,7 @@ const selectBorderStyles = ({ borderColor, borderWidth, borderRadius }) => ({
|
|
|
123
123
|
})
|
|
124
124
|
|
|
125
125
|
const selectTextStyles = (
|
|
126
|
-
{ fontSize, color, lineHeight, fontName, fontWeight, textAlign },
|
|
126
|
+
{ fontSize, color, lineHeight, fontName, fontWeight, textAlign, textLine, textLineStyle },
|
|
127
127
|
themeOptions
|
|
128
128
|
) =>
|
|
129
129
|
applyTextStyles({
|
|
@@ -133,7 +133,9 @@ const selectTextStyles = (
|
|
|
133
133
|
fontName,
|
|
134
134
|
fontWeight,
|
|
135
135
|
themeOptions,
|
|
136
|
-
textAlign
|
|
136
|
+
textAlign,
|
|
137
|
+
textDecorationLine: textLine,
|
|
138
|
+
textDecorationStyle: textLineStyle
|
|
137
139
|
})
|
|
138
140
|
|
|
139
141
|
const selectWebOnlyStyles = (inactive, themeTokens, { accessibilityRole }) => {
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import { Platform, Text, View } from 'react-native'
|
|
4
|
+
import buttonPropTypes, { textAndA11yText } from './propTypes'
|
|
5
|
+
import ButtonBase from './ButtonBase'
|
|
6
|
+
import { useThemeTokensCallback } from '../ThemeProvider'
|
|
7
|
+
import {
|
|
8
|
+
a11yProps,
|
|
9
|
+
focusHandlerProps,
|
|
10
|
+
resolvePressableState,
|
|
11
|
+
selectTokens,
|
|
12
|
+
useInputValue
|
|
13
|
+
} from '../utils'
|
|
14
|
+
import Icon from '../Icon'
|
|
15
|
+
import { getStackedContent } from '../StackView'
|
|
16
|
+
import { getPressHandlersWithArgs } from '../utils/pressability'
|
|
17
|
+
|
|
18
|
+
const selectIconTokens = ({
|
|
19
|
+
icon,
|
|
20
|
+
iconPosition,
|
|
21
|
+
iconSpace,
|
|
22
|
+
iconSize,
|
|
23
|
+
iconColor,
|
|
24
|
+
iconBackground,
|
|
25
|
+
iconBorderRadius,
|
|
26
|
+
iconAlignSelf,
|
|
27
|
+
iconPadding,
|
|
28
|
+
iconTranslateX,
|
|
29
|
+
iconTranslateY
|
|
30
|
+
}) => ({
|
|
31
|
+
icon,
|
|
32
|
+
iconPosition,
|
|
33
|
+
iconSpace,
|
|
34
|
+
iconWrapperStyle: {
|
|
35
|
+
backgroundColor: iconBackground,
|
|
36
|
+
borderRadius: iconBorderRadius,
|
|
37
|
+
alignSelf: iconAlignSelf,
|
|
38
|
+
padding: iconPadding,
|
|
39
|
+
...Platform.select({
|
|
40
|
+
// TODO: https://github.com/telus/universal-design-system/issues/487
|
|
41
|
+
web: { transition: 'color 200ms, background 200ms' }
|
|
42
|
+
})
|
|
43
|
+
},
|
|
44
|
+
iconTokens: {
|
|
45
|
+
size: iconSize,
|
|
46
|
+
color: iconColor,
|
|
47
|
+
translateX: iconTranslateX,
|
|
48
|
+
translateY: iconTranslateY
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const ButtonDropdown = forwardRef(
|
|
53
|
+
(
|
|
54
|
+
{
|
|
55
|
+
value,
|
|
56
|
+
initialValue,
|
|
57
|
+
onChange,
|
|
58
|
+
label,
|
|
59
|
+
tokens,
|
|
60
|
+
variant,
|
|
61
|
+
inactive = false,
|
|
62
|
+
readOnly = false,
|
|
63
|
+
children = null,
|
|
64
|
+
accessibilityRole = 'radio',
|
|
65
|
+
...props
|
|
66
|
+
},
|
|
67
|
+
ref
|
|
68
|
+
) => {
|
|
69
|
+
const { currentValue: isOpen, setValue: setIsOpen } = useInputValue(
|
|
70
|
+
{
|
|
71
|
+
value,
|
|
72
|
+
initialValue,
|
|
73
|
+
onChange,
|
|
74
|
+
readOnly
|
|
75
|
+
},
|
|
76
|
+
'useButtonDropdownValues'
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
const extraState = {
|
|
80
|
+
open: isOpen,
|
|
81
|
+
inactive,
|
|
82
|
+
...variant
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const getTokens = useThemeTokensCallback('ButtonDropdown', tokens, extraState)
|
|
86
|
+
|
|
87
|
+
const getButtonTokens = (buttonState) => selectTokens('Button', getTokens(buttonState))
|
|
88
|
+
|
|
89
|
+
// Pass an object of relevant component state as first argument for any passed-in press handlers
|
|
90
|
+
const pressHandlers = getPressHandlersWithArgs(props, [{ label, open: isOpen }])
|
|
91
|
+
|
|
92
|
+
const handlePress = (event) => {
|
|
93
|
+
if (!inactive) {
|
|
94
|
+
if (pressHandlers.onPress) pressHandlers?.onPress(event)
|
|
95
|
+
setIsOpen(!isOpen, event)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<ButtonBase
|
|
101
|
+
ref={ref}
|
|
102
|
+
{...pressHandlers}
|
|
103
|
+
onPress={handlePress}
|
|
104
|
+
tokens={getButtonTokens}
|
|
105
|
+
inactive={inactive}
|
|
106
|
+
icon={() => null}
|
|
107
|
+
accessibilityRole={accessibilityRole}
|
|
108
|
+
{...props}
|
|
109
|
+
>
|
|
110
|
+
{({ textStyles, ...buttonState }) => {
|
|
111
|
+
// TODO: once Icon/IconButton designs are stable, see if this sort of styling around
|
|
112
|
+
// an icon should go in Icon itself, or possibly via an IconText token set. Related issues:
|
|
113
|
+
// - Icon: https://github.com/telus/universal-design-system/issues/327
|
|
114
|
+
// - IconButton: https://github.com/telus/universal-design-system/issues/281
|
|
115
|
+
// - Token sets: https://github.com/telus/universal-design-system/issues/782
|
|
116
|
+
|
|
117
|
+
const itemTokens = getTokens(buttonState)
|
|
118
|
+
|
|
119
|
+
const {
|
|
120
|
+
iconTokens,
|
|
121
|
+
iconPosition,
|
|
122
|
+
iconSpace,
|
|
123
|
+
iconWrapperStyle,
|
|
124
|
+
icon: IconComponent
|
|
125
|
+
} = selectIconTokens(itemTokens)
|
|
126
|
+
|
|
127
|
+
const iconContent = IconComponent ? (
|
|
128
|
+
<View style={iconWrapperStyle}>
|
|
129
|
+
<Icon icon={IconComponent} tokens={iconTokens} />
|
|
130
|
+
</View>
|
|
131
|
+
) : null
|
|
132
|
+
|
|
133
|
+
const childrenContent = () =>
|
|
134
|
+
typeof children === 'function'
|
|
135
|
+
? children({ ...resolvePressableState(buttonState, extraState), textStyles })
|
|
136
|
+
: children
|
|
137
|
+
|
|
138
|
+
const content = children ? childrenContent() : <Text style={textStyles}>{label}</Text>
|
|
139
|
+
|
|
140
|
+
return getStackedContent(
|
|
141
|
+
iconPosition === 'left' ? [iconContent, content] : [content, iconContent],
|
|
142
|
+
{ space: iconSpace, direction: 'row' }
|
|
143
|
+
)
|
|
144
|
+
}}
|
|
145
|
+
</ButtonBase>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
ButtonDropdown.displayName = 'ButtonDropdown'
|
|
150
|
+
|
|
151
|
+
ButtonDropdown.propTypes = {
|
|
152
|
+
...a11yProps.types,
|
|
153
|
+
...focusHandlerProps.types,
|
|
154
|
+
...buttonPropTypes,
|
|
155
|
+
children: textAndA11yText,
|
|
156
|
+
/**
|
|
157
|
+
* Callback called when a controlled ButtonDropdown gets interacted with.
|
|
158
|
+
*/
|
|
159
|
+
onChange: PropTypes.func,
|
|
160
|
+
/**
|
|
161
|
+
* `value` prop is being used to set the 'open' state of ButtonDropdown. Use it for
|
|
162
|
+
* controlled ButtonDropdown. For uncontrolled ButtonDropdown, use `initialValue`.
|
|
163
|
+
*/
|
|
164
|
+
value: PropTypes.bool,
|
|
165
|
+
/**
|
|
166
|
+
* Use `initialValue` to provide the initial value for an uncontrolled version.
|
|
167
|
+
*/
|
|
168
|
+
initialValue: PropTypes.bool,
|
|
169
|
+
/**
|
|
170
|
+
* The label of ButtonDropdown.
|
|
171
|
+
*/
|
|
172
|
+
label: PropTypes.string,
|
|
173
|
+
/**
|
|
174
|
+
* By default, `ButtonDropdown` is treated by accessibility tools as a radio button.
|
|
175
|
+
*/
|
|
176
|
+
accessibilityRole: PropTypes.string
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export default ButtonDropdown
|
package/src/Button/index.js
CHANGED