@bwlng/blocks 0.1.0-fork.2e294b1

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,971 @@
1
+ import { n as blocks, r as elements, t as validateBlocks } from "./validation-DAttVLF0.js";
2
+ import { Badge, Banner, Button, Checkbox, CodeBlock, Combobox, Dialog, DialogRoot, Input, InputArea, Meter, Radio, Select, SensitiveInput, Switch } from "@cloudflare/kumo";
3
+ import { useCallback, useEffect, useMemo, useState } from "react";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { ArrowDown, ArrowUp, Info, Minus, Warning, WarningCircle } from "@phosphor-icons/react";
6
+ import { Chart, ChartPalette, TimeseriesChart } from "@cloudflare/kumo/components/chart";
7
+ import { BarChart, LineChart, PieChart } from "echarts/charts";
8
+ import { AriaComponent, AxisPointerComponent, GridComponent, TooltipComponent } from "echarts/components";
9
+ import * as echarts from "echarts/core";
10
+ import { CanvasRenderer } from "echarts/renderers";
11
+ import { clsx } from "clsx";
12
+ import { twMerge } from "tailwind-merge";
13
+
14
+ //#region src/elements/button.tsx
15
+ function ButtonElementComponent({ element, onAction }) {
16
+ const [confirmOpen, setConfirmOpen] = useState(false);
17
+ const fireAction = useCallback(() => {
18
+ onAction({
19
+ type: "block_action",
20
+ action_id: element.action_id,
21
+ value: element.value
22
+ });
23
+ }, [
24
+ onAction,
25
+ element.action_id,
26
+ element.value
27
+ ]);
28
+ const handleClick = useCallback(() => {
29
+ if (element.confirm) setConfirmOpen(true);
30
+ else fireAction();
31
+ }, [element.confirm, fireAction]);
32
+ const handleConfirm = useCallback(() => {
33
+ setConfirmOpen(false);
34
+ fireAction();
35
+ }, [fireAction]);
36
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
37
+ variant: element.style === "primary" ? "primary" : element.style === "danger" ? "destructive" : "secondary",
38
+ onClick: handleClick,
39
+ children: element.label
40
+ }), element.confirm && /* @__PURE__ */ jsx(DialogRoot, {
41
+ open: confirmOpen,
42
+ onOpenChange: setConfirmOpen,
43
+ children: /* @__PURE__ */ jsxs(Dialog, { children: [
44
+ /* @__PURE__ */ jsx("h3", {
45
+ className: "text-lg font-semibold text-kumo-default",
46
+ children: element.confirm.title
47
+ }),
48
+ /* @__PURE__ */ jsx("p", {
49
+ className: "mt-1 text-sm text-kumo-subtle",
50
+ children: element.confirm.text
51
+ }),
52
+ /* @__PURE__ */ jsxs("div", {
53
+ className: "flex justify-end gap-2 pt-4",
54
+ children: [/* @__PURE__ */ jsx(Button, {
55
+ variant: "secondary",
56
+ onClick: () => setConfirmOpen(false),
57
+ children: element.confirm.deny
58
+ }), /* @__PURE__ */ jsx(Button, {
59
+ variant: element.confirm.style === "danger" ? "destructive" : "primary",
60
+ onClick: handleConfirm,
61
+ children: element.confirm.confirm
62
+ })]
63
+ })
64
+ ] })
65
+ })] });
66
+ }
67
+
68
+ //#endregion
69
+ //#region src/elements/checkbox.tsx
70
+ function CheckboxElementComponent({ element, onAction, onChange }) {
71
+ const [values, setValues] = useState(element.initial_value ?? []);
72
+ useEffect(() => {
73
+ setValues(element.initial_value ?? []);
74
+ }, [element.initial_value]);
75
+ const handleChange = useCallback((newValues) => {
76
+ setValues(newValues);
77
+ if (onChange) onChange(element.action_id, newValues);
78
+ else onAction({
79
+ type: "block_action",
80
+ action_id: element.action_id,
81
+ value: newValues
82
+ });
83
+ }, [
84
+ onChange,
85
+ onAction,
86
+ element.action_id
87
+ ]);
88
+ return /* @__PURE__ */ jsx(Checkbox.Group, {
89
+ legend: element.label,
90
+ value: values,
91
+ onValueChange: handleChange,
92
+ children: element.options.map((opt) => /* @__PURE__ */ jsx(Checkbox.Item, {
93
+ value: opt.value,
94
+ label: opt.label
95
+ }, opt.value))
96
+ });
97
+ }
98
+
99
+ //#endregion
100
+ //#region src/elements/combobox.tsx
101
+ function ComboboxElementComponent({ element, onAction, onChange }) {
102
+ const initialOption = useMemo(() => element.options.find((o) => o.value === element.initial_value) ?? null, [element.options, element.initial_value]);
103
+ const [selected, setSelected] = useState(initialOption);
104
+ useEffect(() => {
105
+ setSelected(initialOption);
106
+ }, [initialOption]);
107
+ const handleChange = useCallback((newValue) => {
108
+ const opt = newValue;
109
+ setSelected(opt);
110
+ const val = opt?.value ?? null;
111
+ if (onChange) onChange(element.action_id, val);
112
+ else onAction({
113
+ type: "block_action",
114
+ action_id: element.action_id,
115
+ value: val
116
+ });
117
+ }, [
118
+ onChange,
119
+ onAction,
120
+ element.action_id
121
+ ]);
122
+ return /* @__PURE__ */ jsxs(Combobox, {
123
+ label: element.label,
124
+ items: element.options,
125
+ value: selected,
126
+ onValueChange: handleChange,
127
+ children: [/* @__PURE__ */ jsx(Combobox.TriggerInput, { placeholder: element.placeholder ?? "Search..." }), /* @__PURE__ */ jsxs(Combobox.Content, { children: [/* @__PURE__ */ jsx(Combobox.List, { children: (item) => /* @__PURE__ */ jsx(Combobox.Item, {
128
+ value: item,
129
+ children: item.label
130
+ }) }), /* @__PURE__ */ jsx(Combobox.Empty, { children: "No results" })] })]
131
+ });
132
+ }
133
+
134
+ //#endregion
135
+ //#region src/elements/date-input.tsx
136
+ function DateInputElementComponent({ element, onAction, onChange }) {
137
+ const [value, setValue] = useState(element.initial_value ?? "");
138
+ useEffect(() => {
139
+ setValue(element.initial_value ?? "");
140
+ }, [element.initial_value]);
141
+ const handleChange = useCallback((e) => {
142
+ const newValue = e.target.value;
143
+ setValue(newValue);
144
+ if (onChange) onChange(element.action_id, newValue);
145
+ }, [onChange, element.action_id]);
146
+ const handleBlur = useCallback((e) => {
147
+ if (!onChange) onAction({
148
+ type: "block_action",
149
+ action_id: element.action_id,
150
+ value: e.target.value
151
+ });
152
+ }, [
153
+ onChange,
154
+ onAction,
155
+ element.action_id
156
+ ]);
157
+ return /* @__PURE__ */ jsxs("div", {
158
+ className: "flex flex-col gap-1",
159
+ children: [/* @__PURE__ */ jsx("label", {
160
+ className: "text-sm font-medium text-kumo-text",
161
+ children: element.label
162
+ }), /* @__PURE__ */ jsx("input", {
163
+ type: "date",
164
+ value,
165
+ onChange: handleChange,
166
+ onBlur: handleBlur,
167
+ placeholder: element.placeholder,
168
+ className: "h-9 rounded-lg border border-kumo-line bg-kumo-bg px-3 text-sm text-kumo-text outline-none focus:ring-2 focus:ring-kumo-ring"
169
+ })]
170
+ });
171
+ }
172
+
173
+ //#endregion
174
+ //#region src/elements/number-input.tsx
175
+ function NumberInputElementComponent({ element, onAction, onChange }) {
176
+ const handleChange = useCallback((e) => {
177
+ const val = e.target.value === "" ? void 0 : Number(e.target.value);
178
+ if (onChange) onChange(element.action_id, val);
179
+ }, [onChange, element.action_id]);
180
+ const handleBlur = useCallback((e) => {
181
+ if (!onChange) {
182
+ const val = e.target.value === "" ? void 0 : Number(e.target.value);
183
+ onAction({
184
+ type: "block_action",
185
+ action_id: element.action_id,
186
+ value: val
187
+ });
188
+ }
189
+ }, [
190
+ onChange,
191
+ onAction,
192
+ element.action_id
193
+ ]);
194
+ return /* @__PURE__ */ jsx(Input, {
195
+ label: element.label,
196
+ type: "number",
197
+ min: element.min,
198
+ max: element.max,
199
+ defaultValue: element.initial_value,
200
+ onChange: handleChange,
201
+ onBlur: handleBlur
202
+ });
203
+ }
204
+
205
+ //#endregion
206
+ //#region src/elements/radio.tsx
207
+ function RadioElementComponent({ element, onAction, onChange }) {
208
+ const [value, setValue] = useState(element.initial_value ?? "");
209
+ useEffect(() => {
210
+ setValue(element.initial_value ?? "");
211
+ }, [element.initial_value]);
212
+ const handleChange = useCallback((newValue) => {
213
+ setValue(newValue);
214
+ if (onChange) onChange(element.action_id, newValue);
215
+ else onAction({
216
+ type: "block_action",
217
+ action_id: element.action_id,
218
+ value: newValue
219
+ });
220
+ }, [
221
+ onChange,
222
+ onAction,
223
+ element.action_id
224
+ ]);
225
+ return /* @__PURE__ */ jsx(Radio.Group, {
226
+ legend: element.label,
227
+ value,
228
+ onValueChange: handleChange,
229
+ children: element.options.map((opt) => /* @__PURE__ */ jsx(Radio.Item, {
230
+ value: opt.value,
231
+ label: opt.label
232
+ }, opt.value))
233
+ });
234
+ }
235
+
236
+ //#endregion
237
+ //#region src/elements/secret-input.tsx
238
+ function SecretInputElementComponent({ element, onAction, onChange }) {
239
+ const [value, setValue] = useState("");
240
+ const [editing, setEditing] = useState(!element.has_value);
241
+ const handleValueChange = useCallback((v) => {
242
+ setValue(v);
243
+ if (onChange) onChange(element.action_id, v);
244
+ }, [onChange, element.action_id]);
245
+ const handleFocus = useCallback(() => {
246
+ if (!editing) {
247
+ setEditing(true);
248
+ setValue("");
249
+ }
250
+ }, [editing]);
251
+ const handleBlur = useCallback(() => {
252
+ if (!onChange && value) onAction({
253
+ type: "block_action",
254
+ action_id: element.action_id,
255
+ value
256
+ });
257
+ if (!value && element.has_value) setEditing(false);
258
+ }, [
259
+ onChange,
260
+ onAction,
261
+ element.action_id,
262
+ value,
263
+ element.has_value
264
+ ]);
265
+ if (!editing) return /* @__PURE__ */ jsx(SensitiveInput, {
266
+ label: element.label,
267
+ value: "••••••••",
268
+ readOnly: true,
269
+ onFocus: handleFocus,
270
+ placeholder: element.placeholder
271
+ });
272
+ return /* @__PURE__ */ jsx(SensitiveInput, {
273
+ label: element.label,
274
+ value,
275
+ onValueChange: handleValueChange,
276
+ onFocus: handleFocus,
277
+ onBlur: handleBlur,
278
+ placeholder: element.placeholder
279
+ });
280
+ }
281
+
282
+ //#endregion
283
+ //#region src/elements/select.tsx
284
+ function SelectElementComponent({ element, onAction, onChange }) {
285
+ const handleValueChange = useCallback((value) => {
286
+ if (onChange) onChange(element.action_id, value);
287
+ else onAction({
288
+ type: "block_action",
289
+ action_id: element.action_id,
290
+ value
291
+ });
292
+ }, [
293
+ onChange,
294
+ onAction,
295
+ element.action_id
296
+ ]);
297
+ return /* @__PURE__ */ jsx(Select, {
298
+ label: element.label,
299
+ defaultValue: element.initial_value,
300
+ onValueChange: handleValueChange,
301
+ children: element.options.map((opt) => /* @__PURE__ */ jsx(Select.Option, {
302
+ value: opt.value,
303
+ children: opt.label
304
+ }, opt.value))
305
+ });
306
+ }
307
+
308
+ //#endregion
309
+ //#region src/elements/text-input.tsx
310
+ function TextInputElementComponent({ element, onAction, onChange }) {
311
+ const handleChange = useCallback((e) => {
312
+ if (onChange) onChange(element.action_id, e.target.value);
313
+ }, [onChange, element.action_id]);
314
+ const handleBlur = useCallback((e) => {
315
+ if (!onChange) onAction({
316
+ type: "block_action",
317
+ action_id: element.action_id,
318
+ value: e.target.value
319
+ });
320
+ }, [
321
+ onChange,
322
+ onAction,
323
+ element.action_id
324
+ ]);
325
+ if (element.multiline) return /* @__PURE__ */ jsx(InputArea, {
326
+ label: element.label,
327
+ placeholder: element.placeholder,
328
+ defaultValue: element.initial_value,
329
+ onChange: handleChange,
330
+ onBlur: handleBlur
331
+ });
332
+ return /* @__PURE__ */ jsx(Input, {
333
+ label: element.label,
334
+ placeholder: element.placeholder,
335
+ defaultValue: element.initial_value,
336
+ onChange: handleChange,
337
+ onBlur: handleBlur
338
+ });
339
+ }
340
+
341
+ //#endregion
342
+ //#region src/elements/toggle.tsx
343
+ function ToggleElementComponent({ element, onAction, onChange }) {
344
+ const [checked, setChecked] = useState(element.initial_value ?? false);
345
+ const handleChange = useCallback((value) => {
346
+ setChecked(value);
347
+ if (onChange) onChange(element.action_id, value);
348
+ else onAction({
349
+ type: "block_action",
350
+ action_id: element.action_id,
351
+ value
352
+ });
353
+ }, [
354
+ onChange,
355
+ onAction,
356
+ element.action_id
357
+ ]);
358
+ return /* @__PURE__ */ jsx(Switch, {
359
+ label: element.label,
360
+ checked,
361
+ onCheckedChange: handleChange
362
+ });
363
+ }
364
+
365
+ //#endregion
366
+ //#region src/render-element.tsx
367
+ function renderElement(element, onAction, onChange) {
368
+ switch (element.type) {
369
+ case "button": return /* @__PURE__ */ jsx(ButtonElementComponent, {
370
+ element,
371
+ onAction
372
+ });
373
+ case "text_input": return /* @__PURE__ */ jsx(TextInputElementComponent, {
374
+ element,
375
+ onAction,
376
+ onChange
377
+ });
378
+ case "number_input": return /* @__PURE__ */ jsx(NumberInputElementComponent, {
379
+ element,
380
+ onAction,
381
+ onChange
382
+ });
383
+ case "select": return /* @__PURE__ */ jsx(SelectElementComponent, {
384
+ element,
385
+ onAction,
386
+ onChange
387
+ });
388
+ case "toggle": return /* @__PURE__ */ jsx(ToggleElementComponent, {
389
+ element,
390
+ onAction,
391
+ onChange
392
+ });
393
+ case "secret_input": return /* @__PURE__ */ jsx(SecretInputElementComponent, {
394
+ element,
395
+ onAction,
396
+ onChange
397
+ });
398
+ case "checkbox": return /* @__PURE__ */ jsx(CheckboxElementComponent, {
399
+ element,
400
+ onAction,
401
+ onChange
402
+ });
403
+ case "radio": return /* @__PURE__ */ jsx(RadioElementComponent, {
404
+ element,
405
+ onAction,
406
+ onChange
407
+ });
408
+ case "date_input": return /* @__PURE__ */ jsx(DateInputElementComponent, {
409
+ element,
410
+ onAction,
411
+ onChange
412
+ });
413
+ case "combobox": return /* @__PURE__ */ jsx(ComboboxElementComponent, {
414
+ element,
415
+ onAction,
416
+ onChange
417
+ });
418
+ default: return null;
419
+ }
420
+ }
421
+
422
+ //#endregion
423
+ //#region src/blocks/actions.tsx
424
+ function ActionsBlockComponent({ block, onAction }) {
425
+ return /* @__PURE__ */ jsx("div", {
426
+ className: "flex flex-wrap gap-2",
427
+ children: block.elements.map((el, i) => /* @__PURE__ */ jsx("div", { children: renderElement(el, onAction) }, el.action_id ?? i))
428
+ });
429
+ }
430
+
431
+ //#endregion
432
+ //#region src/blocks/banner.tsx
433
+ function useVariantIcon(variant) {
434
+ return useMemo(() => {
435
+ switch (variant) {
436
+ case "alert": return /* @__PURE__ */ jsx(Warning, {
437
+ weight: "fill",
438
+ size: 20
439
+ });
440
+ case "error": return /* @__PURE__ */ jsx(WarningCircle, {
441
+ weight: "fill",
442
+ size: 20
443
+ });
444
+ default: return /* @__PURE__ */ jsx(Info, {
445
+ weight: "fill",
446
+ size: 20
447
+ });
448
+ }
449
+ }, [variant]);
450
+ }
451
+ function BannerBlockComponent({ block }) {
452
+ const variant = block.variant ?? "default";
453
+ return /* @__PURE__ */ jsx(Banner, {
454
+ variant,
455
+ icon: useVariantIcon(variant),
456
+ title: block.title,
457
+ description: block.description
458
+ });
459
+ }
460
+
461
+ //#endregion
462
+ //#region src/utils.ts
463
+ function cn(...inputs) {
464
+ return twMerge(clsx(inputs));
465
+ }
466
+ /**
467
+ * Detects dark mode from `<html data-theme="dark">` or the system
468
+ * `prefers-color-scheme` media query and stays in sync reactively.
469
+ */
470
+ function useIsDarkMode() {
471
+ const [dark, setDark] = useState(() => {
472
+ if (typeof document === "undefined") return false;
473
+ const attr = document.documentElement.getAttribute("data-theme");
474
+ if (attr === "dark") return true;
475
+ if (attr === "light") return false;
476
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
477
+ });
478
+ useEffect(() => {
479
+ const observer = new MutationObserver(() => {
480
+ const attr = document.documentElement.getAttribute("data-theme");
481
+ if (attr === "dark") return setDark(true);
482
+ if (attr === "light") return setDark(false);
483
+ setDark(window.matchMedia("(prefers-color-scheme: dark)").matches);
484
+ });
485
+ observer.observe(document.documentElement, {
486
+ attributes: true,
487
+ attributeFilter: ["data-theme"]
488
+ });
489
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
490
+ const handler = (e) => {
491
+ if (!document.documentElement.hasAttribute("data-theme")) setDark(e.matches);
492
+ };
493
+ mq.addEventListener("change", handler);
494
+ return () => {
495
+ observer.disconnect();
496
+ mq.removeEventListener("change", handler);
497
+ };
498
+ }, []);
499
+ return dark;
500
+ }
501
+ const MINUTE = 60;
502
+ const HOUR = 60 * MINUTE;
503
+ const DAY = 24 * HOUR;
504
+ const WEEK = 7 * DAY;
505
+ const MONTH = 30 * DAY;
506
+ const YEAR = 365 * DAY;
507
+ function formatRelativeTime(iso) {
508
+ const date = new Date(iso);
509
+ const now = Date.now();
510
+ const diff = Math.floor((now - date.getTime()) / 1e3);
511
+ if (diff < 0) return "just now";
512
+ if (diff < MINUTE) return "just now";
513
+ if (diff < HOUR) {
514
+ const mins = Math.floor(diff / MINUTE);
515
+ return mins === 1 ? "1 minute ago" : `${mins} minutes ago`;
516
+ }
517
+ if (diff < DAY) {
518
+ const hours = Math.floor(diff / HOUR);
519
+ return hours === 1 ? "1 hour ago" : `${hours} hours ago`;
520
+ }
521
+ if (diff < WEEK) {
522
+ const days = Math.floor(diff / DAY);
523
+ return days === 1 ? "1 day ago" : `${days} days ago`;
524
+ }
525
+ if (diff < MONTH) {
526
+ const weeks = Math.floor(diff / WEEK);
527
+ return weeks === 1 ? "1 week ago" : `${weeks} weeks ago`;
528
+ }
529
+ if (diff < YEAR) {
530
+ const months = Math.floor(diff / MONTH);
531
+ return months === 1 ? "1 month ago" : `${months} months ago`;
532
+ }
533
+ const years = Math.floor(diff / YEAR);
534
+ return years === 1 ? "1 year ago" : `${years} years ago`;
535
+ }
536
+
537
+ //#endregion
538
+ //#region src/blocks/chart.tsx
539
+ echarts.use([
540
+ BarChart,
541
+ LineChart,
542
+ PieChart,
543
+ AriaComponent,
544
+ AxisPointerComponent,
545
+ GridComponent,
546
+ TooltipComponent,
547
+ CanvasRenderer
548
+ ]);
549
+ const RE_AMP = /&/g;
550
+ const RE_LT = /</g;
551
+ const RE_GT = />/g;
552
+ const RE_QUOT = /"/g;
553
+ const RE_APOS = /'/g;
554
+ function escapeHtml(str) {
555
+ return str.replace(RE_AMP, "&amp;").replace(RE_LT, "&lt;").replace(RE_GT, "&gt;").replace(RE_QUOT, "&quot;").replace(RE_APOS, "&#039;");
556
+ }
557
+ /** Keys that accept HTML strings or executable content in ECharts options */
558
+ const DANGEROUS_KEYS = new Set([
559
+ "formatter",
560
+ "rich",
561
+ "graphic",
562
+ "axisPointer"
563
+ ]);
564
+ function isRecord(v) {
565
+ return typeof v === "object" && v !== null && !Array.isArray(v);
566
+ }
567
+ const RE_HTML_TAG = /<[a-z/!]/i;
568
+ function containsHtml(v) {
569
+ return typeof v === "string" && RE_HTML_TAG.test(v);
570
+ }
571
+ /**
572
+ * Deep-clone an ECharts options object, stripping properties that could
573
+ * inject HTML or executable content. Strings containing HTML tags are
574
+ * replaced with escaped versions.
575
+ */
576
+ function sanitizeOptions(obj) {
577
+ const result = {};
578
+ for (const [key, value] of Object.entries(obj)) {
579
+ if (DANGEROUS_KEYS.has(key)) continue;
580
+ if (containsHtml(value)) result[key] = escapeHtml(value);
581
+ else if (Array.isArray(value)) result[key] = value.map((item) => isRecord(item) ? sanitizeOptions(item) : containsHtml(item) ? escapeHtml(item) : item);
582
+ else if (isRecord(value)) result[key] = sanitizeOptions(value);
583
+ else result[key] = value;
584
+ }
585
+ return result;
586
+ }
587
+ function TimeseriesChartBlock({ block, isDarkMode }) {
588
+ const config = block.config;
589
+ if (config.chart_type !== "timeseries") return null;
590
+ const data = useMemo(() => config.series.map((s, i) => ({
591
+ name: escapeHtml(s.name),
592
+ data: s.data,
593
+ color: s.color ?? ChartPalette.color(i, isDarkMode)
594
+ })), [config.series, isDarkMode]);
595
+ return /* @__PURE__ */ jsx(TimeseriesChart, {
596
+ echarts,
597
+ isDarkMode,
598
+ type: config.style,
599
+ data,
600
+ xAxisName: config.x_axis_name ? escapeHtml(config.x_axis_name) : void 0,
601
+ yAxisName: config.y_axis_name ? escapeHtml(config.y_axis_name) : void 0,
602
+ height: config.height,
603
+ gradient: config.gradient
604
+ });
605
+ }
606
+ function CustomChartBlock({ block, isDarkMode }) {
607
+ const config = block.config;
608
+ if (config.chart_type !== "custom") return null;
609
+ return /* @__PURE__ */ jsx(Chart, {
610
+ echarts,
611
+ isDarkMode,
612
+ options: useMemo(() => {
613
+ const sanitized = sanitizeOptions(config.options);
614
+ if (isRecord(sanitized.tooltip)) sanitized.tooltip.renderMode = "richText";
615
+ else sanitized.tooltip = { renderMode: "richText" };
616
+ return sanitized;
617
+ }, [config.options]),
618
+ height: config.height
619
+ });
620
+ }
621
+ function ChartBlockComponent({ block }) {
622
+ const isDarkMode = useIsDarkMode();
623
+ return /* @__PURE__ */ jsx("div", {
624
+ className: "rounded-lg border border-kumo-line p-4",
625
+ children: block.config.chart_type === "timeseries" ? /* @__PURE__ */ jsx(TimeseriesChartBlock, {
626
+ block,
627
+ isDarkMode
628
+ }) : /* @__PURE__ */ jsx(CustomChartBlock, {
629
+ block,
630
+ isDarkMode
631
+ })
632
+ });
633
+ }
634
+
635
+ //#endregion
636
+ //#region src/blocks/code.tsx
637
+ function CodeBlockComponent({ block }) {
638
+ return /* @__PURE__ */ jsx(CodeBlock, {
639
+ code: block.code,
640
+ lang: block.language
641
+ });
642
+ }
643
+
644
+ //#endregion
645
+ //#region src/blocks/columns.tsx
646
+ function ColumnsBlockComponent({ block, onAction }) {
647
+ return /* @__PURE__ */ jsx("div", {
648
+ className: Math.min(block.columns.length, 3) === 2 ? "grid grid-cols-2 gap-4" : "grid grid-cols-3 gap-4",
649
+ children: block.columns.map((col, i) => /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(BlockRenderer, {
650
+ blocks: col,
651
+ onAction
652
+ }) }, i))
653
+ });
654
+ }
655
+
656
+ //#endregion
657
+ //#region src/blocks/context.tsx
658
+ function ContextBlockComponent({ block }) {
659
+ return /* @__PURE__ */ jsx("p", {
660
+ className: "text-sm text-kumo-subtle",
661
+ children: block.text
662
+ });
663
+ }
664
+
665
+ //#endregion
666
+ //#region src/blocks/divider.tsx
667
+ function DividerBlockComponent() {
668
+ return /* @__PURE__ */ jsx("hr", { className: "my-4 border-kumo-line" });
669
+ }
670
+
671
+ //#endregion
672
+ //#region src/blocks/fields.tsx
673
+ function FieldsBlockComponent({ block }) {
674
+ return /* @__PURE__ */ jsx("div", {
675
+ className: "grid grid-cols-2 gap-x-6 gap-y-3",
676
+ children: block.fields.map((field, i) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
677
+ className: "text-sm text-kumo-subtle",
678
+ children: field.label
679
+ }), /* @__PURE__ */ jsx("div", {
680
+ className: "text-kumo-default",
681
+ children: field.value
682
+ })] }, i))
683
+ });
684
+ }
685
+
686
+ //#endregion
687
+ //#region src/blocks/form.tsx
688
+ function deepEqual(a, b) {
689
+ if (a === b) return true;
690
+ if (Array.isArray(a) && Array.isArray(b)) {
691
+ if (a.length !== b.length) return false;
692
+ return a.every((v, i) => deepEqual(v, b[i]));
693
+ }
694
+ return false;
695
+ }
696
+ function evaluateCondition(condition, values) {
697
+ const fieldValue = values[condition.field];
698
+ if ("eq" in condition && condition.eq !== void 0) return deepEqual(fieldValue, condition.eq);
699
+ if ("neq" in condition && condition.neq !== void 0) return !deepEqual(fieldValue, condition.neq);
700
+ return true;
701
+ }
702
+ function getInitialValues(fields) {
703
+ const values = {};
704
+ for (const field of fields) if ("initial_value" in field && field.initial_value !== void 0) values[field.action_id] = field.initial_value;
705
+ return values;
706
+ }
707
+ function FormBlockComponent({ block, onAction }) {
708
+ const [values, setValues] = useState(() => getInitialValues(block.fields));
709
+ const handleChange = useCallback((actionId, value) => {
710
+ setValues((prev) => ({
711
+ ...prev,
712
+ [actionId]: value
713
+ }));
714
+ }, []);
715
+ function handleSubmit(e) {
716
+ e.preventDefault();
717
+ onAction({
718
+ type: "form_submit",
719
+ action_id: block.submit.action_id,
720
+ block_id: block.block_id,
721
+ values
722
+ });
723
+ }
724
+ return /* @__PURE__ */ jsxs("form", {
725
+ onSubmit: handleSubmit,
726
+ className: "flex flex-col gap-4",
727
+ children: [block.fields.map((field) => {
728
+ if (field.condition && !evaluateCondition(field.condition, values)) return null;
729
+ return /* @__PURE__ */ jsx("div", { children: renderElement(field, onAction, handleChange) }, field.action_id);
730
+ }), /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, {
731
+ type: "submit",
732
+ children: block.submit.label
733
+ }) })]
734
+ });
735
+ }
736
+
737
+ //#endregion
738
+ //#region src/blocks/header.tsx
739
+ function HeaderBlockComponent({ block }) {
740
+ return /* @__PURE__ */ jsx("h2", {
741
+ className: "text-xl font-bold text-kumo-default",
742
+ children: block.text
743
+ });
744
+ }
745
+
746
+ //#endregion
747
+ //#region src/blocks/image.tsx
748
+ function ImageBlockComponent({ block }) {
749
+ return /* @__PURE__ */ jsxs("figure", { children: [/* @__PURE__ */ jsx("img", {
750
+ src: block.url,
751
+ alt: block.alt,
752
+ className: "max-w-full rounded"
753
+ }), block.title && /* @__PURE__ */ jsx("figcaption", {
754
+ className: "mt-1 text-sm text-kumo-subtle",
755
+ children: block.title
756
+ })] });
757
+ }
758
+
759
+ //#endregion
760
+ //#region src/blocks/meter.tsx
761
+ function MeterBlockComponent({ block }) {
762
+ return /* @__PURE__ */ jsx(Meter, {
763
+ label: block.label,
764
+ value: block.value,
765
+ max: block.max,
766
+ min: block.min,
767
+ customValue: block.custom_value
768
+ });
769
+ }
770
+
771
+ //#endregion
772
+ //#region src/blocks/section.tsx
773
+ function SectionBlockComponent({ block, onAction }) {
774
+ return /* @__PURE__ */ jsxs("div", {
775
+ className: "flex items-start justify-between gap-4",
776
+ children: [/* @__PURE__ */ jsx("div", {
777
+ className: "flex-1 text-kumo-default",
778
+ children: block.text
779
+ }), block.accessory && /* @__PURE__ */ jsx("div", {
780
+ className: "flex-shrink-0",
781
+ children: renderElement(block.accessory, onAction)
782
+ })]
783
+ });
784
+ }
785
+
786
+ //#endregion
787
+ //#region src/blocks/stats.tsx
788
+ const trendConfig = {
789
+ up: {
790
+ icon: ArrowUp,
791
+ color: "text-green-600"
792
+ },
793
+ down: {
794
+ icon: ArrowDown,
795
+ color: "text-red-600"
796
+ },
797
+ neutral: {
798
+ icon: Minus,
799
+ color: "text-kumo-subtle"
800
+ }
801
+ };
802
+ function StatCard({ item }) {
803
+ const trend = item.trend ? trendConfig[item.trend] : null;
804
+ const TrendIcon = trend?.icon;
805
+ return /* @__PURE__ */ jsxs("div", {
806
+ className: "flex-1 rounded-lg border border-kumo-line p-4",
807
+ children: [
808
+ /* @__PURE__ */ jsx("div", {
809
+ className: "text-sm text-kumo-subtle",
810
+ children: item.label
811
+ }),
812
+ /* @__PURE__ */ jsxs("div", {
813
+ className: "mt-1 flex items-baseline gap-2",
814
+ children: [/* @__PURE__ */ jsx("span", {
815
+ className: "text-2xl font-bold text-kumo-default",
816
+ children: item.value
817
+ }), TrendIcon && /* @__PURE__ */ jsx("span", {
818
+ className: cn("flex items-center", trend.color),
819
+ children: /* @__PURE__ */ jsx(TrendIcon, { size: 16 })
820
+ })]
821
+ }),
822
+ item.description && /* @__PURE__ */ jsx("div", {
823
+ className: "mt-1 text-sm text-kumo-subtle",
824
+ children: item.description
825
+ })
826
+ ]
827
+ });
828
+ }
829
+ function StatsBlockComponent({ block }) {
830
+ return /* @__PURE__ */ jsx("div", {
831
+ className: "flex gap-4",
832
+ children: block.items.map((item, i) => /* @__PURE__ */ jsx(StatCard, { item }, i))
833
+ });
834
+ }
835
+
836
+ //#endregion
837
+ //#region src/blocks/table.tsx
838
+ function formatCell(value, format) {
839
+ let str;
840
+ if (value == null) str = "";
841
+ else if (typeof value === "string") str = value;
842
+ else if (typeof value === "number" || typeof value === "boolean") str = String(value);
843
+ else if (typeof value === "object") str = JSON.stringify(value);
844
+ else str = "";
845
+ switch (format) {
846
+ case "badge": return /* @__PURE__ */ jsx(Badge, { children: str });
847
+ case "relative_time": return str ? formatRelativeTime(str) : "";
848
+ case "number": {
849
+ const num = Number(value);
850
+ return Number.isNaN(num) ? str : num.toLocaleString();
851
+ }
852
+ case "code": return /* @__PURE__ */ jsx("code", {
853
+ className: "rounded bg-kumo-tint px-1.5 py-0.5 font-mono text-sm",
854
+ children: str
855
+ });
856
+ default: return str;
857
+ }
858
+ }
859
+ function TableBlockComponent({ block, onAction }) {
860
+ const [sort, setSort] = useState(null);
861
+ function handleSort(key) {
862
+ const next = sort?.key === key && sort.dir === "asc" ? {
863
+ key,
864
+ dir: "desc"
865
+ } : {
866
+ key,
867
+ dir: "asc"
868
+ };
869
+ setSort(next);
870
+ onAction({
871
+ type: "block_action",
872
+ action_id: block.page_action_id,
873
+ block_id: block.block_id,
874
+ value: { sort: next }
875
+ });
876
+ }
877
+ function handleLoadMore() {
878
+ onAction({
879
+ type: "block_action",
880
+ action_id: block.page_action_id,
881
+ block_id: block.block_id,
882
+ value: {
883
+ cursor: block.next_cursor,
884
+ sort
885
+ }
886
+ });
887
+ }
888
+ if (block.rows.length === 0 && block.empty_text) return /* @__PURE__ */ jsx("p", {
889
+ className: "py-4 text-center text-sm text-kumo-subtle",
890
+ children: block.empty_text
891
+ });
892
+ return /* @__PURE__ */ jsxs("div", {
893
+ className: "overflow-x-auto",
894
+ children: [/* @__PURE__ */ jsxs("table", {
895
+ className: "w-full text-left text-sm",
896
+ children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", {
897
+ className: "border-b border-kumo-line",
898
+ children: block.columns.map((col) => /* @__PURE__ */ jsx("th", {
899
+ className: cn("px-3 py-2 text-sm font-medium text-kumo-subtle", col.sortable && "cursor-pointer select-none"),
900
+ onClick: col.sortable ? () => handleSort(col.key) : void 0,
901
+ children: /* @__PURE__ */ jsxs("span", {
902
+ className: "inline-flex items-center gap-1",
903
+ children: [col.label, col.sortable && sort?.key === col.key && (sort.dir === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { size: 14 }) : /* @__PURE__ */ jsx(ArrowDown, { size: 14 }))]
904
+ })
905
+ }, col.key))
906
+ }) }), /* @__PURE__ */ jsx("tbody", { children: block.rows.map((row, i) => /* @__PURE__ */ jsx("tr", {
907
+ className: "border-b border-kumo-line last:border-0",
908
+ children: block.columns.map((col) => /* @__PURE__ */ jsx("td", {
909
+ className: "px-3 py-2 text-kumo-default",
910
+ children: formatCell(row[col.key], col.format)
911
+ }, col.key))
912
+ }, i)) })]
913
+ }), block.next_cursor && /* @__PURE__ */ jsx("div", {
914
+ className: "mt-2 flex justify-center",
915
+ children: /* @__PURE__ */ jsx("button", {
916
+ type: "button",
917
+ onClick: handleLoadMore,
918
+ className: "text-sm text-kumo-link hover:underline",
919
+ children: "Load more"
920
+ })
921
+ })]
922
+ });
923
+ }
924
+
925
+ //#endregion
926
+ //#region src/renderer.tsx
927
+ function renderBlock(block, onAction) {
928
+ switch (block.type) {
929
+ case "header": return /* @__PURE__ */ jsx(HeaderBlockComponent, { block });
930
+ case "section": return /* @__PURE__ */ jsx(SectionBlockComponent, {
931
+ block,
932
+ onAction
933
+ });
934
+ case "divider": return /* @__PURE__ */ jsx(DividerBlockComponent, {});
935
+ case "fields": return /* @__PURE__ */ jsx(FieldsBlockComponent, { block });
936
+ case "table": return /* @__PURE__ */ jsx(TableBlockComponent, {
937
+ block,
938
+ onAction
939
+ });
940
+ case "actions": return /* @__PURE__ */ jsx(ActionsBlockComponent, {
941
+ block,
942
+ onAction
943
+ });
944
+ case "stats": return /* @__PURE__ */ jsx(StatsBlockComponent, { block });
945
+ case "form": return /* @__PURE__ */ jsx(FormBlockComponent, {
946
+ block,
947
+ onAction
948
+ });
949
+ case "image": return /* @__PURE__ */ jsx(ImageBlockComponent, { block });
950
+ case "context": return /* @__PURE__ */ jsx(ContextBlockComponent, { block });
951
+ case "columns": return /* @__PURE__ */ jsx(ColumnsBlockComponent, {
952
+ block,
953
+ onAction
954
+ });
955
+ case "chart": return /* @__PURE__ */ jsx(ChartBlockComponent, { block });
956
+ case "meter": return /* @__PURE__ */ jsx(MeterBlockComponent, { block });
957
+ case "banner": return /* @__PURE__ */ jsx(BannerBlockComponent, { block });
958
+ case "code": return /* @__PURE__ */ jsx(CodeBlockComponent, { block });
959
+ default: return null;
960
+ }
961
+ }
962
+ function BlockRenderer({ blocks, onAction }) {
963
+ return /* @__PURE__ */ jsx("div", {
964
+ className: "flex flex-col gap-4",
965
+ children: blocks.map((block, i) => /* @__PURE__ */ jsx("div", { children: renderBlock(block, onAction) }, block.block_id ?? i))
966
+ });
967
+ }
968
+
969
+ //#endregion
970
+ export { BlockRenderer, blocks, cn, elements, formatRelativeTime, renderElement, validateBlocks };
971
+ //# sourceMappingURL=index.js.map