@telus-uds/components-base 1.8.5 → 1.11.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 (69) hide show
  1. package/CHANGELOG.md +47 -2
  2. package/component-docs.json +666 -27
  3. package/lib/Card/Card.js +9 -4
  4. package/lib/Carousel/Carousel.js +672 -0
  5. package/lib/Carousel/CarouselContext.js +59 -0
  6. package/lib/Carousel/CarouselItem/CarouselItem.js +92 -0
  7. package/lib/Carousel/CarouselItem/index.js +13 -0
  8. package/lib/Carousel/dictionary.js +23 -0
  9. package/lib/Carousel/index.js +32 -0
  10. package/lib/ExpandCollapse/Panel.js +10 -1
  11. package/lib/InputSupports/InputSupports.js +10 -3
  12. package/lib/InputSupports/useInputSupports.js +3 -2
  13. package/lib/Modal/Modal.js +4 -0
  14. package/lib/Skeleton/Skeleton.js +1 -0
  15. package/lib/StepTracker/StepTracker.js +15 -12
  16. package/lib/TextInput/TextInput.js +3 -12
  17. package/lib/TextInput/TextInputBase.js +9 -0
  18. package/lib/TextInput/propTypes.js +3 -8
  19. package/lib/index.js +23 -0
  20. package/lib/utils/props/clickProps.js +2 -2
  21. package/lib/utils/props/handlerProps.js +77 -31
  22. package/lib/utils/props/textInputProps.js +8 -1
  23. package/lib/utils/useScrollBlocking.js +66 -0
  24. package/lib/utils/useScrollBlocking.native.js +11 -0
  25. package/lib-module/Card/Card.js +5 -4
  26. package/lib-module/Carousel/Carousel.js +617 -0
  27. package/lib-module/Carousel/CarouselContext.js +43 -0
  28. package/lib-module/Carousel/CarouselItem/CarouselItem.js +75 -0
  29. package/lib-module/Carousel/CarouselItem/index.js +2 -0
  30. package/lib-module/Carousel/dictionary.js +16 -0
  31. package/lib-module/Carousel/index.js +2 -0
  32. package/lib-module/ExpandCollapse/Panel.js +9 -1
  33. package/lib-module/InputSupports/InputSupports.js +10 -3
  34. package/lib-module/InputSupports/useInputSupports.js +3 -2
  35. package/lib-module/Modal/Modal.js +3 -0
  36. package/lib-module/Skeleton/Skeleton.js +1 -0
  37. package/lib-module/StepTracker/StepTracker.js +14 -12
  38. package/lib-module/TextInput/TextInput.js +3 -9
  39. package/lib-module/TextInput/TextInputBase.js +10 -1
  40. package/lib-module/TextInput/propTypes.js +4 -8
  41. package/lib-module/index.js +2 -0
  42. package/lib-module/utils/props/clickProps.js +2 -2
  43. package/lib-module/utils/props/handlerProps.js +78 -31
  44. package/lib-module/utils/props/textInputProps.js +8 -1
  45. package/lib-module/utils/useScrollBlocking.js +58 -0
  46. package/lib-module/utils/useScrollBlocking.native.js +2 -0
  47. package/package.json +3 -3
  48. package/src/Card/Card.jsx +6 -4
  49. package/src/Carousel/Carousel.jsx +649 -0
  50. package/src/Carousel/CarouselContext.jsx +30 -0
  51. package/src/Carousel/CarouselItem/CarouselItem.jsx +66 -0
  52. package/src/Carousel/CarouselItem/index.js +3 -0
  53. package/src/Carousel/dictionary.js +16 -0
  54. package/src/Carousel/index.js +2 -0
  55. package/src/ExpandCollapse/Panel.jsx +8 -1
  56. package/src/InputSupports/InputSupports.jsx +18 -3
  57. package/src/InputSupports/useInputSupports.js +2 -2
  58. package/src/Modal/Modal.jsx +3 -1
  59. package/src/Skeleton/Skeleton.jsx +1 -0
  60. package/src/StepTracker/StepTracker.jsx +21 -8
  61. package/src/TextInput/TextInput.jsx +2 -9
  62. package/src/TextInput/TextInputBase.jsx +11 -1
  63. package/src/TextInput/propTypes.js +3 -7
  64. package/src/index.js +2 -0
  65. package/src/utils/props/clickProps.js +2 -2
  66. package/src/utils/props/handlerProps.js +64 -16
  67. package/src/utils/props/textInputProps.js +7 -1
  68. package/src/utils/useScrollBlocking.js +57 -0
  69. package/src/utils/useScrollBlocking.native.js +2 -0
