@telus-uds/components-base 1.75.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 (44) hide show
  1. package/CHANGELOG.md +17 -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/OrderedList/OrderedList.js +21 -20
  9. package/lib/PriceLockup/PriceLockup.js +220 -0
  10. package/lib/PriceLockup/index.js +10 -0
  11. package/lib/PriceLockup/utils/renderFootnoteContent.js +93 -0
  12. package/lib/PriceLockup/utils/renderFootnoteLinks.js +36 -0
  13. package/lib/PriceLockup/utils/renderPrice.js +147 -0
  14. package/lib/PriceLockup/utils/renderTypography.js +31 -0
  15. package/lib/index.js +8 -0
  16. package/lib-module/Carousel/CarouselThumbnail.js +10 -4
  17. package/lib-module/Carousel/CarouselThumbnailNavigation.js +3 -3
  18. package/lib-module/Footnote/Footnote.js +9 -13
  19. package/lib-module/Link/ChevronLink.js +2 -0
  20. package/lib-module/Link/InlinePressable.js +16 -2
  21. package/lib-module/Link/LinkBase.js +1 -0
  22. package/lib-module/OrderedList/OrderedList.js +21 -20
  23. package/lib-module/PriceLockup/PriceLockup.js +214 -0
  24. package/lib-module/PriceLockup/index.js +2 -0
  25. package/lib-module/PriceLockup/utils/renderFootnoteContent.js +87 -0
  26. package/lib-module/PriceLockup/utils/renderFootnoteLinks.js +28 -0
  27. package/lib-module/PriceLockup/utils/renderPrice.js +141 -0
  28. package/lib-module/PriceLockup/utils/renderTypography.js +23 -0
  29. package/lib-module/index.js +1 -0
  30. package/package.json +1 -1
  31. package/src/Carousel/CarouselThumbnail.jsx +8 -6
  32. package/src/Carousel/CarouselThumbnailNavigation.jsx +3 -4
  33. package/src/Footnote/Footnote.jsx +3 -6
  34. package/src/Link/ChevronLink.jsx +5 -1
  35. package/src/Link/InlinePressable.jsx +36 -15
  36. package/src/Link/LinkBase.jsx +1 -0
  37. package/src/OrderedList/OrderedList.jsx +17 -20
  38. package/src/PriceLockup/PriceLockup.jsx +218 -0
  39. package/src/PriceLockup/index.js +3 -0
  40. package/src/PriceLockup/utils/renderFootnoteContent.jsx +77 -0
  41. package/src/PriceLockup/utils/renderFootnoteLinks.jsx +38 -0
  42. package/src/PriceLockup/utils/renderPrice.jsx +201 -0
  43. package/src/PriceLockup/utils/renderTypography.jsx +13 -0
  44. package/src/index.js +1 -0
