jfs-components 0.1.56 → 0.1.58

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.
@@ -210,12 +210,11 @@ function CardCTA({
210
210
  borderColor,
211
211
  flexDirection: isRating || isSip ? 'column' : 'row',
212
212
  overflow: 'visible',
213
- // Rating cards fill equal-height carousel slots and pin the footer down
214
- // when free space exists; intrinsic height is unchanged when not stretched.
213
+ // Pin footer to the bottom when a parent (e.g. equal-height Carousel)
214
+ // assigns an explicit height. Do not flexGrow that unbounded-grows
215
+ // inside horizontal ScrollViews and blows cards to infinite height.
215
216
  ...(isRating ? {
216
- alignSelf: 'stretch',
217
- justifyContent: 'space-between',
218
- flexGrow: 1
217
+ justifyContent: 'space-between'
219
218
  } : {})
220
219
  };
221
220
 
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  import React, { createContext, useContext, useRef, useState, useEffect, useCallback, useMemo } from 'react';
4
- import { View, ScrollView, Animated, Pressable } from 'react-native';
4
+ import { View, ScrollView, Animated, Pressable, StyleSheet } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
6
  import { EMPTY_MODES } from '../../utils/react-utils';
7
7
  import AutoplayControl from '../AutoplayControl/AutoplayControl';
@@ -96,6 +96,35 @@ export function Carousel({
96
96
  return containerWidth - containerPaddingH * 4;
97
97
  }, [itemWidthProp, itemInset, containerWidth, containerPaddingH, isNumbered]);
98
98
 
99
+ // Equal-height: measure each slide's intrinsic height, then lock every slot
100
+ // to the tallest. Do NOT use contentContainerStyle.alignItems:'stretch' or
101
+ // height:'100%' without a bounded cross-axis — in a horizontal ScrollView
102
+ // that resolves to an unbounded/infinite height.
103
+ const slideHeightsRef = useRef({});
104
+ const [equalizedHeight, setEqualizedHeight] = useState();
105
+ const equalizedHeightRef = useRef(undefined);
106
+ equalizedHeightRef.current = equalizedHeight;
107
+ useEffect(() => {
108
+ slideHeightsRef.current = {};
109
+ equalizedHeightRef.current = undefined;
110
+ setEqualizedHeight(undefined);
111
+ }, [totalItems, effectiveItemWidth, equalHeight]);
112
+ const handleSlideLayout = useCallback(index => event => {
113
+ if (!equalHeight) return;
114
+ // Only measure while unconstrained. After lock, the slot height is forced
115
+ // and onLayout would just echo equalizedHeight.
116
+ if (equalizedHeightRef.current != null) return;
117
+ const height = Math.ceil(event.nativeEvent.layout.height);
118
+ if (height <= 0) return;
119
+ const previous = slideHeightsRef.current[index] ?? 0;
120
+ if (height <= previous) return;
121
+ slideHeightsRef.current[index] = height;
122
+ const measured = items.map((_, i) => slideHeightsRef.current[i]);
123
+ if (measured.some(value => value == null)) return;
124
+ const next = Math.max(...measured);
125
+ setEqualizedHeight(next);
126
+ }, [equalHeight, items]);
127
+
99
128
  // Snap interval = item width + gap
100
129
  const snapInterval = effectiveItemWidth + gap;
101
130
 
@@ -250,8 +279,9 @@ export function Carousel({
250
279
  const contentContainerStyle = {
251
280
  paddingHorizontal: effectivePaddingH,
252
281
  gap,
253
- // Stretch so every slide matches the tallest (default). Opt out with equalHeight={false}.
254
- alignItems: equalHeight ? 'stretch' : 'flex-start'
282
+ // Always flex-start on the cross axis. Stretch + unbounded ScrollView
283
+ // height is what caused infinite-tall slides.
284
+ alignItems: 'flex-start'
255
285
  };
256
286
  return /*#__PURE__*/_jsx(CarouselContext.Provider, {
257
287
  value: contextValue,
@@ -273,6 +303,8 @@ export function Carousel({
273
303
  onScrollEndDrag: handleScrollEndDrag,
274
304
  onMomentumScrollEnd: finishProgrammaticScroll,
275
305
  children: items.map((child, index) => {
306
+ const hasEqualizedHeight = equalHeight && equalizedHeight != null && equalizedHeight > 0;
307
+
276
308
  // Strict slot box: width must be honored; never grow or shrink with
277
309
  // content, and clip anything that misbehaves (e.g. a child whose
278
310
  // inner flex layout would otherwise leak into the next slot on
@@ -283,8 +315,8 @@ export function Carousel({
283
315
  flexShrink: 0,
284
316
  flexBasis: effectiveItemWidth > 0 ? effectiveItemWidth : 'auto',
285
317
  overflow: 'hidden',
286
- ...(equalHeight ? {
287
- alignSelf: 'stretch'
318
+ ...(hasEqualizedHeight ? {
319
+ height: equalizedHeight
288
320
  } : {})
289
321
  };
290
322
 
@@ -294,11 +326,11 @@ export function Carousel({
294
326
  const childOverrideStyle = {
295
327
  width: effectiveItemWidth > 0 ? effectiveItemWidth : undefined,
296
328
  maxWidth: effectiveItemWidth > 0 ? effectiveItemWidth : undefined,
297
- flexGrow: equalHeight ? 1 : 0,
329
+ flexGrow: 0,
298
330
  flexShrink: 0,
299
- ...(equalHeight ? {
300
- alignSelf: 'stretch',
301
- height: '100%'
331
+ ...(hasEqualizedHeight ? {
332
+ height: '100%',
333
+ alignSelf: 'stretch'
302
334
  } : {})
303
335
  };
304
336
 
@@ -312,7 +344,11 @@ export function Carousel({
312
344
  }) : child;
313
345
  return /*#__PURE__*/_jsx(View, {
314
346
  style: slotStyle,
315
- children: childWithModes
347
+ children: /*#__PURE__*/_jsx(View, {
348
+ style: hasEqualizedHeight ? styles.equalizedFill : undefined,
349
+ onLayout: handleSlideLayout(index),
350
+ children: childWithModes
351
+ })
316
352
  }, index);
317
353
  })
318
354
  }), !isNumbered && showPagination && totalItems > 1 && /*#__PURE__*/_jsx(Pagination, {
@@ -461,6 +497,11 @@ function AnimatedDot({
461
497
  }
462
498
  });
463
499
  }
500
+ const styles = StyleSheet.create({
501
+ equalizedFill: {
502
+ flex: 1
503
+ }
504
+ });
464
505
 
465
506
  // ---------------------------------------------------------------------------
466
507
  // Attach sub-components
@@ -143,6 +143,14 @@ const TextInputBase = /*#__PURE__*/forwardRef(function TextInput({
143
143
 
144
144
  // Convert radius to React Native format (if 99999, use a large value for fully rounded)
145
145
  const borderRadius = radius === 99999 ? 99999 : radius;
146
+
147
+ // Row height from tokens: vertical padding above/below the text line.
148
+ // Applied as container `minHeight` (FormField pattern) — NOT as TextInput
149
+ // `lineHeight` / `minHeight`, which breaks iOS vertical centering.
150
+ const padV = typeof paddingVertical === 'number' ? paddingVertical : 0;
151
+ const textLine = typeof lineHeight === 'number' ? lineHeight : 0;
152
+ const iconH = typeof iconSize === 'number' ? iconSize : 0;
153
+ const rowMinHeight = padV * 2 + Math.max(textLine, iconH);
146
154
  const containerStyle = {
147
155
  flexDirection: 'row',
148
156
  alignItems: 'center',
@@ -152,7 +160,10 @@ const TextInputBase = /*#__PURE__*/forwardRef(function TextInput({
152
160
  borderStyle: 'solid',
153
161
  borderRadius,
154
162
  paddingHorizontal,
155
- paddingVertical,
163
+ // Same as FormField / SuggestiveSearch: the ROW owns height via minHeight;
164
+ // paddingVertical on the row would fight that and let the field stretch.
165
+ paddingVertical: 0,
166
+ minHeight: rowMinHeight > 0 ? rowMinHeight : undefined,
156
167
  gap: 8
157
168
  };
158
169
  const hoverStyle = isHovered ? {
@@ -163,18 +174,15 @@ const TextInputBase = /*#__PURE__*/forwardRef(function TextInput({
163
174
  borderWidth: 1
164
175
  } : {};
165
176
 
166
- // ⚠️ DO NOT change to an unconditional `lineHeight`, and do NOT put
167
- // `minHeight: lineHeight` on this single-line input on iOS. Same contract as
168
- // FormField / SuggestiveSearch — read before "simplifying".
177
+ // ⚠️ Same contract as FormField / SuggestiveSearch do not "simplify".
169
178
  //
170
- // facebook/react-native#39145 / #28012: on iOS, a single-line RN TextInput
171
- // (UITextField) with `lineHeight` > font metrics puts ALL the extra space
172
- // ABOVE the glyphs, so placeholder/value sink. Android/Web distribute evenly.
173
- // RN's documented workaround (#39145) and #56774: omit `lineHeight` on
174
- // single-line iOS inputs UITextField centers natively — and let the
175
- // wrapper (`alignItems: 'center'` + padding/icon) own vertical layout.
176
- // Pinning `lineHeight` to `fontSize` is NOT a substitute; it clips custom
177
- // font metrics and floats typed text above center.
179
+ // facebook/react-native#39145 / #28012 / #56774:
180
+ // Single-line iOS UITextField + `lineHeight` (or a tall field box) puts ALL
181
+ // extra space ABOVE the glyphs placeholder/value sink. Documented fix:
182
+ // omit `lineHeight` on the single-line input; let the wrapper
183
+ // (`alignItems: 'center'` + `minHeight`) center it. UITextField centers
184
+ // natively when it is only as tall as its font. Do NOT pin lineHeight to
185
+ // fontSize (clips custom fonts / floats typed text).
178
186
  const textInputStyle = {
179
187
  flex: 1,
180
188
  color: textColor,