jfs-components 0.0.84 → 0.0.86

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/lib/commonjs/components/AllocationComparisonChart/AllocationComparisonChart.js +299 -0
  3. package/lib/commonjs/components/AppBar/AppBar.js +36 -22
  4. package/lib/commonjs/components/AreaLineChart/AreaLineChart.js +866 -0
  5. package/lib/commonjs/components/AreaLineChart/chartMath.js +252 -0
  6. package/lib/commonjs/components/Attached/Attached.js +34 -4
  7. package/lib/commonjs/components/BubbleChart/BubbleChart.js +191 -0
  8. package/lib/commonjs/components/BubbleChart/bubblePacking.js +378 -0
  9. package/lib/commonjs/components/ClusterBubble/ClusterBubble.js +272 -0
  10. package/lib/commonjs/components/FullscreenModal/FullscreenModal.js +52 -89
  11. package/lib/commonjs/components/MetricLegendItem/MetricLegendItem.js +7 -1
  12. package/lib/commonjs/components/index.js +34 -0
  13. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  14. package/lib/commonjs/icons/registry.js +1 -1
  15. package/lib/module/components/AllocationComparisonChart/AllocationComparisonChart.js +293 -0
  16. package/lib/module/components/AppBar/AppBar.js +36 -22
  17. package/lib/module/components/AreaLineChart/AreaLineChart.js +859 -0
  18. package/lib/module/components/AreaLineChart/chartMath.js +242 -0
  19. package/lib/module/components/Attached/Attached.js +34 -4
  20. package/lib/module/components/BubbleChart/BubbleChart.js +185 -0
  21. package/lib/module/components/BubbleChart/bubblePacking.js +370 -0
  22. package/lib/module/components/ClusterBubble/ClusterBubble.js +267 -0
  23. package/lib/module/components/FullscreenModal/FullscreenModal.js +53 -90
  24. package/lib/module/components/MetricLegendItem/MetricLegendItem.js +7 -1
  25. package/lib/module/components/index.js +4 -0
  26. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  27. package/lib/module/icons/registry.js +1 -1
  28. package/lib/typescript/src/components/AllocationComparisonChart/AllocationComparisonChart.d.ts +118 -0
  29. package/lib/typescript/src/components/AreaLineChart/AreaLineChart.d.ts +212 -0
  30. package/lib/typescript/src/components/AreaLineChart/chartMath.d.ts +90 -0
  31. package/lib/typescript/src/components/BubbleChart/BubbleChart.d.ts +81 -0
  32. package/lib/typescript/src/components/BubbleChart/bubblePacking.d.ts +83 -0
  33. package/lib/typescript/src/components/ClusterBubble/ClusterBubble.d.ts +76 -0
  34. package/lib/typescript/src/components/FullscreenModal/FullscreenModal.d.ts +21 -25
  35. package/lib/typescript/src/components/MetricLegendItem/MetricLegendItem.d.ts +7 -1
  36. package/lib/typescript/src/components/index.d.ts +4 -0
  37. package/lib/typescript/src/icons/registry.d.ts +1 -1
  38. package/package.json +1 -1
  39. package/src/components/AllocationComparisonChart/AllocationComparisonChart.tsx +450 -0
  40. package/src/components/AppBar/AppBar.tsx +37 -24
  41. package/src/components/AreaLineChart/AreaLineChart.tsx +1161 -0
  42. package/src/components/AreaLineChart/chartMath.ts +265 -0
  43. package/src/components/Attached/Attached.tsx +36 -5
  44. package/src/components/BubbleChart/BubbleChart.tsx +319 -0
  45. package/src/components/BubbleChart/bubblePacking.ts +397 -0
  46. package/src/components/ClusterBubble/ClusterBubble.tsx +359 -0
  47. package/src/components/FullscreenModal/FullscreenModal.tsx +61 -119
  48. package/src/components/MetricLegendItem/MetricLegendItem.tsx +20 -6
  49. package/src/components/index.ts +4 -0
  50. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  51. package/src/icons/registry.ts +1 -1
@@ -1,8 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  import React, { useMemo } from 'react';
4
- import { View, Text, ScrollView } from 'react-native';
5
- import Animated, { Extrapolation, interpolate, useAnimatedScrollHandler, useAnimatedStyle, useSharedValue } from 'react-native-reanimated';
4
+ import { View, Text, ScrollView, StyleSheet } from 'react-native';
6
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
7
6
  import { useTokens } from '../../design-tokens/JFSThemeProvider';
8
7
  import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
