@telus-uds/components-base 1.72.0 → 1.74.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 (71) hide show
  1. package/CHANGELOG.md +35 -2
  2. package/lib/Box/Box.js +17 -6
  3. package/lib/ExpandCollapse/Panel.js +1 -1
  4. package/lib/FlexGrid/Col/Col.js +42 -19
  5. package/lib/FlexGrid/FlexGrid.js +40 -17
  6. package/lib/FlexGrid/Row/Row.js +45 -22
  7. package/lib/Footnote/Footnote.js +328 -0
  8. package/lib/Footnote/FootnoteLink.js +108 -0
  9. package/lib/Footnote/dictionary.js +19 -0
  10. package/lib/Footnote/index.js +12 -0
  11. package/lib/Listbox/ListboxGroup.js +7 -1
  12. package/lib/MultiSelectFilter/MultiSelectFilter.js +1 -0
  13. package/lib/Notification/Notification.js +13 -5
  14. package/lib/OrderedList/ItemBase.js +7 -1
  15. package/lib/Responsive/Responsive.js +32 -14
  16. package/lib/Responsive/ResponsiveProp.js +46 -0
  17. package/lib/Responsive/ResponsiveWithMediaQueryStyleSheet.js +75 -0
  18. package/lib/ThemeProvider/ThemeProvider.js +5 -2
  19. package/lib/ThemeProvider/index.js +9 -1
  20. package/lib/ThemeProvider/useResponsiveThemeTokens.js +89 -0
  21. package/lib/Typography/Typography.js +50 -22
  22. package/lib/index.js +8 -0
  23. package/lib/server.js +40 -0
  24. package/lib/utils/ssr-media-query/utils/create-media-query-styles.js +39 -6
  25. package/lib-module/Box/Box.js +17 -6
  26. package/lib-module/ExpandCollapse/Panel.js +1 -1
  27. package/lib-module/FlexGrid/Col/Col.js +42 -19
  28. package/lib-module/FlexGrid/FlexGrid.js +40 -17
  29. package/lib-module/FlexGrid/Row/Row.js +45 -22
  30. package/lib-module/Footnote/Footnote.js +319 -0
  31. package/lib-module/Footnote/FootnoteLink.js +101 -0
  32. package/lib-module/Footnote/dictionary.js +12 -0
  33. package/lib-module/Footnote/index.js +4 -0
  34. package/lib-module/Listbox/ListboxGroup.js +7 -1
  35. package/lib-module/MultiSelectFilter/MultiSelectFilter.js +1 -0
  36. package/lib-module/Notification/Notification.js +13 -5
  37. package/lib-module/OrderedList/ItemBase.js +7 -1
  38. package/lib-module/Responsive/Responsive.js +32 -15
  39. package/lib-module/Responsive/ResponsiveProp.js +39 -0
  40. package/lib-module/Responsive/ResponsiveWithMediaQueryStyleSheet.js +67 -0
  41. package/lib-module/ThemeProvider/ThemeProvider.js +5 -2
  42. package/lib-module/ThemeProvider/index.js +1 -0
  43. package/lib-module/ThemeProvider/useResponsiveThemeTokens.js +81 -0
  44. package/lib-module/Typography/Typography.js +52 -24
  45. package/lib-module/index.js +1 -0
  46. package/lib-module/server.js +4 -0
  47. package/lib-module/utils/ssr-media-query/utils/create-media-query-styles.js +36 -6
  48. package/package.json +13 -2
  49. package/src/Box/Box.jsx +35 -17
  50. package/src/ExpandCollapse/Panel.jsx +1 -1
  51. package/src/FlexGrid/Col/Col.jsx +42 -13
  52. package/src/FlexGrid/FlexGrid.jsx +40 -11
  53. package/src/FlexGrid/Row/Row.jsx +40 -16
  54. package/src/Footnote/Footnote.jsx +316 -0
  55. package/src/Footnote/FootnoteLink.jsx +95 -0
  56. package/src/Footnote/dictionary.js +12 -0
  57. package/src/Footnote/index.js +6 -0
  58. package/src/Listbox/ListboxGroup.jsx +9 -2
  59. package/src/MultiSelectFilter/MultiSelectFilter.jsx +2 -0
  60. package/src/Notification/Notification.jsx +15 -3
  61. package/src/OrderedList/ItemBase.jsx +14 -2
  62. package/src/Responsive/Responsive.jsx +31 -12
  63. package/src/Responsive/ResponsiveProp.jsx +33 -0
  64. package/src/Responsive/ResponsiveWithMediaQueryStyleSheet.jsx +60 -0
  65. package/src/ThemeProvider/ThemeProvider.jsx +5 -2
  66. package/src/ThemeProvider/index.js +1 -0
  67. package/src/ThemeProvider/useResponsiveThemeTokens.js +85 -0
  68. package/src/Typography/Typography.jsx +77 -24
  69. package/src/index.js +1 -0
  70. package/src/server.js +4 -0
  71. package/src/utils/ssr-media-query/utils/create-media-query-styles.js +21 -6
