@orchestra-mcp/widgets 0.2.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/dist/index.js ADDED
@@ -0,0 +1,600 @@
1
+ import {
2
+ saveBlob,
3
+ saveFile,
4
+ uuidFilename
5
+ } from "./chunk-UXC444LG.js";
6
+
7
+ // src/Widget/Widget.tsx
8
+ import { useState } from "react";
9
+ import { BoxIcon } from "@orchestra-mcp/icons";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ var Widget = ({
12
+ title,
13
+ subtitle,
14
+ children,
15
+ icon,
16
+ actions,
17
+ collapsible = false,
18
+ defaultCollapsed = false,
19
+ loading = false,
20
+ error,
21
+ onRefresh,
22
+ size = "medium",
23
+ onContextMenu,
24
+ style
25
+ }) => {
26
+ const [collapsed, setCollapsed] = useState(defaultCollapsed);
27
+ const isCollapsed = collapsible && collapsed;
28
+ const headerClasses = [
29
+ "widget__header",
30
+ isCollapsed ? "widget__header--collapsed" : ""
31
+ ].filter(Boolean).join(" ");
32
+ return /* @__PURE__ */ jsxs("div", { className: `widget widget--${size}${isCollapsed ? " widget--collapsed" : ""}`, "data-testid": "widget", style, children: [
33
+ /* @__PURE__ */ jsxs("div", { className: headerClasses, onContextMenu, children: [
34
+ icon && /* @__PURE__ */ jsx("span", { className: "widget__icon", children: icon }),
35
+ /* @__PURE__ */ jsxs("div", { className: "widget__title-group", children: [
36
+ /* @__PURE__ */ jsx("span", { className: "widget__title", children: title }),
37
+ subtitle && /* @__PURE__ */ jsx("span", { className: "widget__subtitle", children: subtitle })
38
+ ] }),
39
+ /* @__PURE__ */ jsxs("div", { className: "widget__actions", children: [
40
+ actions?.map((action) => /* @__PURE__ */ jsx(
41
+ "button",
42
+ {
43
+ className: "widget__action-btn",
44
+ onClick: action.onClick,
45
+ "aria-label": action.label,
46
+ title: action.label,
47
+ type: "button",
48
+ children: action.icon ?? action.label
49
+ },
50
+ action.id
51
+ )),
52
+ onRefresh && /* @__PURE__ */ jsx(
53
+ "button",
54
+ {
55
+ className: "widget__action-btn",
56
+ onClick: onRefresh,
57
+ "aria-label": "Refresh",
58
+ title: "Refresh",
59
+ type: "button",
60
+ children: /* @__PURE__ */ jsx(BoxIcon, { name: "bx-refresh", size: 14 })
61
+ }
62
+ ),
63
+ collapsible && /* @__PURE__ */ jsx(
64
+ "button",
65
+ {
66
+ className: `widget__collapse-btn${isCollapsed ? " widget__collapse-btn--collapsed" : ""}`,
67
+ onClick: () => setCollapsed((c) => !c),
68
+ "aria-label": isCollapsed ? "Expand" : "Collapse",
69
+ type: "button",
70
+ children: /* @__PURE__ */ jsx(BoxIcon, { name: "bx-chevron-down", size: 14 })
71
+ }
72
+ )
73
+ ] })
74
+ ] }),
75
+ /* @__PURE__ */ jsxs("div", { className: `widget__body${isCollapsed ? " widget__body--hidden" : ""}`, children: [
76
+ error && /* @__PURE__ */ jsxs("div", { className: "widget__error", role: "alert", children: [
77
+ /* @__PURE__ */ jsx("span", { className: "widget__error-text", children: error }),
78
+ onRefresh && /* @__PURE__ */ jsx(
79
+ "button",
80
+ {
81
+ className: "widget__retry-btn",
82
+ onClick: onRefresh,
83
+ type: "button",
84
+ children: "Retry"
85
+ }
86
+ )
87
+ ] }),
88
+ !error && children,
89
+ loading && !error && /* @__PURE__ */ jsx("div", { className: "widget__loading", "data-testid": "widget-loading", children: /* @__PURE__ */ jsx("div", { className: "widget__shimmer" }) })
90
+ ] })
91
+ ] });
92
+ };
93
+
94
+ // src/DataTable/DataTable.tsx
95
+ import { useState as useState2, useMemo, useCallback, useRef } from "react";
96
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
97
+ function isNumeric(value) {
98
+ return value !== "" && !isNaN(Number(value));
99
+ }
100
+ function compareValues(a, b) {
101
+ if (isNumeric(a) && isNumeric(b)) {
102
+ return Number(a) - Number(b);
103
+ }
104
+ return a.localeCompare(b);
105
+ }
106
+ function escapeCsvValue(value) {
107
+ if (value.includes(",") || value.includes('"') || value.includes("\n")) {
108
+ return `"${value.replace(/"/g, '""')}"`;
109
+ }
110
+ return value;
111
+ }
112
+ var CopyIcon = () => /* @__PURE__ */ jsxs2("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
113
+ /* @__PURE__ */ jsx2("rect", { x: "5", y: "5", width: "8", height: "8", rx: "1.5", stroke: "currentColor", strokeWidth: "1.5" }),
114
+ /* @__PURE__ */ jsx2("path", { d: "M11 3H4.5A1.5 1.5 0 0 0 3 4.5V11", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
115
+ ] });
116
+ var CheckIcon = () => /* @__PURE__ */ jsx2("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx2("path", { d: "M3.5 8.5L6.5 11.5L12.5 4.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
117
+ var DownloadIcon = () => /* @__PURE__ */ jsx2("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx2("path", { d: "M8 2v8m0 0L5 7m3 3l3-3M3 12h10", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
118
+ var ImageIcon = () => /* @__PURE__ */ jsxs2("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
119
+ /* @__PURE__ */ jsx2("rect", { x: "2", y: "2", width: "12", height: "12", rx: "2", stroke: "currentColor", strokeWidth: "1.5" }),
120
+ /* @__PURE__ */ jsx2("circle", { cx: "6", cy: "6", r: "1.5", fill: "currentColor" }),
121
+ /* @__PURE__ */ jsx2("path", { d: "M2 11l3-3 2 2 3-3 4 4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
122
+ ] });
123
+ var DataTable = ({
124
+ columns,
125
+ rows,
126
+ sortable = false,
127
+ exportable = false,
128
+ exportImage = false,
129
+ showHeader = false,
130
+ label,
131
+ renderCell,
132
+ onLinkClick,
133
+ maxHeight,
134
+ className
135
+ }) => {
136
+ const [sort, setSort] = useState2({ column: null, direction: "asc" });
137
+ const [copied, setCopied] = useState2(false);
138
+ const timerRef = useRef(null);
139
+ const tableRef = useRef(null);
140
+ const handleSort = useCallback((col) => {
141
+ if (!sortable && !col.sortable) return;
142
+ setSort((prev) => {
143
+ if (prev.column === col.key) {
144
+ return prev.direction === "asc" ? { column: col.key, direction: "desc" } : { column: null, direction: "asc" };
145
+ }
146
+ return { column: col.key, direction: "asc" };
147
+ });
148
+ }, [sortable]);
149
+ const sortedRows = useMemo(() => {
150
+ if (!sort.column) return rows;
151
+ const colIndex = columns.findIndex((c) => c.key === sort.column);
152
+ if (colIndex === -1) return rows;
153
+ const sorted = [...rows].sort((a, b) => compareValues(a[colIndex], b[colIndex]));
154
+ return sort.direction === "desc" ? sorted.reverse() : sorted;
155
+ }, [rows, columns, sort]);
156
+ const buildCsv = useCallback(() => {
157
+ const headerLine = columns.map((c) => escapeCsvValue(c.header)).join(",");
158
+ const dataLines = rows.map((row) => row.map(escapeCsvValue).join(","));
159
+ return [headerLine, ...dataLines].join("\n");
160
+ }, [columns, rows]);
161
+ const handleExport = useCallback(async () => {
162
+ await saveFile(buildCsv(), uuidFilename("table-export", "csv"), "text/csv");
163
+ }, [buildCsv]);
164
+ const handleCopy = useCallback(() => {
165
+ const csv = buildCsv();
166
+ navigator.clipboard.writeText(csv).then(() => {
167
+ setCopied(true);
168
+ if (timerRef.current) clearTimeout(timerRef.current);
169
+ timerRef.current = setTimeout(() => setCopied(false), 2e3);
170
+ });
171
+ }, [buildCsv]);
172
+ const handleExportImage = useCallback(async () => {
173
+ if (!tableRef.current) return;
174
+ const { exportToImage } = await import("./exportToImage-QKASM3RA.js");
175
+ await exportToImage(tableRef.current, "table-export");
176
+ }, []);
177
+ const handleTableClick = useCallback((e) => {
178
+ if (!onLinkClick) return;
179
+ const target = e.target;
180
+ const link = target.closest("[data-md-link]");
181
+ if (link) {
182
+ e.preventDefault();
183
+ onLinkClick(link.dataset.mdLink);
184
+ }
185
+ }, [onLinkClick]);
186
+ const isSortable = (col) => sortable || col.sortable === true;
187
+ const hasActions = exportable || exportImage || showHeader;
188
+ const containerStyle = maxHeight ? { maxHeight: `${maxHeight}px` } : void 0;
189
+ const containerClass = [
190
+ "data-table-container",
191
+ showHeader ? "data-table-container--card" : "",
192
+ maxHeight ? "data-table-container--scrollable" : "",
193
+ className ?? ""
194
+ ].filter(Boolean).join(" ");
195
+ return /* @__PURE__ */ jsxs2("div", { className: containerClass, ref: tableRef, children: [
196
+ showHeader && /* @__PURE__ */ jsxs2("div", { className: "data-table__header", children: [
197
+ /* @__PURE__ */ jsxs2("div", { className: "data-table__header-left", children: [
198
+ /* @__PURE__ */ jsxs2("div", { className: "data-table__dots", "aria-hidden": "true", children: [
199
+ /* @__PURE__ */ jsx2("span", { className: "data-table__dot data-table__dot--red" }),
200
+ /* @__PURE__ */ jsx2("span", { className: "data-table__dot data-table__dot--yellow" }),
201
+ /* @__PURE__ */ jsx2("span", { className: "data-table__dot data-table__dot--green" })
202
+ ] }),
203
+ /* @__PURE__ */ jsx2("span", { className: "data-table__badge", children: label || "Table" })
204
+ ] }),
205
+ /* @__PURE__ */ jsxs2("div", { className: "data-table__actions", children: [
206
+ /* @__PURE__ */ jsx2(
207
+ "button",
208
+ {
209
+ type: "button",
210
+ className: `data-table__btn ${copied ? "data-table__btn--copied" : ""}`,
211
+ onClick: handleCopy,
212
+ "aria-label": "Copy as CSV",
213
+ title: "Copy as CSV",
214
+ children: copied ? /* @__PURE__ */ jsx2(CheckIcon, {}) : /* @__PURE__ */ jsx2(CopyIcon, {})
215
+ }
216
+ ),
217
+ exportable && /* @__PURE__ */ jsx2(
218
+ "button",
219
+ {
220
+ type: "button",
221
+ className: "data-table__btn",
222
+ onClick: handleExport,
223
+ "aria-label": "Download CSV",
224
+ title: "Download CSV",
225
+ children: /* @__PURE__ */ jsx2(DownloadIcon, {})
226
+ }
227
+ ),
228
+ exportImage && /* @__PURE__ */ jsx2(
229
+ "button",
230
+ {
231
+ type: "button",
232
+ className: "data-table__btn",
233
+ onClick: handleExportImage,
234
+ "aria-label": "Export as image",
235
+ title: "Export as image",
236
+ children: /* @__PURE__ */ jsx2(ImageIcon, {})
237
+ }
238
+ )
239
+ ] })
240
+ ] }),
241
+ !showHeader && (exportable || exportImage) && /* @__PURE__ */ jsxs2("div", { className: "data-table__toolbar", children: [
242
+ exportable && /* @__PURE__ */ jsx2("button", { type: "button", className: "data-table__export", onClick: handleExport, children: "Export CSV" }),
243
+ exportImage && /* @__PURE__ */ jsx2(
244
+ "button",
245
+ {
246
+ type: "button",
247
+ className: "data-table__export",
248
+ onClick: handleExportImage,
249
+ "aria-label": "Export as image",
250
+ children: "Export Image"
251
+ }
252
+ )
253
+ ] }),
254
+ /* @__PURE__ */ jsx2("div", { className: "data-table__body", style: containerStyle, children: /* @__PURE__ */ jsxs2("table", { className: "data-table", children: [
255
+ /* @__PURE__ */ jsx2("thead", { children: /* @__PURE__ */ jsx2("tr", { children: columns.map((col) => /* @__PURE__ */ jsxs2(
256
+ "th",
257
+ {
258
+ className: [
259
+ `data-table__th--${col.align ?? "left"}`,
260
+ isSortable(col) ? "data-table__th--sortable" : ""
261
+ ].filter(Boolean).join(" "),
262
+ onClick: () => isSortable(col) && handleSort(col),
263
+ children: [
264
+ renderCell ? /* @__PURE__ */ jsx2("span", { dangerouslySetInnerHTML: { __html: renderCell(col.header) } }) : col.header,
265
+ isSortable(col) && sort.column === col.key && /* @__PURE__ */ jsx2("span", { className: "data-table__sort", children: sort.direction === "asc" ? "\u25B2" : "\u25BC" })
266
+ ]
267
+ },
268
+ col.key
269
+ )) }) }),
270
+ /* @__PURE__ */ jsx2("tbody", { onClick: handleTableClick, children: sortedRows.map((row, rowIdx) => /* @__PURE__ */ jsx2("tr", { children: row.map(
271
+ (cell, cellIdx) => renderCell ? /* @__PURE__ */ jsx2(
272
+ "td",
273
+ {
274
+ className: `data-table__td--${columns[cellIdx]?.align ?? "left"}`,
275
+ dangerouslySetInnerHTML: { __html: renderCell(cell) }
276
+ },
277
+ cellIdx
278
+ ) : /* @__PURE__ */ jsx2(
279
+ "td",
280
+ {
281
+ className: `data-table__td--${columns[cellIdx]?.align ?? "left"}`,
282
+ children: cell
283
+ },
284
+ cellIdx
285
+ )
286
+ ) }, rowIdx)) })
287
+ ] }) })
288
+ ] });
289
+ };
290
+
291
+ // src/Charts/LineChart.tsx
292
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
293
+ var COLORS = ["#3b82f6", "#22c55e", "#f59e0b", "#ef4444", "#8b5cf6", "#06b6d4", "#ec4899", "#f97316"];
294
+ var LineChart = ({
295
+ data,
296
+ width = 400,
297
+ height = 300,
298
+ showLegend = false,
299
+ title,
300
+ className
301
+ }) => {
302
+ if (!data.length) {
303
+ return /* @__PURE__ */ jsx3("div", { className: `chart__empty ${className ?? ""}`, style: { width, height }, children: "No data" });
304
+ }
305
+ const pad = { top: 20, right: 20, bottom: 40, left: 50 };
306
+ const w = width - pad.left - pad.right;
307
+ const h = height - pad.top - pad.bottom;
308
+ const max = Math.max(...data.map((d) => d.value));
309
+ const step = w / Math.max(data.length - 1, 1);
310
+ const points = data.map((d, i) => ({
311
+ x: pad.left + i * step,
312
+ y: pad.top + h - d.value / (max || 1) * h
313
+ }));
314
+ const pathD = points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" ");
315
+ return /* @__PURE__ */ jsxs3("div", { className: `chart ${className ?? ""}`, children: [
316
+ title && /* @__PURE__ */ jsx3("p", { className: "chart__title", children: title }),
317
+ /* @__PURE__ */ jsxs3("svg", { className: "chart__svg", width, height, children: [
318
+ [0, 0.25, 0.5, 0.75, 1].map((t) => /* @__PURE__ */ jsx3(
319
+ "line",
320
+ {
321
+ className: "chart__grid-line",
322
+ x1: pad.left,
323
+ x2: width - pad.right,
324
+ y1: pad.top + h * (1 - t),
325
+ y2: pad.top + h * (1 - t)
326
+ },
327
+ t
328
+ )),
329
+ /* @__PURE__ */ jsx3("path", { className: "chart__line", d: pathD, stroke: COLORS[0] }),
330
+ points.map((p, i) => /* @__PURE__ */ jsx3(
331
+ "circle",
332
+ {
333
+ className: "chart__dot",
334
+ cx: p.x,
335
+ cy: p.y,
336
+ r: 4,
337
+ fill: data[i].color ?? COLORS[0]
338
+ },
339
+ i
340
+ )),
341
+ data.map((d, i) => /* @__PURE__ */ jsx3(
342
+ "text",
343
+ {
344
+ className: "chart__axis-label",
345
+ x: points[i].x,
346
+ y: height - 8,
347
+ textAnchor: "middle",
348
+ children: d.label
349
+ },
350
+ i
351
+ ))
352
+ ] }),
353
+ showLegend && /* @__PURE__ */ jsx3("div", { className: "chart__legend", children: data.map((d, i) => /* @__PURE__ */ jsxs3("span", { className: "chart__legend-item", children: [
354
+ /* @__PURE__ */ jsx3("span", { className: "chart__legend-dot", style: { background: d.color ?? COLORS[i % COLORS.length] } }),
355
+ d.label
356
+ ] }, i)) })
357
+ ] });
358
+ };
359
+
360
+ // src/Charts/BarChart.tsx
361
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
362
+ var COLORS2 = ["#3b82f6", "#22c55e", "#f59e0b", "#ef4444", "#8b5cf6", "#06b6d4", "#ec4899", "#f97316"];
363
+ var BarChart = ({
364
+ data,
365
+ width = 400,
366
+ height = 300,
367
+ showLegend = false,
368
+ title,
369
+ className
370
+ }) => {
371
+ if (!data.length) {
372
+ return /* @__PURE__ */ jsx4("div", { className: `chart__empty ${className ?? ""}`, style: { width, height }, children: "No data" });
373
+ }
374
+ const pad = { top: 20, right: 20, bottom: 40, left: 50 };
375
+ const w = width - pad.left - pad.right;
376
+ const h = height - pad.top - pad.bottom;
377
+ const max = Math.max(...data.map((d) => d.value));
378
+ const barW = w / data.length * 0.7;
379
+ const gap = w / data.length * 0.3;
380
+ return /* @__PURE__ */ jsxs4("div", { className: `chart ${className ?? ""}`, children: [
381
+ title && /* @__PURE__ */ jsx4("p", { className: "chart__title", children: title }),
382
+ /* @__PURE__ */ jsxs4("svg", { className: "chart__svg", width, height, children: [
383
+ [0, 0.25, 0.5, 0.75, 1].map((t) => /* @__PURE__ */ jsx4(
384
+ "line",
385
+ {
386
+ className: "chart__grid-line",
387
+ x1: pad.left,
388
+ x2: width - pad.right,
389
+ y1: pad.top + h * (1 - t),
390
+ y2: pad.top + h * (1 - t)
391
+ },
392
+ t
393
+ )),
394
+ data.map((d, i) => {
395
+ const barH = d.value / (max || 1) * h;
396
+ const x = pad.left + i * (barW + gap) + gap / 2;
397
+ return /* @__PURE__ */ jsxs4("g", { children: [
398
+ /* @__PURE__ */ jsx4(
399
+ "rect",
400
+ {
401
+ className: "chart__bar",
402
+ x,
403
+ y: pad.top + h - barH,
404
+ width: barW,
405
+ height: barH,
406
+ rx: 3,
407
+ fill: d.color ?? COLORS2[i % COLORS2.length]
408
+ }
409
+ ),
410
+ /* @__PURE__ */ jsx4("text", { className: "chart__axis-label", x: x + barW / 2, y: height - 8, textAnchor: "middle", children: d.label })
411
+ ] }, i);
412
+ })
413
+ ] }),
414
+ showLegend && /* @__PURE__ */ jsx4("div", { className: "chart__legend", children: data.map((d, i) => /* @__PURE__ */ jsxs4("span", { className: "chart__legend-item", children: [
415
+ /* @__PURE__ */ jsx4("span", { className: "chart__legend-dot", style: { background: d.color ?? COLORS2[i % COLORS2.length] } }),
416
+ d.label
417
+ ] }, i)) })
418
+ ] });
419
+ };
420
+
421
+ // src/Charts/PieChart.tsx
422
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
423
+ var COLORS3 = ["#3b82f6", "#22c55e", "#f59e0b", "#ef4444", "#8b5cf6", "#06b6d4", "#ec4899", "#f97316"];
424
+ function polarToCartesian(cx, cy, r, angle) {
425
+ const rad = (angle - 90) * Math.PI / 180;
426
+ return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) };
427
+ }
428
+ function arcPath(cx, cy, r, startAngle, endAngle) {
429
+ const start = polarToCartesian(cx, cy, r, endAngle);
430
+ const end = polarToCartesian(cx, cy, r, startAngle);
431
+ const large = endAngle - startAngle > 180 ? 1 : 0;
432
+ return `M ${start.x} ${start.y} A ${r} ${r} 0 ${large} 0 ${end.x} ${end.y} L ${cx} ${cy} Z`;
433
+ }
434
+ var PieChart = ({
435
+ data,
436
+ width = 300,
437
+ height = 300,
438
+ showLegend = true,
439
+ title,
440
+ className
441
+ }) => {
442
+ if (!data.length) {
443
+ return /* @__PURE__ */ jsx5("div", { className: `chart__empty ${className ?? ""}`, style: { width, height }, children: "No data" });
444
+ }
445
+ const total = data.reduce((sum, d) => sum + d.value, 0);
446
+ const cx = width / 2;
447
+ const cy = height / 2;
448
+ const r = Math.min(cx, cy) - 10;
449
+ let startAngle = 0;
450
+ const slices = data.map((d, i) => {
451
+ const sweep = total > 0 ? d.value / total * 360 : 0;
452
+ const path = arcPath(cx, cy, r, startAngle, startAngle + sweep);
453
+ startAngle += sweep;
454
+ return { path, color: d.color ?? COLORS3[i % COLORS3.length], label: d.label };
455
+ });
456
+ return /* @__PURE__ */ jsxs5("div", { className: `chart ${className ?? ""}`, children: [
457
+ title && /* @__PURE__ */ jsx5("p", { className: "chart__title", children: title }),
458
+ /* @__PURE__ */ jsx5("svg", { className: "chart__svg", width, height, children: slices.map((s, i) => /* @__PURE__ */ jsx5("path", { className: "chart__slice", d: s.path, fill: s.color }, i)) }),
459
+ showLegend && /* @__PURE__ */ jsx5("div", { className: "chart__legend", children: data.map((d, i) => /* @__PURE__ */ jsxs5("span", { className: "chart__legend-item", children: [
460
+ /* @__PURE__ */ jsx5("span", { className: "chart__legend-dot", style: { background: d.color ?? COLORS3[i % COLORS3.length] } }),
461
+ d.label,
462
+ ": ",
463
+ d.value
464
+ ] }, i)) })
465
+ ] });
466
+ };
467
+
468
+ // src/Charts/AreaChart.tsx
469
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
470
+ var COLORS4 = ["#3b82f6", "#22c55e", "#f59e0b", "#ef4444", "#8b5cf6", "#06b6d4", "#ec4899", "#f97316"];
471
+ var AreaChart = ({
472
+ data,
473
+ width = 400,
474
+ height = 300,
475
+ showLegend = false,
476
+ title,
477
+ className
478
+ }) => {
479
+ if (!data.length) {
480
+ return /* @__PURE__ */ jsx6("div", { className: `chart__empty ${className ?? ""}`, style: { width, height }, children: "No data" });
481
+ }
482
+ const pad = { top: 20, right: 20, bottom: 40, left: 50 };
483
+ const w = width - pad.left - pad.right;
484
+ const h = height - pad.top - pad.bottom;
485
+ const max = Math.max(...data.map((d) => d.value));
486
+ const step = w / Math.max(data.length - 1, 1);
487
+ const points = data.map((d, i) => ({
488
+ x: pad.left + i * step,
489
+ y: pad.top + h - d.value / (max || 1) * h
490
+ }));
491
+ const lineD = points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" ");
492
+ const areaD = `${lineD} L${points[points.length - 1].x},${pad.top + h} L${points[0].x},${pad.top + h} Z`;
493
+ return /* @__PURE__ */ jsxs6("div", { className: `chart ${className ?? ""}`, children: [
494
+ title && /* @__PURE__ */ jsx6("p", { className: "chart__title", children: title }),
495
+ /* @__PURE__ */ jsxs6("svg", { className: "chart__svg", width, height, children: [
496
+ [0, 0.25, 0.5, 0.75, 1].map((t) => /* @__PURE__ */ jsx6(
497
+ "line",
498
+ {
499
+ className: "chart__grid-line",
500
+ x1: pad.left,
501
+ x2: width - pad.right,
502
+ y1: pad.top + h * (1 - t),
503
+ y2: pad.top + h * (1 - t)
504
+ },
505
+ t
506
+ )),
507
+ /* @__PURE__ */ jsx6("path", { className: "chart__area", d: areaD, fill: COLORS4[0] }),
508
+ /* @__PURE__ */ jsx6("path", { className: "chart__line", d: lineD, stroke: COLORS4[0] }),
509
+ points.map((p, i) => /* @__PURE__ */ jsx6(
510
+ "circle",
511
+ {
512
+ className: "chart__dot",
513
+ cx: p.x,
514
+ cy: p.y,
515
+ r: 4,
516
+ fill: data[i].color ?? COLORS4[0]
517
+ },
518
+ i
519
+ )),
520
+ data.map((d, i) => /* @__PURE__ */ jsx6("text", { className: "chart__axis-label", x: points[i].x, y: height - 8, textAnchor: "middle", children: d.label }, i))
521
+ ] }),
522
+ showLegend && /* @__PURE__ */ jsx6("div", { className: "chart__legend", children: data.map((d, i) => /* @__PURE__ */ jsxs6("span", { className: "chart__legend-item", children: [
523
+ /* @__PURE__ */ jsx6("span", { className: "chart__legend-dot", style: { background: d.color ?? COLORS4[i % COLORS4.length] } }),
524
+ d.label
525
+ ] }, i)) })
526
+ ] });
527
+ };
528
+
529
+ // src/Charts/DonutChart.tsx
530
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
531
+ var COLORS5 = ["#3b82f6", "#22c55e", "#f59e0b", "#ef4444", "#8b5cf6", "#06b6d4", "#ec4899", "#f97316"];
532
+ function polarToCartesian2(cx, cy, r, angle) {
533
+ const rad = (angle - 90) * Math.PI / 180;
534
+ return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) };
535
+ }
536
+ function donutArc(cx, cy, outer, inner, start, end) {
537
+ const oStart = polarToCartesian2(cx, cy, outer, end);
538
+ const oEnd = polarToCartesian2(cx, cy, outer, start);
539
+ const iStart = polarToCartesian2(cx, cy, inner, start);
540
+ const iEnd = polarToCartesian2(cx, cy, inner, end);
541
+ const large = end - start > 180 ? 1 : 0;
542
+ return [
543
+ `M ${oStart.x} ${oStart.y}`,
544
+ `A ${outer} ${outer} 0 ${large} 0 ${oEnd.x} ${oEnd.y}`,
545
+ `L ${iStart.x} ${iStart.y}`,
546
+ `A ${inner} ${inner} 0 ${large} 1 ${iEnd.x} ${iEnd.y}`,
547
+ "Z"
548
+ ].join(" ");
549
+ }
550
+ var DonutChart = ({
551
+ data,
552
+ width = 300,
553
+ height = 300,
554
+ showLegend = true,
555
+ title,
556
+ className,
557
+ innerRatio = 0.6,
558
+ centerLabel
559
+ }) => {
560
+ if (!data.length) {
561
+ return /* @__PURE__ */ jsx7("div", { className: `chart__empty ${className ?? ""}`, style: { width, height }, children: "No data" });
562
+ }
563
+ const total = data.reduce((sum, d) => sum + d.value, 0);
564
+ const cx = width / 2;
565
+ const cy = height / 2;
566
+ const outer = Math.min(cx, cy) - 10;
567
+ const inner = outer * innerRatio;
568
+ let startAngle = 0;
569
+ const slices = data.map((d, i) => {
570
+ const sweep = total > 0 ? d.value / total * 360 : 0;
571
+ const path = donutArc(cx, cy, outer, inner, startAngle, startAngle + sweep);
572
+ startAngle += sweep;
573
+ return { path, color: d.color ?? COLORS5[i % COLORS5.length] };
574
+ });
575
+ return /* @__PURE__ */ jsxs7("div", { className: `chart ${className ?? ""}`, children: [
576
+ title && /* @__PURE__ */ jsx7("p", { className: "chart__title", children: title }),
577
+ /* @__PURE__ */ jsxs7("svg", { className: "chart__svg", width, height, children: [
578
+ slices.map((s, i) => /* @__PURE__ */ jsx7("path", { className: "chart__slice", d: s.path, fill: s.color }, i)),
579
+ centerLabel && /* @__PURE__ */ jsx7("text", { className: "chart__donut-label", x: cx, y: cy, children: centerLabel })
580
+ ] }),
581
+ showLegend && /* @__PURE__ */ jsx7("div", { className: "chart__legend", children: data.map((d, i) => /* @__PURE__ */ jsxs7("span", { className: "chart__legend-item", children: [
582
+ /* @__PURE__ */ jsx7("span", { className: "chart__legend-dot", style: { background: d.color ?? COLORS5[i % COLORS5.length] } }),
583
+ d.label,
584
+ ": ",
585
+ d.value
586
+ ] }, i)) })
587
+ ] });
588
+ };
589
+ export {
590
+ AreaChart,
591
+ BarChart,
592
+ DataTable,
593
+ DonutChart,
594
+ LineChart,
595
+ PieChart,
596
+ Widget,
597
+ saveBlob,
598
+ saveFile,
599
+ uuidFilename
600
+ };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@orchestra-mcp/widgets",
3
+ "version": "0.2.0",
4
+ "description": "Utility widget components for Orchestra MCP",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": ["dist"],
17
+ "publishConfig": { "access": "public" },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/orchestra-mcp/widgets"
21
+ },
22
+ "funding": "https://github.com/sponsors/fadymondy",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "@orchestra-mcp/icons": "^0.2.0",
26
+ "html-to-image": "^1.11.13"
27
+ },
28
+ "peerDependencies": {
29
+ "react": ">=18.0.0",
30
+ "react-dom": ">=18.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/react": "^19.0.0",
34
+ "@types/react-dom": "^19.0.0",
35
+ "tsup": "^8.3.5",
36
+ "typescript": "^5.7.2"
37
+ },
38
+ "scripts": {
39
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
40
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
41
+ "prepublishOnly": "pnpm build"
42
+ },
43
+ "keywords": ["orchestra", "mcp", "react", "components", "widgets"]
44
+ }