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.
@@ -7,10 +7,8 @@ exports.default = void 0;
7
7
  var _react = _interopRequireWildcard(require("react"));
8
8
  var _reactNative = require("react-native");
9
9
  var _blur = require("@react-native-community/blur");
10
- var _maskedView = _interopRequireDefault(require("@react-native-masked-view/masked-view"));
11
10
  var _reactNativeSvg = _interopRequireWildcard(require("react-native-svg"));
12
11
  var _jsxRuntime = require("react/jsx-runtime");
13
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
12
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
15
13
  const DEFAULT_FALLBACK_DARK = '#1414174a';
16
14
  const DEFAULT_FALLBACK_LIGHT = '#ffffff66';
@@ -20,32 +18,30 @@ const DEFAULT_FALLBACK_LIGHT = '#ffffff66';
20
18
  const NATIVE_BLUR_NAME = _reactNative.Platform.OS === 'android' ? 'AndroidBlurView' : 'BlurView';
21
19
 
22
20
  /**
23
- * Alpha stop on a layer's vertical reveal mask. `offset` is the vertical
24
- * position (0 = top of the surface, 1 = bottom); `opacity` is the mask alpha
25
- * there (0 = layer hidden / fully clear, 1 = layer fully applied).
21
+ * Alpha stop on the no-native-blur fallback gradient. `offset` is the vertical
22
+ * position (0 = top of the surface, 1 = bottom); `opacity` is the tint alpha
23
+ * there (0 = fully clear, 1 = full fallback color).
26
24
  *
27
- * Using several stops (rather than a single linear 0 → 1 ramp) lets each layer
28
- * EASE in, so the blur swells smoothly toward the bottom instead of appearing
29
- * along a hard horizontal seam. That soft S-curve is what gives the surface its
30
- * "glass" feel rather than a flat translucent panel.
25
+ * Using several stops (rather than a single linear 0 → 1 ramp) eases the tint
26
+ * in, so it swells smoothly toward the bottom instead of along a hard seam —
27
+ * the soft S-curve that gives the surface its "glass" feel.
31
28
  */
32
29
 
33
30
  /**
34
- * A single layer of the progressive ramp.
35
- * - `stops` describe how this layer is revealed from top to bottom.
36
- * - `amount` is this layer's share (0–1) of the max `blurAmount`.
31
+ * A single bottom-anchored band of the progressive blur stack.
32
+ * - `start` is where the band begins, as a fraction from the top (0 = covers
33
+ * the whole surface, 0.8 = only the bottom 20%). Each band runs from `start`
34
+ * to the bottom.
35
+ * - `amount` is the band's own blur strength as a share (0–1) of the peak.
37
36
  *
38
- * We stack just TWO layers on both platforms: a faint base blur that covers
39
- * most of the footer and a slightly stronger accent concentrated near the
40
- * bottom. Keeping the overlap shallow avoids compounding the dark tint of
41
- * multiple `BlurView`s (which is what made the earlier 3-layer ramp read as a
42
- * heavy block), while still giving a genuine variable-radius result — the blur
43
- * radius grows toward the bottom where the two layers overlap.
37
+ * Because each `BlurView` re-blurs everything rendered behind it, the bands
38
+ * COMPOUND where they overlap (toward the bottom), so the effective radius grows
39
+ * downward without any masking. The top region is covered only by the first,
40
+ * faint band; the bottom is covered by all of them.
44
41
  */
45
42
 