@@ -0,0 +1,75 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import View from "react-native-web/dist/exports/View";
4
+ import Platform from "react-native-web/dist/exports/Platform";
5
+ import { layoutTags, getA11yPropsFromHtmlTag, selectSystemProps, a11yProps, viewProps } from '../../utils';
6
+ import { useCarousel } from '../CarouselContext';
7
+ import { jsx as _jsx } from "react/jsx-runtime";
8
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
9
+ /**
10
+ * `Carousel.Item` is used to wrap the content of an individual slide and is suppsoed to be the
11
+ * only top-level component passed to the `Carousel`
12
+ */
13
+
14
+ const CarouselItem = _ref => {
15
+ let {
16
+ children,
17
+ elementIndex,
18
+ tag = 'li',
19
+ hidden,
20
+ ...rest
21
+ } = _ref;
22
+ const {
23
+ width,
24
+ activeIndex
25
+ } = useCarousel();
26
+ const selectedProps = selectProps({ ...rest,
27
+ ...getA11yPropsFromHtmlTag(tag, rest.accessibilityRole)
28
+ });
29
+ const focusabilityProps = activeIndex === elementIndex ? {} : a11yProps.nonFocusableProps;
30
+ const style = {
31
+ width
32
+ };
33
+
34
+ if (hidden && Platform.OS === 'web') {
35
+ // On web, visibility: hidden makes all children non-focusable. It doesn't exist on native.
36
+ style.visibility = 'hidden';
37
+ }
38
+
39
+ return /*#__PURE__*/_jsx(View, {
40
+ style: style,
41
+ ...selectedProps,
42
+ ...focusabilityProps,
43
+ children: children
44
+ });
45
+ };
46
+
47
+ CarouselItem.propTypes = { ...selectedSystemPropTypes,
48
+
49
+ /**
50
+ * Index of the current slide
51
+ * Don't pass this prop when using `Carousel.Item` as it is already being passed by `Carousel` top-level component
52
+ */
53
+ elementIndex: PropTypes.number,
54
+
55
+ /**
56
+ * Provide custom accessibilityLabelledBy for Carousel slide
57
+ */
58
+ accessibilityLabelledBy: PropTypes.string,
59
+
60
+ /**
61
+ * Content of the slide
62
+ */
63
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
64
+
65
+ /**
66
+ * Sets the HTML tag of the outer container. By default `'li'` so that assistive technology sees
67
+ * the Carousel as a list of items.
68
+ *
69
+ * Carousel's innermost container defaults to `'ul'` which can be overridden. If the tag of either
70
+ * `Carousel` or `Carousel.Item` is overriden, the other should be too, to avoid producing invalid HTML.
71
+ */
72
+ tag: PropTypes.oneOf(layoutTags)
73
+ };
74
+ CarouselItem.displayName = 'Carousel.Item';
75
+ export default CarouselItem;
@@ -0,0 +1,2 @@
1
+ import CarouselItem from './CarouselItem';
2
+ export default CarouselItem;
@@ -0,0 +1,16 @@
1
+ // 'stepLabel' and 'stepTrackerLabel' are passed down to StepTracker
2
+ export default {
3
+ en: {
4
+ carouselLabel: '%{stepCount} items',
5
+ iconButtonLabel: 'Show %{itemLabel} %{targetStep} of %{stepCount}',
6
+ stepLabel: '%{itemLabel} %{stepNumber}',
7
+ stepTrackerLabel: '%{itemLabel} %{stepNumber} of %{stepCount}'
8
+ },
9
+ fr: {
10
+ // TODO: French translations here
11
+ carouselLabel: '(fr) %{stepCount} items',
12
+ iconButtonLabel: '(fr) Show %{itemLabel} %{targetStep} of %{stepCount}',
13
+ stepLabel: '(fr) %{itemLabel} %{stepNumber}',
14
+ stepTrackerLabel: '(fr) %{itemLabel} %{stepNumber} of %{stepCount}'
15
+ }
16
+ };
@@ -0,0 +1,2 @@
1
+ export * from './CarouselContext';
2
+ export { default as Carousel } from './Carousel';
@@ -3,6 +3,7 @@ import Animated from "react-native-web/dist/exports/Animated";
3
3
  import Platform from "react-native-web/dist/exports/Platform";
