@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.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +971 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +3 -0
- package/dist/validation-BG2u9jAE.d.ts +450 -0
- package/dist/validation-BG2u9jAE.d.ts.map +1 -0
- package/dist/validation-DAttVLF0.js +1051 -0
- package/dist/validation-DAttVLF0.js.map +1 -0
- package/package.json +53 -0
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, "&").replace(RE_LT, "<").replace(RE_GT, ">").replace(RE_QUOT, """).replace(RE_APOS, "'");
|
|
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
|