@telus-uds/components-base 1.74.0 → 1.76.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 (50) hide show
  1. package/CHANGELOG.md +26 -2
  2. package/lib/Carousel/CarouselThumbnail.js +10 -4
  3. package/lib/Carousel/CarouselThumbnailNavigation.js +3 -3
  4. package/lib/Footnote/Footnote.js +9 -13
  5. package/lib/Link/ChevronLink.js +2 -0
  6. package/lib/Link/InlinePressable.js +15 -2
  7. package/lib/Link/LinkBase.js +1 -0
  8. package/lib/Notification/Notification.js +213 -35
  9. package/lib/OrderedList/OrderedList.js +21 -20
  10. package/lib/PriceLockup/PriceLockup.js +220 -0
  11. package/lib/PriceLockup/index.js +10 -0
  12. package/lib/PriceLockup/utils/renderFootnoteContent.js +93 -0
  13. package/lib/PriceLockup/utils/renderFootnoteLinks.js +36 -0
  14. package/lib/PriceLockup/utils/renderPrice.js +147 -0
  15. package/lib/PriceLockup/utils/renderTypography.js +31 -0
  16. package/lib/index.js +8 -0
  17. package/lib/utils/ssr-media-query/create-stylesheet/index.js +1 -2
  18. package/lib-module/Carousel/CarouselThumbnail.js +10 -4
  19. package/lib-module/Carousel/CarouselThumbnailNavigation.js +3 -3
  20. package/lib-module/Footnote/Footnote.js +9 -13
  21. package/lib-module/Link/ChevronLink.js +2 -0
  22. package/lib-module/Link/InlinePressable.js +16 -2
  23. package/lib-module/Link/LinkBase.js +1 -0
  24. package/lib-module/Notification/Notification.js +216 -38
  25. package/lib-module/OrderedList/OrderedList.js +21 -20
  26. package/lib-module/PriceLockup/PriceLockup.js +214 -0
  27. package/lib-module/PriceLockup/index.js +2 -0
  28. package/lib-module/PriceLockup/utils/renderFootnoteContent.js +87 -0
  29. package/lib-module/PriceLockup/utils/renderFootnoteLinks.js +28 -0
  30. package/lib-module/PriceLockup/utils/renderPrice.js +141 -0
  31. package/lib-module/PriceLockup/utils/renderTypography.js +23 -0
  32. package/lib-module/index.js +1 -0
  33. package/lib-module/utils/ssr-media-query/create-stylesheet/index.js +1 -2
  34. package/package.json +2 -2
  35. package/src/Carousel/CarouselThumbnail.jsx +8 -6
  36. package/src/Carousel/CarouselThumbnailNavigation.jsx +3 -4
  37. package/src/Footnote/Footnote.jsx +3 -6
  38. package/src/Link/ChevronLink.jsx +5 -1
  39. package/src/Link/InlinePressable.jsx +36 -15
  40. package/src/Link/LinkBase.jsx +1 -0
  41. package/src/Notification/Notification.jsx +213 -34
  42. package/src/OrderedList/OrderedList.jsx +17 -20
  43. package/src/PriceLockup/PriceLockup.jsx +218 -0
  44. package/src/PriceLockup/index.js +3 -0
  45. package/src/PriceLockup/utils/renderFootnoteContent.jsx +77 -0
  46. package/src/PriceLockup/utils/renderFootnoteLinks.jsx +38 -0
  47. package/src/PriceLockup/utils/renderPrice.jsx +201 -0
  48. package/src/PriceLockup/utils/renderTypography.jsx +13 -0
  49. package/src/index.js +1 -0
  50. package/src/utils/ssr-media-query/create-stylesheet/index.js +3 -2
@@ -1,8 +1,13 @@
1
- import React, { forwardRef, useState } from 'react'
2
- import { StyleSheet, View } from 'react-native'
1
+ import React, { forwardRef, useState, useRef } from 'react'
2
+ import { View } from 'react-native'
3
3
 
4
4
  import PropTypes from 'prop-types'
