jfs-components 0.0.66 → 0.0.68
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 +20 -0
- package/lib/commonjs/components/CardCTA/CardCTA.js +16 -4
- package/lib/commonjs/components/Image/Image.js +85 -0
- package/lib/commonjs/components/MediaCard/MediaCard.js +46 -29
- package/lib/commonjs/components/index.js +7 -0
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/CardCTA/CardCTA.js +16 -4
- package/lib/module/components/Image/Image.js +80 -0
- package/lib/module/components/MediaCard/MediaCard.js +45 -31
- package/lib/module/components/index.js +1 -0
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/Image/Image.d.ts +45 -0
- package/lib/typescript/src/components/MediaCard/MediaCard.d.ts +22 -4
- package/lib/typescript/src/components/index.d.ts +2 -1
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/CardCTA/CardCTA.tsx +15 -4
- package/src/components/Image/Image.tsx +131 -0
- package/src/components/MediaCard/MediaCard.tsx +116 -85
- package/src/components/index.ts +2 -1
- package/src/icons/registry.ts +1 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type ImageSourcePropType, type ImageStyle, type StyleProp, type ViewStyle, type ImageResizeMode } from 'react-native';
|
|
3
|
+
export type ImageProps = {
|
|
4
|
+
/**
|
|
5
|
+
* Image source. Accepts the same shapes as React Native's `<Image>` plus a
|
|
6
|
+
* raw URL string. Naming is intentionally aligned with `Avatar`,
|
|
7
|
+
* `ProductLabel`, `CardProviderInfo`, etc. so the library has a single,
|
|
8
|
+
* predictable image-source prop name.
|
|
9
|
+
*/
|
|
10
|
+
imageSource?: ImageSourcePropType | string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Width-to-height aspect ratio as a number, e.g. `16 / 9`, `1`, `4 / 3`.
|
|
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.
|
|
19
|
+
*
|
|
20
|
+
* Equivalent CSS: `aspect-ratio: <ratio>`.
|
|
21
|
+
*/
|
|
22
|
+
ratio?: number | undefined;
|
|
23
|
+
/** How the image is fit inside its box. Defaults to `'cover'`. */
|
|
24
|
+
resizeMode?: ImageResizeMode | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Optional explicit width. If omitted and a `ratio` is set, the image
|
|
27
|
+
* defaults to `width: '100%'` so it fills its parent.
|
|
28
|
+
*/
|
|
29
|
+
width?: ViewStyle['width'] | undefined;
|
|
30
|
+
/** Optional explicit height. Usually you only need `ratio` instead. */
|
|
31
|
+
height?: ViewStyle['height'] | undefined;
|
|
32
|
+
/** Border radius applied to the image (clips the underlying raster). */
|
|
33
|
+
borderRadius?: number | undefined;
|
|
34
|
+
/** Style merged onto the underlying RN `<Image>`. */
|
|
35
|
+
style?: StyleProp<ImageStyle> | undefined;
|
|
36
|
+
/** Accessibility label forwarded to RN `<Image>`. */
|
|
37
|
+
accessibilityLabel?: string | undefined;
|
|
38
|
+
/** Hide from the a11y tree (e.g. when image is purely decorative). */
|
|
39
|
+
accessibilityElementsHidden?: boolean | undefined;
|
|
40
|
+
importantForAccessibility?: 'auto' | 'yes' | 'no' | 'no-hide-descendants' | undefined;
|
|
41
|
+
};
|
|
42
|
+
declare function Image({ imageSource, ratio, resizeMode, width, height, borderRadius, style, accessibilityLabel, accessibilityElementsHidden, importantForAccessibility, }: ImageProps): import("react/jsx-runtime").JSX.Element;
|
|
43
|
+
declare const _default: React.MemoExoticComponent<typeof Image>;
|
|
44
|
+
export default _default;
|
|
45
|
+
//# sourceMappingURL=Image.d.ts.map
|
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { type ViewStyle, type TextStyle, type StyleProp } from 'react-native';
|
|
2
|
+
import { type ViewStyle, type TextStyle, type StyleProp, type ImageSourcePropType } from 'react-native';
|
|
3
3
|
export interface MediaCardProps {
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Image source for the background media. Same shape as the rest of the
|
|
6
|
+
* library (`Avatar`, `ProductLabel`, etc.) — accepts a URL string or any
|
|
7
|
+
* RN `ImageSourcePropType`. The card renders this through the shared
|
|
8
|
+
* `<Image>` component, so all image-rendering details (normalization,
|
|
9
|
+
* resize behaviour, `aspectRatio`) live there, not here.
|
|
10
|
+
*/
|
|
11
|
+
imageSource?: ImageSourcePropType | string | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Width-to-height aspect ratio for the background image when using
|
|
14
|
+
* `imageSource`, e.g. `16 / 9`. Forwarded to `<Image ratio>`.
|
|
15
|
+
*/
|
|
16
|
+
ratio?: number | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Escape hatch: a fully custom background node (e.g. a gradient view, a
|
|
19
|
+
* video). Takes precedence over `imageSource`. Prefer `imageSource` for
|
|
20
|
+
* the common case of a single background image.
|
|
7
21
|
*/
|
|
8
22
|
media?: React.ReactNode;
|
|
9
23
|
/**
|
|
@@ -23,8 +37,12 @@ export interface MediaCardProps {
|
|
|
23
37
|
* MediaCard component implementation from Figma node 1241:4140.
|
|
24
38
|
*
|
|
25
39
|
* Features a background media slot, a large title, and a glass-morphism footer.
|
|
40
|
+
*
|
|
41
|
+
* The background can be supplied either as `imageSource` (preferred — uses
|
|
42
|
+
* the shared `<Image>` primitive under the hood) or as a custom `media` node
|
|
43
|
+
* for non-image backgrounds.
|
|
26
44
|
*/
|
|
27
|
-
export declare function MediaCard({ media, children, modes, style, }: MediaCardProps): import("react/jsx-runtime").JSX.Element;
|
|
45
|
+
export declare function MediaCard({ imageSource, ratio, media, children, modes, style, }: MediaCardProps): import("react/jsx-runtime").JSX.Element;
|
|
28
46
|
export declare namespace MediaCard {
|
|
29
47
|
var Header: typeof import("./MediaCard").Header;
|
|
30
48
|
var Title: typeof import("./MediaCard").Title;
|
|
@@ -24,11 +24,12 @@ export { default as HoldingsCard, type HoldingsCardProps } from './HoldingsCard/
|
|
|
24
24
|
export { default as HStack, type HStackProps } from './HStack/HStack';
|
|
25
25
|
export { default as IconButton } from './IconButton/IconButton';
|
|
26
26
|
export { default as IconCapsule } from './IconCapsule/IconCapsule';
|
|
27
|
+
export { default as Image, type ImageProps } from './Image/Image';
|
|
27
28
|
export { default as LazyList } from './LazyList/LazyList';
|
|
28
29
|
export { default as LinearMeter, type LinearMeterProps } from './LinearMeter/LinearMeter';
|
|
29
30
|
export { default as ListGroup } from './ListGroup/ListGroup';
|
|
30
31
|
export { default as ListItem } from './ListItem/ListItem';
|
|
31
|
-
export { default as MediaCard } from './MediaCard/MediaCard';
|
|
32
|
+
export { default as MediaCard, type MediaCardProps } from './MediaCard/MediaCard';
|
|
32
33
|
export { default as MerchantProfile, type MerchantProfileProps } from './MerchantProfile/MerchantProfile';
|
|
33
34
|
export { default as MoneyValue } from './MoneyValue/MoneyValue';
|
|
34
35
|
export { default as NoteInput, type NoteInputProps } from './NoteInput/NoteInput';
|
|
@@ -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:06:55.739Z
|
|
8
8
|
*/
|
|
9
9
|
export declare const iconRegistry: Record<string, {
|
|
10
10
|
path: string;
|
package/package.json
CHANGED
|
@@ -95,11 +95,22 @@ function CardCTA({
|
|
|
95
95
|
justifyContent: 'center',
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// NOTE: rightWrap must NOT shrink on native. On Android (Yoga), the default
|
|
99
|
+
// `flex: 2` shorthand expands to `{ flexGrow: 2, flexShrink: 1, flexBasis: 0 }`,
|
|
100
|
+
// which — combined with `minWidth: 0` — lets Yoga shrink this wrapper below
|
|
101
|
+
// its own padding+IconCapsule width when leftWrap's text is long. Once that
|
|
102
|
+
// happens the horizontal padding collapses, `alignItems: 'flex-end'` pins
|
|
103
|
+
// the IconCapsule against the inner edge, and the icon ends up visually
|
|
104
|
+
// touching the body text (the wrapper "appears not to exist"). Web hides
|
|
105
|
+
// this because browsers honor `min-width: auto` on flex items. Use
|
|
106
|
+
// explicit `flexGrow`/`flexShrink: 0`/`flexBasis: 'auto'` so the wrapper
|
|
107
|
+
// is sized to its content as a floor and only grows for the design's
|
|
108
|
+
// 3:2 ratio when extra space is available. leftWrap already absorbs tight
|
|
109
|
+
// space via `flexShrink: 1` + `minWidth: 0`.
|
|
98
110
|
const rightWrapStyle: ViewStyle = {
|
|
99
|
-
|
|
100
|
-
flexShrink:
|
|
101
|
-
flexBasis:
|
|
102
|
-
minWidth: 0,
|
|
111
|
+
flexGrow: 2,
|
|
112
|
+
flexShrink: 0,
|
|
113
|
+
flexBasis: 'auto',
|
|
103
114
|
paddingHorizontal: rightPaddingH,
|
|
104
115
|
paddingVertical: rightPaddingV,
|
|
105
116
|
alignItems: 'flex-end',
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Image as RNImage,
|
|
4
|
+
View,
|
|
5
|
+
type ImageSourcePropType,
|
|
6
|
+
type ImageStyle,
|
|
7
|
+
type StyleProp,
|
|
8
|
+
type ViewStyle,
|
|
9
|
+
type ImageResizeMode,
|
|
10
|
+
} from 'react-native'
|
|
11
|
+
|
|
12
|
+
export type ImageProps = {
|
|
13
|
+
/**
|
|
14
|
+
* Image source. Accepts the same shapes as React Native's `<Image>` plus a
|
|
15
|
+
* raw URL string. Naming is intentionally aligned with `Avatar`,
|
|
16
|
+
* `ProductLabel`, `CardProviderInfo`, etc. so the library has a single,
|
|
17
|
+
* predictable image-source prop name.
|
|
18
|
+
*/
|
|
19
|
+
imageSource?: ImageSourcePropType | string | undefined
|
|
20
|
+
/**
|
|
21
|
+
* Width-to-height aspect ratio as a number, e.g. `16 / 9`, `1`, `4 / 3`.
|
|
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.
|
|
28
|
+
*
|
|
29
|
+
* Equivalent CSS: `aspect-ratio: <ratio>`.
|
|
30
|
+
*/
|
|
31
|
+
ratio?: number | undefined
|
|
32
|
+
/** How the image is fit inside its box. Defaults to `'cover'`. */
|
|
33
|
+
resizeMode?: ImageResizeMode | undefined
|
|
34
|
+
/**
|
|
35
|
+
* Optional explicit width. If omitted and a `ratio` is set, the image
|
|
36
|
+
* defaults to `width: '100%'` so it fills its parent.
|
|
37
|
+
*/
|
|
38
|
+
width?: ViewStyle['width'] | undefined
|
|
39
|
+
/** Optional explicit height. Usually you only need `ratio` instead. */
|
|
40
|
+
height?: ViewStyle['height'] | undefined
|
|
41
|
+
/** Border radius applied to the image (clips the underlying raster). */
|
|
42
|
+
borderRadius?: number | undefined
|
|
43
|
+
/** Style merged onto the underlying RN `<Image>`. */
|
|
44
|
+
style?: StyleProp<ImageStyle> | undefined
|
|
45
|
+
/** Accessibility label forwarded to RN `<Image>`. */
|
|
46
|
+
accessibilityLabel?: string | undefined
|
|
47
|
+
/** Hide from the a11y tree (e.g. when image is purely decorative). */
|
|
48
|
+
accessibilityElementsHidden?: boolean | undefined
|
|
49
|
+
importantForAccessibility?:
|
|
50
|
+
| 'auto'
|
|
51
|
+
| 'yes'
|
|
52
|
+
| 'no'
|
|
53
|
+
| 'no-hide-descendants'
|
|
54
|
+
| undefined
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeSource(
|
|
58
|
+
imageSource: ImageProps['imageSource']
|
|
59
|
+
): ImageSourcePropType | undefined {
|
|
60
|
+
if (imageSource == null) return undefined
|
|
61
|
+
if (typeof imageSource === 'string') return { uri: imageSource }
|
|
62
|
+
return imageSource
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* `Image` — the library's standard raster image primitive.
|
|
67
|
+
*
|
|
68
|
+
* Why this exists:
|
|
69
|
+
* - Gives consumers a single component for "show an image at a given aspect
|
|
70
|
+
* ratio inside a flex container" without having to remember the
|
|
71
|
+
* `width:'100%' / height:'100%' / resizeMode:'cover'` boilerplate.
|
|
72
|
+
* - Centralizes URL-vs-`{uri}` normalization that several components were
|
|
73
|
+
* re-implementing.
|
|
74
|
+
* - Uses the same `imageSource` prop name as the rest of the library
|
|
75
|
+
* (`Avatar`, `ProductLabel`, `CardProviderInfo`, ...) for a unified API.
|
|
76
|
+
*
|
|
77
|
+
* Layout rules:
|
|
78
|
+
* - If `ratio` is provided, the image lays out with `aspectRatio: ratio`
|
|
79
|
+
* and (unless `width` is given) fills the parent's width.
|
|
80
|
+
* - If neither `ratio` nor explicit dimensions are given, the image fills
|
|
81
|
+
* its parent (`width: '100%'`, `height: '100%'`) — same default as the
|
|
82
|
+
* most common usage in this library (background media, hero images).
|
|
83
|
+
*/
|
|
84
|
+
const DEFAULT_RATIO = 16 / 9
|
|
85
|
+
|
|
86
|
+
function Image({
|
|
87
|
+
imageSource,
|
|
88
|
+
ratio = DEFAULT_RATIO,
|
|
89
|
+
resizeMode = 'cover',
|
|
90
|
+
width,
|
|
91
|
+
height,
|
|
92
|
+
borderRadius,
|
|
93
|
+
style,
|
|
94
|
+
accessibilityLabel,
|
|
95
|
+
accessibilityElementsHidden,
|
|
96
|
+
importantForAccessibility,
|
|
97
|
+
}: ImageProps) {
|
|
98
|
+
const source = useMemo(() => normalizeSource(imageSource), [imageSource])
|
|
99
|
+
|
|
100
|
+
const layoutStyle: ImageStyle = useMemo(() => {
|
|
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 } : {}) }),
|
|
110
|
+
}
|
|
111
|
+
if (borderRadius != null) s.borderRadius = borderRadius
|
|
112
|
+
return s
|
|
113
|
+
}, [ratio, width, height, borderRadius])
|
|
114
|
+
|
|
115
|
+
if (!source) {
|
|
116
|
+
return <View style={[layoutStyle, style as StyleProp<ViewStyle>]} />
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<RNImage
|
|
121
|
+
source={source}
|
|
122
|
+
style={[layoutStyle, style]}
|
|
123
|
+
resizeMode={resizeMode}
|
|
124
|
+
accessibilityLabel={accessibilityLabel}
|
|
125
|
+
accessibilityElementsHidden={accessibilityElementsHidden}
|
|
126
|
+
importantForAccessibility={importantForAccessibility}
|
|
127
|
+
/>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export default React.memo(Image)
|
|
@@ -1,81 +1,102 @@
|
|
|
1
|
-
import React, { createContext, useContext
|
|
2
|
-
import { View, Text, StyleSheet, type ViewStyle, type TextStyle, type StyleProp,
|
|
3
|
-
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import { EMPTY_MODES } from '../../utils/react-utils';
|
|
1
|
+
import React, { createContext, useContext } from 'react'
|
|
2
|
+
import { View, Text, StyleSheet, type ViewStyle, type TextStyle, type StyleProp, type ImageSourcePropType, Platform } from 'react-native'
|
|
3
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
4
|
+
import Image from '../Image/Image'
|
|
5
|
+
import { EMPTY_MODES } from '../../utils/react-utils'
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
* Context to share 'modes' with child components.
|
|
10
|
-
*/
|
|
11
|
-
const MediaCardContext = createContext<{ modes?: Record<string, any> }>({});
|
|
7
|
+
const MediaCardContext = createContext<{ modes?: Record<string, any> }>({})
|
|
12
8
|
|
|
13
9
|
export interface MediaCardProps {
|
|
14
10
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* Image source for the background media. Same shape as the rest of the
|
|
12
|
+
* library (`Avatar`, `ProductLabel`, etc.) — accepts a URL string or any
|
|
13
|
+
* RN `ImageSourcePropType`. The card renders this through the shared
|
|
14
|
+
* `<Image>` component, so all image-rendering details (normalization,
|
|
15
|
+
* resize behaviour, `aspectRatio`) live there, not here.
|
|
17
16
|
*/
|
|
18
|
-
|
|
17
|
+
imageSource?: ImageSourcePropType | string | undefined
|
|
18
|
+
/**
|
|
19
|
+
* Width-to-height aspect ratio for the background image when using
|
|
20
|
+
* `imageSource`, e.g. `16 / 9`. Forwarded to `<Image ratio>`.
|
|
21
|
+
*/
|
|
22
|
+
ratio?: number | undefined
|
|
23
|
+
/**
|
|
24
|
+
* Escape hatch: a fully custom background node (e.g. a gradient view, a
|
|
25
|
+
* video). Takes precedence over `imageSource`. Prefer `imageSource` for
|
|
26
|
+
* the common case of a single background image.
|
|
27
|
+
*/
|
|
28
|
+
media?: React.ReactNode
|
|
19
29
|
/**
|
|
20
30
|
* Content to render inside the card (e.g. MediaCard.Title, MediaCard.Footer).
|
|
21
31
|
*/
|
|
22
|
-
children?: React.ReactNode
|
|
32
|
+
children?: React.ReactNode
|
|
23
33
|
/**
|
|
24
34
|
* Modes object for token resolution.
|
|
25
35
|
*/
|
|
26
|
-
modes?: Record<string, any
|
|
36
|
+
modes?: Record<string, any>
|
|
27
37
|
/**
|
|
28
38
|
* Style overrides for the card container.
|
|
29
39
|
*/
|
|
30
|
-
style?: StyleProp<ViewStyle
|
|
40
|
+
style?: StyleProp<ViewStyle>
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
/**
|
|
34
44
|
* MediaCard component implementation from Figma node 1241:4140.
|
|
35
|
-
*
|
|
45
|
+
*
|
|
36
46
|
* Features a background media slot, a large title, and a glass-morphism footer.
|
|
47
|
+
*
|
|
48
|
+
* The background can be supplied either as `imageSource` (preferred — uses
|
|
49
|
+
* the shared `<Image>` primitive under the hood) or as a custom `media` node
|
|
50
|
+
* for non-image backgrounds.
|
|
37
51
|
*/
|
|
38
52
|
export function MediaCard({
|
|
53
|
+
imageSource,
|
|
54
|
+
ratio,
|
|
39
55
|
media,
|
|
40
56
|
children,
|
|
41
57
|
modes = EMPTY_MODES,
|
|
42
58
|
style,
|
|
43
59
|
}: MediaCardProps) {
|
|
44
|
-
|
|
45
|
-
const radius = parseFloat(getVariableByName('cardMedia/radius', modes) || '24');
|
|
46
|
-
const gap = parseFloat(getVariableByName('cardMedia/gap', modes) || '0');
|
|
47
|
-
// Dimensions from Figma: w=369, h=308. We can make it flexible or default to these?
|
|
48
|
-
// Usually components should be flexible, but stories will constrain them.
|
|
49
|
-
// Figma context shows fixed/hug behavior. Let's start with flex container.
|
|
60
|
+
const radius = parseFloat(getVariableByName('cardMedia/radius', modes) || '24')
|
|
50
61
|
|
|
62
|
+
// No magic minHeight, no aspectRatio on the container. The card simply
|
|
63
|
+
// hugs whatever the background renders at: the <Image> sits in normal
|
|
64
|
+
// flow with `aspectRatio: ratio`, so its rendered height becomes the
|
|
65
|
+
// card's height. Header and Footer are absolutely positioned overlays
|
|
66
|
+
// and don't contribute to layout.
|
|
51
67
|
const containerStyle: ViewStyle = {
|
|
52
68
|
borderRadius: radius,
|
|
53
|
-
gap,
|
|
54
69
|
overflow: 'hidden',
|
|
55
70
|
position: 'relative',
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// `media` wins as an escape hatch (gradient/video/etc.). Otherwise we
|
|
74
|
+
// delegate to the shared <Image> for image-source backgrounds. The
|
|
75
|
+
// background renders in normal flow so its height drives the card.
|
|
76
|
+
const background = media ?? (
|
|
77
|
+
imageSource != null ? (
|
|
78
|
+
<Image
|
|
79
|
+
imageSource={imageSource}
|
|
80
|
+
ratio={ratio}
|
|
81
|
+
resizeMode="cover"
|
|
82
|
+
accessibilityElementsHidden
|
|
83
|
+
importantForAccessibility="no"
|
|
84
|
+
/>
|
|
85
|
+
) : null
|
|
86
|
+
)
|
|
65
87
|
|
|
66
88
|
return (
|
|
67
89
|
<MediaCardContext.Provider value={{ modes }}>
|
|
68
90
|
<View style={[containerStyle, style]}>
|
|
69
|
-
{
|
|
70
|
-
|
|
71
|
-
{
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
{children}
|
|
91
|
+
{background}
|
|
92
|
+
{children != null ? (
|
|
93
|
+
<View style={StyleSheet.absoluteFill} pointerEvents="box-none">
|
|
94
|
+
{children}
|
|
95
|
+
</View>
|
|
96
|
+
) : null}
|
|
76
97
|
</View>
|
|
77
98
|
</MediaCardContext.Provider>
|
|
78
|
-
)
|
|
99
|
+
)
|
|
79
100
|
}
|
|
80
101
|
|
|
81
102
|
// ----------------------------------------------------------------------------
|
|
@@ -88,11 +109,23 @@ export function MediaCard({
|
|
|
88
109
|
* Figma: "title wrap" p-[16px]
|
|
89
110
|
*/
|
|
90
111
|
export function Header({ children, style }: { children?: React.ReactNode; style?: StyleProp<ViewStyle> }) {
|
|
112
|
+
// NOTE: the previous `flex: 1` shorthand expanded on Yoga (Android) to
|
|
113
|
+
// `{ flexGrow: 1, flexShrink: 1, flexBasis: 0 }`. With `flexBasis: 0` the
|
|
114
|
+
// Header has *no intrinsic floor*, so when MediaCard is placed inside a
|
|
115
|
+
// height-unbounded parent — e.g. a Carousel slot whose contentContainer
|
|
116
|
+
// is `alignItems: 'flex-start'` — Yoga's first measurement pass sizes
|
|
117
|
+
// the Header at 0 and the card's overall height becomes non-deterministic.
|
|
118
|
+
// On native this manifests as the card "over-stretching" vertically (the
|
|
119
|
+
// same Yoga foot-gun we fixed in `CardCTA` rightWrap). Web hides it
|
|
120
|
+
// because browsers honor `min-height: auto` on flex items. Use explicit
|
|
121
|
+
// `flexGrow / flexShrink: 0 / flexBasis: 'auto'` so the Header is sized
|
|
122
|
+
// to its content as a floor and only grows to consume the extra space
|
|
123
|
+
// contributed by `MediaCard`'s `minHeight: 308`.
|
|
91
124
|
return (
|
|
92
|
-
<View style={[{ padding: 16,
|
|
125
|
+
<View style={[{ padding: 16, flexGrow: 1, flexShrink: 0, flexBasis: 'auto' }, style]}>
|
|
93
126
|
{children}
|
|
94
127
|
</View>
|
|
95
|
-
)
|
|
128
|
+
)
|
|
96
129
|
}
|
|
97
130
|
|
|
98
131
|
/**
|
|
@@ -100,14 +133,14 @@ export function Header({ children, style }: { children?: React.ReactNode; style?
|
|
|
100
133
|
* Tokens: cardMedia/title/*
|
|
101
134
|
*/
|
|
102
135
|
export function Title({ children, style, modes: propModes }: { children?: React.ReactNode; style?: StyleProp<TextStyle>; modes?: Record<string, any> }) {
|
|
103
|
-
const context = useContext(MediaCardContext)
|
|
104
|
-
const modes = propModes || context.modes || {}
|
|
136
|
+
const context = useContext(MediaCardContext)
|
|
137
|
+
const modes = propModes || context.modes || {}
|
|
105
138
|
|
|
106
|
-
const color = getVariableByName('cardMedia/title/color', modes) || '#ffffff'
|
|
107
|
-
const fontSize = parseFloat(getVariableByName('cardMedia/title/fontSize', modes) || '52')
|
|
108
|
-
const fontFamily = getVariableByName('cardMedia/title/fontFamily', modes) || 'JioType Var'
|
|
109
|
-
const lineHeight = parseFloat(getVariableByName('cardMedia/title/lineHeight', modes) || '68')
|
|
110
|
-
const fontWeight = getVariableByName('cardMedia/title/fontWeight', modes) || '900'
|
|
139
|
+
const color = getVariableByName('cardMedia/title/color', modes) || '#ffffff'
|
|
140
|
+
const fontSize = parseFloat(getVariableByName('cardMedia/title/fontSize', modes) || '52')
|
|
141
|
+
const fontFamily = getVariableByName('cardMedia/title/fontFamily', modes) || 'JioType Var'
|
|
142
|
+
const lineHeight = parseFloat(getVariableByName('cardMedia/title/lineHeight', modes) || '68')
|
|
143
|
+
const fontWeight = getVariableByName('cardMedia/title/fontWeight', modes) || '900'
|
|
111
144
|
|
|
112
145
|
const textStyle: TextStyle = {
|
|
113
146
|
color,
|
|
@@ -115,9 +148,9 @@ export function Title({ children, style, modes: propModes }: { children?: React.
|
|
|
115
148
|
fontFamily,
|
|
116
149
|
lineHeight,
|
|
117
150
|
fontWeight: fontWeight as TextStyle['fontWeight'],
|
|
118
|
-
}
|
|
151
|
+
}
|
|
119
152
|
|
|
120
|
-
return <Text style={[textStyle, style]}>{children}</Text
|
|
153
|
+
return <Text style={[textStyle, style]}>{children}</Text>
|
|
121
154
|
}
|
|
122
155
|
|
|
123
156
|
/**
|
|
@@ -125,20 +158,19 @@ export function Title({ children, style, modes: propModes }: { children?: React.
|
|
|
125
158
|
* Tokens: cardMedia/footer/*, glass/minimal, blur/minimal
|
|
126
159
|
*/
|
|
127
160
|
export function Footer({ children, style, modes: propModes }: { children?: React.ReactNode; style?: StyleProp<ViewStyle>; modes?: Record<string, any> }) {
|
|
128
|
-
const context = useContext(MediaCardContext)
|
|
129
|
-
const modes = propModes || context.modes || {}
|
|
161
|
+
const context = useContext(MediaCardContext)
|
|
162
|
+
const modes = propModes || context.modes || {}
|
|
130
163
|
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
const paddingVertical = parseFloat(getVariableByName('cardMedia/footer/padding/vertical', modes) || '12');
|
|
164
|
+
const gap = parseFloat(getVariableByName('cardMedia/footer/gap', modes) || '24')
|
|
165
|
+
const paddingHorizontal = parseFloat(getVariableByName('cardMedia/footer/padding/horizontal', modes) || '16')
|
|
166
|
+
const paddingVertical = parseFloat(getVariableByName('cardMedia/footer/padding/vertical', modes) || '12')
|
|
135
167
|
|
|
136
168
|
// Glass Effect
|
|
137
169
|
// Figma:
|
|
138
170
|
// blur/minimal/background: "#1414174a"
|
|
139
171
|
// blur/minimal: 29
|
|
140
|
-
const glassBgColor = getVariableByName('blur/minimal/background', modes) || '#1414174a'
|
|
141
|
-
const blurRadius = parseFloat(getVariableByName('blur/minimal', modes) || '29')
|
|
172
|
+
const glassBgColor = getVariableByName('blur/minimal/background', modes) || '#1414174a'
|
|
173
|
+
const blurRadius = parseFloat(getVariableByName('blur/minimal', modes) || '29')
|
|
142
174
|
|
|
143
175
|
const containerStyle: ViewStyle = {
|
|
144
176
|
flexDirection: 'row',
|
|
@@ -150,13 +182,13 @@ export function Footer({ children, style, modes: propModes }: { children?: React
|
|
|
150
182
|
// Web-specific backdrop filter for glass effect
|
|
151
183
|
// @ts-ignore
|
|
152
184
|
...(Platform.OS === 'web' ? { backdropFilter: `blur(${blurRadius}px)` } : {}),
|
|
153
|
-
}
|
|
185
|
+
}
|
|
154
186
|
|
|
155
187
|
return (
|
|
156
188
|
<View style={[containerStyle, style]}>
|
|
157
189
|
{children}
|
|
158
190
|
</View>
|
|
159
|
-
)
|
|
191
|
+
)
|
|
160
192
|
}
|
|
161
193
|
|
|
162
194
|
/**
|
|
@@ -164,20 +196,20 @@ export function Footer({ children, style, modes: propModes }: { children?: React
|
|
|
164
196
|
* Tokens: cardMedia/footer/title/*
|
|
165
197
|
*/
|
|
166
198
|
export function FooterTitle({ children, style, modes: propModes }: { children?: React.ReactNode; style?: StyleProp<TextStyle>; modes?: Record<string, any> }) {
|
|
167
|
-
const context = useContext(MediaCardContext)
|
|
168
|
-
const modes = propModes || context.modes || {}
|
|
199
|
+
const context = useContext(MediaCardContext)
|
|
200
|
+
const modes = propModes || context.modes || {}
|
|
169
201
|
|
|
170
|
-
const color = getVariableByName('cardMedia/footer/title/color', modes) || '#ffffff'
|
|
171
|
-
const fontSize = parseFloat(getVariableByName('cardMedia/footer/title/fontSize', modes) || '14')
|
|
172
|
-
const fontFamily = getVariableByName('cardMedia/footer/title/fontFamily', modes) || 'JioType Var'
|
|
173
|
-
const lineHeight = parseFloat(getVariableByName('cardMedia/footer/title/lineHeight', modes) || '16')
|
|
174
|
-
const fontWeight = getVariableByName('cardMedia/footer/title/fontWeight', modes) || '800'
|
|
202
|
+
const color = getVariableByName('cardMedia/footer/title/color', modes) || '#ffffff'
|
|
203
|
+
const fontSize = parseFloat(getVariableByName('cardMedia/footer/title/fontSize', modes) || '14')
|
|
204
|
+
const fontFamily = getVariableByName('cardMedia/footer/title/fontFamily', modes) || 'JioType Var'
|
|
205
|
+
const lineHeight = parseFloat(getVariableByName('cardMedia/footer/title/lineHeight', modes) || '16')
|
|
206
|
+
const fontWeight = getVariableByName('cardMedia/footer/title/fontWeight', modes) || '800'
|
|
175
207
|
|
|
176
208
|
return (
|
|
177
209
|
<Text style={[{ color, fontSize, fontFamily, lineHeight, fontWeight: fontWeight as any }, style]}>
|
|
178
210
|
{children}
|
|
179
211
|
</Text>
|
|
180
|
-
)
|
|
212
|
+
)
|
|
181
213
|
}
|
|
182
214
|
|
|
183
215
|
/**
|
|
@@ -185,27 +217,26 @@ export function FooterTitle({ children, style, modes: propModes }: { children?:
|
|
|
185
217
|
* Tokens: cardMedia/footer/subtitle/*
|
|
186
218
|
*/
|
|
187
219
|
export function FooterSubtitle({ children, style, modes: propModes }: { children?: React.ReactNode; style?: StyleProp<TextStyle>; modes?: Record<string, any> }) {
|
|
188
|
-
const context = useContext(MediaCardContext)
|
|
189
|
-
const modes = propModes || context.modes || {}
|
|
220
|
+
const context = useContext(MediaCardContext)
|
|
221
|
+
const modes = propModes || context.modes || {}
|
|
190
222
|
|
|
191
|
-
const color = getVariableByName('cardMedia/footer/subtitle/color', modes) || '#f5f7f7a1'
|
|
192
|
-
const fontSize = parseFloat(getVariableByName('cardMedia/footer/subtitle/fontSize', modes) || '12')
|
|
193
|
-
const fontFamily = getVariableByName('cardMedia/footer/subtitle/fontFamily', modes) || 'JioType Var'
|
|
194
|
-
const lineHeight = parseFloat(getVariableByName('cardMedia/footer/subtitle/lineHeight', modes) || '14')
|
|
195
|
-
const fontWeight = getVariableByName('cardMedia/footer/subtitle/fontWeight', modes) || '400'
|
|
223
|
+
const color = getVariableByName('cardMedia/footer/subtitle/color', modes) || '#f5f7f7a1'
|
|
224
|
+
const fontSize = parseFloat(getVariableByName('cardMedia/footer/subtitle/fontSize', modes) || '12')
|
|
225
|
+
const fontFamily = getVariableByName('cardMedia/footer/subtitle/fontFamily', modes) || 'JioType Var'
|
|
226
|
+
const lineHeight = parseFloat(getVariableByName('cardMedia/footer/subtitle/lineHeight', modes) || '14')
|
|
227
|
+
const fontWeight = getVariableByName('cardMedia/footer/subtitle/fontWeight', modes) || '400'
|
|
196
228
|
|
|
197
229
|
return (
|
|
198
230
|
<Text style={[{ color, fontSize, fontFamily, lineHeight, fontWeight: fontWeight as any }, style]}>
|
|
199
231
|
{children}
|
|
200
232
|
</Text>
|
|
201
|
-
)
|
|
233
|
+
)
|
|
202
234
|
}
|
|
203
235
|
|
|
204
|
-
|
|
205
|
-
MediaCard.
|
|
206
|
-
MediaCard.
|
|
207
|
-
MediaCard.
|
|
208
|
-
MediaCard.
|
|
209
|
-
MediaCard.FooterSubtitle = FooterSubtitle;
|
|
236
|
+
MediaCard.Header = Header
|
|
237
|
+
MediaCard.Title = Title
|
|
238
|
+
MediaCard.Footer = Footer
|
|
239
|
+
MediaCard.FooterTitle = FooterTitle
|
|
240
|
+
MediaCard.FooterSubtitle = FooterSubtitle
|
|
210
241
|
|
|
211
|
-
export default MediaCard
|
|
242
|
+
export default MediaCard
|
package/src/components/index.ts
CHANGED
|
@@ -24,11 +24,12 @@ export { default as HoldingsCard, type HoldingsCardProps } from './HoldingsCard/
|
|
|
24
24
|
export { default as HStack, type HStackProps } from './HStack/HStack';
|
|
25
25
|
export { default as IconButton } from './IconButton/IconButton';
|
|
26
26
|
export { default as IconCapsule } from './IconCapsule/IconCapsule';
|
|
27
|
+
export { default as Image, type ImageProps } from './Image/Image';
|
|
27
28
|
export { default as LazyList } from './LazyList/LazyList';
|
|
28
29
|
export { default as LinearMeter, type LinearMeterProps } from './LinearMeter/LinearMeter';
|
|
29
30
|
export { default as ListGroup } from './ListGroup/ListGroup';
|
|
30
31
|
export { default as ListItem } from './ListItem/ListItem';
|
|
31
|
-
export { default as MediaCard } from './MediaCard/MediaCard';
|
|
32
|
+
export { default as MediaCard, type MediaCardProps } from './MediaCard/MediaCard';
|
|
32
33
|
export { default as MerchantProfile, type MerchantProfileProps } from './MerchantProfile/MerchantProfile';
|
|
33
34
|
export { default as MoneyValue } from './MoneyValue/MoneyValue';
|
|
34
35
|
export { default as NoteInput, type NoteInputProps } from './NoteInput/NoteInput';
|
package/src/icons/registry.ts
CHANGED