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 CHANGED
@@ -4,6 +4,26 @@ All notable changes to this project are documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
+ ## [0.0.68] - 2026-04-22
8
+
9
+ ### Added
10
+
11
+ - **`Image`:** New shared raster primitive (`imageSource`, optional `ratio`, defaults to `16 / 9` so layout has a stable height without a magic `minHeight`). Supports explicit `width` + `height` to opt out of `aspectRatio` and fill a parent. Exported from the package barrel.
12
+
13
+ ### Changed
14
+
15
+ - **`MediaCard`:** Background uses the shared `Image` via `imageSource` and `ratio`; card height follows the image in normal flow (no fixed `minHeight`). Title/footer content is an `absoluteFill` overlay with `pointerEvents="box-none"`. Optional `media` escape hatch unchanged (custom node sizes itself).
16
+
17
+ ---
18
+
19
+ ## [0.0.67] - 2026-04-22
20
+
21
+ ### Fixed
22
+
23
+ - **`MediaCard` in `Carousel` (Yoga / Android):** `MediaCard.Header` no longer uses `flex: 1` shorthand (`flexBasis: 0`), which made the header size to zero on the first layout pass when the card sat in a height-unbounded parent (e.g. horizontal carousel content). Replaced with explicit `flexGrow: 1`, `flexShrink: 0`, `flexBasis: 'auto'` so the header has a content-based floor, matching the Yoga fix pattern used for `CardCTA` right-wrap.
24
+
25
+ ---
26
+
7
27
  ## [0.0.65] - 2026-04-21
8
28
 
9
29
  ### Added
@@ -78,11 +78,23 @@ function CardCTA({
78
78
  alignItems: 'flex-start',
79
79
  justifyContent: 'center'
80
80
  };
81
+
82
+ // NOTE: rightWrap must NOT shrink on native. On Android (Yoga), the default
83
+ // `flex: 2` shorthand expands to `{ flexGrow: 2, flexShrink: 1, flexBasis: 0 }`,
84
+ // which — combined with `minWidth: 0` — lets Yoga shrink this wrapper below
85
+ // its own padding+IconCapsule width when leftWrap's text is long. Once that
86
+ // happens the horizontal padding collapses, `alignItems: 'flex-end'` pins
87
+ // the IconCapsule against the inner edge, and the icon ends up visually
88
+ // touching the body text (the wrapper "appears not to exist"). Web hides
89
+ // this because browsers honor `min-width: auto` on flex items. Use
90
+ // explicit `flexGrow`/`flexShrink: 0`/`flexBasis: 'auto'` so the wrapper
91
+ // is sized to its content as a floor and only grows for the design's
92
+ // 3:2 ratio when extra space is available. leftWrap already absorbs tight
93
+ // space via `flexShrink: 1` + `minWidth: 0`.
81
94
  const rightWrapStyle = {
82
- flex: 2,
83
- flexShrink: 1,
84
- flexBasis: 0,
85
- minWidth: 0,
95
+ flexGrow: 2,
96
+ flexShrink: 0,
97
+ flexBasis: 'auto',
86
98
  paddingHorizontal: rightPaddingH,
87
99
  paddingVertical: rightPaddingV,
88
100
  alignItems: 'flex-end',
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _jsxRuntime = require("react/jsx-runtime");
10
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
11
+ function normalizeSource(imageSource) {
12
+ if (imageSource == null) return undefined;
13
+ if (typeof imageSource === 'string') return {
14
+ uri: imageSource
15
+ };
16
+ return imageSource;
17
+ }
18
+
19
+ /**
20
+ * `Image` — the library's standard raster image primitive.
21
+ *
22
+ * Why this exists:
23
+ * - Gives consumers a single component for "show an image at a given aspect
24
+ * ratio inside a flex container" without having to remember the
25
+ * `width:'100%' / height:'100%' / resizeMode:'cover'` boilerplate.
26
+ * - Centralizes URL-vs-`{uri}` normalization that several components were
27
+ * re-implementing.
28
+ * - Uses the same `imageSource` prop name as the rest of the library
29
+ * (`Avatar`, `ProductLabel`, `CardProviderInfo`, ...) for a unified API.
30
+ *
31
+ * Layout rules:
32
+ * - If `ratio` is provided, the image lays out with `aspectRatio: ratio`
33
+ * and (unless `width` is given) fills the parent's width.
34
+ * - If neither `ratio` nor explicit dimensions are given, the image fills
35
+ * its parent (`width: '100%'`, `height: '100%'`) — same default as the
36
+ * most common usage in this library (background media, hero images).
37
+ */
38
+ const DEFAULT_RATIO = 16 / 9;
39
+ function Image({
40
+ imageSource,
41
+ ratio = DEFAULT_RATIO,
42
+ resizeMode = 'cover',
43
+ width,
44
+ height,
45
+ borderRadius,
46
+ style,
47
+ accessibilityLabel,
48
+ accessibilityElementsHidden,
49
+ importantForAccessibility
50
+ }) {
51
+ const source = (0, _react.useMemo)(() => normalizeSource(imageSource), [imageSource]);
52
+ const layoutStyle = (0, _react.useMemo)(() => {
53
+ // If the caller has fully specified width AND height, they're doing a
54
+ // non-aspect layout (e.g. "fill the parent") — respect that and skip
55
+ // `aspectRatio` so it doesn't conflict.
56
+ const isExplicitBox = width != null && height != null;
57
+ const s = {
58
+ width: width ?? '100%',
59
+ ...(isExplicitBox ? {
60
+ height: height
61
+ } : {
62
+ aspectRatio: ratio,
63
+ ...(height != null ? {
64
+ height
65
+ } : {})
66
+ })
67
+ };
68
+ if (borderRadius != null) s.borderRadius = borderRadius;
69
+ return s;
70
+ }, [ratio, width, height, borderRadius]);
71
+ if (!source) {
72
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
73
+ style: [layoutStyle, style]
74
+ });
75
+ }
76
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
77
+ source: source,
78
+ style: [layoutStyle, style],
79
+ resizeMode: resizeMode,
80
+ accessibilityLabel: accessibilityLabel,
81
+ accessibilityElementsHidden: accessibilityElementsHidden,
82
+ importantForAccessibility: importantForAccessibility
83
+ });
84
+ }
85
+ var _default = exports.default = /*#__PURE__*/_react.default.memo(Image);
@@ -13,56 +13,63 @@ exports.default = void 0;
13
13
  var _react = _interopRequireWildcard(require("react"));
