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.
Files changed (48) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/lib/commonjs/components/AvatarGroup/AvatarGroup.js +41 -20
  3. package/lib/commonjs/components/Card/Card.js +29 -12
  4. package/lib/commonjs/components/CardFeedback/CardFeedback.js +16 -2
  5. package/lib/commonjs/components/CardInsight/CardInsight.js +34 -13
  6. package/lib/commonjs/components/CardProviderInfo/CardProviderInfo.js +23 -3
  7. package/lib/commonjs/components/DebitCard/DebitCard.js +23 -3
  8. package/lib/commonjs/components/MediaCard/MediaCard.js +24 -7
  9. package/lib/commonjs/components/ProductMerchandisingCard/ProductMerchandisingCard.js +64 -60
  10. package/lib/commonjs/components/RechargeCard/RechargeCard.js +34 -13
  11. package/lib/commonjs/components/TestimonialsCard/TestimonialsCard.js +23 -5
  12. package/lib/commonjs/icons/registry.js +1 -1
  13. package/lib/commonjs/utils/GlassFill/GlassFill.js +69 -73
  14. package/lib/module/components/AvatarGroup/AvatarGroup.js +43 -21
  15. package/lib/module/components/Card/Card.js +31 -14
  16. package/lib/module/components/CardFeedback/CardFeedback.js +17 -3
  17. package/lib/module/components/CardInsight/CardInsight.js +36 -15
  18. package/lib/module/components/CardProviderInfo/CardProviderInfo.js +25 -5
  19. package/lib/module/components/DebitCard/DebitCard.js +25 -5
  20. package/lib/module/components/MediaCard/MediaCard.js +26 -9
  21. package/lib/module/components/ProductMerchandisingCard/ProductMerchandisingCard.js +66 -62
  22. package/lib/module/components/RechargeCard/RechargeCard.js +36 -15
  23. package/lib/module/components/TestimonialsCard/TestimonialsCard.js +25 -7
  24. package/lib/module/icons/registry.js +1 -1
  25. package/lib/module/utils/GlassFill/GlassFill.js +69 -72
  26. package/lib/typescript/src/components/Card/Card.d.ts +10 -1
  27. package/lib/typescript/src/components/CardFeedback/CardFeedback.d.ts +10 -1
  28. package/lib/typescript/src/components/CardInsight/CardInsight.d.ts +10 -1
  29. package/lib/typescript/src/components/CardProviderInfo/CardProviderInfo.d.ts +10 -1
  30. package/lib/typescript/src/components/DebitCard/DebitCard.d.ts +10 -1
  31. package/lib/typescript/src/components/MediaCard/MediaCard.d.ts +10 -1
  32. package/lib/typescript/src/components/RechargeCard/RechargeCard.d.ts +10 -1
  33. package/lib/typescript/src/components/TestimonialsCard/TestimonialsCard.d.ts +8 -1
  34. package/lib/typescript/src/icons/registry.d.ts +1 -1
  35. package/lib/typescript/src/utils/GlassFill/GlassFill.d.ts +11 -4
  36. package/package.json +1 -2
  37. package/src/components/AvatarGroup/AvatarGroup.tsx +44 -18
  38. package/src/components/Card/Card.tsx +46 -15
  39. package/src/components/CardFeedback/CardFeedback.tsx +29 -4
  40. package/src/components/CardInsight/CardInsight.tsx +48 -17
  41. package/src/components/CardProviderInfo/CardProviderInfo.tsx +36 -3
  42. package/src/components/DebitCard/DebitCard.tsx +36 -3
  43. package/src/components/MediaCard/MediaCard.tsx +40 -9
  44. package/src/components/ProductMerchandisingCard/ProductMerchandisingCard.tsx +76 -47
  45. package/src/components/RechargeCard/RechargeCard.tsx +48 -13
  46. package/src/components/TestimonialsCard/TestimonialsCard.tsx +37 -6
  47. package/src/icons/registry.ts +1 -1
  48. 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 a layer's vertical reveal mask. `offset` is the vertical