46
- // Base reveal: a faint trace of blur begins near the very top and grows
47
- // steadily downward, so the upper half still carries visible glass rather than
48
- // snapping clear. Also reused for the no-native-blur fallback scrim.
43
+ // Eased tint ramp for the no-native-blur fallback scrim. A faint trace begins
44
+ // near the very top and grows steadily downward.
49
45
  const BASE_MASK_STOPS = [{
50
46
  offset: 0.0,
51
47
  opacity: 0
@@ -62,26 +58,26 @@ const BASE_MASK_STOPS = [{
62
58
  offset: 1.0,
63
59
  opacity: 1
64
60
  }];
65
- const PROGRESSIVE_LAYERS = [{
66
- amount: 0.65,
67
- stops: BASE_MASK_STOPS
68
- },
69
- // Accent: the strongest blur, gathering over the lower portion for depth.
70
- {
71
- amount: 1.0,
72
- stops: [{
73
- offset: 0.0,
74
- opacity: 0
75
- }, {
76
- offset: 0.3,
77
- opacity: 0.15
78
- }, {
79
- offset: 0.65,
80
- opacity: 0.65
81
- }, {
82
- offset: 1.0,
83
- opacity: 1
84
- }]
61
+
62
+ // Bottom-anchored blur bands (top → bottom). The first band is faint and covers
63
+ // the whole surface so the top reads as barely-there glass; each subsequent band
64
+ // starts lower and adds strength, compounding toward the bottom for a smooth
65
+ // variable-radius result. Quadrature of these shares lands the bottom at ~peak.
66
+ const PROGRESSIVE_BANDS = [{
67
+ start: 0.0,
68
+ amount: 0.18
69
+ }, {
70
+ start: 0.3,
71
+ amount: 0.28
72
+ }, {
73
+ start: 0.52,
74
+ amount: 0.38
75
+ }, {
76
+ start: 0.7,
77
+ amount: 0.5
78
+ }, {
79
+ start: 0.84,
80
+ amount: 0.6
85
81
  }];
86
82
 
87
83
  /**
@@ -114,13 +110,14 @@ const NATIVE_BLUR_SUPPORTED = (() => {
114
110
  })();
115
111
 
116
112
  /**
117
- * Vertical alpha-gradient mask drawn with `react-native-svg`. `MaskedView`
118
- * reveals its child in proportion to this mask's alpha, so feeding it an eased
119
- * multi-stop gradient makes the layer's blur swell in smoothly from top to
120
- * bottom instead of along a hard seam.
113
+ * Vertical color-gradient scrim drawn with `react-native-svg` — used as the
114
+ * no-native-blur fallback. Paints `color` at the eased per-stop alpha so the
115
+ * tint swells smoothly from clear (top) to full (bottom), exactly mirroring the
116
+ * old `MaskedView`-over-solid-color fallback but with no native-view masking.
121
117
  */
122
- function GradientMask({
118
+ function GradientScrim({
123
119
  id,
120
+ color,
124
121
  stops
125
122
  }) {
126
123
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeSvg.default, {
@@ -135,7 +132,7 @@ function GradientMask({
135
132
  y2: "1",
136
133
  children: stops.map((s, i) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Stop, {
137
134
  offset: s.offset,
138
- stopColor: "#000000",
135
+ stopColor: color,
139
136
  stopOpacity: s.opacity
140
137
  }, i))
141
138
  })
@@ -200,38 +197,37 @@ function GlassFill({
200
197
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
201
198
  style: [_reactNative.StyleSheet.absoluteFill, style],
202
199
  pointerEvents: "none",
203
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_maskedView.default, {
204
- style: _reactNative.StyleSheet.absoluteFill,
205
- maskElement: /*#__PURE__*/(0, _jsxRuntime.jsx)(GradientMask, {
206
- id: `${maskId}-fb`,
207
- stops: BASE_MASK_STOPS
208
- }),
209
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
210
- style: [_reactNative.StyleSheet.absoluteFill, {
211
- backgroundColor: fallbackColor
212
- }]
213
- })
200
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(GradientScrim, {
201
+ id: `${maskId}-fb`,
202
+ color: fallbackColor,
203
+ stops: BASE_MASK_STOPS
214
204
  })
215
205
  });
216
206
  }
217
207
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
218
208
  style: [_reactNative.StyleSheet.absoluteFill, style],
219
209
  pointerEvents: "none",
220
- children: PROGRESSIVE_LAYERS.map((layer, i) => {
221
- const amount = Math.max(1, Math.round(peakBlur * layer.amount));
222
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_maskedView.default, {
223
- style: _reactNative.StyleSheet.absoluteFill,
224
- maskElement: /*#__PURE__*/(0, _jsxRuntime.jsx)(GradientMask, {
225
- id: `${maskId}-${i}`,
226
- stops: layer.stops
227
- }),
228
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_blur.BlurView, {
229
- style: _reactNative.StyleSheet.absoluteFill,
230
- blurType: blurType,
231
- blurAmount: amount,
232
- reducedTransparencyFallbackColor: fallbackColor
233
- })
234
- }, i);
210
+ children: PROGRESSIVE_BANDS.map((band, i) => {
211
+ const amount = Math.max(1, Math.round(peakBlur * band.amount));
212
+ return (
213
+ /*#__PURE__*/
214
+ // Each band is clipped to start at `band.start` and run to
215
+ // the bottom; its `BlurView` blurs the content (and any
216
+ // lower bands) behind it, so the radius compounds downward.
217
+ (0, _jsxRuntime.jsx)(_reactNative.View, {
218
+ style: [_reactNative.StyleSheet.absoluteFill, {
219
+ top: `${band.start * 100}%`,
220
+ overflow: 'hidden'
221
+ }],
222
+ pointerEvents: "none",
223
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_blur.BlurView, {
224
+ style: _reactNative.StyleSheet.absoluteFill,
225
+ blurType: blurType,
226
+ blurAmount: amount,
227
+ reducedTransparencyFallbackColor: fallbackColor
228
+ })
229
+ }, i)
230
+ );
235
231
  })
236
232
  });
237
233
  }
@@ -1,18 +1,21 @@
1
1
  "use strict";
2
2
 
3
- import React from 'react';
3
+ import React, { useId } from 'react';
4
4
  import { View, Platform } from 'react-native';
5
- import MaskedView from '@react-native-masked-view/masked-view';
6
- import Svg, { Path } from 'react-native-svg';
5
+ import Svg, { Defs, Mask, Path, ForeignObject } from 'react-native-svg';
7
6
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
8
7
  import { EMPTY_MODES, flattenChildren } from '../../utils/react-utils';
9
- import { jsx as _jsx } from "react/jsx-runtime";
8
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
9
  function AvatarGroup({
11
10
  modes = EMPTY_MODES,
12
11
  children,
13
12
  style,
14
13
  ...rest
15
14
  }) {
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, '')}`;
16
19
  const gap = getVariableByName('avatarGroup/gap', modes);
17
20
  const avatarSize = getVariableByName('avatar/size', modes);
18
21
  const avatarRadius = getVariableByName('avatar/radius', modes);
@@ -41,7 +44,11 @@ function AvatarGroup({
41
44
  y: r
42
45
  };
43
46
 
44
- // Native Mask Path
47
+ // Native mask path: the full avatar square MINUS a circular hole where the
48
+ // next (overlapping) avatar sits. Drawn as a single `evenodd` path so the
49
+ // hole is cut out of the square. Filled WHITE because react-native-svg's
50
+ // `<Mask>` is a *luminance* mask (white = visible, black/absent = hidden) —
51
+ // the inverse of the old `@react-native-masked-view` alpha convention.
45
52
  const rectPath = `M 0 0 h ${avatarSizeInt} v ${avatarSizeInt} h -${avatarSizeInt} z`;
46
53
  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`;
