@telus-uds/components-base 1.80.1 → 1.82.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 (74) hide show
  1. package/CHANGELOG.md +30 -2
  2. package/lib/Autocomplete/Autocomplete.js +3 -1
  3. package/lib/Badge/Badge.js +1 -10
  4. package/lib/Fieldset/Fieldset.js +3 -1
  5. package/lib/Fieldset/FieldsetContainer.js +9 -2
  6. package/lib/Fieldset/FieldsetContainer.native.js +7 -1
  7. package/lib/Icon/Icon.js +24 -2
  8. package/lib/Listbox/Listbox.js +7 -1
  9. package/lib/Modal/Modal.js +40 -4
  10. package/lib/Modal/WebModal.js +73 -0
  11. package/lib/PriceLockup/PriceLockup.js +4 -1
  12. package/lib/PriceLockup/utils/renderFootnoteContent.js +2 -2
  13. package/lib/PriceLockup/utils/renderFootnoteLinks.js +2 -2
  14. package/lib/PriceLockup/utils/renderPrice.js +2 -2
  15. package/lib/PriceLockup/utils/renderTypography.js +1 -1
  16. package/lib/ProductCard/ProductCard.js +238 -0
  17. package/lib/ProductCard/dictionary.js +45 -0
  18. package/lib/ProductCard/index.js +10 -0
  19. package/lib/ProductCardGroup/ProductCardGroup.js +79 -0
  20. package/lib/ProductCardGroup/index.js +10 -0
  21. package/lib/TextInput/TextInput.js +1 -0
  22. package/lib/TextInput/TextInputBase.js +4 -1
  23. package/lib/index.js +16 -0
  24. package/lib/utils/ssr-media-query/create-stylesheet/index.js +2 -1
  25. package/lib/utils/ssr-media-query/utils/common.js +21 -2
  26. package/lib-module/Autocomplete/Autocomplete.js +3 -1
  27. package/lib-module/Badge/Badge.js +1 -10
  28. package/lib-module/Fieldset/Fieldset.js +3 -1
  29. package/lib-module/Fieldset/FieldsetContainer.js +7 -2
  30. package/lib-module/Fieldset/FieldsetContainer.native.js +10 -3
  31. package/lib-module/Icon/Icon.js +24 -2
  32. package/lib-module/Listbox/Listbox.js +7 -1
  33. package/lib-module/Modal/Modal.js +42 -5
  34. package/lib-module/Modal/WebModal.js +65 -0
  35. package/lib-module/PriceLockup/PriceLockup.js +4 -1
  36. package/lib-module/PriceLockup/utils/renderFootnoteContent.js +2 -2
  37. package/lib-module/PriceLockup/utils/renderFootnoteLinks.js +2 -2
  38. package/lib-module/PriceLockup/utils/renderPrice.js +2 -2
  39. package/lib-module/PriceLockup/utils/renderTypography.js +1 -1
  40. package/lib-module/ProductCard/ProductCard.js +231 -0
  41. package/lib-module/ProductCard/dictionary.js +38 -0
  42. package/lib-module/ProductCard/index.js +2 -0
  43. package/lib-module/ProductCardGroup/ProductCardGroup.js +69 -0
  44. package/lib-module/ProductCardGroup/index.js +2 -0
  45. package/lib-module/TextInput/TextInput.js +1 -0
  46. package/lib-module/TextInput/TextInputBase.js +4 -1
  47. package/lib-module/index.js +2 -0
  48. package/lib-module/utils/ssr-media-query/create-stylesheet/index.js +3 -2
  49. package/lib-module/utils/ssr-media-query/utils/common.js +19 -1
  50. package/package.json +2 -2
  51. package/src/Autocomplete/Autocomplete.jsx +4 -1
  52. package/src/Badge/Badge.jsx +7 -10
  53. package/src/Fieldset/Fieldset.jsx +3 -1
  54. package/src/Fieldset/FieldsetContainer.jsx +8 -1
  55. package/src/Fieldset/FieldsetContainer.native.jsx +7 -2
  56. package/src/Icon/Icon.jsx +30 -2
  57. package/src/Listbox/Listbox.jsx +6 -1
  58. package/src/Modal/Modal.jsx +42 -3
  59. package/src/Modal/WebModal.jsx +60 -0
  60. package/src/PriceLockup/PriceLockup.jsx +8 -2
  61. package/src/PriceLockup/utils/renderFootnoteContent.jsx +2 -2
  62. package/src/PriceLockup/utils/renderFootnoteLinks.jsx +2 -2
  63. package/src/PriceLockup/utils/renderPrice.jsx +2 -2
  64. package/src/PriceLockup/utils/renderTypography.jsx +1 -1
  65. package/src/ProductCard/ProductCard.jsx +193 -0
  66. package/src/ProductCard/dictionary.js +38 -0
  67. package/src/ProductCard/index.js +3 -0
  68. package/src/ProductCardGroup/ProductCardGroup.jsx +75 -0
  69. package/src/ProductCardGroup/index.js +3 -0
  70. package/src/TextInput/TextInput.jsx +1 -0
  71. package/src/TextInput/TextInputBase.jsx +4 -1
  72. package/src/index.js +2 -0
  73. package/src/utils/ssr-media-query/create-stylesheet/index.js +3 -2
  74. package/src/utils/ssr-media-query/utils/common.js +19 -1
