@telus-uds/components-base 3.7.1 → 3.9.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 (54) hide show
  1. package/CHANGELOG.md +34 -2
  2. package/lib/cjs/ActivityIndicator/FullScreenIndicator.js +89 -0
  3. package/lib/cjs/ActivityIndicator/InlineIndicator.js +64 -0
  4. package/lib/cjs/ActivityIndicator/OverlayIndicator.js +156 -0
  5. package/lib/cjs/ActivityIndicator/RenderActivityIndicator.js +88 -0
  6. package/lib/cjs/ActivityIndicator/index.js +91 -23
  7. package/lib/cjs/ActivityIndicator/shared.js +12 -1
  8. package/lib/cjs/ActivityIndicator/sharedProptypes.js +67 -0
  9. package/lib/cjs/Card/Card.js +38 -45
  10. package/lib/cjs/ExpandCollapseMini/ExpandCollapseMiniControl.js +1 -1
  11. package/lib/cjs/List/ListItemMark.js +13 -2
  12. package/lib/cjs/MultiSelectFilter/ModalOverlay.js +19 -5
  13. package/lib/cjs/MultiSelectFilter/MultiSelectFilter.js +22 -9
  14. package/lib/cjs/ToggleSwitch/ToggleSwitch.js +13 -2
  15. package/lib/cjs/Validator/Validator.js +135 -64
  16. package/lib/cjs/utils/index.js +9 -1
  17. package/lib/cjs/utils/useDetectOutsideClick.js +39 -0
  18. package/lib/cjs/utils/useVariants.js +46 -0
  19. package/lib/esm/ActivityIndicator/FullScreenIndicator.js +82 -0
  20. package/lib/esm/ActivityIndicator/InlineIndicator.js +57 -0
  21. package/lib/esm/ActivityIndicator/OverlayIndicator.js +149 -0
  22. package/lib/esm/ActivityIndicator/RenderActivityIndicator.js +83 -0
  23. package/lib/esm/ActivityIndicator/index.js +89 -23
  24. package/lib/esm/ActivityIndicator/shared.js +11 -0
  25. package/lib/esm/ActivityIndicator/sharedProptypes.js +61 -0
  26. package/lib/esm/Card/Card.js +38 -45
  27. package/lib/esm/ExpandCollapseMini/ExpandCollapseMiniControl.js +1 -1
  28. package/lib/esm/List/ListItemMark.js +13 -2
  29. package/lib/esm/MultiSelectFilter/ModalOverlay.js +19 -5
  30. package/lib/esm/MultiSelectFilter/MultiSelectFilter.js +22 -9
  31. package/lib/esm/ToggleSwitch/ToggleSwitch.js +13 -2
  32. package/lib/esm/Validator/Validator.js +135 -64
  33. package/lib/esm/utils/index.js +2 -1
  34. package/lib/esm/utils/useDetectOutsideClick.js +31 -0
  35. package/lib/esm/utils/useVariants.js +41 -0
  36. package/lib/package.json +2 -2
  37. package/package.json +2 -2
  38. package/src/ActivityIndicator/FullScreenIndicator.jsx +65 -0
  39. package/src/ActivityIndicator/InlineIndicator.jsx +47 -0
  40. package/src/ActivityIndicator/OverlayIndicator.jsx +140 -0
  41. package/src/ActivityIndicator/RenderActivityIndicator.jsx +82 -0
  42. package/src/ActivityIndicator/index.jsx +113 -32
  43. package/src/ActivityIndicator/shared.js +11 -0
  44. package/src/ActivityIndicator/sharedProptypes.js +62 -0
  45. package/src/Card/Card.jsx +51 -54
  46. package/src/ExpandCollapseMini/ExpandCollapseMiniControl.jsx +1 -1
  47. package/src/List/ListItemMark.jsx +18 -2
  48. package/src/MultiSelectFilter/ModalOverlay.jsx +18 -5
  49. package/src/MultiSelectFilter/MultiSelectFilter.jsx +21 -10
  50. package/src/ToggleSwitch/ToggleSwitch.jsx +17 -2
  51. package/src/Validator/Validator.jsx +172 -85
  52. package/src/utils/index.js +1 -0
  53. package/src/utils/useDetectOutsideClick.js +35 -0
  54. package/src/utils/useVariants.js +44 -0
