@telus-uds/components-base 1.73.0 → 1.75.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 (35) hide show
  1. package/CHANGELOG.md +27 -3
  2. package/lib/ExpandCollapse/Panel.js +1 -1
  3. package/lib/Footnote/Footnote.js +328 -0
  4. package/lib/Footnote/FootnoteLink.js +108 -0
  5. package/lib/Footnote/dictionary.js +19 -0
  6. package/lib/Footnote/index.js +12 -0
  7. package/lib/Notification/Notification.js +213 -35
  8. package/lib/Responsive/Responsive.js +8 -0
  9. package/lib/Responsive/ResponsiveWithMediaQueryStyleSheet.js +6 -3
  10. package/lib/Typography/Typography.js +3 -1
  11. package/lib/index.js +8 -0
  12. package/lib/utils/ssr-media-query/create-stylesheet/index.js +1 -2
  13. package/lib-module/ExpandCollapse/Panel.js +1 -1
  14. package/lib-module/Footnote/Footnote.js +319 -0
  15. package/lib-module/Footnote/FootnoteLink.js +101 -0
  16. package/lib-module/Footnote/dictionary.js +12 -0
  17. package/lib-module/Footnote/index.js +4 -0
  18. package/lib-module/Notification/Notification.js +216 -38
  19. package/lib-module/Responsive/Responsive.js +8 -0
  20. package/lib-module/Responsive/ResponsiveWithMediaQueryStyleSheet.js +6 -3
  21. package/lib-module/Typography/Typography.js +3 -1
  22. package/lib-module/index.js +1 -0
  23. package/lib-module/utils/ssr-media-query/create-stylesheet/index.js +1 -2
  24. package/package.json +2 -2
  25. package/src/ExpandCollapse/Panel.jsx +1 -1
  26. package/src/Footnote/Footnote.jsx +316 -0
  27. package/src/Footnote/FootnoteLink.jsx +95 -0
  28. package/src/Footnote/dictionary.js +12 -0
  29. package/src/Footnote/index.js +6 -0
  30. package/src/Notification/Notification.jsx +213 -34
  31. package/src/Responsive/Responsive.jsx +8 -2
  32. package/src/Responsive/ResponsiveWithMediaQueryStyleSheet.jsx +6 -4
  33. package/src/Typography/Typography.jsx +6 -1
  34. package/src/index.js +1 -0
  35. package/src/utils/ssr-media-query/create-stylesheet/index.js +3 -2
