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.
- package/CHANGELOG.md +9 -0
- package/lib/commonjs/components/CardCTA/CardCTA.js +4 -5
- package/lib/commonjs/components/Carousel/Carousel.js +50 -9
- package/lib/commonjs/components/TextInput/TextInput.js +20 -12
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/CardCTA/CardCTA.js +4 -5
- package/lib/module/components/Carousel/Carousel.js +51 -10
- package/lib/module/components/TextInput/TextInput.js +20 -12
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/CardCTA/CardCTA.tsx +3 -4
- package/src/components/Carousel/Carousel.tsx +66 -7
- package/src/components/TextInput/TextInput.tsx +20 -12
- package/src/icons/registry.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,15 @@ 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.1.58] - 2026-07-27
|
|
8
|
+
|
|
9
|
+
- `TextInput` / `InputSearch` — restore iOS single-line vertical centering (FormField pattern): row owns height via `minHeight` + `paddingVertical: 0`; omit `lineHeight` on the field on iOS so placeholder and typed text stay centered (RN #39145 / #56774).
|
|
10
|
+
|
|
11
|
+
## [0.1.57] - 2026-07-27
|
|
12
|
+
|
|
13
|
+
- `Carousel` — fix infinite-tall slides: equal-height no longer uses unbounded `alignItems:'stretch'` / `height:'100%'` in a horizontal ScrollView; it measures intrinsic slide heights then locks slots to the tallest.
|
|
14
|
+
- `CardCTA` (rating) — pin the footer when given an explicit height without `flexGrow` (which also caused infinite height in horizontal carousels).
|
|
15
|
+
|
|
7
16
|
## [0.1.56] - 2026-07-27
|
|
8
17
|
|
|
9
18
|
- `Carousel` — `equalHeight` defaults to `true` (hug tallest slide; no token `maxHeight` clip). New `itemInset`, `bleedHorizontal`, and `maxHeight` (`number | false`) props for edge-aligned cards without consumer math. Opt out with `equalHeight={false}` to keep the legacy clipped strip. Storybook docs refreshed for the new API.
|
|
@@ -215,12 +215,11 @@ function CardCTA({
|
|
|
215
215
|
borderColor,
|
|
216
216
|
flexDirection: isRating || isSip ? 'column' : 'row',
|
|
217
217
|
overflow: 'visible',
|
|
218
|
-
//
|
|
219
|
-
//
|
|
218
|
+
// Pin footer to the bottom when a parent (e.g. equal-height Carousel)
|
|
219
|
+
// assigns an explicit height. Do not flexGrow — that unbounded-grows
|
|
220
|
+
// inside horizontal ScrollViews and blows cards to infinite height.
|
|
220
221
|
...(isRating ? {
|
|
221
|
-
|
|
222
|
-
justifyContent: 'space-between',
|
|
223
|
-
flexGrow: 1
|
|
222
|
+
justifyContent: 'space-between'
|
|
224
223
|
} : {})
|
|
225
224
|
};
|
|
226
225
|
|
|
@@ -105,6 +105,35 @@ function Carousel({
|
|
|
105
105
|
return containerWidth - containerPaddingH * 4;
|
|
106
106
|
}, [itemWidthProp, itemInset, containerWidth, containerPaddingH, isNumbered]);
|
|
107
107
|
|
|
108
|
+
// Equal-height: measure each slide's intrinsic height, then lock every slot
|
|
109
|
+
// to the tallest. Do NOT use contentContainerStyle.alignItems:'stretch' or
|
|
110
|
+
// height:'100%' without a bounded cross-axis — in a horizontal ScrollView
|
|
111
|
+
// that resolves to an unbounded/infinite height.
|
|
112
|
+
const slideHeightsRef = (0, _react.useRef)({});
|
|
113
|
+
const [equalizedHeight, setEqualizedHeight] = (0, _react.useState)();
|
|
114
|
+
const equalizedHeightRef = (0, _react.useRef)(undefined);
|
|
115
|
+
equalizedHeightRef.current = equalizedHeight;
|
|
116
|
+
(0, _react.useEffect)(() => {
|
|
117
|
+
slideHeightsRef.current = {};
|
|
118
|
+
equalizedHeightRef.current = undefined;
|
|
119
|
+
setEqualizedHeight(undefined);
|
|
120
|
+
}, [totalItems, effectiveItemWidth, equalHeight]);
|
|
121
|
+
const handleSlideLayout = (0, _react.useCallback)(index => event => {
|
|
122
|
+
if (!equalHeight) return;
|
|
123
|
+
// Only measure while unconstrained. After lock, the slot height is forced
|
|
124
|
+
// and onLayout would just echo equalizedHeight.
|
|
125
|
+
if (equalizedHeightRef.current != null) return;
|
|
126
|
+
const height = Math.ceil(event.nativeEvent.layout.height);
|
|
127
|
+
if (height <= 0) return;
|
|
128
|
+
const previous = slideHeightsRef.current[index] ?? 0;
|
|
129
|
+
if (height <= previous) return;
|
|
130
|
+
slideHeightsRef.current[index] = height;
|
|
131
|
+
const measured = items.map((_, i) => slideHeightsRef.current[i]);
|
|
132
|
+
if (measured.some(value => value == null)) return;
|
|
133
|
+
const next = Math.max(...measured);
|
|
134
|
+
setEqualizedHeight(next);
|
|
135
|
+
}, [equalHeight, items]);
|
|
136
|
+
|
|
108
137
|
// Snap interval = item width + gap
|
|
109
138
|
const snapInterval = effectiveItemWidth + gap;
|
|
110
139
|
|
|
@@ -259,8 +288,9 @@ function Carousel({
|
|
|
259
288
|
const contentContainerStyle = {
|
|
260
289
|
paddingHorizontal: effectivePaddingH,
|
|
261
290
|
gap,
|
|
262
|
-
//
|
|
263
|
-
|
|
291
|
+
// Always flex-start on the cross axis. Stretch + unbounded ScrollView
|
|
292
|
+
// height is what caused infinite-tall slides.
|
|
293
|
+
alignItems: 'flex-start'
|
|
264
294
|
};
|
|
265
295
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(CarouselContext.Provider, {
|
|
266
296
|
value: contextValue,
|
|
@@ -282,6 +312,8 @@ function Carousel({
|
|
|
282
312
|
onScrollEndDrag: handleScrollEndDrag,
|
|
283
313
|
onMomentumScrollEnd: finishProgrammaticScroll,
|
|
284
314
|
children: items.map((child, index) => {
|
|
315
|
+
const hasEqualizedHeight = equalHeight && equalizedHeight != null && equalizedHeight > 0;
|
|
316
|
+
|
|
285
317
|
// Strict slot box: width must be honored; never grow or shrink with
|
|
286
318
|
// content, and clip anything that misbehaves (e.g. a child whose
|
|
287
319
|
// inner flex layout would otherwise leak into the next slot on
|
|
@@ -292,8 +324,8 @@ function Carousel({
|
|
|
292
324
|
flexShrink: 0,
|
|
293
325
|
flexBasis: effectiveItemWidth > 0 ? effectiveItemWidth : 'auto',
|
|
294
326
|
overflow: 'hidden',
|
|
295
|
-
...(
|
|
296
|
-
|
|
327
|
+
...(hasEqualizedHeight ? {
|
|
328
|
+
height: equalizedHeight
|
|
297
329
|
} : {})
|
|
298
330
|
};
|
|
299
331
|
|
|
@@ -303,11 +335,11 @@ function Carousel({
|
|
|
303
335
|
const childOverrideStyle = {
|
|
304
336
|
width: effectiveItemWidth > 0 ? effectiveItemWidth : undefined,
|
|
305
337
|
maxWidth: effectiveItemWidth > 0 ? effectiveItemWidth : undefined,
|
|
306
|
-
flexGrow:
|
|
338
|
+
flexGrow: 0,
|
|
307
339
|
flexShrink: 0,
|
|
308
|
-
...(
|
|
309
|
-
|
|
310
|
-
|
|
340
|
+
...(hasEqualizedHeight ? {
|
|
341
|
+
height: '100%',
|
|
342
|
+
alignSelf: 'stretch'
|
|
311
343
|
} : {})
|
|
312
344
|
};
|
|
313
345
|
|
|
@@ -321,7 +353,11 @@ function Carousel({
|
|
|
321
353
|
}) : child;
|
|
322
354
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
323
355
|
style: slotStyle,
|
|
324
|
-
children:
|
|
356
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
357
|
+
style: hasEqualizedHeight ? styles.equalizedFill : undefined,
|
|
358
|
+
onLayout: handleSlideLayout(index),
|
|
359
|
+
children: childWithModes
|
|
360
|
+
})
|
|
325
361
|
}, index);
|
|
326
362
|
})
|
|
327
363
|
}), !isNumbered && showPagination && totalItems > 1 && /*#__PURE__*/(0, _jsxRuntime.jsx)(Pagination, {
|
|
@@ -470,6 +506,11 @@ function AnimatedDot({
|
|
|
470
506
|
}
|
|
471
507
|
});
|
|
472
508
|
}
|
|
509
|
+
const styles = _reactNative.StyleSheet.create({
|
|
510
|
+
equalizedFill: {
|
|
511
|
+
flex: 1
|
|
512
|
+
}
|
|
513
|
+
});
|
|
473
514
|
|
|
474
515
|
// ---------------------------------------------------------------------------
|
|
475
516
|
// Attach sub-components
|
|
@@ -149,6 +149,14 @@ const TextInputBase = /*#__PURE__*/(0, _react.forwardRef)(function TextInput({
|
|
|
149
149
|
|
|
150
150
|
// Convert radius to React Native format (if 99999, use a large value for fully rounded)
|
|
151
151
|
const borderRadius = radius === 99999 ? 99999 : radius;
|
|
152
|
+
|
|
153
|
+
// Row height from tokens: vertical padding above/below the text line.
|
|
154
|
+
// Applied as container `minHeight` (FormField pattern) — NOT as TextInput
|
|
155
|
+
// `lineHeight` / `minHeight`, which breaks iOS vertical centering.
|
|
156
|
+
const padV = typeof paddingVertical === 'number' ? paddingVertical : 0;
|
|
157
|
+
const textLine = typeof lineHeight === 'number' ? lineHeight : 0;
|
|
158
|
+
const iconH = typeof iconSize === 'number' ? iconSize : 0;
|
|
159
|
+
const rowMinHeight = padV * 2 + Math.max(textLine, iconH);
|
|
152
160
|
const containerStyle = {
|
|
153
161
|
flexDirection: 'row',
|
|
154
162
|
alignItems: 'center',
|
|
@@ -158,7 +166,10 @@ const TextInputBase = /*#__PURE__*/(0, _react.forwardRef)(function TextInput({
|
|
|
158
166
|
borderStyle: 'solid',
|
|
159
167
|
borderRadius,
|
|
160
168
|
paddingHorizontal,
|
|
161
|
-
|
|
169
|
+
// Same as FormField / SuggestiveSearch: the ROW owns height via minHeight;
|
|
170
|
+
// paddingVertical on the row would fight that and let the field stretch.
|
|
171
|
+
paddingVertical: 0,
|
|
172
|
+
minHeight: rowMinHeight > 0 ? rowMinHeight : undefined,
|
|
162
173
|
gap: 8
|
|
163
174
|
};
|
|
164
175
|
const hoverStyle = isHovered ? {
|
|
@@ -169,18 +180,15 @@ const TextInputBase = /*#__PURE__*/(0, _react.forwardRef)(function TextInput({
|
|
|
169
180
|
borderWidth: 1
|
|
170
181
|
} : {};
|
|
171
182
|
|
|
172
|
-
// ⚠️
|
|
173
|
-
// `minHeight: lineHeight` on this single-line input on iOS. Same contract as
|
|
174
|
-
// FormField / SuggestiveSearch — read before "simplifying".
|
|
183
|
+
// ⚠️ Same contract as FormField / SuggestiveSearch — do not "simplify".
|
|
175
184
|
//
|
|
176
|
-
// facebook/react-native#39145 / #28012
|
|
177
|
-
//
|
|
178
|
-
// ABOVE the glyphs
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
// font metrics and floats typed text above center.
|
|
185
|
+
// facebook/react-native#39145 / #28012 / #56774:
|
|
186
|
+
// Single-line iOS UITextField + `lineHeight` (or a tall field box) puts ALL
|
|
187
|
+
// extra space ABOVE the glyphs → placeholder/value sink. Documented fix:
|
|
188
|
+
// omit `lineHeight` on the single-line input; let the wrapper
|
|
189
|
+
// (`alignItems: 'center'` + `minHeight`) center it. UITextField centers
|
|
190
|
+
// natively when it is only as tall as its font. Do NOT pin lineHeight to
|
|
191
|
+
// fontSize (clips custom fonts / floats typed text).
|
|
184
192
|
const textInputStyle = {
|
|
185
193
|
flex: 1,
|
|
186
194
|
color: textColor,
|