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.
- package/CHANGELOG.md +10 -0
- package/lib/commonjs/components/Dropdown/Dropdown.js +37 -18
- package/lib/commonjs/components/FullscreenModal/FullscreenModal.js +3 -2
- package/lib/commonjs/components/TestimonialsCard/TestimonialsCard.js +121 -0
- package/lib/commonjs/components/index.js +7 -0
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/Dropdown/Dropdown.js +37 -18
- package/lib/module/components/FullscreenModal/FullscreenModal.js +3 -2
- package/lib/module/components/TestimonialsCard/TestimonialsCard.js +116 -0
- package/lib/module/components/index.js +1 -0
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/FullscreenModal/FullscreenModal.d.ts +7 -1
- package/lib/typescript/src/components/TestimonialsCard/TestimonialsCard.d.ts +51 -0
- package/lib/typescript/src/components/index.d.ts +1 -0
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/Dropdown/Dropdown.tsx +38 -18
- package/src/components/FullscreenModal/FullscreenModal.tsx +9 -2
- package/src/components/TestimonialsCard/TestimonialsCard.tsx +162 -0
- package/src/components/index.ts +1 -0
- package/src/icons/registry.ts +1 -1
|
@@ -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-
|
|
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
|
@@ -290,15 +290,33 @@ export function Dropdown({
|
|
|
290
290
|
const shadowBlur =
|
|
291
291
|
parseInt(getVariableByName('dropdown/shadow/blur', modes), 10) || 16
|
|
292
292
|
|
|
293
|
-
|
|
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
|
-
|
|
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={[
|
|
330
|
+
style={[shadowStyle, style]}
|
|
313
331
|
accessibilityRole="menu"
|
|
314
332
|
accessibilityLabel={accessibilityLabel || 'Dropdown menu'}
|
|
315
333
|
>
|
|
316
|
-
{
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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)
|
package/src/components/index.ts
CHANGED
|
@@ -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';
|
package/src/icons/registry.ts
CHANGED