@@ -22,6 +22,25 @@ const Icon = /*#__PURE__*/forwardRef((_ref, ref) => {
22
22
  size: size,
23
23
  color: themeTokens.color
24
24
  });
25
+ const paddingStyles = variant !== null && variant !== void 0 && variant.padding ? {
26
+ padding: themeTokens.width,
27
+ width: themeTokens.size + themeTokens.width * 2,
28
+ // sets the diameter of the circle which is the size of the icon plus twice the general padding established to obtain a perfect circle
29
+ height: themeTokens.size + themeTokens.width * 2
30
+ } : {};
31
+ const getIconContentForMobile = () => {
32
+ if (Object.keys(paddingStyles).length) {
33
+ return /*#__PURE__*/_jsx(View, {
34
+ style: {
35
+ backgroundColor: themeTokens.backgroundColor,
36
+ borderRadius: themeTokens.borderRadius,
37
+ ...paddingStyles
38
+ },
39
+ children: iconContent
40
+ });
41
+ }
42
+ return iconContent;
43
+ };
25
44
  return Platform.OS === 'web' ? /*#__PURE__*/_jsx(View, {
26
45
  ref: ref
27
46
  // eslint-disable-next-line react-native/no-inline-styles
@@ -31,11 +50,14 @@ const Icon = /*#__PURE__*/forwardRef((_ref, ref) => {
31
50
  // https://github.com/telus/universal-design-system/issues/487
32
51
  transition: 'transform 200ms, color 200ms',
33
52
  transform: [themeTokens.scale ? `scale(${themeTokens.scale})` : '', themeTokens.translateX ? `translateX(${themeTokens.translateX}px)` : '', themeTokens.translateY ? `translateY(${themeTokens.translateY}px)` : ''].filter(exists => exists).join(' '),
34
- ...style
53
+ ...style,
54
+ backgroundColor: themeTokens.backgroundColor,
55
+ borderRadius: themeTokens.borderRadius,
56
+ ...paddingStyles
35
57
  },
36
58
  dataSet: dataSet,
37
59
  children: iconContent
38
- }) : iconContent;
60
+ }) : getIconContentForMobile();
39
61
  });
40
62
  Icon.displayName = 'Icon';
41
63
  export const iconComponentPropTypes = {
@@ -67,8 +67,14 @@ const Listbox = _ref => {
67
67
  // Return focus to the dropdown control after leaving the last item
68
68
  parentRef === null || parentRef === void 0 ? void 0 : (_parentRef$current3 = parentRef.current) === null || _parentRef$current3 === void 0 ? void 0 : _parentRef$current3.focus();
69
69
  if (onClose) onClose(event);
70
+ } else if (!nextItemRef && firstItemRef) {
71
+ var _firstItemRef$current;
72
+ // If the last item is focused, move the focus to the first one
73
+ event.preventDefault();
74
+ setFocusedIndex(0);
75
+ (_firstItemRef$current = firstItemRef.current) === null || _firstItemRef$current === void 0 ? void 0 : _firstItemRef$current.focus();
70
76
  }
71
- }, [focusedIndex, onClose, parentRef]);
77
+ }, [focusedIndex, onClose, parentRef, firstItemRef]);
72
78
 
73
79
  // Add listeners for mouse clicks outside and for key presses