4
4
  import View from "react-native-web/dist/exports/View";
5
5
  import PropTypes from 'prop-types';
6
+ import ABBPropTypes from 'airbnb-prop-types';
6
7
  import ExpandCollapseControl from './Control';
7
8
  import { useThemeTokens } from '../ThemeProvider';
8
9
  import { a11yProps, getTokensPropType, selectSystemProps, useVerticalExpandAnimation, variantProp, viewProps } from '../utils';
@@ -49,6 +50,7 @@ const ExpandCollapsePanel = /*#__PURE__*/forwardRef((_ref2, ref) => {
49
50
  children,
50
51
  tokens,
51
52
  variant,
53
+ controlRef,
52
54
  ...rest
53
55
  } = _ref2;
54
56
  const [containerHeight, setContainerHeight] = useState(null);
@@ -91,6 +93,7 @@ const ExpandCollapsePanel = /*#__PURE__*/forwardRef((_ref2, ref) => {
91
93
  isExpanded: isExpanded,
92
94
  tokens: controlTokens,
93
95
  onPress: handleControlPress,
96
+ ref: controlRef,
94
97
  children: control
95
98
  }), /*#__PURE__*/_jsx(Animated.View, {
96
99
  ref: animatedRef,
@@ -146,6 +149,11 @@ ExpandCollapsePanel.propTypes = { ...selectedSystemPropTypes,
146
149
  /**
147
150
  * Optional theme token overrides that may be passed to the ExpandCollapseControl element.
148
151
  */
149
- controlTokens: getTokensPropType('ExpandCollapseControl')
152
+ controlTokens: getTokensPropType('ExpandCollapseControl'),
153
+
154
+ /**
155
+ * An optional ref to be attached to the control
156
+ */
157
+ controlRef: ABBPropTypes.ref()
150
158
  };
151
159
  export default ExpandCollapsePanel;
@@ -16,7 +16,8 @@ const InputSupports = /*#__PURE__*/forwardRef((_ref, ref) => {
16
16
  hintPosition = 'inline',
17
17
  feedback,
18
18
  tooltip,
19
- validation
19
+ validation,
20
+ nativeID
20
21
  } = _ref;
21
22
  const {
22
23
  space
@@ -30,7 +31,8 @@ const InputSupports = /*#__PURE__*/forwardRef((_ref, ref) => {
30
31
  feedback,
31
32
  hint,
32
33
  label,
33
- validation
34
+ validation,
35
+ nativeID
34
36
  });
35
37
  return /*#__PURE__*/_jsxs(StackView, {
36
38
  space: space,
@@ -91,6 +93,11 @@ InputSupports.propTypes = {
91
93
  /**
92
94
  * Use to visually mark an input as valid or invalid.
93
95
  */
94
- validation: PropTypes.oneOf(['error', 'success'])
96
+ validation: PropTypes.oneOf(['error', 'success']),
97
+
98
+ /**
99
+ * ID for DOM element on web
100
+ */
101
+ nativeID: PropTypes.string
95
102
  };
96
103
  export default InputSupports;
@@ -7,7 +7,8 @@ const useInputSupports = _ref => {
7
7
  label,
8
8
  feedback,
9
9
  validation,
10
- hint
10
+ hint,
11
+ nativeID
11
12
  } = _ref;
12
13
  const hasValidationError = validation === 'error';
13
14
  const inputId = useUniqueId('input');
@@ -22,7 +23,7 @@ const useInputSupports = _ref => {
22
23
  accessibilityInvalid: hasValidationError
23
24
  };
24
25
  return {
25
- inputId,
26
+ inputId: nativeID || inputId,
26
27
  hintId,
27
28
  feedbackId,
28
29
  a11yProps
@@ -10,6 +10,7 @@ import { a11yProps, copyPropTypes, getTokensPropType, selectSystemProps, useCopy
10
10
  import { useViewport } from '../ViewportProvider';
11
11
  import IconButton from '../IconButton';
12
12
  import dictionary from './dictionary';
13
+ import useScrollBlocking from '../utils/useScrollBlocking';
13
14
  import { jsx as _jsx } from "react/jsx-runtime";
14
15
  import { jsxs as _jsxs } from "react/jsx-runtime";
15
16
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
@@ -110,6 +111,7 @@ const Modal = /*#__PURE__*/forwardRef((_ref5, ref) => {
110
111
  viewport,
111
112
  maxWidth
112
113
  });
114
+ const modalRef = useScrollBlocking(isOpen);
113
115
  const {
114
116
  closeIcon: CloseIconComponent
115
117
  } = themeTokens;
@@ -140,6 +142,7 @@ const Modal = /*#__PURE__*/forwardRef((_ref5, ref) => {
140
142
  ...selectProps(rest),
141
143
  children: /*#__PURE__*/_jsxs(View, {
142
144
  style: [staticStyles.positioningContainer],
145
+ ref: modalRef,
143
146
  children: [/*#__PURE__*/_jsx(View, {
144
147
  style: [staticStyles.sizingContainer, selectContainerStyles(themeTokens)],
145
148
  pointerEvents: "box-none" // don't capture backdrop press events
@@ -19,6 +19,7 @@ const selectSkeletonStyles = _ref => {
19
19
  return {
20
20
  backgroundColor: color,
21
21
  borderRadius: radius,
22
+ maxWidth: '100%',
22
23
  ...fadeAnimation
23
24
  };
24
25
  };
@@ -109,7 +109,10 @@ const StepTracker = /*#__PURE__*/forwardRef((_ref4, ref) => {
109
109
  dictionary,
110
110
  copy
111
111
  });
112
- const stepTrackerLabel = getCopy('stepTrackerLabel').replace('%{stepNumber}', current < steps.length ? current + 1 : steps.length).replace('%{stepCount}', steps.length).replace('%{stepLabel}', current < steps.length ? steps[current] : steps[steps.length - 1]);
112
+ const stepTrackerLabel = showStepTrackerLabel ? getCopy('stepTrackerLabel').replace('%{stepNumber}', current < steps.length ? current + 1 : steps.length).replace('%{stepCount}', steps.length).replace('%{stepLabel}', current < steps.length ? steps[current] : steps[steps.length - 1]) : '';
113
+
114
+ const getStepLabel = index => themeTokens.showStepLabel ? getCopy('stepLabel').replace('%{stepNumber}', index + 1) : '';
115
+
113
116
  if (!steps.length) return null;
114
117
  const selectedProps = selectProps({
115
118
  accessibilityLabel: stepTrackerLabel,
@@ -135,7 +138,7 @@ const StepTracker = /*#__PURE__*/forwardRef((_ref4, ref) => {
135
138
  return /*#__PURE__*/_jsx(Step, {
136
139
  status: current,
137
140
  label: label,
138
- name: getCopy('stepLabel').replace('%{stepNumber}', index + 1),
141
+ name: getStepLabel(index),
139
142
  stepIndex: index,
140
143
  stepCount: steps.length,
141
144
  tokens: themeTokens
@@ -151,19 +154,18 @@ const StepTracker = /*#__PURE__*/forwardRef((_ref4, ref) => {
151
154
  })
152
155
  });
153
156
  });
154
- StepTracker.displayName = 'StepTracker';
157
+ StepTracker.displayName = 'StepTracker'; // If a language dictionary entry is provided, it must contain every key
158
+
159
+ const dictionaryContentShape = PropTypes.shape({
160
+ stepLabel: PropTypes.string.isRequired,
161
+ stepTrackerLabel: PropTypes.string.isRequired
162
+ });
155
163
  StepTracker.propTypes = { ...selectedSystemPropTypes,
156
164
  current: PropTypes.number,
157
- copy: PropTypes.oneOf(['en', 'fr']),
165
+ copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr']), dictionaryContentShape]),
158
166
  dictionary: PropTypes.shape({
159
- en: PropTypes.shape({
160
- stepLabel: PropTypes.string,
161
- stepTrackerLabel: PropTypes.string
162
- }),
163
- fr: PropTypes.shape({
164
- stepLabel: PropTypes.string,
165
- stepTrackerLabel: PropTypes.string
166
- })
167
+ en: dictionaryContentShape,
168
+ fr: dictionaryContentShape
167
169
  }),
168
170
  steps: PropTypes.arrayOf(PropTypes.string),
169
171
  tokens: getTokensPropType('StepTracker'),
@@ -1,5 +1,4 @@
1
1
  import React, { forwardRef } from 'react';
2
- import Platform from "react-native-web/dist/exports/Platform";
3
2
  import { a11yProps, focusHandlerProps, getTokensPropType, inputSupportsProps, selectSystemProps, textInputHandlerProps, textInputProps, variantProp, viewProps } from '../utils';
4
3
  import InputSupports from '../InputSupports';
5
4
  import TextInputBase from './TextInputBase';
@@ -29,15 +28,8 @@ const TextInput = /*#__PURE__*/forwardRef((_ref, ref) => {
29
28
  let {
30
29
  tokens,
31
30
  variant = {},
32
- pattern,
33
31
  ...rest
34
32
  } = _ref;
35
- React.useEffect(() => {
36
- if (Platform.OS === 'web' && pattern && ref.current) {
37
- // eslint-disable-next-line no-param-reassign
38
- ref.current.pattern = pattern;
39
- }
40
- }, [ref, pattern]);
41
33
  const {
42
34
  supportsProps,
43
35
  ...selectedProps
@@ -48,7 +40,9 @@ const TextInput = /*#__PURE__*/forwardRef((_ref, ref) => {
48
40
  validation: supportsProps.validation
49
41
  }
50
42
  };
51
- return /*#__PURE__*/_jsx(InputSupports, { ...supportsProps,
43
+ return /*#__PURE__*/_jsx(InputSupports, {
44
+ nativeID: selectedProps.nativeID,
45
+ ...supportsProps,
52
46
  children: _ref2 => {
53
47
  let {
54
48
  inputId,
@@ -1,4 +1,4 @@
1
- import React, { forwardRef, useState } from 'react';
1
+ import React, { forwardRef, useEffect, useState } from 'react';
2
2
  import Platform from "react-native-web/dist/exports/Platform";
3
3
  import StyleSheet from "react-native-web/dist/exports/StyleSheet";
4
4
  import NativeTextInput from "react-native-web/dist/exports/TextInput";
@@ -133,6 +133,7 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref5, ref) => {
133
133
  onBlur,
134
134
  onMouseOver,
135
135
  onMouseOut,
136
+ pattern,
136
137
  tokens,
137
138
  variant = {},
138
139
  ...rest
@@ -171,6 +172,14 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref5, ref) => {
171
172
  onChange,
172
173
  readOnly
173
174
  });
175
+ const element = ref === null || ref === void 0 ? void 0 : ref.current;
176
+ useEffect(() => {
177
+ if (Platform.OS === 'web' && pattern && element) {
178
+ // React Native Web doesn't support `pattern`, so we have to attach it via a ref,
179
+ // which a `pattern` user must provide anyway to call .checkValidity() on the element.
180
+ element.pattern = pattern;
181
+ }
182
+ }, [element, pattern]);
174
183
 
175
184
  const handleChangeText = event => {
176
185
  var _event$nativeEvent, _event$target;
@@ -1,5 +1,6 @@
1
- import PropTypes from 'prop-types';
2
- import Platform from "react-native-web/dist/exports/Platform";
1
+ import PropTypes from 'prop-types'; // These are prop types specific to UDS TextInput; see also ../utils/props/textInputProps
2
+ // for generic React Native props and HTML input attrs that are passed through.
3
+
3
4
  const textInputPropTypes = {
4
5
  /**
5
6
  * If the input's state is to be controlled by a parent component, use this prop
@@ -27,11 +28,6 @@ const textInputPropTypes = {
27
28
  * Use to react upon input's value changes. Required when the `value` prop is set.
28
29
  * Will receive the input's value as an argument.
29
30
  */
30
- onChange: PropTypes.func,
31
- ...Platform.select({
32
- web: {
33
- pattern: PropTypes.string
34
- }
35
- })
31
+ onChange: PropTypes.func
36
32
  };
37
33
  export default textInputPropTypes;
@@ -3,6 +3,7 @@ export { default as ActivityIndicator } from './ActivityIndicator';
3
3
  export { default as Box } from './Box';
4
4
  export * from './Button';
5
5
  export { default as Card, PressableCardBase } from './Card';
6
+ export * from './Carousel';
6
7
  export { default as Checkbox } from './Checkbox';
7
8
  export * from './Checkbox';
8
9
  export { default as Divider } from './Divider';
@@ -16,6 +17,7 @@ export { default as Icon } from './Icon';
16
17
  export * from './Icon';
17
18
  export { default as IconButton } from './IconButton';
18
19
  export { default as InputLabel } from './InputLabel';
20
+ export { default as InputSupports } from './InputSupports';
19
21
  export * from './Link';
20
22
  export { default as List, ListItem, ListBase } from './List';
21
23
  export { default as Modal } from './Modal';
@@ -1,8 +1,8 @@
1
1
  import PropTypes from 'prop-types';
2
2
  const clickHandlerMapping = {
3
3
  onClick: 'onPress',
4
- mouseDown: 'onPressIn',
5
- mouseUp: 'onPressOut'
4
+ onMouseDown: 'onPressIn',
5
+ onMouseUp: 'onPressOut'
6
6
  };
7
7
  export default {
8
8
  /**
@@ -1,5 +1,7 @@
1
1
  import PropTypes from 'prop-types';
2
- export const focusHandlerProps = {
2
+ import Platform from "react-native-web/dist/exports/Platform";
3
+ import getPropSelector from './getPropSelector';
4
+ const focusHandlerProps = {
3
5
  types: {
4
6
  /**
5
7
  * onBlur handler
@@ -10,19 +12,10 @@ export const focusHandlerProps = {
10
12
  * onFocus handler
11
13
  */
12
14
  onFocus: PropTypes.func
13
- },
14
- select: _ref => {
15
- let {
16
- onBlur,
17
- onFocus
18
- } = _ref;
19
- return {
20
- onBlur,
21
- onFocus
22
- };
23
15
  }
24
16
  };
25
- export const textInputHandlerProps = {
17
+ focusHandlerProps.select = getPropSelector(focusHandlerProps.types);
18
+ const textInputHandlerProps = {
26
19
  types: {
27
20
  /**
28
21
  * onChange handler
@@ -42,24 +35,78 @@ export const textInputHandlerProps = {
42
35
  /**
43
36
  * onSubmitEditing handler
44
37
  */
45
- onSubmitEditing: PropTypes.func
46
- },
47
- select: _ref2 => {
48
- let {
49
- onChange,
50
- onChangeText,
51
- onSubmit,
52
- onSubmitEditing
53
- } = _ref2;
54
- return {
55
- onChange,
56
- onChangeText,
57
- onSubmit,
58
- onSubmitEditing
59
- };
38
+ onSubmitEditing: PropTypes.func,
39
+
40
+ /**
41
+ * onContentSizeChange handler
42
+ */
43
+ onContentSizeChange: PropTypes.func,
44
+
45
+ /**
46
+ * onEndEditing handler
47
+ */
48
+ onEndEditing: PropTypes.func,
49
+
50
+ /**
51
+ * onScroll handler
52
+ */
53
+ onScroll: PropTypes.func,
54
+
55
+ /**
56
+ * onSelectionChange handler
57
+ */
58
+ onSelectionChange: PropTypes.func,
59
+
60
+ /**
61
+ * onKeyPress handler
62
+ */
63
+ onKeyPress: PropTypes.func,
64
+
65
+ /**
66
+ * onKeyUp handler (only supported on Web)
67
+ */
68
+ onKeyUp: PropTypes.func,
69
+
70
+ /**
71
+ * onKeyDown handler (only supported on Web)
72
+ */
73
+ onKeyDown: PropTypes.func
60
74
  }
61
75
  };
62
- export default {
63
- focusHandlerProps,
64
- textInputHandlerProps
65
- };
76
+ const selectTextInputHandlers = getPropSelector(textInputHandlerProps.types);
77
+
78
+ textInputHandlerProps.select = props => {
79
+ // Support for onKeyPress/onKeyUp/onKeyDown is inconsistent between React Native and React Native Web
80
+ const {
81
+ onKeyPress,
82
+ onKeyUp,
83
+ onKeyDown,
84
+ ...resolvedProps
85
+ } = selectTextInputHandlers(props);
86
+
87
+ if (onKeyPress || onKeyUp || onKeyDown) {
88
+ if (Platform.OS !== 'web') {
89
+ // React Native only supports onKeyPress. Call any key handlers supplied in expected order.
90
+ resolvedProps.onKeyPress = event => {
91
+ if (typeof onKeyDown === 'function') onKeyDown(event);
92
+ if (typeof onKeyPress === 'function') onKeyPress(event);
93
+ if (typeof onKeyUp === 'function') onKeyUp(event);
94
+ };
95
+ } else {
96
+ // React Native Web supports onKeyUp the normal way.
97
+ if (onKeyUp) resolvedProps.onKeyUp = onKeyUp; // React Native Web doesn't support the `onKeyDown` prop name, but maps a supplied onKeyPress handler
98
+ // to the onKeyDown event and calls it with a keydown event. Make React Native Web call either or both.
99
+
100
+ if (onKeyPress || onKeyDown) {
101
+ resolvedProps.onKeyPress = event => {
102
+ if (typeof onKeyDown === 'function') onKeyDown(event);
103
+ if (typeof onKeyPress === 'function') onKeyPress(event);
104
+ };
105
+ }
106
+ }
107
+ }
108
+
109
+ return resolvedProps;
110
+ };
111
+
112
+ export { focusHandlerProps, textInputHandlerProps };
@@ -135,7 +135,14 @@ const crossPlatform = { ...textProps,
135
135
  const webOnly = {
136
136
  disabled: PropTypes.bool,
137
137
  dir: PropTypes.oneOf(['auto', 'ltr', 'rtl']),
138
- lang: PropTypes.string
138
+ lang: PropTypes.string,
139
+
140
+ /**
141
+ * Sets the HTML input `pattern` attr. Not supported by React Native Web, but is supported by UDS.
142
+ * Must also pass in a ref and check validity by calling the HTML element's checkValidity method:
143
+ * https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/checkValidity
144
+ */
145
+ pattern: PropTypes.string
139
146
  };
140
147
  /**
141
148
  * These props are supported in React Native but not React Native Web.
@@ -0,0 +1,58 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+
3
+ const addScrollBlocking = (preventScrolling, stopPropagation, ref) => {
4
+ var _ref$current;
5
+
6
+ document.body.addEventListener('touchmove', preventScrolling, {
7
+ passive: false
8
+ });
9
+ (_ref$current = ref.current) === null || _ref$current === void 0 ? void 0 : _ref$current.addEventListener('touchmove', stopPropagation);
10
+ document.body.style.overflow = 'hidden';
11
+ };
12
+
13
+ const removeScrollBlocking = (preventScrolling, stopPropagation, ref) => {
14
+ var _ref$current2;
15
+
16
+ document.body.removeEventListener('touchmove', preventScrolling);
17
+ (_ref$current2 = ref.current) === null || _ref$current2 === void 0 ? void 0 : _ref$current2.removeEventListener('touchmove', stopPropagation);
18
+ document.body.style.overflow = 'inherit';
19
+ };
20
+ /**
21
+ * Disables scrolling when passed `true` or an array where all items are `true`.
22
+ *
23
+ * Returns an optional callback ref. Pass this to an element if it or its children
24
+ * should allow touch-based scrolling within that element's bounds.
25
+ *
26
+ * @param {boolean | boolean[]} conditionProps
27
+ * @returns
28
+ */
29
+
30
+
31
+ const useScrollBlocking = conditionProps => {
32
+ // useRef refs are null on first render and don't trigger a re-render when they get their
33
+ // element. Force re-run when ref mounts to ensure the stopPropagation listener is attached.
34
+ const ref = useRef();
35
+ const [refIsMounted, setRefIsMounted] = useState(false);
36
+ const callbackRef = useCallback(element => {
37
+ ref.current = element;
38
+ setRefIsMounted(Boolean(element));
39
+ }, []);
40
+ const conditionsMet = Array.isArray(conditionProps) ? conditionProps.every(condition => condition) : Boolean(conditionProps);
41
+ const preventScrolling = useCallback(event => event.preventDefault(), []);
42
+ const stopPropagation = useCallback(event => event.stopPropagation(), []);
43
+ useEffect(() => {
44
+ const cleanup = () => removeScrollBlocking(preventScrolling, stopPropagation, ref);
45
+
46
+ if (conditionsMet) {
47
+ addScrollBlocking(preventScrolling, stopPropagation, ref);
48
+ } else {
49
+ cleanup();
50
+ }
51
+
52
+ return cleanup; // preventScrolling and stopPropagation are stable callbacks with no deps, so this
53
+ // will re-run when conditionsMet or refIsMounted flip between true and false.
54
+ }, [preventScrolling, conditionsMet, stopPropagation, refIsMounted]);
55
+ return callbackRef;
56
+ };
57
+
58
+ export default useScrollBlocking;
@@ -0,0 +1,2 @@
1
+ // This is a no-op to emphasize that the original hook is web-only
2
+ export default (() => {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telus-uds/components-base",
3
- "version": "1.8.5",
3
+ "version": "1.11.0",
4
4
  "description": "Base components",
5
5
  "keywords": [
6
6
  "base"
@@ -47,7 +47,7 @@
47
47
  "react": "^17.0.2",
48
48
  "react-dom": "^17.0.2",
49
49
  "react-native": "*",
50
- "react-native-web": "^0.17.0"
50
+ "react-native-web": "~0.17.5"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@storybook/addon-a11y": "^6.5.6",
@@ -66,7 +66,7 @@
66
66
  "dependencies": {
67
67
  "airbnb-prop-types": "^2.16.0",
68
68
  "@telus-uds/system-constants": "^1.0.4",
69
- "@telus-uds/system-theme-tokens": "^2.0.2",
69
+ "@telus-uds/system-theme-tokens": "^2.1.0",
70
70
  "lodash.debounce": "^4.0.8",
71
71
  "lodash.merge": "^4.6.2",
72
72
  "prop-types": "^15.7.2",
package/src/Card/Card.jsx CHANGED
@@ -1,4 +1,4 @@
1
- import React from 'react'
1
+ import React, { forwardRef } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
 
4
4
  import { useThemeTokens } from '../ThemeProvider'
@@ -57,16 +57,18 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, vie
57
57
  * you automatically make inaccessible its children, which may or may not be appropriate
58
58
  * depending on what you are trying to achieve.
59
59
  */
60
- const Card = ({ children, tokens, variant, dataSet, ...rest }) => {
60
+ const Card = forwardRef(({ children, tokens, variant, dataSet, ...rest }, ref) => {
61
61
  const viewport = useViewport()
62
62
  const themeTokens = useThemeTokens('Card', tokens, variant, { viewport })
63
63
 
64
64
  return (
65
- <CardBase tokens={themeTokens} dataSet={dataSet} {...selectProps(rest)}>
65
+ <CardBase ref={ref} tokens={themeTokens} dataSet={dataSet} {...selectProps(rest)}>
66
66
  {children}
67
67
  </CardBase>
68
68
  )
69
- }
69
+ })
70
+
71
+ Card.displayName = 'Card'
70
72
 
71
73
  Card.propTypes = {
72
74
  ...selectedSystemPropTypes,