jfs-components 0.0.99 → 0.1.0

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.
@@ -29,6 +29,12 @@ export type FullscreenModalProps = {
29
29
  heroHeight?: number;
30
30
  /** Whether to render the floating close button (top-right). Defaults to true. */
31
31
  showClose?: boolean;
32
+ /**
33
+ * Additional vertical offset (in px) applied to the floating close button's
34
+ * top position, on top of the base inset (`12 + safe-area top`). Positive
35
+ * values push it further down. Defaults to 0.
36
+ */
37
+ closeOffsetY?: number;
32
38
  /** Press handler for the close button. */
33
39
  onClose?: () => void;
34
40
  /** Accessibility label for the close button. */
@@ -104,6 +110,6 @@ export type FullscreenModalProps = {
104
110
  * </FullscreenModal>
105
111
  * ```
106
112
  */
107
- declare function FullscreenModal({ eyebrow, headline, supportingText, priceText, heroMedia, heroHeight, showClose, onClose, closeAccessibilityLabel, footer, primaryActionLabel, onPrimaryAction, disclaimer, children, modes: propModes, style, contentContainerStyle, testID, }: FullscreenModalProps): import("react/jsx-runtime").JSX.Element;
113
+ declare function FullscreenModal({ eyebrow, headline, supportingText, priceText, heroMedia, heroHeight, showClose, closeOffsetY, onClose, closeAccessibilityLabel, footer, primaryActionLabel, onPrimaryAction, disclaimer, children, modes: propModes, style, contentContainerStyle, testID, }: FullscreenModalProps): import("react/jsx-runtime").JSX.Element;
108
114
  export default FullscreenModal;
109
115
  //# sourceMappingURL=FullscreenModal.d.ts.map
@@ -0,0 +1,51 @@
1
+ import React from 'react';
2
+ import { type ViewStyle, type StyleProp } from 'react-native';
3
+ import { type AvatarProps } from '../Avatar/Avatar';
4
+ export type TestimonialsCardProps = {
5
+ /**
6
+ * The testimonial heading, typically the author's name.
7
+ */
8
+ title?: string;
9
+ /**
10
+ * The testimonial body copy.
11
+ */
12
+ body?: string;
13
+ /**
14
+ * Mode configuration passed to the token resolver. Also forwarded to the
15
+ * Avatar child for consistent theming.
16
+ */
17
+ modes?: Record<string, any>;
18
+ /**
19
+ * Optional style overrides for the card container.
20
+ */
21
+ style?: StyleProp<ViewStyle>;
22
+ /**
23
+ * Props forwarded to the Avatar component (e.g. a custom `imageSource`).
24
+ */
25
+ avatarProps?: Partial<AvatarProps>;
26
+ /**
27
+ * Accessibility label for the card region. Falls back to a label generated
28
+ * from the title and body.
29
+ */
30
+ accessibilityLabel?: string;
31
+ };
32
+ /**
33
+ * TestimonialsCard renders a compact, fixed-width card with a circular avatar,
34
+ * a bold title, and a body paragraph. It is typically used inside a horizontal
35
+ * carousel of customer testimonials.
36
+ *
37
+ * All styling values are resolved from Figma design tokens using the provided
38
+ * `modes`.
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * <TestimonialsCard
43
+ * title="Aarav S."
44
+ * body="I was dreading renewing my car insurance, but JioFinance made it a breeze."
45
+ * />
46
+ * ```
47
+ */
48
+ declare function TestimonialsCard({ title, body, modes, style, avatarProps, accessibilityLabel, }: TestimonialsCardProps): import("react/jsx-runtime").JSX.Element;
49
+ declare const _default: React.MemoExoticComponent<typeof TestimonialsCard>;
50
+ export default _default;
51
+ //# sourceMappingURL=TestimonialsCard.d.ts.map
@@ -130,6 +130,7 @@ export { default as StatItem, type StatItemProps, type StatItemLabelPosition } f
130
130
  export { default as StatGroup, type StatGroupProps, type StatGroupItem } from './StatGroup/StatGroup';
131
131
  export { default as StrengthIndicator, type StrengthIndicatorProps, type StrengthIndicatorConfidence, type StrengthIndicatorConfidenceValue } from './StrengthIndicator/StrengthIndicator';
132
132
  export { default as SummaryTile, type SummaryTileProps } from './SummaryTile/SummaryTile';
133
+ export { default as TestimonialsCard, type TestimonialsCardProps } from './TestimonialsCard/TestimonialsCard';
133
134
  export { default as Text, type TextProps } from './Text/Text';
134
135
  export { default as SegmentedControl, type SegmentedControlProps, type SegmentedControlItem } from './SegmentedControl/SegmentedControl';
135
136
  export { default as Toggle, type ToggleProps } from './Toggle/Toggle';
@@ -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-08T14:11:35.036Z
7
+ * Generated: 2026-06-09T13:55:40.250Z
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.99",
3
+ "version": "0.1.0",
4
4
  "description": "React Native Jio Finance Components Library",
5
5
  "author": "sunshuaiqi@gmail.com",
6
6
  "license": "MIT",
@@ -290,15 +290,33 @@ export function Dropdown({
290
290
  const shadowBlur =
291
291
  parseInt(getVariableByName('dropdown/shadow/blur', modes), 10) || 16
292
292
 
293
- const containerStyle: ViewStyle = {
293
+ // Shadow lives on the OUTER view, which must NOT set `overflow: 'hidden'`.
294
+ // On native, clipping a view also clips its shadow (iOS clips the layer
295
+ // shadow; Android clips the elevation shadow), so the soft popup shadow
296
+ // that renders fine on web (CSS box-shadow paints outside the box) would
297
+ // get cut off. The rounded-corner clipping is moved to a separate inner
298
+ // view below.
299
+ //
300
+ // The `boxShadow` style prop (RN 0.76+ / react-native-web) is used as the
301
+ // single source of truth so the designed color/offset/blur are honored on
302
+ // iOS, Android AND web. We intentionally do NOT also set the legacy
303
+ // `shadow*` / `elevation` props: on the new architecture (and web) those
304
+ // are translated into a box-shadow internally, so combining them with an
305
+ // explicit `boxShadow` would stack two shadows. Android's legacy
306
+ // `elevation` is also undesirable here because it ignores the shadow color
307
+ // and only paints a generic gray shadow.
308
+ const shadowStyle: ViewStyle & { boxShadow?: string } = {
294
309
  backgroundColor: background,
310
+ borderRadius: radius,
311
+ boxShadow: `${shadowOffsetX}px ${shadowOffsetY}px ${shadowBlur}px 0px ${shadowColor}`,
312
+ }
313
+
314
+ // Inner view carries the rounded corners + clipping so list/scroll content
315
+ // stays inside the radius without affecting the outer view's shadow.
316
+ const clipStyle: ViewStyle = {
295
317
  borderRadius: radius,
296
318
  overflow: 'hidden',
297
- shadowColor,
298
- shadowOffset: { width: shadowOffsetX, height: shadowOffsetY },
299
- shadowOpacity: 1,
300
- shadowRadius: shadowBlur / 2,
301
- elevation: 4,
319
+ backgroundColor: background,
302
320
  }
303
321
 
304
322
  const content = (
@@ -309,21 +327,23 @@ export function Dropdown({
309
327
 
310
328
  return (
311
329
  <View
312
- style={[containerStyle, style]}
330
+ style={[shadowStyle, style]}
313
331
  accessibilityRole="menu"
314
332
  accessibilityLabel={accessibilityLabel || 'Dropdown menu'}
315
333
  >
316
- {maxHeight != null ? (
317
- <ScrollView
318
- style={{ maxHeight }}
319
- showsVerticalScrollIndicator={true}
320
- keyboardShouldPersistTaps="handled"
321
- >
322
- {content}
323
- </ScrollView>
324
- ) : (
325
- content
326
- )}
334
+ <View style={clipStyle}>
335
+ {maxHeight != null ? (
336
+ <ScrollView
337
+ style={{ maxHeight }}
338
+ showsVerticalScrollIndicator={true}
339
+ keyboardShouldPersistTaps="handled"
340
+ >
341
+ {content}
342
+ </ScrollView>
343
+ ) : (
344
+ content
345
+ )}
346
+ </View>
327
347
  </View>
328
348
  )
329
349
  }
@@ -71,6 +71,12 @@ export type FullscreenModalProps = {
71
71
  heroHeight?: number
72
72
  /** Whether to render the floating close button (top-right). Defaults to true. */
73
73
  showClose?: boolean
74
+ /**
75
+ * Additional vertical offset (in px) applied to the floating close button's
76
+ * top position, on top of the base inset (`12 + safe-area top`). Positive
77
+ * values push it further down. Defaults to 0.
78
+ */
79
+ closeOffsetY?: number
74
80
  /** Press handler for the close button. */
75
81
  onClose?: () => void
76
82
  /** Accessibility label for the close button. */
@@ -230,6 +236,7 @@ function FullscreenModal({
230
236
  heroMedia,
231
237
  heroHeight = 420,
232
238
  showClose = true,
239
+ closeOffsetY = 0,
233
240
  onClose,
234
241
  closeAccessibilityLabel = 'Close',
235
242
  footer,
@@ -268,8 +275,8 @@ function FullscreenModal({
268
275
  // SafeAreaProvider — every inset is 0, so the layout is unchanged.
269
276
  const insets = useSafeAreaInsets()
270
277
  const closeButtonInsetStyle = useMemo<ViewStyle>(
271
- () => ({ top: 12 + insets.top }),
272
- [insets.top]
278
+ () => ({ top: 12 + insets.top + closeOffsetY }),
279
+ [insets.top, closeOffsetY]
273
280
  )
274
281
  // Extend (not replace) the footer's token bottom padding by the bottom inset
275
282
  // so the action button never sits under the system navigation area.
@@ -0,0 +1,162 @@
1
+ import React from 'react'
2
+ import {
3
+ View,
4
+ Text,
5
+ type ViewStyle,
6
+ type TextStyle,
7
+ type StyleProp,
8
+ } from 'react-native'
9
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
10
+ import { EMPTY_MODES } from '../../utils/react-utils'
11
+ import Avatar, { type AvatarProps } from '../Avatar/Avatar'
12
+
13
+ export type TestimonialsCardProps = {
14
+ /**
15
+ * The testimonial heading, typically the author's name.
16
+ */
17
+ title?: string
18
+ /**
19
+ * The testimonial body copy.
20
+ */
21
+ body?: string
22
+ /**
23
+ * Mode configuration passed to the token resolver. Also forwarded to the
24
+ * Avatar child for consistent theming.
25
+ */
26
+ modes?: Record<string, any>
27
+ /**
28
+ * Optional style overrides for the card container.
29
+ */
30
+ style?: StyleProp<ViewStyle>
31
+ /**
32
+ * Props forwarded to the Avatar component (e.g. a custom `imageSource`).
33
+ */
34
+ avatarProps?: Partial<AvatarProps>
35
+ /**
36
+ * Accessibility label for the card region. Falls back to a label generated
37
+ * from the title and body.
38
+ */
39
+ accessibilityLabel?: string
40
+ }
41
+
42
+ /**
43
+ * TestimonialsCard renders a compact, fixed-width card with a circular avatar,
44
+ * a bold title, and a body paragraph. It is typically used inside a horizontal
45
+ * carousel of customer testimonials.
46
+ *
47
+ * All styling values are resolved from Figma design tokens using the provided
48
+ * `modes`.
49
+ *
50
+ * @example
51
+ * ```tsx
52
+ * <TestimonialsCard
53
+ * title="Aarav S."
54
+ * body="I was dreading renewing my car insurance, but JioFinance made it a breeze."
55
+ * />
56
+ * ```
57
+ */
58
+ function TestimonialsCard({
59
+ title = 'Title',
60
+ body = 'I was dreading renewing my car insurance, but JioFinance made it a breeze.',
61
+ modes = EMPTY_MODES,
62
+ style,
63
+ avatarProps,
64
+ accessibilityLabel,
65
+ }: TestimonialsCardProps) {
66
+ // Container tokens
67
+ const background = getVariableByName('testimonialsCard/background', modes) ?? '#ffffff'
68
+ const borderColor = getVariableByName('testimonialsCard/border/color', modes) ?? '#e8e8e8'
69
+ const radius = getVariableByName('testimonialsCard/radius', modes) ?? 8
70
+ const gap = getVariableByName('testimonialsCard/gap', modes) ?? 8
71
+ const paddingHorizontal = getVariableByName('testimonialsCard/padding/horizontal', modes) ?? 12
72
+ const paddingVertical = getVariableByName('testimonialsCard/padding/vertical', modes) ?? 12
73
+
74
+ // Title typography tokens
75
+ const titleColor = getVariableByName('testimonialsCard/subtitle/color', modes) ?? '#1a1c1f'
76
+ const titleFontSize = getVariableByName('testimonialsCard/title/fontSize', modes) ?? 14
77
+ const titleFontFamily = getVariableByName('testimonialsCard/title/fontFamily', modes) ?? 'JioType Var'
78
+ const titleFontWeightRaw = getVariableByName('testimonialsCard/title/fontWeight', modes) ?? 700
79
+ const titleFontWeight =
80
+ typeof titleFontWeightRaw === 'number' ? titleFontWeightRaw.toString() : titleFontWeightRaw
81
+
82
+ // Body typography tokens
83
+ const bodyColor = getVariableByName('testimonialsCard/title/color', modes) ?? '#010101'
84
+ const bodyFontSize = getVariableByName('testimonialsCard/subtitle/fontSize', modes) ?? 12
85
+ const bodyLineHeight = getVariableByName('testimonialsCard/subtitle/lineHeight', modes) ?? 16
86
+ const bodyFontFamily = getVariableByName('testimonialsCard/subtitle/fontFamily', modes) ?? 'JioType Var'
87
+ const bodyFontWeightRaw = getVariableByName('testimonialsCard/subtitle/fontWeight', modes) ?? 400
88
+ const bodyFontWeight =
89
+ typeof bodyFontWeightRaw === 'number' ? bodyFontWeightRaw.toString() : bodyFontWeightRaw
90
+
91
+ const containerStyle: ViewStyle = {
92
+ width: 180,
93
+ backgroundColor: background as string,
94
+ borderColor: borderColor as string,
95
+ borderWidth: 1,
96
+ borderRadius: radius as number,
97
+ paddingHorizontal: paddingHorizontal as number,
98
+ paddingVertical: paddingVertical as number,
99
+ alignItems: 'flex-start',
100
+ gap: gap as number,
101
+ }
102
+
103
+ const titleTextStyle: TextStyle = {
104
+ color: titleColor as string,
105
+ fontSize: titleFontSize as number,
106
+ lineHeight: bodyLineHeight as number,
107
+ fontFamily: titleFontFamily as string,
108
+ fontWeight: titleFontWeight as TextStyle['fontWeight'],
109
+ width: '100%',
110
+ }
111
+
112
+ const bodyTextStyle: TextStyle = {
113
+ color: bodyColor as string,
114
+ fontSize: bodyFontSize as number,
115
+ lineHeight: bodyLineHeight as number,
116
+ fontFamily: bodyFontFamily as string,
117
+ fontWeight: bodyFontWeight as TextStyle['fontWeight'],
118
+ width: '100%',
119
+ }
120
+
121
+ const avatarModes = {
122
+ ...modes,
123
+ ...(avatarProps?.modes || {}),
124
+ }
125
+
126
+ const resolvedAccessibilityLabel =
127
+ accessibilityLabel ?? `Testimonial${title ? ` from ${title}` : ''}${body ? `: ${body}` : ''}`
128
+
129
+ return (
130
+ <View
131
+ style={[containerStyle, style]}
132
+ accessibilityRole="text"
133
+ accessibilityLabel={resolvedAccessibilityLabel}
134
+ >
135
+ <Avatar
136
+ style="Image"
137
+ modes={avatarModes}
138
+ {...avatarProps}
139
+ />
140
+ <View style={textContainerStyle}>
141
+ {!!title && (
142
+ <Text style={titleTextStyle} accessibilityElementsHidden importantForAccessibility="no-hide-descendants">
143
+ {title}
144
+ </Text>
145
+ )}
146
+ {!!body && (
147
+ <Text style={bodyTextStyle} accessibilityElementsHidden importantForAccessibility="no-hide-descendants">
148
+ {body}
149
+ </Text>
150
+ )}
151
+ </View>
152
+ </View>
153
+ )
154
+ }
155
+
156
+ const textContainerStyle: ViewStyle = {
157
+ width: '100%',
158
+ alignItems: 'flex-start',
159
+ gap: 4,
160
+ }
161
+
162
+ export default React.memo(TestimonialsCard)
@@ -149,6 +149,7 @@ export { default as StatItem, type StatItemProps, type StatItemLabelPosition } f
149
149
  export { default as StatGroup, type StatGroupProps, type StatGroupItem } from './StatGroup/StatGroup';
150
150
  export { default as StrengthIndicator, type StrengthIndicatorProps, type StrengthIndicatorConfidence, type StrengthIndicatorConfidenceValue } from './StrengthIndicator/StrengthIndicator';
151
151
  export { default as SummaryTile, type SummaryTileProps } from './SummaryTile/SummaryTile';
152
+ export { default as TestimonialsCard, type TestimonialsCardProps } from './TestimonialsCard/TestimonialsCard';
152
153
  export { default as Text, type TextProps } from './Text/Text';
153
154
  export { default as SegmentedControl, type SegmentedControlProps, type SegmentedControlItem } from './SegmentedControl/SegmentedControl';
154
155
  export { default as Toggle, type ToggleProps } from './Toggle/Toggle';
@@ -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-08T14:11:35.036Z
7
+ * Generated: 2026-06-09T13:55:40.250Z
8
8
  */
9
9
 
10
10
  // Icon name to SVG data mapping