@telus-uds/components-base 1.75.0 → 1.77.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 +31 -2
- package/lib/Box/Box.js +112 -7
- package/lib/Box/backgroundImageStylesMap.js +101 -0
- package/lib/Carousel/CarouselThumbnail.js +10 -4
- package/lib/Carousel/CarouselThumbnailNavigation.js +3 -3
- package/lib/ExpandCollapse/Panel.js +21 -10
- 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/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/Skeleton/Skeleton.js +6 -3
- package/lib/index.js +8 -0
- package/lib-module/Box/Box.js +115 -9
- package/lib-module/Box/backgroundImageStylesMap.js +94 -0
- package/lib-module/Carousel/CarouselThumbnail.js +10 -4
- package/lib-module/Carousel/CarouselThumbnailNavigation.js +3 -3
- package/lib-module/ExpandCollapse/Panel.js +21 -10
- 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/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/Skeleton/Skeleton.js +6 -3
- package/lib-module/index.js +1 -0
- package/package.json +2 -2
- package/src/Box/Box.jsx +120 -9
- package/src/Box/backgroundImageStylesMap.js +21 -0
- package/src/Carousel/CarouselThumbnail.jsx +8 -6
- package/src/Carousel/CarouselThumbnailNavigation.jsx +3 -4
- package/src/ExpandCollapse/Panel.jsx +16 -10
- 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/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/Skeleton/Skeleton.jsx +8 -3
- package/src/index.js +1 -0
- package/types/Typography.d.ts +1 -0
package/src/Box/Box.jsx
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import React, { forwardRef } from 'react'
|
|
1
|
+
import React, { forwardRef, useEffect, useState } from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
|
-
import { View, ScrollView, Platform } from 'react-native'
|
|
3
|
+
import { View, ScrollView, Platform, StyleSheet, ImageBackground, Image } from 'react-native'
|
|
4
4
|
import { useThemeTokens } from '../ThemeProvider'
|
|
5
5
|
import {
|
|
6
6
|
a11yProps,
|
|
7
7
|
getA11yPropsFromHtmlTag,
|
|
8
8
|
getTokensPropType,
|
|
9
9
|
layoutTags,
|
|
10
|
+
responsiveProps,
|
|
10
11
|
selectSystemProps,
|
|
11
12
|
spacingProps,
|
|
13
|
+
useResponsiveProp,
|
|
12
14
|
useSpacingScale,
|
|
13
15
|
variantProp,
|
|
14
16
|
viewProps
|
|
15
17
|
} from '../utils'
|
|
18
|
+
import backgroundImageStylesMap from './backgroundImageStylesMap'
|
|
16
19
|
|
|
17
20
|
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
|
|
18
21
|
|
|
@@ -69,6 +72,50 @@ const selectBoxStyles = (
|
|
|
69
72
|
return styles
|
|
70
73
|
}
|
|
71
74
|
|
|
75
|
+
const setBackgroundImage = ({
|
|
76
|
+
src,
|
|
77
|
+
alt,
|
|
78
|
+
backgroundImageResizeMode,
|
|
79
|
+
backgroundImagePosition,
|
|
80
|
+
backgroundImageAlign,
|
|
81
|
+
backgroundImageWidth,
|
|
82
|
+
backgroundImageHeight,
|
|
83
|
+
content
|
|
84
|
+
}) => {
|
|
85
|
+
if (backgroundImageResizeMode === 'contain') {
|
|
86
|
+
const containedViewStyle = {
|
|
87
|
+
...staticStyles.containedView,
|
|
88
|
+
width: backgroundImageWidth,
|
|
89
|
+
height: backgroundImageHeight,
|
|
90
|
+
...backgroundImageStylesMap[`${backgroundImagePosition}-${backgroundImageAlign}`]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<View style={staticStyles.containedContainer}>
|
|
95
|
+
<View style={containedViewStyle}>
|
|
96
|
+
<Image
|
|
97
|
+
source={src}
|
|
98
|
+
alt={alt}
|
|
99
|
+
style={staticStyles.containedImage}
|
|
100
|
+
accessibilityIgnoresInvertColors
|
|
101
|
+
/>
|
|
102
|
+
</View>
|
|
103
|
+
{content}
|
|
104
|
+
</View>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
return (
|
|
108
|
+
<ImageBackground
|
|
109
|
+
source={src}
|
|
110
|
+
alt={alt}
|
|
111
|
+
style={staticStyles.backgroundImageContainer}
|
|
112
|
+
resizeMode={backgroundImageResizeMode}
|
|
113
|
+
>
|
|
114
|
+
{content}
|
|
115
|
+
</ImageBackground>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
72
119
|
/**
|
|
73
120
|
* A layout utility component. Use Box to create space (padding) around content.
|
|
74
121
|
*
|
|
@@ -154,6 +201,7 @@ const Box = forwardRef(
|
|
|
154
201
|
testID,
|
|
155
202
|
dataSet,
|
|
156
203
|
customGradient,
|
|
204
|
+
backgroundImage,
|
|
157
205
|
...rest
|
|
158
206
|
},
|
|
159
207
|
ref
|
|
@@ -174,23 +222,52 @@ const Box = forwardRef(
|
|
|
174
222
|
...selectBoxStyles(themeTokens, customGradient)
|
|
175
223
|
}
|
|
176
224
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
225
|
+
let content = children
|
|
226
|
+
if (typeof customGradient === 'function')
|
|
227
|
+
content = customGradient(styles.colors, styles)(children)
|
|
228
|
+
|
|
229
|
+
const { src = '', alt = '', resizeMode = '', position = '', align = '' } = backgroundImage || {}
|
|
230
|
+
const backgroundImageResizeMode = useResponsiveProp(resizeMode, 'cover')
|
|
231
|
+
const backgroundImagePosition = useResponsiveProp(position, 'none')
|
|
232
|
+
const backgroundImageAlign = useResponsiveProp(align, 'stretch')
|
|
233
|
+
const [backgroundImageWidth, setBackgroundImageWidth] = useState(0)
|
|
234
|
+
const [backgroundImageHeight, setBackgroundImageHeight] = useState(0)
|
|
235
|
+
if (backgroundImage)
|
|
236
|
+
content = setBackgroundImage({
|
|
237
|
+
src,
|
|
238
|
+
alt,
|
|
239
|
+
backgroundImageResizeMode,
|
|
240
|
+
backgroundImagePosition,
|
|
241
|
+
backgroundImageAlign,
|
|
242
|
+
backgroundImageWidth,
|
|
243
|
+
backgroundImageHeight,
|
|
244
|
+
content
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
useEffect(() => {
|
|
248
|
+
if (backgroundImage && backgroundImageWidth === 0 && backgroundImageHeight === 0) {
|
|
249
|
+
Image.getSize(src, (width, height) => {
|
|
250
|
+
// Only update the state if the size has changed
|
|
251
|
+
if (width !== backgroundImageWidth || height !== backgroundImageHeight) {
|
|
252
|
+
setBackgroundImageWidth(width)
|
|
253
|
+
setBackgroundImageHeight(height)
|
|
254
|
+
}
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
}, [backgroundImage, backgroundImageWidth, backgroundImageHeight, src])
|
|
181
258
|
|
|
182
259
|
if (scroll) {
|
|
183
260
|
const scrollProps = typeof scroll === 'object' ? scroll : {}
|
|
184
261
|
scrollProps.contentContainerStyle = [styles, scrollProps.contentContainerStyle]
|
|
185
262
|
return (
|
|
186
263
|
<ScrollView {...scrollProps} {...props} testID={testID} dataSet={dataSet} ref={ref}>
|
|
187
|
-
{
|
|
264
|
+
{content}
|
|
188
265
|
</ScrollView>
|
|
189
266
|
)
|
|
190
267
|
}
|
|
191
268
|
return (
|
|
192
269
|
<View {...props} style={styles} testID={testID} dataSet={dataSet} ref={ref}>
|
|
193
|
-
{
|
|
270
|
+
{content}
|
|
194
271
|
</View>
|
|
195
272
|
)
|
|
196
273
|
}
|
|
@@ -284,7 +361,41 @@ Box.propTypes = {
|
|
|
284
361
|
/**
|
|
285
362
|
Use this prop if need to add a custom gradient for mobile
|
|
286
363
|
*/
|
|
287
|
-
customGradient: PropTypes.func
|
|
364
|
+
customGradient: PropTypes.func,
|
|
365
|
+
/**
|
|
366
|
+
* Use this prop to add a background image to the box.
|
|
367
|
+
*/
|
|
368
|
+
backgroundImage: PropTypes.shape({
|
|
369
|
+
src: PropTypes.string.isRequired,
|
|
370
|
+
alt: PropTypes.string,
|
|
371
|
+
resizeMode: responsiveProps.getTypeOptionallyByViewport(
|
|
372
|
+
PropTypes.oneOf(['cover', 'contain', 'stretch', 'repeat', 'center'])
|
|
373
|
+
),
|
|
374
|
+
position: responsiveProps.getTypeOptionallyByViewport(
|
|
375
|
+
PropTypes.oneOf(['none', 'bottom', 'left', 'right', 'top'])
|
|
376
|
+
),
|
|
377
|
+
align: responsiveProps.getTypeOptionallyByViewport(
|
|
378
|
+
PropTypes.oneOf(['start', 'end', 'center', 'stretch'])
|
|
379
|
+
)
|
|
380
|
+
})
|
|
288
381
|
}
|
|
289
382
|
|
|
290
383
|
export default Box
|
|
384
|
+
|
|
385
|
+
const staticStyles = StyleSheet.create({
|
|
386
|
+
backgroundImageContainer: {
|
|
387
|
+
flex: 1
|
|
388
|
+
},
|
|
389
|
+
containedContainer: {
|
|
390
|
+
flex: 1,
|
|
391
|
+
overflow: 'hidden'
|
|
392
|
+
},
|
|
393
|
+
containedView: {
|
|
394
|
+
zIndex: -1,
|
|
395
|
+
position: 'absolute'
|
|
396
|
+
},
|
|
397
|
+
containedImage: {
|
|
398
|
+
width: '100%',
|
|
399
|
+
height: '100%'
|
|
400
|
+
}
|
|
401
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
'top-start': { top: 0 },
|
|
3
|
+
'top-center': { left: 0, right: 0, marginHorizontal: 'auto' },
|
|
4
|
+
'top-end': { top: 0, right: 0 },
|
|
5
|
+
'right-start': { top: 0, right: 0 },
|
|
6
|
+
'left-start': { top: 0 },
|
|
7
|
+
'left-center': { top: 0, bottom: 0, marginVertical: 'auto' },
|
|
8
|
+
'none-start': { top: 0, bottom: 0, marginVertical: 'auto' },
|
|
9
|
+
'none-center': { top: 0, bottom: 0, left: 0, right: 0, margin: 'auto' },
|
|
10
|
+
'right-center': { top: 0, bottom: 0, right: 0, marginVertical: 'auto' },
|
|
11
|
+
'none-end': { top: 0, bottom: 0, right: 0, marginVertical: 'auto' },
|
|
12
|
+
'bottom-start': { bottom: 0, left: 0 },
|
|
13
|
+
'left-end': { bottom: 0, left: 0 },
|
|
14
|
+
'bottom-center': { left: 0, right: 0, bottom: 0, marginHorizontal: 'auto' },
|
|
15
|
+
'bottom-end': { right: 0, bottom: 0 },
|
|
16
|
+
'right-end': { right: 0, bottom: 0 },
|
|
17
|
+
'top-stretch': { left: 0, right: 0, width: '100%' },
|
|
18
|
+
'left-stretch': { top: 0, bottom: 0, height: '100%' },
|
|
19
|
+
'right-stretch': { top: 0, bottom: 0, right: 0, height: '100%' },
|
|
20
|
+
'bottom-stretch': { left: 0, right: 0, bottom: 0, width: '100%' }
|
|
21
|
+
}
|
|
@@ -32,7 +32,7 @@ const CarouselThumbnail = ({ accessibilityLabel, alt, index, src }) => {
|
|
|
32
32
|
// Allow using the spacebar for navigation
|
|
33
33
|
if (event?.key === ' ') goTo(index)
|
|
34
34
|
}
|
|
35
|
-
const { borderWidth, padding, selectedBorderColor, selectedBorderWidth, size } =
|
|
35
|
+
const { borderWidth, padding, selectedBorderColor, selectedBorderWidth, size, margin } =
|
|
36
36
|
getThumbnailTokens({ viewport })
|
|
37
37
|
const styles = {
|
|
38
38
|
image: {
|
|
@@ -42,7 +42,12 @@ const CarouselThumbnail = ({ accessibilityLabel, alt, index, src }) => {
|
|
|
42
42
|
selected: {
|
|
43
43
|
borderColor: selectedBorderColor,
|
|
44
44
|
borderWidth: selectedBorderWidth,
|
|
45
|
-
padding: padding - selectedBorderWidth
|
|
45
|
+
padding: padding - selectedBorderWidth,
|
|
46
|
+
marginBottom: margin + selectedBorderWidth
|
|
47
|
+
},
|
|
48
|
+
nonSelected: {
|
|
49
|
+
padding: padding - borderWidth,
|
|
50
|
+
marginBottom: margin + selectedBorderWidth
|
|
46
51
|
}
|
|
47
52
|
}
|
|
48
53
|
|
|
@@ -62,10 +67,7 @@ const CarouselThumbnail = ({ accessibilityLabel, alt, index, src }) => {
|
|
|
62
67
|
|
|
63
68
|
return [
|
|
64
69
|
pressableStyles,
|
|
65
|
-
index === activeIndex
|
|
66
|
-
{
|
|
67
|
-
outline: 'none'
|
|
68
|
-
}
|
|
70
|
+
index === activeIndex ? [styles.selected, { outline: 'none' }] : styles.nonSelected
|
|
69
71
|
]
|
|
70
72
|
}}
|
|
71
73
|
>
|
|
@@ -14,20 +14,19 @@ const CarouselThumbnailNavigation = forwardRef(({ thumbnails = [] }, ref) => {
|
|
|
14
14
|
if (thumbnails.length !== totalItems) {
|
|
15
15
|
throw new Error('Thumbnail set provided does not match the number of slides in the carousel')
|
|
16
16
|
}
|
|
17
|
-
const { containerPaddingTop: thumbnailContainerPaddingTop
|
|
18
|
-
useThemeTokens('CarouselThumbnail')
|
|
17
|
+
const { containerPaddingTop: thumbnailContainerPaddingTop } = useThemeTokens('CarouselThumbnail')
|
|
19
18
|
const stackWrapTokens = {
|
|
20
19
|
justifyContent: 'flex-start'
|
|
21
20
|
}
|
|
22
21
|
const containerStyles = {
|
|
23
22
|
justifyContent: 'center',
|
|
24
23
|
alignItems,
|
|
25
|
-
paddingTop: thumbnailContainerPaddingTop
|
|
24
|
+
paddingTop: thumbnailContainerPaddingTop
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
return (
|
|
29
28
|
<View style={containerStyles}>
|
|
30
|
-
<StackWrap direction="row" tokens={stackWrapTokens} ref={ref}>
|
|
29
|
+
<StackWrap direction="row" tokens={stackWrapTokens} space={2} ref={ref}>
|
|
31
30
|
{thumbnails.map(({ accessibilityLabel, alt, src }, index) => (
|
|
32
31
|
<CarouselThumbnail
|
|
33
32
|
accessibilityLabel={accessibilityLabel}
|
|
@@ -64,6 +64,10 @@ const selectContentPanelStyles = ({
|
|
|
64
64
|
marginBottom
|
|
65
65
|
})
|
|
66
66
|
|
|
67
|
+
const selectControlPanelStyles = ({ contentPanelBackgroundColor }) => ({
|
|
68
|
+
backgroundColor: contentPanelBackgroundColor
|
|
69
|
+
})
|
|
70
|
+
|
|
67
71
|
/**
|
|
68
72
|
* An item in an `ExpandCollapse` which contains collapsible `children` and a `control` that opens
|
|
69
73
|
* and closes the collapsible children when pressed.
|
|
@@ -136,16 +140,18 @@ const ExpandCollapsePanel = forwardRef(
|
|
|
136
140
|
</View>
|
|
137
141
|
) : (
|
|
138
142
|
<View ref={ref} style={themeTokens}>
|
|
139
|
-
<
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
143
|
+
<View style={selectControlPanelStyles(themeTokens)}>
|
|
144
|
+
<ExpandCollapseControl
|
|
145
|
+
{...selectedProps}
|
|
146
|
+
isExpanded={isExpanded}
|
|
147
|
+
tokens={controlTokens}
|
|
148
|
+
variant={variant}
|
|
149
|
+
onPress={handleControlPress}
|
|
150
|
+
ref={controlRef}
|
|
151
|
+
>
|
|
152
|
+
{control}
|
|
153
|
+
</ExpandCollapseControl>
|
|
154
|
+
</View>
|
|
149
155
|
{isExpanded && (
|
|
150
156
|
<View
|
|
151
157
|
style={{
|
|
@@ -156,13 +156,10 @@ const Footnote = ({
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
return (
|
|
159
|
-
// TODO: Extract the OrderedList.Item from the array when the issue #4361 is fixed
|
|
160
159
|
<OrderedList start={number}>
|
|
161
|
-
{
|
|
162
|
-
<
|
|
163
|
-
|
|
164
|
-
</OrderedList.Item>
|
|
165
|
-
]}
|
|
160
|
+
<OrderedList.Item key={number}>
|
|
161
|
+
<Text style={selectCustomContentFontStyle(themeTokens)}>{content}</Text>
|
|
162
|
+
</OrderedList.Item>
|
|
166
163
|
</OrderedList>
|
|
167
164
|
)
|
|
168
165
|
}, [content, number, themeTokens])
|
package/src/Link/ChevronLink.jsx
CHANGED
|
@@ -12,7 +12,10 @@ import LinkBase from './LinkBase'
|
|
|
12
12
|
* ChevronLink is not intended to be deeply themable; variants passed to ChevronLink are forwarded to Link.
|
|
13
13
|
*/
|
|
14
14
|
const ChevronLink = forwardRef(
|
|
15
|
-
(
|
|
15
|
+
(
|
|
16
|
+
{ direction = 'right', children, tokens = {}, variant, dataSet, onPress, ...otherlinkProps },
|
|
17
|
+
ref
|
|
18
|
+
) => {
|
|
16
19
|
const getChevronTokens = useThemeTokensCallback('ChevronLink', tokens, variant)
|
|
17
20
|
const applyChevronTokens = (linkState) => {
|
|
18
21
|
const { leftIcon, rightIcon, iconDisplace, height, fontSize, ...otherTokens } =
|
|
@@ -38,6 +41,7 @@ const ChevronLink = forwardRef(
|
|
|
38
41
|
tokens={getTokens}
|
|
39
42
|
dataSet={dataSet}
|
|
40
43
|
ref={ref}
|
|
44
|
+
onPress={onPress}
|
|
41
45
|
>
|
|
42
46
|
{children}
|
|
43
47
|
</LinkBase>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { forwardRef } from 'react'
|
|
2
|
-
import { Pressable, StyleSheet } from 'react-native'
|
|
1
|
+
import React, { forwardRef, useCallback } from 'react'
|
|
2
|
+
import { Pressable, StyleSheet, Platform } from 'react-native'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @typedef {import('react-native').PressableProps} PressableProps
|
|
@@ -14,19 +14,40 @@ import { Pressable, StyleSheet } from 'react-native'
|
|
|
14
14
|
* @param {PressableProps} PressableProps
|
|
15
15
|
*/
|
|
16
16
|
// React Native exports prop Types but not propTypes, use JSDoc types here rather than duplicate RN
|
|
17
|
-
|
|
18
|
-
const InlinePressable = forwardRef(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
]
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
)
|
|
17
|
+
/* eslint-disable react/prop-types */
|
|
18
|
+
const InlinePressable = forwardRef(
|
|
19
|
+
({ children, inlineFlex = true, style, onPress, ...props }, ref) => {
|
|
20
|
+
const handlePress = useCallback(() => {
|
|
21
|
+
if (onPress) {
|
|
22
|
+
onPress()
|
|
23
|
+
}
|
|
24
|
+
}, [onPress])
|
|
25
|
+
|
|
26
|
+
const handleKeyPress = useCallback(
|
|
27
|
+
(e) => {
|
|
28
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
29
|
+
handlePress()
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
[handlePress]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Pressable
|
|
37
|
+
ref={ref}
|
|
38
|
+
style={(pressState) => [
|
|
39
|
+
typeof style === 'function' ? style(pressState) : style,
|
|
40
|
+
staticStyles[inlineFlex ? 'inlineFlex' : 'inline']
|
|
41
|
+
]}
|
|
42
|
+
onPress={handlePress}
|
|
43
|
+
onKeyDown={Platform.OS === 'web' ? handleKeyPress : undefined}
|
|
44
|
+
{...props}
|
|
45
|
+
>
|
|
46
|
+
{(pressState) => (typeof children === 'function' ? children(pressState) : children)}
|
|
47
|
+
</Pressable>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
)
|
|
30
51
|
InlinePressable.displayName = 'InlinePressable'
|
|
31
52
|
|
|
32
53
|
const staticStyles = StyleSheet.create({
|
package/src/Link/LinkBase.jsx
CHANGED
|
@@ -162,6 +162,7 @@ const LinkBase = forwardRef(
|
|
|
162
162
|
inlineFlex={hasIcon}
|
|
163
163
|
// assuming links without icons should be inline (even if they are long)
|
|
164
164
|
ref={ref}
|
|
165
|
+
keyboardShouldPersistTaps="handled"
|
|
165
166
|
style={(linkState) => {
|
|
166
167
|
const themeTokens = resolveLinkTokens(linkState)
|
|
167
168
|
const outerBorderStyles = selectOuterBorderStyles(themeTokens)
|
|
@@ -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
|
+
})
|