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,866 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ exports.useChart = useChart;
8
+ var _react = _interopRequireWildcard(require("react"));
9
+ var _reactNative = require("react-native");
10
+ var _reactNativeSvg = _interopRequireWildcard(require("react-native-svg"));
11
+ var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
12
+ var _JFSThemeProvider = require("../../design-tokens/JFSThemeProvider");
13
+ var _reactUtils = require("../../utils/react-utils");
14
+ var _MetricLegendItem = _interopRequireDefault(require("../MetricLegendItem/MetricLegendItem"));
15
+ var _chartMath = require("./chartMath");
16
+ var _jsxRuntime = require("react/jsx-runtime");
17
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
18
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
19
+ // --- Public types ---------------------------------------------------------
20
+
21
+ /** A single data point. Bare numbers are also accepted in `data`. */
22
+
23
+ /** One line+area series. Pass one for a single chart, several to overlap. */
24
+
25
+ // --- Internal resolved types ----------------------------------------------
26
+
27
+ const ChartContext = /*#__PURE__*/(0, _react.createContext)(null);
28
+
29
+ /** Access the surrounding chart geometry from a decorator/sub-component. */
30
+ function useChart() {
31
+ const ctx = (0, _react.useContext)(ChartContext);
32
+ if (!ctx) {
33
+ throw new Error('AreaLineChart sub-components must be used within <AreaLineChart>');
34
+ }
35
+ return ctx;
36
+ }
37
+
38
+ // --- Helpers ---------------------------------------------------------------
39
+
40
+ const DEFAULT_APPEARANCE_CYCLE = ['Primary', 'Secondary', 'Tertiary', 'Quaternary', 'Quinary', 'Senary'];
41
+ const DEFAULT_INSET = {
42
+ top: 16,
43
+ bottom: 8,
44
+ left: 8,
45
+ right: 8
46
+ };
47
+ const toNumber = (value, fallback) => {
48
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
49
+ if (typeof value === 'string') {
50
+ const parsed = Number(value);
51
+ if (Number.isFinite(parsed)) return parsed;
52
+ }
53
+ return fallback;
54
+ };
55
+ const toFontWeight = (value, fallback) => {
56
+ if (typeof value === 'number') return String(value);
57
+ if (typeof value === 'string') return value;
58
+ return fallback;
59
+ };
60
+ const appearanceFor = index => DEFAULT_APPEARANCE_CYCLE[index % DEFAULT_APPEARANCE_CYCLE.length];
61
+
62
+ /** Resolve a series' strong (line/dot) color via the `dataViz/bg` token. */
63
+ const resolveLineColor = (color, appearance, modes) => {
64
+ if (color) return color;
65
+ return (0, _figmaVariablesResolver.getVariableByName)('dataViz/bg', {
66
+ ...modes,
67
+ 'Appearance / DataViz': appearance,
68
+ 'Emphasis / DataViz': 'High'
69
+ }) ?? '#5d00b5';
70
+ };
71
+
72
+ /** Resolve a series' light area-fill color via the `dataViz/bg` token. */
73
+ const resolveAreaColor = (color, lineColor, appearance, modes) => {
74
+ if (color) return color;
75
+ return (0, _figmaVariablesResolver.getVariableByName)('dataViz/bg', {
76
+ ...modes,
77
+ 'Appearance / DataViz': appearance,
78
+ 'Emphasis / DataViz': 'Low'
79
+ }) ?? lineColor;
80
+ };
81
+ const defaultFormatY = value => String(value);
82
+ const defaultFormatX = label => String(label);
83
+
84
+ // --- Main component --------------------------------------------------------
85
+
86
+ /**
87
+ * `AreaLineChart` is a lightweight, token-driven area/line chart built
88
+ * entirely on `react-native-svg`. A single `series` renders one filled
89
+ * area with a line on top (plus an optional goal pin); multiple `series`
90
+ * overlap with an automatic legend. It supports smooth/linear curves,
91
+ * dashed "projected" segments, a configurable grid and axes, and a
92
+ * cross-platform crosshair tooltip (hover on web, press-drag on native).
93
+ *
94
+ * The reusable building blocks (`AreaLineChart.Grid`, `.XAxis`, `.YAxis`,
95
+ * `.GoalPin`) read the shared chart geometry through `useChart()`, so you
96
+ * can also compose them manually or add your own SVG decorators as
97
+ * children.
98
+ *
99
+ * @component
100
+ */
101
+ function AreaLineChart({
102
+ series,
103
+ xLabels,
104
+ yMin,
105
+ yMax,
106
+ numberOfTicks = 4,
107
+ curve = 'linear',
108
+ height = 218,
109
+ contentInset,
110
+ showGrid = true,
111
+ showXAxis = true,
112
+ showYAxis = true,
113
+ showLegend = true,
114
+ showDots = false,
115
+ formatX = defaultFormatX,
116
+ formatY = defaultFormatY,
117
+ formatValue,
118
+ goalPin,
119
+ activeIndex: activeIndexProp,
120
+ defaultActiveIndex = null,
121
+ onActiveIndexChange,
122
+ interactive = true,
123
+ modes: propModes = _reactUtils.EMPTY_MODES,
124
+ style,
125
+ children,
126
+ accessibilityLabel
127
+ }) {
128
+ const {
129
+ modes: globalModes
130
+ } = (0, _JFSThemeProvider.useTokens)();
131
+ const modes = (0, _react.useMemo)(() => ({
132
+ ...globalModes,
133
+ ...propModes
134
+ }), [globalModes, propModes]);
135
+ const inset = (0, _react.useMemo)(() => ({
136
+ ...DEFAULT_INSET,
137
+ ...(contentInset || {})
138
+ }), [contentInset]);
139
+
140
+ // Plot width is measured; height is fixed by the prop.
141
+ const [plotWidth, setPlotWidth] = (0, _react.useState)(0);
142
+ const handlePlotLayout = (0, _react.useCallback)(e => {
143
+ const w = e.nativeEvent.layout.width;
144
+ setPlotWidth(prev => Math.abs(prev - w) > 0.5 ? w : prev);
145
+ }, []);
146
+
147
+ // Active index (controlled or uncontrolled).
148
+ const isControlled = activeIndexProp !== undefined;
149
+ const [uncontrolledActive, setUncontrolledActive] = (0, _react.useState)(defaultActiveIndex);
150
+ const activeIndex = isControlled ? activeIndexProp : uncontrolledActive;
151
+ const setActiveIndex = (0, _react.useCallback)(index => {
152
+ if (!isControlled) setUncontrolledActive(index);
153
+ onActiveIndexChange?.(index);
154
+ }, [isControlled, onActiveIndexChange]);
155
+
156
+ // Resolve every series (points + colors).
157
+ const resolvedSeries = (0, _react.useMemo)(() => {
158
+ return series.map((s, index) => {
159
+ const appearance = s.appearance ?? appearanceFor(index);
160
+ const lineColor = resolveLineColor(s.color, appearance, modes);
161
+ const areaColor = resolveAreaColor(s.areaColor, lineColor, appearance, modes);
162
+ return {
163
+ key: s.key ?? `series-${index}`,
164
+ label: s.label,
165
+ appearance,
166
+ lineColor,
167
+ areaColor,
168
+ showArea: s.showArea !== false,
169
+ showLine: s.showLine !== false,
170
+ points: (0, _chartMath.resolvePoints)(s.data)
171
+ };
172
+ });
173
+ }, [series, modes]);
174
+
175
+ // Canonical point count comes from the longest series.
176
+ const count = (0, _react.useMemo)(() => resolvedSeries.reduce((max, s) => Math.max(max, s.points.length), 0), [resolvedSeries]);
177
+
178
+ // Domains.
179
+ const xDomain = (0, _react.useMemo)(() => {
180
+ let min = Infinity;
181
+ let max = -Infinity;
182
+ for (const s of resolvedSeries) {
183
+ const [lo, hi] = (0, _chartMath.extent)(s.points, 'x');
184
+ if (s.points.length) {
185
+ if (lo < min) min = lo;
186
+ if (hi > max) max = hi;
187
+ }
188
+ }
189
+ if (!Number.isFinite(min) || !Number.isFinite(max)) return [0, 0];
190
+ return [min, max];
191
+ }, [resolvedSeries]);
192
+ const {
193
+ yDomain,
194
+ yTicks
195
+ } = (0, _react.useMemo)(() => {
196
+ let dataMin = Infinity;
197
+ let dataMax = -Infinity;
198
+ for (const s of resolvedSeries) {
199
+ const [lo, hi] = (0, _chartMath.extent)(s.points, 'y');
200
+ if (s.points.length) {
201
+ if (lo < dataMin) dataMin = lo;
202
+ if (hi > dataMax) dataMax = hi;
203
+ }
204
+ }
205
+ if (!Number.isFinite(dataMin) || !Number.isFinite(dataMax)) {
206
+ dataMin = 0;
207
+ dataMax = 1;
208
+ }
209
+ const lo = yMin !== undefined ? yMin : Math.min(0, dataMin);
210
+ const hi = yMax !== undefined ? yMax : dataMax;
211
+ const ticks = (0, _chartMath.niceTicks)(lo, hi, numberOfTicks);
212
+ const domain = ticks.length >= 2 ? [ticks[0], ticks[ticks.length - 1]] : [lo, hi === lo ? lo + 1 : hi];
213
+ return {
214
+ yDomain: domain,
215
+ yTicks: ticks
216
+ };
217
+ }, [resolvedSeries, yMin, yMax, numberOfTicks]);
218
+
219
+ // Scales.
220
+ const xScale = (0, _react.useMemo)(() => (0, _chartMath.createLinearScale)(xDomain[0] === xDomain[1] ? [xDomain[0], xDomain[0] + 1] : xDomain, [inset.left, Math.max(inset.left, plotWidth - inset.right)]), [xDomain, inset.left, inset.right, plotWidth]);
221
+ const yScale = (0, _react.useMemo)(() => (0, _chartMath.createLinearScale)(yDomain, [height - inset.bottom, inset.top]), [yDomain, height, inset.bottom, inset.top]);
222
+
223
+ // Canonical x pixel positions (from the longest series).
224
+ const indexXs = (0, _react.useMemo)(() => {
225
+ const base = resolvedSeries.find(s => s.points.length === count);
226
+ if (!base) return [];
227
+ return base.points.map(p => xScale(p.x));
228
+ }, [resolvedSeries, count, xScale]);
229
+ const ctx = (0, _react.useMemo)(() => ({
230
+ width: plotWidth,
231
+ height,
232
+ inset,
233
+ xScale,
234
+ yScale,
235
+ yTicks,
236
+ indexXs,
237
+ count,
238
+ series: resolvedSeries,
239
+ curve,
240
+ activeIndex,
241
+ setActiveIndex,
242
+ xLabels,
243
+ formatX,
244
+ formatY,
245
+ showDots,
246
+ modes
247
+ }), [plotWidth, height, inset, xScale, yScale, yTicks, indexXs, count, resolvedSeries, curve, activeIndex, setActiveIndex, xLabels, formatX, formatY, showDots, modes]);
248
+ const isMultiSeries = resolvedSeries.length > 1;
249
+ const resolvedFormatValue = formatValue ?? (v => formatY(v));
250
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(ChartContext.Provider, {
251
+ value: ctx,
252
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
253
+ style: [styles.container, style],
254
+ accessibilityRole: "image",
255
+ accessibilityLabel: accessibilityLabel,
256
+ children: [showLegend && isMultiSeries ? /*#__PURE__*/(0, _jsxRuntime.jsx)(ChartLegend, {}) : null, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
257
+ style: styles.body,
258
+ children: [showYAxis ? /*#__PURE__*/(0, _jsxRuntime.jsx)(ChartYAxis, {}) : null, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
259
+ style: styles.plotColumn,
260
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
261
+ style: [styles.plot, {
262
+ height
263
+ }],
264
+ onLayout: handlePlotLayout,
265
+ children: plotWidth > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
266
+ children: [showGrid ? /*#__PURE__*/(0, _jsxRuntime.jsx)(ChartGrid, {}) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(ChartSeriesLayer, {}), goalPin ? /*#__PURE__*/(0, _jsxRuntime.jsx)(ChartGoalPin, {
267
+ value: goalPin.value,
268
+ atIndex: goalPin.atIndex,
269
+ seriesIndex: goalPin.seriesIndex
270
+ }) : null, children, interactive ? /*#__PURE__*/(0, _jsxRuntime.jsx)(ChartInteractionLayer, {
271
+ formatValue: resolvedFormatValue,
272
+ series: series
273
+ }) : null]
274
+ }) : null
275
+ }), showXAxis ? /*#__PURE__*/(0, _jsxRuntime.jsx)(ChartXAxis, {}) : null]
276
+ })]
277
+ })]
278
+ })
279
+ });
280
+ }
281
+
282
+ // --- Series layer (areas + lines + static dots) ---------------------------
283
+
284
+ function ChartSeriesLayer() {
285
+ const {
286
+ width,
287
+ height,
288
+ series,
289
+ xScale,
290
+ yScale,
291
+ yDomainBaseline,
292
+ curve,
293
+ showDots
294
+ } = useChartWithBaseline();
295
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeSvg.default, {
296
+ style: _reactNative.StyleSheet.absoluteFill,
297
+ width: width,
298
+ height: height,
299
+ children: [series.map(s => s.showArea && s.points.length ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Path, {
300
+ d: (0, _chartMath.buildAreaPath)(toPixelPoints(s.points, xScale, yScale), yDomainBaseline, curve),
301
+ fill: s.areaColor
302
+ }, `area-${s.key}`) : null), series.map(s => {
303
+ if (!s.showLine || s.points.length < 2) return null;
304
+ const pixelPoints = toPixelPoints(s.points, xScale, yScale);
305
+ const segments = (0, _chartMath.buildLineSegments)(pixelPoints, curve);
306
+ return segments.map((seg, i) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Path, {
307
+ d: seg.d,
308
+ stroke: s.lineColor,
309
+ strokeWidth: 2,
310
+ fill: "none",
311
+ strokeLinecap: "round",
312
+ strokeLinejoin: "round",
313
+ strokeDasharray: seg.dashed ? '5,4' : undefined
314
+ }, `line-${s.key}-${i}`));
315
+ }), showDots ? series.map(s => s.points.map((p, i) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Circle, {
316
+ cx: xScale(p.x),
317
+ cy: yScale(p.y),
318
+ r: 4,
319
+ fill: s.lineColor
320
+ }, `dot-${s.key}-${i}`))) : null]
321
+ });
322
+ }
323
+
324
+ // --- Grid ------------------------------------------------------------------
325
+
326
+ /** Background grid lines aligned to the y-ticks (horizontal) and x data points (vertical). */
327
+ function ChartGrid({
328
+ direction = 'horizontal',
329
+ stroke = 'rgba(0,0,0,0.08)',
330
+ strokeWidth = 1,
331
+ strokeDasharray
332
+ }) {
333
+ const {
334
+ width,
335
+ height,
336
+ inset,
337
+ xScale,
338
+ yScale,
339
+ yTicks,
340
+ indexXs
341
+ } = useChart();
342
+ const showH = direction === 'horizontal' || direction === 'both';
343
+ const showV = direction === 'vertical' || direction === 'both';
344
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeSvg.default, {
345
+ style: _reactNative.StyleSheet.absoluteFill,
346
+ width: width,
347
+ height: height,
348
+ pointerEvents: "none",
349
+ children: [showH ? yTicks.map(t => {
350
+ const y = yScale(t);
351
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Line, {
352
+ x1: inset.left,
353
+ x2: width - inset.right,
354
+ y1: y,
355
+ y2: y,
356
+ stroke: stroke,
357
+ strokeWidth: strokeWidth,
358
+ strokeDasharray: strokeDasharray
359
+ }, `gh-${t}`);
360
+ }) : null, showV ? indexXs.map((x, i) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Line, {
361
+ x1: x,
362
+ x2: x,
363
+ y1: inset.top,
364
+ y2: height - inset.bottom,
365
+ stroke: stroke,
366
+ strokeWidth: strokeWidth,
367
+ strokeDasharray: strokeDasharray
368
+ }, `gv-${i}`)) : null]
369
+ });
370
+ }
371
+
372
+ // --- Y axis ----------------------------------------------------------------
373
+
374
+ /** Y-axis tick labels, vertically positioned to align with the grid. */
375
+ function ChartYAxis({
376
+ showLabels = true,
377
+ showTicks = false,
378
+ tickLength = 4,
379
+ showAxisLine = false,
380
+ formatLabel
381
+ }) {
382
+ const {
383
+ height,
384
+ inset,
385
+ yScale,
386
+ yTicks,
387
+ formatY,
388
+ modes
389
+ } = useChart();
390
+ const typo = useAxisTypography(modes);
391
+ const format = formatLabel ?? formatY;
392
+ const lineHeight = typo.lineHeight;
393
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
394
+ style: {
395
+ height,
396
+ justifyContent: 'flex-start',
397
+ flexDirection: 'row'
398
+ },
399
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
400
+ style: {
401
+ width: undefined,
402
+ height
403
+ },
404
+ children: yTicks.map(t => {
405
+ const y = yScale(t);
406
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
407
+ style: {
408
+ position: 'absolute',
409
+ right: showTicks ? tickLength + 4 : 0,
410
+ top: y - lineHeight / 2,
411
+ flexDirection: 'row',
412
+ alignItems: 'center'
413
+ },
414
+ children: showLabels ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
415
+ style: typo.style,
416
+ numberOfLines: 1,
417
+ children: format(t)
418
+ }) : null
419
+ }, `yl-${t}`);
420
+ })
421
+ }), showTicks ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeSvg.default, {
422
+ width: tickLength,
423
+ height: height,
424
+ pointerEvents: "none",
425
+ children: [yTicks.map(t => {
426
+ const y = yScale(t);
427
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Line, {
428
+ x1: 0,
429
+ x2: tickLength,
430
+ y1: y,
431
+ y2: y,
432
+ stroke: "rgba(0,0,0,0.2)",
433
+ strokeWidth: 1
434
+ }, `yt-${t}`);
435
+ }), showAxisLine ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Line, {
436
+ x1: tickLength,
437
+ x2: tickLength,
438
+ y1: inset.top,
439
+ y2: height - inset.bottom,
440
+ stroke: "rgba(0,0,0,0.2)",
441
+ strokeWidth: 1
442
+ }) : null]
443
+ }) : null]
444
+ });
445
+ }
446
+
447
+ // --- X axis ----------------------------------------------------------------
448
+
449
+ /** X-axis labels, horizontally positioned to align with the data points. */
450
+ function ChartXAxis({
451
+ showLabels = true,
452
+ showTicks = false,
453
+ tickLength = 4,
454
+ selectable = true,
455
+ formatLabel
456
+ }) {
457
+ const {
458
+ width,
459
+ inset,
460
+ xScale,
461
+ indexXs,
462
+ count,
463
+ xLabels,
464
+ formatX,
465
+ modes,
466
+ activeIndex,
467
+ setActiveIndex
468
+ } = useChart();
469
+ const typo = useAxisTypography(modes);
470
+ const format = formatLabel ?? formatX;
471
+ const activeColor = (0, _figmaVariablesResolver.getVariableByName)('dataViz/bg', {
472
+ ...modes,
473
+ 'Appearance / DataViz': 'Primary',
474
+ 'Emphasis / DataViz': 'High'
475
+ }) ?? '#5d00b5';
476
+ const labels = xLabels ?? indexXs.map((_, i) => i);
477
+ const labelCount = labels.length;
478
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
479
+ style: {
480
+ width: '100%',
481
+ height: typo.lineHeight + (showTicks ? tickLength : 0)
482
+ },
483
+ children: [showTicks ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.default, {
484
+ style: _reactNative.StyleSheet.absoluteFill,
485
+ width: width,
486
+ height: tickLength,
487
+ pointerEvents: "none",
488
+ children: indexXs.map((x, i) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Line, {
489
+ x1: x,
490
+ x2: x,
491
+ y1: 0,
492
+ y2: tickLength,
493
+ stroke: "rgba(0,0,0,0.2)",
494
+ strokeWidth: 1
495
+ }, `xt-${i}`))
496
+ }) : null, showLabels ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
497
+ style: {
498
+ position: 'absolute',
499
+ left: 0,
500
+ right: 0,
501
+ top: showTicks ? tickLength : 0
502
+ },
503
+ children: labels.map((label, i) => {
504
+ // Map a label to its data index (handles fewer labels than points).
505
+ const dataIndex = labelCount === count ? i : Math.round(i / Math.max(1, labelCount - 1) * (count - 1));
506
+ const x = labelCount === count ? indexXs[i] ?? xScale(i) : inset.left + i / Math.max(1, labelCount - 1) * (width - inset.left - inset.right);
507
+ const isActive = activeIndex === dataIndex;
508
+ const content = /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
509
+ style: [typo.style, isActive ? {
510
+ color: activeColor,
511
+ fontWeight: '700'
512
+ } : null],
513
+ numberOfLines: 1,
514
+ children: format(label, i)
515
+ });
516
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
517
+ style: {
518
+ position: 'absolute',
519
+ left: x,
520
+ transform: [{
521
+ translateX: -50
522
+ }],
523
+ width: 100,
524
+ alignItems: 'center'
525
+ },
526
+ children: selectable ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
527
+ onPress: () => setActiveIndex(isActive ? null : dataIndex),
528
+ hitSlop: 8,
529
+ children: content
530
+ }) : content
531
+ }, `xl-${i}`);
532
+ })
533
+ }) : null]
534
+ });
535
+ }
536
+
537
+ // --- Goal pin --------------------------------------------------------------
538
+
539
+ /** A pill marker anchored to a data point, with a dashed connector to the baseline. */
540
+ function ChartGoalPin({
541
+ value,
542
+ atIndex,
543
+ seriesIndex = 0,
544
+ color,
545
+ textColor
546
+ }) {
547
+ const {
548
+ height,
549
+ inset,
550
+ xScale,
551
+ yScale,
552
+ series,
553
+ count,
554
+ modes
555
+ } = useChart();
556
+ const s = series[seriesIndex] ?? series[0];
557
+ if (!s || s.points.length === 0) return null;
558
+ const index = atIndex ?? count - 1;
559
+ const point = s.points[Math.min(Math.max(0, index), s.points.length - 1)];
560
+ if (!point) return null;
561
+ const x = xScale(point.x);
562
+ const y = yScale(point.y);
563
+ const pinColor = color ?? s.lineColor;
564
+ const pinTextColor = textColor ?? (0, _figmaVariablesResolver.getVariableByName)('mode/Grey/2500', modes) ?? '#ffffff';
565
+ const PIN_SIZE = 32;
566
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
567
+ style: _reactNative.StyleSheet.absoluteFill,
568
+ pointerEvents: "none",
569
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeSvg.default, {
570
+ style: _reactNative.StyleSheet.absoluteFill,
571
+ width: "100%",
572
+ height: height,
573
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Line, {
574
+ x1: x,
575
+ x2: x,
576
+ y1: PIN_SIZE / 2,
577
+ y2: height - inset.bottom,
578
+ stroke: pinColor,
579
+ strokeWidth: 1.5,
580
+ strokeDasharray: "4,4"
581
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Circle, {
582
+ cx: x,
583
+ cy: y,
584
+ r: 5,
585
+ fill: pinColor,
586
+ stroke: "#ffffff",
587
+ strokeWidth: 2
588
+ })]
589
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
590
+ style: {
591
+ position: 'absolute',
592
+ left: x - PIN_SIZE / 2,
593
+ top: 0,
594
+ width: PIN_SIZE,
595
+ height: PIN_SIZE,
596
+ borderRadius: 999,
597
+ backgroundColor: pinColor,
598
+ alignItems: 'center',
599
+ justifyContent: 'center',
600
+ paddingHorizontal: 4
601
+ },
602
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
603
+ style: {
604
+ color: pinTextColor,
605
+ fontSize: 10,
606
+ lineHeight: 13,
607
+ textAlign: 'center'
608
+ },
609
+ children: value
610
+ })
611
+ })]
612
+ });
613
+ }
614
+
615
+ // --- Interaction layer (crosshair + active dots + tooltip) ----------------
616
+
617
+ function ChartInteractionLayer({
618
+ formatValue,
619
+ series: rawSeries
620
+ }) {
621
+ const {
622
+ width,
623
+ height,
624
+ inset,
625
+ xScale,
626
+ yScale,
627
+ indexXs,
628
+ series,
629
+ activeIndex,
630
+ setActiveIndex,
631
+ modes
632
+ } = useChart();
633
+ const viewRef = (0, _react.useRef)(null);
634
+ const updateFromX = (0, _react.useCallback)(locationX => {
635
+ const idx = (0, _chartMath.nearestIndex)(indexXs, locationX);
636
+ if (idx >= 0) setActiveIndex(idx);
637
+ }, [indexXs, setActiveIndex]);
638
+ const panResponder = (0, _react.useMemo)(() => _reactNative.PanResponder.create({
639
+ onStartShouldSetPanResponder: () => true,
640
+ onMoveShouldSetPanResponder: () => true,
641
+ onPanResponderGrant: e => updateFromX(e.nativeEvent.locationX),
642
+ onPanResponderMove: e => updateFromX(e.nativeEvent.locationX)
643
+ }), [updateFromX]);
644
+
645
+ // Web-only hover support (no button pressed) via DOM listeners.
646
+ (0, _react.useEffect)(() => {
647
+ if (_reactNative.Platform.OS !== 'web') return;
648
+ const node = viewRef.current;
649
+ if (!node) return;
650
+ const onMove = ev => {
651
+ const rect = node.getBoundingClientRect();
652
+ updateFromX(ev.clientX - rect.left);
653
+ };
654
+ const onLeave = () => setActiveIndex(null);
655
+ node.addEventListener('mousemove', onMove);
656
+ node.addEventListener('mouseleave', onLeave);
657
+ return () => {
658
+ node.removeEventListener('mousemove', onMove);
659
+ node.removeEventListener('mouseleave', onLeave);
660
+ };
661
+ }, [updateFromX, setActiveIndex]);
662
+ const hasActive = activeIndex !== null && activeIndex >= 0;
663
+ const activeX = hasActive ? indexXs[activeIndex] : 0;
664
+ const tooltipItems = (0, _react.useMemo)(() => {
665
+ if (!hasActive) return [];
666
+ return series.map((s, sIndex) => {
667
+ const point = s.points[activeIndex];
668
+ if (!point) return null;
669
+ return {
670
+ key: String(s.key),
671
+ label: s.label ?? `Series ${sIndex + 1}`,
672
+ value: formatValue(point.y, rawSeries[sIndex]),
673
+ color: s.lineColor,
674
+ y: yScale(point.y)
675
+ };
676
+ }).filter(Boolean);
677
+ }, [hasActive, series, activeIndex, formatValue, rawSeries, yScale]);
678
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
679
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
680
+ ref: viewRef,
681
+ style: _reactNative.StyleSheet.absoluteFill,
682
+ ...panResponder.panHandlers
683
+ }), hasActive ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
684
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeSvg.default, {
685
+ style: _reactNative.StyleSheet.absoluteFill,
686
+ width: width,
687
+ height: height,
688
+ pointerEvents: "none",
689
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Line, {
690
+ x1: activeX,
691
+ x2: activeX,
692
+ y1: inset.top,
693
+ y2: height - inset.bottom,
694
+ stroke: "#0f0d0a",
695
+ strokeWidth: 1
696
+ }), tooltipItems.map(item => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Circle, {
697
+ cx: activeX,
698
+ cy: item.y,
699
+ r: 6,
700
+ fill: item.color,
701
+ stroke: "#ffffff",
702
+ strokeWidth: 2
703
+ }, `active-${item.key}`))]
704
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(ChartTooltip, {
705
+ x: activeX,
706
+ width: width,
707
+ items: tooltipItems,
708
+ modes: modes
709
+ })]
710
+ }) : null]
711
+ });
712
+ }
713
+
714
+ // --- Inline tooltip --------------------------------------------------------
715
+
716
+ function ChartTooltip({
717
+ x,
718
+ width,
719
+ items,
720
+ modes
721
+ }) {
722
+ const [size, setSize] = (0, _react.useState)(null);
723
+ const bg = (0, _figmaVariablesResolver.getVariableByName)('tooltip/background', modes) ?? '#0f0d0a';
724
+ const paddingH = toNumber((0, _figmaVariablesResolver.getVariableByName)('tooltip/padding/horizontal', modes), 12);
725
+ const paddingV = toNumber((0, _figmaVariablesResolver.getVariableByName)('tooltip/padding/vertical', modes), 8);
726
+ const radius = toNumber((0, _figmaVariablesResolver.getVariableByName)('radius', modes), 8);
727
+ const labelColor = (0, _figmaVariablesResolver.getVariableByName)('tooltip/label/color', modes) ?? '#ffffff';
728
+ if (items.length === 0) return null;
729
+
730
+ // Horizontally clamp so the box stays inside the plot.
731
+ const boxW = size?.width ?? 0;
732
+ const screenPad = 4;
733
+ let left = x - boxW / 2;
734
+ if (boxW > 0) {
735
+ left = Math.max(screenPad, Math.min(left, width - boxW - screenPad));
736
+ }
737
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
738
+ pointerEvents: "none",
739
+ onLayout: e => setSize({
740
+ width: e.nativeEvent.layout.width,
741
+ height: e.nativeEvent.layout.height
742
+ }),
743
+ style: {
744
+ position: 'absolute',
745
+ top: 0,
746
+ left,
747
+ backgroundColor: bg,
748
+ borderRadius: radius,
749
+ paddingHorizontal: paddingH,
750
+ paddingVertical: paddingV,
751
+ gap: 4,
752
+ opacity: size ? 1 : 0,
753
+ shadowColor: '#000',
754
+ shadowOffset: {
755
+ width: 0,
756
+ height: 2
757
+ },
758
+ shadowOpacity: 0.25,
759
+ shadowRadius: 3.84,
760
+ elevation: 5
761
+ },
762
+ children: items.map(item => /*#__PURE__*/(0, _jsxRuntime.jsx)(_MetricLegendItem.default, {
763
+ label: item.label,
764
+ value: item.value,
765
+ indicatorColor: item.color,
766
+ modes: modes,
767
+ labelStyle: {
768
+ color: labelColor
769
+ },
770
+ valueStyle: {
771
+ color: labelColor,
772
+ fontWeight: '700'
773
+ },
774
+ style: {
775
+ gap: 8
776
+ }
777
+ }, item.key))
778
+ });
779
+ }
780
+
781
+ // --- Legend ----------------------------------------------------------------
782
+
783
+ function ChartLegend() {
784
+ const {
785
+ series,
786
+ modes
787
+ } = useChart();
788
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
789
+ style: styles.legend,
790
+ children: series.map((s, i) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_MetricLegendItem.default, {
791
+ label: s.label ?? `Series ${i + 1}`,
792
+ indicatorColor: s.lineColor,
793
+ indicatorShape: s.showArea === false ? 'line' : 'dot',
794
+ modes: modes
795
+ }, `legend-${s.key}`))
796
+ });
797
+ }
798
+
799
+ // --- Shared hooks / utils --------------------------------------------------
800
+
801
+ /** Resolve `axisItem/*` typography tokens into a memoized text style. */
802
+ function useAxisTypography(modes) {
803
+ return (0, _react.useMemo)(() => {
804
+ const color = (0, _figmaVariablesResolver.getVariableByName)('axisItem/color', modes) ?? '#000000';
805
+ const fontFamily = (0, _figmaVariablesResolver.getVariableByName)('axisItem/fontFamily', modes) ?? 'JioType Var';
806
+ const fontSize = toNumber((0, _figmaVariablesResolver.getVariableByName)('axisItem/fontSize', modes), 12);
807
+ const lineHeight = toNumber((0, _figmaVariablesResolver.getVariableByName)('axisItem/lineHeight', modes), 16);
808
+ const fontWeight = toFontWeight((0, _figmaVariablesResolver.getVariableByName)('axisItem/fontWeight', modes), '400');
809
+ return {
810
+ lineHeight,
811
+ style: {
812
+ color,
813
+ fontFamily,
814
+ fontSize,
815
+ lineHeight,
816
+ fontWeight
817
+ }
818
+ };
819
+ }, [modes]);
820
+ }
821
+
822
+ /** Like `useChart` but also exposes the area baseline pixel-y. */
823
+ function useChartWithBaseline() {
824
+ const ctx = useChart();
825
+ const yDomainBaseline = ctx.yScale(ctx.yScale.domain[0]);
826
+ return {
827
+ ...ctx,
828
+ yDomainBaseline
829
+ };
830
+ }
831
+ const toPixelPoints = (points, xScale, yScale) => points.map(p => ({
832
+ x: xScale(p.x),
833
+ y: yScale(p.y),
834
+ projected: p.projected
835
+ }));
836
+ const styles = _reactNative.StyleSheet.create({
837
+ container: {
838
+ width: '100%',
839
+ gap: 8
840
+ },
841
+ body: {
842
+ flexDirection: 'row',
843
+ gap: 8
844
+ },
845
+ plotColumn: {
846
+ flex: 1,
847
+ minWidth: 0,
848
+ gap: 8
849
+ },
850
+ plot: {
851
+ width: '100%',
852
+ position: 'relative'
853
+ },
854
+ legend: {
855
+ flexDirection: 'row',
856
+ flexWrap: 'wrap',
857
+ gap: 12
858
+ }
859
+ });
860
+
861
+ // Attach reusable sub-components.
862
+ AreaLineChart.Grid = ChartGrid;
863
+ AreaLineChart.XAxis = ChartXAxis;
864
+ AreaLineChart.YAxis = ChartYAxis;
865
+ AreaLineChart.GoalPin = ChartGoalPin;
866
+ var _default = exports.default = AreaLineChart;