jfs-components 0.0.63 → 0.0.64

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.
@@ -22,6 +22,10 @@ declare function Section({ title, supportText, showSupportText, slot, slotDirect
22
22
  declare namespace Section {
23
23
  var Bento: typeof SectionBento;
24
24
  }
25
+ type BentoToggleRenderState = {
26
+ expanded: boolean;
27
+ toggle: () => void;
28
+ };
25
29
  type SectionBentoProps = {
26
30
  navSlot?: React.ReactNode;
27
31
  upiSlot?: React.ReactNode;
@@ -33,7 +37,44 @@ type SectionBentoProps = {
33
37
  * Web-specific accessibility props (only used on web platform)
34
38
  */
35
39
  webAccessibilityProps?: WebAccessibilityProps;
40
+ /**
41
+ * Total cell count visible when collapsed (real items + the toggle cell).
42
+ * Defaults to {@link SLOT_GRID_MAX_COLUMNS} (4) so the collapsed state fills
43
+ * exactly one row. When `navSlot.length <= collapsedCount`, expansion is
44
+ * disabled (no toggle injected, no animation wrappers — identical to the
45
+ * legacy behavior for back-compat).
46
+ */
47
+ collapsedCount?: number;
48
+ /**
49
+ * Uncontrolled initial expanded state. Ignored when `expanded` is provided.
50
+ * Defaults to `false`.
51
+ */
52
+ defaultExpanded?: boolean;
53
+ /**
54
+ * Controlled expanded state. When provided, `onExpandedChange` should also
55
+ * be provided so the component can request changes.
56
+ */
57
+ expanded?: boolean;
58
+ /**
59
+ * Called when the user taps the toggle. Required in controlled mode; ignored
60
+ * in uncontrolled mode unless you want to observe the change.
61
+ */
62
+ onExpandedChange?: (next: boolean) => void;
63
+ /** Label shown on the toggle cell when collapsed. Default `'More'`. */
64
+ toggleMoreLabel?: string;
65
+ /** Label shown on the toggle cell when expanded. Default `'Less'`. */
66
+ toggleLessLabel?: string;
67
+ /** Icon name shown on the toggle when collapsed. Default `'ic_chevron_down'`. */
68
+ toggleMoreIcon?: string;
69
+ /** Icon name shown on the toggle when expanded. Default `'ic_chevron_up'`. */
70
+ toggleLessIcon?: string;
71
+ /**
72
+ * Escape hatch: render a custom toggle cell instead of the default ListItem.
73
+ * The provided node is rendered in the toggle's grid slot. Wire `toggle()` to
74
+ * any tap interaction inside it. Height + per-cell animations still apply.
75
+ */
76
+ renderToggle?: (state: BentoToggleRenderState) => React.ReactNode;
36
77
  } & React.ComponentProps<typeof View>;
37
- declare function SectionBento({ navSlot, upiSlot, modes, style, accessibilityLabel: _accessibilityLabel, accessibilityHint, ...rest }: SectionBentoProps): import("react/jsx-runtime").JSX.Element;
78
+ declare function SectionBento({ navSlot, upiSlot, modes, style, accessibilityLabel: _accessibilityLabel, accessibilityHint, collapsedCount, defaultExpanded, expanded: controlledExpanded, onExpandedChange, toggleMoreLabel, toggleLessLabel, toggleMoreIcon, toggleLessIcon, renderToggle, ...rest }: SectionBentoProps): import("react/jsx-runtime").JSX.Element;
38
79
  export default Section;
39
80
  //# sourceMappingURL=Section.d.ts.map
@@ -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-04-20T08:18:55.051Z
7
+ * Generated: 2026-04-20T20:41:14.535Z
8
8
  */
9
9
  export declare const iconRegistry: Record<string, {
10
10
  path: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jfs-components",
3
- "version": "0.0.63",
3
+ "version": "0.0.64",
4
4
  "description": "React Native Jio Finance Components Library",
5
5
  "author": "sunshuaiqi@gmail.com",
6
6
  "license": "MIT",
@@ -1,5 +1,5 @@
1
1
  import React, { useCallback, useEffect, useState, useRef } from 'react'
2
- import { Platform, StyleSheet, Text, useWindowDimensions, View } from 'react-native'
2
+ import { Platform, StyleSheet, Text, useWindowDimensions, View, ViewStyle } from 'react-native'
3
3
  import {
4
4
  Gesture,
5
5
  GestureDetector,
@@ -167,10 +167,19 @@ function Drawer({
167
167
  setMode(newMode)
168
168
  }, [])
169
169
 
170
+ // Gesture policy:
171
+ // • activeOffsetY: require a clear *vertical* drag (10px) before this
172
+ // pan claims the gesture. Matches typical iOS scroll activation feel.
173
+ // • failOffsetX: if the finger crosses ~16px horizontally *before* we
174
+ // activate, surrender the gesture entirely so any horizontal child
175
+ // (FlatList horizontal, swiper, slider, etc.) can scroll cleanly
176
+ // without the drawer also translating on Y.
177
+ // • simultaneousWithExternalGesture(scrollRef): cooperate with the
178
+ // drawer's own internal vertical ScrollView for nested scrolling.
170
179
  const gesture = Gesture.Pan()
171
180
  .simultaneousWithExternalGesture(scrollRef)
172
- .activeOffsetY([-5, 5])
173
- .activeOffsetX([-5, 5])
181
+ .activeOffsetY([-10, 10])
182
+ .failOffsetX([-16, 16])
174
183
  .onStart(() => {
175
184
  context.value = { y: translateY.value }
176
185
  isDrawerActive.value = true
@@ -179,6 +188,16 @@ function Drawer({
179
188
  scrollTopTranslationOffset.value = 0
180
189
  })
181
190
  .onUpdate((event) => {
191
+ // Defense-in-depth: even after vertical activation, if the *current*
192
+ // motion is dominantly horizontal (e.g., the user activated with a
193
+ // small Y nudge and then curved into a horizontal swipe on a child
194
+ // carousel), don't translate the drawer this frame. failOffsetX
195
+ // already prevents activation in pure-horizontal swipes; this guards
196
+ // the diagonal-then-horizontal case.
197
+ if (Math.abs(event.translationX) > Math.abs(event.translationY) * 1.5) {
198
+ return
199
+ }
200
+
182
201
  // Logic for nested scrolling:
183
202
  // If we are at the expanded position (minTranslateY) AND content is
184
203
  // scrolled down (scrollY > 0), let the ScrollView handle the gesture.
@@ -299,6 +318,36 @@ function Drawer({
299
318
  const titleLineHeight = getVariableByName('drawer/title/lineHeight', modes) || 17
300
319
  const titlePaddingBottom = getVariableByName('drawer/titleWrap/padding/bottom', modes) || 8
301
320
 
321
+ // Drop shadow — Figma layers two shadows (primary + secondary) sharing
322
+ // the same offsetY/blur but with their own offsetX and color.
323
+ const shadowPrimaryOffsetX = (getVariableByName('drawer/shadow/primary/offsetX', modes) ?? 0) as number
324
+ const shadowPrimaryOffsetY = (getVariableByName('drawer/shadow/primary/offsetY', modes) ?? 16) as number
325
+ const shadowPrimaryBlur = (getVariableByName('drawer/shadow/primary/blur', modes) ?? 48) as number
326
+ const shadowPrimaryColor = (getVariableByName('drawer/shadow/primary/color', modes) ?? 'rgba(12, 13, 16, 0.16)') as string
327
+ const shadowSecondaryOffsetX = (getVariableByName('drawer/shadow/secondary/offsetX', modes) ?? 0) as number
328
+ const shadowSecondaryColor = (getVariableByName('drawer/shadow/secondary/color', modes) ?? 'rgba(12, 13, 16, 0.12)') as string
329
+
330
+ // Cross-platform shadow style. Web supports stacking two shadows via
331
+ // boxShadow. iOS only supports a single native shadow per view, so we
332
+ // apply the more prominent (primary) one. Android uses elevation.
333
+ const shadowStyle: ViewStyle = Platform.select({
334
+ web: {
335
+ boxShadow:
336
+ `${shadowSecondaryOffsetX}px ${shadowPrimaryOffsetY}px ${shadowPrimaryBlur}px 0px ${shadowSecondaryColor}, ` +
337
+ `${shadowPrimaryOffsetX}px ${shadowPrimaryOffsetY}px ${shadowPrimaryBlur}px 0px ${shadowPrimaryColor}`,
338
+ } as ViewStyle,
339
+ ios: {
340
+ shadowColor: shadowPrimaryColor,
341
+ shadowOffset: { width: shadowPrimaryOffsetX, height: shadowPrimaryOffsetY },
342
+ shadowOpacity: 1,
343
+ shadowRadius: shadowPrimaryBlur / 2,
344
+ },
345
+ android: {
346
+ elevation: 16,
347
+ },
348
+ default: {},
349
+ }) as ViewStyle
350
+
302
351
  const defaultAccessibilityLabel = accessibilityLabel || title || 'Drawer'
303
352
 
304
353
  return (
@@ -314,11 +363,8 @@ function Drawer({
314
363
  backgroundColor,
315
364
  borderTopLeftRadius: radius,
316
365
  borderTopRightRadius: radius,
317
- paddingLeft,
318
- paddingRight,
319
- paddingBottom,
320
- rowGap: drawerGap,
321
366
  },
367
+ shadowStyle,
322
368
  sheetStyle,
323
369
  animatedStyle,
324
370
  ]}
@@ -327,57 +373,73 @@ function Drawer({
327
373
  accessibilityLabel={undefined}
328
374
  accessibilityHint={accessibilityHint || 'Swipe up to expand, swipe down to collapse'}
329
375
  >
330
- {/* Handle Area */}
331
- <View style={[styles.handleArea, (!title && !header) && { paddingBottom: 0 }]}>
332
- <View
333
- style={[
334
- {
335
- backgroundColor: handleColor,
336
- width: handleWidth,
337
- height: handleHeight,
338
- borderRadius: handleRadius
339
- },
340
- ]}
341
- />
342
- </View>
343
-
344
- {/* Custom Header Slot */}
345
- {header}
346
-
347
- {/* Title (Legacy/Simple Mode) */}
348
- {title && (
349
- <Text
350
- style={[
351
- {
352
- color: titleColor,
353
- fontSize: titleSize,
354
- fontWeight: titleWeight as any,
355
- lineHeight: titleLineHeight,
356
- marginBottom: titlePaddingBottom,
357
- }
358
- ]}
359
- >
360
- {title}
361
- </Text>
362
- )}
363
-
364
- {/* Scrollable Content */}
365
- <AnimatedScrollView
366
- ref={scrollRef}
367
- style={[styles.content, contentStyle]}
368
- contentContainerStyle={[{ paddingBottom: paddingBottom + bottomInset, gap: drawerGap, flexDirection: 'column', alignItems: 'stretch' }, contentContainerStyle]}
369
- showsVerticalScrollIndicator={showsVerticalScrollIndicator}
370
- animatedProps={animatedScrollProps}
371
- alwaysBounceVertical={false}
372
- overScrollMode="always"
373
- onScroll={useAnimatedScrollHandler((event) => {
374
- scrollY.value = event.contentOffset.y
375
- })}
376
- scrollEventThrottle={16}
376
+ {/* Inner clip layer — keeps overflow:'hidden' off the shadow-carrying
377
+ outer view so iOS doesn't clip the drop shadow. */}
378
+ <View
379
+ style={[
380
+ styles.sheetInner,
381
+ {
382
+ borderTopLeftRadius: radius,
383
+ borderTopRightRadius: radius,
384
+ paddingLeft,
385
+ paddingRight,
386
+ paddingBottom,
387
+ rowGap: drawerGap,
388
+ },
389
+ ]}
377
390
  >
378
- {/* Prevent touch propagation for text selection if needed */}
379
- {children}
380
- </AnimatedScrollView>
391
+ {/* Handle Area */}
392
+ <View style={[styles.handleArea, (!title && !header) && { paddingBottom: 0 }]}>
393
+ <View
394
+ style={[
395
+ {
396
+ backgroundColor: handleColor,
397
+ width: handleWidth,
398
+ height: handleHeight,
399
+ borderRadius: handleRadius
400
+ },
401
+ ]}
402
+ />
403
+ </View>
404
+
405
+ {/* Custom Header Slot */}
406
+ {header}
407
+
408
+ {/* Title (Legacy/Simple Mode) */}
409
+ {title && (
410
+ <Text
411
+ style={[
412
+ {
413
+ color: titleColor,
414
+ fontSize: titleSize,
415
+ fontWeight: titleWeight as any,
416
+ lineHeight: titleLineHeight,
417
+ marginBottom: titlePaddingBottom,
418
+ }
419
+ ]}
420
+ >
421
+ {title}
422
+ </Text>
423
+ )}
424
+
425
+ {/* Scrollable Content */}
426
+ <AnimatedScrollView
427
+ ref={scrollRef}
428
+ style={[styles.content, contentStyle]}
429
+ contentContainerStyle={[{ paddingBottom: paddingBottom + bottomInset, gap: drawerGap, flexDirection: 'column', alignItems: 'stretch' }, contentContainerStyle]}
430
+ showsVerticalScrollIndicator={showsVerticalScrollIndicator}
431
+ animatedProps={animatedScrollProps}
432
+ alwaysBounceVertical={false}
433
+ overScrollMode="always"
434
+ onScroll={useAnimatedScrollHandler((event) => {
435
+ scrollY.value = event.contentOffset.y
436
+ })}
437
+ scrollEventThrottle={16}
438
+ >
439
+ {/* Prevent touch propagation for text selection if needed */}
440
+ {children}
441
+ </AnimatedScrollView>
442
+ </View>
381
443
  </Animated.View>
382
444
  </GestureDetector>
383
445
  </GestureHandlerRootView>
@@ -399,6 +461,9 @@ const styles = StyleSheet.create({
399
461
  width: '100%',
400
462
  position: 'absolute',
401
463
  top: 0,
464
+ },
465
+ sheetInner: {
466
+ flex: 1,
402
467
  overflow: 'hidden',
403
468
  },
404
469
  handleArea: {