jfs-components 0.0.95 → 0.0.99

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.
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  import React, { useId, useMemo, useState } from 'react';
4
- import { StyleSheet, View } from 'react-native';
4
+ import { View } from 'react-native';
5
5
  import Animated, { interpolate, useAnimatedStyle } from 'react-native-reanimated';
6
6
  import Svg, { Defs, LinearGradient as SvgLinearGradient, Rect, Stop } from 'react-native-svg';
7
7
  import { getVariableByName } from '../design-tokens/figma-variables-resolver';
@@ -16,12 +16,20 @@ const SOLID_OVERLAY_COLOR = 'rgba(255, 255, 255, 1)';
16
16
  // `pointerEvents: 'none'` lives on the style (not the deprecated prop) so it
17
17
  // works on both native and React Native Web without warnings.
18
18
  const absoluteFillStyle = {
19
- ...StyleSheet.absoluteFillObject,
19
+ position: 'absolute',
20
+ top: 0,
21
+ left: 0,
22
+ right: 0,
23
+ bottom: 0,
20
24
  overflow: 'hidden',
21
25
  pointerEvents: 'none'
22
26
  };
23
27
  const solidOverlayStyle = {
24
- ...StyleSheet.absoluteFillObject,
28
+ position: 'absolute',
29
+ top: 0,
30
+ left: 0,
31
+ right: 0,
32
+ bottom: 0,
25
33
  backgroundColor: SOLID_OVERLAY_COLOR
26
34
  };
