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 CHANGED
@@ -4,6 +4,16 @@ All notable changes to this project are documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
+ ## [0.1.0] - 2026-06-08
8
+
9
+ - Added `TestimonialsCard` — compact avatar + title + body card for testimonial carousels.
10
+ - `Drawer` — programmatic ref API (`expand` / `collapse` / `toggle` / `getState`), controlled `state` + `onStateChange`, touch-overlay and gesture-loop fixes.
11
+ - `FullscreenModal` — safe-area-aware footer and close button; new `closeOffsetY` prop.
12
+ - `Dropdown` — `boxShadow`-based popup shadow with inner clip view (fixes clipped shadow on native).
13
+ - `Spinner` / `Skeleton` — shimmer overlay uses explicit absolute positioning instead of `absoluteFillObject`.
14
+
15
+ ---
16
+
7
17
  ## [0.0.95] - 2026-06-04
8
18
 
9
19
  - Added `Icon` — token-driven design-system icon primitive (`iconName`, `source`, `children` slot); exported from the package barrel.
@@ -178,18 +178,34 @@ function Dropdown({
178
178
  const shadowOffsetX = parseInt((0, _figmaVariablesResolver.getVariableByName)('dropdown/shadow/offsetX', modes), 10) || 0;
179
179
  const shadowOffsetY = parseInt((0, _figmaVariablesResolver.getVariableByName)('dropdown/shadow/offsetY', modes), 10) || 4;
180
180
  const shadowBlur = parseInt((0, _figmaVariablesResolver.getVariableByName)('dropdown/shadow/blur', modes), 10) || 16;
181
- const containerStyle = {
181
+
182
+ // Shadow lives on the OUTER view, which must NOT set `overflow: 'hidden'`.
183
+ // On native, clipping a view also clips its shadow (iOS clips the layer
184
+ // shadow; Android clips the elevation shadow), so the soft popup shadow
185
+ // that renders fine on web (CSS box-shadow paints outside the box) would
186
+ // get cut off. The rounded-corner clipping is moved to a separate inner
187
+ // view below.
188
+ //
189
+ // The `boxShadow` style prop (RN 0.76+ / react-native-web) is used as the
190
+ // single source of truth so the designed color/offset/blur are honored on
191
+ // iOS, Android AND web. We intentionally do NOT also set the legacy
192
+ // `shadow*` / `elevation` props: on the new architecture (and web) those
193
+ // are translated into a box-shadow internally, so combining them with an
194
+ // explicit `boxShadow` would stack two shadows. Android's legacy
195
+ // `elevation` is also undesirable here because it ignores the shadow color
196
+ // and only paints a generic gray shadow.
197
+ const shadowStyle = {
182
198
  backgroundColor: background,
199
+ borderRadius: radius,
200
+ boxShadow: `${shadowOffsetX}px ${shadowOffsetY}px ${shadowBlur}px 0px ${shadowColor}`
201
+ };
202
+
203
+ // Inner view carries the rounded corners + clipping so list/scroll content
204
+ // stays inside the radius without affecting the outer view's shadow.
205
+ const clipStyle = {
183
206
  borderRadius: radius,
184
207
  overflow: 'hidden',
185
- shadowColor,
186
- shadowOffset: {
187
- width: shadowOffsetX,
188
- height: shadowOffsetY
189
- },
190
- shadowOpacity: 1,
191
- shadowRadius: shadowBlur / 2,
192
- elevation: 4
208
+ backgroundColor: background
193
209
  };
194
210
  const content = /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
195
211
  style: {
@@ -198,17 +214,20 @@ function Dropdown({
198
214
  children: (0, _reactUtils.cloneChildrenWithModes)(children, modes)
199
215
  });
200
216
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
201
- style: [containerStyle, style],
217
+ style: [shadowStyle, style],
202
218
  accessibilityRole: "menu",
203
219
  accessibilityLabel: accessibilityLabel || 'Dropdown menu',
204
- children: maxHeight != null ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
205
- style: {
206
- maxHeight
207
- },
208
- showsVerticalScrollIndicator: true,
209
- keyboardShouldPersistTaps: "handled",
210
- children: content
211
- }) : content
220
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
221
+ style: clipStyle,
222
+ children: maxHeight != null ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
223
+ style: {
224
+ maxHeight
225
+ },
226
+ showsVerticalScrollIndicator: true,
227
+ keyboardShouldPersistTaps: "handled",
228
+ children: content
229
+ }) : content
230
+ })
212
231
  });