@@ -29,22 +28,11 @@ const FULLSCREEN_MODAL_FORCED_MODES = Object.freeze({
29
28
  context5: 'Fullscreen Modal'
30
29
  });
31
30
 
32
- // Reanimated-driven ScrollView so the parallax handler runs on the UI thread.
33
- // Module scope so the wrapped component identity is stable across renders.
34
- const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView);
35
-
36
- // Parallax tuning. The hero collapses by HEIGHT only as the user scrolls up —
37
- // its full width is preserved and the media keeps a fixed aspect ratio (it is
38
- // cropped, never scaled or squished, like a `cover` background). When no
39
- // explicit `heroMinHeight` is given, the hero collapses to this fraction of
40
- // its resting height.
41
- const HERO_MIN_HEIGHT_RATIO = 0.45;
42
-
43
31
  // ---------------------------------------------------------------------------
44
32
  // Hero text — the eyebrow / headline / supporting / price block. Built inline
45
33
  // (rather than reusing <PageHero>) so we can render BOTH a supporting
46
34
  // paragraph AND a price line with the exact PageHero token gaps, and overlay
47
- // it on the parallax media without PageHero's media/button scaffolding.
35
+ // it on the hero media without PageHero's media/button scaffolding.
48
36
  // ---------------------------------------------------------------------------
49
37
 
