@gelatin-hero/framer-charts 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/README.md ADDED
@@ -0,0 +1,36 @@
1
+ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2
+
3
+ ## Getting Started
4
+
5
+ First, run the development server:
6
+
7
+ ```bash
8
+ npm run dev
9
+ # or
10
+ yarn dev
11
+ # or
12
+ pnpm dev
13
+ # or
14
+ bun dev
15
+ ```
16
+
17
+ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
+
19
+ You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20
+
21
+ This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
+
23
+ ## Learn More
24
+
25
+ To learn more about Next.js, take a look at the following resources:
26
+
27
+ - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
+ - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29
+
30
+ You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31
+
32
+ ## Deploy on Vercel
33
+
34
+ The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35
+
36
+ Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
@@ -0,0 +1,9 @@
1
+ "use client";
2
+
3
+ // src/data/settlement-speeds.csv?raw
4
+ var settlement_speeds_default = ",Aug 25,Sep 25,Oct 25,Nov 25,Dec 25,Jan 26,Feb 26\r\nCrypto Instant,86.40%,89.30%,87.00%,85.40%,89.10%,90.60%,84.10%\r\nCrypto Fast,10.10%,7.40%,9.40%,9.00%,6.90%,5.90%,9.80%\r\nCrypto SLA,3.40%,3.20%,3.50%,5.50%,4.00%,3.50%,6.10%\r\nG3 Instant,27.40%,25.60%,41.60%,48.30%,66.50%,67.80%,85.40%\r\nG3 Fast,26.10%,33.40%,29.60%,31.90%,22.60%,15.70%,5.00%\r\nG3 SLA,46.50%,41.00%,28.80%,19.80%,10.90%,16.50%,9.60%\r\nG7 Instant,68.90%,70.60%,87.80%,90.20%,83.20%,88.60%,90.20%\r\nG7 Fast,14.80%,17.40%,5.10%,4.40%,12.00%,5.70%,4.30%\r\nG7 SLA,16.40%,11.90%,7.10%,5.40%,4.80%,5.70%,5.50%";
5
+
6
+ export {
7
+ settlement_speeds_default
8
+ };
9
+ //# sourceMappingURL=chunk-24EYBU3B.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/data/settlement-speeds.csv"],"sourcesContent":[",Aug 25,Sep 25,Oct 25,Nov 25,Dec 25,Jan 26,Feb 26\r\nCrypto Instant,86.40%,89.30%,87.00%,85.40%,89.10%,90.60%,84.10%\r\nCrypto Fast,10.10%,7.40%,9.40%,9.00%,6.90%,5.90%,9.80%\r\nCrypto SLA,3.40%,3.20%,3.50%,5.50%,4.00%,3.50%,6.10%\r\nG3 Instant,27.40%,25.60%,41.60%,48.30%,66.50%,67.80%,85.40%\r\nG3 Fast,26.10%,33.40%,29.60%,31.90%,22.60%,15.70%,5.00%\r\nG3 SLA,46.50%,41.00%,28.80%,19.80%,10.90%,16.50%,9.60%\r\nG7 Instant,68.90%,70.60%,87.80%,90.20%,83.20%,88.60%,90.20%\r\nG7 Fast,14.80%,17.40%,5.10%,4.40%,12.00%,5.70%,4.30%\r\nG7 SLA,16.40%,11.90%,7.10%,5.40%,4.80%,5.70%,5.50%"],"mappings":";;;AAAA;","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,955 @@
1
+ "use client";
2
+ import {
3
+ settlement_speeds_default
4
+ } from "./chunk-24EYBU3B.mjs";
5
+
6
+ // src/framer/G7SettlementChart.framer.tsx
7
+ import React4 from "react";
8
+
9
+ // src/components/BarChart.tsx
10
+ import React3 from "react";
11
+ import { RiArrowLeftSLine, RiArrowRightSLine } from "@remixicon/react";
12
+ import {
13
+ Bar,
14
+ CartesianGrid,
15
+ Label,
16
+ BarChart as RechartsBarChart,
17
+ Legend as RechartsLegend,
18
+ ResponsiveContainer,
19
+ Tooltip,
20
+ XAxis,
21
+ YAxis
22
+ } from "recharts";
23
+
24
+ // src/lib/useOnWindowResize.ts
25
+ import * as React2 from "react";
26
+ var useOnWindowResize = (handler) => {
27
+ React2.useEffect(() => {
28
+ const handleResize = () => {
29
+ handler();
30
+ };
31
+ handleResize();
32
+ window.addEventListener("resize", handleResize);
33
+ return () => window.removeEventListener("resize", handleResize);
34
+ }, [handler]);
35
+ };
36
+
37
+ // src/lib/chartColors.ts
38
+ var chartColors = {
39
+ blue: {
40
+ bg: "bg-blue-500",
41
+ stroke: "stroke-blue-500",
42
+ fill: "fill-blue-500",
43
+ text: "text-blue-500"
44
+ },
45
+ emerald: {
46
+ bg: "bg-emerald-500",
47
+ stroke: "stroke-emerald-500",
48
+ fill: "fill-emerald-500",
49
+ text: "text-emerald-500"
50
+ },
51
+ violet: {
52
+ bg: "bg-violet-500",
53
+ stroke: "stroke-violet-500",
54
+ fill: "fill-violet-500",
55
+ text: "text-violet-500"
56
+ },
57
+ amber: {
58
+ bg: "bg-amber-500",
59
+ stroke: "stroke-amber-500",
60
+ fill: "fill-amber-500",
61
+ text: "text-amber-500"
62
+ },
63
+ gray: {
64
+ bg: "bg-gray-500",
65
+ stroke: "stroke-gray-500",
66
+ fill: "fill-gray-500",
67
+ text: "text-gray-500"
68
+ },
69
+ cyan: {
70
+ bg: "bg-cyan-500",
71
+ stroke: "stroke-cyan-500",
72
+ fill: "fill-cyan-500",
73
+ text: "text-cyan-500"
74
+ },
75
+ pink: {
76
+ bg: "bg-pink-500",
77
+ stroke: "stroke-pink-500",
78
+ fill: "fill-pink-500",
79
+ text: "text-pink-500"
80
+ },
81
+ lime: {
82
+ bg: "bg-lime-500",
83
+ stroke: "stroke-lime-500",
84
+ fill: "fill-lime-500",
85
+ text: "text-lime-500"
86
+ },
87
+ fuchsia: {
88
+ bg: "bg-fuchsia-500",
89
+ stroke: "stroke-fuchsia-500",
90
+ fill: "fill-fuchsia-500",
91
+ text: "text-fuchsia-500"
92
+ }
93
+ };
94
+ var AvailableChartColors = Object.keys(
95
+ chartColors
96
+ );
97
+ var constructCategoryColors = (categories, colors) => {
98
+ const categoryColors = /* @__PURE__ */ new Map();
99
+ categories.forEach((category, index) => {
100
+ categoryColors.set(category, colors[index % colors.length]);
101
+ });
102
+ return categoryColors;
103
+ };
104
+ var getColorClassName = (color, type) => {
105
+ var _a;
106
+ const fallbackColor = {
107
+ bg: "bg-gray-500",
108
+ stroke: "stroke-gray-500",
109
+ fill: "fill-gray-500",
110
+ text: "text-gray-500"
111
+ };
112
+ return ((_a = chartColors[color]) == null ? void 0 : _a[type]) ?? fallbackColor[type];
113
+ };
114
+
115
+ // src/lib/cx.ts
116
+ import clsx from "clsx";
117
+ import { twMerge } from "tailwind-merge";
118
+ function cx(...args) {
119
+ return twMerge(clsx(...args));
120
+ }
121
+
122
+ // src/lib/getYAxisDomain.ts
123
+ var getYAxisDomain = (autoMinValue, minValue, maxValue) => {
124
+ const minDomain = autoMinValue ? "auto" : minValue ?? 0;
125
+ const maxDomain = maxValue ?? "auto";
126
+ return [minDomain, maxDomain];
127
+ };
128
+
129
+ // src/components/BarChart.tsx
130
+ function deepEqual(obj1, obj2) {
131
+ if (obj1 === obj2) return true;
132
+ if (typeof obj1 !== "object" || typeof obj2 !== "object" || obj1 === null || obj2 === null) {
133
+ return false;
134
+ }
135
+ const keys1 = Object.keys(obj1);
136
+ const keys2 = Object.keys(obj2);
137
+ if (keys1.length !== keys2.length) return false;
138
+ for (const key of keys1) {
139
+ if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) return false;
140
+ }
141
+ return true;
142
+ }
143
+ var renderShape = (props, activeBar, activeLegend, layout) => {
144
+ const { fillOpacity, name, payload, value } = props;
145
+ let { x, width, y, height } = props;
146
+ if (layout === "horizontal" && height < 0) {
147
+ y += height;
148
+ height = Math.abs(height);
149
+ } else if (layout === "vertical" && width < 0) {
150
+ x += width;
151
+ width = Math.abs(width);
152
+ }
153
+ return /* @__PURE__ */ React3.createElement(
154
+ "rect",
155
+ {
156
+ x,
157
+ y,
158
+ width,
159
+ height,
160
+ opacity: activeBar || activeLegend && activeLegend !== name ? deepEqual(activeBar, { ...payload, value }) ? fillOpacity : 0.3 : fillOpacity
161
+ }
162
+ );
163
+ };
164
+ var LegendItem = ({
165
+ name,
166
+ color,
167
+ onClick,
168
+ activeLegend
169
+ }) => {
170
+ const hasOnValueChange = !!onClick;
171
+ return /* @__PURE__ */ React3.createElement(
172
+ "li",
173
+ {
174
+ className: cx(
175
+ "group inline-flex flex-nowrap items-center gap-1.5 rounded-sm px-2 py-1 whitespace-nowrap transition",
176
+ hasOnValueChange ? "cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800" : "cursor-default"
177
+ ),
178
+ onClick: (e) => {
179
+ e.stopPropagation();
180
+ onClick == null ? void 0 : onClick(name, color);
181
+ }
182
+ },
183
+ /* @__PURE__ */ React3.createElement(
184
+ "span",
185
+ {
186
+ className: cx(
187
+ "size-2 shrink-0 rounded-xs",
188
+ getColorClassName(color, "bg"),
189
+ activeLegend && activeLegend !== name ? "opacity-40" : "opacity-100"
190
+ ),
191
+ "aria-hidden": true
192
+ }
193
+ ),
194
+ /* @__PURE__ */ React3.createElement(
195
+ "p",
196
+ {
197
+ className: cx(
198
+ "truncate text-xs whitespace-nowrap",
199
+ "text-black",
200
+ hasOnValueChange && "group-hover:text-gray-700",
201
+ activeLegend && activeLegend !== name ? "opacity-40" : "opacity-100"
202
+ )
203
+ },
204
+ name
205
+ )
206
+ );
207
+ };
208
+ var ScrollButton = ({ icon, onClick, disabled }) => {
209
+ const Icon = icon;
210
+ const [isPressed, setIsPressed] = React3.useState(false);
211
+ const intervalRef = React3.useRef(null);
212
+ React3.useEffect(() => {
213
+ if (isPressed) {
214
+ intervalRef.current = setInterval(() => {
215
+ onClick == null ? void 0 : onClick();
216
+ }, 300);
217
+ } else {
218
+ clearInterval(intervalRef.current);
219
+ }
220
+ return () => clearInterval(intervalRef.current);
221
+ }, [isPressed, onClick]);
222
+ React3.useEffect(() => {
223
+ if (disabled) {
224
+ clearInterval(intervalRef.current);
225
+ setIsPressed(false);
226
+ }
227
+ }, [disabled]);
228
+ return /* @__PURE__ */ React3.createElement(
229
+ "button",
230
+ {
231
+ type: "button",
232
+ className: cx(
233
+ "group inline-flex size-5 items-center truncate rounded-sm transition",
234
+ disabled ? "cursor-not-allowed text-gray-400 dark:text-gray-600" : "cursor-pointer text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-gray-50"
235
+ ),
236
+ disabled,
237
+ onClick: (e) => {
238
+ e.stopPropagation();
239
+ onClick == null ? void 0 : onClick();
240
+ },
241
+ onMouseDown: (e) => {
242
+ e.stopPropagation();
243
+ setIsPressed(true);
244
+ },
245
+ onMouseUp: (e) => {
246
+ e.stopPropagation();
247
+ setIsPressed(false);
248
+ }
249
+ },
250
+ /* @__PURE__ */ React3.createElement(Icon, { className: "size-full", "aria-hidden": "true" })
251
+ );
252
+ };
253
+ var Legend = React3.forwardRef((props, ref) => {
254
+ const {
255
+ categories,
256
+ colors = AvailableChartColors,
257
+ className,
258
+ onClickLegendItem,
259
+ activeLegend,
260
+ enableLegendSlider = false,
261
+ ...other
262
+ } = props;
263
+ const scrollableRef = React3.useRef(null);
264
+ const scrollButtonsRef = React3.useRef(null);
265
+ const [hasScroll, setHasScroll] = React3.useState(null);
266
+ const [isKeyDowned, setIsKeyDowned] = React3.useState(null);
267
+ const intervalRef = React3.useRef(null);
268
+ const checkScroll = React3.useCallback(() => {
269
+ const scrollable = scrollableRef == null ? void 0 : scrollableRef.current;
270
+ if (!scrollable) return;
271
+ const hasLeftScroll = scrollable.scrollLeft > 0;
272
+ const hasRightScroll = scrollable.scrollWidth - scrollable.clientWidth > scrollable.scrollLeft;
273
+ setHasScroll({ left: hasLeftScroll, right: hasRightScroll });
274
+ }, [setHasScroll]);
275
+ const scrollToTest = React3.useCallback(
276
+ (direction) => {
277
+ const element = scrollableRef == null ? void 0 : scrollableRef.current;
278
+ const scrollButtons = scrollButtonsRef == null ? void 0 : scrollButtonsRef.current;
279
+ const scrollButtonsWith = (scrollButtons == null ? void 0 : scrollButtons.clientWidth) ?? 0;
280
+ const width = (element == null ? void 0 : element.clientWidth) ?? 0;
281
+ if (element && enableLegendSlider) {
282
+ element.scrollTo({
283
+ left: direction === "left" ? element.scrollLeft - width + scrollButtonsWith : element.scrollLeft + width - scrollButtonsWith,
284
+ behavior: "smooth"
285
+ });
286
+ setTimeout(() => {
287
+ checkScroll();
288
+ }, 400);
289
+ }
290
+ },
291
+ [enableLegendSlider, checkScroll]
292
+ );
293
+ React3.useEffect(() => {
294
+ const keyDownHandler = (key) => {
295
+ if (key === "ArrowLeft") {
296
+ scrollToTest("left");
297
+ } else if (key === "ArrowRight") {
298
+ scrollToTest("right");
299
+ }
300
+ };
301
+ if (isKeyDowned) {
302
+ keyDownHandler(isKeyDowned);
303
+ intervalRef.current = setInterval(() => {
304
+ keyDownHandler(isKeyDowned);
305
+ }, 300);
306
+ } else {
307
+ clearInterval(intervalRef.current);
308
+ }
309
+ return () => clearInterval(intervalRef.current);
310
+ }, [isKeyDowned, scrollToTest]);
311
+ const keyDown = (e) => {
312
+ e.stopPropagation();
313
+ if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
314
+ e.preventDefault();
315
+ setIsKeyDowned(e.key);
316
+ }
317
+ };
318
+ const keyUp = (e) => {
319
+ e.stopPropagation();
320
+ setIsKeyDowned(null);
321
+ };
322
+ React3.useEffect(() => {
323
+ const scrollable = scrollableRef == null ? void 0 : scrollableRef.current;
324
+ if (enableLegendSlider) {
325
+ checkScroll();
326
+ scrollable == null ? void 0 : scrollable.addEventListener("keydown", keyDown);
327
+ scrollable == null ? void 0 : scrollable.addEventListener("keyup", keyUp);
328
+ }
329
+ return () => {
330
+ scrollable == null ? void 0 : scrollable.removeEventListener("keydown", keyDown);
331
+ scrollable == null ? void 0 : scrollable.removeEventListener("keyup", keyUp);
332
+ };
333
+ }, [checkScroll, enableLegendSlider]);
334
+ return /* @__PURE__ */ React3.createElement(
335
+ "ol",
336
+ {
337
+ ref,
338
+ className: cx("relative overflow-hidden", className),
339
+ ...other
340
+ },
341
+ /* @__PURE__ */ React3.createElement(
342
+ "div",
343
+ {
344
+ ref: scrollableRef,
345
+ tabIndex: 0,
346
+ className: cx(
347
+ "flex h-full",
348
+ enableLegendSlider ? (hasScroll == null ? void 0 : hasScroll.right) || (hasScroll == null ? void 0 : hasScroll.left) ? "snap-mandatory items-center overflow-auto pr-12 pl-4 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden" : "" : "flex-wrap"
349
+ )
350
+ },
351
+ categories.map((category, index) => /* @__PURE__ */ React3.createElement(
352
+ LegendItem,
353
+ {
354
+ key: `item-${index}`,
355
+ name: category,
356
+ color: colors[index],
357
+ onClick: onClickLegendItem,
358
+ activeLegend
359
+ }
360
+ ))
361
+ ),
362
+ enableLegendSlider && ((hasScroll == null ? void 0 : hasScroll.right) || (hasScroll == null ? void 0 : hasScroll.left)) ? /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(
363
+ "div",
364
+ {
365
+ className: cx(
366
+ "absolute top-0 right-0 bottom-0 flex h-full items-center justify-center pr-1",
367
+ "bg-white dark:bg-gray-950"
368
+ )
369
+ },
370
+ /* @__PURE__ */ React3.createElement(
371
+ ScrollButton,
372
+ {
373
+ icon: RiArrowLeftSLine,
374
+ onClick: () => {
375
+ setIsKeyDowned(null);
376
+ scrollToTest("left");
377
+ },
378
+ disabled: !(hasScroll == null ? void 0 : hasScroll.left)
379
+ }
380
+ ),
381
+ /* @__PURE__ */ React3.createElement(
382
+ ScrollButton,
383
+ {
384
+ icon: RiArrowRightSLine,
385
+ onClick: () => {
386
+ setIsKeyDowned(null);
387
+ scrollToTest("right");
388
+ },
389
+ disabled: !(hasScroll == null ? void 0 : hasScroll.right)
390
+ }
391
+ )
392
+ )) : null
393
+ );
394
+ });
395
+ Legend.displayName = "Legend";
396
+ var ChartLegend = ({ payload }, categoryColors, setLegendHeight, activeLegend, onClick, enableLegendSlider, legendPosition, yAxisWidth) => {
397
+ const legendRef = React3.useRef(null);
398
+ useOnWindowResize(() => {
399
+ var _a;
400
+ const calculateHeight = (height) => height ? Number(height) + 15 : 60;
401
+ setLegendHeight(calculateHeight((_a = legendRef.current) == null ? void 0 : _a.clientHeight));
402
+ });
403
+ const filteredPayload = payload.filter((item) => item.type !== "none");
404
+ const paddingLeft = legendPosition === "left" && yAxisWidth ? yAxisWidth - 8 : 0;
405
+ return /* @__PURE__ */ React3.createElement(
406
+ "div",
407
+ {
408
+ style: { paddingLeft },
409
+ ref: legendRef,
410
+ className: cx(
411
+ "flex items-center",
412
+ { "justify-center": legendPosition === "center" },
413
+ {
414
+ "justify-start": legendPosition === "left"
415
+ },
416
+ { "justify-end": legendPosition === "right" }
417
+ )
418
+ },
419
+ /* @__PURE__ */ React3.createElement(
420
+ Legend,
421
+ {
422
+ categories: filteredPayload.map((entry) => entry.value),
423
+ colors: filteredPayload.map(
424
+ (entry) => categoryColors.get(entry.value)
425
+ ),
426
+ onClickLegendItem: onClick,
427
+ activeLegend,
428
+ enableLegendSlider
429
+ }
430
+ )
431
+ );
432
+ };
433
+ var ChartTooltip = ({
434
+ active,
435
+ payload,
436
+ label,
437
+ valueFormatter
438
+ }) => {
439
+ if (active && payload && payload.length) {
440
+ return /* @__PURE__ */ React3.createElement(
441
+ "div",
442
+ {
443
+ className: cx(
444
+ "rounded-md border text-sm shadow-md",
445
+ "border-gray-200 dark:border-gray-800",
446
+ "bg-white dark:bg-gray-950"
447
+ )
448
+ },
449
+ /* @__PURE__ */ React3.createElement("div", { className: cx("border-b border-inherit px-4 py-2") }, /* @__PURE__ */ React3.createElement(
450
+ "p",
451
+ {
452
+ className: cx(
453
+ "font-medium",
454
+ "text-gray-900 dark:text-gray-50"
455
+ )
456
+ },
457
+ label
458
+ )),
459
+ /* @__PURE__ */ React3.createElement("div", { className: cx("space-y-1 px-4 py-2") }, payload.map(({ value, category, color }, index) => /* @__PURE__ */ React3.createElement(
460
+ "div",
461
+ {
462
+ key: `id-${index}`,
463
+ className: "flex items-center justify-between space-x-8"
464
+ },
465
+ /* @__PURE__ */ React3.createElement("div", { className: "flex items-center space-x-2" }, /* @__PURE__ */ React3.createElement(
466
+ "span",
467
+ {
468
+ "aria-hidden": "true",
469
+ className: cx(
470
+ "size-2 shrink-0 rounded-xs",
471
+ getColorClassName(color, "bg")
472
+ )
473
+ }
474
+ ), /* @__PURE__ */ React3.createElement(
475
+ "p",
476
+ {
477
+ className: cx(
478
+ "text-right whitespace-nowrap",
479
+ "text-gray-700 dark:text-gray-300"
480
+ )
481
+ },
482
+ category
483
+ )),
484
+ /* @__PURE__ */ React3.createElement(
485
+ "p",
486
+ {
487
+ className: cx(
488
+ "text-right font-medium whitespace-nowrap tabular-nums",
489
+ "text-gray-900 dark:text-gray-50"
490
+ )
491
+ },
492
+ valueFormatter(value)
493
+ )
494
+ )))
495
+ );
496
+ }
497
+ return null;
498
+ };
499
+ var BarChart = React3.forwardRef(
500
+ (props, forwardedRef) => {
501
+ const {
502
+ data = [],
503
+ categories = [],
504
+ index,
505
+ colors = AvailableChartColors,
506
+ valueFormatter = (value) => value.toString(),
507
+ startEndOnly = false,
508
+ showXAxis = true,
509
+ showYAxis = true,
510
+ showGridLines = true,
511
+ yAxisWidth = 56,
512
+ intervalType = "equidistantPreserveStart",
513
+ showTooltip = true,
514
+ showLegend = true,
515
+ autoMinValue = false,
516
+ minValue,
517
+ maxValue,
518
+ allowDecimals = true,
519
+ className,
520
+ onValueChange,
521
+ enableLegendSlider = false,
522
+ barCategoryGap,
523
+ tickGap = 5,
524
+ xAxisLabel,
525
+ yAxisLabel,
526
+ layout = "horizontal",
527
+ type = "default",
528
+ legendPosition = "right",
529
+ tooltipCallback,
530
+ customTooltip,
531
+ ...other
532
+ } = props;
533
+ const CustomTooltip = customTooltip;
534
+ const paddingValue = !showXAxis && !showYAxis || startEndOnly && !showYAxis ? 0 : 20;
535
+ const [legendHeight, setLegendHeight] = React3.useState(60);
536
+ const [activeLegend, setActiveLegend] = React3.useState(
537
+ void 0
538
+ );
539
+ const categoryColors = constructCategoryColors(categories, colors);
540
+ const [activeBar, setActiveBar] = React3.useState(void 0);
541
+ const yAxisDomain = getYAxisDomain(autoMinValue, minValue, maxValue);
542
+ const hasOnValueChange = !!onValueChange;
543
+ const stacked = type === "stacked" || type === "percent";
544
+ const prevActiveRef = React3.useRef(void 0);
545
+ const prevLabelRef = React3.useRef(void 0);
546
+ function valueToPercent(value) {
547
+ return `${(value * 100).toFixed(0)}%`;
548
+ }
549
+ function onBarClick(data2, _, event) {
550
+ var _a, _b, _c, _d;
551
+ event.stopPropagation();
552
+ if (!onValueChange) return;
553
+ if (deepEqual(activeBar, { ...data2.payload, value: data2.value })) {
554
+ setActiveLegend(void 0);
555
+ setActiveBar(void 0);
556
+ onValueChange == null ? void 0 : onValueChange(null);
557
+ } else {
558
+ setActiveLegend((_b = (_a = data2.tooltipPayload) == null ? void 0 : _a[0]) == null ? void 0 : _b.dataKey);
559
+ setActiveBar({
560
+ ...data2.payload,
561
+ value: data2.value
562
+ });
563
+ onValueChange == null ? void 0 : onValueChange({
564
+ eventType: "bar",
565
+ categoryClicked: (_d = (_c = data2.tooltipPayload) == null ? void 0 : _c[0]) == null ? void 0 : _d.dataKey,
566
+ ...data2.payload
567
+ });
568
+ }
569
+ }
570
+ function onCategoryClick(dataKey) {
571
+ if (!hasOnValueChange) return;
572
+ if (dataKey === activeLegend && !activeBar) {
573
+ setActiveLegend(void 0);
574
+ onValueChange == null ? void 0 : onValueChange(null);
575
+ } else {
576
+ setActiveLegend(dataKey);
577
+ onValueChange == null ? void 0 : onValueChange({
578
+ eventType: "category",
579
+ categoryClicked: dataKey
580
+ });
581
+ }
582
+ setActiveBar(void 0);
583
+ }
584
+ return /* @__PURE__ */ React3.createElement(
585
+ "div",
586
+ {
587
+ ref: forwardedRef,
588
+ className: cx("h-80 w-full", className),
589
+ "tremor-id": "tremor-raw",
590
+ ...other
591
+ },
592
+ /* @__PURE__ */ React3.createElement(ResponsiveContainer, null, /* @__PURE__ */ React3.createElement(
593
+ RechartsBarChart,
594
+ {
595
+ data,
596
+ onClick: hasOnValueChange && (activeLegend || activeBar) ? () => {
597
+ setActiveBar(void 0);
598
+ setActiveLegend(void 0);
599
+ onValueChange == null ? void 0 : onValueChange(null);
600
+ } : void 0,
601
+ margin: {
602
+ bottom: xAxisLabel ? 30 : void 0,
603
+ left: yAxisLabel ? 20 : void 0,
604
+ right: yAxisLabel ? 5 : void 0,
605
+ top: 5
606
+ },
607
+ stackOffset: type === "percent" ? "expand" : void 0,
608
+ layout,
609
+ barCategoryGap
610
+ },
611
+ showGridLines ? /* @__PURE__ */ React3.createElement(
612
+ CartesianGrid,
613
+ {
614
+ className: cx("stroke-gray-100 stroke-1"),
615
+ horizontal: layout !== "vertical",
616
+ vertical: layout === "vertical"
617
+ }
618
+ ) : null,
619
+ /* @__PURE__ */ React3.createElement(
620
+ XAxis,
621
+ {
622
+ hide: !showXAxis,
623
+ tick: {
624
+ transform: layout !== "vertical" ? "translate(0, 6)" : void 0
625
+ },
626
+ fill: "",
627
+ stroke: "",
628
+ className: cx(
629
+ "text-xs",
630
+ "fill-gray-500 dark:fill-gray-500",
631
+ { "mt-4": layout !== "vertical" }
632
+ ),
633
+ tickLine: false,
634
+ axisLine: false,
635
+ minTickGap: tickGap,
636
+ ...layout !== "vertical" ? {
637
+ padding: {
638
+ left: paddingValue,
639
+ right: paddingValue
640
+ },
641
+ dataKey: index,
642
+ interval: startEndOnly ? "preserveStartEnd" : intervalType,
643
+ ticks: startEndOnly ? [data[0][index], data[data.length - 1][index]] : void 0
644
+ } : {
645
+ type: "number",
646
+ domain: yAxisDomain,
647
+ tickFormatter: type === "percent" ? valueToPercent : valueFormatter,
648
+ allowDecimals
649
+ }
650
+ },
651
+ xAxisLabel && /* @__PURE__ */ React3.createElement(
652
+ Label,
653
+ {
654
+ position: "insideBottom",
655
+ offset: -20,
656
+ className: "fill-gray-800 text-sm font-medium dark:fill-gray-200"
657
+ },
658
+ xAxisLabel
659
+ )
660
+ ),
661
+ /* @__PURE__ */ React3.createElement(
662
+ YAxis,
663
+ {
664
+ width: yAxisWidth,
665
+ hide: !showYAxis,
666
+ axisLine: false,
667
+ tickLine: false,
668
+ fill: "",
669
+ stroke: "",
670
+ className: cx(
671
+ "text-xs",
672
+ "fill-gray-500 dark:fill-gray-500"
673
+ ),
674
+ tick: {
675
+ transform: layout !== "vertical" ? "translate(-3, 0)" : "translate(0, 0)"
676
+ },
677
+ ...layout !== "vertical" ? {
678
+ type: "number",
679
+ domain: yAxisDomain,
680
+ tickFormatter: type === "percent" ? valueToPercent : valueFormatter,
681
+ allowDecimals
682
+ } : {
683
+ dataKey: index,
684
+ ticks: startEndOnly ? [data[0][index], data[data.length - 1][index]] : void 0,
685
+ type: "category",
686
+ interval: "equidistantPreserveStart"
687
+ }
688
+ },
689
+ yAxisLabel && /* @__PURE__ */ React3.createElement(
690
+ Label,
691
+ {
692
+ position: "insideLeft",
693
+ style: { textAnchor: "middle" },
694
+ angle: -90,
695
+ offset: -15,
696
+ className: "fill-gray-800 text-sm font-medium dark:fill-gray-200"
697
+ },
698
+ yAxisLabel
699
+ )
700
+ ),
701
+ /* @__PURE__ */ React3.createElement(
702
+ Tooltip,
703
+ {
704
+ wrapperStyle: { outline: "none", zIndex: 10 },
705
+ isAnimationActive: true,
706
+ animationDuration: 100,
707
+ cursor: { fill: "#d1d5db", opacity: "0.15" },
708
+ offset: 20,
709
+ position: {
710
+ y: layout === "horizontal" ? 0 : void 0,
711
+ x: layout === "horizontal" ? void 0 : yAxisWidth + 20
712
+ },
713
+ content: ({ active, payload, label }) => {
714
+ const cleanPayload = payload ? payload.map((item) => ({
715
+ category: item.dataKey,
716
+ value: item.value,
717
+ index: item.payload[index],
718
+ color: categoryColors.get(
719
+ item.dataKey
720
+ ),
721
+ type: item.type,
722
+ payload: item.payload
723
+ })) : [];
724
+ if (tooltipCallback && (active !== prevActiveRef.current || label !== prevLabelRef.current)) {
725
+ tooltipCallback({ active, payload: cleanPayload, label });
726
+ prevActiveRef.current = active;
727
+ prevLabelRef.current = label;
728
+ }
729
+ return showTooltip && active ? CustomTooltip ? /* @__PURE__ */ React3.createElement(
730
+ CustomTooltip,
731
+ {
732
+ active,
733
+ payload: cleanPayload,
734
+ label
735
+ }
736
+ ) : /* @__PURE__ */ React3.createElement(
737
+ ChartTooltip,
738
+ {
739
+ active,
740
+ payload: cleanPayload,
741
+ label,
742
+ valueFormatter
743
+ }
744
+ ) : null;
745
+ }
746
+ }
747
+ ),
748
+ showLegend ? /* @__PURE__ */ React3.createElement(
749
+ RechartsLegend,
750
+ {
751
+ verticalAlign: "top",
752
+ height: legendHeight,
753
+ content: ({ payload }) => ChartLegend(
754
+ { payload },
755
+ categoryColors,
756
+ setLegendHeight,
757
+ activeLegend,
758
+ hasOnValueChange ? (clickedLegendItem) => onCategoryClick(clickedLegendItem) : void 0,
759
+ enableLegendSlider,
760
+ legendPosition,
761
+ yAxisWidth
762
+ )
763
+ }
764
+ ) : null,
765
+ categories.map((category) => /* @__PURE__ */ React3.createElement(
766
+ Bar,
767
+ {
768
+ className: cx(
769
+ getColorClassName(
770
+ categoryColors.get(category),
771
+ "fill"
772
+ ),
773
+ onValueChange ? "cursor-pointer" : ""
774
+ ),
775
+ key: category,
776
+ name: category,
777
+ type: "linear",
778
+ dataKey: category,
779
+ stackId: stacked ? "stack" : void 0,
780
+ isAnimationActive: false,
781
+ fill: "",
782
+ shape: (props2) => renderShape(props2, activeBar, activeLegend, layout),
783
+ onClick: onBarClick
784
+ }
785
+ ))
786
+ ))
787
+ );
788
+ }
789
+ );
790
+ BarChart.displayName = "BarChart";
791
+
792
+ // src/charts/SettlementSpeedChart.tsx
793
+ function SettlementSpeedChart({
794
+ title,
795
+ subtitle,
796
+ data,
797
+ index,
798
+ categories,
799
+ colors,
800
+ className
801
+ }) {
802
+ return /* @__PURE__ */ React.createElement("div", { className }, /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-black" }, title), subtitle && /* @__PURE__ */ React.createElement("p", { className: "mt-1 text-sm text-gray-500" }, subtitle), /* @__PURE__ */ React.createElement(
803
+ BarChart,
804
+ {
805
+ className: "mt-4 h-72",
806
+ data,
807
+ index,
808
+ categories,
809
+ colors,
810
+ type: "percent",
811
+ showLegend: true,
812
+ showTooltip: true,
813
+ valueFormatter: (value) => `${value}%`
814
+ }
815
+ ));
816
+ }
817
+
818
+ // src/data/parseSettlementData.ts
819
+ function parsePercentage(value) {
820
+ return parseFloat(value.replace("%", ""));
821
+ }
822
+ function parseSettlementCSV(csv) {
823
+ const lines = csv.trim().split("\n");
824
+ const headers = lines[0].split(",");
825
+ const months = headers.slice(1);
826
+ const rows = {};
827
+ for (let i = 1; i < lines.length; i++) {
828
+ const cols = lines[i].split(",");
829
+ const label = cols[0];
830
+ const values = cols.slice(1).map(parsePercentage);
831
+ rows[label] = values;
832
+ }
833
+ function buildGroup(prefix) {
834
+ return months.map((month, i) => ({
835
+ month,
836
+ Instant: rows[`${prefix} Instant`][i],
837
+ Fast: rows[`${prefix} Fast`][i],
838
+ SLA: rows[`${prefix} SLA`][i]
839
+ }));
840
+ }
841
+ return {
842
+ crypto: buildGroup("Crypto"),
843
+ g3: buildGroup("G3"),
844
+ g7: buildGroup("G7")
845
+ };
846
+ }
847
+ var settlementData = parseSettlementCSV(settlement_speeds_default);
848
+
849
+ // src/framer/G7SettlementChart.framer.tsx
850
+ var DEFAULT_CSV_URL = "";
851
+ function G7SettlementChart({
852
+ title = "G7",
853
+ subtitle = "Settlement speed distribution",
854
+ csvUrl = DEFAULT_CSV_URL,
855
+ colors,
856
+ width = "100%",
857
+ height = "auto"
858
+ }) {
859
+ const [data, setData] = React4.useState([]);
860
+ React4.useEffect(() => {
861
+ if (!csvUrl) return;
862
+ fetch(csvUrl).then((res) => res.text()).then((csv) => {
863
+ const parsed = parseSettlementCSV(csv);
864
+ setData(parsed.g7);
865
+ }).catch(console.error);
866
+ }, [csvUrl]);
867
+ if (data.length === 0) {
868
+ return /* @__PURE__ */ React4.createElement("div", { style: { width, height }, className: "flex items-center justify-center text-sm text-gray-400" }, csvUrl ? "Loading..." : "Set a CSV URL");
869
+ }
870
+ return /* @__PURE__ */ React4.createElement("div", { style: { width, height } }, /* @__PURE__ */ React4.createElement(
871
+ SettlementSpeedChart,
872
+ {
873
+ title,
874
+ subtitle,
875
+ data,
876
+ index: "month",
877
+ categories: ["Instant", "Fast", "SLA"],
878
+ colors
879
+ }
880
+ ));
881
+ }
882
+
883
+ // src/framer/G3SettlementChart.framer.tsx
884
+ import React5 from "react";
885
+ function G3SettlementChart({
886
+ title = "G3",
887
+ subtitle = "Settlement speed distribution",
888
+ csvUrl = "",
889
+ colors,
890
+ width = "100%",
891
+ height = "auto"
892
+ }) {
893
+ const [data, setData] = React5.useState([]);
894
+ React5.useEffect(() => {
895
+ if (!csvUrl) return;
896
+ fetch(csvUrl).then((res) => res.text()).then((csv) => {
897
+ const parsed = parseSettlementCSV(csv);
898
+ setData(parsed.g3);
899
+ }).catch(console.error);
900
+ }, [csvUrl]);
901
+ if (data.length === 0) {
902
+ return /* @__PURE__ */ React5.createElement("div", { style: { width, height }, className: "flex items-center justify-center text-sm text-gray-400" }, csvUrl ? "Loading..." : "Set a CSV URL");
903
+ }
904
+ return /* @__PURE__ */ React5.createElement("div", { style: { width, height } }, /* @__PURE__ */ React5.createElement(
905
+ SettlementSpeedChart,
906
+ {
907
+ title,
908
+ subtitle,
909
+ data,
910
+ index: "month",
911
+ categories: ["Instant", "Fast", "SLA"],
912
+ colors
913
+ }
914
+ ));
915
+ }
916
+
917
+ // src/framer/CryptoSettlementChart.framer.tsx
918
+ import React6 from "react";
919
+ function CryptoSettlementChart({
920
+ title = "Crypto",
921
+ subtitle = "Settlement speed distribution",
922
+ csvUrl = "",
923
+ colors,
924
+ width = "100%",
925
+ height = "auto"
926
+ }) {
927
+ const [data, setData] = React6.useState([]);
928
+ React6.useEffect(() => {
929
+ if (!csvUrl) return;
930
+ fetch(csvUrl).then((res) => res.text()).then((csv) => {
931
+ const parsed = parseSettlementCSV(csv);
932
+ setData(parsed.crypto);
933
+ }).catch(console.error);
934
+ }, [csvUrl]);
935
+ if (data.length === 0) {
936
+ return /* @__PURE__ */ React6.createElement("div", { style: { width, height }, className: "flex items-center justify-center text-sm text-gray-400" }, csvUrl ? "Loading..." : "Set a CSV URL");
937
+ }
938
+ return /* @__PURE__ */ React6.createElement("div", { style: { width, height } }, /* @__PURE__ */ React6.createElement(
939
+ SettlementSpeedChart,
940
+ {
941
+ title,
942
+ subtitle,
943
+ data,
944
+ index: "month",
945
+ categories: ["Instant", "Fast", "SLA"],
946
+ colors
947
+ }
948
+ ));
949
+ }
950
+ export {
951
+ CryptoSettlementChart,
952
+ G3SettlementChart,
953
+ G7SettlementChart
954
+ };
955
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/framer/G7SettlementChart.framer.tsx","../src/components/BarChart.tsx","../src/lib/useOnWindowResize.ts","../src/lib/chartColors.ts","../src/lib/cx.ts","../src/lib/getYAxisDomain.ts","../src/charts/SettlementSpeedChart.tsx","../src/data/parseSettlementData.ts","../src/framer/G3SettlementChart.framer.tsx","../src/framer/CryptoSettlementChart.framer.tsx"],"sourcesContent":["\"use client\"\n\nimport React from \"react\"\nimport { SettlementSpeedChart } from \"../charts/SettlementSpeedChart\"\nimport { parseSettlementCSV } from \"../data/parseSettlementData\"\nimport type { AvailableChartColorsKeys } from \"../lib/chartColors\"\n\ninterface Props {\n title?: string\n subtitle?: string\n csvUrl?: string\n colors?: AvailableChartColorsKeys[]\n width?: number | string\n height?: number | string\n}\n\nconst DEFAULT_CSV_URL = \"\"\n\nexport default function G7SettlementChart({\n title = \"G7\",\n subtitle = \"Settlement speed distribution\",\n csvUrl = DEFAULT_CSV_URL,\n colors,\n width = \"100%\",\n height = \"auto\",\n}: Props) {\n const [data, setData] = React.useState<Record<string, any>[]>([])\n\n React.useEffect(() => {\n if (!csvUrl) return\n fetch(csvUrl)\n .then((res) => res.text())\n .then((csv) => {\n const parsed = parseSettlementCSV(csv)\n setData(parsed.g7)\n })\n .catch(console.error)\n }, [csvUrl])\n\n if (data.length === 0) {\n return (\n <div style={{ width, height }} className=\"flex items-center justify-center text-sm text-gray-400\">\n {csvUrl ? \"Loading...\" : \"Set a CSV URL\"}\n </div>\n )\n }\n\n return (\n <div style={{ width, height }}>\n <SettlementSpeedChart\n title={title}\n subtitle={subtitle}\n data={data}\n index=\"month\"\n categories={[\"Instant\", \"Fast\", \"SLA\"]}\n colors={colors}\n />\n </div>\n )\n}\n","// Tremor BarChart [v1.0.0]\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\n\"use client\"\n\nimport React from \"react\"\nimport { RiArrowLeftSLine, RiArrowRightSLine } from \"@remixicon/react\"\nimport {\n Bar,\n CartesianGrid,\n Label,\n BarChart as RechartsBarChart,\n Legend as RechartsLegend,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis,\n} from \"recharts\"\nimport type { AxisDomain } from \"recharts/types/util/types\"\n\nimport { useOnWindowResize } from \"@/lib/useOnWindowResize\"\nimport {\n AvailableChartColors,\n type AvailableChartColorsKeys,\n constructCategoryColors,\n getColorClassName,\n} from \"@/lib/chartColors\"\nimport { cx } from \"@/lib/cx\"\nimport { getYAxisDomain } from \"@/lib/getYAxisDomain\"\n\nfunction deepEqual<T>(obj1: T, obj2: T): boolean {\n if (obj1 === obj2) return true\n if (\n typeof obj1 !== \"object\" ||\n typeof obj2 !== \"object\" ||\n obj1 === null ||\n obj2 === null\n ) {\n return false\n }\n const keys1 = Object.keys(obj1) as Array<keyof T>\n const keys2 = Object.keys(obj2) as Array<keyof T>\n if (keys1.length !== keys2.length) return false\n for (const key of keys1) {\n if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) return false\n }\n return true\n}\n\nconst renderShape = (\n props: any,\n activeBar: any | undefined,\n activeLegend: string | undefined,\n layout: string,\n) => {\n const { fillOpacity, name, payload, value } = props\n let { x, width, y, height } = props\n if (layout === \"horizontal\" && height < 0) {\n y += height\n height = Math.abs(height)\n } else if (layout === \"vertical\" && width < 0) {\n x += width\n width = Math.abs(width)\n }\n return (\n <rect\n x={x}\n y={y}\n width={width}\n height={height}\n opacity={\n activeBar || (activeLegend && activeLegend !== name)\n ? deepEqual(activeBar, { ...payload, value })\n ? fillOpacity\n : 0.3\n : fillOpacity\n }\n />\n )\n}\n\ninterface LegendItemProps {\n name: string\n color: AvailableChartColorsKeys\n onClick?: (name: string, color: AvailableChartColorsKeys) => void\n activeLegend?: string\n}\n\nconst LegendItem = ({\n name,\n color,\n onClick,\n activeLegend,\n}: LegendItemProps) => {\n const hasOnValueChange = !!onClick\n return (\n <li\n className={cx(\n \"group inline-flex flex-nowrap items-center gap-1.5 rounded-sm px-2 py-1 whitespace-nowrap transition\",\n hasOnValueChange\n ? \"cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800\"\n : \"cursor-default\",\n )}\n onClick={(e) => {\n e.stopPropagation()\n onClick?.(name, color)\n }}\n >\n <span\n className={cx(\n \"size-2 shrink-0 rounded-xs\",\n getColorClassName(color, \"bg\"),\n activeLegend && activeLegend !== name ? \"opacity-40\" : \"opacity-100\",\n )}\n aria-hidden={true}\n />\n <p\n className={cx(\n \"truncate text-xs whitespace-nowrap\",\n \"text-black\",\n hasOnValueChange && \"group-hover:text-gray-700\",\n activeLegend && activeLegend !== name ? \"opacity-40\" : \"opacity-100\",\n )}\n >\n {name}\n </p>\n </li>\n )\n}\n\ninterface ScrollButtonProps {\n icon: React.ElementType\n onClick?: () => void\n disabled?: boolean\n}\n\nconst ScrollButton = ({ icon, onClick, disabled }: ScrollButtonProps) => {\n const Icon = icon\n const [isPressed, setIsPressed] = React.useState(false)\n const intervalRef = React.useRef<NodeJS.Timeout | null>(null)\n\n React.useEffect(() => {\n if (isPressed) {\n intervalRef.current = setInterval(() => {\n onClick?.()\n }, 300)\n } else {\n clearInterval(intervalRef.current as NodeJS.Timeout)\n }\n return () => clearInterval(intervalRef.current as NodeJS.Timeout)\n }, [isPressed, onClick])\n\n React.useEffect(() => {\n if (disabled) {\n clearInterval(intervalRef.current as NodeJS.Timeout)\n setIsPressed(false)\n }\n }, [disabled])\n\n return (\n <button\n type=\"button\"\n className={cx(\n \"group inline-flex size-5 items-center truncate rounded-sm transition\",\n disabled\n ? \"cursor-not-allowed text-gray-400 dark:text-gray-600\"\n : \"cursor-pointer text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-gray-50\",\n )}\n disabled={disabled}\n onClick={(e) => {\n e.stopPropagation()\n onClick?.()\n }}\n onMouseDown={(e) => {\n e.stopPropagation()\n setIsPressed(true)\n }}\n onMouseUp={(e) => {\n e.stopPropagation()\n setIsPressed(false)\n }}\n >\n <Icon className=\"size-full\" aria-hidden=\"true\" />\n </button>\n )\n}\n\ninterface LegendProps extends React.OlHTMLAttributes<HTMLOListElement> {\n categories: string[]\n colors?: AvailableChartColorsKeys[]\n onClickLegendItem?: (category: string, color: string) => void\n activeLegend?: string\n enableLegendSlider?: boolean\n}\n\ntype HasScrollProps = {\n left: boolean\n right: boolean\n}\n\nconst Legend = React.forwardRef<HTMLOListElement, LegendProps>((props, ref) => {\n const {\n categories,\n colors = AvailableChartColors,\n className,\n onClickLegendItem,\n activeLegend,\n enableLegendSlider = false,\n ...other\n } = props\n const scrollableRef = React.useRef<HTMLInputElement>(null)\n const scrollButtonsRef = React.useRef<HTMLDivElement>(null)\n const [hasScroll, setHasScroll] = React.useState<HasScrollProps | null>(null)\n const [isKeyDowned, setIsKeyDowned] = React.useState<string | null>(null)\n const intervalRef = React.useRef<NodeJS.Timeout | null>(null)\n\n const checkScroll = React.useCallback(() => {\n const scrollable = scrollableRef?.current\n if (!scrollable) return\n const hasLeftScroll = scrollable.scrollLeft > 0\n const hasRightScroll =\n scrollable.scrollWidth - scrollable.clientWidth > scrollable.scrollLeft\n setHasScroll({ left: hasLeftScroll, right: hasRightScroll })\n }, [setHasScroll])\n\n const scrollToTest = React.useCallback(\n (direction: \"left\" | \"right\") => {\n const element = scrollableRef?.current\n const scrollButtons = scrollButtonsRef?.current\n const scrollButtonsWith = scrollButtons?.clientWidth ?? 0\n const width = element?.clientWidth ?? 0\n if (element && enableLegendSlider) {\n element.scrollTo({\n left:\n direction === \"left\"\n ? element.scrollLeft - width + scrollButtonsWith\n : element.scrollLeft + width - scrollButtonsWith,\n behavior: \"smooth\",\n })\n setTimeout(() => {\n checkScroll()\n }, 400)\n }\n },\n [enableLegendSlider, checkScroll],\n )\n\n React.useEffect(() => {\n const keyDownHandler = (key: string) => {\n if (key === \"ArrowLeft\") {\n scrollToTest(\"left\")\n } else if (key === \"ArrowRight\") {\n scrollToTest(\"right\")\n }\n }\n if (isKeyDowned) {\n keyDownHandler(isKeyDowned)\n intervalRef.current = setInterval(() => {\n keyDownHandler(isKeyDowned)\n }, 300)\n } else {\n clearInterval(intervalRef.current as NodeJS.Timeout)\n }\n return () => clearInterval(intervalRef.current as NodeJS.Timeout)\n }, [isKeyDowned, scrollToTest])\n\n const keyDown = (e: KeyboardEvent) => {\n e.stopPropagation()\n if (e.key === \"ArrowLeft\" || e.key === \"ArrowRight\") {\n e.preventDefault()\n setIsKeyDowned(e.key)\n }\n }\n const keyUp = (e: KeyboardEvent) => {\n e.stopPropagation()\n setIsKeyDowned(null)\n }\n\n React.useEffect(() => {\n const scrollable = scrollableRef?.current\n if (enableLegendSlider) {\n checkScroll()\n scrollable?.addEventListener(\"keydown\", keyDown)\n scrollable?.addEventListener(\"keyup\", keyUp)\n }\n return () => {\n scrollable?.removeEventListener(\"keydown\", keyDown)\n scrollable?.removeEventListener(\"keyup\", keyUp)\n }\n }, [checkScroll, enableLegendSlider])\n\n return (\n <ol\n ref={ref}\n className={cx(\"relative overflow-hidden\", className)}\n {...other}\n >\n <div\n ref={scrollableRef}\n tabIndex={0}\n className={cx(\n \"flex h-full\",\n enableLegendSlider\n ? hasScroll?.right || hasScroll?.left\n ? \"snap-mandatory items-center overflow-auto pr-12 pl-4 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\n : \"\"\n : \"flex-wrap\",\n )}\n >\n {categories.map((category, index) => (\n <LegendItem\n key={`item-${index}`}\n name={category}\n color={colors[index] as AvailableChartColorsKeys}\n onClick={onClickLegendItem}\n activeLegend={activeLegend}\n />\n ))}\n </div>\n {enableLegendSlider && (hasScroll?.right || hasScroll?.left) ? (\n <>\n <div\n className={cx(\n \"absolute top-0 right-0 bottom-0 flex h-full items-center justify-center pr-1\",\n \"bg-white dark:bg-gray-950\",\n )}\n >\n <ScrollButton\n icon={RiArrowLeftSLine}\n onClick={() => {\n setIsKeyDowned(null)\n scrollToTest(\"left\")\n }}\n disabled={!hasScroll?.left}\n />\n <ScrollButton\n icon={RiArrowRightSLine}\n onClick={() => {\n setIsKeyDowned(null)\n scrollToTest(\"right\")\n }}\n disabled={!hasScroll?.right}\n />\n </div>\n </>\n ) : null}\n </ol>\n )\n})\n\nLegend.displayName = \"Legend\"\n\nconst ChartLegend = (\n { payload }: any,\n categoryColors: Map<string, AvailableChartColorsKeys>,\n setLegendHeight: React.Dispatch<React.SetStateAction<number>>,\n activeLegend: string | undefined,\n onClick?: (category: string, color: string) => void,\n enableLegendSlider?: boolean,\n legendPosition?: \"left\" | \"center\" | \"right\",\n yAxisWidth?: number,\n) => {\n const legendRef = React.useRef<HTMLDivElement>(null)\n\n useOnWindowResize(() => {\n const calculateHeight = (height: number | undefined) =>\n height ? Number(height) + 15 : 60\n setLegendHeight(calculateHeight(legendRef.current?.clientHeight))\n })\n\n const filteredPayload = payload.filter((item: any) => item.type !== \"none\")\n\n const paddingLeft =\n legendPosition === \"left\" && yAxisWidth ? yAxisWidth - 8 : 0\n\n return (\n <div\n style={{ paddingLeft: paddingLeft }}\n ref={legendRef}\n className={cx(\n \"flex items-center\",\n { \"justify-center\": legendPosition === \"center\" },\n {\n \"justify-start\": legendPosition === \"left\",\n },\n { \"justify-end\": legendPosition === \"right\" },\n )}\n >\n <Legend\n categories={filteredPayload.map((entry: any) => entry.value)}\n colors={filteredPayload.map((entry: any) =>\n categoryColors.get(entry.value),\n )}\n onClickLegendItem={onClick}\n activeLegend={activeLegend}\n enableLegendSlider={enableLegendSlider}\n />\n </div>\n )\n}\n\ntype TooltipProps = Pick<ChartTooltipProps, \"active\" | \"payload\" | \"label\">\n\ntype PayloadItem = {\n category: string\n value: number\n index: string\n color: AvailableChartColorsKeys\n type?: string\n payload: any\n}\n\ninterface ChartTooltipProps {\n active: boolean | undefined\n payload: PayloadItem[]\n label: string\n valueFormatter: (value: number) => string\n}\n\nconst ChartTooltip = ({\n active,\n payload,\n label,\n valueFormatter,\n}: ChartTooltipProps) => {\n if (active && payload && payload.length) {\n return (\n <div\n className={cx(\n \"rounded-md border text-sm shadow-md\",\n \"border-gray-200 dark:border-gray-800\",\n \"bg-white dark:bg-gray-950\",\n )}\n >\n <div className={cx(\"border-b border-inherit px-4 py-2\")}>\n <p\n className={cx(\n \"font-medium\",\n \"text-gray-900 dark:text-gray-50\",\n )}\n >\n {label}\n </p>\n </div>\n <div className={cx(\"space-y-1 px-4 py-2\")}>\n {payload.map(({ value, category, color }, index) => (\n <div\n key={`id-${index}`}\n className=\"flex items-center justify-between space-x-8\"\n >\n <div className=\"flex items-center space-x-2\">\n <span\n aria-hidden=\"true\"\n className={cx(\n \"size-2 shrink-0 rounded-xs\",\n getColorClassName(color, \"bg\"),\n )}\n />\n <p\n className={cx(\n \"text-right whitespace-nowrap\",\n \"text-gray-700 dark:text-gray-300\",\n )}\n >\n {category}\n </p>\n </div>\n <p\n className={cx(\n \"text-right font-medium whitespace-nowrap tabular-nums\",\n \"text-gray-900 dark:text-gray-50\",\n )}\n >\n {valueFormatter(value)}\n </p>\n </div>\n ))}\n </div>\n </div>\n )\n }\n return null\n}\n\ntype BaseEventProps = {\n eventType: \"category\" | \"bar\"\n categoryClicked: string\n [key: string]: number | string\n}\n\ntype BarChartEventProps = BaseEventProps | null | undefined\n\ninterface BarChartProps extends React.HTMLAttributes<HTMLDivElement> {\n data: Record<string, any>[]\n index: string\n categories: string[]\n colors?: AvailableChartColorsKeys[]\n valueFormatter?: (value: number) => string\n startEndOnly?: boolean\n showXAxis?: boolean\n showYAxis?: boolean\n showGridLines?: boolean\n yAxisWidth?: number\n intervalType?: \"preserveStartEnd\" | \"equidistantPreserveStart\"\n showTooltip?: boolean\n showLegend?: boolean\n autoMinValue?: boolean\n minValue?: number\n maxValue?: number\n allowDecimals?: boolean\n onValueChange?: (value: BarChartEventProps) => void\n enableLegendSlider?: boolean\n tickGap?: number\n barCategoryGap?: string | number\n xAxisLabel?: string\n yAxisLabel?: string\n layout?: \"vertical\" | \"horizontal\"\n type?: \"default\" | \"stacked\" | \"percent\"\n legendPosition?: \"left\" | \"center\" | \"right\"\n tooltipCallback?: (tooltipCallbackContent: TooltipProps) => void\n customTooltip?: React.ComponentType<TooltipProps>\n}\n\nconst BarChart = React.forwardRef<HTMLDivElement, BarChartProps>(\n (props, forwardedRef) => {\n const {\n data = [],\n categories = [],\n index,\n colors = AvailableChartColors,\n valueFormatter = (value: number) => value.toString(),\n startEndOnly = false,\n showXAxis = true,\n showYAxis = true,\n showGridLines = true,\n yAxisWidth = 56,\n intervalType = \"equidistantPreserveStart\",\n showTooltip = true,\n showLegend = true,\n autoMinValue = false,\n minValue,\n maxValue,\n allowDecimals = true,\n className,\n onValueChange,\n enableLegendSlider = false,\n barCategoryGap,\n tickGap = 5,\n xAxisLabel,\n yAxisLabel,\n layout = \"horizontal\",\n type = \"default\",\n legendPosition = \"right\",\n tooltipCallback,\n customTooltip,\n ...other\n } = props\n const CustomTooltip = customTooltip\n const paddingValue =\n (!showXAxis && !showYAxis) || (startEndOnly && !showYAxis) ? 0 : 20\n const [legendHeight, setLegendHeight] = React.useState(60)\n const [activeLegend, setActiveLegend] = React.useState<string | undefined>(\n undefined,\n )\n const categoryColors = constructCategoryColors(categories, colors)\n const [activeBar, setActiveBar] = React.useState<any | undefined>(undefined)\n const yAxisDomain = getYAxisDomain(autoMinValue, minValue, maxValue)\n const hasOnValueChange = !!onValueChange\n const stacked = type === \"stacked\" || type === \"percent\"\n\n const prevActiveRef = React.useRef<boolean | undefined>(undefined)\n const prevLabelRef = React.useRef<string | undefined>(undefined)\n\n function valueToPercent(value: number) {\n return `${(value * 100).toFixed(0)}%`\n }\n\n function onBarClick(data: any, _: any, event: React.MouseEvent) {\n event.stopPropagation()\n if (!onValueChange) return\n if (deepEqual(activeBar, { ...data.payload, value: data.value })) {\n setActiveLegend(undefined)\n setActiveBar(undefined)\n onValueChange?.(null)\n } else {\n setActiveLegend(data.tooltipPayload?.[0]?.dataKey)\n setActiveBar({\n ...data.payload,\n value: data.value,\n })\n onValueChange?.({\n eventType: \"bar\",\n categoryClicked: data.tooltipPayload?.[0]?.dataKey,\n ...data.payload,\n })\n }\n }\n\n function onCategoryClick(dataKey: string) {\n if (!hasOnValueChange) return\n if (dataKey === activeLegend && !activeBar) {\n setActiveLegend(undefined)\n onValueChange?.(null)\n } else {\n setActiveLegend(dataKey)\n onValueChange?.({\n eventType: \"category\",\n categoryClicked: dataKey,\n })\n }\n setActiveBar(undefined)\n }\n\n return (\n <div\n ref={forwardedRef}\n className={cx(\"h-80 w-full\", className)}\n tremor-id=\"tremor-raw\"\n {...other}\n >\n <ResponsiveContainer>\n <RechartsBarChart\n data={data}\n onClick={\n hasOnValueChange && (activeLegend || activeBar)\n ? () => {\n setActiveBar(undefined)\n setActiveLegend(undefined)\n onValueChange?.(null)\n }\n : undefined\n }\n margin={{\n bottom: xAxisLabel ? 30 : undefined,\n left: yAxisLabel ? 20 : undefined,\n right: yAxisLabel ? 5 : undefined,\n top: 5,\n }}\n stackOffset={type === \"percent\" ? \"expand\" : undefined}\n layout={layout}\n barCategoryGap={barCategoryGap}\n >\n {showGridLines ? (\n <CartesianGrid\n className={cx(\"stroke-gray-100 stroke-1\")}\n horizontal={layout !== \"vertical\"}\n vertical={layout === \"vertical\"}\n />\n ) : null}\n <XAxis\n hide={!showXAxis}\n tick={{\n transform:\n layout !== \"vertical\" ? \"translate(0, 6)\" : undefined,\n }}\n fill=\"\"\n stroke=\"\"\n className={cx(\n \"text-xs\",\n \"fill-gray-500 dark:fill-gray-500\",\n { \"mt-4\": layout !== \"vertical\" },\n )}\n tickLine={false}\n axisLine={false}\n minTickGap={tickGap}\n {...(layout !== \"vertical\"\n ? {\n padding: {\n left: paddingValue,\n right: paddingValue,\n },\n dataKey: index,\n interval: startEndOnly ? \"preserveStartEnd\" : intervalType,\n ticks: startEndOnly\n ? [data[0][index], data[data.length - 1][index]]\n : undefined,\n }\n : {\n type: \"number\",\n domain: yAxisDomain as AxisDomain,\n tickFormatter:\n type === \"percent\" ? valueToPercent : valueFormatter,\n allowDecimals: allowDecimals,\n })}\n >\n {xAxisLabel && (\n <Label\n position=\"insideBottom\"\n offset={-20}\n className=\"fill-gray-800 text-sm font-medium dark:fill-gray-200\"\n >\n {xAxisLabel}\n </Label>\n )}\n </XAxis>\n <YAxis\n width={yAxisWidth}\n hide={!showYAxis}\n axisLine={false}\n tickLine={false}\n fill=\"\"\n stroke=\"\"\n className={cx(\n \"text-xs\",\n \"fill-gray-500 dark:fill-gray-500\",\n )}\n tick={{\n transform:\n layout !== \"vertical\"\n ? \"translate(-3, 0)\"\n : \"translate(0, 0)\",\n }}\n {...(layout !== \"vertical\"\n ? {\n type: \"number\",\n domain: yAxisDomain as AxisDomain,\n tickFormatter:\n type === \"percent\" ? valueToPercent : valueFormatter,\n allowDecimals: allowDecimals,\n }\n : {\n dataKey: index,\n ticks: startEndOnly\n ? [data[0][index], data[data.length - 1][index]]\n : undefined,\n type: \"category\",\n interval: \"equidistantPreserveStart\",\n })}\n >\n {yAxisLabel && (\n <Label\n position=\"insideLeft\"\n style={{ textAnchor: \"middle\" }}\n angle={-90}\n offset={-15}\n className=\"fill-gray-800 text-sm font-medium dark:fill-gray-200\"\n >\n {yAxisLabel}\n </Label>\n )}\n </YAxis>\n <Tooltip\n wrapperStyle={{ outline: \"none\", zIndex: 10 }}\n isAnimationActive={true}\n animationDuration={100}\n cursor={{ fill: \"#d1d5db\", opacity: \"0.15\" }}\n offset={20}\n position={{\n y: layout === \"horizontal\" ? 0 : undefined,\n x: layout === \"horizontal\" ? undefined : yAxisWidth + 20,\n }}\n content={({ active, payload, label }) => {\n const cleanPayload: TooltipProps[\"payload\"] = payload\n ? payload.map((item: any) => ({\n category: item.dataKey,\n value: item.value,\n index: item.payload[index],\n color: categoryColors.get(\n item.dataKey,\n ) as AvailableChartColorsKeys,\n type: item.type,\n payload: item.payload,\n }))\n : []\n\n if (\n tooltipCallback &&\n (active !== prevActiveRef.current ||\n label !== prevLabelRef.current)\n ) {\n tooltipCallback({ active, payload: cleanPayload, label })\n prevActiveRef.current = active\n prevLabelRef.current = label\n }\n\n return showTooltip && active ? (\n CustomTooltip ? (\n <CustomTooltip\n active={active}\n payload={cleanPayload}\n label={label}\n />\n ) : (\n <ChartTooltip\n active={active}\n payload={cleanPayload}\n label={label}\n valueFormatter={valueFormatter}\n />\n )\n ) : null\n }}\n />\n {showLegend ? (\n <RechartsLegend\n verticalAlign=\"top\"\n height={legendHeight}\n content={({ payload }) =>\n ChartLegend(\n { payload },\n categoryColors,\n setLegendHeight,\n activeLegend,\n hasOnValueChange\n ? (clickedLegendItem: string) =>\n onCategoryClick(clickedLegendItem)\n : undefined,\n enableLegendSlider,\n legendPosition,\n yAxisWidth,\n )\n }\n />\n ) : null}\n {categories.map((category) => (\n <Bar\n className={cx(\n getColorClassName(\n categoryColors.get(category) as AvailableChartColorsKeys,\n \"fill\",\n ),\n onValueChange ? \"cursor-pointer\" : \"\",\n )}\n key={category}\n name={category}\n type=\"linear\"\n dataKey={category}\n stackId={stacked ? \"stack\" : undefined}\n isAnimationActive={false}\n fill=\"\"\n shape={(props: any) =>\n renderShape(props, activeBar, activeLegend, layout)\n }\n onClick={onBarClick}\n />\n ))}\n </RechartsBarChart>\n </ResponsiveContainer>\n </div>\n )\n },\n)\n\nBarChart.displayName = \"BarChart\"\n\nexport { BarChart, type BarChartEventProps, type TooltipProps }\n","// Tremor useOnWindowResize [v0.0.2]\n\nimport * as React from \"react\"\n\nexport const useOnWindowResize = (handler: () => void) => {\n React.useEffect(() => {\n const handleResize = () => {\n handler()\n }\n handleResize()\n window.addEventListener(\"resize\", handleResize)\n\n return () => window.removeEventListener(\"resize\", handleResize)\n }, [handler])\n}\n","// Tremor chartColors [v0.1.0]\n\nexport type ColorUtility = \"bg\" | \"stroke\" | \"fill\" | \"text\"\n\nexport const chartColors = {\n blue: {\n bg: \"bg-blue-500\",\n stroke: \"stroke-blue-500\",\n fill: \"fill-blue-500\",\n text: \"text-blue-500\",\n },\n emerald: {\n bg: \"bg-emerald-500\",\n stroke: \"stroke-emerald-500\",\n fill: \"fill-emerald-500\",\n text: \"text-emerald-500\",\n },\n violet: {\n bg: \"bg-violet-500\",\n stroke: \"stroke-violet-500\",\n fill: \"fill-violet-500\",\n text: \"text-violet-500\",\n },\n amber: {\n bg: \"bg-amber-500\",\n stroke: \"stroke-amber-500\",\n fill: \"fill-amber-500\",\n text: \"text-amber-500\",\n },\n gray: {\n bg: \"bg-gray-500\",\n stroke: \"stroke-gray-500\",\n fill: \"fill-gray-500\",\n text: \"text-gray-500\",\n },\n cyan: {\n bg: \"bg-cyan-500\",\n stroke: \"stroke-cyan-500\",\n fill: \"fill-cyan-500\",\n text: \"text-cyan-500\",\n },\n pink: {\n bg: \"bg-pink-500\",\n stroke: \"stroke-pink-500\",\n fill: \"fill-pink-500\",\n text: \"text-pink-500\",\n },\n lime: {\n bg: \"bg-lime-500\",\n stroke: \"stroke-lime-500\",\n fill: \"fill-lime-500\",\n text: \"text-lime-500\",\n },\n fuchsia: {\n bg: \"bg-fuchsia-500\",\n stroke: \"stroke-fuchsia-500\",\n fill: \"fill-fuchsia-500\",\n text: \"text-fuchsia-500\",\n },\n} as const satisfies {\n [color: string]: {\n [key in ColorUtility]: string\n }\n}\n\nexport type AvailableChartColorsKeys = keyof typeof chartColors\n\nexport const AvailableChartColors: AvailableChartColorsKeys[] = Object.keys(\n chartColors,\n) as Array<AvailableChartColorsKeys>\n\nexport const constructCategoryColors = (\n categories: string[],\n colors: AvailableChartColorsKeys[],\n): Map<string, AvailableChartColorsKeys> => {\n const categoryColors = new Map<string, AvailableChartColorsKeys>()\n categories.forEach((category, index) => {\n categoryColors.set(category, colors[index % colors.length])\n })\n return categoryColors\n}\n\nexport const getColorClassName = (\n color: AvailableChartColorsKeys,\n type: ColorUtility,\n): string => {\n const fallbackColor = {\n bg: \"bg-gray-500\",\n stroke: \"stroke-gray-500\",\n fill: \"fill-gray-500\",\n text: \"text-gray-500\",\n }\n return chartColors[color]?.[type] ?? fallbackColor[type]\n}\n","// Tremor cx [v0.0.0]\n\nimport clsx, { type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cx(...args: ClassValue[]) {\n return twMerge(clsx(...args))\n}\n","// Tremor getYAxisDomain [v0.0.0]\n\nexport const getYAxisDomain = (\n autoMinValue: boolean,\n minValue: number | undefined,\n maxValue: number | undefined,\n) => {\n const minDomain = autoMinValue ? \"auto\" : (minValue ?? 0)\n const maxDomain = maxValue ?? \"auto\"\n return [minDomain, maxDomain]\n}\n","\"use client\"\n\nimport { BarChart } from \"@/components/BarChart\"\nimport type { AvailableChartColorsKeys } from \"@/lib/chartColors\"\n\ninterface SettlementSpeedChartProps {\n title: string\n subtitle?: string\n data: Record<string, any>[]\n index: string\n categories: string[]\n colors?: AvailableChartColorsKeys[]\n className?: string\n}\n\nexport function SettlementSpeedChart({\n title,\n subtitle,\n data,\n index,\n categories,\n colors,\n className,\n}: SettlementSpeedChartProps) {\n return (\n <div className={className}>\n <h3 className=\"text-lg font-semibold text-black\">\n {title}\n </h3>\n {subtitle && (\n <p className=\"mt-1 text-sm text-gray-500\">\n {subtitle}\n </p>\n )}\n <BarChart\n className=\"mt-4 h-72\"\n data={data}\n index={index}\n categories={categories}\n colors={colors}\n type=\"percent\"\n showLegend={true}\n showTooltip={true}\n valueFormatter={(value) => `${value}%`}\n />\n </div>\n )\n}\n","interface SettlementRow {\n month: string\n Instant: number\n Fast: number\n SLA: number\n}\n\nexport interface SettlementData {\n crypto: SettlementRow[]\n g3: SettlementRow[]\n g7: SettlementRow[]\n}\n\nfunction parsePercentage(value: string): number {\n return parseFloat(value.replace(\"%\", \"\"))\n}\n\nexport function parseSettlementCSV(csv: string): SettlementData {\n const lines = csv.trim().split(\"\\n\")\n const headers = lines[0].split(\",\")\n const months = headers.slice(1) // [\"Aug 25\", \"Sep 25\", ...]\n\n const rows: Record<string, number[]> = {}\n for (let i = 1; i < lines.length; i++) {\n const cols = lines[i].split(\",\")\n const label = cols[0]\n const values = cols.slice(1).map(parsePercentage)\n rows[label] = values\n }\n\n function buildGroup(prefix: string): SettlementRow[] {\n return months.map((month, i) => ({\n month,\n Instant: rows[`${prefix} Instant`][i],\n Fast: rows[`${prefix} Fast`][i],\n SLA: rows[`${prefix} SLA`][i],\n }))\n }\n\n return {\n crypto: buildGroup(\"Crypto\"),\n g3: buildGroup(\"G3\"),\n g7: buildGroup(\"G7\"),\n }\n}\n\n// For local dev: import CSV statically\n// For Framer: components fetch CSV at runtime via URL\nlet _cachedData: SettlementData | null = null\n\nexport async function loadSettlementData(): Promise<SettlementData> {\n if (_cachedData) return _cachedData\n const rawCSV = (await import(\"./settlement-speeds.csv?raw\")).default\n _cachedData = parseSettlementCSV(rawCSV)\n return _cachedData\n}\n\n// Synchronous access for static import (local dev)\nimport rawCSV from \"./settlement-speeds.csv?raw\"\nexport const settlementData = parseSettlementCSV(rawCSV)\n","\"use client\"\n\nimport React from \"react\"\nimport { SettlementSpeedChart } from \"../charts/SettlementSpeedChart\"\nimport { parseSettlementCSV } from \"../data/parseSettlementData\"\nimport type { AvailableChartColorsKeys } from \"../lib/chartColors\"\n\ninterface Props {\n title?: string\n subtitle?: string\n csvUrl?: string\n colors?: AvailableChartColorsKeys[]\n width?: number | string\n height?: number | string\n}\n\nexport default function G3SettlementChart({\n title = \"G3\",\n subtitle = \"Settlement speed distribution\",\n csvUrl = \"\",\n colors,\n width = \"100%\",\n height = \"auto\",\n}: Props) {\n const [data, setData] = React.useState<Record<string, any>[]>([])\n\n React.useEffect(() => {\n if (!csvUrl) return\n fetch(csvUrl)\n .then((res) => res.text())\n .then((csv) => {\n const parsed = parseSettlementCSV(csv)\n setData(parsed.g3)\n })\n .catch(console.error)\n }, [csvUrl])\n\n if (data.length === 0) {\n return (\n <div style={{ width, height }} className=\"flex items-center justify-center text-sm text-gray-400\">\n {csvUrl ? \"Loading...\" : \"Set a CSV URL\"}\n </div>\n )\n }\n\n return (\n <div style={{ width, height }}>\n <SettlementSpeedChart\n title={title}\n subtitle={subtitle}\n data={data}\n index=\"month\"\n categories={[\"Instant\", \"Fast\", \"SLA\"]}\n colors={colors}\n />\n </div>\n )\n}\n","\"use client\"\n\nimport React from \"react\"\nimport { SettlementSpeedChart } from \"../charts/SettlementSpeedChart\"\nimport { parseSettlementCSV } from \"../data/parseSettlementData\"\nimport type { AvailableChartColorsKeys } from \"../lib/chartColors\"\n\ninterface Props {\n title?: string\n subtitle?: string\n csvUrl?: string\n colors?: AvailableChartColorsKeys[]\n width?: number | string\n height?: number | string\n}\n\nexport default function CryptoSettlementChart({\n title = \"Crypto\",\n subtitle = \"Settlement speed distribution\",\n csvUrl = \"\",\n colors,\n width = \"100%\",\n height = \"auto\",\n}: Props) {\n const [data, setData] = React.useState<Record<string, any>[]>([])\n\n React.useEffect(() => {\n if (!csvUrl) return\n fetch(csvUrl)\n .then((res) => res.text())\n .then((csv) => {\n const parsed = parseSettlementCSV(csv)\n setData(parsed.crypto)\n })\n .catch(console.error)\n }, [csvUrl])\n\n if (data.length === 0) {\n return (\n <div style={{ width, height }} className=\"flex items-center justify-center text-sm text-gray-400\">\n {csvUrl ? \"Loading...\" : \"Set a CSV URL\"}\n </div>\n )\n }\n\n return (\n <div style={{ width, height }}>\n <SettlementSpeedChart\n title={title}\n subtitle={subtitle}\n data={data}\n index=\"month\"\n categories={[\"Instant\", \"Fast\", \"SLA\"]}\n colors={colors}\n />\n </div>\n )\n}\n"],"mappings":"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","names":["React","React","React","React","data","props","React","React","React","React","React"]}
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import {
3
+ settlement_speeds_default
4
+ } from "./chunk-24EYBU3B.mjs";
5
+ export {
6
+ settlement_speeds_default as default
7
+ };
8
+ //# sourceMappingURL=settlement-speeds-PQNHMD2W.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@gelatin-hero/framer-charts",
3
+ "version": "0.1.0",
4
+ "description": "Settlement speed chart components for Framer",
5
+ "main": "dist/index.mjs",
6
+ "module": "dist/index.mjs",
7
+ "files": ["dist"],
8
+ "scripts": {
9
+ "dev": "next dev",
10
+ "build": "next build",
11
+ "build:lib": "tsup",
12
+ "start": "next start",
13
+ "lint": "next lint",
14
+ "prepublishOnly": "npm run build:lib"
15
+ },
16
+ "peerDependencies": {
17
+ "react": ">=18",
18
+ "react-dom": ">=18"
19
+ },
20
+ "dependencies": {
21
+ "@remixicon/react": "^4.9.0",
22
+ "clsx": "^2.1.1",
23
+ "next": "14.2.28",
24
+ "react": "^18",
25
+ "react-dom": "^18",
26
+ "recharts": "^3.7.0",
27
+ "tailwind-merge": "^3.5.0",
28
+ "tailwind-variants": "^3.2.2"
29
+ },
30
+ "devDependencies": {
31
+ "@tailwindcss/postcss": "^4.2.1",
32
+ "@types/node": "^20",
33
+ "@types/react": "^18",
34
+ "@types/react-dom": "^18",
35
+ "eslint": "^8",
36
+ "eslint-config-next": "14.2.28",
37
+ "postcss": "^8",
38
+ "tailwindcss": "^4.2.1",
39
+ "tsup": "^8.5.1",
40
+ "typescript": "^5"
41
+ }
42
+ }