5
- import { applyTextStyles, useTheme, useThemeTokens } from '../ThemeProvider'
5
+ import {
6
+ applyTextStyles,
7
+ useTheme,
8
+ useThemeTokens,
9
+ useResponsiveThemeTokens
10
+ } from '../ThemeProvider'
6
11
  import {
7
12
  a11yProps,
8
13
  getTokensPropType,
@@ -11,7 +16,9 @@ import {
11
16
  variantProp,
12
17
  viewProps,
13
18
  wrapStringsInText,
14
- useResponsiveProp
19
+ useResponsiveProp,
20
+ createMediaQueryStyles,
21
+ StyleSheet
15
22
  } from '../utils'
16
23
  import IconButton from '../IconButton'
17
24
  import useCopy from '../utils/useCopy'
@@ -61,6 +68,110 @@ const selectContentContainerStyle = (maxWidth) => ({
61
68
  width: maxWidth || '100%'
62
69
  })
63
70
 
71
+ const getMediaQueryStyles = (themeTokens, themeOptions, viewport, mediaIdsRef, dismissible) => {
72
+ const transformedSelectContainerStyles = Object.entries(themeTokens).reduce(
73
+ (acc, [vp, viewportTokens]) => {
74
+ acc[vp] = selectContainerStyles({ ...viewportTokens })
75
+ return acc
76
+ },
77
+ {}
78
+ )
79
+
80
+ const selectContainerMediaQueryStyles = createMediaQueryStyles(transformedSelectContainerStyles)
81
+
82
+ const { ids: containerIds, styles: containerStyles } = StyleSheet.create({
83
+ container: { flexDirection: 'row', ...selectContainerMediaQueryStyles }
84
+ })
85
+
86
+ const { ids: contentContainerIds, styles: contentContainerStyles } = StyleSheet.create({
87
+ contentContainer: {
88
+ flexDirection: 'row',
89
+ flexShrink: 1,
90
+ justifyContent: 'space-between',
91
+ ...createMediaQueryStyles({
92
+ xs: { width: themeOptions?.contentMaxWidth.xs || '100%' },
93
+ md: { width: themeOptions?.contentMaxWidth.md || '100%' },
94
+ lg: { width: themeOptions?.contentMaxWidth.lg || '100%' },
95
+ sm: { width: themeOptions?.contentMaxWidth.sm || '100%' },
96
+ xl: { width: themeOptions?.contentMaxWidth.xl || '100%' }
97
+ })
98
+ }
99
+ })
100
+
101
+ const { ids: staticContentContainerIds, styles: staticContentContainerStyles } =
102
+ StyleSheet.create({
103
+ staticContentContainer: { flexDirection: 'row', flexShrink: 1 }
104
+ })
105
+
106
+ const { ids: iconContainerIds, styles: iconContainerStyles } = StyleSheet.create({
107
+ iconContainer: selectIconContainerStyles(themeTokens[viewport])
108
+ })
109
+
110
+ const { ids: dismissButtonContainerIds, styles: dismissButtonContainerStyles } =
111
+ StyleSheet.create({
112
+ dismissButtonContainer: selectDismissButtonContainerStyles(themeTokens[viewport])
113
+ })
114
+
115
+ const { ids: textIds, styles: textStyles } = StyleSheet.create({
116
+ text: selectTextStyles(themeTokens[viewport], themeOptions, dismissible)
117
+ })
118
+
119
+ const { styles: selectIconPropsStyles } = StyleSheet.create({
120
+ selectIconProps: selectIconProps(themeTokens[viewport])
121
+ })
122
+
123
+ const { styles: selectDismissIconPropsStyles } = StyleSheet.create({
124
+ selectDismissIconProps: selectDismissIconProps(themeTokens[viewport])
125
+ })
126
+
127
+ // eslint-disable-next-line no-param-reassign
128
+ mediaIdsRef.current = {
129
+ containerIds,
130
+ contentContainerIds,
131
+ staticContentContainerIds,
132
+ iconContainerIds,
133
+ dismissButtonContainerIds,
134
+ textIds
135
+ }
136
+
137
+ return {
138
+ containerStyles,
139
+ contentContainerStyles,
140
+ staticContentContainerStyles,
141
+ iconContainerStyles,
142
+ dismissButtonContainerStyles,
143
+ textStyles,
144
+ selectIconPropsStyles,
145
+ selectDismissIconPropsStyles
146
+ }
147
+ }
148
+
149
+ const getDefaultStyles = (themeTokens, themeOptions, maxWidth, dismissible) => ({
150
+ containerStyles: {
151
+ container: { flexDirection: 'row', ...selectContainerStyles(themeTokens) }
152
+ },
153
+ contentContainerStyles: {
154
+ contentContainer: {
155
+ flexDirection: 'row',
156
+ flexShrink: 1,
157
+ justifyContent: 'space-between',
158
+ ...selectContentContainerStyle(maxWidth)
159
+ }
160
+ },
161
+ staticContentContainerStyles: {
162
+ staticContentContainer: { flexDirection: 'row', flexShrink: 1 }
163
+ },
164
+ iconContainerStyles: { iconContainer: { ...selectIconContainerStyles(themeTokens) } },
165
+ dismissButtonContainerStyles: {
166
+ dismissButtonContainer: { ...selectDismissButtonContainerStyles(themeTokens) }
167
+ },
168
+ textStyles: { text: { ...selectTextStyles(themeTokens, themeOptions, dismissible) } },
169
+ selectIconPropsStyles: { selectIconProps: { ...selectIconProps(themeTokens) } },
170
+ selectDismissIconPropsStyles: {
171
+ selectDismissIconProps: { ...selectDismissIconProps(themeTokens) }
172
+ }
173
+ })
174
+
64
175
  /**
65
176
  * A banner that highlights important messages:
66
177
  * - Status message to show there is an error or outage of services
@@ -116,43 +227,121 @@ const Notification = forwardRef(
116
227
  ({ children, system, dismissible, copy = 'en', tokens, variant, ...rest }, ref) => {
117
228
  const [isDismissed, setIsDismissed] = useState(false)
118
229
  const viewport = useViewport()
119
- const themeTokens = useThemeTokens('Notification', tokens, variant, { system, viewport })
120
230
  const getCopy = useCopy({ dictionary, copy })
231
+
121
232
  const { themeOptions } = useTheme()
122
- const contentMaxWidth = useResponsiveProp(themeOptions?.contentMaxWidth)
233
+ const { enableMediaQueryStyleSheet } = themeOptions
234
+ const useTokens = enableMediaQueryStyleSheet ? useResponsiveThemeTokens : useThemeTokens
235
+ const themeTokens = useTokens('Notification', tokens, variant, { system, viewport })
236
+ const maxWidth = useResponsiveProp(themeOptions?.contentMaxWidth)
237
+
238
+ const notificationComponentRef = useRef({
239
+ containerStyles: {},
240
+ contentContainerStyles: {},
241
+ staticContentContainerStyles: {},
242
+ iconContainerStyles: {},
243
+ dismissButtonContainerStyles: {},
244
+ textStyles: {},
245
+ selectIconPropsStyles: {},
246
+ selectDismissIconPropsStyles: {}
247
+ })
248
+ const mediaIdsRef = useRef({
249
+ containerIds: {},
250
+ contentContainerIds: {},
251
+ staticContentContainerIds: {},
252
+ iconContainerIds: {},
253
+ dismissButtonContainerIds: {},
254
+ textIds: {},
255
+ selectIconPropsIds: {},
256
+ selectDismissIconPropsIds: {}
257
+ })
258
+
259
+ if (enableMediaQueryStyleSheet) {
260
+ notificationComponentRef.current = getMediaQueryStyles(
261
+ themeTokens,
262
+ themeOptions,
263
+ viewport,
264
+ mediaIdsRef,
265
+ dismissible
266
+ )
267
+ } else {
268
+ notificationComponentRef.current = getDefaultStyles(
269
+ themeTokens,
270
+ themeOptions,
271
+ maxWidth,
272
+ dismissible
273
+ )
274
+ }
123
275
 
124
276
  if (isDismissed) {
125
277
  return null
126
278
  }
127
279
 
128
- const textStyles = selectTextStyles(themeTokens, themeOptions, dismissible)
129
-
130
280
  const content = wrapStringsInText(
131
- typeof children === 'function' ? children({ textStyles, variant }) : children,
132
- { style: textStyles }
281
+ typeof children === 'function'
282
+ ? children({ textStyles: notificationComponentRef.current.textStyles.text, variant })
283
+ : children,
284
+ { style: notificationComponentRef.current.textStyles.text }
133
285
  )
134
286
 
135
- const { icon: IconComponent, dismissIcon: DismissIconComponent, dismissIconColor } = themeTokens
287
+ const {
288
+ icon: IconComponent,
289
+ dismissIcon: DismissIconComponent,
290
+ dismissIconColor
291
+ } = enableMediaQueryStyleSheet === false ? themeTokens : themeTokens[viewport]
136
292
 
137
293
  const onDismissPress = () => setIsDismissed(true)
138
294
 
139
295
  return (
140
296
  <View
141
297
  ref={ref}
142
- style={[staticStyles.container, selectContainerStyles(themeTokens)]}
298
+ style={notificationComponentRef.current.containerStyles.container}
299
+ dataSet={mediaIdsRef && { media: mediaIdsRef.current.containerIds.container }}
143
300
  {...selectProps(rest)}
144
301
  >
145
- <View style={[staticStyles.content, selectContentContainerStyle(contentMaxWidth)]}>
146
- <View style={staticStyles.contentContainer}>
302
+ <View
303
+ style={notificationComponentRef.current.contentContainerStyles.contentContainer}
304
+ dataSet={
305
+ mediaIdsRef && { media: mediaIdsRef.current.contentContainerIds.contentContainer }
306
+ }
307
+ >
308
+ <View
309
+ style={
310
+ notificationComponentRef.current.staticContentContainerStyles.staticContentContainer
311
+ }
312
+ dataSet={
313
+ mediaIdsRef && {
314
+ media: mediaIdsRef.current.staticContentContainerIds.staticContentContainer
315
+ }
316
+ }
317
+ >
147
318
  {IconComponent && (
148
- <View style={selectIconContainerStyles(themeTokens)}>
149
- <IconComponent {...selectIconProps(themeTokens)} />
319
+ <View
320
+ style={notificationComponentRef.current.iconContainerStyles.iconContainer}
321
+ dataSet={
322
+ mediaIdsRef && { media: mediaIdsRef.current.iconContainerIds.iconContainer }
323
+ }
324
+ >
325
+ <IconComponent
326
+ {...notificationComponentRef.current.selectIconPropsStyles.selectIconProps}
327
+ />
150
328
  </View>
151
329
  )}
152
- {content && typeof content === 'function' ? content({ textStyles, variant }) : content}
330
+ {content && typeof content === 'function'
331
+ ? content({ textStyles: notificationComponentRef.current.textStyles.text, variant })
332
+ : content}
153
333
  </View>
154
334
  {dismissible && DismissIconComponent && (
155
- <View style={selectDismissButtonContainerStyles(themeTokens)}>
335
+ <View
336
+ style={
337
+ notificationComponentRef.current.dismissButtonContainerStyles.dismissButtonContainer
338
+ }
339
+ dataSet={
340
+ mediaIdsRef && {
341
+ media: mediaIdsRef.current.dismissButtonContainerIds.dismissButtonContainer
342
+ }
343
+ }
344
+ >
156
345
  <IconButton
157
346
  action="close"
158
347
  onPress={onDismissPress}
@@ -161,7 +350,12 @@ const Notification = forwardRef(
161
350
  accessibilityLabel={getCopy('dismiss')}
162
351
  variant={{ inverse: dismissIconColor === '#ffffff', size: 'small' }}
163
352
  >
164
- {() => <DismissIconComponent {...selectDismissIconProps(themeTokens)} />}
353
+ {() => (
354
+ <DismissIconComponent
355
+ {...notificationComponentRef.current.selectDismissIconPropsStyles
356
+ .selectDismissIconProps}
357
+ />
358
+ )}
165
359
  </IconButton>
166
360
  </View>
167
361
  )}
@@ -198,18 +392,3 @@ Notification.propTypes = {
198
392
  }
199
393
 
200
394
  export default Notification
201
-
202
- const staticStyles = StyleSheet.create({
203
- container: {
204
- flexDirection: 'row'
205
- },
206
- contentContainer: {
207
- flexDirection: 'row',
208
- flexShrink: 1
209
- },
210
- content: {
211
- flexDirection: 'row',
212
- flexShrink: 1,
213
- justifyContent: 'space-between'
214
- }
215
- })
@@ -7,29 +7,26 @@ import Item from './Item'
7
7
 
8
8
  const [selectProps, selectedSystemPropsTypes] = selectSystemProps([htmlAttrs, viewProps])
9
9
 
10
- const getChildrenWithParentVariants = (variant, children, start) => {
11
- if (variant)
12
- return children.map((child, i) => {
10
+ const OrderedList = forwardRef(({ children, start, variant, ...rest }, ref) => {
11
+ const childrenWithParentVariants = useMemo(() => {
12
+ const addVariantToProps = (child, i, isLastChild) => {
13
13
  const existingChildVariants = child.props?.variant ?? {}
14
- return {
15
- ...child,
16
- props: {
17
- ...child.props,
18
- index: start + i,
19
- isLastChild: i === children.length - 1,
20
- variant: { ...existingChildVariants, ...variant }
21
- }
14
+ return React.cloneElement(child, {
15
+ index: start + i,
16
+ isLastChild,
17
+ variant: { ...existingChildVariants, ...variant }
18
+ })
19
+ }
20
+
21
+ if (variant) {
22
+ if (Array.isArray(children)) {
23
+ return children.map((child, i) => addVariantToProps(child, i, i === children.length - 1))
22
24
  }
23
- })
24
-
25
- return children
26
- }
25
+ return [addVariantToProps(children, 0, true)]
26
+ }
27
27
 
28
- const OrderedList = forwardRef(({ children, start, variant, ...rest }, ref) => {
29
- const childrenWithParentVariants = useMemo(
30
- () => getChildrenWithParentVariants(variant, children, start),
31
- [children, variant, start]
32
- )
28
+ return children
29
+ }, [children, variant, start])
33
30
 
34
31
  return (
35
32
  <OrderedListBase ref={ref} {...selectProps(rest)}>
@@ -0,0 +1,218 @@
1
+ import React from 'react'
2
+ import { StyleSheet, View } from 'react-native'
3
+ import PropTypes from 'prop-types'
4
+ import Divider from '../Divider'
5
+ import { useViewport } from '../ViewportProvider'
6
+ import { useThemeTokens } from '../ThemeProvider'
7
+ import { selectSystemProps, getTokensPropType, htmlAttrs, viewProps } from '../utils'
8
+ import renderFootnoteContent from './utils/renderFootnoteContent'
9
+ import renderPrice from './utils/renderPrice'
10
+ import renderTypography from './utils/renderTypography'
11
+
12
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs, viewProps])
13
+
14
+ const selectTopTextTypographyTokens = ({ topTextFontSize, topTextLineHeight }) => ({
15
+ fontSize: topTextFontSize,
16
+ lineHeight: topTextLineHeight
17
+ })
18
+
19
+ const selectCurrencySymbolTypographyTokens = ({
20
+ currencySymbolFontSize,
21
+ currencySymbolLineHeight,
22
+ currencySymbolFontWeight
23
+ }) => ({
24
+ fontSize: currencySymbolFontSize,
25
+ lineHeight: currencySymbolLineHeight,
26
+ fontWeight: currencySymbolFontWeight
27
+ })
28
+
29
+ const selectAmountTypographyTokens = ({
30
+ amountFontSize,
31
+ amountLineHeight,
32
+ amountLetterSpacing,
33
+ amountFontWeight,
34
+ fontColor
35
+ }) => {
36
+ // This is used to apply the proper line height to the amount
37
+ const lineHeightMultiplier = 1.18
38
+ return {
39
+ color: fontColor,
40
+ fontSize: amountFontSize,
41
+ lineHeight: amountLineHeight * lineHeightMultiplier,
42
+ letterSpacing: amountLetterSpacing,
43
+ fontWeight: amountFontWeight
44
+ }
45
+ }
46
+
47
+ const selectCentsTypographyTokens = ({ centsFontSize, centsLineHeight, centsFontWeight }) => ({
48
+ fontSize: centsFontSize,
49
+ lineHeight: centsLineHeight,
50
+ fontWeight: centsFontWeight
51
+ })
52
+
53
+ const selectRateTypographyTokens = ({ rateFontSize, rateLineHeight, rateFontWeight }) => ({
54
+ fontSize: rateFontSize,
55
+ lineHeight: rateLineHeight,
56
+ fontWeight: rateFontWeight
57
+ })
58
+
59
+ const selectBottomTextTypographyTokens = ({ bottomTextFontSize, bottomTextLineHeight }) => ({
60
+ fontSize: bottomTextFontSize,
61
+ lineHeight: bottomTextLineHeight
62
+ })
63
+
64
+ const PriceLockup = ({
65
+ size = 'medium',
66
+ signDirection = 'left',
67
+ footnoteLinks,
68
+ topText,
69
+ price,
70
+ currencySymbol = '$',
71
+ rateText,
72
+ ratePosition = 'right',
73
+ bottomText,
74
+ onClickFootnote,
75
+ strikeThrough,
76
+ a11yText,
77
+ tokens: priceLockupTokens,
78
+ variant = {},
79
+ ...rest
80
+ }) => {
81
+ const viewport = useViewport()
82
+ const {
83
+ footnoteMarginTop,
84
+ footnoteGap,
85
+ bottomTextMarginTop,
86
+ priceMarginBottom,
87
+ bottomLinksMarginLeft,
88
+ topTextMarginBottom,
89
+ fontColor,
90
+ dividerColor,
91
+ ...themeTokens
92
+ } = useThemeTokens(
93
+ 'PriceLockup',
94
+ priceLockupTokens,
95
+ { ...variant, size },
96
+ { viewport, strikeThrough }
97
+ )
98
+ const currencySymbolTypographyTokens = selectCurrencySymbolTypographyTokens(themeTokens)
99
+ const amountTypographyTokens = selectAmountTypographyTokens(themeTokens)
100
+ const centsTypographyTokens = selectCentsTypographyTokens(themeTokens)
101
+ const rateTypographyTokens = selectRateTypographyTokens(themeTokens)
102
+ const topTextTypographyTokens = selectTopTextTypographyTokens(themeTokens)
103
+ const bottomTextTypographyTokens = selectBottomTextTypographyTokens(themeTokens)
104
+
105
+ return (
106
+ <View style={[staticStyles.priceLockupContainer, { ...selectProps(rest) }]}>
107
+ {topText ? <View>{renderTypography(topText, topTextTypographyTokens)}</View> : null}
108
+ {renderPrice(
109
+ price,
110
+ rateText,
111
+ ratePosition,
112
+ signDirection,
113
+ currencySymbol,
114
+ currencySymbolTypographyTokens,
115
+ amountTypographyTokens,
116
+ centsTypographyTokens,
117
+ rateTypographyTokens,
118
+ fontColor,
119
+ strikeThrough,
120
+ a11yText,
121
+ bottomText,
122
+ bottomLinksMarginLeft,
123
+ footnoteLinks,
124
+ onClickFootnote,
125
+ themeTokens
126
+ )}
127
+ {bottomText ? (
128
+ <>
129
+ <Divider
130
+ testID="price-lockup-divider"
131
+ role="separator"
132
+ tokens={{ color: dividerColor }}
133
+ />
134
+ {renderFootnoteContent(
135
+ footnoteMarginTop,
136
+ bottomTextMarginTop,
137
+ bottomText,
138
+ bottomTextTypographyTokens,
139
+ fontColor,
140
+ footnoteLinks,
141
+ bottomLinksMarginLeft,
142
+ onClickFootnote,
143
+ themeTokens
144
+ )}
145
+ </>
146
+ ) : null}
147
+ </View>
148
+ )
149
+ }
150
+
151
+ PriceLockup.displayName = 'PriceLockup'
152
+ PriceLockup.propTypes = {
153
+ ...selectedSystemPropTypes,
154
+ /**
155
+ * Size of the component
156
+ *
157
+ * Small for pricing in product catalogue pages, medium for pricing in product comparison cards.
158
+ */
159
+ size: PropTypes.oneOf(['small', 'medium']),
160
+ /**
161
+ * If currency symbol other than `$` to be used
162
+ */
163
+ currencySymbol: PropTypes.string,
164
+ /**
165
+ * Shows additional info above the price
166
+ */
167
+ topText: PropTypes.string,
168
+ /**
169
+ * Monetary value (including decimals separated by ".")
170
+ */
171
+ price: PropTypes.string.isRequired,
172
+ /**
173
+ * Shows month/year unit
174
+ */
175
+ rateText: PropTypes.string,
176
+ /**
177
+ * Shows additional info below the price with a `Divider`
178
+ */
179
+ bottomText: PropTypes.string,
180
+ /**
181
+ * Displays which side the currency should appear (left, right)
182
+ */
183
+ signDirection: PropTypes.oneOf(['right', 'left']),
184
+ /**
185
+ * Displays where the rate should appear (bottom, right)
186
+ */
187
+ ratePosition: PropTypes.oneOf(['right', 'bottom']),
188
+ /**
189
+ * Shows additional link for context
190
+ */
191
+ footnoteLinks: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])),
192
+ /**
193
+ * Function to be called when a footnote link is clicked
194
+ */
195
+ onClickFootnote: PropTypes.func,
196
+ /**
197
+ * To show price savings comparison
198
+ */
199
+ strikeThrough: PropTypes.bool,
200
+ /**
201
+ * To provide a11y text for `PriceLockup` component
202
+ *
203
+ * **Note:** a11yText will override strikethrough price, so it must include price (ie. "was 50 dollars per month")
204
+ */
205
+ a11yText: PropTypes.string,
206
+ /**
207
+ * `PriceLockup` tokens
208
+ */
209
+ tokens: getTokensPropType('PriceLockup')
210
+ }
211
+
212
+ export default PriceLockup
213
+
214
+ const staticStyles = StyleSheet.create({
215
+ priceLockupContainer: {
216
+ alignSelf: 'flex-start'
217
+ }
218
+ })
@@ -0,0 +1,3 @@
1
+ import PriceLockup from './PriceLockup'
2
+
3
+ export default PriceLockup
@@ -0,0 +1,77 @@
1
+ import React from 'react'
2
+ import { StyleSheet, View, Text, Platform } from 'react-native'
3
+ import renderTypography from './renderTypography'
4
+ import renderFootnoteLinks from './renderFootnoteLinks'
5
+
6
+ const selectFootnoteContainer = ({ footnoteMarginTop }) => ({
7
+ marginTop: footnoteMarginTop
8
+ })
9
+
10
+ const selectFootnoteBottomTextContainer = ({ bottomTextMarginTop }) => ({
11
+ marginTop: bottomTextMarginTop
12
+ })
13
+
14
+ const selectFootnoteLinksStyles = ({ bottomLinksMarginLeft }) => ({
15
+ marginLeft: bottomLinksMarginLeft
16
+ })
17
+
18
+ const renderFootnoteContent = (
19
+ footnoteMarginTop,
20
+ bottomTextMarginTop,
21
+ bottomText,
22
+ bottomTextTypographyTokens,
23
+ fontColor,
24
+ footnoteLinks,
25
+ bottomLinksMarginLeft,
26
+ onClickFootnote,
27
+ themeTokens
28
+ ) => {
29
+ const MAX_FOOTNOTE_LINKS_ALLOWED = 3
30
+ return (
31
+ <>
32
+ <View
33
+ style={[staticStyles.footnoteContainer, selectFootnoteContainer({ footnoteMarginTop })]}
34
+ >
35
+ <Text style={selectFootnoteBottomTextContainer({ bottomTextMarginTop })}>
36
+ {renderTypography(bottomText, bottomTextTypographyTokens, undefined, fontColor)}{' '}
37
+ </Text>
38
+ {footnoteLinks.length <= MAX_FOOTNOTE_LINKS_ALLOWED ? (
39
+ <View
40
+ style={[
41
+ staticStyles.footnoteLinkContainer,
42
+ selectFootnoteLinksStyles({ bottomLinksMarginLeft })
43
+ ]}
44
+ >
45
+ {renderFootnoteLinks(footnoteLinks, themeTokens, onClickFootnote)}
46
+ </View>
47
+ ) : null}
48
+ </View>
49
+ {footnoteLinks.length > MAX_FOOTNOTE_LINKS_ALLOWED ? (
50
+ <View style={staticStyles.verticalFootnoteLinkContainer}>
51
+ {renderFootnoteLinks(footnoteLinks, themeTokens, onClickFootnote)}
52
+ </View>
53
+ ) : null}
54
+ </>
55
+ )
56
+ }
57
+
58
+ export default renderFootnoteContent
59
+
60
+ const staticStyles = StyleSheet.create({
61
+ footnoteContainer: {
62
+ flexDirection: 'row'
63
+ },
64
+ footnoteLinkContainer: {
65
+ flexDirection: 'row',
66
+ ...Platform.select({
67
+ ios: { alignSelf: 'flex-end', top: 2 },
68
+ android: { alignSelf: 'center', top: -8 }
69
+ })
70
+ },
71
+ verticalFootnoteLinkContainer: {
72
+ ...Platform.select({
73
+ ios: { top: 10 },
74
+ android: { top: -2 }
75
+ })
76
+ }
77
+ })
@@ -0,0 +1,38 @@
1
+ import React from 'react'
2
+ import FootnoteLink from '../../Footnote/FootnoteLink'
3
+
4
+ const selectFootnoteLinkStyles = (
5
+ {
6
+ footnoteLinkColor,
7
+ footnoteLinkFontName,
8
+ footnoteLinkFontWeight,
9
+ footnoteLinkLineHeight,
10
+ footnoteLinkFontSize
11
+ },
12
+ footnoteLinks
13
+ ) => {
14
+ // This is used to apply the proper line height when there is 4 or more footnote links
15
+ const MAX_FOOTNOTE_LINKS_ALLOWED = 3
16
+ const lineHeight =
17
+ footnoteLinks.length > MAX_FOOTNOTE_LINKS_ALLOWED
18
+ ? footnoteLinkFontSize * footnoteLinkLineHeight
19
+ : undefined
20
+ return {
21
+ color: footnoteLinkColor,
22
+ fontName: footnoteLinkFontName,
23
+ fontWeight: footnoteLinkFontWeight,
24
+ lineHeight,
25
+ fontSize: footnoteLinkFontSize
26
+ }
27
+ }
28
+
29
+ const renderFootnoteLinks = (footnoteLinks, themeTokens, onClickFootnote) =>
30
+ footnoteLinks && footnoteLinks.length > 0 ? (
31
+ <FootnoteLink
32
+ tokens={selectFootnoteLinkStyles(themeTokens, footnoteLinks)}
33
+ content={footnoteLinks}
34
+ onClick={onClickFootnote}
35
+ />
36
+ ) : null
37
+
38
+ export default renderFootnoteLinks