jfs-components 0.0.67 → 0.0.69
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 +29 -0
- package/lib/commonjs/components/Image/Image.js +17 -10
- package/lib/commonjs/components/MediaCard/MediaCard.js +96 -56
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/Image/Image.js +17 -10
- package/lib/module/components/MediaCard/MediaCard.js +96 -56
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/Image/Image.d.ts +6 -21
- package/lib/typescript/src/components/MediaCard/MediaCard.d.ts +36 -10
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +2 -1
- package/src/components/Image/Image.tsx +18 -12
- package/src/components/MediaCard/MediaCard.tsx +122 -50
- package/src/icons/registry.ts +1 -1
|
@@ -10,8 +10,12 @@ export type ImageProps = {
|
|
|
10
10
|
imageSource?: ImageSourcePropType | string | undefined;
|
|
11
11
|
/**
|
|
12
12
|
* Width-to-height aspect ratio as a number, e.g. `16 / 9`, `1`, `4 / 3`.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Defaults to `16 / 9` so the image always has a sensible rendered height
|
|
14
|
+
* even when the parent has no fixed height (e.g. inside `MediaCard`,
|
|
15
|
+
* which hugs whatever the image renders at).
|
|
16
|
+
*
|
|
17
|
+
* Pass `ratio={null as any}` is not supported — explicitly set a number,
|
|
18
|
+
* or override with `width` + `height` if you need a non-aspect layout.
|
|
15
19
|
*
|
|
16
20
|
* Equivalent CSS: `aspect-ratio: <ratio>`.
|
|
17
21
|
*/
|
|
@@ -35,25 +39,6 @@ export type ImageProps = {
|
|
|
35
39
|
accessibilityElementsHidden?: boolean | undefined;
|
|
36
40
|
importantForAccessibility?: 'auto' | 'yes' | 'no' | 'no-hide-descendants' | undefined;
|
|
37
41
|
};
|
|
38
|
-
/**
|
|
39
|
-
* `Image` — the library's standard raster image primitive.
|
|
40
|
-
*
|
|
41
|
-
* Why this exists:
|
|
42
|
-
* - Gives consumers a single component for "show an image at a given aspect
|
|
43
|
-
* ratio inside a flex container" without having to remember the
|
|
44
|
-
* `width:'100%' / height:'100%' / resizeMode:'cover'` boilerplate.
|
|
45
|
-
* - Centralizes URL-vs-`{uri}` normalization that several components were
|
|
46
|
-
* re-implementing.
|
|
47
|
-
* - Uses the same `imageSource` prop name as the rest of the library
|
|
48
|
-
* (`Avatar`, `ProductLabel`, `CardProviderInfo`, ...) for a unified API.
|
|
49
|
-
*
|
|
50
|
-
* Layout rules:
|
|
51
|
-
* - If `ratio` is provided, the image lays out with `aspectRatio: ratio`
|
|
52
|
-
* and (unless `width` is given) fills the parent's width.
|
|
53
|
-
* - If neither `ratio` nor explicit dimensions are given, the image fills
|
|
54
|
-
* its parent (`width: '100%'`, `height: '100%'`) — same default as the
|
|
55
|
-
* most common usage in this library (background media, hero images).
|
|
56
|
-
*/
|
|
57
42
|
declare function Image({ imageSource, ratio, resizeMode, width, height, borderRadius, style, accessibilityLabel, accessibilityElementsHidden, importantForAccessibility, }: ImageProps): import("react/jsx-runtime").JSX.Element;
|
|
58
43
|
declare const _default: React.MemoExoticComponent<typeof Image>;
|
|
59
44
|
export default _default;
|
|
@@ -36,11 +36,20 @@ export interface MediaCardProps {
|
|
|
36
36
|
/**
|
|
37
37
|
* MediaCard component implementation from Figma node 1241:4140.
|
|
38
38
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* the
|
|
43
|
-
*
|
|
39
|
+
* Layout contract (important — read this before editing):
|
|
40
|
+
* - The **background** (image or custom `media`) is the only child in
|
|
41
|
+
* normal flow. It dictates the card's height — typically via
|
|
42
|
+
* `aspectRatio` on the inner `<Image>`. There is no `minHeight`.
|
|
43
|
+
* - `Header` and `Footer` are **absolutely positioned overlays**:
|
|
44
|
+
* - `Header` pinned to top-left/right with safe padding.
|
|
45
|
+
* - `Footer` pinned to bottom-left/right with `zIndex: 2` so it sits
|
|
46
|
+
* on top of the header (and on top of the image). This guarantees
|
|
47
|
+
* the footer never moves no matter how many lines the title wraps
|
|
48
|
+
* to — the title may overflow the header bounds, but the footer's
|
|
49
|
+
* position is a function of the card box, not the title.
|
|
50
|
+
* - `pointerEvents="box-none"` is applied so taps still land on the
|
|
51
|
+
* interactive elements inside the overlays without the wrapper itself
|
|
52
|
+
* capturing them.
|
|
44
53
|
*/
|
|
45
54
|
export declare function MediaCard({ imageSource, ratio, media, children, modes, style, }: MediaCardProps): import("react/jsx-runtime").JSX.Element;
|
|
46
55
|
export declare namespace MediaCard {
|
|
@@ -51,9 +60,11 @@ export declare namespace MediaCard {
|
|
|
51
60
|
var FooterSubtitle: typeof import("./MediaCard").FooterSubtitle;
|
|
52
61
|
}
|
|
53
62
|
/**
|
|
54
|
-
* Header
|
|
55
|
-
*
|
|
56
|
-
*
|
|
63
|
+
* Header overlay — pinned to the top of the card. Title content can wrap to
|
|
64
|
+
* any number of lines without affecting the footer's position; if it grows
|
|
65
|
+
* taller than the card, the card's `overflow: 'hidden'` clips it.
|
|
66
|
+
*
|
|
67
|
+
* Default `padding: 16` matches the Figma "title wrap" spec.
|
|
57
68
|
*/
|
|
58
69
|
export declare function Header({ children, style }: {
|
|
59
70
|
children?: React.ReactNode;
|
|
@@ -69,8 +80,23 @@ export declare function Title({ children, style, modes: propModes }: {
|
|
|
69
80
|
modes?: Record<string, any>;
|
|
70
81
|
}): import("react/jsx-runtime").JSX.Element;
|
|
71
82
|
/**
|
|
72
|
-
* Glass Footer
|
|
73
|
-
*
|
|
83
|
+
* Glass Footer — pinned to the bottom of the card, **always** on top of the
|
|
84
|
+
* Header (`zIndex: 2`).
|
|
85
|
+
*
|
|
86
|
+
* Glass implementation (April 2026 best practice for RN/Expo):
|
|
87
|
+
* - **iOS:** `expo-blur`'s `BlurView` renders a native `UIVisualEffectView`,
|
|
88
|
+
* so this is a real OS-level live blur of whatever's underneath. We pick
|
|
89
|
+
* `tint` from the Figma "Contrast Context" mode (`'dark'` / `'light'`)
|
|
90
|
+
* and a moderate intensity that matches the Figma `blur/minimal` token.
|
|
91
|
+
* - **Android:** the same `BlurView` with `experimentalBlurMethod="dimezisBlurView"`
|
|
92
|
+
* enables the hardware-accelerated `RenderEffect` blur on Android 12+.
|
|
93
|
+
* On older Android, expo-blur cleanly degrades to a tinted scrim — we
|
|
94
|
+
* layer a subtle noise/grain overlay on top so the surface still reads
|
|
95
|
+
* as "frosted glass" instead of a flat color.
|
|
96
|
+
* - **Web:** `BlurView` on web is implemented as `backdrop-filter: blur()`,
|
|
97
|
+
* which already worked in the previous version. Same component, same API.
|
|
98
|
+
*
|
|
99
|
+
* Tokens still drive the tint color, blur radius and inner spacing.
|
|
74
100
|
*/
|
|
75
101
|
export declare function Footer({ children, style, modes: propModes }: {
|
|
76
102
|
children?: React.ReactNode;
|
|
@@ -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-04-
|
|
7
|
+
* Generated: 2026-04-22T12:14:37.458Z
|
|
8
8
|
*/
|
|
9
9
|
export declare const iconRegistry: Record<string, {
|
|
10
10
|
path: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jfs-components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.69",
|
|
4
4
|
"description": "React Native Jio Finance Components Library",
|
|
5
5
|
"author": "sunshuaiqi@gmail.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
"ajv": "^8.17.1",
|
|
87
87
|
"ajv-keywords": "^5.1.0",
|
|
88
88
|
"expo": "^54.0.33",
|
|
89
|
+
"expo-blur": "~15.0.8",
|
|
89
90
|
"patch-package": "^8.0.1",
|
|
90
91
|
"react-native-gesture-handler": "^2.29.1",
|
|
91
92
|
"react-native-reanimated": "3.18.1",
|
|
@@ -2,7 +2,6 @@ import React, { useMemo } from 'react'
|
|
|
2
2
|
import {
|
|
3
3
|
Image as RNImage,
|
|
4
4
|
View,
|
|
5
|
-
StyleSheet,
|
|
6
5
|
type ImageSourcePropType,
|
|
7
6
|
type ImageStyle,
|
|
8
7
|
type StyleProp,
|
|
@@ -20,8 +19,12 @@ export type ImageProps = {
|
|
|
20
19
|
imageSource?: ImageSourcePropType | string | undefined
|
|
21
20
|
/**
|
|
22
21
|
* Width-to-height aspect ratio as a number, e.g. `16 / 9`, `1`, `4 / 3`.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* Defaults to `16 / 9` so the image always has a sensible rendered height
|
|
23
|
+
* even when the parent has no fixed height (e.g. inside `MediaCard`,
|
|
24
|
+
* which hugs whatever the image renders at).
|
|
25
|
+
*
|
|
26
|
+
* Pass `ratio={null as any}` is not supported — explicitly set a number,
|
|
27
|
+
* or override with `width` + `height` if you need a non-aspect layout.
|
|
25
28
|
*
|
|
26
29
|
* Equivalent CSS: `aspect-ratio: <ratio>`.
|
|
27
30
|
*/
|
|
@@ -78,9 +81,11 @@ function normalizeSource(
|
|
|
78
81
|
* its parent (`width: '100%'`, `height: '100%'`) — same default as the
|
|
79
82
|
* most common usage in this library (background media, hero images).
|
|
80
83
|
*/
|
|
84
|
+
const DEFAULT_RATIO = 16 / 9
|
|
85
|
+
|
|
81
86
|
function Image({
|
|
82
87
|
imageSource,
|
|
83
|
-
ratio,
|
|
88
|
+
ratio = DEFAULT_RATIO,
|
|
84
89
|
resizeMode = 'cover',
|
|
85
90
|
width,
|
|
86
91
|
height,
|
|
@@ -93,14 +98,15 @@ function Image({
|
|
|
93
98
|
const source = useMemo(() => normalizeSource(imageSource), [imageSource])
|
|
94
99
|
|
|
95
100
|
const layoutStyle: ImageStyle = useMemo(() => {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
// If the caller has fully specified width AND height, they're doing a
|
|
102
|
+
// non-aspect layout (e.g. "fill the parent") — respect that and skip
|
|
103
|
+
// `aspectRatio` so it doesn't conflict.
|
|
104
|
+
const isExplicitBox = width != null && height != null
|
|
105
|
+
const s: ImageStyle = {
|
|
106
|
+
width: width ?? '100%',
|
|
107
|
+
...(isExplicitBox
|
|
108
|
+
? { height: height as number }
|
|
109
|
+
: { aspectRatio: ratio, ...(height != null ? { height } : {}) }),
|
|
104
110
|
}
|
|
105
111
|
if (borderRadius != null) s.borderRadius = borderRadius
|
|
106
112
|
return s
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { createContext, useContext } from 'react'
|
|
2
2
|
import { View, Text, StyleSheet, type ViewStyle, type TextStyle, type StyleProp, type ImageSourcePropType, Platform } from 'react-native'
|
|
3
|
+
import { BlurView, type BlurTint } from 'expo-blur'
|
|
3
4
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
4
5
|
import Image from '../Image/Image'
|
|
5
6
|
import { EMPTY_MODES } from '../../utils/react-utils'
|
|
@@ -43,11 +44,20 @@ export interface MediaCardProps {
|
|
|
43
44
|
/**
|
|
44
45
|
* MediaCard component implementation from Figma node 1241:4140.
|
|
45
46
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* the
|
|
50
|
-
*
|
|
47
|
+
* Layout contract (important — read this before editing):
|
|
48
|
+
* - The **background** (image or custom `media`) is the only child in
|
|
49
|
+
* normal flow. It dictates the card's height — typically via
|
|
50
|
+
* `aspectRatio` on the inner `<Image>`. There is no `minHeight`.
|
|
51
|
+
* - `Header` and `Footer` are **absolutely positioned overlays**:
|
|
52
|
+
* - `Header` pinned to top-left/right with safe padding.
|
|
53
|
+
* - `Footer` pinned to bottom-left/right with `zIndex: 2` so it sits
|
|
54
|
+
* on top of the header (and on top of the image). This guarantees
|
|
55
|
+
* the footer never moves no matter how many lines the title wraps
|
|
56
|
+
* to — the title may overflow the header bounds, but the footer's
|
|
57
|
+
* position is a function of the card box, not the title.
|
|
58
|
+
* - `pointerEvents="box-none"` is applied so taps still land on the
|
|
59
|
+
* interactive elements inside the overlays without the wrapper itself
|
|
60
|
+
* capturing them.
|
|
51
61
|
*/
|
|
52
62
|
export function MediaCard({
|
|
53
63
|
imageSource,
|
|
@@ -58,19 +68,13 @@ export function MediaCard({
|
|
|
58
68
|
style,
|
|
59
69
|
}: MediaCardProps) {
|
|
60
70
|
const radius = parseFloat(getVariableByName('cardMedia/radius', modes) || '24')
|
|
61
|
-
const gap = parseFloat(getVariableByName('cardMedia/gap', modes) || '0')
|
|
62
71
|
|
|
63
72
|
const containerStyle: ViewStyle = {
|
|
64
73
|
borderRadius: radius,
|
|
65
|
-
gap,
|
|
66
74
|
overflow: 'hidden',
|
|
67
75
|
position: 'relative',
|
|
68
|
-
minHeight: 308,
|
|
69
76
|
}
|
|
70
77
|
|
|
71
|
-
// `media` wins for back-compat / custom nodes; otherwise we delegate to
|
|
72
|
-
// the shared <Image> for image-source backgrounds. All raster-rendering
|
|
73
|
-
// concerns (URL-vs-{uri}, resizeMode, aspect-ratio) live in <Image>.
|
|
74
78
|
const background = media ?? (
|
|
75
79
|
imageSource != null ? (
|
|
76
80
|
<Image
|
|
@@ -86,11 +90,12 @@ export function MediaCard({
|
|
|
86
90
|
return (
|
|
87
91
|
<MediaCardContext.Provider value={{ modes }}>
|
|
88
92
|
<View style={[containerStyle, style]}>
|
|
89
|
-
{background
|
|
90
|
-
|
|
93
|
+
{background}
|
|
94
|
+
{children != null ? (
|
|
95
|
+
<View style={StyleSheet.absoluteFill} pointerEvents="box-none">
|
|
96
|
+
{children}
|
|
97
|
+
</View>
|
|
91
98
|
) : null}
|
|
92
|
-
|
|
93
|
-
{children}
|
|
94
99
|
</View>
|
|
95
100
|
</MediaCardContext.Provider>
|
|
96
101
|
)
|
|
@@ -101,25 +106,28 @@ export function MediaCard({
|
|
|
101
106
|
// ----------------------------------------------------------------------------
|
|
102
107
|
|
|
103
108
|
/**
|
|
104
|
-
* Header
|
|
105
|
-
*
|
|
106
|
-
*
|
|
109
|
+
* Header overlay — pinned to the top of the card. Title content can wrap to
|
|
110
|
+
* any number of lines without affecting the footer's position; if it grows
|
|
111
|
+
* taller than the card, the card's `overflow: 'hidden'` clips it.
|
|
112
|
+
*
|
|
113
|
+
* Default `padding: 16` matches the Figma "title wrap" spec.
|
|
107
114
|
*/
|
|
108
115
|
export function Header({ children, style }: { children?: React.ReactNode; style?: StyleProp<ViewStyle> }) {
|
|
109
|
-
// NOTE: the previous `flex: 1` shorthand expanded on Yoga (Android) to
|
|
110
|
-
// `{ flexGrow: 1, flexShrink: 1, flexBasis: 0 }`. With `flexBasis: 0` the
|
|
111
|
-
// Header has *no intrinsic floor*, so when MediaCard is placed inside a
|
|
112
|
-
// height-unbounded parent — e.g. a Carousel slot whose contentContainer
|
|
113
|
-
// is `alignItems: 'flex-start'` — Yoga's first measurement pass sizes
|
|
114
|
-
// the Header at 0 and the card's overall height becomes non-deterministic.
|
|
115
|
-
// On native this manifests as the card "over-stretching" vertically (the
|
|
116
|
-
// same Yoga foot-gun we fixed in `CardCTA` rightWrap). Web hides it
|
|
117
|
-
// because browsers honor `min-height: auto` on flex items. Use explicit
|
|
118
|
-
// `flexGrow / flexShrink: 0 / flexBasis: 'auto'` so the Header is sized
|
|
119
|
-
// to its content as a floor and only grows to consume the extra space
|
|
120
|
-
// contributed by `MediaCard`'s `minHeight: 308`.
|
|
121
116
|
return (
|
|
122
|
-
<View
|
|
117
|
+
<View
|
|
118
|
+
style={[
|
|
119
|
+
{
|
|
120
|
+
position: 'absolute',
|
|
121
|
+
top: 0,
|
|
122
|
+
left: 0,
|
|
123
|
+
right: 0,
|
|
124
|
+
padding: 16,
|
|
125
|
+
zIndex: 1,
|
|
126
|
+
},
|
|
127
|
+
style,
|
|
128
|
+
]}
|
|
129
|
+
pointerEvents="box-none"
|
|
130
|
+
>
|
|
123
131
|
{children}
|
|
124
132
|
</View>
|
|
125
133
|
)
|
|
@@ -151,8 +159,23 @@ export function Title({ children, style, modes: propModes }: { children?: React.
|
|
|
151
159
|
}
|
|
152
160
|
|
|
153
161
|
/**
|
|
154
|
-
* Glass Footer
|
|
155
|
-
*
|
|
162
|
+
* Glass Footer — pinned to the bottom of the card, **always** on top of the
|
|
163
|
+
* Header (`zIndex: 2`).
|
|
164
|
+
*
|
|
165
|
+
* Glass implementation (April 2026 best practice for RN/Expo):
|
|
166
|
+
* - **iOS:** `expo-blur`'s `BlurView` renders a native `UIVisualEffectView`,
|
|
167
|
+
* so this is a real OS-level live blur of whatever's underneath. We pick
|
|
168
|
+
* `tint` from the Figma "Contrast Context" mode (`'dark'` / `'light'`)
|
|
169
|
+
* and a moderate intensity that matches the Figma `blur/minimal` token.
|
|
170
|
+
* - **Android:** the same `BlurView` with `experimentalBlurMethod="dimezisBlurView"`
|
|
171
|
+
* enables the hardware-accelerated `RenderEffect` blur on Android 12+.
|
|
172
|
+
* On older Android, expo-blur cleanly degrades to a tinted scrim — we
|
|
173
|
+
* layer a subtle noise/grain overlay on top so the surface still reads
|
|
174
|
+
* as "frosted glass" instead of a flat color.
|
|
175
|
+
* - **Web:** `BlurView` on web is implemented as `backdrop-filter: blur()`,
|
|
176
|
+
* which already worked in the previous version. Same component, same API.
|
|
177
|
+
*
|
|
178
|
+
* Tokens still drive the tint color, blur radius and inner spacing.
|
|
156
179
|
*/
|
|
157
180
|
export function Footer({ children, style, modes: propModes }: { children?: React.ReactNode; style?: StyleProp<ViewStyle>; modes?: Record<string, any> }) {
|
|
158
181
|
const context = useContext(MediaCardContext)
|
|
@@ -162,28 +185,77 @@ export function Footer({ children, style, modes: propModes }: { children?: React
|
|
|
162
185
|
const paddingHorizontal = parseFloat(getVariableByName('cardMedia/footer/padding/horizontal', modes) || '16')
|
|
163
186
|
const paddingVertical = parseFloat(getVariableByName('cardMedia/footer/padding/vertical', modes) || '12')
|
|
164
187
|
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
188
|
+
// Figma tokens:
|
|
189
|
+
// blur/minimal/background -> tint laid over the native blur
|
|
190
|
+
// blur/minimal -> blur radius (px). expo-blur takes a 0-100
|
|
191
|
+
// "intensity" instead of px; we map roughly:
|
|
192
|
+
// intensity ≈ clamp(radius * 1.7, 0, 100).
|
|
169
193
|
const glassBgColor = getVariableByName('blur/minimal/background', modes) || '#1414174a'
|
|
170
194
|
const blurRadius = parseFloat(getVariableByName('blur/minimal', modes) || '29')
|
|
195
|
+
const intensity = Math.max(0, Math.min(100, Math.round(blurRadius * 1.7)))
|
|
171
196
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
paddingHorizontal,
|
|
177
|
-
paddingVertical,
|
|
178
|
-
backgroundColor: glassBgColor,
|
|
179
|
-
// Web-specific backdrop filter for glass effect
|
|
180
|
-
// @ts-ignore
|
|
181
|
-
...(Platform.OS === 'web' ? { backdropFilter: `blur(${blurRadius}px)` } : {}),
|
|
182
|
-
}
|
|
197
|
+
// Pick the iOS/Android material tint from "Contrast Context" mode so the
|
|
198
|
+
// glass adapts to dark/light backgrounds the same way the Figma tokens do.
|
|
199
|
+
const contrast = (modes['Contrast Context'] || 'on dark') as string
|
|
200
|
+
const tint: BlurTint = contrast === 'on light' ? 'light' : 'dark'
|
|
183
201
|
|
|
184
202
|
return (
|
|
185
|
-
<View
|
|
186
|
-
{
|
|
203
|
+
<View
|
|
204
|
+
style={[
|
|
205
|
+
{
|
|
206
|
+
position: 'absolute',
|
|
207
|
+
left: 0,
|
|
208
|
+
right: 0,
|
|
209
|
+
bottom: 0,
|
|
210
|
+
overflow: 'hidden',
|
|
211
|
+
// zIndex 2 ensures Footer always paints above Header,
|
|
212
|
+
// regardless of which is rendered first in the tree.
|
|
213
|
+
zIndex: 2,
|
|
214
|
+
},
|
|
215
|
+
style,
|
|
216
|
+
]}
|
|
217
|
+
pointerEvents="box-none"
|
|
218
|
+
>
|
|
219
|
+
{/* Native live blur. On Android pre-12 expo-blur falls back to a
|
|
220
|
+
tinted scrim automatically; on web it's a backdrop-filter. */}
|
|
221
|
+
<BlurView
|
|
222
|
+
style={StyleSheet.absoluteFill}
|
|
223
|
+
tint={tint}
|
|
224
|
+
intensity={intensity}
|
|
225
|
+
experimentalBlurMethod="dimezisBlurView"
|
|
226
|
+
/>
|
|
227
|
+
|
|
228
|
+
{/* Token-driven tint laid on top of the live blur — keeps the
|
|
229
|
+
Figma color signature regardless of platform blur quality. */}
|
|
230
|
+
<View style={[StyleSheet.absoluteFill, { backgroundColor: glassBgColor }]} />
|
|
231
|
+
|
|
232
|
+
{/* Subtle noise/grain on Android only, to compensate for the
|
|
233
|
+
lower-fidelity blur — purely additive, no behavior change.
|
|
234
|
+
On iOS/web the native blur already has natural texture. */}
|
|
235
|
+
{Platform.OS === 'android' ? (
|
|
236
|
+
<View
|
|
237
|
+
style={[
|
|
238
|
+
StyleSheet.absoluteFill,
|
|
239
|
+
{
|
|
240
|
+
backgroundColor: 'rgba(255,255,255,0.03)',
|
|
241
|
+
opacity: 0.6,
|
|
242
|
+
},
|
|
243
|
+
]}
|
|
244
|
+
pointerEvents="none"
|
|
245
|
+
/>
|
|
246
|
+
) : null}
|
|
247
|
+
|
|
248
|
+
<View
|
|
249
|
+
style={{
|
|
250
|
+
flexDirection: 'row',
|
|
251
|
+
alignItems: 'center',
|
|
252
|
+
gap,
|
|
253
|
+
paddingHorizontal,
|
|
254
|
+
paddingVertical,
|
|
255
|
+
}}
|
|
256
|
+
>
|
|
257
|
+
{children}
|
|
258
|
+
</View>
|
|
187
259
|
</View>
|
|
188
260
|
)
|
|
189
261
|
}
|
package/src/icons/registry.ts
CHANGED