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.
@@ -170,18 +170,34 @@ export function Dropdown({
170
170
  const shadowOffsetX = parseInt(getVariableByName('dropdown/shadow/offsetX', modes), 10) || 0;
171
171
  const shadowOffsetY = parseInt(getVariableByName('dropdown/shadow/offsetY', modes), 10) || 4;
172
172
  const shadowBlur = parseInt(getVariableByName('dropdown/shadow/blur', modes), 10) || 16;
173
- const containerStyle = {
173
+
174
+ // Shadow lives on the OUTER view, which must NOT set `overflow: 'hidden'`.
175
+ // On native, clipping a view also clips its shadow (iOS clips the layer
176
+ // shadow; Android clips the elevation shadow), so the soft popup shadow
177
+ // that renders fine on web (CSS box-shadow paints outside the box) would
178
+ // get cut off. The rounded-corner clipping is moved to a separate inner
179
+ // view below.
180
+ //
181
+ // The `boxShadow` style prop (RN 0.76+ / react-native-web) is used as the
182
+ // single source of truth so the designed color/offset/blur are honored on
183
+ // iOS, Android AND web. We intentionally do NOT also set the legacy
184
+ // `shadow*` / `elevation` props: on the new architecture (and web) those
185
+ // are translated into a box-shadow internally, so combining them with an
186
+ // explicit `boxShadow` would stack two shadows. Android's legacy
187
+ // `elevation` is also undesirable here because it ignores the shadow color
188
+ // and only paints a generic gray shadow.
189
+ const shadowStyle = {
174
190
  backgroundColor: background,
191
+ borderRadius: radius,
192
+ boxShadow: `${shadowOffsetX}px ${shadowOffsetY}px ${shadowBlur}px 0px ${shadowColor}`
193
+ };
194
+
195
+ // Inner view carries the rounded corners + clipping so list/scroll content
196
+ // stays inside the radius without affecting the outer view's shadow.
197
+ const clipStyle = {
175
198
  borderRadius: radius,
176
199
  overflow: 'hidden',
177
- shadowColor,
178
- shadowOffset: {
179
- width: shadowOffsetX,
180
- height: shadowOffsetY
181
- },
182
- shadowOpacity: 1,
183
- shadowRadius: shadowBlur / 2,
184
- elevation: 4
200
+ backgroundColor: background
185
201
  };
186
202
  const content = /*#__PURE__*/_jsx(View, {
187
203
  style: {
@@ -190,17 +206,20 @@ export function Dropdown({
190
206
  children: cloneChildrenWithModes(children, modes)
191
207
  });
192
208
  return /*#__PURE__*/_jsx(View, {
193
- style: [containerStyle, style],
209
+ style: [shadowStyle, style],
194
210
  accessibilityRole: "menu",
195
211
  accessibilityLabel: accessibilityLabel || 'Dropdown menu',
196
- children: maxHeight != null ? /*#__PURE__*/_jsx(ScrollView, {
197
- style: {
198
- maxHeight
199
- },
200
- showsVerticalScrollIndicator: true,
201
- keyboardShouldPersistTaps: "handled",
202
- children: content
203
- }) : content
212
+ children: /*#__PURE__*/_jsx(View, {
213
+ style: clipStyle,
214
+ children: maxHeight != null ? /*#__PURE__*/_jsx(ScrollView, {
215
+ style: {
216
+ maxHeight
217
+ },
218
+ showsVerticalScrollIndicator: true,
219
+ keyboardShouldPersistTaps: "handled",
220
+ children: content
221
+ }) : content
222
+ })
204
223
  });
205
224
  }
206
225
  export default Dropdown;
@@ -184,6 +184,7 @@ function FullscreenModal({
184
184
  heroMedia,
185
185
  heroHeight = 420,
186
186
  showClose = true,
187
+ closeOffsetY = 0,
187
188
  onClose,
188
189
  closeAccessibilityLabel = 'Close',
189
190
  footer,
@@ -220,8 +221,8 @@ function FullscreenModal({
220
221
  // SafeAreaProvider — every inset is 0, so the layout is unchanged.
221
222
  const insets = useSafeAreaInsets();
222
223
  const closeButtonInsetStyle = useMemo(() => ({
223
- top: 12 + insets.top
224
- }), [insets.top]);
224
+ top: 12 + insets.top + closeOffsetY
225
+ }), [insets.top, closeOffsetY]);
225
226
  // Extend (not replace) the footer's token bottom padding by the bottom inset
226
227
  // so the action button never sits under the system navigation area.
227
228
  const footerInsetStyle = useMemo(() => {
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+
3
+ import React from 'react';
4
+ import { View, Text } from 'react-native';
5
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
+ import { EMPTY_MODES } from '../../utils/react-utils';
7
+ import Avatar from '../Avatar/Avatar';
8
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
+ /**
10
+ * TestimonialsCard renders a compact, fixed-width card with a circular avatar,
11
+ * a bold title, and a body paragraph. It is typically used inside a horizontal
12
+ * carousel of customer testimonials.
13
+ *
14
+ * All styling values are resolved from Figma design tokens using the provided
15
+ * `modes`.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * <TestimonialsCard
20
+ * title="Aarav S."
21
+ * body="I was dreading renewing my car insurance, but JioFinance made it a breeze."
22
+ * />
23
+ * ```
24
+ */
25
+ function TestimonialsCard({
26
+ title = 'Title',
27
+ body = 'I was dreading renewing my car insurance, but JioFinance made it a breeze.',
28
+ modes = EMPTY_MODES,
29
+ style,
30
+ avatarProps,
31
+ accessibilityLabel
32
+ }) {
33
+ // Container tokens
34
+ const background = getVariableByName('testimonialsCard/background', modes) ?? '#ffffff';
35
+ const borderColor = getVariableByName('testimonialsCard/border/color', modes) ?? '#e8e8e8';
36
+ const radius = getVariableByName('testimonialsCard/radius', modes) ?? 8;
37
+ const gap = getVariableByName('testimonialsCard/gap', modes) ?? 8;
38
+ const paddingHorizontal = getVariableByName('testimonialsCard/padding/horizontal', modes) ?? 12;
39
+ const paddingVertical = getVariableByName('testimonialsCard/padding/vertical', modes) ?? 12;
40
+
41
+ // Title typography tokens
42
+ const titleColor = getVariableByName('testimonialsCard/subtitle/color', modes) ?? '#1a1c1f';
43
+ const titleFontSize = getVariableByName('testimonialsCard/title/fontSize', modes) ?? 14;
44
+ const titleFontFamily = getVariableByName('testimonialsCard/title/fontFamily', modes) ?? 'JioType Var';
45
+ const titleFontWeightRaw = getVariableByName('testimonialsCard/title/fontWeight', modes) ?? 700;
46
+ const titleFontWeight = typeof titleFontWeightRaw === 'number' ? titleFontWeightRaw.toString() : titleFontWeightRaw;
47
+
48
+ // Body typography tokens
49
+ const bodyColor = getVariableByName('testimonialsCard/title/color', modes) ?? '#010101';
50
+ const bodyFontSize = getVariableByName('testimonialsCard/subtitle/fontSize', modes) ?? 12;
51
+ const bodyLineHeight = getVariableByName('testimonialsCard/subtitle/lineHeight', modes) ?? 16;
52
+ const bodyFontFamily = getVariableByName('testimonialsCard/subtitle/fontFamily', modes) ?? 'JioType Var';
53
+ const bodyFontWeightRaw = getVariableByName('testimonialsCard/subtitle/fontWeight', modes) ?? 400;
54
+ const bodyFontWeight = typeof bodyFontWeightRaw === 'number' ? bodyFontWeightRaw.toString() : bodyFontWeightRaw;
55
+ const containerStyle = {
56
+ width: 180,
57
+ backgroundColor: background,
58
+ borderColor: borderColor,
59
+ borderWidth: 1,
60
+ borderRadius: radius,
61
+ paddingHorizontal: paddingHorizontal,
62
+ paddingVertical: paddingVertical,
63
+ alignItems: 'flex-start',
64
+ gap: gap
65
+ };
66
+ const titleTextStyle = {
67
+ color: titleColor,
68
+ fontSize: titleFontSize,
69
+ lineHeight: bodyLineHeight,
70
+ fontFamily: titleFontFamily,
71
+ fontWeight: titleFontWeight,
72
+ width: '100%'
73
+ };
74
+ const bodyTextStyle = {
75
+ color: bodyColor,
76
+ fontSize: bodyFontSize,
77
+ lineHeight: bodyLineHeight,
78
+ fontFamily: bodyFontFamily,
79
+ fontWeight: bodyFontWeight,
80
+ width: '100%'
81
+ };
82
+ const avatarModes = {
83
+ ...modes,
84
+ ...(avatarProps?.modes || {})
85
+ };
86
+ const resolvedAccessibilityLabel = accessibilityLabel ?? `Testimonial${title ? ` from ${title}` : ''}${body ? `: ${body}` : ''}`;
87
+ return /*#__PURE__*/_jsxs(View, {
88
+ style: [containerStyle, style],
89
+ accessibilityRole: "text",
90
+ accessibilityLabel: resolvedAccessibilityLabel,
91
+ children: [/*#__PURE__*/_jsx(Avatar, {
92
+ style: "Image",
93
+ modes: avatarModes,
94
+ ...avatarProps
95
+ }), /*#__PURE__*/_jsxs(View, {
96
+ style: textContainerStyle,
97
+ children: [!!title && /*#__PURE__*/_jsx(Text, {
98
+ style: titleTextStyle,
99
+ accessibilityElementsHidden: true,
100
+ importantForAccessibility: "no-hide-descendants",
101
+ children: title
102
+ }), !!body && /*#__PURE__*/_jsx(Text, {
103
+ style: bodyTextStyle,
104
+ accessibilityElementsHidden: true,
105
+ importantForAccessibility: "no-hide-descendants",
106
+ children: body
107
+ })]
108
+ })]
109
+ });
110
+ }
111
+ const textContainerStyle = {
112
+ width: '100%',
113
+ alignItems: 'flex-start',
114
+ gap: 4
115
+ };
116
+ export default /*#__PURE__*/React.memo(TestimonialsCard);
@@ -131,6 +131,7 @@ export { default as StatItem } from './StatItem/StatItem';
131
131
  export { default as StatGroup } from './StatGroup/StatGroup';
132
132
  export { default as StrengthIndicator } from './StrengthIndicator/StrengthIndicator';
133
133
  export { default as SummaryTile } from './SummaryTile/SummaryTile';
134
+ export { default as TestimonialsCard } from './TestimonialsCard/TestimonialsCard';
134
135
  export { default as Text } from './Text/Text';
135
136
  export { default as SegmentedControl } from './SegmentedControl/SegmentedControl';
136
137
  export { default as Toggle } from './Toggle/Toggle';