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
@@ -0,0 +1,293 @@
1
+ "use strict";
2
+
3
+ import React from 'react';
4
+ import { View, Text } from 'react-native';
5
+ import Svg, { Line } from 'react-native-svg';
6
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
7
+ import { useTokens } from '../../design-tokens/JFSThemeProvider';
8
+ import { EMPTY_MODES } from '../../utils/react-utils';
9
+ import MetricLegendItem from '../MetricLegendItem/MetricLegendItem';
10
+
11
+ /**
12
+ * One vertical pill in the {@link AllocationComparisonChartProps.data} array.
13
+ *
14
+ * Each segment renders a single bar whose **height encodes `value`** (the
15
+ * "current" reading) and, when supplied, a **`baseline`** overlay drawn from
16
+ * the bottom up with a dashed marker line (the "recommended" reading). Both
17
+ * are measured against the same shared scale so bars and baselines are
18
+ * directly comparable across segments.
19
+ */
20
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
21
+ const DEFAULT_DATA = [{
22
+ label: 'Small & Mid',
23
+ value: 65,
24
+ baseline: 35
25
+ }, {
26
+ label: 'Large',
27
+ value: 25
28
+ }, {
29
+ label: 'Others',
30
+ value: 10
31
+ }];
32
+ const toNumber = (value, fallback) => {
33
+ if (typeof value === 'number') {
34
+ return Number.isFinite(value) ? value : fallback;
35
+ }
36
+ if (typeof value === 'string') {
37
+ const parsed = Number(value);
38
+ return Number.isFinite(parsed) ? parsed : fallback;
39
+ }
40
+ return fallback;
41
+ };
42
+ const toFontWeight = (value, fallback) => {
43
+ if (typeof value === 'number') {
44
+ return String(value);
45
+ }
46
+ if (typeof value === 'string') {
47
+ return value;
48
+ }
49
+ return fallback;
50
+ };
51
+ const isShown = node => node !== undefined && node !== null && node !== false;
52
+ /**
53
+ * Internal: one vertical pill column (the Figma "Segment Indicator"). Not
54
+ * exported — the ergonomic public unit is the chart driven by `data`. The
55
+ * `segmentIndicator/*` token names are mirrored here so design ↔ code token
56
+ * alignment is preserved.
57
+ */
58
+ function SegmentBar({
59
+ segment,
60
+ barHeightPx,
61
+ baselineHeightPx,
62
+ baselineLabel,
63
+ showMarker,
64
+ theme
65
+ }) {
66
+ const {
67
+ barWidth,
68
+ pillRadius,
69
+ gap,
70
+ currentColor,
71
+ baselineColor,
72
+ lineColor,
73
+ lineSize,
74
+ labelStyle
75
+ } = theme;
76
+ const fillColor = segment.color ?? currentColor;
77
+ const overlayColor = segment.baselineColor ?? baselineColor;
78
+ const showValueLabel = isShown(segment.valueLabel);
79
+ const hasBaseline = baselineHeightPx !== null && baselineHeightPx > 0;
80
+ const overlayHeight = hasBaseline ? Math.min(baselineHeightPx, barHeightPx) : 0;
81
+ const overlayRadius = Math.min(pillRadius, barWidth / 2, overlayHeight / 2);
82
+ return /*#__PURE__*/_jsxs(View, {
83
+ style: {
84
+ flex: 1,
85
+ alignItems: 'center',
86
+ justifyContent: 'flex-end',
87
+ gap
88
+ },
89
+ accessibilityLabel: segment.accessibilityLabel,
90
+ children: [showValueLabel ? /*#__PURE__*/_jsx(Text, {
91
+ numberOfLines: 1,
92
+ style: labelStyle,
93
+ children: segment.valueLabel
94
+ }) : null, /*#__PURE__*/_jsx(View, {
95
+ style: {
96
+ width: barWidth,
97
+ height: Math.max(barHeightPx, 1),
98
+ borderRadius: pillRadius,
99
+ backgroundColor: fillColor,
100
+ position: 'relative'
101
+ },
102
+ children: hasBaseline ? /*#__PURE__*/_jsxs(_Fragment, {
103
+ children: [/*#__PURE__*/_jsx(View, {
104
+ style: {
105
+ position: 'absolute',
106
+ left: 0,
107
+ right: 0,
108
+ bottom: 0,
109
+ height: overlayHeight,
110
+ backgroundColor: overlayColor,
111
+ borderBottomLeftRadius: overlayRadius,
112
+ borderBottomRightRadius: overlayRadius
113
+ }
114
+ }), showMarker ? /*#__PURE__*/_jsxs(View, {
115
+ style: {
116
+ position: 'absolute',
117
+ left: 0,
118
+ bottom: overlayHeight,
119
+ height: 0,
120
+ flexDirection: 'row',
121
+ alignItems: 'center'
122
+ },
123
+ pointerEvents: "none",
124
+ children: [/*#__PURE__*/_jsx(Svg, {
125
+ width: barWidth,
126
+ height: Math.max(lineSize, 1),
127
+ children: /*#__PURE__*/_jsx(Line, {
128
+ x1: 0,
129
+ y1: Math.max(lineSize, 1) / 2,
130
+ x2: barWidth,
131
+ y2: Math.max(lineSize, 1) / 2,
132
+ stroke: lineColor,
133
+ strokeWidth: lineSize,
134
+ strokeDasharray: "2 2"
135
+ })
136
+ }), isShown(baselineLabel) ? /*#__PURE__*/_jsx(Text, {
137
+ numberOfLines: 1,
138
+ style: [labelStyle, {
139
+ marginLeft: 6
140
+ }],
141
+ children: baselineLabel
142
+ }) : null]
143
+ }) : null]
144
+ }) : null
145
+ }), /*#__PURE__*/_jsx(Text, {
146
+ numberOfLines: 1,
147
+ style: labelStyle,
148
+ children: segment.label
149
+ })]
150
+ });
151
+ }
152
+
153
+ /**
154
+ * `AllocationComparisonChart` plots a row of vertical pill bars that compare a
155
+ * **current** reading (each bar's height) against an optional **recommended**
156
+ * baseline (a filled overlay drawn from the bottom up, marked with a dashed
157
+ * line). Every bar and baseline shares a single scale, so heights are directly
158
+ * comparable across segments — no axes required.
159
+ *
160
+ * The chart is driven entirely by the `data` array: each entry pairs a
161
+ * `value`, an optional `baseline` and its `label`, so a bar can never drift
162
+ * out of sync with its caption or its baseline marker.
163
+ *
164
+ * Colors, fonts, spacing and the pill radius resolve from the Figma
165
+ * `segmentIndicator/*`, `metricLegendItem/*` and `allocationComparisonChart/*`
166
+ * tokens via the `modes` prop.
167
+ *
168
+ * @component
169
+ */
170
+ function AllocationComparisonChart({
171
+ data = DEFAULT_DATA,
172
+ max,
173
+ height = 154,
174
+ barWidth,
175
+ showLegend = true,
176
+ valueLegendLabel = 'Current',
177
+ baselineLegendLabel = 'Recommended',
178
+ formatValue = value => `${value}%`,
179
+ modes: propModes = EMPTY_MODES,
180
+ style,
181
+ chartStyle,
182
+ legendStyle,
183
+ accessibilityLabel
184
+ }) {
185
+ const {
186
+ modes: globalModes
187
+ } = useTokens();
188
+ const modes = React.useMemo(() => ({
189
+ ...globalModes,
190
+ ...propModes
191
+ }), [globalModes, propModes]);
192
+ const trackWidth = toNumber(getVariableByName('segmentIndicator/track/width', modes), 28);
193
+ const resolvedBarWidth = barWidth ?? trackWidth;
194
+ const radiusToken = toNumber(getVariableByName('segmentIndicator/indicator/radius', modes), 99999);
195
+ const pillRadius = Math.min(radiusToken, resolvedBarWidth / 2);
196
+ const gap = toNumber(getVariableByName('segmentIndicator/gap', modes), 4);
197
+ const chartGap = toNumber(getVariableByName('allocationComparisonChart/gap', modes), 8);
198
+ const currentColor = getVariableByName('segmentIndicator/indicator/background', modes) ?? '#5d00b5';
199
+ const baselineColor = getVariableByName('segmentIndicator/indicator/foreground', modes) ?? '#b84fbd';
200
+ const lineColor = getVariableByName('segmentIndicator/indicator/line/color', modes) ?? '#ffffff';
201
+ const lineSize = toNumber(getVariableByName('segmentIndicator/indicator/line/size', modes), 1);
202
+ const foreground = getVariableByName('segmentIndicator/foreground', modes) ?? '#0c0d10';
203
+ const fontFamily = getVariableByName('segmentIndicator/fontFamily', modes) ?? 'JioType Var';
204
+ const fontSize = toNumber(getVariableByName('segmentIndicator/fontSize', modes), 12);
205
+ const lineHeight = toNumber(getVariableByName('segmentIndicator/lineHeight', modes), 12);
206
+ const fontWeight = toFontWeight(getVariableByName('segmentIndicator/fontWeight', modes), '400');
207
+ const labelStyle = {
208
+ color: foreground,
209
+ fontFamily,
210
+ fontSize,
211
+ lineHeight,
212
+ fontWeight,
213
+ textAlign: 'center'
214
+ };
215
+ const computedMax = max ?? data.reduce((acc, seg) => Math.max(acc, seg.value, seg.baseline ?? 0), 0);
216
+ const safeMax = computedMax > 0 ? computedMax : 1;
217
+ const firstBaselineIndex = data.findIndex(seg => typeof seg.baseline === 'number');
218
+ const hasAnyBaseline = firstBaselineIndex !== -1;
219
+ const theme = {
220
+ barWidth: resolvedBarWidth,
221
+ pillRadius,
222
+ gap,
223
+ currentColor,
224
+ baselineColor,
225
+ lineColor,
226
+ lineSize,
227
+ labelStyle
228
+ };
229
+ const defaultAccessibilityLabel = accessibilityLabel ?? `Allocation comparison of ${data.length} segment${data.length === 1 ? '' : 's'}: ` + data.map(seg => {
230
+ const label = typeof seg.label === 'string' ? seg.label : 'segment';
231
+ const base = typeof seg.baseline === 'number' ? `, recommended ${seg.baseline}` : '';
232
+ return `${label} ${seg.value}${base}`;
233
+ }).join('; ');
234
+ return /*#__PURE__*/_jsxs(View, {
235
+ style: [{
236
+ width: '100%'
237
+ }, style],
238
+ accessibilityLabel: defaultAccessibilityLabel,
239
+ children: [showLegend ? /*#__PURE__*/_jsxs(View, {
240
+ style: [{
241
+ flexDirection: 'row',
242
+ alignItems: 'center',
243
+ gap: 8,
244
+ marginBottom: chartGap
245
+ }, legendStyle],
246
+ children: [/*#__PURE__*/_jsx(MetricLegendItem, {
247
+ label: valueLegendLabel,
248
+ indicatorColor: currentColor,
249
+ modes: modes,
250
+ style: {
251
+ flexGrow: 0,
252
+ flexShrink: 1
253
+ }
254
+ }), hasAnyBaseline ? /*#__PURE__*/_jsx(MetricLegendItem, {
255
+ label: baselineLegendLabel,
256
+ indicatorColor: baselineColor,
257
+ modes: modes,
258
+ style: {
259
+ flexGrow: 0,
260
+ flexShrink: 1
261
+ }
262
+ }) : null]
263
+ }) : null, /*#__PURE__*/_jsx(View, {
264
+ accessibilityRole: "image",
265
+ style: [{
266
+ flexDirection: 'row',
267
+ alignItems: 'flex-end',
268
+ gap: 8,
269
+ width: '100%'
270
+ }, chartStyle],
271
+ children: data.map((segment, index) => {
272
+ const ratio = Math.max(0, Math.min(1, segment.value / safeMax));
273
+ const barHeightPx = Math.max(0, height * ratio);
274
+ const baselineHeightPx = typeof segment.baseline === 'number' ? Math.max(0, Math.min(1, segment.baseline / safeMax)) * height : null;
275
+ const baselineLabel = segment.baselineLabel === undefined ? typeof segment.baseline === 'number' ? formatValue(segment.baseline) : undefined : segment.baselineLabel;
276
+ const resolvedSegment = {
277
+ ...segment,
278
+ valueLabel: segment.valueLabel === undefined ? formatValue(segment.value) : segment.valueLabel
279
+ };
280
+ const showMarker = segment.showMarker ?? index === firstBaselineIndex;
281
+ return /*#__PURE__*/_jsx(SegmentBar, {
282
+ segment: resolvedSegment,
283
+ barHeightPx: barHeightPx,
284
+ baselineHeightPx: baselineHeightPx,
285
+ baselineLabel: baselineLabel,
286
+ showMarker: showMarker,
287
+ theme: theme
288
+ }, segment.key ?? `segment-${index}`);
289
+ })
290
+ })]
291
+ });
292
+ }
293
+ export default AllocationComparisonChart;
@@ -134,21 +134,31 @@ export default function AppBar({
134
134
  justifyContent: hasInFlowMiddle ? 'flex-start' : 'space-between'
135
135
  };
136
136
 
137
- // Absolutely-centered middle box for SubPage, mirroring the Figma geometry.
138
- // `left/top: 50%` + a negative translate keeps it centered regardless of the
139
- // bar width, while the fixed width clips overly-wide content (overflow:
140
- // hidden) instead of letting it bleed under the leading/actions slots.
141
- const subPageMiddleStyle = {
137
+ // Absolutely-positioned overlay for the SubPage middle slot. Instead of
138
+ // centering a fixed-height box with `top/left: 50%` + transform (which on
139
+ // Android resolves the percentage against an intermediate, content-driven
140
+ // parent height and lands a few px too high), the overlay STRETCHES to fill
141
+ // the whole bar (`top/bottom/left/right: 0`) and centers its content with
142
+ // flexbox. This uses the exact same full-height vertical reference as the
143
+ // leading/actions row (`alignItems: 'center'`), so the middle content always
144
+ // sits on the same line as the back arrow and end slot on every platform.
145
+ const subPageMiddleOverlayStyle = {
142
146
  position: 'absolute',
143
- top: '50%',
144
- left: '50%',
147
+ top: 0,
148
+ left: 0,
149
+ right: 0,
150
+ bottom: 0,
151
+ flexDirection: 'row',
152
+ alignItems: 'center',
153
+ justifyContent: 'center'
154
+ };
155
+
156
+ // Fixed-width clipping box (mirrors the Figma "slot wrap" geometry): keeps the
157
+ // middle content from bleeding under the leading/actions slots while staying
158
+ // centered within the overlay above.
159
+ const subPageMiddleBoxStyle = {
145
160
  width: middleSlotWidth,
146
161
  height: SUBPAGE_MIDDLE_HEIGHT,
147
- transform: [{
148
- translateX: -middleSlotWidth / 2
149
- }, {
150
- translateY: -SUBPAGE_MIDDLE_HEIGHT / 2
151
- }],
152
162
  flexDirection: 'row',
153
163
  alignItems: 'center',
154
164
  justifyContent: 'center',
@@ -183,19 +193,23 @@ export default function AppBar({
183
193
  style: actionsStyle,
184
194
  children: processedActions
185
195
  }), isSub && processedMiddle && /*#__PURE__*/_jsx(View, {
186
- style: subPageMiddleStyle,
196
+ style: subPageMiddleOverlayStyle,
187
197
  pointerEvents: "box-none",
188
198
  children: /*#__PURE__*/_jsx(View, {
189
- style: {
190
- flex: 1,
191
- minWidth: 1,
192
- height: '100%',
193
- flexDirection: 'row',
194
- alignItems: 'center',
195
- justifyContent: 'center'
196
- },
199
+ style: subPageMiddleBoxStyle,
197
200
  pointerEvents: "box-none",
198
- children: processedMiddle
201
+ children: /*#__PURE__*/_jsx(View, {
202
+ style: {
203
+ flex: 1,
204
+ minWidth: 1,
205
+ height: '100%',
206
+ flexDirection: 'row',
207
+ alignItems: 'center',
208
+ justifyContent: 'center'
209
+ },
210
+ pointerEvents: "box-none",
211
+ children: processedMiddle
212
+ })
199
213
  })
200
214
  })]
201
215
  });