14
14
  var _reactNative = require("react-native");
15
15
  var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
16
+ var _Image = _interopRequireDefault(require("../Image/Image"));
16
17
  var _reactUtils = require("../../utils/react-utils");
17
18
  var _jsxRuntime = require("react/jsx-runtime");
19
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
18
20
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
19
- /**
20
- * Context to share 'modes' with child components.
21
- */const MediaCardContext = /*#__PURE__*/(0, _react.createContext)({});
21
+ const MediaCardContext = /*#__PURE__*/(0, _react.createContext)({});
22
22
  /**
23
23
  * MediaCard component implementation from Figma node 1241:4140.
24
- *
24
+ *
25
25
  * Features a background media slot, a large title, and a glass-morphism footer.
26
+ *
27
+ * The background can be supplied either as `imageSource` (preferred — uses
28
+ * the shared `<Image>` primitive under the hood) or as a custom `media` node
29
+ * for non-image backgrounds.
26
30
  */
27
31
  function MediaCard({
32
+ imageSource,
33
+ ratio,
28
34
  media,
29
35
  children,
30
36
  modes = _reactUtils.EMPTY_MODES,
31
37
  style
32
38
  }) {
33
- // Container Tokens
34
39
  const radius = parseFloat((0, _figmaVariablesResolver.getVariableByName)('cardMedia/radius', modes) || '24');
35
- const gap = parseFloat((0, _figmaVariablesResolver.getVariableByName)('cardMedia/gap', modes) || '0');
36
- // Dimensions from Figma: w=369, h=308. We can make it flexible or default to these?
37
- // Usually components should be flexible, but stories will constrain them.
38
- // Figma context shows fixed/hug behavior. Let's start with flex container.
39
40
 
41
+ // No magic minHeight, no aspectRatio on the container. The card simply
42
+ // hugs whatever the background renders at: the <Image> sits in normal
43
+ // flow with `aspectRatio: ratio`, so its rendered height becomes the
44
+ // card's height. Header and Footer are absolutely positioned overlays
45
+ // and don't contribute to layout.
40
46
  const containerStyle = {
41
47
  borderRadius: radius,
42
- gap,
43
48
  overflow: 'hidden',
44
- position: 'relative',
45
- // Default dimensions from Figma if needed, but better to let parent control or use defaults in stories.
46
- // However, to match "Maximize existing component usage", we follow patterns.
47
- // We'll trust the parent layout or style prop for width/height.
48
- minHeight: 308 // inferred from Figma height as a good default or minimum
49
+ position: 'relative'
49
50
  };
50
- const mediaWithModes = /*#__PURE__*/(0, _react.isValidElement)(media) ? /*#__PURE__*/(0, _react.cloneElement)(media, {
51
- modes: {
52
- ...media.props.modes,
53
- ...modes
54
- }
55
- }) : media;
51
+
52
+ // `media` wins as an escape hatch (gradient/video/etc.). Otherwise we
53
+ // delegate to the shared <Image> for image-source backgrounds. The
54
+ // background renders in normal flow so its height drives the card.
55
+ const background = media ?? (imageSource != null ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Image.default, {
56
+ imageSource: imageSource,
57
+ ratio: ratio,
58
+ resizeMode: "cover",
59
+ accessibilityElementsHidden: true,
60
+ importantForAccessibility: "no"
61
+ }) : null);
56
62
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(MediaCardContext.Provider, {
57
63
  value: {
58
64
  modes
59
65
  },
60
66
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
61
67
  style: [containerStyle, style],
62
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
68
+ children: [background, children != null ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
63
69
  style: _reactNative.StyleSheet.absoluteFill,
64
- children: mediaWithModes
65
- }), children]
70
+ pointerEvents: "box-none",
71
+ children: children
72
+ }) : null]
66
73
  })