27
35
 
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- type DrawerProps = {
2
+ export type DrawerProps = {
3
3
  modes?: Record<string, any>;
4
4
  style?: import('react-native').StyleProp<import('react-native').ViewStyle>;
5
5
  title?: string;
@@ -11,7 +11,19 @@ type DrawerProps = {
11
11
  children?: React.ReactNode;
12
12
  collapsedHeight?: number;
13
13
  expandedRatio?: number;
14
+ /**
15
+ * Sets the drawer state when it first mounts (uncontrolled mode).
16
+ * Ignored once `state` is provided (controlled mode).
17
+ */
14
18
  initialState?: 'collapsed' | 'expanded';
19
+ /**
20
+ * Programmatically controls the drawer's collapsed/expanded state.
21
+ * When provided, the drawer becomes a controlled component: it will
22
+ * animate to match this value (reusing the same spring used by gestures)
23
+ * and gesture-driven changes are reported via `onStateChange` for the
24
+ * parent to apply back. Leave undefined for uncontrolled behaviour.
25
+ */
26
+ state?: 'collapsed' | 'expanded';
15
27
  contentStyle?: any;
16
28
  sheetStyle?: any;
17
29
  accessibilityLabel?: string;
@@ -31,9 +43,16 @@ type DrawerProps = {
31
43
  onStateChange?: (state: 'collapsed' | 'expanded') => void;
32
44
  };
33
45
  /**
34
- * Drawer component with nested scrolling support.
35
- * Uses react-native-gesture-handler and react-native-reanimated.
46
+ * Imperative handle exposed via `ref` for programmatic control of the drawer.
47
+ * Each method animates using the same spring as gesture-driven transitions.
36
48
  */
37
- declare function Drawer({ modes, style, title, header, children, collapsedHeight, expandedRatio, initialState, contentStyle, sheetStyle, accessibilityLabel, accessibilityHint, contentContainerStyle, showsVerticalScrollIndicator, bottomInset, onStateChange, }: DrawerProps): import("react/jsx-runtime").JSX.Element;
49
+ export type DrawerHandle = {
50
+ expand: () => void;
51
+ collapse: () => void;
52
+ toggle: () => void;
53
+ /** Current settled state of the drawer. */
54
+ getState: () => 'collapsed' | 'expanded';
55
+ };
56
+ declare const Drawer: React.ForwardRefExoticComponent<DrawerProps & React.RefAttributes<DrawerHandle>>;
38
57
  export default Drawer;
39
58
  //# sourceMappingURL=Drawer.d.ts.map
@@ -22,7 +22,7 @@ export { default as CardFinancialCondition, type CardFinancialConditionProps } f
22
22
  export { default as CardInsight, type CardInsightProps } from './CardInsight/CardInsight';
23
23
  export { default as Disclaimer } from './Disclaimer/Disclaimer';
24
24
  export { default as Divider, type DividerProps, type DividerDirection } from './Divider/Divider';
25
- export { default as Drawer } from './Drawer/Drawer';
25
+ export { default as Drawer, type DrawerProps, type DrawerHandle } from './Drawer/Drawer';
26
26
  export { default as Dropdown, DropdownItem, type DropdownProps, type DropdownItemProps } from './Dropdown/Dropdown';
27
27
  export { default as DropdownInput, type DropdownInputProps, type DropdownInputOption, type DropdownInputOptionValue } from './DropdownInput/DropdownInput';
28
28
  export { default as SuggestiveSearch, type SuggestiveSearchProps, type SuggestiveSearchOption, type SuggestiveSearchOptionValue, type SuggestiveSearchItem } from './SuggestiveSearch/SuggestiveSearch';
@@ -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-06-04T14:40:09.533Z
7
+ * Generated: 2026-06-08T14:11:35.036Z
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.95",
3
+ "version": "0.0.99",
4
4
  "description": "React Native Jio Finance Components Library",
5
5
  "author": "sunshuaiqi@gmail.com",
6
6
  "license": "MIT",
@@ -1,9 +1,8 @@
1
- import React, { useCallback, useEffect, useState, useRef } from 'react'
1
+ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'
2
2
  import { Platform, StyleSheet, Text, useWindowDimensions, View, ViewStyle } from 'react-native'
3
3
  import {
4
4
  Gesture,
5
5
  GestureDetector,
6
- GestureHandlerRootView,
7
6
  ScrollView,
8
7
  } from 'react-native-gesture-handler'
9
8
  import Animated, {
@@ -53,7 +52,7 @@ function rubberBand(value: number, min: number, max: number, friction: number =
53
52
  return value
54
53
  }
55
54
 
56
- type DrawerProps = {
55
+ export type DrawerProps = {
57
56
 
58
57
  modes?: Record<string, any>
59
58
  style?: import('react-native').StyleProp<import('react-native').ViewStyle>
@@ -66,7 +65,19 @@ type DrawerProps = {
66
65
  children?: React.ReactNode
67
66
  collapsedHeight?: number
68
67
  expandedRatio?: number
68
+ /**
69
+ * Sets the drawer state when it first mounts (uncontrolled mode).
70
+ * Ignored once `state` is provided (controlled mode).
71
+ */
69
72
  initialState?: 'collapsed' | 'expanded'
73
+ /**
74
+ * Programmatically controls the drawer's collapsed/expanded state.
75
+ * When provided, the drawer becomes a controlled component: it will
76
+ * animate to match this value (reusing the same spring used by gestures)
77
+ * and gesture-driven changes are reported via `onStateChange` for the
78
+ * parent to apply back. Leave undefined for uncontrolled behaviour.
79
+ */
80
+ state?: 'collapsed' | 'expanded'
70
81
  contentStyle?: any
71
82
  sheetStyle?: any
72
83
  accessibilityLabel?: string
@@ -86,11 +97,23 @@ type DrawerProps = {
86
97
  onStateChange?: (state: 'collapsed' | 'expanded') => void
87
98
  }
88
99
 
100
+ /**
101
+ * Imperative handle exposed via `ref` for programmatic control of the drawer.
102
+ * Each method animates using the same spring as gesture-driven transitions.
103
+ */
104
+ export type DrawerHandle = {
105
+ expand: () => void
106
+ collapse: () => void
107
+ toggle: () => void
108
+ /** Current settled state of the drawer. */
109
+ getState: () => 'collapsed' | 'expanded'
110
+ }
111
+
89
112
  /**
90
113
  * Drawer component with nested scrolling support.
91
114
  * Uses react-native-gesture-handler and react-native-reanimated.
92
115
  */
93
- function Drawer({
116
+ function DrawerInner({
94
117
 
95
118
  modes = EMPTY_MODES,
96
119
  style,
@@ -100,6 +123,7 @@ function Drawer({
100
123
  collapsedHeight = 200,
101
124
  expandedRatio = 0.90,
102
125
  initialState = 'collapsed',
126
+ state,
103
127
  contentStyle,
104
128
  sheetStyle,
105
129
  accessibilityLabel,
@@ -108,7 +132,7 @@ function Drawer({
108
132
  showsVerticalScrollIndicator = false,
109
133
  bottomInset = 80,
110
134
  onStateChange,
111
- }: DrawerProps) {
135
+ }: DrawerProps, ref: React.Ref<DrawerHandle>) {
112
136
  const { height: screenHeight } = useWindowDimensions()
113
137
 
114
138
  // Calculate snap points
@@ -168,15 +192,58 @@ function Drawer({
168
192
  translateY.value = withSpring(destination, SPRING_CONFIG)
169
193
  }, [translateY])
170
194
 
171
- // Update JS state for accessibility/logic if needed
195
+ // Update the JS-side mode. Pure: only schedules the state update. Side
196
+ // effects (notifying the parent via onStateChange) MUST NOT live inside the
197
+ // setState updater — doing so calls the parent's setState during React's
198
+ // render phase, which throws "Cannot update a component while rendering a
199
+ // different component" and causes an infinite update loop. We report changes
200
+ // from a dedicated effect below instead.
172
201
  const updateMode = useCallback((newMode: 'collapsed' | 'expanded') => {
173
- setMode((prev) => {
174
- if (prev !== newMode) {
175
- onStateChange?.(newMode)
176
- }
177
- return newMode
178
- })
179
- }, [onStateChange])
202
+ setMode(newMode)
203
+ }, [])
204
+
205
+ // Notify the parent exactly once per settled mode change, after commit.
206
+ const reportedModeRef = useRef(mode)
207
+ useEffect(() => {
208
+ if (reportedModeRef.current !== mode) {
209
+ reportedModeRef.current = mode
210
+ onStateChange?.(mode)
211
+ }
212
+ }, [mode, onStateChange])
213
+
214
+ // Programmatic transition (JS thread). Reuses the same spring as gestures:
215
+ // animates `translateY`, keeps the scroll-gate (`isFullyExpanded`) in sync,
216
+ // and updates `mode` (which notifies via the onStateChange effect above).
217
+ const applyMode = useCallback((newMode: 'collapsed' | 'expanded') => {
218
+ const target = newMode === 'expanded' ? minTranslateY : maxTranslateY
219
+ translateY.value = withSpring(target, SPRING_CONFIG)
220
+ isFullyExpanded.value = newMode === 'expanded'
221
+ setMode(newMode)
222
+ }, [minTranslateY, maxTranslateY, translateY, isFullyExpanded])
223
+
224
+ // Controlled mode: react ONLY to genuine changes of the `state` prop, tracked
225
+ // against its previous value. We must NOT reconcile `state` against the
226
+ // internal `mode` on every render: a gesture updates `mode` optimistically
227
+ // one render before the parent echoes it back through `onStateChange` into
228
+ // `state`, so a `state !== mode` check would "correct" the gesture and fight
229
+ // the echo, ping-ponging forever (Maximum update depth exceeded).
230
+ // `applyMode` is idempotent, so re-applying the already-current mode is a
231
+ // no-op when the parent simply mirrors our own change back.
232
+ const prevStateProp = useRef(state)
233
+ useEffect(() => {
234
+ if (state === undefined) return
235
+ if (state === prevStateProp.current) return
236
+ prevStateProp.current = state
237
+ applyMode(state)
238
+ }, [state, applyMode])
239
+
240
+ // Imperative API for parents holding a ref.
241
+ useImperativeHandle(ref, () => ({
242
+ expand: () => applyMode('expanded'),
243
+ collapse: () => applyMode('collapsed'),
244
+ toggle: () => applyMode(mode === 'expanded' ? 'collapsed' : 'expanded'),
245
+ getState: () => mode,
246
+ }), [applyMode, mode])
180
247
 
181
248
  // Gesture policy:
182
249
  // • activeOffsetY: require a clear *vertical* drag (10px) before this
@@ -362,7 +429,16 @@ function Drawer({
362
429
  const defaultAccessibilityLabel = accessibilityLabel || title || 'Drawer'
363
430
 
364
431
  return (
365
- <GestureHandlerRootView style={[styles.host, style]} pointerEvents="box-none">
432
+ // IMPORTANT: the host is a plain box-none View, NOT a GestureHandlerRootView.
433
+ // On Android, GestureHandlerRootView renders a *native* root view that
434
+ // intercepts every touch within its bounds (to feed the gesture system) and
435
+ // does NOT honor pointerEvents="box-none". Because this host fills the whole
436
+ // screen as an overlay, using GestureHandlerRootView here swallowed all
437
+ // touches to content rendered behind the drawer (buttons, page content).
438
+ // Per the standard react-native-gesture-handler architecture, a single
439
+ // GestureHandlerRootView must wrap the app root; this overlay only needs to
440
+ // let touches fall through where the sheet isn't.
441
+ <View style={[styles.host, style]} pointerEvents="box-none">
366
442
  <GestureDetector gesture={gesture}>
367
443
  <Animated.View
368
444
  style={[
@@ -457,7 +533,7 @@ function Drawer({
457
533
  </View>
458
534
  </Animated.View>
459
535
  </GestureDetector>
460
- </GestureHandlerRootView>
536
+ </View>
461
537
  )
462
538
  }
463
539
 
@@ -492,4 +568,7 @@ const styles = StyleSheet.create({
492
568
  },
493
569
  })
494
570
 
571
+ const Drawer = forwardRef<DrawerHandle, DrawerProps>(DrawerInner)
572
+ Drawer.displayName = 'Drawer'
573
+
495
574
  export default Drawer
@@ -7,6 +7,7 @@ import {
7
7
  type ViewStyle,
8
8
  type TextStyle,
9
9
  } from 'react-native'
10
+ import { useSafeAreaInsets } from 'react-native-safe-area-context'
10
11
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
11
12
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
12
13
  import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
@@ -260,6 +261,23 @@ function FullscreenModal({
260
261
 
261
262
  const rootGap = Number(getVariableByName('fullScreenModal/gap', modes)) || 16
262
263
 
264
+ // Safe-area insets so the floating chrome clears the system bars: the close
265
+ // button drops below the status bar / notch, and the sticky footer keeps its
266
+ // designed bottom padding ON TOP of the bottom inset (home indicator /
267
+ // Android gesture or nav bar). On web — and anywhere without a
268
+ // SafeAreaProvider — every inset is 0, so the layout is unchanged.
269
+ const insets = useSafeAreaInsets()
270
+ const closeButtonInsetStyle = useMemo<ViewStyle>(
271
+ () => ({ top: 12 + insets.top }),
272
+ [insets.top]
273
+ )
274
+ // Extend (not replace) the footer's token bottom padding by the bottom inset
275
+ // so the action button never sits under the system navigation area.
276
+ const footerInsetStyle = useMemo<ViewStyle>(() => {
277
+ const base = Number(getVariableByName('actionFooter/padding/bottom', modes)) || 41
278
+ return { paddingBottom: base + insets.bottom }
279
+ }, [modes, insets.bottom])
280
+
263
281
  // Drives the background's parallax-free sync with the scroll. The hero media
264
282
  // lives at the ROOT (so it is never clipped to the content height and sits
265
283
  // behind the transparent footer), but we translate it up by the exact scroll
@@ -388,7 +406,9 @@ function FullscreenModal({
388
406
  </Animated.ScrollView>
389
407
 
390
408
  {footerContent ? (
391
- <ActionFooter modes={modes}>{footerContent}</ActionFooter>
409
+ <ActionFooter modes={modes} style={footerInsetStyle}>
410
+ {footerContent}
411
+ </ActionFooter>
392
412
  ) : null}
393
413
 
394
414
  {showClose ? (
@@ -396,7 +416,7 @@ function FullscreenModal({
396
416
  iconName="ic_close"
397
417
  modes={modes}
398
418
  accessibilityLabel={closeAccessibilityLabel}
399
- style={closeButtonStyle}
419
+ style={[closeButtonStyle, closeButtonInsetStyle]}
400
420
  {...(onClose ? { onPress: onClose } : {})}
401
421
  />
402
422
  ) : null}
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect } from 'react'
2
- import { StyleSheet, View, type StyleProp, type ViewProps, type ViewStyle } from 'react-native'
2
+ import { View, type StyleProp, type ViewProps, type ViewStyle } from 'react-native'
3
3
  import Animated, {
4
4
  Easing,
5
5
  cancelAnimation,
@@ -171,7 +171,7 @@ const useSegmentRotation = (
171
171
  }
172
172
  }, [gravity, index, spreadMinRad, spreadMaxRad, spreadOutFrac])
173
173
 
174
- const fullSize: ViewStyle = { ...StyleSheet.absoluteFillObject }
174
+ const fullSize: ViewStyle = { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }
175
175
 
176
176
  function Spinner({
177
177
  size = DEFAULT_SIZE,
@@ -22,7 +22,7 @@ export { default as CardFinancialCondition, type CardFinancialConditionProps } f
22
22
  export { default as CardInsight, type CardInsightProps } from './CardInsight/CardInsight';
23
23
  export { default as Disclaimer } from './Disclaimer/Disclaimer';
24
24
  export { default as Divider, type DividerProps, type DividerDirection } from './Divider/Divider';
25
- export { default as Drawer } from './Drawer/Drawer';
25
+ export { default as Drawer, type DrawerProps, type DrawerHandle } from './Drawer/Drawer';
26
26
  export { default as Dropdown, DropdownItem, type DropdownProps, type DropdownItemProps } from './Dropdown/Dropdown';
27
27
  export { default as DropdownInput, type DropdownInputProps, type DropdownInputOption, type DropdownInputOptionValue } from './DropdownInput/DropdownInput';
28
28
  export { default as SuggestiveSearch, type SuggestiveSearchProps, type SuggestiveSearchOption, type SuggestiveSearchOptionValue, type SuggestiveSearchItem } from './SuggestiveSearch/SuggestiveSearch';
@@ -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-06-04T14:40:09.533Z
7
+ * Generated: 2026-06-08T14:11:35.036Z
8
8
  */
9
9
 
10
10
  // Icon name to SVG data mapping
@@ -1,7 +1,6 @@
1
1
  import React, { useId, useMemo, useState } from 'react'
2
2
  import {
3
3
  type LayoutChangeEvent,
4
- StyleSheet,
5
4
  View,
6
5
  type DimensionValue,
7
6
  type StyleProp,
@@ -60,13 +59,21 @@ const SOLID_OVERLAY_COLOR = 'rgba(255, 255, 255, 1)'
60
59
  // `pointerEvents: 'none'` lives on the style (not the deprecated prop) so it
61
60
  // works on both native and React Native Web without warnings.
62
61
  const absoluteFillStyle: ViewStyle = {
63
- ...StyleSheet.absoluteFillObject,
62
+ position: 'absolute',
63
+ top: 0,
64
+ left: 0,
65
+ right: 0,
66
+ bottom: 0,
64
67
  overflow: 'hidden',
65
68
  pointerEvents: 'none',
66
69
  }
67
70
 
68
71
  const solidOverlayStyle: ViewStyle = {
69
- ...StyleSheet.absoluteFillObject,
72
+ position: 'absolute',
73
+ top: 0,
74
+ left: 0,
75
+ right: 0,
76
+ bottom: 0,
70
77
  backgroundColor: SOLID_OVERLAY_COLOR,
71
78
  }
72
79