@@ -0,0 +1,316 @@
1
+ import React, { useEffect, useState, useCallback } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import {
4
+ StyleSheet,
5
+ Text,
6
+ Dimensions,
7
+ TouchableWithoutFeedback,
8
+ Modal,
9
+ View,
10
+ SafeAreaView,
11
+ ScrollView
12
+ } from 'react-native'
13
+
14
+ import defaultDictionary from './dictionary'
15
+ import { getTokensPropType, htmlAttrs, selectSystemProps, useCopy, viewProps } from '../utils'
16
+ import { useViewport } from '../ViewportProvider'
17
+ import { useTheme, useThemeTokens } from '../ThemeProvider'
18
+ import Typography from '../Typography'
19
+ import Icon from '../Icon'
20
+ import OrderedList from '../OrderedList'
21
+
22
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs, viewProps])
23
+
24
+ const selectFootnoteStyle = ({
25
+ screenHeight,
26
+ footnoteBackground,
27
+ isVisible,
28
+ footnoteBorderTopSizeMd,
29
+ footnoteBorderColorMd
30
+ }) => ({
31
+ left: 0,
32
+ backgroundColor: footnoteBackground,
33
+ display: isVisible ? 'flex' : 'none',
34
+ height: screenHeight,
35
+ borderTopColor: footnoteBorderColorMd,
36
+ borderTopWidth: footnoteBorderTopSizeMd
37
+ })
38
+
39
+ const selectFootnoteHeaderContentStyle = ({
40
+ footnoteHeaderPaddingTop,
41
+ footnoteHeaderPaddingBottom,
42
+ footnoteHeaderPaddingRight,
43
+ footnoteHeaderPaddingLeft,
44
+ headerMargin
45
+ }) => ({
46
+ paddingTop: footnoteHeaderPaddingTop,
47
+ paddingBottom: footnoteHeaderPaddingBottom,
48
+ paddingRight: footnoteHeaderPaddingRight,
49
+ paddingLeft: footnoteHeaderPaddingLeft,
50
+ marginRight: headerMargin
51
+ })
52
+
53
+ const selectFootnoteCloseButtonStyle = ({
54
+ closeButtonBorderSize,
55
+ closeButtonBorderColor,
56
+ closeButtonHeight,
57
+ closeButtonBackgroundColor,
58
+ closeButtonMarginTop,
59
+ closeButtonMarginRight,
60
+ closeButtonMarginBottom,
61
+ closeButtonMarginLeft,
62
+ closeButtonWidth
63
+ }) => ({
64
+ backgroundColor: closeButtonBackgroundColor,
65
+ borderWidth: closeButtonBorderSize,
66
+ borderColor: closeButtonBorderColor,
67
+ height: closeButtonHeight,
68
+ marginTop: closeButtonMarginTop,
69
+ marginRight: closeButtonMarginRight,
70
+ marginBottom: closeButtonMarginBottom,
71
+ marginLeft: closeButtonMarginLeft,
72
+ width: closeButtonWidth
73
+ })
74
+
75
+ const selectFootnoteBodyStyle = ({
76
+ maxWidth,
77
+ footnoteBodyBackground,
78
+ footnoteBodyPaddingTop,
79
+ footnoteBodyPaddingRight,
80
+ footnoteBodyPaddingBottom,
81
+ footnoteBodyPaddingLeft,
82
+ screenWidth
83
+ }) => ({
84
+ maxWidth,
85
+ backgroundColor: footnoteBodyBackground,
86
+ paddingTop: footnoteBodyPaddingTop,
87
+ paddingRight: footnoteBodyPaddingRight * 2,
88
+ paddingBottom: footnoteBodyPaddingBottom,
89
+ paddingLeft: footnoteBodyPaddingLeft,
90
+ width: screenWidth
91
+ })
92
+
93
+ const selectCustomContentFontStyle = ({
94
+ listItemColor,
95
+ listItemFontSize,
96
+ listItemLineHeight,
97
+ listItemPaddingLeft,
98
+ listItemFontName,
99
+ listItemFontWeight
100
+ }) => ({
101
+ fontSize: listItemFontSize,
102
+ lineHeight: listItemLineHeight * listItemFontSize,
103
+ paddingLeft: listItemPaddingLeft,
104
+ color: listItemColor,
105
+ fontName: listItemFontName,
106
+ fontWeight: listItemFontWeight
107
+ })
108
+
109
+ /**
110
+ * Use `Footnote` to display a single legal content.
111
+ *
112
+ * ## Usage Criteria
113
+ *
114
+ * - Use `Footnote` to display a single legal statement
115
+ * - Display on top of all UI, including other sticky elements such as Cart Summary
116
+ * - Dismiss by clicking on the close button, clicking anywhere outside of the `Footnote`
117
+ * - Use copy to set language, ‘en’ for English or ‘fr’ for French
118
+ *
119
+ * ## Accessibility requirements
120
+ *
121
+ * - Only one instance of `Footnote` should display at a time
122
+ * - Place `Footnote` as the last element in the body or main
123
+ * - When `Footnote` is open, the inert prop must be set on all children of body excluding the Footnote
124
+ * - When `Footnote` is closed, focus must return to the initiating element
125
+ */
126
+ const Footnote = ({
127
+ copy = 'en',
128
+ number = undefined,
129
+ content = undefined,
130
+ onClose,
131
+ isOpen = false,
132
+ tokens,
133
+ variant = {},
134
+ dictionary = defaultDictionary,
135
+ ...rest
136
+ }) => {
137
+ const viewport = useViewport()
138
+
139
+ const themeTokens = useThemeTokens('Footnote', tokens, variant, { viewport })
140
+ const themeOptions = useTheme()
141
+
142
+ const getCopy = useCopy({ dictionary, copy })
143
+
144
+ const [isVisible, setIsVisible] = useState(false)
145
+
146
+ const screenHeight = Dimensions.get('screen').height
147
+ const screenWidth = Dimensions.get('screen').width
148
+
149
+ const getFootnoteBodyContent = useCallback(() => {
150
+ if (!number || !content) {
151
+ return null
152
+ }
153
+
154
+ if (React.isValidElement(content)) {
155
+ return <View style={selectCustomContentFontStyle(themeTokens)}>{content}</View>
156
+ }
157
+
158
+ return (
159
+ // TODO: Extract the OrderedList.Item from the array when the issue #4361 is fixed
160
+ <OrderedList start={number}>
161
+ {[
162
+ <OrderedList.Item key={number}>
163
+ <Text style={selectCustomContentFontStyle(themeTokens)}>{content}</Text>
164
+ </OrderedList.Item>
165
+ ]}
166
+ </OrderedList>
167
+ )
168
+ }, [content, number, themeTokens])
169
+
170
+ useEffect(() => {
171
+ if (isOpen) {
172
+ setIsVisible(true)
173
+ }
174
+ }, [isOpen])
175
+
176
+ const closeFootnote = useCallback(
177
+ (event, options) => {
178
+ onClose(event, options)
179
+ setIsVisible(false)
180
+ },
181
+ [onClose]
182
+ )
183
+
184
+ const handleClose = (event) => closeFootnote(event, { returnFocus: true })
185
+
186
+ return (
187
+ <View {...selectProps(rest)}>
188
+ <Modal visible={isVisible} animationType="slide">
189
+ <SafeAreaView style={staticStyles.container}>
190
+ <ScrollView
191
+ style={selectFootnoteStyle({
192
+ screenHeight,
193
+ isVisible,
194
+ ...themeTokens
195
+ })}
196
+ >
197
+ <View style={staticStyles.content}>
198
+ <View
199
+ style={[selectFootnoteHeaderContentStyle(themeTokens), staticStyles.headerContent]}
200
+ >
201
+ <Typography
202
+ tokens={{
203
+ fontSize: themeTokens?.headerFontSize,
204
+ lineHeight: themeTokens?.headerLineHeight
205
+ }}
206
+ variant={{ size: 'h4' }}
207
+ >
208
+ {getCopy('heading')}
209
+ </Typography>
210
+ <TouchableWithoutFeedback
211
+ onPress={handleClose}
212
+ accessibilityLabel={getCopy('close')}
213
+ >
214
+ <View
215
+ style={[selectFootnoteCloseButtonStyle(themeTokens), staticStyles.closeButton]}
216
+ >
217
+ <Icon
218
+ icon={themeTokens?.closeIcon}
219
+ tokens={{ size: themeTokens?.closeButtonIconSize }}
220
+ />
221
+ </View>
222
+ </TouchableWithoutFeedback>
223
+ </View>
224
+ <View
225
+ style={selectFootnoteBodyStyle({
226
+ maxWidth: themeOptions.contentMaxWidth,
227
+ screenWidth,
228
+ ...themeTokens
229
+ })}
230
+ >
231
+ {getFootnoteBodyContent()}
232
+ </View>
233
+ </View>
234
+ </ScrollView>
235
+ </SafeAreaView>
236
+ </Modal>
237
+ </View>
238
+ )
239
+ }
240
+
241
+ const copyShape = PropTypes.shape({
242
+ close: PropTypes.string.isRequired,
243
+ heading: PropTypes.string.isRequired
244
+ })
245
+
246
+ // If a language dictionary entry is provided, it must contain every key
247
+ const dictionaryContentShape = PropTypes.shape({
248
+ a11yLabel: PropTypes.string.isRequired,
249
+ close: PropTypes.string.isRequired,
250
+ heading: PropTypes.string.isRequired
251
+ })
252
+
253
+ Footnote.propTypes = {
254
+ ...selectedSystemPropTypes,
255
+ tokens: getTokensPropType('Footnote'),
256
+ /**
257
+ * The content.
258
+ */
259
+ content: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
260
+ /**
261
+ * Use the `copy` prop to either select provided English or French copy by passing 'en' or 'fr' respectively.
262
+ * To provide your own, pass a JSON object with the keys `heading` and `close`.
263
+ */
264
+ copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr']), copyShape]),
265
+ /**
266
+ * A boolean flag used hide or show the `Footnote`. Set to `true` to open the `Footnote`.
267
+ */
268
+ isOpen: PropTypes.bool,
269
+ /**
270
+ * The number, must match the number of the `FootnoteLink` that initiated the `Footnote`.
271
+ */
272
+ number: PropTypes.number,
273
+ /**
274
+ * A callback function to handle the closing of the footnote.
275
+ *
276
+ * @param {SyntheticEvent} event The React `SyntheticEvent`
277
+ * @param {Object} options Custom options
278
+ * @param {boolean} options.returnFocus Should the `Footnote` return focus on close
279
+ */
280
+ onClose: PropTypes.func.isRequired,
281
+ /**
282
+ * Override the default dictionary, by passing the complete dictionary object for `en` and `fr`
283
+ */
284
+ dictionary: PropTypes.shape({
285
+ en: dictionaryContentShape,
286
+ fr: dictionaryContentShape
287
+ })
288
+ }
289
+
290
+ export default Footnote
291
+
292
+ const staticStyles = StyleSheet.create({
293
+ container: {
294
+ flex: 1
295
+ },
296
+ content: {
297
+ marginLeft: 'auto',
298
+ marginRight: 'auto',
299
+ left: 0,
300
+ right: 0,
301
+ maxWidth: 1200,
302
+ paddingBottom: 100
303
+ },
304
+ headerContent: {
305
+ alignItems: 'center',
306
+ display: 'flex',
307
+ flexDirection: 'row',
308
+ justifyContent: 'space-between'
309
+ },
310
+ closeButton: {
311
+ alignItems: 'center',
312
+ borderRadius: 50,
313
+ display: 'flex',
314
+ justifyContent: 'center'
315
+ }
316
+ })
@@ -0,0 +1,95 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { View, StyleSheet, Platform, TouchableWithoutFeedback } from 'react-native'
4
+ import dictionary from './dictionary'
5
+ import { htmlAttrs, selectSystemProps, useCopy, viewProps, wrapStringsInText } from '../utils'
6
+ import { applyTextStyles, useThemeTokens } from '../ThemeProvider'
7
+
8
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs, viewProps])
9
+
10
+ // The top property varies between devices due to how devices render the viewport.
11
+ const selectTextStyle = ({ fontSize, lineHeight }) => ({
12
+ fontSize,
13
+ lineHeight: lineHeight !== 1 ? lineHeight : fontSize * 2,
14
+ ...Platform.select({
15
+ ios: {
16
+ top: -fontSize / 2
17
+ },
18
+ android: {
19
+ top: fontSize / 4
20
+ },
21
+ web: {
22
+ top: -fontSize / 2.8
23
+ }
24
+ })
25
+ })
26
+
27
+ const FootnoteLink = ({ copy = 'en', content = [], onClick, tokens, variant = {}, ...rest }) => {
28
+ const themeTokens = useThemeTokens('FootnoteLink', tokens, variant)
29
+ const textStyles = applyTextStyles(themeTokens)
30
+
31
+ const numbers = Array.isArray(content) ? content : [content]
32
+ const refs = numbers.map(() => React.createRef())
33
+
34
+ const handleOnClick = (index) => {
35
+ onClick(numbers[index], refs[index])
36
+ }
37
+ const getCopy = useCopy({ dictionary, copy })
38
+
39
+ return (
40
+ <>
41
+ {numbers.map((num, index) => (
42
+ <View
43
+ accessibilityLabel={getCopy('a11yLabel')}
44
+ key={num}
45
+ ref={refs[index]}
46
+ {...selectProps(rest)}
47
+ >
48
+ <TouchableWithoutFeedback onPress={handleOnClick} accessibilityRole="button">
49
+ {wrapStringsInText(`${num}${index !== numbers.length - 1 ? ',' : ''}`, {
50
+ style: {
51
+ ...textStyles,
52
+ ...staticStyles.text,
53
+ ...selectTextStyle(themeTokens)
54
+ }
55
+ })}
56
+ </TouchableWithoutFeedback>
57
+ </View>
58
+ ))}
59
+ </>
60
+ )
61
+ }
62
+
63
+ const copyShape = PropTypes.shape({ a11yLabel: PropTypes.string.isRequired })
64
+
65
+ FootnoteLink.propTypes = {
66
+ ...selectedSystemPropTypes,
67
+ /**
68
+ * Use the `copy` prop to either select provided English or French copy by passing 'en' or 'fr' respectively.
69
+ * To provide your own, pass a JSON object with the key `a11yLabel`.
70
+ */
71
+ copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr']), copyShape]),
72
+ /**
73
+ * The footnote number, or multiple numbers if passed as an array.
74
+ * If using an array, a comma-separated group of numbers will be rendered as superscript.
75
+ */
76
+ content: PropTypes.oneOfType([
77
+ PropTypes.number,
78
+ PropTypes.arrayOf(PropTypes.number),
79
+ PropTypes.string,
80
+ PropTypes.arrayOf(PropTypes.string)
81
+ ]).isRequired,
82
+ /**
83
+ * A callback function to handle the click of a FootnoteLink.
84
+ */
85
+ onClick: PropTypes.func.isRequired
86
+ }
87
+
88
+ export default FootnoteLink
89
+
90
+ const staticStyles = StyleSheet.create({
91
+ text: {
92
+ position: 'relative',
93
+ textDecorationLine: 'underline'
94
+ }
95
+ })
@@ -0,0 +1,12 @@
1
+ export default {
2
+ en: {
3
+ a11yLabel: 'Read legal footnote',
4
+ close: 'close',
5
+ heading: 'Terms and conditions'
6
+ },
7
+ fr: {
8
+ a11yLabel: 'Lire la note de bas de page légale',
9
+ close: 'fermer',
10
+ heading: 'Modalités et conditions'
11
+ }
12
+ }
@@ -0,0 +1,6 @@
1
+ import Footnote from './Footnote'
2
+ import FootnoteLink from './FootnoteLink'
3
+
4
+ Footnote.Link = FootnoteLink
5
+
6
+ export default Footnote