47
54
  const maskPath = `${rectPath} ${circlePath}`;
@@ -99,25 +106,40 @@ function AvatarGroup({
99
106
  }, index);
100
107
  }
101
108
 
102
- // Native MaskedView
109
+ // Native: mask the avatar with react-native-svg instead of the
110
+ // unmaintained `@react-native-masked-view/masked-view`, whose
111
+ // legacy `requireNativeComponent('RNCMaskedView')` resolves to a
112
+ // plain object (crashing "Element type is invalid") under the New
113
+ // Architecture interop. react-native-svg ships a Fabric/codegen
114
+ // `<Mask>` + `<ForeignObject>` that resolves on both architectures;
115
+ // the avatar is hosted inside the `<ForeignObject>` and revealed
116
+ // through the luminance mask.
117
+ const maskId = `${maskBaseId}-${index}`;
103
118
  return /*#__PURE__*/_jsx(View, {
104
119
  style: wrapperStyle,
105
- children: /*#__PURE__*/_jsx(MaskedView, {
106
- style: {
107
- flex: 1,
108
- height: '100%'
109
- },
110
- maskElement: /*#__PURE__*/_jsx(Svg, {
111
- height: "100%",
112
- width: "100%",
113
- viewBox: `0 0 ${avatarSizeInt} ${avatarSizeInt}`,
114
- children: /*#__PURE__*/_jsx(Path, {
115
- d: maskPath,
116
- fill: "black",
117
- fillRule: "evenodd"
120
+ children: /*#__PURE__*/_jsxs(Svg, {
121
+ height: "100%",
122
+ width: "100%",
123
+ viewBox: `0 0 ${avatarSizeInt} ${avatarSizeInt}`,
124
+ children: [/*#__PURE__*/_jsx(Defs, {
125
+ children: /*#__PURE__*/_jsx(Mask, {
126
+ id: maskId,
127
+ maskUnits: "userSpaceOnUse",
128
+ maskContentUnits: "userSpaceOnUse",
129
+ children: /*#__PURE__*/_jsx(Path, {
130
+ d: maskPath,
131
+ fill: "white",
132
+ fillRule: "evenodd"
133
+ })
118
134
  })
119
- }),
120
- children: clonedChild
135
+ }), /*#__PURE__*/_jsx(ForeignObject, {
136
+ x: "0",
137
+ y: "0",
138
+ width: avatarSizeInt,
139
+ height: avatarSizeInt,
140
+ mask: `url(#${maskId})`,
141
+ children: clonedChild
142
+ })]
121
143
  })
122
144
  }, index);
123
145
  })
@@ -1,9 +1,8 @@
1
1
  "use strict";
2
2
 
3
3
  import React, { useId, useMemo } from 'react';
4
- import { View, Text, Pressable, StyleSheet, Platform, Image as RNImage } from 'react-native';
5
- import MaskedView from '@react-native-masked-view/masked-view';
6
- import Svg, { Defs, LinearGradient, Stop, Rect } from 'react-native-svg';
4
+ import { View, Text, Pressable, StyleSheet } from 'react-native';
5
+ import Svg, { Defs, LinearGradient, Stop, Rect, Mask, Filter, FeGaussianBlur, Image as SvgImage } from 'react-native-svg';
7
6
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
8
7
  import { EMPTY_MODES, resolveTruncation } from '../../utils/react-utils';
9
8
  import Avatar from '../Avatar/Avatar';
@@ -65,7 +64,9 @@ function ProductMerchandisingCard({
65
64
  const rawId = useId();
66
65
  const safeId = rawId.replace(/[^a-zA-Z0-9_-]/g, '');
67
66
  const scrimId = `pmc-footer-scrim-${safeId}`;
67
+ const blurGradientId = `pmc-footer-blurgrad-${safeId}`;
68
68
  const blurMaskId = `pmc-footer-blurmask-${safeId}`;
69
+ const blurFilterId = `pmc-footer-blurfilter-${safeId}`;
69
70
  const normalizedImageSource = useMemo(() => normalizeImageSource(imageSource), [imageSource]);
70
71
  const tokens = useMemo(() => resolveTokens(modes), [modes]);
71
72
  const containerStyle = {
@@ -159,57 +160,63 @@ function ProductMerchandisingCard({
159
160
  children: [normalizedImageSource != null ? /*#__PURE__*/_jsx(View, {
160
161
  style: StyleSheet.absoluteFill,
161
162
  pointerEvents: "none",
162
- children: /*#__PURE__*/_jsx(MaskedView, {
163
- style: StyleSheet.absoluteFill,
164
- maskElement: /*#__PURE__*/_jsxs(Svg, {
165
- width: "100%",
166
- height: "100%",
167
- children: [/*#__PURE__*/_jsx(Defs, {
168
- children: /*#__PURE__*/_jsxs(LinearGradient, {
169
- id: blurMaskId,
170
- x1: "0",
171
- y1: "0",
172
- x2: "0",
173
- y2: "1",
174
- children: [/*#__PURE__*/_jsx(Stop, {
175
- offset: "0",
176
- stopColor: "#000000",
177
- stopOpacity: 0
178
- }), /*#__PURE__*/_jsx(Stop, {
179
- offset: "0.35",
180
- stopColor: "#000000",
181
- stopOpacity: 0.5
182
- }), /*#__PURE__*/_jsx(Stop, {
183
- offset: "0.7",
184
- stopColor: "#000000",
185
- stopOpacity: 0.92
186
- }), /*#__PURE__*/_jsx(Stop, {
187
- offset: "1",
188
- stopColor: "#000000",
189
- stopOpacity: 1
190
- })]
163
+ children: /*#__PURE__*/_jsxs(Svg, {
164
+ width: "100%",
165
+ height: "100%",
166
+ children: [/*#__PURE__*/_jsxs(Defs, {
167
+ children: [/*#__PURE__*/_jsx(Filter, {
168
+ id: blurFilterId,
169
+ x: "-15%",
170
+ y: "-15%",
171
+ width: "130%",
172
+ height: "130%",
173
+ children: /*#__PURE__*/_jsx(FeGaussianBlur, {
174
+ stdDeviation: tokens.imageBlurStdDeviation,
175
+ edgeMode: "duplicate"
176
+ })
177
+ }), /*#__PURE__*/_jsxs(LinearGradient, {
178
+ id: blurGradientId,
179
+ x1: "0",
180
+ y1: "0",
181
+ x2: "0",
182
+ y2: "1",
183
+ children: [/*#__PURE__*/_jsx(Stop, {
184
+ offset: "0",
185
+ stopColor: "#ffffff",
186
+ stopOpacity: 0
187
+ }), /*#__PURE__*/_jsx(Stop, {
188
+ offset: "0.35",
189
+ stopColor: "#ffffff",
190
+ stopOpacity: 0.5
191
+ }), /*#__PURE__*/_jsx(Stop, {
192
+ offset: "0.7",
193
+ stopColor: "#ffffff",
194
+ stopOpacity: 0.92
195
+ }), /*#__PURE__*/_jsx(Stop, {
196
+ offset: "1",
197
+ stopColor: "#ffffff",
198
+ stopOpacity: 1
199
+ })]
200
+ }), /*#__PURE__*/_jsx(Mask, {
201
+ id: blurMaskId,
202
+ children: /*#__PURE__*/_jsx(Rect, {
203
+ x: "0",
204
+ y: "0",
205
+ width: "100%",
206
+ height: "100%",
207
+ fill: `url(#${blurGradientId})`
191
208
  })
192
- }), /*#__PURE__*/_jsx(Rect, {
193
- x: "0",
194
- y: "0",
195
- width: "100%",
196
- height: "100%",
197
- fill: `url(#${blurMaskId})`
198
209
  })]
199
- }),
200
- children: /*#__PURE__*/_jsx(RNImage, {
201
- source: normalizedImageSource,
202
- blurRadius: tokens.imageBlurRadius,
203
- resizeMode: "cover",
204
- style: {
205
- position: 'absolute',
206
- left: 0,
207
- right: 0,
208
- bottom: 0,
209
- width: '100%',
210
- height
211
- }
212
- })
210
+ }), /*#__PURE__*/_jsx(SvgImage, {
211
+ href: normalizedImageSource,
212
+ x: "0",
213
+ y: "0",
214
+ width: "100%",
215
+ height: "100%",
216
+ preserveAspectRatio: "xMidYMid slice",
217
+ filter: `url(#${blurFilterId})`,
218
+ mask: `url(#${blurMaskId})`
219
+ })]
213
220
  })
214
221
  }) : /*#__PURE__*/_jsx(GlassFill, {
215
222
  tint: "dark",
@@ -328,19 +335,16 @@ function resolveTokens(modes) {
328
335
  const sbFamily = asStr(getVariableByName('productMerchandisingcard/specialbadge/text/fontfamily', modes), 'JioType Var');
329
336
  const sbWeight = asStr(getVariableByName('productMerchandisingcard/specialbadge/text/fontweight', modes), '500');
330
337
 
331
- // `Image.blurRadius` is interpreted differently per platform — iOS applies a
332
- // strong Gaussian while Android's IterativeBoxBlur is gentler at the same
333
- // numeric radius so we tune each platform to land on the same perceived
334
- // "minimal" frosted strength as the Figma design.
335
- const imageBlurRadius = Platform.select({
336
- ios: Math.max(6, Math.round(blurRadius * 0.5)),
337
- android: Math.max(12, Math.round(blurRadius * 0.95)),
338
- default: Math.max(8, Math.round(blurRadius * 0.6))
339
- });
338
+ // SVG `<FeGaussianBlur stdDeviation>` is resolution-independent and renders
339
+ // an identical Gaussian on iOS, Android and web so unlike the old
340
+ // `Image.blurRadius` path it needs no per-platform tuning. `stdDeviation` is
341
+ // roughly half a perceptual "blur radius", so we scale the `blur/minimal`
342
+ // token down to land on the same subtle frosted strength as the design.
343
+ const imageBlurStdDeviation = Math.max(4, Math.round(blurRadius * 0.35));
340
344
  return {
341
345
  radius,
342
346
  blurIntensity: Math.max(0, Math.min(100, Math.round(blurRadius * BLUR_INTENSITY_FACTOR))),
343
- imageBlurRadius,
347
+ imageBlurStdDeviation,
344
348
  header: {
345
349
  flexDirection: 'row',
346
350
  alignItems: 'flex-start',