50
38
  function HeroText({
@@ -129,8 +117,9 @@ function HeroText({
129
117
  }
130
118
 
131
119
  /**
132
- * FullscreenModal — a full-screen takeover surface with a parallax media hero,
133
- * a scrollable body, a floating close button, and a sticky `ActionFooter`.
120
+ * FullscreenModal — a full-screen takeover surface with a full-bleed media
121
+ * hero, a scrollable body, a floating close button, and a sticky
122
+ * `ActionFooter`.
134
123
  *
135
124
  * The component always themes itself with `context5: 'Fullscreen Modal'`
136
125
  * (non-overridable) so every nested component (Section, ListItem, Button,
@@ -138,14 +127,12 @@ function HeroText({
138
127
  * That mode is cascaded into `children`, the footer, and the hero text via
139
128
  * `cloneChildrenWithModes` / the merged `modes` object.
140
129
  *
141
- * ### Parallax
142
- * As the user scrolls up, the hero collapses by **height only** (from
143
- * `heroHeight` to `heroMinHeight`) its **full width is always preserved**.
144
- * The `heroMedia` is pinned to the top at a fixed size and `cover`-cropped by
145
- * the collapsing clip, so it keeps a perfect aspect ratio the whole time
146
- * (never scaled or squished). Because it collapses slower than the content
147
- * scrolls, the media lags behind for the parallax depth cue. Disable with
148
- * `parallax={false}`.
130
+ * ### Hero
131
+ * The `heroMedia` is rendered full modal width inside the scroll body and
132
+ * takes its height from its own aspect ratio. The hero text (eyebrow /
133
+ * headline / supporting / price) is overlaid on top, anchored to the bottom.
134
+ * The whole hero scrolls together with the rest of the content — there is no
135
+ * parallax effect.
149
136
  *
150
137
  * @component
151
138
  * @example
@@ -155,7 +142,7 @@ function HeroText({
155
142
  * headline="Get more from your money."
156
143
  * supportingText="JioFinance+ is your upgraded financial experience…"
157
144
  * priceText="₹999/year · ₹0 until 2027"
158
- * heroMedia={<LottiePlayer source={hero} size={{ width: 360, height: 420 }} />}
145
+ * heroMedia={<Image imageSource={hero} ratio={3 / 4} />}
159
146
  * primaryActionLabel="Upgrade for free"
160
147
  * disclaimer="By upgrading, we'll check your eligibility with Experian."
161
148
  * onPrimaryAction={() => upgrade()}
@@ -173,8 +160,6 @@ function FullscreenModal({
173
160
  priceText = '₹999/year · ₹0 until 2027',
174
161
  heroMedia,
175
162
  heroHeight = 420,
176
- heroMinHeight,
177
- parallax = true,
178
163
  showClose = true,
179
164
  onClose,
180
165
  closeAccessibilityLabel = 'Close',
@@ -201,49 +186,13 @@ function FullscreenModal({
201
186
  ...FULLSCREEN_MODAL_FORCED_MODES
202
187
  }), [globalModes, propModes]);
203
188
  const rootGap = Number(getVariableByName('fullScreenModal/gap', modes)) || 16;
204
- const minHeight = heroMinHeight ?? Math.round(heroHeight * HERO_MIN_HEIGHT_RATIO);
205
- const scrollY = useSharedValue(0);
206
- const onScroll = useAnimatedScrollHandler(event => {
207
- scrollY.value = event.contentOffset.y;
208
- });
209
-
210
- // Collapse the hero by HEIGHT only as the user scrolls up. The clip's width
211
- // never changes and the media inside is pinned full-size at the top, so the
212
- // art is cropped (cover) rather than scaled or narrowed — it keeps a perfect
213
- // aspect ratio the whole time. Pull-down (negative offset) is clamped, so the
214
- // hero never grows past its resting height.
215
- const heroAnimatedStyle = useAnimatedStyle(() => {
216
- const height = interpolate(scrollY.value, [0, heroHeight], [heroHeight, minHeight], Extrapolation.CLAMP);
217
- return {
218
- height
219
- };
220
- });
221
189
  const processedHeroMedia = useMemo(() => heroMedia ? cloneChildrenWithModes(heroMedia, modes, FULLSCREEN_MODAL_FORCED_MODES) : null, [heroMedia, modes]);
222
190
  const processedChildren = useMemo(() => children ? cloneChildrenWithModes(children, modes, FULLSCREEN_MODAL_FORCED_MODES) : null, [children, modes]);
223
191
 
224
- // The clip is full-width and top-pinned; its height is what animates. Width
225
- // is intentionally never animated.
226
- const heroClipBaseStyle = useMemo(() => ({
227
- position: 'absolute',
228
- top: 0,
229
- left: 0,
230
- right: 0,
231
- overflow: 'hidden'
232
- }), []);
233
-
234
- // The media sits at a fixed full-size box pinned to the top of the clip, so
235
- // the collapsing clip crops it from the bottom (cover) instead of resizing
236
- // it. Full width, fixed height — a perfect, constant aspect ratio.
237
- const heroMediaWrapStyle = useMemo(() => ({
238
- position: 'absolute',
239
- top: 0,
240
- left: 0,
241
- right: 0,
242
- height: heroHeight,
243
- alignItems: 'stretch'
244
- }), [heroHeight]);
245
- const heroTextRegionStyle = useMemo(() => ({
246
- height: heroHeight,
192
+ // No-media fallback: without hero media the text region needs an explicit
193
+ // resting height (driven by `heroHeight`) so the hero still has presence.
194
+ const heroTextFallbackStyle = useMemo(() => ({
195
+ minHeight: heroHeight,
247
196
  justifyContent: 'flex-end',
248
197
  paddingHorizontal: 16,
249
198
  paddingBottom: 16
@@ -254,15 +203,28 @@ function FullscreenModal({
254
203
  paddingTop: rootGap,
255
204
  paddingBottom: 24
256
205
  }, contentContainerStyle], [backgroundColor, rootGap, contentContainerStyle]);
257
- const heroClip = /*#__PURE__*/_jsx(Animated.View, {
258
- style: [heroClipBaseStyle, parallax ? heroAnimatedStyle : {
259
- height: heroHeight
260
- }],
261
- pointerEvents: "none",
262
- children: /*#__PURE__*/_jsx(View, {
263
- style: heroMediaWrapStyle,
264
- children: processedHeroMedia
265
- })
206
+ const heroTextNode = /*#__PURE__*/_jsx(HeroText, {
207
+ eyebrow: eyebrow,
208
+ headline: headline,
209
+ supportingText: supportingText,
210
+ priceText: priceText,
211
+ modes: modes
212
+ });
213
+
214
+ // The hero scrolls inline with the body (no parallax). When media is present
215
+ // it is laid out full modal width and takes its height from its own aspect
216
+ // ratio; the hero text is overlaid on top, anchored to the bottom. Without
217
+ // media the text simply renders in flow at the fallback height.
218
+ const hero = processedHeroMedia ? /*#__PURE__*/_jsxs(View, {
219
+ style: heroMediaContainerStyle,
220
+ children: [processedHeroMedia, /*#__PURE__*/_jsx(View, {
221
+ style: heroTextOverlayStyle,
222
+ pointerEvents: "box-none",
223
+ children: heroTextNode
224
+ })]
225
+ }) : /*#__PURE__*/_jsx(View, {
226
+ style: heroTextFallbackStyle,
227
+ children: heroTextNode
266
228
  });
267
229
 
268
230
  // Footer: a fully custom node, or the default Button + Disclaimer column.
@@ -291,26 +253,15 @@ function FullscreenModal({
291
253
  backgroundColor
292
254
  }, style],
293
255
  testID: testID,
294
- children: [processedHeroMedia ? heroClip : null, /*#__PURE__*/_jsxs(AnimatedScrollView, {
256
+ children: [/*#__PURE__*/_jsxs(ScrollView, {
295
257
  style: scrollViewStyle,
296
258
  contentContainerStyle: scrollContentStyle,
297
- showsVerticalScrollIndicator: false,
298
- onScroll: onScroll,
299
- scrollEventThrottle: 16
259
+ showsVerticalScrollIndicator: false
300
260
  // Tap an input in the body and it focuses on the FIRST tap, even when
301
261
  // the keyboard is already open (default 'never' eats that tap).
302
262
  ,
303
263
  keyboardShouldPersistTaps: "handled",
304
- children: [/*#__PURE__*/_jsx(View, {
305
- style: heroTextRegionStyle,
306
- children: /*#__PURE__*/_jsx(HeroText, {
307
- eyebrow: eyebrow,
308
- headline: headline,
309
- supportingText: supportingText,
310
- priceText: priceText,
311
- modes: modes
312
- })
313
- }), /*#__PURE__*/_jsx(View, {
264
+ children: [hero, /*#__PURE__*/_jsx(View, {
314
265
  style: bodyStyle,
315
266
  children: processedChildren
316
267
  })]
@@ -349,4 +300,16 @@ const closeButtonStyle = {
349
300
  top: 12,
350
301
  right: 12
351
302
  };
303
+ // Full-width hero wrapper; height comes from the media's own aspect ratio.
304
+ const heroMediaContainerStyle = {
305
+ width: '100%',
306
+ position: 'relative'
307
+ };
308
+ // Hero text overlaid on the media, anchored to the bottom edge.
309
+ const heroTextOverlayStyle = {
310
+ ...StyleSheet.absoluteFillObject,
311
+ justifyContent: 'flex-end',
312
+ paddingHorizontal: 16,
313
+ paddingBottom: 16
314
+ };
352
315
  export default FullscreenModal;
@@ -17,6 +17,7 @@ function MetricLegendItem({
17
17
  label = 'Current (4 months)',
18
18
  value,
19
19
  indicatorColor,
20
+ indicatorShape = 'dot',
20
21
  modes = EMPTY_MODES,
21
22
  style,
22
23
  indicatorStyle,
@@ -49,7 +50,12 @@ function MetricLegendItem({
49
50
  }, style],
50
51
  accessibilityRole: "text",
51
52
  children: [/*#__PURE__*/_jsx(View, {
52
- style: [{
53
+ style: [indicatorShape === 'line' ? {
54
+ width: indicatorSize * 2,
55
+ height: Math.max(2, Math.round(indicatorSize / 4)),
56
+ borderRadius: indicatorRadius,
57
+ backgroundColor: indicatorBg
58
+ } : {
53
59
  width: indicatorSize,
54
60
  height: indicatorSize,
55
61
  borderRadius: indicatorRadius,
@@ -39,6 +39,7 @@ export { default as CircularProgressBarDoted } from './CircularProgressBarDoted/
39
39
  export { default as CircularRating } from './CircularRating/CircularRating';
40
40
  export { default as CoverageRing } from './CoverageRing/CoverageRing';
41
41
  export { default as CoverageBarComparison } from './CoverageBarComparison/CoverageBarComparison';
42
+ export { default as AllocationComparisonChart } from './AllocationComparisonChart/AllocationComparisonChart';
42
43
  export { default as MonthlyStatusGrid, CalendarGlyph } from './MonthlyStatusGrid/MonthlyStatusGrid';
43
44
  export { default as Gauge } from './Gauge/Gauge';
44
45
  export { default as HoldingsCard } from './HoldingsCard/HoldingsCard';
@@ -102,6 +103,9 @@ export { default as RadioButton } from './RadioButton/RadioButton';
102
103
  export { default as RechargeCard } from './RechargeCard/RechargeCard';
103
104
  export { default as SavingsGoalSummary } from './SavingsGoalSummary/SavingsGoalSummary';
104
105
  export { default as DonutChart, DonutChartSegment } from './DonutChart/DonutChart';
106
+ export { default as AreaLineChart, useChart } from './AreaLineChart/AreaLineChart';
107
+ export { default as ClusterBubble } from './ClusterBubble/ClusterBubble';
108
+ export { default as BubbleChart } from './BubbleChart/BubbleChart';
105
109
  export { default as DonutChartSummary } from './DonutChartSummary/DonutChartSummary';
106
110
  export { default as RangeTrack } from './RangeTrack/RangeTrack';
107
111
  export { default as SegmentedTrack, SegmentedTrackSegment } from './SegmentedTrack/SegmentedTrack';