67
74
  });
68
75
  }
@@ -80,10 +87,24 @@ function Header({
80
87
  children,
81
88
  style
82
89
  }) {
90
+ // NOTE: the previous `flex: 1` shorthand expanded on Yoga (Android) to
91
+ // `{ flexGrow: 1, flexShrink: 1, flexBasis: 0 }`. With `flexBasis: 0` the
92
+ // Header has *no intrinsic floor*, so when MediaCard is placed inside a
93
+ // height-unbounded parent — e.g. a Carousel slot whose contentContainer
94
+ // is `alignItems: 'flex-start'` — Yoga's first measurement pass sizes
95
+ // the Header at 0 and the card's overall height becomes non-deterministic.
96
+ // On native this manifests as the card "over-stretching" vertically (the
97
+ // same Yoga foot-gun we fixed in `CardCTA` rightWrap). Web hides it
98
+ // because browsers honor `min-height: auto` on flex items. Use explicit
99
+ // `flexGrow / flexShrink: 0 / flexBasis: 'auto'` so the Header is sized
100
+ // to its content as a floor and only grows to consume the extra space
101
+ // contributed by `MediaCard`'s `minHeight: 308`.
83
102
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
84
103
  style: [{
85
104
  padding: 16,
86
- flex: 1
105
+ flexGrow: 1,
106
+ flexShrink: 0,
107
+ flexBasis: 'auto'
87
108
  }, style],
88
109
  children: children
89
110
  });
@@ -129,8 +150,6 @@ function Footer({
129
150
  }) {
130
151
  const context = (0, _react.useContext)(MediaCardContext);
131
152
  const modes = propModes || context.modes || {};
132
-
133
- // Tokens
134
153
  const gap = parseFloat((0, _figmaVariablesResolver.getVariableByName)('cardMedia/footer/gap', modes) || '24');
135
154
  const paddingHorizontal = parseFloat((0, _figmaVariablesResolver.getVariableByName)('cardMedia/footer/padding/horizontal', modes) || '16');
136
155
  const paddingVertical = parseFloat((0, _figmaVariablesResolver.getVariableByName)('cardMedia/footer/padding/vertical', modes) || '12');
@@ -215,8 +234,6 @@ function FooterSubtitle({
215
234
  children: children
216
235
  });
217
236
  }
218
-
219
- // Attach sub-components
220
237
  MediaCard.Header = Header;
221
238
  MediaCard.Title = Title;
222
239
  MediaCard.Footer = Footer;
@@ -201,6 +201,12 @@ Object.defineProperty(exports, "IconCapsule", {
201
201
  return _IconCapsule.default;
202
202
  }
203
203
  });
204
+ Object.defineProperty(exports, "Image", {
205
+ enumerable: true,
206
+ get: function () {
207
+ return _Image.default;
208
+ }
209
+ });
204
210
  Object.defineProperty(exports, "InputSearch", {
205
211
  enumerable: true,
206
212
  get: function () {
@@ -531,6 +537,7 @@ var _HoldingsCard = _interopRequireDefault(require("./HoldingsCard/HoldingsCard"
531
537
  var _HStack = _interopRequireDefault(require("./HStack/HStack"));
532
538
  var _IconButton = _interopRequireDefault(require("./IconButton/IconButton"));
533
539
  var _IconCapsule = _interopRequireDefault(require("./IconCapsule/IconCapsule"));
540
+ var _Image = _interopRequireDefault(require("./Image/Image"));
534
541
  var _LazyList = _interopRequireDefault(require("./LazyList/LazyList"));
535
542
  var _LinearMeter = _interopRequireDefault(require("./LinearMeter/LinearMeter"));
536
543
  var _ListGroup = _interopRequireDefault(require("./ListGroup/ListGroup"));