@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.
Files changed (112) hide show
  1. package/CHANGELOG.md +42 -2
  2. package/__tests17__/ThemeProvider/ThemeProvider.test.jsx +2 -1
  3. package/component-docs.json +1035 -231
  4. package/jest.config-android.js +17 -0
  5. package/jest.config-ios.js +18 -0
  6. package/jest.config-web.js +31 -0
  7. package/lib/BaseProvider/index.js +2 -1
  8. package/lib/Box/Box.js +14 -1
  9. package/lib/Button/ButtonBase.js +6 -2
  10. package/lib/Button/ButtonDropdown.js +207 -0
  11. package/lib/Button/index.js +8 -0
  12. package/lib/Carousel/Carousel.js +34 -6
  13. package/lib/Carousel/CarouselItem/CarouselItem.js +7 -1
  14. package/lib/Carousel/CarouselTabs/CarouselTabsPanel.js +22 -14
  15. package/lib/FlexGrid/Col/Col.js +1 -3
  16. package/lib/FlexGrid/FlexGrid.js +3 -5
  17. package/lib/FlexGrid/Row/Row.js +3 -3
  18. package/lib/IconButton/IconButton.js +12 -4
  19. package/lib/MultiSelectFilter/MultiSelectFilter.js +276 -0
  20. package/lib/MultiSelectFilter/dictionary.js +19 -0
  21. package/lib/MultiSelectFilter/index.js +13 -0
  22. package/lib/Pagination/SideButton.js +6 -4
  23. package/lib/Responsive/Responsive.js +58 -0
  24. package/lib/Responsive/index.js +13 -0
  25. package/lib/Search/Search.js +33 -63
  26. package/lib/Select/Picker.native.js +16 -13
  27. package/lib/Select/Select.js +7 -1
  28. package/lib/Select/constants.js +15 -0
  29. package/lib/StepTracker/Step.js +2 -1
  30. package/lib/Tags/Tags.js +10 -4
  31. package/lib/TextInput/TextInput.js +9 -2
  32. package/lib/TextInput/TextInputBase.js +98 -20
  33. package/lib/TextInput/dictionary.js +15 -0
  34. package/lib/ThemeProvider/ThemeProvider.js +6 -1
  35. package/lib/index.js +18 -0
  36. package/lib/utils/BaseView/BaseView.js +64 -0
  37. package/lib/utils/BaseView/BaseView.native.js +16 -0
  38. package/lib/utils/BaseView/index.js +13 -0
  39. package/lib/utils/index.js +10 -1
  40. package/lib/utils/input.js +11 -3
  41. package/lib/utils/props/handlerProps.js +5 -0
  42. package/lib-module/BaseProvider/index.js +2 -1
  43. package/lib-module/Box/Box.js +14 -1
  44. package/lib-module/Button/ButtonBase.js +6 -2
  45. package/lib-module/Button/ButtonDropdown.js +181 -0
  46. package/lib-module/Button/index.js +2 -1
  47. package/lib-module/Carousel/Carousel.js +34 -6
  48. package/lib-module/Carousel/CarouselItem/CarouselItem.js +8 -2
  49. package/lib-module/Carousel/CarouselTabs/CarouselTabsPanel.js +24 -16
  50. package/lib-module/FlexGrid/Col/Col.js +2 -3
  51. package/lib-module/FlexGrid/FlexGrid.js +2 -3
  52. package/lib-module/FlexGrid/Row/Row.js +2 -2
  53. package/lib-module/IconButton/IconButton.js +14 -4
  54. package/lib-module/MultiSelectFilter/MultiSelectFilter.js +248 -0
  55. package/lib-module/MultiSelectFilter/dictionary.js +12 -0
  56. package/lib-module/MultiSelectFilter/index.js +2 -0
  57. package/lib-module/Pagination/SideButton.js +6 -4
  58. package/lib-module/Responsive/Responsive.js +45 -0
  59. package/lib-module/Responsive/index.js +2 -0
  60. package/lib-module/Search/Search.js +33 -61
  61. package/lib-module/Select/Picker.native.js +15 -13
  62. package/lib-module/Select/Select.js +6 -1
  63. package/lib-module/Select/constants.js +5 -0
  64. package/lib-module/StepTracker/Step.js +2 -1
  65. package/lib-module/Tags/Tags.js +10 -4
  66. package/lib-module/TextInput/TextInput.js +6 -0
  67. package/lib-module/TextInput/TextInputBase.js +96 -21
  68. package/lib-module/TextInput/dictionary.js +8 -0
  69. package/lib-module/ThemeProvider/ThemeProvider.js +6 -1
  70. package/lib-module/index.js +2 -0
  71. package/lib-module/utils/BaseView/BaseView.js +43 -0
  72. package/lib-module/utils/BaseView/BaseView.native.js +6 -0
  73. package/lib-module/utils/BaseView/index.js +2 -0
  74. package/lib-module/utils/index.js +2 -1
  75. package/lib-module/utils/input.js +11 -3
  76. package/lib-module/utils/props/handlerProps.js +5 -0
  77. package/package.json +6 -3
  78. package/src/BaseProvider/index.jsx +4 -1
  79. package/src/Box/Box.jsx +14 -1
  80. package/src/Button/ButtonBase.jsx +4 -2
  81. package/src/Button/ButtonDropdown.jsx +179 -0
  82. package/src/Button/index.js +2 -1
  83. package/src/Carousel/Carousel.jsx +48 -13
  84. package/src/Carousel/CarouselItem/CarouselItem.jsx +9 -2
  85. package/src/Carousel/CarouselTabs/CarouselTabsPanel.jsx +19 -15
  86. package/src/FlexGrid/Col/Col.jsx +4 -4
  87. package/src/FlexGrid/FlexGrid.jsx +11 -10
  88. package/src/FlexGrid/Row/Row.jsx +4 -3
  89. package/src/IconButton/IconButton.jsx +3 -1
  90. package/src/MultiSelectFilter/MultiSelectFilter.jsx +227 -0
  91. package/src/MultiSelectFilter/dictionary.js +12 -0
  92. package/src/MultiSelectFilter/index.js +3 -0
  93. package/src/Pagination/SideButton.jsx +5 -5
  94. package/src/Responsive/Responsive.jsx +33 -0
  95. package/src/Responsive/index.js +3 -0
  96. package/src/Search/Search.jsx +19 -33
  97. package/src/Select/Picker.native.jsx +29 -14
  98. package/src/Select/Select.jsx +7 -1
  99. package/src/Select/constants.js +5 -0
  100. package/src/StepTracker/Step.jsx +5 -1
  101. package/src/Tags/Tags.jsx +46 -33
  102. package/src/TextInput/TextInput.jsx +5 -0
  103. package/src/TextInput/TextInputBase.jsx +85 -20
  104. package/src/TextInput/dictionary.js +8 -0
  105. package/src/ThemeProvider/ThemeProvider.jsx +5 -1
  106. package/src/index.js +2 -0
  107. package/src/utils/BaseView/BaseView.jsx +38 -0
  108. package/src/utils/BaseView/BaseView.native.jsx +6 -0
  109. package/src/utils/BaseView/index.js +3 -0
  110. package/src/utils/index.js +1 -0
  111. package/src/utils/input.js +9 -4
  112. 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 { a11yProps, getTokensPropType, selectSystemProps, textInputHandlerProps, textInputProps, useInputValue, variantProp, viewProps } from '../utils';
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 TextInputBase = /*#__PURE__*/forwardRef((_ref5, ref) => {
129
+ const selectButtonsContainerStyle = _ref5 => {
125
130
  let {
126
- value,
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
- readOnly,
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
- } = _ref5;
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 = ref === null || ref === void 0 ? void 0 : ref.current;
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: 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
- value: PropTypes.string,
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
- readOnly: PropTypes.bool,
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,
@@ -0,0 +1,8 @@
1
+ export default {
2
+ en: {
3
+ clearButtonAccessibilityLabel: 'Clear'
4
+ },
5
+ fr: {
6
+ clearButtonAccessibilityLabel: 'Effacer'
7
+ }
8
+ };
@@ -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
  };
@@ -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;
@@ -0,0 +1,6 @@
1
+ import BaseView from "react-native-web/dist/exports/View";
2
+ /**
3
+ * Android crashes on non-standard style properties like `zIndex` so adding a `BaseView` for native platforms
4
+ */
5
+
6
+ export default BaseView;
@@ -0,0 +1,2 @@
1
+ import BaseView from './BaseView';
2
+ 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
- if (!isControlled) setOwnValue(newValue); // Call onChange handler if there's something for it to handle (event or a changed value)
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
  /**
@@ -27,6 +27,11 @@ const textInputHandlerProps = {
27
27
  */
28
28
  onChangeText: PropTypes.func,
29
29
 
30
+ /**
31
+ * onClear handler
32
+ */
33
+ onClear: PropTypes.func,
34
+
30
35
  /**
31
36
  * onSubmit handler
32
37
  */
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.4",
12
- "@telus-uds/system-theme-tokens": "^2.6.0",
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.18.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({ forceAbsoluteFontSizing: PropTypes.bool })
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
- const styles = { backgroundColor: tokens.backgroundColor }
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
@@ -1,5 +1,6 @@
1
1
  import Button from './Button'
2
2
  import ButtonLink from './ButtonLink'
3
3
  import ButtonGroup from './ButtonGroup'
4
+ import ButtonDropdown from './ButtonDropdown'
4
5
 
5
- export { Button, ButtonGroup, ButtonLink }
6
+ export { Button, ButtonDropdown, ButtonGroup, ButtonLink }