@@ -37,7 +37,7 @@ const Validator = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
37
37
  supportsProps
38
38
  } = selectProps(rest);
39
39
  const strValidation = supportsProps.validation;
40
- const [individualCodes, setIndividualCodes] = React.useState({});
40
+ const [, setIndividualCodes] = React.useState({});
41
41
  const [text, setText] = React.useState(value);
42
42
  const validatorsLength = 6;
43
43
  const prefix = 'code';
@@ -55,64 +55,85 @@ const Validator = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
55
55
  const [codeReferences, singleCodes] = React.useMemo(() => {
56
56
  const codes = [];
57
57
  const valueCodes = {};
58
- for (let i = 0; validatorsLength && i < validatorsLength; i += 1) {
58
+ Array.from({
59
+ length: validatorsLength
60
+ }, (_, i) => {
59
61
  codes[prefix + i] = /*#__PURE__*/React.createRef();
60
62
  valueCodes[prefix + i] = '';
61
63
  valueCodes[prefix + i + sufixValidation] = '';
62
- }
64
+ return null;
65
+ });
63
66
  return [codes, valueCodes];
64
- }, []);
65
- const handleSingleCodes = (codeId, val, validation) => {
67
+ }, [validatorsLength, prefix, sufixValidation]);
68
+ const handleSingleCodes = React.useCallback((codeId, val, validation) => {
66
69
  singleCodes[codeId] = val;
67
70
  singleCodes[codeId + sufixValidation] = validation;
68
- /* eslint-disable no-unused-expressions */
69
- setIndividualCodes({
70
- ...individualCodes,
71
+ setIndividualCodes(prev => ({
72
+ ...prev,
71
73
  [codeId]: val
72
- });
73
- };
74
- const changeDataMasking = boxElement => {
74
+ }));
75
+ }, [singleCodes, sufixValidation]);
76
+ const changeDataMasking = React.useCallback(boxElement => {
75
77
  let charMasking = '';
76
78
  const element = boxElement;
77
- if (mask && mask.length === 1) charMasking = mask;else if (mask && mask.length > 1) charMasking = mask.substring(0, 1);
78
- if (charMasking) element.value = charMasking;
79
- };
80
- const handleChangeCode = () => {
81
- let code = '';
82
- for (let i = 0; i < validatorsLength; i += 1) code += singleCodes[prefix + i];
83
- if (typeof onChange === 'function') onChange(code, singleCodes);
84
- };
85
- const handleChangeCodeValues = (event, codeId, nextIndex) => {
79
+ if (mask && mask.length === 1) {
80
+ charMasking = mask;
81
+ } else if (mask && mask.length > 1) {
82
+ charMasking = mask.substring(0, 1);
83
+ }
84
+ if (charMasking && element) {
85
+ element.value = charMasking;
86
+ }
87
+ }, [mask]);
88
+ const handleChangeCode = React.useCallback(() => {
89
+ const code = Array.from({
90
+ length: validatorsLength
91
+ }, (_, i) => singleCodes[prefix + i] || '').join('');
92
+ if (typeof onChange === 'function') {
93
+ onChange(code, singleCodes);
94
+ }
95
+ }, [validatorsLength, singleCodes, prefix, onChange]);
96
+ const handleChangeCodeValues = React.useCallback((event, codeId, nextIndex) => {
86
97
  const codeElement = codeReferences[codeId]?.current;
87
98
  const val = event.nativeEvent?.value || event.target?.value;
88
- if (Number(val).toString() === 'NaN') {
89
- codeElement.value = singleCodes[codeId] ?? '';
90
- return;
99
+
100
+ // Only allow numeric characters and limit to single digit
101
+ const numericOnly = val.replace(/\D/g, '').substring(0, 1);
102
+ if (codeElement && codeElement.value) {
103
+ codeElement.value = numericOnly;
91
104
  }
92
- handleSingleCodes(codeId, codeElement?.value ?? singleCodes[codeId], 'success');
105
+ handleSingleCodes(codeId, numericOnly, numericOnly ? 'success' : '');
93
106
  handleChangeCode();
94
107
  if (nextIndex === validatorsLength) {
95
- codeElement.blur();
108
+ codeElement?.blur();
96
109
  changeDataMasking(codeElement);
97
110
  return;
98
111
  }
99
- if (codeElement?.value?.length > 0) {
100
- codeReferences[prefix + nextIndex].current.focus();
112
+ if (numericOnly.length > 0) {
113
+ const nextElement = codeReferences[prefix + nextIndex]?.current;
114
+ nextElement?.focus();
101
115
  changeDataMasking(codeElement);
102
116
  }
103
- };
117
+ }, [codeReferences, handleSingleCodes, handleChangeCode, validatorsLength, changeDataMasking, prefix]);
104
118
  const handleKeyPress = (event, currentIndex, previousIndex) => {
105
- if (!(event.keyCode === 8 || event.code === 'Backspace')) return;
119
+ if (!(event.keyCode === 8 || event.code === 'Backspace')) {
120
+ return;
121
+ }
106
122
  if (currentIndex > 0) {
107
- codeReferences[prefix + currentIndex].current.value = '';
108
- codeReferences[prefix + previousIndex].current.focus();
123
+ const currentElement = codeReferences[prefix + currentIndex]?.current;
124
+ const previousElement = codeReferences[prefix + previousIndex]?.current;
125
+ if (currentElement && currentElement.value) {
126
+ currentElement.value = '';
127
+ }
128
+ previousElement?.focus();
109
129
  }
110
130
  handleSingleCodes(prefix + currentIndex, '', '');
111
131
  handleChangeCode();
112
132
  };
113
133
  const getCodeComponents = () => {
114
- const components = [];
115
- for (let i = 0; validatorsLength && i < validatorsLength; i += 1) {
134
+ return Array.from({
135
+ length: validatorsLength
136
+ }, (_, i) => {
116
137
  const codeId = prefix + i;
117
138
  const codeInputProps = {
118
139
  nativeID: codeId,
@@ -120,65 +141,115 @@ const Validator = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
120
141
  ref: codeReferences[codeId] ?? null,
121
142
  validation: strValidation || singleCodes[codeId + sufixValidation],
122
143
  tokens: selectCodeTextInputTokens(themeTokens),
123
- onFocus: () => codeReferences[codeId]?.current?.select() ?? null,
144
+ onFocus: () => {
145
+ const element = codeReferences[codeId]?.current;
146
+ if (Platform.OS === 'web' && element?.select) {
147
+ return element.select() ?? null;
148
+ }
149
+ if (element?.focus) {
150
+ return element.focus() ?? null;
151
+ }
152
+ return null;
153
+ },
124
154
  onKeyPress: event => handleKeyPress(event, i, i - 1),
125
155
  onMouseOver: handleMouseOver,
126
156
  onMouseOut: handleMouseOut,
127
157
  inactive
128
158
  };
129
- codeInputProps.validation || delete codeInputProps.validation;
130
- components.push(/*#__PURE__*/_jsx(View, {
159
+ if (!codeInputProps.validation) {
160
+ delete codeInputProps.validation;
161
+ }
162
+ return /*#__PURE__*/_jsx(View, {
131
163
  style: staticStyles.codeInputWidth,
132
164
  children: /*#__PURE__*/_jsx(TextInput, {
133
165
  ...codeInputProps
134
166
  })
135
- }, codeId));
136
- }
137
- return components;
167
+ }, codeId);
168
+ });
138
169
  };
139
170
  React.useEffect(() => {
140
- /* eslint-disable no-unused-expressions */
141
- if (Number(value).toString() !== 'NaN') setText(value);
171
+ if (Number(value).toString() !== 'NaN') {
172
+ setText(value);
173
+ }
142
174
  }, [value]);
143
-
144
- /* eslint-disable react-hooks/exhaustive-deps */
145
175
  React.useEffect(() => {
146
- for (let i = 0; i < validatorsLength; i += 1) {
147
- if (mask && text[i]) codeReferences[prefix + i].current.value = mask;else codeReferences[prefix + i].current.value = text[i] ?? '';
176
+ Array.from({
177
+ length: validatorsLength
178
+ }, (_, i) => {
179
+ const element = codeReferences[prefix + i]?.current;
180
+ if (element && element.value !== undefined) {
181
+ if (mask && text[i]) {
182
+ element.value = mask;
183
+ } else {
184
+ element.value = text[i] ?? '';
185
+ }
186
+ }
148
187
  handleSingleCodes(prefix + i, text[i] ?? '', text[i] ? 'success' : '');
149
- }
150
- }, [text]);
151
-
152
- /* eslint-disable react-hooks/exhaustive-deps */
188
+ return null;
189
+ });
190
+ }, [text, mask, validatorsLength, prefix, codeReferences, handleSingleCodes]);
153
191
  React.useEffect(() => {
154
192
  const handlePasteCode = event => {
193
+ event.preventDefault();
194
+
195
+ // Clear current state first
155
196
  setText('');
197
+
198
+ // Clear all individual input fields and their state
199
+ Array.from({
200
+ length: validatorsLength
201
+ }, (_, i) => {
202
+ const element = codeReferences[prefix + i]?.current;
203
+ if (element && element.value !== undefined) {
204
+ element.value = '';
205
+ }
206
+ handleSingleCodes(prefix + i, '', '');
207
+ return null;
208
+ });
156
209
  const clipBoardText = event.clipboardData.getData('text');
157
- if (Number(clipBoardText).toString() !== 'NaN') setText(clipBoardText);
210
+
211
+ // Validate that input contains only digits and truncate to 6 characters
212
+ const numericOnly = clipBoardText.replace(/\D/g, '').substring(0, validatorsLength);
213
+ if (numericOnly.length > 0) {
214
+ setText(numericOnly);
215
+ }
158
216
  };
159
217
  const handleCopy = event => {
160
- let clipBoardText = '';
161
- for (let i = 0; i < validatorsLength; i += 1) singleCodes[prefix + i] && (clipBoardText += singleCodes[prefix + i]);
218
+ const clipBoardText = Array.from({
219
+ length: validatorsLength
220
+ }, (_, i) => singleCodes[prefix + i] || '').join('');
162
221
  event.clipboardData.setData('text/plain', clipBoardText);
163
222
  event.preventDefault();
164
223
  };
165
224
  if (Platform.OS === 'web') {
166
- for (let i = 0; i < validatorsLength; i += 1) {
167
- codeReferences[prefix + i].current.addEventListener('paste', handlePasteCode);
168
- codeReferences[prefix + i].current.addEventListener('copy', handleCopy);
169
- codeReferences[prefix + i].current.addEventListener('input', event => handleChangeCodeValues(event, prefix + i, i + 1));
170
- }
225
+ Array.from({
226
+ length: validatorsLength
227
+ }, (_, i) => {
228
+ const element = codeReferences[prefix + i]?.current;
229
+ if (element && typeof element.addEventListener === 'function') {
230
+ element.addEventListener('paste', handlePasteCode);
231
+ element.addEventListener('copy', handleCopy);
232
+ element.addEventListener('input', event => handleChangeCodeValues(event, prefix + i, i + 1));
233
+ }
234
+ return null;
235
+ });
171
236
  }
172
237
  return () => {
173
- if (Platform.oldValue === 'web') {
174
- for (let i = 0; i < validatorsLength; i += 1) {
175
- codeReferences[prefix + i]?.current?.removeEventListener('paste', handlePasteCode);
176
- codeReferences[prefix + i]?.current?.removeEventListener('copy', handleCopy);
177
- codeReferences[prefix + i]?.current?.removeEventListener('input', event => handleChangeCodeValues(event, prefix + i, i + 1));
178
- }
238
+ if (Platform.OS === 'web') {
239
+ Array.from({
240
+ length: validatorsLength
241
+ }, (_, i) => {
242
+ const element = codeReferences[prefix + i]?.current;
243
+ if (element && typeof element.removeEventListener === 'function') {
244
+ element.removeEventListener('paste', handlePasteCode);
245
+ element.removeEventListener('copy', handleCopy);
246
+ element.removeEventListener('input', event => handleChangeCodeValues(event, prefix + i, i + 1));
247
+ }
248
+ return null;
249
+ });
179
250
  }
180
251
  };
181
- }, []);
252
+ }, [validatorsLength, prefix, codeReferences, handleChangeCodeValues, handleSingleCodes, singleCodes]);
182
253
  return /*#__PURE__*/_jsx(InputSupports, {
183
254
  ...supportsProps,
184
255
  feedbackProps: {
@@ -23,4 +23,5 @@ export { default as htmlAttrs } from './htmlAttrs';
23
23
  export { transformGradient } from './transformGradient';
24
24
  export { default as convertFromMegaByteToByte } from './convertFromMegaByteToByte';
25
25
  export { default as formatImageSource } from './formatImageSource';
26
- export { default as getSpacingScale } from './getSpacingScale';
26
+ export { default as getSpacingScale } from './getSpacingScale';
27
+ export { default as useVariants } from './useVariants';
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import Platform from "react-native-web/dist/exports/Platform";
3
+ /**
4
+ * Hook to detect clicks outside of a ref, only on web.
5
+ *
6
+ * @param {React.RefObject<HTMLElement>} ref
7
+ * Reference to the element you want to “protect.”
8
+ * @param {() => void} onOutside
9
+ * Callback invoked when a click occurs outside that ref.
10
+ * @param {boolean} [enabled=true]
11
+ * Flag to enable or disable the outside-click detection at runtime.
12
+ * @deprecated Will be removed in next major release; detection will always be enabled.
13
+ */
14
+ function useDetectOutsideClick(ref, onOutside) {
15
+ let enabled = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
16
+ React.useEffect(() => {
17
+ if (!enabled || Platform.OS !== 'web') {
18
+ return undefined;
19
+ }
20
+ const handleClickOutside = e => {
21
+ if (ref.current && !ref.current.contains(e.target)) {
22
+ onOutside();
23
+ }
24
+ };
25
+ document.addEventListener('mousedown', handleClickOutside);
26
+ return () => {
27
+ document.removeEventListener('mousedown', handleClickOutside);
28
+ };
29
+ }, [ref, onOutside, enabled]);
30
+ }
31
+ export default useDetectOutsideClick;
@@ -0,0 +1,41 @@
1
+ import { getComponentTheme, useTheme } from '../ThemeProvider';
2
+
3
+ /**
4
+ * Generates a label string for a variant based on the provided key and value.
5
+ *
6
+ * @param {string} key - The name of the variant.
7
+ * @param {*} value - The value of the variant. If it's a string, it will be appended to the key.
8
+ * @returns {string} The formatted variant label (e.g., "color: red" or "size").
9
+ */
10
+ const getVariantLabel = (key, value) => `${key}${typeof value === 'string' ? `: ${value}` : ''}`;
11
+
12
+ /**
13
+ * Retrieves the variant options for a given component from the theme.
14
+ *
15
+ * @param {string} componentName - The name of the component to get variants for.
16
+ * @returns {Array<Array>} An array of variant tuples. Each tuple contains:
17
+ * - {string|undefined} The variant key (e.g., 'size', 'color', or undefined for default).
18
+ * - {string|undefined} The variant value (e.g., 'small', 'primary', or undefined for default).
19
+ * - {string} The human-readable label for the variant.
20
+ * Returns [['default', {}]] if no componentName is provided.
21
+ * @throws {Error} If the theme does not define appearances for the given component.
22
+ */
23
+ const useVariants = componentName => {
24
+ const theme = useTheme();
25
+ if (!componentName) return [['default', {}]];
26
+ const {
27
+ appearances
28
+ } = getComponentTheme(theme, componentName);
29
+ if (!appearances) {
30
+ throw new Error(`Theme ${theme.metadata?.name} does not have any appearances set for ${componentName}`);
31
+ }
32
+ const variants = Object.entries(appearances).reduce((pairs, _ref) => {
33
+ let [key, {
34
+ values,
35
+ type
36
+ } = {}] = _ref;
37
+ return type === 'variant' ? [...pairs, ...values.map(value => [key, value, getVariantLabel(key, value)])] : pairs;
38
+ }, [[undefined, undefined, 'default style']]);
39
+ return variants;
40
+ };
41
+ export default useVariants;
package/lib/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "@gorhom/portal": "^1.0.14",
13
13
  "@react-native-picker/picker": "^2.9.0",
14
14
  "@telus-uds/system-constants": "^3.0.0",
15
- "@telus-uds/system-theme-tokens": "^4.5.0",
15
+ "@telus-uds/system-theme-tokens": "^4.7.0",
16
16
  "airbnb-prop-types": "^2.16.0",
17
17
  "css-mediaquery": "^0.1.2",
18
18
  "expo-document-picker": "^13.0.1",
@@ -84,6 +84,6 @@
84
84
  "standard-engine": {
85
85
  "skip": true
86
86
  },
87
- "version": "3.7.1",
87
+ "version": "3.9.0",
88
88
  "types": "types/index.d.ts"
89
89
  }
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "@gorhom/portal": "^1.0.14",
13
13
  "@react-native-picker/picker": "^2.9.0",
14
14
  "@telus-uds/system-constants": "^3.0.0",
15
- "@telus-uds/system-theme-tokens": "^4.5.0",
15
+ "@telus-uds/system-theme-tokens": "^4.7.0",
16
16
  "airbnb-prop-types": "^2.16.0",
17
17
  "css-mediaquery": "^0.1.2",
18
18
  "expo-document-picker": "^13.0.1",
@@ -84,6 +84,6 @@
84
84
  "standard-engine": {
85
85
  "skip": true
86
86
  },
87
- "version": "3.7.1",
87
+ "version": "3.9.0",
88
88
  "types": "types/index.d.ts"
89
89
  }
@@ -0,0 +1,65 @@
1
+ import React from 'react'
2
+ import { View, StyleSheet, Platform } from 'react-native'
3
+ import PropTypes from 'prop-types'
4
+ import StackView from '../StackView'
5
+ import Typography from '../Typography'
6
+ import RenderActivityIndicator from './RenderActivityIndicator'
7
+ import { BACKDROP_Z_INDEX, SPACE_WITH_LABEL, SPACE_WITHOUT_LABEL } from './shared'
8
+ import { activityIndicatorCommonProps } from './sharedProptypes'
9
+
10
+ const FullScreenIndicator = React.forwardRef(
11
+ ({ variantProps, label, labelPosition, labelMapping, backgroundColor, showLabel }, ref) => (
12
+ <View style={[staticStyles.fullScreenOverlay, { backgroundColor }]}>
13
+ <StackView
14
+ space={showLabel ? SPACE_WITH_LABEL : SPACE_WITHOUT_LABEL}
15
+ tokens={{ alignItems: 'center' }}
16
+ direction={labelMapping[labelPosition]}
17
+ >
18
+ <RenderActivityIndicator {...variantProps} ref={ref} label={label} />
19
+ {showLabel && <Typography>{label}</Typography>}
20
+ </StackView>
21
+ </View>
22
+ )
23
+ )
24
+
25
+ FullScreenIndicator.displayName = 'FullScreenIndicator'
26
+
27
+ const staticStyles = StyleSheet.create({
28
+ fullScreenOverlay: {
29
+ ...Platform.select({
30
+ web: {
31
+ position: 'fixed',
32
+ top: 0,
33
+ left: 0,
34
+ width: '100vw',
35
+ height: '100vh',
36
+ zIndex: BACKDROP_Z_INDEX,
37
+ justifyContent: 'center',
38
+ alignItems: 'center'
39
+ },
40
+ default: {
41
+ position: 'absolute',
42
+ top: 0,
43
+ left: 0,
44
+ right: 0,
45
+ bottom: 0,
46
+ justifyContent: 'center',
47
+ alignItems: 'center',
48
+ ...(Platform.OS === 'android' ? { elevation: 5 } : { zIndex: BACKDROP_Z_INDEX })
49
+ }
50
+ })
51
+ }
52
+ })
53
+
54
+ FullScreenIndicator.propTypes = {
55
+ /**
56
+ * Shared props
57
+ * */
58
+ ...activityIndicatorCommonProps,
59
+ /**
60
+ * Background color for overlay
61
+ * */
62
+ backgroundColor: PropTypes.string.isRequired
63
+ }
64
+
65
+ export default FullScreenIndicator
@@ -0,0 +1,47 @@
1
+ import React from 'react'
2
+ import { View, StyleSheet, Platform } from 'react-native'
3
+ import PropTypes from 'prop-types'
4
+ import StackView from '../StackView'
5
+ import Typography from '../Typography'
6
+ import RenderActivityIndicator from './RenderActivityIndicator'
7
+ import { activityIndicatorCommonProps } from './sharedProptypes'
8
+ import { SPACE_WITH_LABEL, SPACE_WITHOUT_LABEL } from './shared'
9
+
10
+ const InlineIndicator = React.forwardRef(
11
+ ({ variantProps, label, labelPosition, labelMapping, showLabel }, ref) => (
12
+ <View style={staticStyles.container}>
13
+ <StackView
14
+ space={showLabel ? SPACE_WITH_LABEL : SPACE_WITHOUT_LABEL}
15
+ direction={labelMapping[labelPosition]}
16
+ tokens={{ alignItems: 'center' }}
17
+ >
18
+ <RenderActivityIndicator {...variantProps} ref={ref} label={label} />
19
+ {showLabel && <Typography>{label}</Typography>}
20
+ </StackView>
21
+ </View>
22
+ )
23
+ )
24
+
25
+ InlineIndicator.displayName = 'InlineIndicator'
26
+
27
+ const staticStyles = StyleSheet.create({
28
+ container: {
29
+ flexDirection: 'column',
30
+ alignItems: 'center',
31
+ display: Platform.OS === 'web' ? 'inline-flex' : 'flex',
32
+ alignSelf: 'flex-start'
33
+ }
34
+ })
35
+
36
+ InlineIndicator.propTypes = {
37
+ /**
38
+ * Shared props
39
+ * */
40
+ ...activityIndicatorCommonProps,
41
+ /**
42
+ * Whether the indicator sits inline with text/other elements
43
+ * */
44
+ inline: PropTypes.bool
45
+ }
46
+
47
+ export default InlineIndicator
@@ -0,0 +1,140 @@
1
+ import React from 'react'
2
+ import { View, Platform, StyleSheet } from 'react-native'
3
+ import PropTypes from 'prop-types'
4
+ import StackView from '../StackView'
5
+ import Typography from '../Typography'
6
+ import RenderActivityIndicator from './RenderActivityIndicator'
7
+ import { BACKDROP_Z_INDEX, BACKDROP_OPACITY, SPACE_WITH_LABEL, SPACE_WITHOUT_LABEL } from './shared'
8
+ import { activityIndicatorCommonProps } from './sharedProptypes'
9
+
10
+ const recursiveMap = (nodeChildren, fn) =>
11
+ React.Children.map(nodeChildren, (child) => {
12
+ if (!React.isValidElement(child)) return child
13
+ if (child.props?.children) {
14
+ return fn(
15
+ React.cloneElement(child, {
16
+ children: recursiveMap(child.props.children, fn)
17
+ })
18
+ )
19
+ }
20
+ return fn(child)
21
+ })
22
+
23
+ const OverlayIndicator = React.forwardRef(
24
+ (
25
+ {
26
+ variantProps,
27
+ label,
28
+ labelPosition,
29
+ labelMapping,
30
+ children,
31
+ inline,
32
+ showLabel,
33
+ isActive = true
34
+ },
35
+ ref
36
+ ) => {
37
+ const safeChildren =
38
+ Platform.OS === 'web' && isActive
39
+ ? recursiveMap(children, (c) =>
40
+ React.isValidElement(c) ? React.cloneElement(c, { role: 'presentation' }) : c
41
+ )
42
+ : children
43
+
44
+ // inline + children
45
+ if (inline && children) {
46
+ return (
47
+ <View
48
+ style={[staticStyles.container, Platform.OS === 'web' && staticStyles.inlineContainer]}
49
+ >
50
+ <View style={staticStyles.backdropContainer}>{safeChildren}</View>
51
+ <View style={staticStyles.buttonOverlaySpinner}>
52
+ <RenderActivityIndicator {...variantProps} ref={ref} label={label} />
53
+ </View>
54
+ </View>
55
+ )
56
+ }
57
+
58
+ // overlay
59
+ return (
60
+ <View style={staticStyles.container}>
61
+ <View style={staticStyles.backdropContainer}>{safeChildren}</View>
62
+
63
+ <View
64
+ style={[
65
+ staticStyles.overlayContainer,
66
+ Platform.OS === 'web' && staticStyles.webOverlayContainer
67
+ ]}
68
+ >
69
+ <StackView
70
+ space={showLabel ? SPACE_WITH_LABEL : SPACE_WITHOUT_LABEL}
71
+ tokens={{ alignItems: 'center' }}
72
+ direction={labelMapping[labelPosition]}
73
+ >
74
+ <RenderActivityIndicator {...variantProps} ref={ref} label={label} />
75
+ {showLabel && <Typography>{label}</Typography>}
76
+ </StackView>
77
+ </View>
78
+ </View>
79
+ )
80
+ }
81
+ )
82
+
83
+ OverlayIndicator.displayName = 'OverlayIndicator'
84
+
85
+ const staticStyles = StyleSheet.create({
86
+ container: { position: 'relative' },
87
+ inlineContainer: { display: 'inline-flex' },
88
+ backdropContainer: { opacity: BACKDROP_OPACITY },
89
+ overlayContainer: {
90
+ position: 'absolute',
91
+ top: '50%',
92
+ left: '50%',
93
+ ...Platform.select({
94
+ ios: { zIndex: BACKDROP_Z_INDEX },
95
+ android: { elevation: 5 }
96
+ })
97
+ },
98
+
99
+ webOverlayContainer: {
100
+ transform: [{ translateX: '-50%' }, { translateY: '-50%' }],
101
+ zIndex: BACKDROP_Z_INDEX
102
+ },
103
+
104
+ buttonOverlaySpinner: {
105
+ position: 'absolute',
106
+ top: '50%',
107
+ left: '50%',
108
+ ...Platform.select({
109
+ web: {
110
+ transform: 'translate(-50%, -50%)',
111
+ zIndex: BACKDROP_Z_INDEX
112
+ },
113
+ default: {
114
+ transform: [{ translateX: -0.5 }, { translateY: -0.5 }],
115
+ zIndex: BACKDROP_Z_INDEX
116
+ }
117
+ })
118
+ }
119
+ })
120
+
121
+ OverlayIndicator.propTypes = {
122
+ /**
123
+ * Shared props
124
+ * */
125
+ ...activityIndicatorCommonProps,
126
+ /**
127
+ * Background color for overlay
128
+ * */
129
+ inline: PropTypes.bool,
130
+ /**
131
+ * Children on which the overlay is drawn
132
+ * */
133
+ children: PropTypes.node,
134
+ /**
135
+ * Controls whether the overlay is active and should apply the presentation role
136
+ * */
137
+ isActive: PropTypes.bool
138
+ }
139
+
140
+ export default OverlayIndicator