jfs-components 0.1.17 → 0.1.18
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 +9 -0
- package/lib/commonjs/components/AvatarGroup/AvatarGroup.js +41 -20
- package/lib/commonjs/components/ProductMerchandisingCard/ProductMerchandisingCard.js +64 -60
- 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/ProductMerchandisingCard/ProductMerchandisingCard.js +66 -62
- package/lib/module/icons/registry.js +1 -1
- package/lib/module/utils/GlassFill/GlassFill.js +69 -72
- 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/ProductMerchandisingCard/ProductMerchandisingCard.tsx +76 -47
- package/src/icons/registry.ts +1 -1
- package/src/utils/GlassFill/GlassFill.tsx +59 -56
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import React, { useId } from 'react';
|
|
4
4
|
import { View, StyleSheet, Platform, UIManager } from 'react-native';
|
|
5
5
|
import { BlurView } from '@react-native-community/blur';
|
|
6
|
-
import MaskedView from '@react-native-masked-view/masked-view';
|
|
7
6
|
import Svg, { Defs, LinearGradient, Stop, Rect } from 'react-native-svg';
|
|
8
7
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
9
8
|
const DEFAULT_FALLBACK_DARK = '#1414174a';
|
|
@@ -14,32 +13,30 @@ const DEFAULT_FALLBACK_LIGHT = '#ffffff66';
|
|
|
14
13
|
const NATIVE_BLUR_NAME = Platform.OS === 'android' ? 'AndroidBlurView' : 'BlurView';
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
|
-
* Alpha stop on
|
|
18
|
-
* position (0 = top of the surface, 1 = bottom); `opacity` is the
|
|
19
|
-
* there (0 =
|
|
16
|
+
* Alpha stop on the no-native-blur fallback gradient. `offset` is the vertical
|
|
17
|
+
* position (0 = top of the surface, 1 = bottom); `opacity` is the tint alpha
|
|
18
|
+
* there (0 = fully clear, 1 = full fallback color).
|
|
20
19
|
*
|
|
21
|
-
* Using several stops (rather than a single linear 0 → 1 ramp)
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* "glass" feel rather than a flat translucent panel.
|
|
20
|
+
* Using several stops (rather than a single linear 0 → 1 ramp) eases the tint
|
|
21
|
+
* in, so it swells smoothly toward the bottom instead of along a hard seam —
|
|
22
|
+
* the soft S-curve that gives the surface its "glass" feel.
|
|
25
23
|
*/
|
|
26
24
|
|
|
27
25
|
/**
|
|
28
|
-
* A single
|
|
29
|
-
* - `
|
|
30
|
-
*
|
|
26
|
+
* A single bottom-anchored band of the progressive blur stack.
|
|
27
|
+
* - `start` is where the band begins, as a fraction from the top (0 = covers
|
|
28
|
+
* the whole surface, 0.8 = only the bottom 20%). Each band runs from `start`
|
|
29
|
+
* to the bottom.
|
|
30
|
+
* - `amount` is the band's own blur strength as a share (0–1) of the peak.
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* heavy block), while still giving a genuine variable-radius result — the blur
|
|
37
|
-
* radius grows toward the bottom where the two layers overlap.
|
|
32
|
+
* Because each `BlurView` re-blurs everything rendered behind it, the bands
|
|
33
|
+
* COMPOUND where they overlap (toward the bottom), so the effective radius grows
|
|
34
|
+
* downward without any masking. The top region is covered only by the first,
|
|
35
|
+
* faint band; the bottom is covered by all of them.
|
|
38
36
|
*/
|
|
39
37
|
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
// snapping clear. Also reused for the no-native-blur fallback scrim.
|
|
38
|
+
// Eased tint ramp for the no-native-blur fallback scrim. A faint trace begins
|
|
39
|
+
// near the very top and grows steadily downward.
|
|
43
40
|
const BASE_MASK_STOPS = [{
|
|
44
41
|
offset: 0.0,
|
|
45
42
|
opacity: 0
|
|
@@ -56,26 +53,26 @@ const BASE_MASK_STOPS = [{
|
|
|
56
53
|
offset: 1.0,
|
|
57
54
|
opacity: 1
|
|
58
55
|
}];
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
//
|
|
64
|
-
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
56
|
+
|
|
57
|
+
// Bottom-anchored blur bands (top → bottom). The first band is faint and covers
|
|
58
|
+
// the whole surface so the top reads as barely-there glass; each subsequent band
|
|
59
|
+
// starts lower and adds strength, compounding toward the bottom for a smooth
|
|
60
|
+
// variable-radius result. Quadrature of these shares lands the bottom at ~peak.
|
|
61
|
+
const PROGRESSIVE_BANDS = [{
|
|
62
|
+
start: 0.0,
|
|
63
|
+
amount: 0.18
|
|
64
|
+
}, {
|
|
65
|
+
start: 0.3,
|
|
66
|
+
amount: 0.28
|
|
67
|
+
}, {
|
|
68
|
+
start: 0.52,
|
|
69
|
+
amount: 0.38
|
|
70
|
+
}, {
|
|
71
|
+
start: 0.7,
|
|
72
|
+
amount: 0.5
|
|
73
|
+
}, {
|
|
74
|
+
start: 0.84,
|
|
75
|
+
amount: 0.6
|
|
79
76
|
}];
|
|
80
77
|
|
|
81
78
|
/**
|
|
@@ -108,13 +105,14 @@ const NATIVE_BLUR_SUPPORTED = (() => {
|
|
|
108
105
|
})();
|
|
109
106
|
|
|
110
107
|
/**
|
|
111
|
-
* Vertical
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
108
|
+
* Vertical color-gradient scrim drawn with `react-native-svg` — used as the
|
|
109
|
+
* no-native-blur fallback. Paints `color` at the eased per-stop alpha so the
|
|
110
|
+
* tint swells smoothly from clear (top) to full (bottom), exactly mirroring the
|
|
111
|
+
* old `MaskedView`-over-solid-color fallback but with no native-view masking.
|
|
115
112
|
*/
|
|
116
|
-
function
|
|
113
|
+
function GradientScrim({
|
|
117
114
|
id,
|
|
115
|
+
color,
|
|
118
116
|
stops
|
|
119
117
|
}) {
|
|
120
118
|
return /*#__PURE__*/_jsxs(Svg, {
|
|
@@ -129,7 +127,7 @@ function GradientMask({
|
|
|
129
127
|
y2: "1",
|
|
130
128
|
children: stops.map((s, i) => /*#__PURE__*/_jsx(Stop, {
|
|
131
129
|
offset: s.offset,
|
|
132
|
-
stopColor:
|
|
130
|
+
stopColor: color,
|
|
133
131
|
stopOpacity: s.opacity
|
|
134
132
|
}, i))
|
|
135
133
|
})
|
|
@@ -194,38 +192,37 @@ function GlassFill({
|
|
|
194
192
|
return /*#__PURE__*/_jsx(View, {
|
|
195
193
|
style: [StyleSheet.absoluteFill, style],
|
|
196
194
|
pointerEvents: "none",
|
|
197
|
-
children: /*#__PURE__*/_jsx(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
stops: BASE_MASK_STOPS
|
|
202
|
-
}),
|
|
203
|
-
children: /*#__PURE__*/_jsx(View, {
|
|
204
|
-
style: [StyleSheet.absoluteFill, {
|
|
205
|
-
backgroundColor: fallbackColor
|
|
206
|
-
}]
|
|
207
|
-
})
|
|
195
|
+
children: /*#__PURE__*/_jsx(GradientScrim, {
|
|
196
|
+
id: `${maskId}-fb`,
|
|
197
|
+
color: fallbackColor,
|
|
198
|
+
stops: BASE_MASK_STOPS
|
|
208
199
|
})
|
|
209
200
|
});
|
|
210
201
|
}
|
|
211
202
|
return /*#__PURE__*/_jsx(View, {
|
|
212
203
|
style: [StyleSheet.absoluteFill, style],
|
|
213
204
|
pointerEvents: "none",
|
|
214
|
-
children:
|
|
215
|
-
const amount = Math.max(1, Math.round(peakBlur *
|
|
216
|
-
return
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
205
|
+
children: PROGRESSIVE_BANDS.map((band, i) => {
|
|
206
|
+
const amount = Math.max(1, Math.round(peakBlur * band.amount));
|
|
207
|
+
return (
|
|
208
|
+
/*#__PURE__*/
|
|
209
|
+
// Each band is clipped to start at `band.start` and run to
|
|
210
|
+
// the bottom; its `BlurView` blurs the content (and any
|
|
211
|
+
// lower bands) behind it, so the radius compounds downward.
|
|
212
|
+
_jsx(View, {
|
|
213
|
+
style: [StyleSheet.absoluteFill, {
|
|
214
|
+
top: `${band.start * 100}%`,
|
|
215
|
+
overflow: 'hidden'
|
|
216
|
+
}],
|
|
217
|
+
pointerEvents: "none",
|
|
218
|
+
children: /*#__PURE__*/_jsx(BlurView, {
|
|
219
|
+
style: StyleSheet.absoluteFill,
|
|
220
|
+
blurType: blurType,
|
|
221
|
+
blurAmount: amount,
|
|
222
|
+
reducedTransparencyFallbackColor: fallbackColor
|
|
223
|
+
})
|
|
224
|
+
}, i)
|
|
225
|
+
);
|
|
229
226
|
})
|
|
230
227
|
});
|
|
231
228
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Auto-generated from SVG files in src/icons/
|
|
5
5
|
* DO NOT EDIT MANUALLY - Run "npm run icons:generate" to regenerate
|
|
6
6
|
*
|
|
7
|
-
* Generated: 2026-06-
|
|
7
|
+
* Generated: 2026-06-29T08:47:00.741Z
|
|
8
8
|
*/
|
|
9
9
|
export declare const iconRegistry: Record<string, {
|
|
10
10
|
path: string;
|
|
@@ -25,10 +25,17 @@ export interface GlassFillProps {
|
|
|
25
25
|
/**
|
|
26
26
|
* Render a *progressive* (variable) blur instead of a uniform one: fully
|
|
27
27
|
* clear at the top, easing into a soft blur toward the bottom. Implemented
|
|
28
|
-
* by stacking
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* mask-free by stacking several bottom-anchored `BlurView` "bands": each
|
|
29
|
+
* band is clipped to start a little lower than the last, and because a
|
|
30
|
+
* `BlurView` blurs whatever is rendered behind it, the bands COMPOUND toward
|
|
31
|
+
* the bottom (a lower band re-blurs the already-blurred output above it) so
|
|
32
|
+
* the effective radius swells smoothly downward. This deliberately avoids
|
|
33
|
+
* masking a native view — the old implementation wrapped each `BlurView` in
|
|
34
|
+
* `@react-native-masked-view/masked-view`, a legacy Paper component that
|
|
35
|
+
* resolves to a plain object (crashing "Element type is invalid") under the
|
|
36
|
+
* New Architecture interop. Works on iOS and Android with no extra native
|
|
37
|
+
* module. When the native blur isn't linked at all, falls back to a pure
|
|
38
|
+
* `react-native-svg` gradient scrim (see below).
|
|
32
39
|
*/
|
|
33
40
|
progressive?: boolean;
|
|
34
41
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jfs-components",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "React Native Jio Finance Components Library",
|
|
5
5
|
"author": "sunshuaiqi@gmail.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -83,7 +83,6 @@
|
|
|
83
83
|
"@expo/metro-runtime": "^6.1.2",
|
|
84
84
|
"@gorhom/bottom-sheet": "5.2.6",
|
|
85
85
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
86
|
-
"@react-native-masked-view/masked-view": "^0.3.2",
|
|
87
86
|
"@shopify/flash-list": "^2.2.0",
|
|
88
87
|
"ajv": "^8.17.1",
|
|
89
88
|
"ajv-keywords": "^5.1.0",
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useId } from 'react';
|
|
2
2
|
import { View, Platform, type ViewStyle } from 'react-native';
|
|
3
|
-
import
|
|
4
|
-
import Svg, { Path } from 'react-native-svg';
|
|
3
|
+
import Svg, { Defs, Mask, Path, ForeignObject } from 'react-native-svg';
|
|
5
4
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
5
|
import { EMPTY_MODES, flattenChildren } from '../../utils/react-utils';
|
|
7
6
|
import type { Modes } from '../../design-tokens'
|
|
@@ -13,6 +12,11 @@ type AvatarGroupProps = {
|
|
|
13
12
|
} & React.ComponentProps<typeof View>;
|
|
14
13
|
|
|
15
14
|
function AvatarGroup({ modes = EMPTY_MODES, children, style, ...rest }: AvatarGroupProps) {
|
|
15
|
+
// Stable, collision-free base for the per-avatar SVG mask ids so multiple
|
|
16
|
+
// groups on one screen (and multiple avatars in a group) never clash.
|
|
17
|
+
const rawId = useId();
|
|
18
|
+
const maskBaseId = `avatargroup-mask-${rawId.replace(/[^a-zA-Z0-9_-]/g, '')}`;
|
|
19
|
+
|
|
16
20
|
const gap = getVariableByName('avatarGroup/gap', modes);
|
|
17
21
|
|
|
18
22
|
|
|
@@ -43,7 +47,11 @@ function AvatarGroup({ modes = EMPTY_MODES, children, style, ...rest }: AvatarGr
|
|
|
43
47
|
const holeRadius = effectiveRadius + borderSizeInt;
|
|
44
48
|
const holeCenter = { x: nextAvatarX + r, y: r };
|
|
45
49
|
|
|
46
|
-
// Native
|
|
50
|
+
// Native mask path: the full avatar square MINUS a circular hole where the
|
|
51
|
+
// next (overlapping) avatar sits. Drawn as a single `evenodd` path so the
|
|
52
|
+
// hole is cut out of the square. Filled WHITE because react-native-svg's
|
|
53
|
+
// `<Mask>` is a *luminance* mask (white = visible, black/absent = hidden) —
|
|
54
|
+
// the inverse of the old `@react-native-masked-view` alpha convention.
|
|
47
55
|
const rectPath = `M 0 0 h ${avatarSizeInt} v ${avatarSizeInt} h -${avatarSizeInt} z`;
|
|
48
56
|
const circlePath = `M ${holeCenter.x},${holeCenter.y} m -${holeRadius},0 a ${holeRadius},${holeRadius} 0 1,0 ${holeRadius * 2},0 a ${holeRadius},${holeRadius} 0 1,0 -${holeRadius * 2},0`;
|
|
49
57
|
const maskPath = `${rectPath} ${circlePath}`;
|
|
@@ -101,23 +109,41 @@ function AvatarGroup({ modes = EMPTY_MODES, children, style, ...rest }: AvatarGr
|
|
|
101
109
|
);
|
|
102
110
|
}
|
|
103
111
|
|
|
104
|
-
// Native
|
|
112
|
+
// Native: mask the avatar with react-native-svg instead of the
|
|
113
|
+
// unmaintained `@react-native-masked-view/masked-view`, whose
|
|
114
|
+
// legacy `requireNativeComponent('RNCMaskedView')` resolves to a
|
|
115
|
+
// plain object (crashing "Element type is invalid") under the New
|
|
116
|
+
// Architecture interop. react-native-svg ships a Fabric/codegen
|
|
117
|
+
// `<Mask>` + `<ForeignObject>` that resolves on both architectures;
|
|
118
|
+
// the avatar is hosted inside the `<ForeignObject>` and revealed
|
|
119
|
+
// through the luminance mask.
|
|
120
|
+
const maskId = `${maskBaseId}-${index}`;
|
|
105
121
|
return (
|
|
106
122
|
<View key={index} style={wrapperStyle}>
|
|
107
|
-
<
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
<Path
|
|
112
|
-
d={maskPath}
|
|
113
|
-
fill="black"
|
|
114
|
-
fillRule="evenodd"
|
|
115
|
-
/>
|
|
116
|
-
</Svg>
|
|
117
|
-
}
|
|
123
|
+
<Svg
|
|
124
|
+
height="100%"
|
|
125
|
+
width="100%"
|
|
126
|
+
viewBox={`0 0 ${avatarSizeInt} ${avatarSizeInt}`}
|
|
118
127
|
>
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
<Defs>
|
|
129
|
+
<Mask
|
|
130
|
+
id={maskId}
|
|
131
|
+
maskUnits="userSpaceOnUse"
|
|
132
|
+
maskContentUnits="userSpaceOnUse"
|
|
133
|
+
>
|
|
134
|
+
<Path d={maskPath} fill="white" fillRule="evenodd" />
|
|
135
|
+
</Mask>
|
|
136
|
+
</Defs>
|
|
137
|
+
<ForeignObject
|
|
138
|
+
x="0"
|
|
139
|
+
y="0"
|
|
140
|
+
width={avatarSizeInt}
|
|
141
|
+
height={avatarSizeInt}
|
|
142
|
+
mask={`url(#${maskId})`}
|
|
143
|
+
>
|
|
144
|
+
{clonedChild}
|
|
145
|
+
</ForeignObject>
|
|
146
|
+
</Svg>
|
|
121
147
|
</View>
|
|
122
148
|
);
|
|
123
149
|
})}
|
|
@@ -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',
|
package/src/icons/registry.ts
CHANGED