jfs-components 0.0.66 → 0.0.67

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.
@@ -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
- flex: 2,
78
- flexShrink: 1,
79
- flexBasis: 0,
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,73 @@
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
+ function Image({
34
+ imageSource,
35
+ ratio,
36
+ resizeMode = 'cover',
37
+ width,
38
+ height,
39
+ borderRadius,
40
+ style,
41
+ accessibilityLabel,
42
+ accessibilityElementsHidden,
43
+ importantForAccessibility
44
+ }) {
45
+ const source = useMemo(() => normalizeSource(imageSource), [imageSource]);
46
+ const layoutStyle = useMemo(() => {
47
+ const s = {};
48
+ if (ratio != null) {
49
+ s.aspectRatio = ratio;
50
+ s.width = width ?? '100%';
51
+ if (height != null) s.height = height;
52
+ } else {
53
+ s.width = width ?? '100%';
54
+ s.height = height ?? '100%';
55
+ }
56
+ if (borderRadius != null) s.borderRadius = borderRadius;
57
+ return s;
58
+ }, [ratio, width, height, borderRadius]);
59
+ if (!source) {
60
+ return /*#__PURE__*/_jsx(View, {
61
+ style: [layoutStyle, style]
62
+ });
63
+ }
64
+ return /*#__PURE__*/_jsx(RNImage, {
65
+ source: source,
66
+ style: [layoutStyle, style],
67
+ resizeMode: resizeMode,
68
+ accessibilityLabel: accessibilityLabel,
69
+ accessibilityElementsHidden: accessibilityElementsHidden,
70
+ importantForAccessibility: importantForAccessibility
71
+ });
72
+ }
73
+ export default /*#__PURE__*/React.memo(Image);
@@ -1,59 +1,59 @@
1
1
  "use strict";
2
2
 
3
- import React, { createContext, useContext, isValidElement, cloneElement } from 'react';
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
28
  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
-
31
29
  const containerStyle = {
32
30
  borderRadius: radius,
33
31
  gap,
34
32
  overflow: 'hidden',
35
33
  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
34
+ minHeight: 308
40
35
  };
41
- const mediaWithModes = /*#__PURE__*/isValidElement(media) ? /*#__PURE__*/cloneElement(media, {
42
- modes: {
43
- ...media.props.modes,
44
- ...modes
45
- }
46
- }) : media;
36
+
37
+ // `media` wins for back-compat / custom nodes; otherwise we delegate to
38
+ // the shared <Image> for image-source backgrounds. All raster-rendering
39
+ // concerns (URL-vs-{uri}, resizeMode, aspect-ratio) live in <Image>.
40
+ const background = media ?? (imageSource != null ? /*#__PURE__*/_jsx(Image, {
41
+ imageSource: imageSource,
42
+ ratio: ratio,
43
+ resizeMode: "cover",
44
+ accessibilityElementsHidden: true,
45
+ importantForAccessibility: "no"
46
+ }) : null);
47
47
  return /*#__PURE__*/_jsx(MediaCardContext.Provider, {
48
48
  value: {
49
49
  modes
50
50
  },
51
51
  children: /*#__PURE__*/_jsxs(View, {
52
52
  style: [containerStyle, style],
53
- children: [/*#__PURE__*/_jsx(View, {
53
+ children: [background ? /*#__PURE__*/_jsx(View, {
54
54
  style: StyleSheet.absoluteFill,
55
- children: mediaWithModes
56
- }), children]
55
+ children: background
56
+ }) : null, children]
57
57
  })
58
58
  });
59
59
  }
@@ -71,10 +71,24 @@ export function Header({
71
71
  children,
72
72
  style
73
73
  }) {
74
+ // NOTE: the previous `flex: 1` shorthand expanded on Yoga (Android) to
75
+ // `{ flexGrow: 1, flexShrink: 1, flexBasis: 0 }`. With `flexBasis: 0` the
76
+ // Header has *no intrinsic floor*, so when MediaCard is placed inside a
77
+ // height-unbounded parent — e.g. a Carousel slot whose contentContainer
78
+ // is `alignItems: 'flex-start'` — Yoga's first measurement pass sizes
79
+ // the Header at 0 and the card's overall height becomes non-deterministic.
80
+ // On native this manifests as the card "over-stretching" vertically (the
81
+ // same Yoga foot-gun we fixed in `CardCTA` rightWrap). Web hides it
82
+ // because browsers honor `min-height: auto` on flex items. Use explicit
83
+ // `flexGrow / flexShrink: 0 / flexBasis: 'auto'` so the Header is sized
84
+ // to its content as a floor and only grows to consume the extra space
85
+ // contributed by `MediaCard`'s `minHeight: 308`.
74
86
  return /*#__PURE__*/_jsx(View, {
75
87
  style: [{
76
88
  padding: 16,
77
- flex: 1
89
+ flexGrow: 1,
90
+ flexShrink: 0,
91
+ flexBasis: 'auto'
78
92
  }, style],
79
93
  children: children
80
94
  });
@@ -120,8 +134,6 @@ export function Footer({
120
134
  }) {
121
135
  const context = useContext(MediaCardContext);
122
136
  const modes = propModes || context.modes || {};
123
-
124
- // Tokens
125
137
  const gap = parseFloat(getVariableByName('cardMedia/footer/gap', modes) || '24');
126
138
  const paddingHorizontal = parseFloat(getVariableByName('cardMedia/footer/padding/horizontal', modes) || '16');
127
139
  const paddingVertical = parseFloat(getVariableByName('cardMedia/footer/padding/vertical', modes) || '12');
@@ -206,8 +218,6 @@ export function FooterSubtitle({
206
218
  children: children
207
219
  });
208
220
  }
209
-
210
- // Attach sub-components
211
221
  MediaCard.Header = Header;
212
222
  MediaCard.Title = Title;
213
223
  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';