@@ -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
@@ -0,0 +1,201 @@
1
+ import React from 'react'
2
+ import { StyleSheet, View, Text, Platform } from 'react-native'
3
+ import A11yText from '../../A11yText'
4
+ import renderTypography from './renderTypography'
5
+ import renderFootnoteLinks from './renderFootnoteLinks'
6
+
7
+ const selectStrikeThroughStyles = ({ strikeThroughColor }) => ({
8
+ textDecorationColor: strikeThroughColor
9
+ })
10
+
11
+ const selectFootnoteLinksStyles = ({ bottomLinksMarginLeft }) => ({
12
+ marginLeft: bottomLinksMarginLeft
13
+ })
14
+
15
+ const renderCurrencySymbol = (
16
+ currencySymbol,
17
+ currencySymbolTypographyTokens,
18
+ ratePosition,
19
+ fontColor
20
+ ) => renderTypography(`${currencySymbol}`, currencySymbolTypographyTokens, ratePosition, fontColor)
21
+
22
+ const renderAmount = (
23
+ amount,
24
+ amountTypographyTokens,
25
+ strikeThrough,
26
+ a11yText,
27
+ fontColor,
28
+ themeTokens
29
+ ) => {
30
+ const amountText = renderTypography(amount, amountTypographyTokens, undefined, fontColor) // undefined is ratePosition
31
+ if (strikeThrough) {
32
+ return (
33
+ <>
34
+ <A11yText text={a11yText} />
35
+ <Text style={[staticStyles.strikeThroughContainer, selectStrikeThroughStyles(themeTokens)]}>
36
+ {amountText}
37
+ </Text>
38
+ </>
39
+ )
40
+ }
41
+
42
+ return amountText
43
+ }
44
+
45
+ const renderPrice = (
46
+ price,
47
+ rateText,
48
+ ratePosition,
49
+ signDirection,
50
+ currencySymbol,
51
+ currencySymbolTypographyTokens,
52
+ amountTypographyTokens,
53
+ centsTypographyTokens,
54
+ rateTypographyTokens,
55
+ fontColor,
56
+ strikeThrough,
57
+ a11yText,
58
+ bottomText,
59
+ bottomLinksMarginLeft,
60
+ footnoteLinks,
61
+ onClickFootnote,
62
+ themeTokens
63
+ ) => {
64
+ const priceString = price
65
+ const lastDotPosition = priceString.lastIndexOf('.')
66
+ const lastCommaPosition = priceString.lastIndexOf(',')
67
+ const [separator, separatorPosition] =
68
+ lastDotPosition > lastCommaPosition ? ['.', lastDotPosition] : [',', lastCommaPosition]
69
+
70
+ // If the separator is at the fourth character from the end of the string or more, it's most probably
71
+ // a part of the amount, and the cents are not included in the price string
72
+ const EXCLUDE_CENTS = 3
73
+ const hasCents =
74
+ separatorPosition !== -1 && separatorPosition >= priceString.length - EXCLUDE_CENTS
75
+ const amount = hasCents ? priceString.substring(0, separatorPosition) : priceString
76
+ const cents = hasCents ? priceString.substring(separatorPosition + 1) : null
77
+
78
+ const footnoteLinkPositionStyles = rateText
79
+ ? staticStyles.footnoteLinksWithoutBottomText
80
+ : staticStyles.footnoteLinksWithoutBottomTextAndRateText
81
+
82
+ const MAX_FOOTNOTE_LINKS_ALLOWED = 3
83
+ return (
84
+ <>
85
+ <View
86
+ style={
87
+ ratePosition === 'bottom'
88
+ ? staticStyles.priceContainerColumn
89
+ : staticStyles.priceContainerRow
90
+ }
91
+ >
92
+ <View style={staticStyles.priceContainer}>
93
+ {signDirection === 'left' ? (
94
+ <Text>
95
+ {renderCurrencySymbol(
96
+ currencySymbol,
97
+ currencySymbolTypographyTokens,
98
+ ratePosition,
99
+ fontColor
100
+ )}
101
+ </Text>
102
+ ) : null}
103
+ {renderAmount(
104
+ amount,
105
+ amountTypographyTokens,
106
+ strikeThrough,
107
+ a11yText,
108
+ fontColor,
109
+ themeTokens
110
+ )}
111
+ {cents
112
+ ? renderTypography(`${separator}${cents}`, centsTypographyTokens, undefined, fontColor)
113
+ : null}
114
+ {signDirection === 'right' ? (
115
+ <Text style={staticStyles.currencySymbol}>
116
+ {renderCurrencySymbol(
117
+ currencySymbol,
118
+ currencySymbolTypographyTokens,
119
+ ratePosition,
120
+ fontColor
121
+ )}
122
+ </Text>
123
+ ) : null}
124
+ </View>
125
+ {rateText ? (
126
+ <Text
127
+ style={
128
+ ratePosition === 'bottom'
129
+ ? staticStyles.rateTextVerticalPosition
130
+ : [staticStyles.rateTextPosition, staticStyles.rateTextVerticalPosition]
131
+ }
132
+ >
133
+ {renderTypography(rateText, rateTypographyTokens, ratePosition, fontColor)}
134
+ </Text>
135
+ ) : null}
136
+ {!bottomText && footnoteLinks.length <= MAX_FOOTNOTE_LINKS_ALLOWED ? (
137
+ <Text
138
+ style={[
139
+ footnoteLinkPositionStyles,
140
+ selectFootnoteLinksStyles({ bottomLinksMarginLeft })
141
+ ]}
142
+ >
143
+ {renderFootnoteLinks(footnoteLinks, themeTokens, onClickFootnote)}
144
+ </Text>
145
+ ) : null}
146
+ </View>
147
+ {!bottomText && footnoteLinks.length > MAX_FOOTNOTE_LINKS_ALLOWED ? (
148
+ <View style={staticStyles.verticalFootnoteLinkContainer}>
149
+ {renderFootnoteLinks(footnoteLinks, themeTokens, onClickFootnote)}
150
+ </View>
151
+ ) : null}
152
+ </>
153
+ )
154
+ }
155
+
156
+ export default renderPrice
157
+
158
+ const staticStyles = StyleSheet.create({
159
+ priceContainerRow: {
160
+ flexDirection: 'row'
161
+ },
162
+ priceContainerColumn: {
163
+ flexDirection: 'column'
164
+ },
165
+ priceContainer: {
166
+ flexDirection: 'row',
167
+ alignSelf: 'flex-start',
168
+ marginTop: 4
169
+ },
170
+ currencySymbol: {
171
+ paddingLeft: 4
172
+ },
173
+ verticalFootnoteLinkContainer: {
174
+ ...Platform.select({
175
+ ios: { top: 10 },
176
+ android: { top: -2 }
177
+ })
178
+ },
179
+ footnoteLinksWithoutBottomText: {
180
+ ...Platform.select({
181
+ ios: { alignSelf: 'flex-end', top: -10 },
182
+ android: { alignSelf: 'center', top: -14 }
183
+ })
184
+ },
185
+ footnoteLinksWithoutBottomTextAndRateText: {
186
+ ...Platform.select({
187
+ ios: { alignSelf: 'center' },
188
+ android: { alignSelf: 'flex-start' }
189
+ })
190
+ },
191
+ strikeThroughContainer: {
192
+ textDecorationLine: 'line-through',
193
+ textDecorationThickness: '1px'
194
+ },
195
+ rateTextPosition: {
196
+ alignSelf: 'flex-end'
197
+ },
198
+ rateTextVerticalPosition: {
199
+ top: -8
200
+ }
201
+ })
@@ -0,0 +1,13 @@
1
+ import React from 'react'
2
+ import Typography from '../../Typography'
3
+
4
+ const renderTypography = (value, themeTokens, ratePosition, fontColor) => {
5
+ const customProps =
6
+ ratePosition === 'bottom'
7
+ ? { variant: { size: 'micro' }, tokens: { color: fontColor } }
8
+ : { tokens: { ...themeTokens, color: fontColor } }
9
+
10
+ return <Typography {...customProps}>{value}</Typography>
11
+ }
12
+
13
+ export default renderTypography
package/src/index.js CHANGED
@@ -32,6 +32,7 @@ export { default as MultiSelectFilter } from './MultiSelectFilter'
32
32
  export { default as Notification } from './Notification'
33
33
  export { default as OrderedList } from './OrderedList'
34
34
  export { default as Pagination } from './Pagination'
35
+ export { default as PriceLockup } from './PriceLockup'
35
36
  export { default as Progress } from './Progress'
36
37
  export { default as QuickLinks } from './QuickLinks'
37
38
  export { default as QuickLinksFeature } from './QuickLinksFeature'