package/src/Box/Box.jsx CHANGED
@@ -1,6 +1,6 @@
1
1
  import React, { forwardRef } from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { View, ScrollView } from 'react-native'
3
+ import { View, ScrollView, Platform } from 'react-native'
4
4
  import { useThemeTokens } from '../ThemeProvider'
5
5
  import {
6
6
  a11yProps,
@@ -23,17 +23,20 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, vie
23
23
  * @typedef {import('../utils/props/spacingProps.js').SpacingOptions} SpacingOptions
24
24
  */
25
25
 
26
- const selectBoxStyles = ({
27
- backgroundColor,
28
- gradient,
29
- borderWidth,
30
- borderColor,
31
- borderTopLeftRadius,
32
- borderTopRightRadius,
33
- borderBottomLeftRadius,
34
- borderBottomRightRadius,
35
- ...rest
36
- }) => {
26
+ const selectBoxStyles = (
27
+ {
28
+ backgroundColor,
29
+ gradient,
30
+ borderWidth,
31
+ borderColor,
32
+ borderTopLeftRadius,
33
+ borderTopRightRadius,
34
+ borderBottomLeftRadius,
35
+ borderBottomRightRadius,
36
+ ...rest
37
+ },
38
+ customGradient
39
+ ) => {
37
40
  const styles = {
38
41
  backgroundColor,
39
42
  borderWidth,
@@ -48,7 +51,12 @@ const selectBoxStyles = ({
48
51
  angle,
49
52
  stops: [stopOne, stopTwo]
50
53
  } = gradient
51
- styles.backgroundImage = `linear-gradient(${angle}deg, ${stopOne.color}, 75% , ${stopTwo.color})`
54
+
55
+ if (Platform.OS === 'web') {
56
+ styles.backgroundImage = `linear-gradient(${angle}deg, ${stopOne.color}, 75%, ${stopTwo.color})`
57
+ } else if (customGradient && Platform.OS !== 'web') {
58
+ styles.colors = [stopOne.color, stopTwo.color]
59
+ }
52
60
  }
53
61
 
54
62
  const paddings = ['paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom']
@@ -145,6 +153,7 @@ const Box = forwardRef(
145
153
  accessibilityRole,
146
154
  testID,
147
155
  dataSet,
156
+ customGradient,
148
157
  ...rest
149
158
  },
150
159
  ref
@@ -162,21 +171,26 @@ const Box = forwardRef(
162
171
  paddingRight: useSpacingScale(right),
163
172
  paddingTop: useSpacingScale(top),
164
173
  paddingBottom: useSpacingScale(bottom),
165
- ...selectBoxStyles(themeTokens)
174
+ ...selectBoxStyles(themeTokens, customGradient)
166
175
  }
167
176
 
177
+ const childrenToRender =
178
+ typeof customGradient === 'function'
179
+ ? customGradient(styles.colors, styles)(children)
180
+ : children
181
+
168
182
  if (scroll) {
169
183
  const scrollProps = typeof scroll === 'object' ? scroll : {}
170
184
  scrollProps.contentContainerStyle = [styles, scrollProps.contentContainerStyle]
171
185
  return (
172
186
  <ScrollView {...scrollProps} {...props} testID={testID} dataSet={dataSet} ref={ref}>
173
- {children}
187
+ {childrenToRender}
174
188
  </ScrollView>
175
189
  )
176
190
  }
177
191
  return (
178
192
  <View {...props} style={styles} testID={testID} dataSet={dataSet} ref={ref}>
179
- {children}
193
+ {childrenToRender}
180
194
  </View>
181
195
  )
182
196
  }
@@ -266,7 +280,11 @@ Box.propTypes = {
266
280
  /**
267
281
  * Box accepts any content as children.
268
282
  */
269
- children: PropTypes.node.isRequired
283
+ children: PropTypes.node.isRequired,
284
+ /**
285
+ Use this prop if need to add a custom gradient for mobile
286
+ */
287
+ customGradient: PropTypes.func
270
288
  }
271
289
 
272
290
  export default Box
@@ -168,7 +168,7 @@ const ExpandCollapsePanel = forwardRef(
168
168
  })
169
169
  }}
