jfs-components 0.1.17 → 0.1.19
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 +15 -0
- package/lib/commonjs/components/AvatarGroup/AvatarGroup.js +41 -20
- package/lib/commonjs/components/Card/Card.js +29 -12
- package/lib/commonjs/components/CardFeedback/CardFeedback.js +16 -2
- package/lib/commonjs/components/CardInsight/CardInsight.js +34 -13
- package/lib/commonjs/components/CardProviderInfo/CardProviderInfo.js +23 -3
- package/lib/commonjs/components/DebitCard/DebitCard.js +23 -3
- package/lib/commonjs/components/MediaCard/MediaCard.js +24 -7
- package/lib/commonjs/components/ProductMerchandisingCard/ProductMerchandisingCard.js +64 -60
- package/lib/commonjs/components/RechargeCard/RechargeCard.js +34 -13
- package/lib/commonjs/components/TestimonialsCard/TestimonialsCard.js +23 -5
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/commonjs/utils/GlassFill/GlassFill.js +69 -73
- package/lib/module/components/AvatarGroup/AvatarGroup.js +43 -21
- package/lib/module/components/Card/Card.js +31 -14
- package/lib/module/components/CardFeedback/CardFeedback.js +17 -3
- package/lib/module/components/CardInsight/CardInsight.js +36 -15
- package/lib/module/components/CardProviderInfo/CardProviderInfo.js +25 -5
- package/lib/module/components/DebitCard/DebitCard.js +25 -5
- package/lib/module/components/MediaCard/MediaCard.js +26 -9
- package/lib/module/components/ProductMerchandisingCard/ProductMerchandisingCard.js +66 -62
- package/lib/module/components/RechargeCard/RechargeCard.js +36 -15
- package/lib/module/components/TestimonialsCard/TestimonialsCard.js +25 -7
- package/lib/module/icons/registry.js +1 -1
- package/lib/module/utils/GlassFill/GlassFill.js +69 -72
- package/lib/typescript/src/components/Card/Card.d.ts +10 -1
- package/lib/typescript/src/components/CardFeedback/CardFeedback.d.ts +10 -1
- package/lib/typescript/src/components/CardInsight/CardInsight.d.ts +10 -1
- package/lib/typescript/src/components/CardProviderInfo/CardProviderInfo.d.ts +10 -1
- package/lib/typescript/src/components/DebitCard/DebitCard.d.ts +10 -1
- package/lib/typescript/src/components/MediaCard/MediaCard.d.ts +10 -1
- package/lib/typescript/src/components/RechargeCard/RechargeCard.d.ts +10 -1
- package/lib/typescript/src/components/TestimonialsCard/TestimonialsCard.d.ts +8 -1
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/lib/typescript/src/utils/GlassFill/GlassFill.d.ts +11 -4
- package/package.json +1 -2
- package/src/components/AvatarGroup/AvatarGroup.tsx +44 -18
- package/src/components/Card/Card.tsx +46 -15
- package/src/components/CardFeedback/CardFeedback.tsx +29 -4
- package/src/components/CardInsight/CardInsight.tsx +48 -17
- package/src/components/CardProviderInfo/CardProviderInfo.tsx +36 -3
- package/src/components/DebitCard/DebitCard.tsx +36 -3
- package/src/components/MediaCard/MediaCard.tsx +40 -9
- package/src/components/ProductMerchandisingCard/ProductMerchandisingCard.tsx +76 -47
- package/src/components/RechargeCard/RechargeCard.tsx +48 -13
- package/src/components/TestimonialsCard/TestimonialsCard.tsx +37 -6
- package/src/icons/registry.ts +1 -1
- package/src/utils/GlassFill/GlassFill.tsx +59 -56
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { createContext, useContext, isValidElement, cloneElement } from 'react';
|
|
2
|
-
import { View, Text,
|
|
2
|
+
import { View, Text, Pressable, type ViewStyle, type TextStyle, type StyleProp, Image } from 'react-native';
|
|
3
3
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
4
4
|
import IconComponent from '../../icons/Icon';
|
|
5
5
|
import type { Modes } from '../../design-tokens'
|
|
@@ -22,6 +22,15 @@ export interface CardFeedbackProps {
|
|
|
22
22
|
* Style overrides for the card container.
|
|
23
23
|
*/
|
|
24
24
|
style?: StyleProp<ViewStyle>;
|
|
25
|
+
/**
|
|
26
|
+
* Press handler for the whole card. When set, the card becomes pressable
|
|
27
|
+
* (rendered through a `Pressable` with `accessibilityRole="button"`).
|
|
28
|
+
*/
|
|
29
|
+
onPress?: () => void;
|
|
30
|
+
/** Disable interaction when `onPress` is set. */
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
/** Accessibility label forwarded to the pressable wrapper. */
|
|
33
|
+
accessibilityLabel?: string;
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
/**
|
|
@@ -33,6 +42,9 @@ export function CardFeedback({
|
|
|
33
42
|
children,
|
|
34
43
|
modes: propModes,
|
|
35
44
|
style,
|
|
45
|
+
onPress,
|
|
46
|
+
disabled,
|
|
47
|
+
accessibilityLabel,
|
|
36
48
|
}: CardFeedbackProps) {
|
|
37
49
|
const modes = {
|
|
38
50
|
'Appearance.System': 'positive',
|
|
@@ -61,9 +73,22 @@ export function CardFeedback({
|
|
|
61
73
|
|
|
62
74
|
return (
|
|
63
75
|
<CardFeedbackContext.Provider value={{ modes }}>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
{onPress ? (
|
|
77
|
+
<Pressable
|
|
78
|
+
style={[containerStyle, style]}
|
|
79
|
+
onPress={onPress}
|
|
80
|
+
disabled={disabled}
|
|
81
|
+
accessibilityRole="button"
|
|
82
|
+
accessibilityLabel={accessibilityLabel}
|
|
83
|
+
accessibilityState={{ disabled: !!disabled }}
|
|
84
|
+
>
|
|
85
|
+
{children}
|
|
86
|
+
</Pressable>
|
|
87
|
+
) : (
|
|
88
|
+
<View style={[containerStyle, style]} accessibilityLabel={accessibilityLabel}>
|
|
89
|
+
{children}
|
|
90
|
+
</View>
|
|
91
|
+
)}
|
|
67
92
|
</CardFeedbackContext.Provider>
|
|
68
93
|
);
|
|
69
94
|
}
|
|
@@ -2,6 +2,7 @@ import React from 'react'
|
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
Text,
|
|
5
|
+
Pressable,
|
|
5
6
|
type StyleProp,
|
|
6
7
|
type ViewStyle,
|
|
7
8
|
type TextStyle,
|
|
@@ -45,6 +46,15 @@ export type CardInsightProps = {
|
|
|
45
46
|
modes?: Modes
|
|
46
47
|
/** Override container styles. */
|
|
47
48
|
style?: StyleProp<ViewStyle>
|
|
49
|
+
/**
|
|
50
|
+
* Press handler for the whole card. When set, the card becomes pressable
|
|
51
|
+
* (rendered through a `Pressable` with `accessibilityRole="button"`).
|
|
52
|
+
*/
|
|
53
|
+
onPress?: () => void
|
|
54
|
+
/** Disable interaction when `onPress` is set. */
|
|
55
|
+
disabled?: boolean
|
|
56
|
+
/** Accessibility label forwarded to the pressable wrapper (defaults to `title`). */
|
|
57
|
+
accessibilityLabel?: string
|
|
48
58
|
}
|
|
49
59
|
|
|
50
60
|
/**
|
|
@@ -67,6 +77,9 @@ function CardInsight({
|
|
|
67
77
|
divider,
|
|
68
78
|
modes = EMPTY_MODES,
|
|
69
79
|
style,
|
|
80
|
+
onPress,
|
|
81
|
+
disabled,
|
|
82
|
+
accessibilityLabel,
|
|
70
83
|
}: CardInsightProps) {
|
|
71
84
|
const background =
|
|
72
85
|
(getVariableByName('cardInsight/background', modes) as string | null) ?? '#ffffff'
|
|
@@ -160,23 +173,20 @@ function CardInsight({
|
|
|
160
173
|
const footerNode = renderFooter()
|
|
161
174
|
const showDivider = footerNode !== null && divider !== false && divider !== null
|
|
162
175
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
style,
|
|
178
|
-
]}
|
|
179
|
-
>
|
|
176
|
+
const containerStyle: ViewStyle = {
|
|
177
|
+
backgroundColor: background,
|
|
178
|
+
borderRadius: radius,
|
|
179
|
+
borderWidth,
|
|
180
|
+
borderColor,
|
|
181
|
+
paddingHorizontal,
|
|
182
|
+
paddingVertical,
|
|
183
|
+
gap,
|
|
184
|
+
overflow: 'hidden',
|
|
185
|
+
alignItems: 'stretch',
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const content = (
|
|
189
|
+
<>
|
|
180
190
|
<View
|
|
181
191
|
style={{
|
|
182
192
|
flexDirection: 'row',
|
|
@@ -237,6 +247,27 @@ function CardInsight({
|
|
|
237
247
|
{showDivider ? renderDivider() : null}
|
|
238
248
|
|
|
239
249
|
{footerNode}
|
|
250
|
+
</>
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
if (onPress) {
|
|
254
|
+
return (
|
|
255
|
+
<Pressable
|
|
256
|
+
style={[containerStyle, style]}
|
|
257
|
+
onPress={onPress}
|
|
258
|
+
disabled={disabled}
|
|
259
|
+
accessibilityRole="button"
|
|
260
|
+
accessibilityLabel={accessibilityLabel ?? title}
|
|
261
|
+
accessibilityState={{ disabled: !!disabled }}
|
|
262
|
+
>
|
|
263
|
+
{content}
|
|
264
|
+
</Pressable>
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<View style={[containerStyle, style]} accessibilityLabel={accessibilityLabel}>
|
|
270
|
+
{content}
|
|
240
271
|
</View>
|
|
241
272
|
)
|
|
242
273
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { View, type ViewStyle, type StyleProp, type ImageSourcePropType } from 'react-native'
|
|
2
|
+
import { View, Pressable, type ViewStyle, type StyleProp, type ImageSourcePropType } from 'react-native'
|
|
3
3
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
4
4
|
import ProductLabel from '../ProductLabel/ProductLabel'
|
|
5
5
|
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
@@ -16,6 +16,15 @@ export type CardProviderInfoProps = {
|
|
|
16
16
|
modes?: Modes
|
|
17
17
|
/** Override container styles. */
|
|
18
18
|
style?: StyleProp<ViewStyle>
|
|
19
|
+
/**
|
|
20
|
+
* Press handler for the whole card. When set, the card becomes pressable
|
|
21
|
+
* (rendered through a `Pressable` with `accessibilityRole="button"`).
|
|
22
|
+
*/
|
|
23
|
+
onPress?: () => void
|
|
24
|
+
/** Disable interaction when `onPress` is set. */
|
|
25
|
+
disabled?: boolean
|
|
26
|
+
/** Accessibility label forwarded to the pressable wrapper (defaults to `label`). */
|
|
27
|
+
accessibilityLabel?: string
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
/**
|
|
@@ -31,6 +40,9 @@ function CardProviderInfo({
|
|
|
31
40
|
children,
|
|
32
41
|
modes = EMPTY_MODES,
|
|
33
42
|
style,
|
|
43
|
+
onPress,
|
|
44
|
+
disabled,
|
|
45
|
+
accessibilityLabel,
|
|
34
46
|
}: CardProviderInfoProps) {
|
|
35
47
|
const background = getVariableByName('card/providerInfo/background', modes) ?? '#fef4e5'
|
|
36
48
|
const border = getVariableByName('card/providerInfo/border', modes) ?? '#fef4e5'
|
|
@@ -59,8 +71,8 @@ function CardProviderInfo({
|
|
|
59
71
|
rows.push(childArray.slice(i, i + 2))
|
|
60
72
|
}
|
|
61
73
|
|
|
62
|
-
|
|
63
|
-
|
|
74
|
+
const content = (
|
|
75
|
+
<>
|
|
64
76
|
<ProductLabel label={label} imageSource={imageSource} modes={modes} />
|
|
65
77
|
{childArray.length > 0 && (
|
|
66
78
|
<View style={{ rowGap: gridGapNum }}>
|
|
@@ -75,6 +87,27 @@ function CardProviderInfo({
|
|
|
75
87
|
))}
|
|
76
88
|
</View>
|
|
77
89
|
)}
|
|
90
|
+
</>
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if (onPress) {
|
|
94
|
+
return (
|
|
95
|
+
<Pressable
|
|
96
|
+
style={[containerStyle, style]}
|
|
97
|
+
onPress={onPress}
|
|
98
|
+
disabled={disabled}
|
|
99
|
+
accessibilityRole="button"
|
|
100
|
+
accessibilityLabel={accessibilityLabel ?? label}
|
|
101
|
+
accessibilityState={{ disabled: !!disabled }}
|
|
102
|
+
>
|
|
103
|
+
{content}
|
|
104
|
+
</Pressable>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<View style={[containerStyle, style]} accessibilityLabel={accessibilityLabel}>
|
|
110
|
+
{content}
|
|
78
111
|
</View>
|
|
79
112
|
)
|
|
80
113
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { View, Text, Image, type ViewStyle, type TextStyle, type ImageStyle, type StyleProp, type ImageSourcePropType } from 'react-native'
|
|
2
|
+
import { View, Text, Image, Pressable, type ViewStyle, type TextStyle, type ImageStyle, type StyleProp, type ImageSourcePropType } from 'react-native'
|
|
3
3
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
4
4
|
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
5
5
|
import { cloneChildrenWithModes, EMPTY_MODES } from '../../utils/react-utils'
|
|
@@ -28,6 +28,15 @@ export type DebitCardProps = {
|
|
|
28
28
|
modes?: Modes;
|
|
29
29
|
/** Container style overrides */
|
|
30
30
|
style?: StyleProp<ViewStyle>;
|
|
31
|
+
/**
|
|
32
|
+
* Press handler for the whole card. When set, the card becomes pressable
|
|
33
|
+
* (rendered through a `Pressable` with `accessibilityRole="button"`).
|
|
34
|
+
*/
|
|
35
|
+
onPress?: () => void;
|
|
36
|
+
/** Disable interaction when `onPress` is set. */
|
|
37
|
+
disabled?: boolean;
|
|
38
|
+
/** Accessibility label forwarded to the pressable wrapper. */
|
|
39
|
+
accessibilityLabel?: string;
|
|
31
40
|
};
|
|
32
41
|
|
|
33
42
|
function DebitCard({
|
|
@@ -41,6 +50,9 @@ function DebitCard({
|
|
|
41
50
|
providerLogoSlot,
|
|
42
51
|
modes: propModes = EMPTY_MODES,
|
|
43
52
|
style,
|
|
53
|
+
onPress,
|
|
54
|
+
disabled,
|
|
55
|
+
accessibilityLabel,
|
|
44
56
|
}: DebitCardProps) {
|
|
45
57
|
const { modes: globalModes } = useTokens()
|
|
46
58
|
const modes = { ...globalModes, ...propModes }
|
|
@@ -149,8 +161,8 @@ function DebitCard({
|
|
|
149
161
|
return cardArtSource
|
|
150
162
|
}, [cardArtSource])
|
|
151
163
|
|
|
152
|
-
|
|
153
|
-
|
|
164
|
+
const content = (
|
|
165
|
+
<>
|
|
154
166
|
<View style={headerStyle}>
|
|
155
167
|
<View style={headerLeftStyle}>
|
|
156
168
|
{bankLogoSlot ? cloneChildrenWithModes(bankLogoSlot, modes) : null}
|
|
@@ -179,6 +191,27 @@ function DebitCard({
|
|
|
179
191
|
<Text style={textStyle}>{expireDate}</Text>
|
|
180
192
|
</View>
|
|
181
193
|
</View>
|
|
194
|
+
</>
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if (onPress) {
|
|
198
|
+
return (
|
|
199
|
+
<Pressable
|
|
200
|
+
style={[containerStyle, style]}
|
|
201
|
+
onPress={onPress}
|
|
202
|
+
disabled={disabled}
|
|
203
|
+
accessibilityRole="button"
|
|
204
|
+
accessibilityLabel={accessibilityLabel ?? `${bankName} ${cardType} ${cardNumber}`}
|
|
205
|
+
accessibilityState={{ disabled: !!disabled }}
|
|
206
|
+
>
|
|
207
|
+
{content}
|
|
208
|
+
</Pressable>
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<View style={[containerStyle, style]} accessibilityLabel={accessibilityLabel}>
|
|
214
|
+
{content}
|
|
182
215
|
</View>
|
|
183
216
|
)
|
|
184
217
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { createContext, useContext } from 'react'
|
|
2
|
-
import { View, Text, StyleSheet, type ViewStyle, type TextStyle, type StyleProp, type ImageSourcePropType } from 'react-native'
|
|
2
|
+
import { View, Text, Pressable, StyleSheet, type ViewStyle, type TextStyle, type StyleProp, type ImageSourcePropType } from 'react-native'
|
|
3
3
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
4
4
|
import Image from '../Image/Image'
|
|
5
5
|
import GlassFill, { type GlassTint } from '../../utils/GlassFill/GlassFill'
|
|
@@ -40,6 +40,15 @@ export interface MediaCardProps {
|
|
|
40
40
|
* Style overrides for the card container.
|
|
41
41
|
*/
|
|
42
42
|
style?: StyleProp<ViewStyle>
|
|
43
|
+
/**
|
|
44
|
+
* Press handler for the whole card. When set, the card becomes pressable
|
|
45
|
+
* (rendered through a `Pressable` with `accessibilityRole="button"`).
|
|
46
|
+
*/
|
|
47
|
+
onPress?: () => void
|
|
48
|
+
/** Disable interaction when `onPress` is set. */
|
|
49
|
+
disabled?: boolean
|
|
50
|
+
/** Accessibility label forwarded to the pressable wrapper. */
|
|
51
|
+
accessibilityLabel?: string
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
/**
|
|
@@ -67,6 +76,9 @@ export function MediaCard({
|
|
|
67
76
|
children,
|
|
68
77
|
modes = EMPTY_MODES,
|
|
69
78
|
style,
|
|
79
|
+
onPress,
|
|
80
|
+
disabled,
|
|
81
|
+
accessibilityLabel,
|
|
70
82
|
}: MediaCardProps) {
|
|
71
83
|
const radius = parseFloat(getVariableByName('cardMedia/radius', modes))
|
|
72
84
|
|
|
@@ -88,16 +100,35 @@ export function MediaCard({
|
|
|
88
100
|
) : null
|
|
89
101
|
)
|
|
90
102
|
|
|
103
|
+
const content = (
|
|
104
|
+
<>
|
|
105
|
+
{background}
|
|
106
|
+
{children != null ? (
|
|
107
|
+
<View style={StyleSheet.absoluteFill} pointerEvents="box-none">
|
|
108
|
+
{children}
|
|
109
|
+
</View>
|
|
110
|
+
) : null}
|
|
111
|
+
</>
|
|
112
|
+
)
|
|
113
|
+
|
|
91
114
|
return (
|
|
92
115
|
<MediaCardContext.Provider value={{ modes }}>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
116
|
+
{onPress ? (
|
|
117
|
+
<Pressable
|
|
118
|
+
style={[containerStyle, style]}
|
|
119
|
+
onPress={onPress}
|
|
120
|
+
disabled={disabled}
|
|
121
|
+
accessibilityRole="button"
|
|
122
|
+
accessibilityLabel={accessibilityLabel}
|
|
123
|
+
accessibilityState={{ disabled: !!disabled }}
|
|
124
|
+
>
|
|
125
|
+
{content}
|
|
126
|
+
</Pressable>
|
|
127
|
+
) : (
|
|
128
|
+
<View style={[containerStyle, style]} accessibilityLabel={accessibilityLabel}>
|
|
129
|
+
{content}
|
|
130
|
+
</View>
|
|
131
|
+
)}
|
|
101
132
|
</MediaCardContext.Provider>
|
|
102
133
|
)
|
|
103
134
|
}
|
|
@@ -4,15 +4,21 @@ import {
|
|
|
4
4
|
Text,
|
|
5
5
|
Pressable,
|
|
6
6
|
StyleSheet,
|
|
7
|
-
Platform,
|
|
8
|
-
Image as RNImage,
|
|
9
7
|
type ViewStyle,
|
|
10
8
|
type TextStyle,
|
|
11
9
|
type StyleProp,
|
|
12
10
|
type ImageSourcePropType,
|
|
13
11
|
} from 'react-native'
|
|
14
|
-
import
|
|
15
|
-
|
|
12
|
+
import Svg, {
|
|
13
|
+
Defs,
|
|
14
|
+
LinearGradient,
|
|
15
|
+
Stop,
|
|
16
|
+
Rect,
|
|
17
|
+
Mask,
|
|
18
|
+
Filter,
|
|
19
|
+
FeGaussianBlur,
|
|
20
|
+
Image as SvgImage,
|
|
21
|
+
} from 'react-native-svg'
|
|
16
22
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
17
23
|
import { EMPTY_MODES, resolveTruncation } from '../../utils/react-utils'
|
|
18
24
|
import Avatar from '../Avatar/Avatar'
|
|
@@ -138,7 +144,9 @@ function ProductMerchandisingCard({
|
|
|
138
144
|
const rawId = useId()
|
|
139
145
|
const safeId = rawId.replace(/[^a-zA-Z0-9_-]/g, '')
|
|
140
146
|
const scrimId = `pmc-footer-scrim-${safeId}`
|
|
147
|
+
const blurGradientId = `pmc-footer-blurgrad-${safeId}`
|
|
141
148
|
const blurMaskId = `pmc-footer-blurmask-${safeId}`
|
|
149
|
+
const blurFilterId = `pmc-footer-blurfilter-${safeId}`
|
|
142
150
|
|
|
143
151
|
const normalizedImageSource = useMemo(() => normalizeImageSource(imageSource), [imageSource])
|
|
144
152
|
|
|
@@ -234,41 +242,65 @@ function ProductMerchandisingCard({
|
|
|
234
242
|
|
|
235
243
|
<View style={tokens.footer} pointerEvents="box-none">
|
|
236
244
|
{/* Progressive blur — clear at the top of the footer, ramping to
|
|
237
|
-
a soft blur at the bottom.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
a soft blur at the bottom. We reveal a *blurred copy of the
|
|
246
|
+
card's own image* through a vertical gradient mask, rendered
|
|
247
|
+
entirely with `react-native-svg` (a Fabric/codegen-correct
|
|
248
|
+
native module that resolves on both the Old and New
|
|
249
|
+
architectures). The SVG `<Image>` is blurred by an
|
|
250
|
+
`<FeGaussianBlur>` filter (identical, resolution-independent
|
|
251
|
+
Gaussian on iOS, Android AND web) and revealed by a luminance
|
|
252
|
+
`<Mask>` fed a vertical gradient, so the blur swells in
|
|
253
|
+
smoothly toward the bottom. This deliberately avoids the
|
|
254
|
+
unmaintained `@react-native-masked-view/masked-view` (a legacy
|
|
255
|
+
Paper component that resolves to a plain object — and crashes
|
|
256
|
+
"Element type is invalid" — under the New Architecture interop)
|
|
257
|
+
and the flaky native `BlurView`. Because the mask is fully
|
|
258
|
+
transparent at the top, any minor crop mismatch between this
|
|
259
|
+
cover-fitted copy and the full-bleed background is invisible:
|
|
260
|
+
where they could differ, the blurred layer isn't drawn. */}
|
|
247
261
|
{normalizedImageSource != null ? (
|
|
248
262
|
<View style={StyleSheet.absoluteFill} pointerEvents="none">
|
|
249
|
-
<
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
</
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
263
|
+
<Svg width="100%" height="100%">
|
|
264
|
+
<Defs>
|
|
265
|
+
<Filter
|
|
266
|
+
id={blurFilterId}
|
|
267
|
+
x="-15%"
|
|
268
|
+
y="-15%"
|
|
269
|
+
width="130%"
|
|
270
|
+
height="130%"
|
|
271
|
+
>
|
|
272
|
+
<FeGaussianBlur
|
|
273
|
+
stdDeviation={tokens.imageBlurStdDeviation}
|
|
274
|
+
edgeMode="duplicate"
|
|
275
|
+
/>
|
|
276
|
+
</Filter>
|
|
277
|
+
<LinearGradient id={blurGradientId} x1="0" y1="0" x2="0" y2="1">
|
|
278
|
+
<Stop offset="0" stopColor="#ffffff" stopOpacity={0} />
|
|
279
|
+
<Stop offset="0.35" stopColor="#ffffff" stopOpacity={0.5} />
|
|
280
|
+
<Stop offset="0.7" stopColor="#ffffff" stopOpacity={0.92} />
|
|
281
|
+
<Stop offset="1" stopColor="#ffffff" stopOpacity={1} />
|
|
282
|
+
</LinearGradient>
|
|
283
|
+
<Mask id={blurMaskId}>
|
|
284
|
+
<Rect
|
|
285
|
+
x="0"
|
|
286
|
+
y="0"
|
|
287
|
+
width="100%"
|
|
288
|
+
height="100%"
|
|
289
|
+
fill={`url(#${blurGradientId})`}
|
|
290
|
+
/>
|
|
291
|
+
</Mask>
|
|
292
|
+
</Defs>
|
|
293
|
+
<SvgImage
|
|
294
|
+
href={normalizedImageSource as any}
|
|
295
|
+
x="0"
|
|
296
|
+
y="0"
|
|
297
|
+
width="100%"
|
|
298
|
+
height="100%"
|
|
299
|
+
preserveAspectRatio="xMidYMid slice"
|
|
300
|
+
filter={`url(#${blurFilterId})`}
|
|
301
|
+
mask={`url(#${blurMaskId})`}
|
|
270
302
|
/>
|
|
271
|
-
</
|
|
303
|
+
</Svg>
|
|
272
304
|
</View>
|
|
273
305
|
) : (
|
|
274
306
|
<GlassFill tint="dark" intensity={tokens.blurIntensity} progressive />
|
|
@@ -348,7 +380,7 @@ const SPECIAL_BADGE_INTENSITY = 17 // ~5px CSS blur / ~2 native blurAmount
|
|
|
348
380
|
interface ResolvedTokens {
|
|
349
381
|
radius: number
|
|
350
382
|
blurIntensity: number
|
|
351
|
-
|
|
383
|
+
imageBlurStdDeviation: number
|
|
352
384
|
header: ViewStyle
|
|
353
385
|
footer: ViewStyle
|
|
354
386
|
textWrap: ViewStyle
|
|
@@ -394,20 +426,17 @@ function resolveTokens(modes: Modes): ResolvedTokens {
|
|
|
394
426
|
const sbFamily = asStr(getVariableByName('productMerchandisingcard/specialbadge/text/fontfamily', modes), 'JioType Var')
|
|
395
427
|
const sbWeight = asStr(getVariableByName('productMerchandisingcard/specialbadge/text/fontweight', modes), '500')
|
|
396
428
|
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
//
|
|
400
|
-
// "
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
android: Math.max(12, Math.round(blurRadius * 0.95)),
|
|
404
|
-
default: Math.max(8, Math.round(blurRadius * 0.6)),
|
|
405
|
-
}) as number
|
|
429
|
+
// SVG `<FeGaussianBlur stdDeviation>` is resolution-independent and renders
|
|
430
|
+
// an identical Gaussian on iOS, Android and web — so unlike the old
|
|
431
|
+
// `Image.blurRadius` path it needs no per-platform tuning. `stdDeviation` is
|
|
432
|
+
// roughly half a perceptual "blur radius", so we scale the `blur/minimal`
|
|
433
|
+
// token down to land on the same subtle frosted strength as the design.
|
|
434
|
+
const imageBlurStdDeviation = Math.max(4, Math.round(blurRadius * 0.35))
|
|
406
435
|
|
|
407
436
|
return {
|
|
408
437
|
radius,
|
|
409
438
|
blurIntensity: Math.max(0, Math.min(100, Math.round(blurRadius * BLUR_INTENSITY_FACTOR))),
|
|
410
|
-
|
|
439
|
+
imageBlurStdDeviation,
|
|
411
440
|
header: {
|
|
412
441
|
flexDirection: 'row',
|
|
413
442
|
alignItems: 'flex-start',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { View, Text, type ViewStyle } from 'react-native';
|
|
2
|
+
import { View, Text, Pressable, type ViewStyle } from 'react-native';
|
|
3
3
|
import ButtonGroup from '../ButtonGroup/ButtonGroup';
|
|
4
4
|
import AvatarGroup from '../AvatarGroup/AvatarGroup';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
@@ -65,6 +65,15 @@ export type RechargeCardProps = {
|
|
|
65
65
|
*/
|
|
66
66
|
modes?: Modes;
|
|
67
67
|
style?: ViewStyle;
|
|
68
|
+
/**
|
|
69
|
+
* Press handler for the whole card. When set, the card becomes pressable
|
|
70
|
+
* (rendered through a `Pressable` with `accessibilityRole="button"`).
|
|
71
|
+
*/
|
|
72
|
+
onPress?: () => void;
|
|
73
|
+
/** Disable interaction when `onPress` is set. */
|
|
74
|
+
disabled?: boolean;
|
|
75
|
+
/** Accessibility label forwarded to the pressable wrapper (defaults to `title`). */
|
|
76
|
+
accessibilityLabel?: string;
|
|
68
77
|
};
|
|
69
78
|
|
|
70
79
|
/**
|
|
@@ -81,6 +90,9 @@ export default function RechargeCard({
|
|
|
81
90
|
actions,
|
|
82
91
|
modes = EMPTY_MODES,
|
|
83
92
|
style,
|
|
93
|
+
onPress,
|
|
94
|
+
disabled,
|
|
95
|
+
accessibilityLabel,
|
|
84
96
|
}: RechargeCardProps) {
|
|
85
97
|
// Container Tokens (defaults mirror Figma node 2235:937).
|
|
86
98
|
const backgroundColor = getVariableByName('rechargeCard/background', modes);
|
|
@@ -132,18 +144,20 @@ export default function RechargeCard({
|
|
|
132
144
|
const hasSubscriptions = React.Children.count(subscriptionContent) > 0;
|
|
133
145
|
|
|
134
146
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
+
const containerStyle: ViewStyle = {
|
|
148
|
+
backgroundColor,
|
|
149
|
+
paddingHorizontal,
|
|
150
|
+
paddingVertical,
|
|
151
|
+
gap,
|
|
152
|
+
borderRadius: radius,
|
|
153
|
+
borderWidth: strokeWidth,
|
|
154
|
+
borderColor: strokeColor,
|
|
155
|
+
minWidth,
|
|
156
|
+
alignItems: 'flex-start',
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const content = (
|
|
160
|
+
<>
|
|
147
161
|
{/* Header */}
|
|
148
162
|
<View style={{ gap: headerGap, alignItems: 'flex-start' }}>
|
|
149
163
|
<Text style={{
|
|
@@ -244,6 +258,27 @@ export default function RechargeCard({
|
|
|
244
258
|
<ButtonGroup modes={{ ...DEFAULT_BUTTON_GROUP_MODES, ...modes }}>
|
|
245
259
|
{actions}
|
|
246
260
|
</ButtonGroup>
|
|
261
|
+
</>
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
if (onPress) {
|
|
265
|
+
return (
|
|
266
|
+
<Pressable
|
|
267
|
+
style={[containerStyle, style]}
|
|
268
|
+
onPress={onPress}
|
|
269
|
+
disabled={disabled}
|
|
270
|
+
accessibilityRole="button"
|
|
271
|
+
accessibilityLabel={accessibilityLabel ?? title}
|
|
272
|
+
accessibilityState={{ disabled: !!disabled }}
|
|
273
|
+
>
|
|
274
|
+
{content}
|
|
275
|
+
</Pressable>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<View style={[containerStyle, style]} accessibilityLabel={accessibilityLabel}>
|
|
281
|
+
{content}
|
|
247
282
|
</View>
|
|
248
283
|
);
|
|
249
284
|
}
|