213
232
  }
214
233
  var _default = exports.default = Dropdown;
@@ -189,6 +189,7 @@ function FullscreenModal({
189
189
  heroMedia,
190
190
  heroHeight = 420,
191
191
  showClose = true,
192
+ closeOffsetY = 0,
192
193
  onClose,
193
194
  closeAccessibilityLabel = 'Close',
194
195
  footer,
@@ -225,8 +226,8 @@ function FullscreenModal({
225
226
  // SafeAreaProvider — every inset is 0, so the layout is unchanged.
226
227
  const insets = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)();
227
228
  const closeButtonInsetStyle = (0, _react.useMemo)(() => ({
228
- top: 12 + insets.top
229
- }), [insets.top]);
229
+ top: 12 + insets.top + closeOffsetY
230
+ }), [insets.top, closeOffsetY]);
230
231
  // Extend (not replace) the footer's token bottom padding by the bottom inset
231
232
  // so the action button never sits under the system navigation area.
232
233
  const footerInsetStyle = (0, _react.useMemo)(() => {
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _react = _interopRequireDefault(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
10
+ var _reactUtils = require("../../utils/react-utils");
11
+ var _Avatar = _interopRequireDefault(require("../Avatar/Avatar"));
12
+ var _jsxRuntime = require("react/jsx-runtime");
13
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
+ /**
15
+ * TestimonialsCard renders a compact, fixed-width card with a circular avatar,
16
+ * a bold title, and a body paragraph. It is typically used inside a horizontal
17
+ * carousel of customer testimonials.
18
+ *
19
+ * All styling values are resolved from Figma design tokens using the provided
20
+ * `modes`.
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * <TestimonialsCard
25
+ * title="Aarav S."
26
+ * body="I was dreading renewing my car insurance, but JioFinance made it a breeze."
27
+ * />
28
+ * ```
29
+ */
30
+ function TestimonialsCard({
31
+ title = 'Title',
32
+ body = 'I was dreading renewing my car insurance, but JioFinance made it a breeze.',
33
+ modes = _reactUtils.EMPTY_MODES,
34
+ style,
35
+ avatarProps,
36
+ accessibilityLabel
37
+ }) {
38
+ // Container tokens
39
+ const background = (0, _figmaVariablesResolver.getVariableByName)('testimonialsCard/background', modes) ?? '#ffffff';
40
+ const borderColor = (0, _figmaVariablesResolver.getVariableByName)('testimonialsCard/border/color', modes) ?? '#e8e8e8';
41
+ const radius = (0, _figmaVariablesResolver.getVariableByName)('testimonialsCard/radius', modes) ?? 8;
42
+ const gap = (0, _figmaVariablesResolver.getVariableByName)('testimonialsCard/gap', modes) ?? 8;
43
+ const paddingHorizontal = (0, _figmaVariablesResolver.getVariableByName)('testimonialsCard/padding/horizontal', modes) ?? 12;
44
+ const paddingVertical = (0, _figmaVariablesResolver.getVariableByName)('testimonialsCard/padding/vertical', modes) ?? 12;
45
+
46
+ // Title typography tokens
47
+ const titleColor = (0, _figmaVariablesResolver.getVariableByName)('testimonialsCard/subtitle/color', modes) ?? '#1a1c1f';
48
+ const titleFontSize = (0, _figmaVariablesResolver.getVariableByName)('testimonialsCard/title/fontSize', modes) ?? 14;
49
+ const titleFontFamily = (0, _figmaVariablesResolver.getVariableByName)('testimonialsCard/title/fontFamily', modes) ?? 'JioType Var';
50
+ const titleFontWeightRaw = (0, _figmaVariablesResolver.getVariableByName)('testimonialsCard/title/fontWeight', modes) ?? 700;
51
+ const titleFontWeight = typeof titleFontWeightRaw === 'number' ? titleFontWeightRaw.toString() : titleFontWeightRaw;
52
+
53
+ // Body typography tokens
54
+ const bodyColor = (0, _figmaVariablesResolver.getVariableByName)('testimonialsCard/title/color', modes) ?? '#010101';
55
+ const bodyFontSize = (0, _figmaVariablesResolver.getVariableByName)('testimonialsCard/subtitle/fontSize', modes) ?? 12;
56
+ const bodyLineHeight = (0, _figmaVariablesResolver.getVariableByName)('testimonialsCard/subtitle/lineHeight', modes) ?? 16;
57
+ const bodyFontFamily = (0, _figmaVariablesResolver.getVariableByName)('testimonialsCard/subtitle/fontFamily', modes) ?? 'JioType Var';
58
+ const bodyFontWeightRaw = (0, _figmaVariablesResolver.getVariableByName)('testimonialsCard/subtitle/fontWeight', modes) ?? 400;
59
+ const bodyFontWeight = typeof bodyFontWeightRaw === 'number' ? bodyFontWeightRaw.toString() : bodyFontWeightRaw;
60
+ const containerStyle = {
61
+ width: 180,
62
+ backgroundColor: background,
63
+ borderColor: borderColor,
64
+ borderWidth: 1,
65
+ borderRadius: radius,
66
+ paddingHorizontal: paddingHorizontal,
67
+ paddingVertical: paddingVertical,
68
+ alignItems: 'flex-start',
69
+ gap: gap
70
+ };
71
+ const titleTextStyle = {
72
+ color: titleColor,
73
+ fontSize: titleFontSize,
74
+ lineHeight: bodyLineHeight,
75
+ fontFamily: titleFontFamily,
76
+ fontWeight: titleFontWeight,
77
+ width: '100%'
78
+ };
79
+ const bodyTextStyle = {
80
+ color: bodyColor,
81
+ fontSize: bodyFontSize,
82
+ lineHeight: bodyLineHeight,
83
+ fontFamily: bodyFontFamily,
84
+ fontWeight: bodyFontWeight,
85
+ width: '100%'
86
+ };
87
+ const avatarModes = {
88
+ ...modes,
89
+ ...(avatarProps?.modes || {})
90
+ };
91
+ const resolvedAccessibilityLabel = accessibilityLabel ?? `Testimonial${title ? ` from ${title}` : ''}${body ? `: ${body}` : ''}`;
92
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
93
+ style: [containerStyle, style],
94
+ accessibilityRole: "text",
95
+ accessibilityLabel: resolvedAccessibilityLabel,
96
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Avatar.default, {
97
+ style: "Image",
98
+ modes: avatarModes,
99
+ ...avatarProps
100
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
101
+ style: textContainerStyle,
102
+ children: [!!title && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
103
+ style: titleTextStyle,
104
+ accessibilityElementsHidden: true,
105
+ importantForAccessibility: "no-hide-descendants",
106
+ children: title
107
+ }), !!body && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
108
+ style: bodyTextStyle,
109
+ accessibilityElementsHidden: true,
110
+ importantForAccessibility: "no-hide-descendants",
111
+ children: body
112
+ })]
113
+ })]
114
+ });
115
+ }
116
+ const textContainerStyle = {
117
+ width: '100%',
118
+ alignItems: 'flex-start',
119
+ gap: 4
120
+ };
121
+ var _default = exports.default = /*#__PURE__*/_react.default.memo(TestimonialsCard);
@@ -723,6 +723,12 @@ Object.defineProperty(exports, "Tabs", {
723
723
  return _Tabs.default;
724
724
  }
725
725
  });
726
+ Object.defineProperty(exports, "TestimonialsCard", {
727
+ enumerable: true,
728
+ get: function () {
729
+ return _TestimonialsCard.default;
730
+ }
731
+ });
726
732
  Object.defineProperty(exports, "Text", {
727
733
  enumerable: true,
728
734
  get: function () {
@@ -981,6 +987,7 @@ var _StatItem = _interopRequireDefault(require("./StatItem/StatItem"));
981
987
  var _StatGroup = _interopRequireDefault(require("./StatGroup/StatGroup"));
982
988
  var _StrengthIndicator = _interopRequireDefault(require("./StrengthIndicator/StrengthIndicator"));
983
989
  var _SummaryTile = _interopRequireDefault(require("./SummaryTile/SummaryTile"));
990
+ var _TestimonialsCard = _interopRequireDefault(require("./TestimonialsCard/TestimonialsCard"));
984
991
  var _Text = _interopRequireDefault(require("./Text/Text"));
985
992
  var _SegmentedControl = _interopRequireDefault(require("./SegmentedControl/SegmentedControl"));
986
993
  var _Toggle = _interopRequireDefault(require("./Toggle/Toggle"));