18
- * position (0 = top of the surface, 1 = bottom); `opacity` is the mask alpha
19
- * there (0 = layer hidden / fully clear, 1 = layer fully applied).
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) lets each layer
22
- * EASE in, so the blur swells smoothly toward the bottom instead of appearing
23
- * along a hard horizontal seam. That soft S-curve is what gives the surface its
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 layer of the progressive ramp.
29
- * - `stops` describe how this layer is revealed from top to bottom.
30
- * - `amount` is this layer's share (0–1) of the max `blurAmount`.
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
- * We stack just TWO layers on both platforms: a faint base blur that covers
33
- * most of the footer and a slightly stronger accent concentrated near the
34
- * bottom. Keeping the overlap shallow avoids compounding the dark tint of
35
- * multiple `BlurView`s (which is what made the earlier 3-layer ramp read as a
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
- // Base reveal: a faint trace of blur begins near the very top and grows
41
- // steadily downward, so the upper half still carries visible glass rather than
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
- const PROGRESSIVE_LAYERS = [{
60
- amount: 0.65,
61
- stops: BASE_MASK_STOPS
62
- },
63
- // Accent: the strongest blur, gathering over the lower portion for depth.
64
- {
65
- amount: 1.0,
66
- stops: [{
67
- offset: 0.0,
68
- opacity: 0
69
- }, {
70
- offset: 0.3,
71
- opacity: 0.15
72
- }, {
73
- offset: 0.65,
74
- opacity: 0.65
75
- }, {
76
- offset: 1.0,
77
- opacity: 1
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 alpha-gradient mask drawn with `react-native-svg`. `MaskedView`
112
- * reveals its child in proportion to this mask's alpha, so feeding it an eased
113
- * multi-stop gradient makes the layer's blur swell in smoothly from top to
114
- * bottom instead of along a hard seam.
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 GradientMask({
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: "#000000",
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(MaskedView, {
198
- style: StyleSheet.absoluteFill,
199
- maskElement: /*#__PURE__*/_jsx(GradientMask, {
200
- id: `${maskId}-fb`,
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: PROGRESSIVE_LAYERS.map((layer, i) => {
215
- const amount = Math.max(1, Math.round(peakBlur * layer.amount));
216
- return /*#__PURE__*/_jsx(MaskedView, {
217
- style: StyleSheet.absoluteFill,
218
- maskElement: /*#__PURE__*/_jsx(GradientMask, {
219
- id: `${maskId}-${i}`,
220
- stops: layer.stops
221
- }),
222
- children: /*#__PURE__*/_jsx(BlurView, {
223
- style: StyleSheet.absoluteFill,
224
- blurType: blurType,
225
- blurAmount: amount,
226
- reducedTransparencyFallbackColor: fallbackColor
227
- })
228
- }, i);
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
  }
@@ -29,6 +29,15 @@ export interface CardProps {
29
29
  * Style overrides for the card container.
30
30
  */
31
31
  style?: StyleProp<ViewStyle>;
32
+ /**
33
+ * Press handler for the whole card. When set, the card becomes pressable
34
+ * (rendered through a `Pressable` with `accessibilityRole="button"`).
35
+ */
36
+ onPress?: () => void;
37
+ /** Disable interaction when `onPress` is set. */
38
+ disabled?: boolean;
39
+ /** Accessibility label forwarded to the pressable wrapper. */
40
+ accessibilityLabel?: string;
32
41
  }
33
42
  /**
34
43
  * Card component implementation from Figma node 765:6186.
@@ -45,7 +54,7 @@ export interface CardProps {
45
54
  * </Card>
46
55
  * ```
47
56
  */
48
- export declare function Card({ header, media, children, modes, mediaAspectRatio, style, }: CardProps): import("react/jsx-runtime").JSX.Element;
57
+ export declare function Card({ header, media, children, modes, mediaAspectRatio, style, onPress, disabled, accessibilityLabel, }: CardProps): import("react/jsx-runtime").JSX.Element;
49
58
  export declare namespace Card {
50
59
  var Title: ({ children, style, modes: propModes }: CardTextProps) => import("react/jsx-runtime").JSX.Element;
51
60
  var SupportText: ({ children, style, modes: propModes }: CardTextProps) => import("react/jsx-runtime").JSX.Element;
@@ -14,13 +14,22 @@ export interface CardFeedbackProps {
14
14
  * Style overrides for the card container.
15
15
  */
16
16
  style?: StyleProp<ViewStyle>;
17
+ /**
18
+ * Press handler for the whole card. When set, the card becomes pressable
19
+ * (rendered through a `Pressable` with `accessibilityRole="button"`).
20
+ */
21
+ onPress?: () => void;
22
+ /** Disable interaction when `onPress` is set. */
23
+ disabled?: boolean;
24
+ /** Accessibility label forwarded to the pressable wrapper. */
25
+ accessibilityLabel?: string;
17
26
  }
18
27
  /**
19
28
  * CardFeedback component from Figma node 1280:4481.
20
29
  *
21
30
  * Used for status updates, warnings, or feedback.
22
31
  */
23
- export declare function CardFeedback({ children, modes: propModes, style, }: CardFeedbackProps): import("react/jsx-runtime").JSX.Element;
32
+ export declare function CardFeedback({ children, modes: propModes, style, onPress, disabled, accessibilityLabel, }: CardFeedbackProps): import("react/jsx-runtime").JSX.Element;
24
33
  export declare namespace CardFeedback {
25
34
  var Icon: typeof import("./CardFeedback").Icon;
26
35
  var Content: typeof import("./CardFeedback").Content;
@@ -32,6 +32,15 @@ export type CardInsightProps = {
32
32
  modes?: Modes;
33
33
  /** Override container styles. */
34
34
  style?: StyleProp<ViewStyle>;
35
+ /**
36
+ * Press handler for the whole card. When set, the card becomes pressable
37
+ * (rendered through a `Pressable` with `accessibilityRole="button"`).
38
+ */
39
+ onPress?: () => void;
40
+ /** Disable interaction when `onPress` is set. */
41
+ disabled?: boolean;
42
+ /** Accessibility label forwarded to the pressable wrapper (defaults to `title`). */
43
+ accessibilityLabel?: string;
35
44
  };
36
45
  /**
37
46
  * `CardInsight` renders a single insight card composed of:
@@ -44,6 +53,6 @@ export type CardInsightProps = {
44
53
  * @component
45
54
  * @param {CardInsightProps} props
46
55
  */
47
- declare function CardInsight({ title, subtitle, badge, children, footer, divider, modes, style, }: CardInsightProps): import("react/jsx-runtime").JSX.Element;
56
+ declare function CardInsight({ title, subtitle, badge, children, footer, divider, modes, style, onPress, disabled, accessibilityLabel, }: CardInsightProps): import("react/jsx-runtime").JSX.Element;
48
57
  export default CardInsight;
49
58
  //# sourceMappingURL=CardInsight.d.ts.map
@@ -12,6 +12,15 @@ export type CardProviderInfoProps = {
12
12
  modes?: Modes;
13
13
  /** Override container styles. */
14
14
  style?: StyleProp<ViewStyle>;
15
+ /**
16
+ * Press handler for the whole card. When set, the card becomes pressable
17
+ * (rendered through a `Pressable` with `accessibilityRole="button"`).
18
+ */
19
+ onPress?: () => void;
20
+ /** Disable interaction when `onPress` is set. */
21
+ disabled?: boolean;
22
+ /** Accessibility label forwarded to the pressable wrapper (defaults to `label`). */
23
+ accessibilityLabel?: string;
15
24
  };
16
25
  /**
17
26
  * CardProviderInfo displays a product header (ProductLabel) followed by a
@@ -20,6 +29,6 @@ export type CardProviderInfoProps = {
20
29
  * @component
21
30
  * @param {CardProviderInfoProps} props
22
31
  */
23
- declare function CardProviderInfo({ label, imageSource, children, modes, style, }: CardProviderInfoProps): import("react/jsx-runtime").JSX.Element;
32
+ declare function CardProviderInfo({ label, imageSource, children, modes, style, onPress, disabled, accessibilityLabel, }: CardProviderInfoProps): import("react/jsx-runtime").JSX.Element;
24
33
  export default CardProviderInfo;
25
34
  //# sourceMappingURL=CardProviderInfo.d.ts.map
@@ -22,7 +22,16 @@ export type DebitCardProps = {
22
22
  modes?: Modes;
23
23
  /** Container style overrides */
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
- declare function DebitCard({ cardholderName, cardNumber, expireDate, bankName, cardType, cardArtSource, bankLogoSlot, providerLogoSlot, modes: propModes, style, }: DebitCardProps): import("react/jsx-runtime").JSX.Element;
35
+ declare function DebitCard({ cardholderName, cardNumber, expireDate, bankName, cardType, cardArtSource, bankLogoSlot, providerLogoSlot, modes: propModes, style, onPress, disabled, accessibilityLabel, }: DebitCardProps): import("react/jsx-runtime").JSX.Element;
27
36
  export default DebitCard;
28
37
  //# sourceMappingURL=DebitCard.d.ts.map
@@ -33,6 +33,15 @@ export interface MediaCardProps {
33
33
  * Style overrides for the card container.
34
34
  */
35
35
  style?: StyleProp<ViewStyle>;
36
+ /**
37
+ * Press handler for the whole card. When set, the card becomes pressable
38
+ * (rendered through a `Pressable` with `accessibilityRole="button"`).
39
+ */
40
+ onPress?: () => void;
41
+ /** Disable interaction when `onPress` is set. */
42
+ disabled?: boolean;
43
+ /** Accessibility label forwarded to the pressable wrapper. */
44
+ accessibilityLabel?: string;
36
45
  }
37
46
  /**
38
47
  * MediaCard component implementation from Figma node 1241:4140.
@@ -52,7 +61,7 @@ export interface MediaCardProps {
52
61
  * interactive elements inside the overlays without the wrapper itself
53
62
  * capturing them.
54
63
  */
55
- export declare function MediaCard({ imageSource, ratio, media, children, modes, style, }: MediaCardProps): import("react/jsx-runtime").JSX.Element;
64
+ export declare function MediaCard({ imageSource, ratio, media, children, modes, style, onPress, disabled, accessibilityLabel, }: MediaCardProps): import("react/jsx-runtime").JSX.Element;
56
65
  export declare namespace MediaCard {
57
66
  var Header: typeof import("./MediaCard").Header;
58
67
  var Title: typeof import("./MediaCard").Title;
@@ -42,10 +42,19 @@ export type RechargeCardProps = {
42
42
  */
43
43
  modes?: Modes;
44
44
  style?: ViewStyle;
45
+ /**
46
+ * Press handler for the whole card. When set, the card becomes pressable
47
+ * (rendered through a `Pressable` with `accessibilityRole="button"`).
48
+ */
49
+ onPress?: () => void;
50
+ /** Disable interaction when `onPress` is set. */
51
+ disabled?: boolean;
52
+ /** Accessibility label forwarded to the pressable wrapper (defaults to `title`). */
53
+ accessibilityLabel?: string;
45
54
  };
46
55
  /**
47
56
  * RechargeCard component implementation from Figma node 2235:937.
48
57
  * Displays a recharge plan with price, validity, data, and subscription details.
49
58
  */
50
- export default function RechargeCard({ title, price, validity, data, disclaimer, subscriptionContent, actions, modes, style, }: RechargeCardProps): import("react/jsx-runtime").JSX.Element;
59
+ export default function RechargeCard({ title, price, validity, data, disclaimer, subscriptionContent, actions, modes, style, onPress, disabled, accessibilityLabel, }: RechargeCardProps): import("react/jsx-runtime").JSX.Element;
51
60
  //# sourceMappingURL=RechargeCard.d.ts.map
@@ -29,6 +29,13 @@ export type TestimonialsCardProps = {
29
29
  * from the title and body.
30
30
  */
31
31
  accessibilityLabel?: string;
32
+ /**
33
+ * Press handler for the whole card. When set, the card becomes pressable
34
+ * (rendered through a `Pressable` with `accessibilityRole="button"`).
35
+ */
36
+ onPress?: () => void;
37
+ /** Disable interaction when `onPress` is set. */
38
+ disabled?: boolean;
32
39
  };
33
40
  /**
34
41
  * TestimonialsCard renders a compact, fixed-width card with a circular avatar,
@@ -46,7 +53,7 @@ export type TestimonialsCardProps = {
46
53
  * />
47
54
  * ```
48
55
  */
49
- declare function TestimonialsCard({ title, body, modes, style, avatarProps, accessibilityLabel, }: TestimonialsCardProps): import("react/jsx-runtime").JSX.Element;
56
+ declare function TestimonialsCard({ title, body, modes, style, avatarProps, accessibilityLabel, onPress, disabled, }: TestimonialsCardProps): import("react/jsx-runtime").JSX.Element;
50
57
  declare const _default: React.MemoExoticComponent<typeof TestimonialsCard>;
51
58
  export default _default;
52
59
  //# sourceMappingURL=TestimonialsCard.d.ts.map
@@ -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-26T16:43:17.033Z
7
+ * Generated: 2026-06-29T11:40:01.320Z
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 two `MaskedView` + `BlurView` layers (a faint base + a
29
- * slightly stronger accent near the bottom), each revealed via an eased
30
- * multi-stop SVG gradient mask so the blur swells smoothly rather than
31
- * along a hard seam. Works on iOS and Android with no extra native module.
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.17",
3
+ "version": "0.1.19",
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 MaskedView from '@react-native-masked-view/masked-view';
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 Mask Path
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 MaskedView
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
- <MaskedView
108
- style={{ flex: 1, height: '100%' }}
109
- maskElement={
110
- <Svg height="100%" width="100%" viewBox={`0 0 ${avatarSizeInt} ${avatarSizeInt}`}>
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
- {clonedChild}
120
- </MaskedView>
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
  })}
@@ -1,5 +1,5 @@
1
1
  import React, { createContext, useContext, isValidElement, cloneElement, useMemo } from 'react';
2
- import { View, Text, StyleSheet, type ViewStyle, type TextStyle, type StyleProp } from 'react-native';
2
+ import { View, Text, Pressable, type ViewStyle, type TextStyle, type StyleProp } from 'react-native';
3
3
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
4
4
  import { EMPTY_MODES } from '../../utils/react-utils';
5
5
  import type { Modes } from '../../design-tokens'
@@ -39,6 +39,15 @@ export interface CardProps {
39
39
  * Style overrides for the card container.
40
40
  */
41
41
  style?: StyleProp<ViewStyle>;
42
+ /**
43
+ * Press handler for the whole card. When set, the card becomes pressable
44
+ * (rendered through a `Pressable` with `accessibilityRole="button"`).
45
+ */
46
+ onPress?: () => void;
47
+ /** Disable interaction when `onPress` is set. */
48
+ disabled?: boolean;
49
+ /** Accessibility label forwarded to the pressable wrapper. */
50
+ accessibilityLabel?: string;
42
51
  }
43
52
 
44
53
  /**
@@ -63,6 +72,9 @@ export function Card({
63
72
  modes = EMPTY_MODES,
64
73
  mediaAspectRatio = 154 / 116,
65
74
  style,
75
+ onPress,
76
+ disabled,
77
+ accessibilityLabel,
66
78
  }: CardProps) {
67
79
  // Resolve Card Container Tokens
68
80
  const backgroundColor = getVariableByName('card/background/color', modes);
@@ -124,23 +136,42 @@ export function Card({
124
136
  paddingVertical: contentPaddingVertical,
125
137
  };
126
138
 
139
+ const content = (
140
+ <>
141
+ {header && (
142
+ <View style={headerWrapperStyle}>
143
+ {headerWithModes}
144
+ </View>
145
+ )}
146
+ {media && (
147
+ <View style={mediaWrapperStyle}>
148
+ {mediaWithModes}
149
+ </View>
150
+ )}
151
+ <View style={contentWrapperStyle}>
152
+ {children}
153
+ </View>
154
+ </>
155
+ );
156
+
127
157
  return (
128
158
  <CardContext.Provider value={{ modes }}>
129
- <View style={[containerStyle, style]}>
130
- {header && (
131
- <View style={headerWrapperStyle}>
132
- {headerWithModes}
133
- </View>
134
- )}
135
- {media && (
136
- <View style={mediaWrapperStyle}>
137
- {mediaWithModes}
138
- </View>
139
- )}
140
- <View style={contentWrapperStyle}>
141
- {children}
159
+ {onPress ? (
160
+ <Pressable
161
+ style={[containerStyle, style]}
162
+ onPress={onPress}
163
+ disabled={disabled}
164
+ accessibilityRole="button"
165
+ accessibilityLabel={accessibilityLabel}
166
+ accessibilityState={{ disabled: !!disabled }}
167
+ >
168
+ {content}
169
+ </Pressable>
170
+ ) : (
171
+ <View style={[containerStyle, style]} accessibilityLabel={accessibilityLabel}>
172
+ {content}
142
173
  </View>
143
- </View>
174
+ )}
144
175
  </CardContext.Provider>
145
176
  );
146
177
  }