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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Auto-generated from SVG files in src/icons/
|
|
5
5
|
* DO NOT EDIT MANUALLY - Run "npm run icons:generate" to regenerate
|
|
6
6
|
*
|
|
7
|
-
* Generated: 2026-07-
|
|
7
|
+
* Generated: 2026-07-27T21:47:09.131Z
|
|
8
8
|
*/
|
|
9
9
|
export declare const iconRegistry: Record<string, {
|
|
10
10
|
path: string;
|
package/package.json
CHANGED
|
@@ -284,13 +284,12 @@ function CardCTA({
|
|
|
284
284
|
borderColor,
|
|
285
285
|
flexDirection: isRating || isSip ? 'column' : 'row',
|
|
286
286
|
overflow: 'visible',
|
|
287
|
-
//
|
|
288
|
-
//
|
|
287
|
+
// Pin footer to the bottom when a parent (e.g. equal-height Carousel)
|
|
288
|
+
// assigns an explicit height. Do not flexGrow — that unbounded-grows
|
|
289
|
+
// inside horizontal ScrollViews and blows cards to infinite height.
|
|
289
290
|
...(isRating
|
|
290
291
|
? {
|
|
291
|
-
alignSelf: 'stretch',
|
|
292
292
|
justifyContent: 'space-between' as const,
|
|
293
|
-
flexGrow: 1,
|
|
294
293
|
}
|
|
295
294
|
: {}),
|
|
296
295
|
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
ScrollView,
|
|
13
13
|
Animated,
|
|
14
14
|
Pressable,
|
|
15
|
+
StyleSheet,
|
|
15
16
|
type ViewStyle,
|
|
16
17
|
type StyleProp,
|
|
17
18
|
type NativeSyntheticEvent,
|
|
@@ -210,6 +211,44 @@ export function Carousel({
|
|
|
210
211
|
return containerWidth - containerPaddingH * 4
|
|
211
212
|
}, [itemWidthProp, itemInset, containerWidth, containerPaddingH, isNumbered])
|
|
212
213
|
|
|
214
|
+
// Equal-height: measure each slide's intrinsic height, then lock every slot
|
|
215
|
+
// to the tallest. Do NOT use contentContainerStyle.alignItems:'stretch' or
|
|
216
|
+
// height:'100%' without a bounded cross-axis — in a horizontal ScrollView
|
|
217
|
+
// that resolves to an unbounded/infinite height.
|
|
218
|
+
const slideHeightsRef = useRef<Record<number, number>>({})
|
|
219
|
+
const [equalizedHeight, setEqualizedHeight] = useState<number | undefined>()
|
|
220
|
+
const equalizedHeightRef = useRef<number | undefined>(undefined)
|
|
221
|
+
equalizedHeightRef.current = equalizedHeight
|
|
222
|
+
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
slideHeightsRef.current = {}
|
|
225
|
+
equalizedHeightRef.current = undefined
|
|
226
|
+
setEqualizedHeight(undefined)
|
|
227
|
+
}, [totalItems, effectiveItemWidth, equalHeight])
|
|
228
|
+
|
|
229
|
+
const handleSlideLayout = useCallback(
|
|
230
|
+
(index: number) => (event: LayoutChangeEvent) => {
|
|
231
|
+
if (!equalHeight) return
|
|
232
|
+
// Only measure while unconstrained. After lock, the slot height is forced
|
|
233
|
+
// and onLayout would just echo equalizedHeight.
|
|
234
|
+
if (equalizedHeightRef.current != null) return
|
|
235
|
+
|
|
236
|
+
const height = Math.ceil(event.nativeEvent.layout.height)
|
|
237
|
+
if (height <= 0) return
|
|
238
|
+
|
|
239
|
+
const previous = slideHeightsRef.current[index] ?? 0
|
|
240
|
+
if (height <= previous) return
|
|
241
|
+
|
|
242
|
+
slideHeightsRef.current[index] = height
|
|
243
|
+
const measured = items.map((_, i) => slideHeightsRef.current[i])
|
|
244
|
+
if (measured.some(value => value == null)) return
|
|
245
|
+
|
|
246
|
+
const next = Math.max(...(measured as number[]))
|
|
247
|
+
setEqualizedHeight(next)
|
|
248
|
+
},
|
|
249
|
+
[equalHeight, items],
|
|
250
|
+
)
|
|
251
|
+
|
|
213
252
|
// Snap interval = item width + gap
|
|
214
253
|
const snapInterval = effectiveItemWidth + gap
|
|
215
254
|
|
|
@@ -412,8 +451,9 @@ export function Carousel({
|
|
|
412
451
|
const contentContainerStyle: ViewStyle = {
|
|
413
452
|
paddingHorizontal: effectivePaddingH,
|
|
414
453
|
gap,
|
|
415
|
-
//
|
|
416
|
-
|
|
454
|
+
// Always flex-start on the cross axis. Stretch + unbounded ScrollView
|
|
455
|
+
// height is what caused infinite-tall slides.
|
|
456
|
+
alignItems: 'flex-start',
|
|
417
457
|
}
|
|
418
458
|
|
|
419
459
|
return (
|
|
@@ -435,6 +475,9 @@ export function Carousel({
|
|
|
435
475
|
onMomentumScrollEnd={finishProgrammaticScroll}
|
|
436
476
|
>
|
|
437
477
|
{items.map((child, index) => {
|
|
478
|
+
const hasEqualizedHeight =
|
|
479
|
+
equalHeight && equalizedHeight != null && equalizedHeight > 0
|
|
480
|
+
|
|
438
481
|
// Strict slot box: width must be honored; never grow or shrink with
|
|
439
482
|
// content, and clip anything that misbehaves (e.g. a child whose
|
|
440
483
|
// inner flex layout would otherwise leak into the next slot on
|
|
@@ -445,7 +488,7 @@ export function Carousel({
|
|
|
445
488
|
flexShrink: 0,
|
|
446
489
|
flexBasis: effectiveItemWidth > 0 ? effectiveItemWidth : 'auto',
|
|
447
490
|
overflow: 'hidden',
|
|
448
|
-
...(
|
|
491
|
+
...(hasEqualizedHeight ? { height: equalizedHeight } : {}),
|
|
449
492
|
}
|
|
450
493
|
|
|
451
494
|
// The cloned style forces the child's outer node to also honor the
|
|
@@ -454,10 +497,10 @@ export function Carousel({
|
|
|
454
497
|
const childOverrideStyle: ViewStyle = {
|
|
455
498
|
width: effectiveItemWidth > 0 ? effectiveItemWidth : undefined,
|
|
456
499
|
maxWidth: effectiveItemWidth > 0 ? effectiveItemWidth : undefined,
|
|
457
|
-
flexGrow:
|
|
500
|
+
flexGrow: 0,
|
|
458
501
|
flexShrink: 0,
|
|
459
|
-
...(
|
|
460
|
-
? {
|
|
502
|
+
...(hasEqualizedHeight
|
|
503
|
+
? { height: '100%' as const, alignSelf: 'stretch' }
|
|
461
504
|
: {}),
|
|
462
505
|
}
|
|
463
506
|
|
|
@@ -474,7 +517,17 @@ export function Carousel({
|
|
|
474
517
|
|
|
475
518
|
return (
|
|
476
519
|
<View key={index} style={slotStyle}>
|
|
477
|
-
{
|
|
520
|
+
{/*
|
|
521
|
+
Measure intrinsic height on an unconstrained inner wrapper.
|
|
522
|
+
After equalization the outer slot is height-locked and this
|
|
523
|
+
inner fills it — further onLayouts are ignored.
|
|
524
|
+
*/}
|
|
525
|
+
<View
|
|
526
|
+
style={hasEqualizedHeight ? styles.equalizedFill : undefined}
|
|
527
|
+
onLayout={handleSlideLayout(index)}
|
|
528
|
+
>
|
|
529
|
+
{childWithModes}
|
|
530
|
+
</View>
|
|
478
531
|
</View>
|
|
479
532
|
)
|
|
480
533
|
})}
|
|
@@ -667,6 +720,12 @@ function AnimatedDot({
|
|
|
667
720
|
)
|
|
668
721
|
}
|
|
669
722
|
|
|
723
|
+
const styles = StyleSheet.create({
|
|
724
|
+
equalizedFill: {
|
|
725
|
+
flex: 1,
|
|
726
|
+
},
|
|
727
|
+
})
|
|
728
|
+
|
|
670
729
|
// ---------------------------------------------------------------------------
|
|
671
730
|
// Attach sub-components
|
|
672
731
|
// ---------------------------------------------------------------------------
|
|
@@ -162,6 +162,14 @@ const TextInputBase = forwardRef<RNTextInput, TextInputProps>(function TextInput
|
|
|
162
162
|
// Convert radius to React Native format (if 99999, use a large value for fully rounded)
|
|
163
163
|
const borderRadius = radius === 99999 ? 99999 : radius
|
|
164
164
|
|
|
165
|
+
// Row height from tokens: vertical padding above/below the text line.
|
|
166
|
+
// Applied as container `minHeight` (FormField pattern) — NOT as TextInput
|
|
167
|
+
// `lineHeight` / `minHeight`, which breaks iOS vertical centering.
|
|
168
|
+
const padV = typeof paddingVertical === 'number' ? paddingVertical : 0
|
|
169
|
+
const textLine = typeof lineHeight === 'number' ? lineHeight : 0
|
|
170
|
+
const iconH = typeof iconSize === 'number' ? iconSize : 0
|
|
171
|
+
const rowMinHeight = padV * 2 + Math.max(textLine, iconH)
|
|
172
|
+
|
|
165
173
|
const containerStyle: ViewStyle = {
|
|
166
174
|
flexDirection: 'row',
|
|
167
175
|
alignItems: 'center',
|
|
@@ -171,25 +179,25 @@ const TextInputBase = forwardRef<RNTextInput, TextInputProps>(function TextInput
|
|
|
171
179
|
borderStyle: 'solid' as const,
|
|
172
180
|
borderRadius,
|
|
173
181
|
paddingHorizontal,
|
|
174
|
-
|
|
182
|
+
// Same as FormField / SuggestiveSearch: the ROW owns height via minHeight;
|
|
183
|
+
// paddingVertical on the row would fight that and let the field stretch.
|
|
184
|
+
paddingVertical: 0,
|
|
185
|
+
minHeight: rowMinHeight > 0 ? rowMinHeight : undefined,
|
|
175
186
|
gap: 8,
|
|
176
187
|
}
|
|
177
188
|
|
|
178
189
|
const hoverStyle: ViewStyle = isHovered ? { opacity: 0.95 } : {}
|
|
179
190
|
const focusContainerStyle: ViewStyle = isFocused ? { borderColor: '#222', borderWidth: 1 } : {}
|
|
180
191
|
|
|
181
|
-
// ⚠️
|
|
182
|
-
// `minHeight: lineHeight` on this single-line input on iOS. Same contract as
|
|
183
|
-
// FormField / SuggestiveSearch — read before "simplifying".
|
|
192
|
+
// ⚠️ Same contract as FormField / SuggestiveSearch — do not "simplify".
|
|
184
193
|
//
|
|
185
|
-
// facebook/react-native#39145 / #28012
|
|
186
|
-
//
|
|
187
|
-
// ABOVE the glyphs
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
// font metrics and floats typed text above center.
|
|
194
|
+
// facebook/react-native#39145 / #28012 / #56774:
|
|
195
|
+
// Single-line iOS UITextField + `lineHeight` (or a tall field box) puts ALL
|
|
196
|
+
// extra space ABOVE the glyphs → placeholder/value sink. Documented fix:
|
|
197
|
+
// omit `lineHeight` on the single-line input; let the wrapper
|
|
198
|
+
// (`alignItems: 'center'` + `minHeight`) center it. UITextField centers
|
|
199
|
+
// natively when it is only as tall as its font. Do NOT pin lineHeight to
|
|
200
|
+
// fontSize (clips custom fonts / floats typed text).
|
|
193
201
|
const textInputStyle: TextStyle = {
|
|
194
202
|
flex: 1,
|
|
195
203
|
color: textColor,
|
package/src/icons/registry.ts
CHANGED