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
|
@@ -73,11 +73,23 @@ function CardCTA({
|
|
|
73
73
|
alignItems: 'flex-start',
|
|
74
74
|
justifyContent: 'center'
|
|
75
75
|
};
|
|
76
|
+
|
|
77
|
+
// NOTE: rightWrap must NOT shrink on native. On Android (Yoga), the default
|
|
78
|
+
// `flex: 2` shorthand expands to `{ flexGrow: 2, flexShrink: 1, flexBasis: 0 }`,
|
|
79
|
+
// which — combined with `minWidth: 0` — lets Yoga shrink this wrapper below
|
|
80
|
+
// its own padding+IconCapsule width when leftWrap's text is long. Once that
|
|
81
|
+
// happens the horizontal padding collapses, `alignItems: 'flex-end'` pins
|
|
82
|
+
// the IconCapsule against the inner edge, and the icon ends up visually
|
|
83
|
+
// touching the body text (the wrapper "appears not to exist"). Web hides
|
|
84
|
+
// this because browsers honor `min-width: auto` on flex items. Use
|
|
85
|
+
// explicit `flexGrow`/`flexShrink: 0`/`flexBasis: 'auto'` so the wrapper
|
|
86
|
+
// is sized to its content as a floor and only grows for the design's
|
|
87
|
+
// 3:2 ratio when extra space is available. leftWrap already absorbs tight
|
|
88
|
+
// space via `flexShrink: 1` + `minWidth: 0`.
|
|
76
89
|
const rightWrapStyle = {
|
|
77
|
-
|
|
78
|
-
flexShrink:
|
|
79
|
-
flexBasis:
|
|
80
|
-
minWidth: 0,
|
|
90
|
+
flexGrow: 2,
|
|
91
|
+
flexShrink: 0,
|
|
92
|
+
flexBasis: 'auto',
|
|
81
93
|
paddingHorizontal: rightPaddingH,
|
|
82
94
|
paddingVertical: rightPaddingV,
|
|
83
95
|
alignItems: 'flex-end',
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { useMemo } from 'react';
|
|
4
|
+
import { Image as RNImage, View } from 'react-native';
|
|
5
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
|
+
function normalizeSource(imageSource) {
|
|
7
|
+
if (imageSource == null) return undefined;
|
|
8
|
+
if (typeof imageSource === 'string') return {
|
|
9
|
+
uri: imageSource
|
|
10
|
+
};
|
|
11
|
+
return imageSource;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* `Image` — the library's standard raster image primitive.
|
|
16
|
+
*
|
|
17
|
+
* Why this exists:
|
|
18
|
+
* - Gives consumers a single component for "show an image at a given aspect
|
|
19
|
+
* ratio inside a flex container" without having to remember the
|
|
20
|
+
* `width:'100%' / height:'100%' / resizeMode:'cover'` boilerplate.
|
|
21
|
+
* - Centralizes URL-vs-`{uri}` normalization that several components were
|
|
22
|
+
* re-implementing.
|
|
23
|
+
* - Uses the same `imageSource` prop name as the rest of the library
|
|
24
|
+
* (`Avatar`, `ProductLabel`, `CardProviderInfo`, ...) for a unified API.
|
|
25
|
+
*
|
|
26
|
+
* Layout rules:
|
|
27
|
+
* - If `ratio` is provided, the image lays out with `aspectRatio: ratio`
|
|
28
|
+
* and (unless `width` is given) fills the parent's width.
|
|
29
|
+
* - If neither `ratio` nor explicit dimensions are given, the image fills
|
|
30
|
+
* its parent (`width: '100%'`, `height: '100%'`) — same default as the
|
|
31
|
+
* most common usage in this library (background media, hero images).
|
|
32
|
+
*/
|
|
33
|
+
const DEFAULT_RATIO = 16 / 9;
|
|
34
|
+
function Image({
|
|
35
|
+
imageSource,
|
|
36
|
+
ratio = DEFAULT_RATIO,
|
|
37
|
+
resizeMode = 'cover',
|
|
38
|
+
width,
|
|
39
|
+
height,
|
|
40
|
+
borderRadius,
|
|
41
|
+
style,
|
|
42
|
+
accessibilityLabel,
|
|
43
|
+
accessibilityElementsHidden,
|
|
44
|
+
importantForAccessibility
|
|
45
|
+
}) {
|
|
46
|
+
const source = useMemo(() => normalizeSource(imageSource), [imageSource]);
|
|
47
|
+
const layoutStyle = useMemo(() => {
|
|
48
|
+
// If the caller has fully specified width AND height, they're doing a
|
|
49
|
+
// non-aspect layout (e.g. "fill the parent") — respect that and skip
|
|
50
|
+
// `aspectRatio` so it doesn't conflict.
|
|
51
|
+
const isExplicitBox = width != null && height != null;
|
|
52
|
+
const s = {
|
|
53
|
+
width: width ?? '100%',
|
|
54
|
+
...(isExplicitBox ? {
|
|
55
|
+
height: height
|
|
56
|
+
} : {
|
|
57
|
+
aspectRatio: ratio,
|
|
58
|
+
...(height != null ? {
|
|
59
|
+
height
|
|
60
|
+
} : {})
|
|
61
|
+
})
|
|
62
|
+
};
|
|
63
|
+
if (borderRadius != null) s.borderRadius = borderRadius;
|
|
64
|
+
return s;
|
|
65
|
+
}, [ratio, width, height, borderRadius]);
|
|
66
|
+
if (!source) {
|
|
67
|
+
return /*#__PURE__*/_jsx(View, {
|
|
68
|
+
style: [layoutStyle, style]
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return /*#__PURE__*/_jsx(RNImage, {
|
|
72
|
+
source: source,
|
|
73
|
+
style: [layoutStyle, style],
|
|
74
|
+
resizeMode: resizeMode,
|
|
75
|
+
accessibilityLabel: accessibilityLabel,
|
|
76
|
+
accessibilityElementsHidden: accessibilityElementsHidden,
|
|
77
|
+
importantForAccessibility: importantForAccessibility
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
export default /*#__PURE__*/React.memo(Image);
|
|
@@ -1,59 +1,63 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import React, { createContext, useContext
|
|
3
|
+
import React, { createContext, useContext } from 'react';
|
|
4
4
|
import { View, Text, StyleSheet, Platform } from 'react-native';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
|
+
import Image from '../Image/Image';
|
|
6
7
|
import { EMPTY_MODES } from '../../utils/react-utils';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Context to share 'modes' with child components.
|
|
10
|
-
*/
|
|
11
8
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
12
9
|
const MediaCardContext = /*#__PURE__*/createContext({});
|
|
13
10
|
/**
|
|
14
11
|
* MediaCard component implementation from Figma node 1241:4140.
|
|
15
|
-
*
|
|
12
|
+
*
|
|
16
13
|
* Features a background media slot, a large title, and a glass-morphism footer.
|
|
14
|
+
*
|
|
15
|
+
* The background can be supplied either as `imageSource` (preferred — uses
|
|
16
|
+
* the shared `<Image>` primitive under the hood) or as a custom `media` node
|
|
17
|
+
* for non-image backgrounds.
|
|
17
18
|
*/
|
|
18
19
|
export function MediaCard({
|
|
20
|
+
imageSource,
|
|
21
|
+
ratio,
|
|
19
22
|
media,
|
|
20
23
|
children,
|
|
21
24
|
modes = EMPTY_MODES,
|
|
22
25
|
style
|
|
23
26
|
}) {
|
|
24
|
-
// Container Tokens
|
|
25
27
|
const radius = parseFloat(getVariableByName('cardMedia/radius', modes) || '24');
|
|
26
|
-
const gap = parseFloat(getVariableByName('cardMedia/gap', modes) || '0');
|
|
27
|
-
// Dimensions from Figma: w=369, h=308. We can make it flexible or default to these?
|
|
28
|
-
// Usually components should be flexible, but stories will constrain them.
|
|
29
|
-
// Figma context shows fixed/hug behavior. Let's start with flex container.
|
|
30
28
|
|
|
29
|
+
// No magic minHeight, no aspectRatio on the container. The card simply
|
|
30
|
+
// hugs whatever the background renders at: the <Image> sits in normal
|
|
31
|
+
// flow with `aspectRatio: ratio`, so its rendered height becomes the
|
|
32
|
+
// card's height. Header and Footer are absolutely positioned overlays
|
|
33
|
+
// and don't contribute to layout.
|
|
31
34
|
const containerStyle = {
|
|
32
35
|
borderRadius: radius,
|
|
33
|
-
gap,
|
|
34
36
|
overflow: 'hidden',
|
|
35
|
-
position: 'relative'
|
|
36
|
-
// Default dimensions from Figma if needed, but better to let parent control or use defaults in stories.
|
|
37
|
-
// However, to match "Maximize existing component usage", we follow patterns.
|
|
38
|
-
// We'll trust the parent layout or style prop for width/height.
|
|
39
|
-
minHeight: 308 // inferred from Figma height as a good default or minimum
|
|
37
|
+
position: 'relative'
|
|
40
38
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
|
|
40
|
+
// `media` wins as an escape hatch (gradient/video/etc.). Otherwise we
|
|
41
|
+
// delegate to the shared <Image> for image-source backgrounds. The
|
|
42
|
+
// background renders in normal flow so its height drives the card.
|
|
43
|
+
const background = media ?? (imageSource != null ? /*#__PURE__*/_jsx(Image, {
|
|
44
|
+
imageSource: imageSource,
|
|
45
|
+
ratio: ratio,
|
|
46
|
+
resizeMode: "cover",
|
|
47
|
+
accessibilityElementsHidden: true,
|
|
48
|
+
importantForAccessibility: "no"
|
|
49
|
+
}) : null);
|
|
47
50
|
return /*#__PURE__*/_jsx(MediaCardContext.Provider, {
|
|
48
51
|
value: {
|
|
49
52
|
modes
|
|
50
53
|
},
|
|
51
54
|
children: /*#__PURE__*/_jsxs(View, {
|
|
52
55
|
style: [containerStyle, style],
|
|
53
|
-
children: [/*#__PURE__*/_jsx(View, {
|
|
56
|
+
children: [background, children != null ? /*#__PURE__*/_jsx(View, {
|
|
54
57
|
style: StyleSheet.absoluteFill,
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
pointerEvents: "box-none",
|
|
59
|
+
children: children
|
|
60
|
+
}) : null]
|
|
57
61
|
})
|
|
58
62
|
});
|
|
59
63
|
}
|
|
@@ -71,10 +75,24 @@ export function Header({
|
|
|
71
75
|
children,
|
|
72
76
|
style
|
|
73
77
|
}) {
|
|
78
|
+
// NOTE: the previous `flex: 1` shorthand expanded on Yoga (Android) to
|
|
79
|
+
// `{ flexGrow: 1, flexShrink: 1, flexBasis: 0 }`. With `flexBasis: 0` the
|
|
80
|
+
// Header has *no intrinsic floor*, so when MediaCard is placed inside a
|
|
81
|
+
// height-unbounded parent — e.g. a Carousel slot whose contentContainer
|
|
82
|
+
// is `alignItems: 'flex-start'` — Yoga's first measurement pass sizes
|
|
83
|
+
// the Header at 0 and the card's overall height becomes non-deterministic.
|
|
84
|
+
// On native this manifests as the card "over-stretching" vertically (the
|
|
85
|
+
// same Yoga foot-gun we fixed in `CardCTA` rightWrap). Web hides it
|
|
86
|
+
// because browsers honor `min-height: auto` on flex items. Use explicit
|
|
87
|
+
// `flexGrow / flexShrink: 0 / flexBasis: 'auto'` so the Header is sized
|
|
88
|
+
// to its content as a floor and only grows to consume the extra space
|
|
89
|
+
// contributed by `MediaCard`'s `minHeight: 308`.
|
|
74
90
|
return /*#__PURE__*/_jsx(View, {
|
|
75
91
|
style: [{
|
|
76
92
|
padding: 16,
|
|
77
|
-
|
|
93
|
+
flexGrow: 1,
|
|
94
|
+
flexShrink: 0,
|
|
95
|
+
flexBasis: 'auto'
|
|
78
96
|
}, style],
|
|
79
97
|
children: children
|
|
80
98
|
});
|
|
@@ -120,8 +138,6 @@ export function Footer({
|
|
|
120
138
|
}) {
|
|
121
139
|
const context = useContext(MediaCardContext);
|
|
122
140
|
const modes = propModes || context.modes || {};
|
|
123
|
-
|
|
124
|
-
// Tokens
|
|
125
141
|
const gap = parseFloat(getVariableByName('cardMedia/footer/gap', modes) || '24');
|
|
126
142
|
const paddingHorizontal = parseFloat(getVariableByName('cardMedia/footer/padding/horizontal', modes) || '16');
|
|
127
143
|
const paddingVertical = parseFloat(getVariableByName('cardMedia/footer/padding/vertical', modes) || '12');
|
|
@@ -206,8 +222,6 @@ export function FooterSubtitle({
|
|
|
206
222
|
children: children
|
|
207
223
|
});
|
|
208
224
|
}
|
|
209
|
-
|
|
210
|
-
// Attach sub-components
|
|
211
225
|
MediaCard.Header = Header;
|
|
212
226
|
MediaCard.Title = Title;
|
|
213
227
|
MediaCard.Footer = Footer;
|
|
@@ -25,6 +25,7 @@ export { default as HoldingsCard } from './HoldingsCard/HoldingsCard';
|
|
|
25
25
|
export { default as HStack } from './HStack/HStack';
|
|
26
26
|
export { default as IconButton } from './IconButton/IconButton';
|
|
27
27
|
export { default as IconCapsule } from './IconCapsule/IconCapsule';
|
|
28
|
+
export { default as Image } from './Image/Image';
|
|
28
29
|
export { default as LazyList } from './LazyList/LazyList';
|
|
29
30
|
export { default as LinearMeter } from './LinearMeter/LinearMeter';
|
|
30
31
|
export { default as ListGroup } from './ListGroup/ListGroup';
|