@telus-uds/components-web 1.2.0 → 1.4.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 (140) hide show
  1. package/CHANGELOG.md +34 -2
  2. package/lib/Breadcrumbs/Breadcrumbs.js +247 -0
  3. package/lib/Breadcrumbs/Item/Item.js +165 -0
  4. package/lib/Breadcrumbs/index.js +15 -0
  5. package/lib/Callout/Callout.js +121 -0
  6. package/lib/Callout/index.js +13 -0
  7. package/lib/DatePicker/CalendarContainer.js +221 -0
  8. package/lib/DatePicker/DatePicker.js +329 -0
  9. package/lib/DatePicker/dictionary.js +134 -0
  10. package/lib/DatePicker/index.js +13 -0
  11. package/lib/DatePicker/reactDatesCss.js +12 -0
  12. package/lib/ExpandCollapseMini/ExpandCollapseMini.js +75 -0
  13. package/lib/ExpandCollapseMini/ExpandCollapseMiniControl.js +95 -0
  14. package/lib/ExpandCollapseMini/index.js +13 -0
  15. package/lib/Footnote/Footnote.js +571 -0
  16. package/lib/Footnote/FootnoteLink.js +149 -0
  17. package/lib/Footnote/dictionary.js +19 -0
  18. package/lib/Footnote/index.js +16 -0
  19. package/lib/OrderedList/Item.js +162 -0
  20. package/lib/OrderedList/ItemBase.js +42 -0
  21. package/lib/OrderedList/OrderedList.js +94 -0
  22. package/lib/OrderedList/OrderedListBase.js +68 -0
  23. package/lib/OrderedList/constants.js +9 -0
  24. package/lib/OrderedList/index.js +16 -0
  25. package/lib/PreviewCard/AuthorDate.js +64 -0
  26. package/lib/PreviewCard/PreviewCard.js +236 -0
  27. package/lib/PreviewCard/index.js +13 -0
  28. package/lib/PriceLockup/PriceLockup.js +237 -0
  29. package/lib/PriceLockup/index.js +13 -0
  30. package/lib/PriceLockup/tokens.js +131 -0
  31. package/lib/ResponsiveImage/ResponsiveImage.js +115 -0
  32. package/lib/ResponsiveImage/index.js +13 -0
  33. package/lib/Ribbon/Ribbon.js +0 -1
  34. package/lib/Span/Span.js +88 -0
  35. package/lib/Span/index.js +13 -0
  36. package/lib/index.js +91 -1
  37. package/lib/shared/FullBleedContent/FullBleedContent.js +121 -0
  38. package/lib/shared/FullBleedContent/getFullBleedBorderRadius.js +73 -0
  39. package/lib/shared/FullBleedContent/index.js +29 -0
  40. package/lib/shared/FullBleedContent/useFullBleedContentProps.js +73 -0
  41. package/lib/utils/index.js +32 -0
  42. package/lib/utils/logger.js +31 -0
  43. package/lib/utils/media.js +54 -0
  44. package/lib/utils/renderStructuredContent.js +89 -0
  45. package/lib/utils/useTypographyTheme.js +32 -0
  46. package/lib-module/Breadcrumbs/Breadcrumbs.js +228 -0
  47. package/lib-module/Breadcrumbs/Item/Item.js +141 -0
  48. package/lib-module/Breadcrumbs/index.js +1 -0
  49. package/lib-module/Callout/Callout.js +106 -0
  50. package/lib-module/Callout/index.js +2 -0
  51. package/lib-module/DatePicker/CalendarContainer.js +208 -0
  52. package/lib-module/DatePicker/DatePicker.js +302 -0
  53. package/lib-module/DatePicker/dictionary.js +127 -0
  54. package/lib-module/DatePicker/index.js +2 -0
  55. package/lib-module/DatePicker/reactDatesCss.js +3 -0
  56. package/lib-module/ExpandCollapseMini/ExpandCollapseMini.js +56 -0
  57. package/lib-module/ExpandCollapseMini/ExpandCollapseMiniControl.js +80 -0
  58. package/lib-module/ExpandCollapseMini/index.js +2 -0
  59. package/lib-module/Footnote/Footnote.js +541 -0
  60. package/lib-module/Footnote/FootnoteLink.js +130 -0
  61. package/lib-module/Footnote/dictionary.js +12 -0
  62. package/lib-module/Footnote/index.js +4 -0
  63. package/lib-module/OrderedList/Item.js +139 -0
  64. package/lib-module/OrderedList/ItemBase.js +28 -0
  65. package/lib-module/OrderedList/OrderedList.js +71 -0
  66. package/lib-module/OrderedList/OrderedListBase.js +48 -0
  67. package/lib-module/OrderedList/constants.js +2 -0
  68. package/lib-module/OrderedList/index.js +4 -0
  69. package/lib-module/PreviewCard/AuthorDate.js +53 -0
  70. package/lib-module/PreviewCard/PreviewCard.js +211 -0
  71. package/lib-module/PreviewCard/index.js +2 -0
  72. package/lib-module/PriceLockup/PriceLockup.js +213 -0
  73. package/lib-module/PriceLockup/index.js +2 -0
  74. package/lib-module/PriceLockup/tokens.js +120 -0
  75. package/lib-module/ResponsiveImage/ResponsiveImage.js +100 -0
  76. package/lib-module/ResponsiveImage/index.js +2 -0
  77. package/lib-module/Ribbon/Ribbon.js +1 -2
  78. package/lib-module/Span/Span.js +70 -0
  79. package/lib-module/Span/index.js +2 -0
  80. package/lib-module/index.js +10 -0
  81. package/lib-module/shared/FullBleedContent/FullBleedContent.js +106 -0
  82. package/lib-module/shared/FullBleedContent/getFullBleedBorderRadius.js +65 -0
  83. package/lib-module/shared/FullBleedContent/index.js +4 -0
  84. package/lib-module/shared/FullBleedContent/useFullBleedContentProps.js +65 -0
  85. package/lib-module/utils/index.js +5 -1
  86. package/lib-module/utils/logger.js +18 -0
  87. package/lib-module/utils/media.js +46 -0
  88. package/lib-module/utils/renderStructuredContent.js +77 -0
  89. package/lib-module/utils/useTypographyTheme.js +24 -0
  90. package/package.json +9 -4
  91. package/src/Breadcrumbs/Breadcrumbs.jsx +222 -0
  92. package/src/Breadcrumbs/Item/Item.jsx +127 -0
  93. package/src/Breadcrumbs/index.js +1 -0
  94. package/src/Callout/Callout.jsx +76 -0
  95. package/src/Callout/index.js +3 -0
  96. package/src/DatePicker/CalendarContainer.jsx +210 -0
  97. package/src/DatePicker/DatePicker.jsx +303 -0
  98. package/src/DatePicker/dictionary.js +92 -0
  99. package/src/DatePicker/index.js +3 -0
  100. package/src/DatePicker/reactDatesCss.js +892 -0
  101. package/src/ExpandCollapseMini/ExpandCollapseMini.jsx +48 -0
  102. package/src/ExpandCollapseMini/ExpandCollapseMiniControl.jsx +67 -0
  103. package/src/ExpandCollapseMini/index.js +3 -0
  104. package/src/Footnote/Footnote.jsx +468 -0
  105. package/src/Footnote/FootnoteLink.jsx +120 -0
  106. package/src/Footnote/dictionary.js +12 -0
  107. package/src/Footnote/index.js +6 -0
  108. package/src/OrderedList/Item.jsx +121 -0
  109. package/src/OrderedList/ItemBase.jsx +18 -0
  110. package/src/OrderedList/OrderedList.jsx +61 -0
  111. package/src/OrderedList/OrderedListBase.jsx +38 -0
  112. package/src/OrderedList/constants.js +2 -0
  113. package/src/OrderedList/index.js +6 -0
  114. package/src/PreviewCard/AuthorDate.jsx +31 -0
  115. package/src/PreviewCard/PreviewCard.jsx +201 -0
  116. package/src/PreviewCard/index.js +3 -0
  117. package/src/PriceLockup/PriceLockup.jsx +210 -0
  118. package/src/PriceLockup/index.js +3 -0
  119. package/src/PriceLockup/tokens.js +58 -0
  120. package/src/ResponsiveImage/ResponsiveImage.jsx +77 -0
  121. package/src/ResponsiveImage/index.js +3 -0
  122. package/src/Ribbon/Ribbon.jsx +0 -1
  123. package/src/Span/Span.jsx +66 -0
  124. package/src/Span/index.js +3 -0
  125. package/src/index.js +10 -0
  126. package/src/shared/FullBleedContent/FullBleedContent.jsx +90 -0
  127. package/src/shared/FullBleedContent/getFullBleedBorderRadius.js +55 -0
  128. package/src/shared/FullBleedContent/index.js +6 -0
  129. package/src/shared/FullBleedContent/useFullBleedContentProps.js +63 -0
  130. package/src/utils/index.js +5 -1
  131. package/src/utils/logger.js +20 -0
  132. package/src/utils/media.js +40 -0
  133. package/src/utils/renderStructuredContent.jsx +73 -0
  134. package/src/utils/useTypographyTheme.js +14 -0
  135. package/types/Callout.d.ts +13 -0
  136. package/types/DatePicker.d.ts +21 -0
  137. package/types/Footnote.d.ts +21 -0
  138. package/types/FootnoteLink.d.ts +20 -0
  139. package/types/PriceLockup.d.ts +22 -0
  140. package/types/common.d.ts +14 -0