170
170
  >
171
- <View style={selectContainerStyles(themeTokens)}>{isExpanded ? children : null}</View>
171
+ <View style={selectContainerStyles(themeTokens)}>{children}</View>
172
172
  </View>
173
173
  </Animated.View>
174
174
  </View>
@@ -5,6 +5,8 @@ import { Platform } from 'react-native'
5
5
  import GutterContext from '../providers/GutterContext'
6
6
  import applyInheritance from '../helpers'
7
7
  import { responsiveProps, BaseView, StyleSheet, createMediaQueryStyles } from '../../utils'
8
+ import { useViewport } from '../../ViewportProvider'
9
+ import { useTheme } from '../../ThemeProvider'
8
10
 
9
11
  const Col = forwardRef(
10
12
  (
@@ -27,6 +29,10 @@ const Col = forwardRef(
27
29
  ref
28
30
  ) => {
29
31
  const gutter = useContext(GutterContext)
32
+ const viewport = useViewport()
33
+ const {
34
+ themeOptions: { enableMediaQueryStyleSheet }
35
+ } = useTheme()
30
36
  const hiddenLevels = applyInheritance([xs, sm, md, lg, xl])
31
37
 
32
38
  const getHorizontalAlignLevel = () => {
@@ -104,7 +110,16 @@ const Col = forwardRef(
104
110
  xl: offsetsWithIheritance[4]
105
111
  }
106
112
 
107
- const mediaQueryStyles = createMediaQueryStyles({
113
+ const staticStyles = {
114
+ flexGrow: 1,
115
+ flexShrink: 0,
116
+ flexBasis: 'auto',
117
+ maxWidth: '100%',
118
+ paddingLeft: gutter ? 16 : 0,
119
+ paddingRight: gutter ? 16 : 0
120
+ }
121
+
122
+ const stylesByViewport = {
108
123
  xs: {
109
124
  display: hiddenLevels[0] === 0 ? 'none' : shown,
110
125
  textAlign: horizontalAlignLevel[0],
@@ -135,21 +150,35 @@ const Col = forwardRef(
135
150
  ...calculateWidth(sizes.xl),
136
151
  ...calculateOffset(offsets.xl)
137
152
  }
138
- })
153
+ }
139
154
 
140
- const { ids, styles } = StyleSheet.create({
141
- col: {
142
- flexGrow: 1,
143
- flexShrink: 0,
144
- flexBasis: 'auto',
145
- maxWidth: '100%',
146
- paddingLeft: gutter ? 16 : 0,
147
- paddingRight: gutter ? 16 : 0,
148
- ...mediaQueryStyles
155
+ let colStyles
156
+ let mediaIds
157
+
158
+ if (enableMediaQueryStyleSheet) {
159
+ const mediaQueryStyles = createMediaQueryStyles(stylesByViewport)
160
+ const { ids, styles } = StyleSheet.create({
161
+ col: {
162
+ ...staticStyles,
163
+ ...mediaQueryStyles
164
+ }
165
+ })
166
+ colStyles = styles.col
167
+ mediaIds = ids.col
168
+ } else {
169
+ colStyles = {
170
+ ...staticStyles,
171
+ ...stylesByViewport[viewport]
149
172
  }
150
- })
173
+ }
174
+
151
175
  return (
152
- <BaseView ref={ref} {...viewProps} style={[styles.col]} dataSet={{ media: ids.col }}>
176
+ <BaseView
177
+ ref={ref}
178
+ {...viewProps}
179
+ style={colStyles}
180
+ dataSet={mediaIds && { media: mediaIds }}
181
+ >
153
182
  {children}
154
183
  </BaseView>
155
184
  )
@@ -16,6 +16,8 @@ import Row from './Row'
16
16
  import Col from './Col'
17
17
  import GutterContext from './providers/GutterContext'
18
18
  import applyInheritance from './helpers'
19
+ import { useTheme } from '../ThemeProvider'
20
+ import { useViewport } from '../ViewportProvider'
19
21
 
20
22
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
21
23
 
@@ -42,7 +44,15 @@ const FlexGrid = forwardRef(
42
44
  ref
43
45
  ) => {
44
46
  const reverseLevel = applyInheritance([xsReverse, smReverse, mdReverse, lgReverse, xlReverse])
45
- const mediaQueryStyles = createMediaQueryStyles({
47
+ const viewport = useViewport()
48
+ const {
49
+ themeOptions: { enableMediaQueryStyleSheet }
50
+ } = useTheme()
51
+
52
+ let flexgridStyles
53
+ let mediaIds
54
+
55
+ const stylesByViewport = {
46
56
  xs: {
47
57
  flexDirection: reverseLevel[0] ? 'column-reverse' : 'column'
48
58
  },
@@ -62,17 +72,31 @@ const FlexGrid = forwardRef(
62
72
  maxWidth: limitWidth && viewports.map.get('xl'),
63
73
  flexDirection: reverseLevel[4] ? 'column-reverse' : 'column'
64
74
  }
65
- })
75
+ }
66
76
 
67
- const { ids, styles } = StyleSheet.create({
68
- flexgrid: {
69
- flexWrap: 'wrap',
70
- width: outsideGutter ? '100%' : 'auto',
71
- marginVertical: 0,
72
- marginHorizontal: outsideGutter ? 'auto' : -16,
73
- ...mediaQueryStyles
77
+ const staticStyles = {
78
+ flexWrap: 'wrap',
79
+ width: outsideGutter ? '100%' : 'auto',
80
+ marginVertical: 0,
81
+ marginHorizontal: outsideGutter ? 'auto' : -16
82
+ }
83
+
84
+ if (enableMediaQueryStyleSheet) {
85
+ const mediaQueryStyles = createMediaQueryStyles(stylesByViewport)
86
+ const { ids, styles } = StyleSheet.create({
87
+ flexgrid: {
88
+ ...staticStyles,
89
+ ...mediaQueryStyles
90
+ }
91
+ })
92
+ flexgridStyles = styles.flexgrid
93
+ mediaIds = ids.flexgrid
94
+ } else {
95
+ flexgridStyles = {
96
+ ...staticStyles,
97
+ ...stylesByViewport[viewport]
74
98
  }
75
- })
99
+ }
76
100
 
77
101
  const props = {
78
102
  accessibilityRole,
@@ -82,7 +106,12 @@ const FlexGrid = forwardRef(
82
106
 
83
107
  return (
84
108
  <GutterContext.Provider value={gutter}>
85
- <BaseView ref={ref} {...props} style={[styles.flexgrid]} dataSet={{ media: ids.flexgrid }}>
109
+ <BaseView
110
+ ref={ref}
111
+ {...props}
112
+ style={flexgridStyles}
113
+ dataSet={mediaIds && { media: mediaIds }}
114
+ >
86
115
  {children}
87
116
  </BaseView>
88
117
  </GutterContext.Provider>
@@ -3,6 +3,8 @@ import PropTypes from 'prop-types'
3
3
 
4
4
  import applyInheritance from '../helpers'
5
5
  import { BaseView, StyleSheet, createMediaQueryStyles } from '../../utils'
6
+ import { useTheme } from '../../ThemeProvider'
7
+ import { useViewport } from '../../ViewportProvider'
6
8
 
7
9
  const horizontalAlignStyles = (horizontalAlign) => {
8
10
  switch (horizontalAlign) {
@@ -66,8 +68,23 @@ const Row = forwardRef(
66
68
  },
67
69
  ref
68
70
  ) => {
71
+ const {
72
+ themeOptions: { enableMediaQueryStyleSheet }
73
+ } = useTheme()
74
+ const viewport = useViewport()
75
+ const staticStyles = {
76
+ width: '100%',
77
+ marginVertical: 0,
78
+ marginHorizontal: 'auto',
79
+ flexGrow: 0,
80
+ flexShrink: 1,
81
+ flexBasis: 'auto',
82
+ ...horizontalAlignStyles(horizontalAlign),
83
+ ...verticalAlignStyles(verticalAlign),
84
+ ...distributeStyles(distribute)
85
+ }
69
86
  const reverseLevel = applyInheritance([xsReverse, smReverse, mdReverse, lgReverse, xlReverse])
70
- const mediaQueryStyles = createMediaQueryStyles({
87
+ const stylesByViewport = {
71
88
  xs: {
72
89
  flexDirection: reverseLevel[0] ? 'row-reverse' : 'row',
73
90
  flexWrap: reverseLevel[0] ? 'wrap-reverse' : 'wrap'
@@ -88,23 +105,30 @@ const Row = forwardRef(
88
105
  flexDirection: reverseLevel[4] ? 'row-reverse' : 'row',
89
106
  flexWrap: reverseLevel[4] ? 'wrap-reverse' : 'wrap'
90
107
  }
91
- })
92
- const { ids, styles } = StyleSheet.create({
93
- row: {
94
- width: '100%',
95
- marginVertical: 0,
96
- marginHorizontal: 'auto',
97
- flexGrow: 0,
98
- flexShrink: 1,
99
- flexBasis: 'auto',
100
- ...horizontalAlignStyles(horizontalAlign),
101
- ...verticalAlignStyles(verticalAlign),
102
- ...distributeStyles(distribute),
103
- ...mediaQueryStyles
108
+ }
109
+
110
+ let rowStyles
111
+ let mediaIds
112
+
113
+ if (enableMediaQueryStyleSheet) {
114
+ const mediaQueryStyles = createMediaQueryStyles(stylesByViewport)
115
+ const { ids, styles } = StyleSheet.create({
116
+ row: {
117
+ ...staticStyles,
118
+ ...mediaQueryStyles
119
+ }
120
+ })
121
+ rowStyles = styles.row
122
+ mediaIds = ids.row
123
+ } else {
124
+ rowStyles = {
125
+ ...staticStyles,
126
+ ...stylesByViewport[viewport]
104
127
  }
105
- })
128
+ }
129
+
106
130
  return (
107
- <BaseView ref={ref} {...rest} style={[styles.row]} dataSet={{ media: ids.row }}>
131
+ <BaseView ref={ref} {...rest} style={rowStyles} dataSet={mediaIds && { media: mediaIds }}>
108
132
  {children}
109
133
  </BaseView>
110
134
  )
@@ -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
+ })