74
80
  useEffect(() => {
@@ -1,4 +1,4 @@
1
- import React, { forwardRef } from 'react';
1
+ import React, { forwardRef, useEffect, useRef } from 'react';
2
2
  import StyleSheet from "react-native-web/dist/exports/StyleSheet";
3
3
  import TouchableWithoutFeedback from "react-native-web/dist/exports/TouchableWithoutFeedback";
4
4
  import View from "react-native-web/dist/exports/View";
@@ -13,8 +13,10 @@ import IconButton from '../IconButton';
13
13
  import dictionary from './dictionary';
14
14
  import useScrollBlocking from '../utils/useScrollBlocking';
15
15
  import ModalContent from './ModalContent';
16
+ import WebModal from './WebModal';
16
17
  import { jsx as _jsx } from "react/jsx-runtime";
17
18
  import { jsxs as _jsxs } from "react/jsx-runtime";
19
+ import { Fragment as _Fragment } from "react/jsx-runtime";
18
20
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
19
21
  const selectContainerStyles = _ref => {
20
22
  let {
@@ -143,12 +145,32 @@ const Modal = /*#__PURE__*/forwardRef((_ref5, ref) => {
143
145
  // Show the custom react node passed to `closedButton` or the default close button if `closeButton` is `undefined`.
144
146
  // Hide the close button if `closeButton` is `null`.
145
147
  const showCloseButton = closeButton !== null;
148
+
149
+ // These refs are used to manage focus in the web modal container
150
+ const focusTrapRef = useRef(null);
151
+ const closeButtonRef = useRef(null);
152
+ useEffect(() => {
153
+ if (Platform.OS === 'web') {
154
+ const handleFocus = () => {
155
+ // If the focus is on the last item of the web modal container, move it to the close button
156
+ if (document.activeElement === focusTrapRef.current) {
157
+ closeButtonRef.current.focus();
158
+ }
159
+ return undefined;
160
+ };
161
+
162
+ // Add an event listener to manage focus in the web modal container
163
+ document.addEventListener('focusin', handleFocus);
164
+
165
+ // Clean up the event listener
166
+ return () => document.removeEventListener('focusin', handleFocus);
167
+ }
168
+ return undefined;
169
+ }, []);
146
170
  if (!isOpen) {
147
171
  return null;
148
172
  }
149
- return /*#__PURE__*/_jsx(NativeModal, {
150
- transparent: true,
151
- ...selectProps(rest),
173
+ const content = /*#__PURE__*/_jsx(_Fragment, {
152
174
  children: /*#__PURE__*/_jsxs(ScrollView, {
153
175
  contentContainerStyle: [staticStyles.positioningContainer],
154
176
  ref: modalRef,
@@ -166,7 +188,8 @@ const Modal = /*#__PURE__*/forwardRef((_ref5, ref) => {
166
188
  onPress: handleClose,
167
189
  icon: CloseIconComponent,
168
190
  accessibilityRole: "button",
169
- accessibilityLabel: closeLabel
191
+ accessibilityLabel: closeLabel,
192
+ ref: closeButtonRef
170
193
  })
171
194
  }), /*#__PURE__*/_jsx(ModalContent, {
172
195
  tokens: tokens,
@@ -198,6 +221,20 @@ const Modal = /*#__PURE__*/forwardRef((_ref5, ref) => {
198
221
  })]
199
222
  })
200
223
  });
224
+ if (Platform.OS === 'web') {
225
+ return /*#__PURE__*/_jsxs(WebModal, {
226
+ ...selectProps(rest),
227
+ children: [content, /*#__PURE__*/_jsx(View, {
228
+ accessibilityRole: "button",
229
+ ref: focusTrapRef
230
+ })]
231
+ });
232
+ }
233
+ return /*#__PURE__*/_jsx(NativeModal, {
234
+ transparent: true,
235
+ ...selectProps(rest),
236
+ children: content
237
+ });
201
238
  });
202
239
  Modal.displayName = 'Modal';
203
240
  Modal.propTypes = {
@@ -0,0 +1,65 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import View from "react-native-web/dist/exports/View";
4
+ import StyleSheet from "react-native-web/dist/exports/StyleSheet";
5
+ import { a11yProps, selectSystemProps, viewProps } from '../utils';
6
+ import { jsx as _jsx } from "react/jsx-runtime";
7
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
8
+
9
+ /**
10
+ * WebModal component.
11
+ *
12
+ * @component
13
+ * @param {Object} props - The component props.
14
+ * @param {ReactNode} props.children - The content of the modal.
15
+ * @returns {JSX.Element} The rendered WebModal component.
16
+ */
17
+ const WebModal = _ref => {
18
+ let {
19
+ children,
20
+ ...rest
21
+ } = _ref;
22
+ return /*#__PURE__*/_jsx(View, {
23
+ style: staticStyles.container,
24
+ ...selectProps(rest),
25
+ children: /*#__PURE__*/_jsx(View, {
26
+ style: staticStyles.content,
27
+ children: children
28
+ })
29
+ });
30
+ };
31
+ WebModal.propTypes = {
32
+ ...selectedSystemPropTypes,
33
+ // children to be rendered within the modal
34
+ children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)])
35
+ };
36
+ const staticStyles = StyleSheet.create({
37
+ container: {
38
+ position: 'fixed',
39
+ backgroundColor: 'rgba(0, 0, 0, 0)',
40
+ top: 0,
41
+ right: 0,
42
+ left: 0,
43
+ bottom: 0,
44
+ alignItems: 'stretch',
45
+ boxSizing: 'border-box',
46
+ display: 'flex',
47
+ flexBasis: 'auto',
48
+ flexDirection: 'column',
49
+ flexShrink: 0,
50
+ listStyle: 'none',
51
+ margin: 0,
52
+ minHeight: 0,
53
+ minWidth: 0,
54
+ padding: 0,
55
+ textDecoration: 'none',
56
+ zIndex: 1
57
+ },
58
+ content: {
59
+ flex: 1,
60
+ flexGrow: 1,
61
+ flexShrink: 1,
62
+ flexBasis: 0
63
+ }
64
+ });
65
+ export default WebModal;
@@ -112,7 +112,6 @@ const PriceLockup = _ref7 => {
112
112
  bottomTextMarginTop,
113
113
  priceMarginBottom,
114
114
  bottomLinksMarginLeft,
115
- topTextMarginBottom,
116
115
  fontColor,
117
116
  dividerColor,
118
117
  ...themeTokens
@@ -134,6 +133,7 @@ const PriceLockup = _ref7 => {
134
133
  ...selectProps(rest)
135
134
  }],
136
135
  children: [topText ? /*#__PURE__*/_jsx(View, {
136
+ style: staticStyles.topText,
137
137
  children: renderTypography(topText, topTextTypographyTokens)
138
138
  }) : null, renderPrice(price, rateText, ratePosition, signDirection, currencySymbol, currencySymbolTypographyTokens, amountTypographyTokens, centsTypographyTokens, rateTypographyTokens, fontColor, strikeThrough, a11yText, bottomText, bottomLinksMarginLeft, footnoteLinks, onClickFootnote, themeTokens), bottomText ? /*#__PURE__*/_jsxs(_Fragment, {
139
139
  children: [/*#__PURE__*/_jsx(Divider, {
@@ -210,5 +210,8 @@ export default PriceLockup;
210
210
  const staticStyles = StyleSheet.create({
211
211
  priceLockupContainer: {
212
212
  alignSelf: 'flex-start'
213
+ },
214
+ topText: {
215
+ marginBottom: 4
213
216
  }
214
217
  });
@@ -44,13 +44,13 @@ const renderFootnoteContent = (footnoteMarginTop, bottomTextMarginTop, bottomTex
44
44
  bottomTextMarginTop
45
45
  }),
46
46
  children: [renderTypography(bottomText, bottomTextTypographyTokens, undefined, fontColor), ' ']
47
- }), footnoteLinks.length <= MAX_FOOTNOTE_LINKS_ALLOWED ? /*#__PURE__*/_jsx(View, {
47
+ }), (footnoteLinks === null || footnoteLinks === void 0 ? void 0 : footnoteLinks.length) <= MAX_FOOTNOTE_LINKS_ALLOWED ? /*#__PURE__*/_jsx(View, {
48
48
  style: [staticStyles.footnoteLinkContainer, selectFootnoteLinksStyles({
49
49
  bottomLinksMarginLeft
50
50
  })],
51
51
  children: renderFootnoteLinks(footnoteLinks, themeTokens, onClickFootnote)
52
52
  }) : null]
53
- }), footnoteLinks.length > MAX_FOOTNOTE_LINKS_ALLOWED ? /*#__PURE__*/_jsx(View, {
53
+ }), (footnoteLinks === null || footnoteLinks === void 0 ? void 0 : footnoteLinks.length) > MAX_FOOTNOTE_LINKS_ALLOWED ? /*#__PURE__*/_jsx(View, {
54
54
  style: staticStyles.verticalFootnoteLinkContainer,
55
55
  children: renderFootnoteLinks(footnoteLinks, themeTokens, onClickFootnote)
56
56
  }) : null]
@@ -11,7 +11,7 @@ const selectFootnoteLinkStyles = (_ref, footnoteLinks) => {
11
11
  } = _ref;
12
12
  // This is used to apply the proper line height when there is 4 or more footnote links
13
13
  const MAX_FOOTNOTE_LINKS_ALLOWED = 3;
14
- const lineHeight = footnoteLinks.length > MAX_FOOTNOTE_LINKS_ALLOWED ? footnoteLinkFontSize * footnoteLinkLineHeight : undefined;
14
+ const lineHeight = (footnoteLinks === null || footnoteLinks === void 0 ? void 0 : footnoteLinks.length) > MAX_FOOTNOTE_LINKS_ALLOWED ? footnoteLinkFontSize * footnoteLinkLineHeight : undefined;
15
15
  return {
16
16
  color: footnoteLinkColor,
17
17
  fontName: footnoteLinkFontName,
@@ -20,7 +20,7 @@ const selectFootnoteLinkStyles = (_ref, footnoteLinks) => {
20
20
  fontSize: footnoteLinkFontSize
21
21
  };
22
22
  };
23
- const renderFootnoteLinks = (footnoteLinks, themeTokens, onClickFootnote) => footnoteLinks && footnoteLinks.length > 0 ? /*#__PURE__*/_jsx(FootnoteLink, {
23
+ const renderFootnoteLinks = (footnoteLinks, themeTokens, onClickFootnote) => (footnoteLinks === null || footnoteLinks === void 0 ? void 0 : footnoteLinks.length) > 0 ? /*#__PURE__*/_jsx(FootnoteLink, {
24
24
  tokens: selectFootnoteLinkStyles(themeTokens, footnoteLinks),
25
25
  content: footnoteLinks,
26
26
  onClick: onClickFootnote
@@ -68,13 +68,13 @@ const renderPrice = (price, rateText, ratePosition, signDirection, currencySymbo
68
68
  }), rateText ? /*#__PURE__*/_jsx(Text, {
69
69
  style: ratePosition === 'bottom' ? staticStyles.rateTextVerticalPosition : [staticStyles.rateTextPosition, staticStyles.rateTextVerticalPosition],
70
70
  children: renderTypography(rateText, rateTypographyTokens, ratePosition, fontColor)
71
- }) : null, !bottomText && footnoteLinks.length <= MAX_FOOTNOTE_LINKS_ALLOWED ? /*#__PURE__*/_jsx(Text, {
71
+ }) : null, !bottomText && (footnoteLinks === null || footnoteLinks === void 0 ? void 0 : footnoteLinks.length) <= MAX_FOOTNOTE_LINKS_ALLOWED ? /*#__PURE__*/_jsx(Text, {
72
72
  style: [footnoteLinkPositionStyles, selectFootnoteLinksStyles({
73
73
  bottomLinksMarginLeft
74
74
  })],
75
75
  children: renderFootnoteLinks(footnoteLinks, themeTokens, onClickFootnote)
76
76
  }) : null]
77
- }), !bottomText && footnoteLinks.length > MAX_FOOTNOTE_LINKS_ALLOWED ? /*#__PURE__*/_jsx(View, {
77
+ }), !bottomText && (footnoteLinks === null || footnoteLinks === void 0 ? void 0 : footnoteLinks.length) > MAX_FOOTNOTE_LINKS_ALLOWED ? /*#__PURE__*/_jsx(View, {
78
78
  style: staticStyles.verticalFootnoteLinkContainer,
79
79
  children: renderFootnoteLinks(footnoteLinks, themeTokens, onClickFootnote)
80
80
  }) : null]
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import Typography from '../../Typography';
3
3
  import { jsx as _jsx } from "react/jsx-runtime";
4
4
  const renderTypography = (value, themeTokens, ratePosition, fontColor) => {
5
- const customProps = ratePosition === 'bottom' ? {
5
+ const customProps = ratePosition === 'bottom' && value !== '$' ? {
6
6
  variant: {
7
7
  size: 'micro'
8
8
  },
@@ -0,0 +1,231 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import StyleSheet from "react-native-web/dist/exports/StyleSheet";
4
+ import View from "react-native-web/dist/exports/View";
5
+ import Image from "react-native-web/dist/exports/Image";
6
+ import { useViewport } from '../ViewportProvider';
7
+ import { useThemeTokens } from '../ThemeProvider';
8
+ import defaultDictionary from './dictionary';
9
+ import { selectSystemProps, getTokensPropType, htmlAttrs, viewProps, useInputValue, useCopy, a11yProps } from '../utils';
10
+ import Badge from '../Badge';
11
+ import PriceLockup from '../PriceLockup';
12
+ import Typography from '../Typography';
13
+ import { Button } from '../Button';
14
+ import StackView from '../StackView';
15
+ import Box from '../Box';
16
+ import Icon from '../Icon';
17
+ import { jsx as _jsx } from "react/jsx-runtime";
18
+ import { jsxs as _jsxs } from "react/jsx-runtime";
19
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs, viewProps, a11yProps]);
20
+ const selectProductCardTokens = _ref => {
21
+ let {
22
+ borderStyle,
23
+ borderColor,
24
+ borderWidth,
25
+ borderRadius,
26
+ paddingHorizontal,
27
+ paddingVertical
28
+ } = _ref;
29
+ return {
30
+ borderStyle,
31
+ borderColor,
32
+ borderWidth,
33
+ borderRadius,
34
+ paddingHorizontal,
35
+ paddingVertical
36
+ };
37
+ };
38
+ const ProductCard = _ref2 => {
39
+ var _getCopy;
40
+ let {
41
+ copy = 'en',
42
+ dictionary = defaultDictionary,
43
+ image = {
44
+ src: '',
45
+ alt: ''
46
+ },
47
+ cardId,
48
+ isSelected,
49
+ onSelect,
50
+ tokens,
51
+ ...rest
52
+ } = _ref2;
53
+ const viewport = useViewport();
54
+ const themeTokens = useThemeTokens('ProductCard', tokens, {
55
+ viewport
56
+ });
57
+ const getCopy = useCopy({
58
+ copy,
59
+ dictionary
60
+ });
61
+ const {
62
+ currentValue,
63
+ setValue
64
+ } = useInputValue();
65
+ const hasClicked = isSelected || currentValue;
66
+ const handlePress = event => {
67
+ if (cardId) {
68
+ onSelect(cardId);
69
+ } else {
70
+ setValue(!currentValue, event);
71
+ }
72
+ };
73
+ const renderButton = hasClicked ? /*#__PURE__*/_jsx(Box, {
74
+ vertical: 1,
75
+ children: /*#__PURE__*/_jsxs(StackView, {
76
+ space: 2,
77
+ direction: "row",
78
+ children: [/*#__PURE__*/_jsx(Icon, {
79
+ icon: themeTokens.selectedButtonIcon,
80
+ variant: {
81
+ color: 'success'
82
+ }
83
+ }), /*#__PURE__*/_jsx(Typography, {
84
+ variant: {
85
+ size: 'h4'
86
+ },
87
+ tokens: {
88
+ fontWeight: 400
89
+ },
90
+ children: getCopy('selectedButtonLabel')
91
+ })]
92
+ })
93
+ }) : /*#__PURE__*/_jsx(Button, {
94
+ onPress: handlePress,
95
+ variant: {
96
+ purpose: 'primary',
97
+ size: 'small',
98
+ width: 'full'
99
+ },
100
+ children: getCopy('buttonLabel')
101
+ });
102
+ return /*#__PURE__*/_jsxs(View, {
103
+ ...selectProps(rest),
104
+ style: [selectProductCardTokens(themeTokens), staticStyles.container],
105
+ children: [image !== null && image !== void 0 && image.src ? /*#__PURE__*/_jsx(View, {
106
+ style: staticStyles.imageContainer,
107
+ children: /*#__PURE__*/_jsx(Image, {
108
+ source: image.src,
109
+ style: staticStyles.image,
110
+ alt: image.alt,
111
+ accessibilityLabel: image.alt,
112
+ resizeMethod: "resize",
113
+ accessibilityIgnoresInvertColors: true
114
+ })
115
+ }) : null, /*#__PURE__*/_jsx(View, {
116
+ style: staticStyles.textContainer,
117
+ children: /*#__PURE__*/_jsx(Box, {
118
+ left: 3,
119
+ children: /*#__PURE__*/_jsxs(StackView, {
120
+ space: 1,
121
+ children: [getCopy('badgeText') ? /*#__PURE__*/_jsx(Badge, {
122
+ variant: {
123
+ outline: true,
124
+ purpose: 'editorial'
125
+ },
126
+ children: getCopy('badgeText')
127
+ }) : null, /*#__PURE__*/_jsx(Typography, {
128
+ variant: {
129
+ size: 'h6'
130
+ },
131
+ children: getCopy('brandName')
132
+ }), /*#__PURE__*/_jsx(Typography, {
133
+ variant: {
134
+ size: 'h4',
135
+ colour: 'brand'
136
+ },
137
+ tokens: {
138
+ fontWeight: 400
139
+ },
140
+ children: getCopy('productName')
141
+ }), /*#__PURE__*/_jsxs(StackView, {
142
+ space: 3,
143
+ divider: true,
144
+ direction: "row",
145
+ children: [/*#__PURE__*/_jsx(PriceLockup, {
146
+ ...getCopy('primaryPrice'),
147
+ size: "small",
148
+ ratePosition: "bottom"
149
+ }), (_getCopy = getCopy('secondaryPrice')) !== null && _getCopy !== void 0 && _getCopy.price ? /*#__PURE__*/_jsx(PriceLockup, {
150
+ ...getCopy('secondaryPrice'),
151
+ size: "small",
152
+ ratePosition: "bottom"
153
+ }) : null]
154
+ }), /*#__PURE__*/_jsx(Box, {
155
+ top: 2,
156
+ children: /*#__PURE__*/_jsxs(StackView, {
157
+ space: 2,
158
+ children: [/*#__PURE__*/_jsx(Typography, {
159
+ variant: {
160
+ size: 'h6'
161
+ },
162
+ tokens: {
163
+ fontWeight: 500
164
+ },
165
+ children: getCopy('term')
166
+ }), getCopy('buttonLabel') ? /*#__PURE__*/_jsx(Box, {
167
+ top: 1,
168
+ children: renderButton
169
+ }) : null]
170
+ })
171
+ })]
172
+ })
173
+ })
174
+ })]
175
+ });
176
+ };
177
+ ProductCard.displayName = 'ProductCard';
178
+
179
+ // If a language dictionary entry is provided, it must contain every key
180
+ const dictionaryContentShape = PropTypes.shape({
181
+ badgeText: PropTypes.string,
182
+ brandName: PropTypes.string.isRequired,
183
+ productName: PropTypes.string.isRequired,
184
+ primaryPrice: PropTypes.object.isRequired,
185
+ secondaryPrice: PropTypes.object,
186
+ term: PropTypes.string.isRequired,
187
+ buttonLabel: PropTypes.string.isRequired,
188
+ selectedButtonLabel: PropTypes.string.isRequired
189
+ });
190
+ ProductCard.propTypes = {
191
+ ...selectedSystemPropTypes,
192
+ image: PropTypes.shape({
193
+ src: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
194
+ alt: PropTypes.string
195
+ }),
196
+ /**
197
+ * Select English or French copy for the place holder labels.
198
+ * You may also pass in a custom dictionary object.
199
+ */
200
+ copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr'])]),
201
+ /**
202
+ * Override the default dictionary, by passing the complete dictionary object for `en` and `fr`
203
+ */
204
+ dictionary: PropTypes.shape({
205
+ en: dictionaryContentShape,
206
+ fr: dictionaryContentShape
207
+ }),
208
+ tokens: getTokensPropType('ProductCard')
209
+ };
210
+ export default ProductCard;
211
+ const staticStyles = StyleSheet.create({
212
+ container: {
213
+ flexDirection: 'row',
214
+ flex: 1
215
+ },
216
+ imageContainer: {
217
+ width: '30%',
218
+ minWidth: 96,
219
+ maxWidth: 96
220
+ },
221
+ image: {
222
+ resizeMode: 'contain',
223
+ width: '100%',
224
+ height: undefined,
225
+ // This is to maintain the aspect ratio
226
+ aspectRatio: 0.8
227
+ },
228
+ textContainer: {
229
+ width: '70%'
230
+ }
231
+ });
@@ -0,0 +1,38 @@
1
+ export default {
2
+ en: {
3
+ badgeText: '',
4
+ brandName: 'Brand name',
5
+ productName: 'Product name',
6
+ primaryPrice: {
7
+ price: '00',
8
+ signDirection: 'left',
9
+ rateText: '/month'
10
+ },
11
+ secondaryPrice: {
12
+ price: '',
13
+ signDirection: 'left',
14
+ rateText: 'Upfront'
15
+ },
16
+ term: '24 months | 0% APR Bring-it-Back applied',
17
+ buttonLabel: 'Select this phone',
18
+ selectedButtonLabel: 'Selected phone'
19
+ },
20
+ fr: {
21
+ badgeText: '',
22
+ brandName: 'Brand name',
23
+ productName: 'Product name',
24
+ primaryPrice: {
25
+ price: '00',
26
+ signDirection: 'right',
27
+ rateText: '/mois'
28
+ },
29
+ secondaryPrice: {
30
+ price: '',
31
+ signDirection: 'right',
32
+ rateText: "d'acompte"
33
+ },
34
+ term: '24 mois | TAP de 0% | avec Option retour',
35
+ buttonLabel: 'Sélectionner ce téléphone',
36
+ selectedButtonLabel: 'Téléphone sélectionné'
37
+ }
38
+ };
@@ -0,0 +1,2 @@
1
+ import ProductCard from './ProductCard';
2
+ export default ProductCard;
@@ -0,0 +1,69 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import View from "react-native-web/dist/exports/View";
4
+ import { selectSystemProps, htmlAttrs, viewProps, a11yProps } from '../utils';
5
+ import ProductCard from '../ProductCard';
6
+ import { StackWrap } from '../StackView';
7
+ import { jsx as _jsx } from "react/jsx-runtime";
8
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs, viewProps, a11yProps]);
9
+ const ProductCardGroup = _ref => {
10
+ let {
11
+ productCards,
12
+ maxSelection = 1,
13
+ ...rest
14
+ } = _ref;
15
+ const [selectedCardIds, setSelectedCardIds] = useState([]);
16
+ const handleSelect = id => {
17
+ const isAlreadySelected = selectedCardIds.includes(id);
18
+ let updatedSelectedCardIds;
19
+ if (isAlreadySelected) {
20
+ updatedSelectedCardIds = selectedCardIds.filter(cardId => cardId !== id);
21
+ } else if (maxSelection && selectedCardIds.length >= maxSelection) {
22
+ updatedSelectedCardIds = selectedCardIds.filter((_, index) => index !== 0).concat(id);
23
+ } else {
24
+ updatedSelectedCardIds = [...selectedCardIds, id];
25
+ }
26
+ setSelectedCardIds(updatedSelectedCardIds);
27
+ };
28
+ return /*#__PURE__*/_jsx(View, {
29
+ ...selectProps(rest),
30
+ children: /*#__PURE__*/_jsx(StackWrap, {
31
+ direction: {
32
+ xs: 'column',
33
+ lg: 'row'
34
+ },
35
+ space: 4,
36
+ children: productCards.map((cardProperties, index) => {
37
+ const cardId = cardProperties.id || index;
38
+ return /*#__PURE__*/_jsx(View, {
39
+ children: /*#__PURE__*/_jsx(ProductCard, {
40
+ cardId: cardId,
41
+ isSelected: selectedCardIds.includes(cardId),
42
+ onSelect: handleSelect,
43
+ ...cardProperties
44
+ }, cardId)
45
+ }, cardId);
46
+ })
47
+ })
48
+ });
49
+ };
50
+ ProductCardGroup.displayName = 'ProductCardGroup';
51
+ ProductCardGroup.propTypes = {
52
+ ...selectedSystemPropTypes,
53
+ /**
54
+ * The maximum number of ProductCards a user may select at once. Defaults to 1.
55
+ * To have no limit and allow any number of selections, pass `null`.
56
+ */
57
+ maxSelection: PropTypes.number,
58
+ /**
59
+ * Props to be passed to the `ProductCard` component.
60
+ * id is required for each card.
61
+ * You may also pass in a custom dictionary object.
62
+ */
63
+ productCards: PropTypes.arrayOf(PropTypes.shape({
64
+ id: PropTypes.string.isRequired,
65
+ image: PropTypes.object,
66
+ dictionary: PropTypes.object
67
+ })).isRequired
68
+ };
69
+ export default ProductCardGroup;
@@ -0,0 +1,2 @@
1
+ import ProductCardGroup from './ProductCardGroup';
2
+ export default ProductCardGroup;
@@ -73,6 +73,7 @@ TextInput.propTypes = {
73
73
  * A callback which if provided will get a clear button rendered and will be called whenever that button gets pressed.
74
74
  */
75
75
  onClear: PropTypes.func,
76
+ onKeyPress: PropTypes.func,
76
77
  tokens: getTokensPropType('TextInput'),
77
78
  variant: variantProp.propType
78
79
  };
@@ -195,6 +195,7 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref7, ref) => {
195
195
  value,
196
196
  variant = {},
197
197
  type,
198
+ onKeyPress,
198
199
  ...rest
199
200
  } = _ref7;
200
201
  const [isFocused, setIsFocused] = useState(false);
@@ -327,7 +328,8 @@ const TextInputBase = /*#__PURE__*/forwardRef((_ref7, ref) => {
327
328
  onChange: handleChangeText,
328
329
  defaultValue: initialValue,
329
330
  maxLength: type === 'card' ? 19 : undefined,
330
- value: isControlled ? currentValue : undefined
331
+ value: isControlled ? currentValue : undefined,
332
+ onKeyPress
331
333
  };
332
334
  const {
333
335
  themeOptions
@@ -396,6 +398,7 @@ TextInputBase.propTypes = {
396
398
  onFocus: PropTypes.func,
397
399
  onMouseOut: PropTypes.func,
398
400
  onMouseOver: PropTypes.func,
401
+ onKeyPress: PropTypes.func,
399
402
  readOnly: PropTypes.bool,
400
403
  tokens: getTokensPropType('TextInput', 'TextArea'),
401
404
  value: PropTypes.string,