@@ -0,0 +1,541 @@
1
+ import React, { useRef, useEffect, useState, useCallback } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styled, { createGlobalStyle } from 'styled-components';
4
+ import { Icon, Portal, selectSystemProps, Typography, useCopy, useTheme, useResponsiveProp, useThemeTokens } from '@telus-uds/components-base';
5
+ import Close from '../../__fixtures__/icons/Close';
6
+ import OrderedListBase from '../OrderedList/OrderedListBase';
7
+ import { htmlAttrs, media, renderStructuredContent } from '../utils';
8
+ import dictionary from './dictionary';
9
+ import { jsx as _jsx } from "react/jsx-runtime";
10
+ import { jsxs as _jsxs } from "react/jsx-runtime";
11
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs]);
12
+ const GlobalBodyScrollLock = /*#__PURE__*/createGlobalStyle({
13
+ 'html, body': media().until('md').css({
14
+ overflow: 'hidden'
15
+ })
16
+ });
17
+ const StyledFootnote = /*#__PURE__*/styled.div.withConfig({
18
+ displayName: "Footnote__StyledFootnote",
19
+ componentId: "components-web__sc-1563bo5-0"
20
+ })(_ref => {
21
+ let {
22
+ footnoteBackground,
23
+ isVisible,
24
+ footnoteBorderTop
25
+ } = _ref;
26
+ return {
27
+ position: 'fixed',
28
+ overflowY: 'scroll',
29
+ top: 0,
30
+ left: 0,
31
+ height: '100vh',
32
+ width: '100vw',
33
+ backgroundColor: footnoteBackground,
34
+ display: 'block',
35
+ boxShadow: '0 0 16px 0 rgba(0, 0, 0, 0.1)',
36
+ transform: 'translateY(100%)',
37
+ transition: 'transform 500ms ease-out',
38
+ '@media() (prefers-reduced-motion: reduce)': {
39
+ transition: 'none'
40
+ },
41
+ zIndex: 99999,
42
+ visibility: isVisible ? 'visible' : 'hidden',
43
+ ...media().from('md').css({
44
+ top: 'auto',
45
+ bottom: 0,
46
+ height: 'auto',
47
+ maxHeight: '50vh',
48
+ borderTop: footnoteBorderTop
49
+ })
50
+ };
51
+ }, _ref2 => {
52
+ let {
53
+ isOpen
54
+ } = _ref2;
55
+
56
+ if (isOpen) {
57
+ return {
58
+ transform: 'translateY(0)'
59
+ };
60
+ }
61
+
62
+ return {};
63
+ });
64
+ const StyledFootnoteHeader = /*#__PURE__*/styled.div.withConfig({
65
+ displayName: "Footnote__StyledFootnoteHeader",
66
+ componentId: "components-web__sc-1563bo5-1"
67
+ })({
68
+ position: 'relative',
69
+ width: '100%'
70
+ });
71
+ const StyledHeader = /*#__PURE__*/styled.div.withConfig({
72
+ displayName: "Footnote__StyledHeader",
73
+ componentId: "components-web__sc-1563bo5-2"
74
+ })(_ref3 => {
75
+ let {
76
+ headerMargin
77
+ } = _ref3;
78
+ return {
79
+ alignItems: 'center',
80
+ display: 'flex',
81
+ flexDirection: 'row',
82
+ justifyContent: 'space-between',
83
+ margin: headerMargin
84
+ };
85
+ });
86
+ const StyledFootnoteBody = /*#__PURE__*/styled.div.withConfig({
87
+ displayName: "Footnote__StyledFootnoteBody",
88
+ componentId: "components-web__sc-1563bo5-3"
89
+ })({
90
+ overflow: 'auto',
91
+ transition: 'height 300ms ease-out, opacity 200ms ease-out',
92
+ transform: 'translateZ(0)',
93
+ '@media() (prefers-reduced-motion: reduce)': {
94
+ transition: 'height 1ms ease-out, opacity 1ms ease-out'
95
+ }
96
+ }, _ref4 => {
97
+ let {
98
+ footnoteBodyBackground,
99
+ footnoteBodyPadding
100
+ } = _ref4;
101
+ return {
102
+ backgroundColor: footnoteBodyBackground,
103
+ padding: footnoteBodyPadding
104
+ };
105
+ }, _ref5 => {
106
+ let {
107
+ headerHeight
108
+ } = _ref5;
109
+ return {
110
+ maxHeight: `calc(100vh - ${headerHeight}px)`,
111
+ ...media().from('md').css({
112
+ maxHeight: `calc(50vh - ${headerHeight}px)`
113
+ })
114
+ };
115
+ }, _ref6 => {
116
+ let {
117
+ bodyHeight,
118
+ isTextVisible
119
+ } = _ref6;
120
+ return {
121
+ height: bodyHeight,
122
+ opacity: isTextVisible ? 1 : 0
123
+ };
124
+ });
125
+ const List = /*#__PURE__*/styled(OrderedListBase).withConfig({
126
+ displayName: "Footnote__List",
127
+ componentId: "components-web__sc-1563bo5-4"
128
+ })(_ref7 => {
129
+ let {
130
+ listPaddingLeft
131
+ } = _ref7;
132
+ return {
133
+ listStylePosition: 'outside',
134
+ paddingLeft: listPaddingLeft
135
+ };
136
+ });
137
+ const ListItem = /*#__PURE__*/styled(OrderedListBase.Item).withConfig({
138
+ displayName: "Footnote__ListItem",
139
+ componentId: "components-web__sc-1563bo5-5"
140
+ })(_ref8 => {
141
+ let {
142
+ listItemMarkerFontSize,
143
+ listItemMarkerLineHeight,
144
+ listItemColor,
145
+ listItemFontSize,
146
+ listItemLineHeight,
147
+ listItemPaddingLeft
148
+ } = _ref8;
149
+ return {
150
+ display: 'list-item',
151
+ '&::marker': {
152
+ fontFamily: 'HelveticaNow400normal',
153
+ fontSize: listItemMarkerFontSize,
154
+ lineHeight: listItemMarkerLineHeight,
155
+ textAlign: 'end !important'
156
+ },
157
+ color: listItemColor,
158
+ fontFamily: 'HelveticaNow400normal',
159
+ fontSize: listItemFontSize,
160
+ lineHeight: listItemLineHeight,
161
+ paddingLeft: listItemPaddingLeft
162
+ };
163
+ });
164
+ const CloseButton = /*#__PURE__*/styled.button.withConfig({
165
+ displayName: "Footnote__CloseButton",
166
+ componentId: "components-web__sc-1563bo5-6"
167
+ })(_ref9 => {
168
+ let {
169
+ closeButtonBorder,
170
+ closeButtonHeight,
171
+ closeButtonMargin,
172
+ closeButtonWidth
173
+ } = _ref9;
174
+ return {
175
+ alignItems: 'center',
176
+ borderRadius: '50%',
177
+ cursor: 'pointer',
178
+ display: 'flex',
179
+ justifyContent: 'center',
180
+ border: closeButtonBorder,
181
+ height: closeButtonHeight,
182
+ margin: closeButtonMargin,
183
+ width: closeButtonWidth
184
+ };
185
+ });
186
+ const ContentContainer = /*#__PURE__*/styled.div.withConfig({
187
+ displayName: "Footnote__ContentContainer",
188
+ componentId: "components-web__sc-1563bo5-7"
189
+ })({
190
+ 'margin-left': 'auto',
191
+ 'margin-right': 'auto',
192
+ left: 0,
193
+ right: 0
194
+ }, _ref10 => {
195
+ let {
196
+ maxWidth
197
+ } = _ref10;
198
+ return {
199
+ width: maxWidth
200
+ };
201
+ });
202
+
203
+ const usePrevious = value => {
204
+ const ref = useRef();
205
+ useEffect(() => {
206
+ ref.current = value;
207
+ });
208
+
209
+ if (ref.current) {
210
+ return ref.current;
211
+ }
212
+
213
+ return {};
214
+ };
215
+ /**
216
+ * Use `Footnote` to display a single legal content.
217
+ *
218
+ * ## Usage Criteria
219
+ *
220
+ * - Use `Footnote` to display a single legal statement
221
+ * - Display on top of all UI, including other sticky elements such as Cart Summary
222
+ * - Dismiss by clicking on the close button, clicking anywhere outside of the `Footnote`, or by pressing the ESC key
223
+ * - Responsive display based on breakpoints
224
+ * - Use copy to set language, ‘en’ for English or ‘fr’ for French
225
+ *
226
+ * ## Accessibility requirements
227
+ *
228
+ * - Only one instance of `Footnote` should display at a time
229
+ * - Place `Footnote` as the last element in the body or main
230
+ * - When `Footnote` is open, the inert prop must be set on all children of body excluding the Footnote
231
+ * - When `Footnote` is closed, focus must return to the initiating element
232
+ */
233
+
234
+
235
+ const Footnote = props => {
236
+ var _theme$themeOptions;
237
+
238
+ const {
239
+ copy,
240
+ number,
241
+ content,
242
+ onClose,
243
+ isOpen,
244
+ tokens,
245
+ variant = {},
246
+ ...rest
247
+ } = props;
248
+ const {
249
+ footnoteBackground,
250
+ footnoteBorderTopSizeMd,
251
+ footnoteBorderColorMd,
252
+ headerMargin,
253
+ footnoteBodyBackground,
254
+ footnoteBodyPaddingLeft,
255
+ footnoteBodyPaddingRight,
256
+ footnoteBodyPaddingTop,
257
+ footnoteBodyPaddingBottom,
258
+ listPaddingLeft,
259
+ listItemMarkerFontSize,
260
+ listItemMarkerLineHeight,
261
+ listItemColor,
262
+ listItemFontSize,
263
+ listItemLineHeight,
264
+ listItemPaddingLeft,
265
+ closeButtonBorderSize,
266
+ closeButtonBorderColor,
267
+ closeButtonHeight,
268
+ closeButtonMarginTop,
269
+ closeButtonMarginLeft,
270
+ closeButtonMarginRight,
271
+ closeButtonMarginBottom,
272
+ closeButtonWidth,
273
+ closeButtonIconSize
274
+ } = useThemeTokens('Footnote', tokens, variant);
275
+ const footnoteRef = useRef(null);
276
+ const headerRef = useRef(null);
277
+ const bodyRef = useRef(null);
278
+ const listRef = useRef(null);
279
+ const headingRef = useRef(null);
280
+ const [data, setData] = useState({
281
+ content: null,
282
+ number: null
283
+ });
284
+ const [headerHeight, setHeaderHeight] = useState('auto');
285
+ const [bodyHeight, setBodyHeight] = useState('auto');
286
+ const [isVisible, setIsVisible] = useState(false);
287
+ const [isTextVisible, setIsTextVisible] = useState(true);
288
+ const getCopy = useCopy({
289
+ dictionary,
290
+ copy
291
+ });
292
+ const prevProps = usePrevious(props);
293
+ const theme = useTheme();
294
+ const maxWidth = useResponsiveProp((_theme$themeOptions = theme.themeOptions) === null || _theme$themeOptions === void 0 ? void 0 : _theme$themeOptions.contentMaxWidth);
295
+ const closeFootnote = useCallback((event, options) => {
296
+ onClose(event, options);
297
+ }, [onClose]); // Listen for ESCAPE, close button clicks, and clicks outside of the Footnote. Call onClose.
298
+
299
+ const handleClose = useCallback(event => {
300
+ var _footnoteRef$current, _footnoteRef$current2;
301
+
302
+ if (event.type === 'keydown') {
303
+ if (event.key === 'Escape' || event.key === 27) {
304
+ closeFootnote(event, {
305
+ returnFocus: true
306
+ });
307
+ }
308
+ } else if ((event.type === 'click' || event.type === 'mousedown') && footnoteRef !== null && footnoteRef !== void 0 && footnoteRef.current && event.target && !(footnoteRef !== null && footnoteRef !== void 0 && (_footnoteRef$current = footnoteRef.current) !== null && _footnoteRef$current !== void 0 && _footnoteRef$current.contains(event.target)) && event.target.getAttribute('data-tds-id') !== 'footnote-link') {
309
+ closeFootnote(event, {
310
+ returnFocus: false
311
+ });
312
+ } else if (event.type === 'touchstart' && footnoteRef !== null && footnoteRef !== void 0 && footnoteRef.current && event.touches[0].target && !(footnoteRef !== null && footnoteRef !== void 0 && (_footnoteRef$current2 = footnoteRef.current) !== null && _footnoteRef$current2 !== void 0 && _footnoteRef$current2.contains(event.touches[0].target)) && event.touches[0].target.getAttribute('data-tds-id') !== 'footnote-link') {
313
+ closeFootnote(event, {
314
+ returnFocus: false
315
+ });
316
+ }
317
+ }, [closeFootnote]);
318
+
319
+ const saveCurrentHeight = () => {
320
+ const oldHeight = listRef.current.offsetHeight;
321
+ setBodyHeight(oldHeight);
322
+ };
323
+
324
+ const focusHeading = () => {
325
+ if (Boolean(content) && isVisible && headingRef && headingRef.current !== null) {
326
+ headingRef.current.focus();
327
+ }
328
+ };
329
+
330
+ const handleStyledFootnoteTransitionEnd = event => {
331
+ if (event.propertyName === 'transform' && !isOpen) {
332
+ setIsVisible(false);
333
+ } else {
334
+ focusHeading();
335
+ }
336
+ };
337
+
338
+ const handleTransitionEnd = event => {
339
+ event.persist();
340
+
341
+ if (event.propertyName === 'opacity' && !isTextVisible) {
342
+ setData({
343
+ content,
344
+ number
345
+ });
346
+
347
+ if (bodyHeight !== listRef.current.offsetHeight) {
348
+ // Set new height
349
+ setBodyHeight(listRef.current.offsetHeight);
350
+ } else {
351
+ setIsTextVisible(true);
352
+ }
353
+ } else {
354
+ setBodyHeight(listRef.current.offsetHeight);
355
+ }
356
+
357
+ if (event.propertyName === 'height' && !isTextVisible) {
358
+ setIsTextVisible(true);
359
+ }
360
+ };
361
+
362
+ const resetFootnote = () => {
363
+ // Reset footnote state if closed
364
+ if (!isOpen) {
365
+ setBodyHeight('auto');
366
+ setIsTextVisible(true);
367
+ }
368
+ }; // Set height of header on mount
369
+
370
+
371
+ useEffect(() => {
372
+ var _headerRef$current;
373
+
374
+ setHeaderHeight((_headerRef$current = headerRef.current) === null || _headerRef$current === void 0 ? void 0 : _headerRef$current.offsetHeight);
375
+ }, []);
376
+
377
+ const preventDefault = event => {
378
+ if (!bodyRef.current.contains(event.touches[0].target)) {
379
+ event.preventDefault();
380
+ }
381
+ }; // Add listeners for mouse clicks outside of Footnote and for ESCAPE key presses
382
+
383
+
384
+ useEffect(() => {
385
+ if (isOpen) {
386
+ setIsVisible(true);
387
+ document.addEventListener('mousedown', handleClose);
388
+ window.addEventListener('click', handleClose);
389
+ window.addEventListener('keydown', handleClose);
390
+ window.addEventListener('touchstart', handleClose);
391
+ window.addEventListener('touchmove', preventDefault, {
392
+ passive: false
393
+ });
394
+ }
395
+
396
+ return () => {
397
+ if (isOpen) {
398
+ document.addEventListener('mousedown', handleClose);
399
+ window.removeEventListener('click', handleClose);
400
+ window.removeEventListener('keydown', handleClose);
401
+ window.removeEventListener('touchstart', handleClose);
402
+ window.removeEventListener('touchmove', preventDefault);
403
+ }
404
+ };
405
+ }, [handleClose, isOpen]); // Set data if opening a new footnote
406
+
407
+ useEffect(() => {
408
+ if (isOpen && !prevProps.isOpen) {
409
+ setData({
410
+ content,
411
+ number
412
+ });
413
+ }
414
+ }, [isOpen, prevProps.isOpen, content, number]);
415
+ useEffect(() => {
416
+ if (isOpen && prevProps.isOpen && number !== prevProps.number) {
417
+ saveCurrentHeight();
418
+ setIsTextVisible(false);
419
+ }
420
+ }, [number, isOpen, prevProps.isOpen, prevProps.number]); // Reset footnote on close
421
+
422
+ useEffect(resetFootnote, [isOpen]);
423
+ return /*#__PURE__*/_jsx(Portal, {
424
+ children: /*#__PURE__*/_jsxs("div", { ...selectProps(rest),
425
+ children: [isOpen && /*#__PURE__*/_jsx(GlobalBodyScrollLock, {}), /*#__PURE__*/_jsx(StyledFootnote, {
426
+ ref: footnoteRef,
427
+ isOpen: isOpen,
428
+ isVisible: isVisible,
429
+ onTransitionEnd: handleStyledFootnoteTransitionEnd,
430
+ tabIndex: 0,
431
+ footnoteBackground: footnoteBackground,
432
+ footnoteBorderTop: `${footnoteBorderTopSizeMd}px solid ${footnoteBorderColorMd}`,
433
+ children: /*#__PURE__*/_jsxs(ContentContainer, {
434
+ maxWidth: maxWidth,
435
+ children: [/*#__PURE__*/_jsx(StyledFootnoteHeader, {
436
+ ref: headerRef,
437
+ children: /*#__PURE__*/_jsxs(StyledHeader, {
438
+ ref: headingRef,
439
+ headerMargin: headerMargin,
440
+ children: [/*#__PURE__*/_jsx(Typography, {
441
+ block: true,
442
+ heading: true,
443
+ tabIndex: -1,
444
+ variant: {
445
+ size: 'h4'
446
+ },
447
+ children: getCopy('heading')
448
+ }), /*#__PURE__*/_jsx(CloseButton, {
449
+ closeButtonBorder: `${closeButtonBorderSize}px solid ${closeButtonBorderColor}`,
450
+ closeButtonWidth: `${closeButtonWidth}px`,
451
+ closeButtonHeight: `${closeButtonHeight}px`,
452
+ closeButtonMargin: `${closeButtonMarginTop}px ${closeButtonMarginRight}px ${closeButtonMarginBottom}px ${closeButtonMarginLeft}px`,
453
+ onClick: event => {
454
+ closeFootnote(event, {
455
+ returnFocus: true
456
+ });
457
+ },
458
+ "aria-label": getCopy('close'),
459
+ children: /*#__PURE__*/_jsx(Icon, {
460
+ icon: Close,
461
+ tokens: {
462
+ size: `${closeButtonIconSize}px`
463
+ }
464
+ })
465
+ })]
466
+ })
467
+ }), /*#__PURE__*/_jsx(StyledFootnoteBody, {
468
+ ref: bodyRef,
469
+ bodyHeight: bodyHeight,
470
+ headerHeight: headerHeight,
471
+ isTextVisible: isTextVisible,
472
+ onTransitionEnd: handleTransitionEnd,
473
+ maxWidth: theme.contentMaxWidth,
474
+ footnoteBodyBackground: footnoteBodyBackground,
475
+ footnoteBodyPadding: `${footnoteBodyPaddingTop}px ${footnoteBodyPaddingRight}px ${footnoteBodyPaddingBottom}px ${footnoteBodyPaddingLeft}px`,
476
+ children: data.number && data.content && /*#__PURE__*/_jsx(List, {
477
+ start: data.number,
478
+ ref: listRef,
479
+ listPaddingLeft: listPaddingLeft,
480
+ children: /*#__PURE__*/_jsx(ListItem, {
481
+ listItemMarkerFontSize: listItemMarkerFontSize,
482
+ listItemMarkerLineHeight: listItemMarkerLineHeight,
483
+ listItemColor: listItemColor,
484
+ listItemFontSize: listItemFontSize,
485
+ listItemLineHeight: listItemLineHeight,
486
+ listItemPaddingLeft: listItemPaddingLeft,
487
+ children: /*#__PURE__*/_jsx(Typography, {
488
+ children: renderStructuredContent(data.content)
489
+ })
490
+ })
491
+ })
492
+ })]
493
+ })
494
+ })]
495
+ })
496
+ });
497
+ };
498
+
499
+ const copyShape = PropTypes.shape({
500
+ close: PropTypes.string.isRequired,
501
+ heading: PropTypes.string.isRequired
502
+ });
503
+ Footnote.propTypes = { ...selectedSystemPropTypes,
504
+
505
+ /**
506
+ * The content.
507
+ */
508
+ content: PropTypes.string,
509
+
510
+ /**
511
+ * Use the `copy` prop to either select provided English or French copy by passing 'en' or 'fr' respectively.
512
+ * To provide your own, pass a JSON object with the keys `heading` and `close`.
513
+ */
514
+ copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr']), copyShape]),
515
+
516
+ /**
517
+ * A boolean flag used hide or show the `Footnote`. Set to `true` to open the `Footnote`.
518
+ */
519
+ isOpen: PropTypes.bool,
520
+
521
+ /**
522
+ * The number, must match the number of the `FootnoteLink` that initiated the `Footnote`.
523
+ */
524
+ number: PropTypes.number,
525
+
526
+ /**
527
+ * A callback function to handle the closing of the footnote.
528
+ *
529
+ * @param {SyntheticEvent} event The React `SyntheticEvent`
530
+ * @param {Object} options Custom options
531
+ * @param {boolean} options.returnFocus Should the `Footnote` return focus on close
532
+ */
533
+ onClose: PropTypes.func.isRequired
534
+ };
535
+ Footnote.defaultProps = {
536
+ isOpen: false,
537
+ number: undefined,
538
+ content: undefined,
539
+ copy: 'en'
540
+ };
541
+ export default Footnote;
@@ -0,0 +1,130 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styled from 'styled-components';
4
+ import { selectSystemProps, useCopy, useThemeTokens } from '@telus-uds/components-base';
5
+ import dictionary from './dictionary';
6
+ import { htmlAttrs } from '../utils';
7
+ import { jsx as _jsx } from "react/jsx-runtime";
8
+ import { Fragment as _Fragment } from "react/jsx-runtime";
9
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs]);
10
+ const StyledSup = /*#__PURE__*/styled.sup.withConfig({
11
+ displayName: "FootnoteLink__StyledSup",
12
+ componentId: "components-web__sc-17nd7xo-0"
13
+ })(_ref => {
14
+ let {
15
+ fontSize = 'smaller',
16
+ lineHeight,
17
+ paddingLeft,
18
+ paddingRight
19
+ } = _ref;
20
+ return {
21
+ border: 0,
22
+ color: 'inherit',
23
+ cursor: 'pointer',
24
+ fontSize,
25
+ lineHeight,
26
+ margin: 0,
27
+ paddingVertical: 0,
28
+ paddingLeft,
29
+ paddingRight,
30
+ textDecoration: 'underline'
31
+ };
32
+ });
33
+ /**
34
+ * Use `FootnoteLink` to open `Footnote` component and display related legal content.
35
+ *
36
+ * ## Usage Criteria
37
+ *
38
+ * - Use FootnoteLink to open a Footnote component and display related legal content.
39
+ * - Avoid using FootnoteLink if there is only one annotation on a page. Consider including
40
+ * the annotation as part of the content whenever possible.
41
+ */
42
+
43
+ const FootnoteLink = _ref2 => {
44
+ let {
45
+ copy = 'en',
46
+ number = [],
47
+ onClick,
48
+ fontSize,
49
+ tokens,
50
+ variant = {},
51
+ ...rest
52
+ } = _ref2;
53
+ const {
54
+ lineHeight,
55
+ paddingLeft,
56
+ paddingRight
57
+ } = useThemeTokens('FootnoteLink', tokens, variant);
58
+ const numbers = Array.isArray(number) ? number : [number];
59
+ const refs = numbers.map(() => /*#__PURE__*/React.createRef());
60
+
61
+ const handleClick = index => {
62
+ onClick(numbers[index], refs[index]);
63
+ };
64
+
65
+ const getCopy = useCopy({
66
+ dictionary,
67
+ copy
68
+ });
69
+
70
+ const handleOnClick = (event, index) => {
71
+ event.preventDefault();
72
+ event.stopPropagation();
73
+ handleClick(index);
74
+ };
75
+
76
+ const handleOnKeyDown = (event, index) => {
77
+ if (event.key === 'Enter' || event.key === 13) {
78
+ handleClick(index);
79
+ }
80
+ };
81
+
82
+ return /*#__PURE__*/_jsx(_Fragment, {
83
+ children: numbers.map((num, index) => /*#__PURE__*/_jsx(StyledSup, {
84
+ onKeyDown: event => handleOnKeyDown(event, index),
85
+ role: "button",
86
+ "aria-label": getCopy('a11yLabel'),
87
+ ref: refs[index],
88
+ onClick: event => handleOnClick(event, index),
89
+ fontSize: fontSize,
90
+ lineHeight: lineHeight,
91
+ paddingLeft: paddingLeft,
92
+ paddingRight: paddingRight,
93
+ ...selectProps(rest),
94
+ children: `${num}${index !== numbers.length - 1 ? ',' : ''}`
95
+ }, num))
96
+ });
97
+ };
98
+
99
+ const copyShape = PropTypes.shape({
100
+ a11yLabel: PropTypes.string.isRequired
101
+ });
102
+ FootnoteLink.propTypes = { ...selectedSystemPropTypes,
103
+
104
+ /**
105
+ * Use the `copy` prop to either select provided English or French copy by passing 'en' or 'fr' respectively.
106
+ * To provide your own, pass a JSON object with the key `a11yLabel`.
107
+ */
108
+ copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr']), copyShape]),
109
+
110
+ /**
111
+ * The footnote number, or multiple numbers if passed as an array.
112
+ * If using an array, a comma-separated group of numbers will be rendered as superscript.
113
+ */
114
+ number: PropTypes.oneOfType([PropTypes.number, PropTypes.arrayOf(PropTypes.number), PropTypes.string, PropTypes.arrayOf(PropTypes.string)]).isRequired,
115
+
116
+ /**
117
+ * A callback function to handle the click of a FootnoteLink.
118
+ */
119
+ onClick: PropTypes.func.isRequired,
120
+
121
+ /**
122
+ * Override default `fontSize` to set specific font size value
123
+ */
124
+ fontSize: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
125
+ };
126
+ FootnoteLink.defaultProps = {
127
+ copy: 'en',
128
+ fontSize: 'smaller'
129
+ };
130
+ export default FootnoteLink;
@@ -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,4 @@
1
+ import Footnote from './Footnote';
2
+ import FootnoteLink from './FootnoteLink';
3
+ Footnote.Link = FootnoteLink;
4
+ export default Footnote;