@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.
- package/CHANGELOG.md +26 -2
- package/lib/Carousel/CarouselThumbnail.js +10 -4
- package/lib/Carousel/CarouselThumbnailNavigation.js +3 -3
- package/lib/Footnote/Footnote.js +9 -13
- package/lib/Link/ChevronLink.js +2 -0
- package/lib/Link/InlinePressable.js +15 -2
- package/lib/Link/LinkBase.js +1 -0
- package/lib/Notification/Notification.js +213 -35
- package/lib/OrderedList/OrderedList.js +21 -20
- package/lib/PriceLockup/PriceLockup.js +220 -0
- package/lib/PriceLockup/index.js +10 -0
- package/lib/PriceLockup/utils/renderFootnoteContent.js +93 -0
- package/lib/PriceLockup/utils/renderFootnoteLinks.js +36 -0
- package/lib/PriceLockup/utils/renderPrice.js +147 -0
- package/lib/PriceLockup/utils/renderTypography.js +31 -0
- package/lib/index.js +8 -0
- package/lib/utils/ssr-media-query/create-stylesheet/index.js +1 -2
- package/lib-module/Carousel/CarouselThumbnail.js +10 -4
- package/lib-module/Carousel/CarouselThumbnailNavigation.js +3 -3
- package/lib-module/Footnote/Footnote.js +9 -13
- package/lib-module/Link/ChevronLink.js +2 -0
- package/lib-module/Link/InlinePressable.js +16 -2
- package/lib-module/Link/LinkBase.js +1 -0
- package/lib-module/Notification/Notification.js +216 -38
- package/lib-module/OrderedList/OrderedList.js +21 -20
- package/lib-module/PriceLockup/PriceLockup.js +214 -0
- package/lib-module/PriceLockup/index.js +2 -0
- package/lib-module/PriceLockup/utils/renderFootnoteContent.js +87 -0
- package/lib-module/PriceLockup/utils/renderFootnoteLinks.js +28 -0
- package/lib-module/PriceLockup/utils/renderPrice.js +141 -0
- package/lib-module/PriceLockup/utils/renderTypography.js +23 -0
- package/lib-module/index.js +1 -0
- package/lib-module/utils/ssr-media-query/create-stylesheet/index.js +1 -2
- package/package.json +2 -2
- package/src/Carousel/CarouselThumbnail.jsx +8 -6
- package/src/Carousel/CarouselThumbnailNavigation.jsx +3 -4
- package/src/Footnote/Footnote.jsx +3 -6
- package/src/Link/ChevronLink.jsx +5 -1
- package/src/Link/InlinePressable.jsx +36 -15
- package/src/Link/LinkBase.jsx +1 -0
- package/src/Notification/Notification.jsx +213 -34
- package/src/OrderedList/OrderedList.jsx +17 -20
- package/src/PriceLockup/PriceLockup.jsx +218 -0
- package/src/PriceLockup/index.js +3 -0
- package/src/PriceLockup/utils/renderFootnoteContent.jsx +77 -0
- package/src/PriceLockup/utils/renderFootnoteLinks.jsx +38 -0
- package/src/PriceLockup/utils/renderPrice.jsx +201 -0
- package/src/PriceLockup/utils/renderTypography.jsx +13 -0
- package/src/index.js +1 -0
- 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 {
|
|
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 {
|
|
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
|
|
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'
|
|
132
|
-
|
|
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 {
|
|
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={
|
|
298
|
+
style={notificationComponentRef.current.containerStyles.container}
|
|
299
|
+
dataSet={mediaIdsRef && { media: mediaIdsRef.current.containerIds.container }}
|
|
143
300
|
{...selectProps(rest)}
|
|
144
301
|
>
|
|
145
|
-
<View
|
|
146
|
-
|
|
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
|
|
149
|
-
|
|
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'
|
|
330
|
+
{content && typeof content === 'function'
|
|
331
|
+
? content({ textStyles: notificationComponentRef.current.textStyles.text, variant })
|
|
332
|
+
: content}
|
|
153
333
|
</View>
|
|
154
334
|
{dismissible && DismissIconComponent && (
|
|
155
|
-
<View
|
|
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
|
-
{() =>
|
|
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
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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,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
|