tsquare 0.3.2 → 0.4.1
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/CHANGELOG.md +47 -0
- package/dist/catalog.d.ts +258 -0
- package/dist/chunks/{chunk-X46EDSCQ.js → chunk-QLIHML44.js} +443 -16
- package/dist/cli.js +1 -1
- package/dist/components.d.ts +26 -1
- package/dist/index.js +1 -1
- package/dist/playground/index.js +82 -14
- package/dist/prompt.d.ts +1 -1
- package/examples/dashboard.tsq +6 -1
- package/package.json +1 -1
- package/skill/tsquare/SKILL.md +1 -1
- package/skill/tsquare/reference.md +56 -3
|
@@ -207,7 +207,7 @@ var wireframeSchema = defineSchema(
|
|
|
207
207
|
"The root element MUST be a Board. A Board's children are Screens (and optional Notes placed beside them).",
|
|
208
208
|
"Every Screen MUST be a direct child of the Board. Each Screen is one view of the product; use several Screens to show several views or states.",
|
|
209
209
|
"Screens lay out their children top to bottom. Use Stack (direction row/column) and Grid inside a Screen to arrange content.",
|
|
210
|
-
"Modal and
|
|
210
|
+
"Modal, Drawer and Toast are overlays. They MUST be direct children of a Screen, and SHOULD be listed last in the Screen's children so they draw on top.",
|
|
211
211
|
"NavBar goes first in a Screen's children and TabBar goes last (before any overlay).",
|
|
212
212
|
"This is a low-fidelity wireframe: prefer placeholders (Image boxes, Text with lines) over invented copy, unless the copy matters to the design.",
|
|
213
213
|
'Every element MUST include a "children" array. Leaf elements use an empty array: "children": [].',
|
|
@@ -221,6 +221,13 @@ var o = (t) => t.nullish();
|
|
|
221
221
|
var align = z.enum(["start", "center", "end", "stretch"]);
|
|
222
222
|
var justify = z.enum(["start", "center", "end", "between", "around"]);
|
|
223
223
|
var iconName = z.string().refine(isIcon, { error: (iss) => unknownIconMessage(String(iss.input)) }).describe("Lucide icon name in kebab-case, e.g. menu, search, arrow-left, settings, bell, user");
|
|
224
|
+
var day = z.number().int().min(1).max(31);
|
|
225
|
+
var percent = z.number().min(0).max(100);
|
|
226
|
+
var bulletItem = z.union([
|
|
227
|
+
z.string(),
|
|
228
|
+
// strict: a misspelled key is an error, not silently dropped
|
|
229
|
+
z.strictObject({ label: z.string(), icon: o(iconName), muted: o(z.boolean()) })
|
|
230
|
+
]);
|
|
224
231
|
var componentDefinitions = {
|
|
225
232
|
// ── Canvas ────────────────────────────────────────────────────────────
|
|
226
233
|
Board: {
|
|
@@ -231,7 +238,7 @@ var componentDefinitions = {
|
|
|
231
238
|
gap: o(z.number()),
|
|
232
239
|
padding: o(z.number()),
|
|
233
240
|
accent: o(z.string().refine(isAccent, { error: (iss) => accentMessage(String(iss.input)) })).describe(
|
|
234
|
-
`The one UI color: primary buttons, solid badges, checked controls, toggles, active tabs, ghost buttons. ${Object.keys(ACCENTS).join(", ")}, or a hex color like #1a73e8. Omit for grayscale.`
|
|
241
|
+
`The one UI color: primary buttons, solid badges, checked controls, toggles, sliders, progress, selected days and pages, active tabs, ghost buttons. ${Object.keys(ACCENTS).join(", ")}, or a hex color like #1a73e8. Omit for grayscale.`
|
|
235
242
|
)
|
|
236
243
|
}),
|
|
237
244
|
slots: ["default"],
|
|
@@ -303,6 +310,15 @@ var componentDefinitions = {
|
|
|
303
310
|
description: "Bordered container with optional title. Children stack vertically.",
|
|
304
311
|
example: { title: "Revenue", variant: "outline" }
|
|
305
312
|
},
|
|
313
|
+
Accordion: {
|
|
314
|
+
props: z.object({
|
|
315
|
+
title: o(z.string()),
|
|
316
|
+
open: o(z.boolean()).describe("Show the children. Default closed: only the title row")
|
|
317
|
+
}),
|
|
318
|
+
slots: ["default"],
|
|
319
|
+
description: "Collapsible section: a title row with a chevron, children below when open. Stack several for an FAQ.",
|
|
320
|
+
example: { title: "Shipping", open: true }
|
|
321
|
+
},
|
|
306
322
|
Divider: {
|
|
307
323
|
props: z.object({ vertical: o(z.boolean()) }),
|
|
308
324
|
slots: [],
|
|
@@ -341,17 +357,39 @@ var componentDefinitions = {
|
|
|
341
357
|
description: "Body text, or placeholder lines when `lines` is set and `text` is not.",
|
|
342
358
|
example: { lines: 3 }
|
|
343
359
|
},
|
|
360
|
+
Bullets: {
|
|
361
|
+
props: z.object({
|
|
362
|
+
items: z.array(bulletItem).describe("Text, or {label icon muted} to change one line, e.g. {label=SSO icon=x muted}"),
|
|
363
|
+
numbered: o(z.boolean()).describe("1. 2. 3. instead of dots"),
|
|
364
|
+
icon: o(iconName).describe("A Lucide icon instead of dots, e.g. check for a feature list")
|
|
365
|
+
}),
|
|
366
|
+
slots: [],
|
|
367
|
+
description: "Bulleted, numbered or icon list of short lines.",
|
|
368
|
+
example: { items: ["Fast setup", "No credit card"] }
|
|
369
|
+
},
|
|
344
370
|
Image: {
|
|
345
371
|
props: z.object({
|
|
346
372
|
height: o(z.number()),
|
|
347
373
|
width: o(z.union([z.number(), z.string()])).describe("Default fills the container width"),
|
|
348
374
|
label: o(z.string()),
|
|
349
|
-
rounded: o(z.boolean())
|
|
375
|
+
rounded: o(z.boolean()),
|
|
376
|
+
kind: o(z.enum(["photo", "map"])).describe("map draws roads and a pin instead of the X")
|
|
350
377
|
}),
|
|
351
378
|
slots: [],
|
|
352
|
-
description: "Image placeholder: a box with an X through it.",
|
|
379
|
+
description: "Image placeholder: a box with an X through it, or a map.",
|
|
353
380
|
example: { height: 180, label: "Hero photo" }
|
|
354
381
|
},
|
|
382
|
+
Chart: {
|
|
383
|
+
props: z.object({
|
|
384
|
+
kind: o(z.enum(["line", "bar", "area", "pie", "donut"])).describe("Default line"),
|
|
385
|
+
title: o(z.string()),
|
|
386
|
+
height: o(z.number()).describe("Default 180"),
|
|
387
|
+
width: o(z.union([z.number(), z.string()])).describe("Default fills the container width")
|
|
388
|
+
}),
|
|
389
|
+
slots: [],
|
|
390
|
+
description: "Chart placeholder: a generic line, bar, area, pie or donut shape, no data.",
|
|
391
|
+
example: { kind: "bar", title: "Signups" }
|
|
392
|
+
},
|
|
355
393
|
Icon: {
|
|
356
394
|
props: z.object({
|
|
357
395
|
name: o(iconName),
|
|
@@ -399,7 +437,8 @@ var componentDefinitions = {
|
|
|
399
437
|
label: o(z.string()),
|
|
400
438
|
placeholder: o(z.string()),
|
|
401
439
|
value: o(z.string()),
|
|
402
|
-
type: o(z.enum(["text", "password", "search", "email"])),
|
|
440
|
+
type: o(z.enum(["text", "password", "search", "email", "date", "code"])).describe("date shows a calendar icon; code draws one box per digit"),
|
|
441
|
+
digits: o(z.number().int().min(2).max(10)).describe("Boxes for type=code, default 6"),
|
|
403
442
|
multiline: o(z.number().int().min(1)).describe("Number of rows; 2 or more makes a textarea"),
|
|
404
443
|
helper: o(z.string()),
|
|
405
444
|
error: o(z.boolean()).describe("Validation error: red border and red helper text"),
|
|
@@ -440,6 +479,39 @@ var componentDefinitions = {
|
|
|
440
479
|
description: "Dropdown field (closed state).",
|
|
441
480
|
example: { label: "Country", value: "United States" }
|
|
442
481
|
},
|
|
482
|
+
Slider: {
|
|
483
|
+
props: z.object({
|
|
484
|
+
label: o(z.string()),
|
|
485
|
+
value: o(z.number().min(0)).describe("Handle position: 0 to 100, or any amount (the track scales to fit)"),
|
|
486
|
+
range: o(z.tuple([z.number().min(0), z.number().min(0)])).describe("Two handles instead, e.g. [20, 80] or [50, 400]")
|
|
487
|
+
}),
|
|
488
|
+
slots: [],
|
|
489
|
+
description: "Slider with one handle, or two with `range`.",
|
|
490
|
+
example: { label: "Volume", value: 30 }
|
|
491
|
+
},
|
|
492
|
+
Progress: {
|
|
493
|
+
props: z.object({
|
|
494
|
+
label: o(z.string()),
|
|
495
|
+
value: o(percent).describe("Percent done"),
|
|
496
|
+
shape: o(z.enum(["bar", "circle"])).describe("Default bar; circle is a ring with the percent inside"),
|
|
497
|
+
steps: o(z.number().int().min(2).max(10)).describe("Draw a stepper with this many steps instead"),
|
|
498
|
+
step: o(z.number().int().min(1)).describe("The current step, with steps")
|
|
499
|
+
}),
|
|
500
|
+
slots: [],
|
|
501
|
+
description: "Progress bar, ring (circle), or stepper (steps).",
|
|
502
|
+
example: { label: "Uploading", value: 40 }
|
|
503
|
+
},
|
|
504
|
+
Calendar: {
|
|
505
|
+
props: z.object({
|
|
506
|
+
month: o(z.string()).describe('Month and year, e.g. "October 2026"; the days match that month'),
|
|
507
|
+
selected: o(day),
|
|
508
|
+
range: o(z.tuple([day, day])).describe("Selected days, e.g. [12, 18]"),
|
|
509
|
+
marked: o(z.array(day)).describe("Days with a dot, e.g. events")
|
|
510
|
+
}),
|
|
511
|
+
slots: [],
|
|
512
|
+
description: "Month calendar.",
|
|
513
|
+
example: { month: "October 2026", selected: 14 }
|
|
514
|
+
},
|
|
443
515
|
// ── Navigation & data ─────────────────────────────────────────────────
|
|
444
516
|
NavBar: {
|
|
445
517
|
props: z.object({
|
|
@@ -470,6 +542,15 @@ var componentDefinitions = {
|
|
|
470
542
|
description: "In-page tabs with an underline on the active one.",
|
|
471
543
|
example: { items: ["Overview", "Activity", "Settings"], active: 0 }
|
|
472
544
|
},
|
|
545
|
+
Pagination: {
|
|
546
|
+
props: z.object({
|
|
547
|
+
pages: z.number().int().min(1),
|
|
548
|
+
current: o(z.number().int().min(1)).describe("Default 1")
|
|
549
|
+
}),
|
|
550
|
+
slots: [],
|
|
551
|
+
description: "Page numbers with previous and next.",
|
|
552
|
+
example: { pages: 12, current: 3 }
|
|
553
|
+
},
|
|
473
554
|
List: {
|
|
474
555
|
props: z.object({ dividers: o(z.boolean()), grow: o(z.boolean()) }),
|
|
475
556
|
slots: ["default"],
|
|
@@ -522,6 +603,17 @@ var componentDefinitions = {
|
|
|
522
603
|
slots: ["default"],
|
|
523
604
|
description: "Panel sliding in from an edge: side nav (left), filters/details (right), or bottom sheet. Must be a direct child of a Screen, listed last.",
|
|
524
605
|
example: { side: "left", title: "Menu", size: 300 }
|
|
606
|
+
},
|
|
607
|
+
Toast: {
|
|
608
|
+
props: z.object({
|
|
609
|
+
text: o(z.string()),
|
|
610
|
+
action: o(z.string()).describe('A text button, e.g. "Undo"'),
|
|
611
|
+
icon: o(iconName),
|
|
612
|
+
position: o(z.enum(["bottom", "top"])).describe("Default bottom, above a TabBar")
|
|
613
|
+
}),
|
|
614
|
+
slots: [],
|
|
615
|
+
description: "Short message over the screen, e.g. after saving. Must be a direct child of a Screen, listed last.",
|
|
616
|
+
example: { text: "Message sent", action: "Undo" }
|
|
525
617
|
}
|
|
526
618
|
};
|
|
527
619
|
var catalog = defineCatalog(wireframeSchema, {
|
|
@@ -672,7 +764,7 @@ function Screen({ element, children }) {
|
|
|
672
764
|
const type = isValidElement(c) ? c.props.element?.type : null;
|
|
673
765
|
if (type === "NavBar") top.push(c);
|
|
674
766
|
else if (type === "TabBar") bottom.push(c);
|
|
675
|
-
else if (type === "Modal" || type === "Drawer") overlays.push(c);
|
|
767
|
+
else if (type === "Modal" || type === "Drawer" || type === "Toast") overlays.push(c);
|
|
676
768
|
else body.push(c);
|
|
677
769
|
}
|
|
678
770
|
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", flexShrink: 0 }, children: [
|
|
@@ -711,7 +803,7 @@ function Screen({ element, children }) {
|
|
|
711
803
|
}
|
|
712
804
|
),
|
|
713
805
|
bottom,
|
|
714
|
-
overlays
|
|
806
|
+
overlays.map((o2) => isValidElement(o2) ? cloneElement(o2, { aboveTabBar: bottom.length > 0 }) : o2)
|
|
715
807
|
] })
|
|
716
808
|
]
|
|
717
809
|
}
|
|
@@ -814,6 +906,16 @@ function Card({ element, children, dir, stretch }) {
|
|
|
814
906
|
}
|
|
815
907
|
);
|
|
816
908
|
}
|
|
909
|
+
function Accordion({ element, children }) {
|
|
910
|
+
const p = element.props;
|
|
911
|
+
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", borderBottom: `1px solid ${theme.line}` }, children: [
|
|
912
|
+
/* @__PURE__ */ jsxs(Box, { style: { alignItems: "center", justifyContent: "space-between", gap: 12, padding: "12px 0" }, children: [
|
|
913
|
+
/* @__PURE__ */ jsx(Box, { style: { fontSize: 15, fontWeight: 600, color: theme.ink }, children: p.title ?? "" }),
|
|
914
|
+
/* @__PURE__ */ jsx(IconGlyph, { name: p.open ? "chevron-up" : "chevron-down", size: 18, color: theme.muted })
|
|
915
|
+
] }),
|
|
916
|
+
p.open ? /* @__PURE__ */ jsx(Box, { style: { flexDirection: "column", gap: 10, paddingBottom: 14 }, children: withDir(children, "column") }) : null
|
|
917
|
+
] });
|
|
918
|
+
}
|
|
817
919
|
function Divider({ element, dir }) {
|
|
818
920
|
const vertical = element.props.vertical ?? dir === "row";
|
|
819
921
|
return vertical ? /* @__PURE__ */ jsx(Box, { style: { width: 1, alignSelf: "stretch", backgroundColor: theme.line, flexShrink: 0 } }) : /* @__PURE__ */ jsx(Box, { style: { height: 1, alignSelf: "stretch", backgroundColor: theme.line, flexShrink: 0 } });
|
|
@@ -876,6 +978,21 @@ function Text({ element }) {
|
|
|
876
978
|
}
|
|
877
979
|
);
|
|
878
980
|
}
|
|
981
|
+
function Bullets({ element }) {
|
|
982
|
+
const p = element.props;
|
|
983
|
+
const items = p.items ?? [];
|
|
984
|
+
return /* @__PURE__ */ jsx(Box, { style: { flexDirection: "column", gap: 8 }, children: items.map((item, i) => {
|
|
985
|
+
const { label, icon, muted } = typeof item === "string" ? { label: item, icon: null, muted: false } : item;
|
|
986
|
+
const color = muted ? theme.muted : theme.text;
|
|
987
|
+
const glyph = icon ?? p.icon;
|
|
988
|
+
const marker = glyph ? /* @__PURE__ */ jsx(IconGlyph, { name: glyph, size: 18, color }) : p.numbered ? /* @__PURE__ */ jsx(Box, { style: { minWidth: 18, color, fontWeight: 600 }, children: `${i + 1}.` }) : /* @__PURE__ */ jsx(Box, { style: { width: 18, height: 22, alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx(Box, { style: { width: 6, height: 6, borderRadius: 3, backgroundColor: color } }) });
|
|
989
|
+
return /* @__PURE__ */ jsxs(Box, { style: { gap: 10, alignItems: glyph ? "center" : "flex-start", fontSize: 15, lineHeight: 1.45, color }, children: [
|
|
990
|
+
marker,
|
|
991
|
+
/* @__PURE__ */ jsx(Box, { style: { flexShrink: 1 }, children: label })
|
|
992
|
+
] }, i);
|
|
993
|
+
}) });
|
|
994
|
+
}
|
|
995
|
+
var MAP_ROADS = ["M0 42 C30 38 60 50 100 44", "M38 0 C42 30 34 60 44 100", "M0 78 L100 70", "M72 0 L80 100", "M0 16 L40 22", "M44 60 L100 92"];
|
|
879
996
|
function Image({ element, dir }) {
|
|
880
997
|
const p = element.props;
|
|
881
998
|
const h = p.height ?? 160;
|
|
@@ -896,7 +1013,12 @@ function Image({ element, dir }) {
|
|
|
896
1013
|
justifyContent: "center"
|
|
897
1014
|
},
|
|
898
1015
|
children: [
|
|
899
|
-
/* @__PURE__ */ jsxs(
|
|
1016
|
+
p.kind === "map" ? /* @__PURE__ */ jsxs("svg", { width: "100%", height: "100%", viewBox: "0 0 100 100", preserveAspectRatio: "none", style: { position: "absolute", top: 0, left: 0 }, children: [
|
|
1017
|
+
/* @__PURE__ */ jsx("rect", { x: "0", y: "0", width: "100", height: "100", fill: theme.fill2 }),
|
|
1018
|
+
/* @__PURE__ */ jsx("rect", { x: "62", y: "8", width: "26", height: "22", fill: theme.fill }),
|
|
1019
|
+
/* @__PURE__ */ jsx("rect", { x: "8", y: "64", width: "20", height: "26", fill: theme.fill }),
|
|
1020
|
+
MAP_ROADS.map((d, i) => /* @__PURE__ */ jsx("path", { d, fill: "none", stroke: theme.paper, strokeWidth: i < 2 ? 7 : 4, vectorEffect: "non-scaling-stroke" }, i))
|
|
1021
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
900
1022
|
"svg",
|
|
901
1023
|
{
|
|
902
1024
|
width: "100%",
|
|
@@ -910,6 +1032,10 @@ function Image({ element, dir }) {
|
|
|
910
1032
|
]
|
|
911
1033
|
}
|
|
912
1034
|
),
|
|
1035
|
+
p.kind === "map" ? /* @__PURE__ */ jsx(Box, { style: { position: "absolute", left: 0, right: 0, top: 0, bottom: 0, alignItems: "center", justifyContent: "center", paddingBottom: 28 }, children: /* @__PURE__ */ jsxs("svg", { width: "28", height: "36", viewBox: "0 0 24 31", children: [
|
|
1036
|
+
/* @__PURE__ */ jsx("path", { d: "M12 1C6.5 1 2 5.3 2 10.7 2 18 12 30 12 30s10-12 10-19.3C22 5.3 17.5 1 12 1z", fill: theme.ink }),
|
|
1037
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "10.5", r: "3.6", fill: theme.paper })
|
|
1038
|
+
] }) }) : null,
|
|
913
1039
|
p.label ? /* @__PURE__ */ jsx(
|
|
914
1040
|
Box,
|
|
915
1041
|
{
|
|
@@ -918,7 +1044,9 @@ function Image({ element, dir }) {
|
|
|
918
1044
|
fontSize: 12,
|
|
919
1045
|
color: theme.muted,
|
|
920
1046
|
backgroundColor: theme.fill,
|
|
921
|
-
borderRadius: 4
|
|
1047
|
+
borderRadius: 4,
|
|
1048
|
+
// a map keeps its pin in the middle, so the label goes in the corner
|
|
1049
|
+
...p.kind === "map" ? { position: "absolute", left: 8, bottom: 8, backgroundColor: theme.paper } : {}
|
|
922
1050
|
},
|
|
923
1051
|
children: p.label
|
|
924
1052
|
}
|
|
@@ -931,6 +1059,48 @@ function Icon({ element, dir, aligned }) {
|
|
|
931
1059
|
const p = element.props;
|
|
932
1060
|
return /* @__PURE__ */ jsx(Box, { style: { flexShrink: 0, ...hug(dir, aligned) }, children: /* @__PURE__ */ jsx(IconGlyph, { name: p.name, size: p.size ?? 20 }) });
|
|
933
1061
|
}
|
|
1062
|
+
var CHART_LINE = [62, 48, 55, 30, 38, 22, 34, 12];
|
|
1063
|
+
var CHART_BARS = [55, 72, 40, 86, 64, 48, 78];
|
|
1064
|
+
function Chart({ element, dir }) {
|
|
1065
|
+
const p = element.props;
|
|
1066
|
+
const kind = p.kind ?? "line";
|
|
1067
|
+
const h = p.height ?? 180;
|
|
1068
|
+
const w = p.width ?? (dir === "row" ? void 0 : "100%");
|
|
1069
|
+
const round = kind === "pie" || kind === "donut";
|
|
1070
|
+
const plot = (() => {
|
|
1071
|
+
if (round) {
|
|
1072
|
+
const slice = (from, to, fill, k) => {
|
|
1073
|
+
const a0 = from / 100 * 2 * Math.PI - Math.PI / 2, a1 = to / 100 * 2 * Math.PI - Math.PI / 2;
|
|
1074
|
+
const x0 = 50 + 46 * Math.cos(a0), y0 = 50 + 46 * Math.sin(a0), x1 = 50 + 46 * Math.cos(a1), y1 = 50 + 46 * Math.sin(a1);
|
|
1075
|
+
return /* @__PURE__ */ jsx("path", { d: `M50 50 L${x0} ${y0} A46 46 0 ${to - from > 50 ? 1 : 0} 1 ${x1} ${y1} Z`, fill, stroke: theme.paper, strokeWidth: "1.5" }, k);
|
|
1076
|
+
};
|
|
1077
|
+
return /* @__PURE__ */ jsxs("svg", { width: h - 32, height: h - 32, viewBox: "0 0 100 100", children: [
|
|
1078
|
+
slice(0, 45, theme.lineStrong, 0),
|
|
1079
|
+
slice(45, 75, theme.line, 1),
|
|
1080
|
+
slice(75, 100, theme.fill2, 2),
|
|
1081
|
+
kind === "donut" ? /* @__PURE__ */ jsx("circle", { cx: "50", cy: "50", r: "26", fill: theme.paper }) : null
|
|
1082
|
+
] });
|
|
1083
|
+
}
|
|
1084
|
+
const grid = [25, 50, 75].map((y) => /* @__PURE__ */ jsx("line", { x1: "0", y1: y, x2: "300", y2: y, stroke: theme.fill2, strokeWidth: "1", vectorEffect: "non-scaling-stroke" }, y));
|
|
1085
|
+
if (kind === "bar") {
|
|
1086
|
+
const bw = 300 / CHART_BARS.length;
|
|
1087
|
+
return /* @__PURE__ */ jsxs("svg", { width: "100%", height: "100%", viewBox: "0 0 300 100", preserveAspectRatio: "none", children: [
|
|
1088
|
+
grid,
|
|
1089
|
+
CHART_BARS.map((v, i) => /* @__PURE__ */ jsx("rect", { x: i * bw + bw * 0.2, y: 100 - v, width: bw * 0.6, height: v, rx: "2", fill: theme.line }, i))
|
|
1090
|
+
] });
|
|
1091
|
+
}
|
|
1092
|
+
const pts = CHART_LINE.map((y, i) => `${i / (CHART_LINE.length - 1) * 300},${y}`).join(" ");
|
|
1093
|
+
return /* @__PURE__ */ jsxs("svg", { width: "100%", height: "100%", viewBox: "0 0 300 100", preserveAspectRatio: "none", children: [
|
|
1094
|
+
grid,
|
|
1095
|
+
kind === "area" ? /* @__PURE__ */ jsx("polygon", { points: `0,100 ${pts} 300,100`, fill: theme.fill2 }) : null,
|
|
1096
|
+
/* @__PURE__ */ jsx("polyline", { points: pts, fill: "none", stroke: theme.lineStrong, strokeWidth: "2.5", strokeLinejoin: "round", vectorEffect: "non-scaling-stroke" })
|
|
1097
|
+
] });
|
|
1098
|
+
})();
|
|
1099
|
+
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", gap: 10, width: w, height: h, flexShrink: 0, ...dir === "row" ? { flexGrow: 1, flexBasis: "0px" } : {} }, children: [
|
|
1100
|
+
p.title ? /* @__PURE__ */ jsx(Box, { style: { fontSize: 13, fontWeight: 600, color: theme.text }, children: p.title }) : null,
|
|
1101
|
+
/* @__PURE__ */ jsx(Box, { style: { flexGrow: 1, borderBottom: round ? void 0 : `1.5px solid ${theme.line}`, justifyContent: round ? "center" : void 0, alignItems: round ? "center" : void 0 }, children: plot })
|
|
1102
|
+
] });
|
|
1103
|
+
}
|
|
934
1104
|
function AvatarCircle({ initials, size = 40 }) {
|
|
935
1105
|
return /* @__PURE__ */ jsx(
|
|
936
1106
|
Box,
|
|
@@ -1036,8 +1206,35 @@ function Field({ children, height = 42, top = false, error = false }) {
|
|
|
1036
1206
|
}
|
|
1037
1207
|
);
|
|
1038
1208
|
}
|
|
1039
|
-
function
|
|
1209
|
+
function CodeBoxes({ digits, value, c = GRAY }) {
|
|
1210
|
+
return /* @__PURE__ */ jsx(Box, { style: { gap: 8 }, children: Array.from({ length: digits }, (_, i) => /* @__PURE__ */ jsx(
|
|
1211
|
+
Box,
|
|
1212
|
+
{
|
|
1213
|
+
style: {
|
|
1214
|
+
width: 44,
|
|
1215
|
+
height: 52,
|
|
1216
|
+
borderRadius: 8,
|
|
1217
|
+
border: `1.5px solid ${i === value.length ? c.accent : theme.lineStrong}`,
|
|
1218
|
+
alignItems: "center",
|
|
1219
|
+
justifyContent: "center",
|
|
1220
|
+
fontSize: 22,
|
|
1221
|
+
fontWeight: 600,
|
|
1222
|
+
color: theme.ink
|
|
1223
|
+
},
|
|
1224
|
+
children: value[i] ?? ""
|
|
1225
|
+
},
|
|
1226
|
+
i
|
|
1227
|
+
)) });
|
|
1228
|
+
}
|
|
1229
|
+
function Input({ element, dir, colors }) {
|
|
1040
1230
|
const p = element.props;
|
|
1231
|
+
if (p.type === "code") {
|
|
1232
|
+
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", gap: 6, ...hug(dir) }, children: [
|
|
1233
|
+
/* @__PURE__ */ jsx(FieldLabel, { text: p.label }),
|
|
1234
|
+
/* @__PURE__ */ jsx(CodeBoxes, { digits: p.digits ?? 6, value: String(p.value ?? "").slice(0, p.digits ?? 6), c: colors }),
|
|
1235
|
+
p.helper ? /* @__PURE__ */ jsx(Box, { style: { fontSize: 12, color: p.error ? TONES.danger : theme.muted }, children: p.helper }) : null
|
|
1236
|
+
] });
|
|
1237
|
+
}
|
|
1041
1238
|
const rows = p.multiline ?? 1;
|
|
1042
1239
|
const isPassword = p.type === "password";
|
|
1043
1240
|
const shown = p.value ?? null;
|
|
@@ -1047,7 +1244,8 @@ function Input({ element, dir }) {
|
|
|
1047
1244
|
/* @__PURE__ */ jsxs(Field, { height: rows > 1 ? rows * 22 + 20 : 42, top: rows > 1, error: !!p.error, children: [
|
|
1048
1245
|
p.type === "search" ? /* @__PURE__ */ jsx(IconGlyph, { name: "search", size: 18, color: theme.muted }) : null,
|
|
1049
1246
|
/* @__PURE__ */ jsx(Box, { style: { flexGrow: 1, color: content ? theme.ink : theme.muted }, children: content ?? p.placeholder ?? (isPassword ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : "") }),
|
|
1050
|
-
isPassword ? /* @__PURE__ */ jsx(IconGlyph, { name: "eye-off", size: 18, color: theme.muted }) : null
|
|
1247
|
+
isPassword ? /* @__PURE__ */ jsx(IconGlyph, { name: "eye-off", size: 18, color: theme.muted }) : null,
|
|
1248
|
+
p.type === "date" ? /* @__PURE__ */ jsx(IconGlyph, { name: "calendar", size: 18, color: theme.muted }) : null
|
|
1051
1249
|
] }),
|
|
1052
1250
|
p.helper ? /* @__PURE__ */ jsx(Box, { style: { fontSize: 12, color: p.error ? TONES.danger : theme.muted }, children: p.helper }) : null
|
|
1053
1251
|
] });
|
|
@@ -1118,6 +1316,133 @@ function Toggle({ element, colors }) {
|
|
|
1118
1316
|
/* @__PURE__ */ jsx(Switch, { on: p.on, c: colors })
|
|
1119
1317
|
] });
|
|
1120
1318
|
}
|
|
1319
|
+
function Slider({ element, colors: c = GRAY }) {
|
|
1320
|
+
const p = element.props;
|
|
1321
|
+
const [a, b] = p.range ?? [0, p.value ?? 50];
|
|
1322
|
+
const top = Math.max(a, b);
|
|
1323
|
+
const max = top <= 100 ? 100 : Math.pow(10, Math.floor(Math.log10(top * 1.25))) * Math.ceil(top * 1.25 / Math.pow(10, Math.floor(Math.log10(top * 1.25))));
|
|
1324
|
+
const from = a / max * 100, to = b / max * 100;
|
|
1325
|
+
const thumb = (at, k) => /* @__PURE__ */ jsx(Box, { style: { position: "absolute", left: `${at}%`, top: 0, marginLeft: -10, width: 20, height: 20, borderRadius: 10, backgroundColor: theme.paper, border: `2px solid ${c.accent}` } }, k);
|
|
1326
|
+
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", gap: 10 }, children: [
|
|
1327
|
+
/* @__PURE__ */ jsx(FieldLabel, { text: p.label }),
|
|
1328
|
+
/* @__PURE__ */ jsxs(Box, { style: { position: "relative", height: 20, marginLeft: 10, marginRight: 10 }, children: [
|
|
1329
|
+
/* @__PURE__ */ jsx(Box, { style: { position: "absolute", left: 0, right: 0, top: 8, height: 4, borderRadius: 2, backgroundColor: theme.fill2 } }),
|
|
1330
|
+
/* @__PURE__ */ jsx(Box, { style: { position: "absolute", left: `${from}%`, width: `${to - from}%`, top: 8, height: 4, borderRadius: 2, backgroundColor: c.accent } }),
|
|
1331
|
+
p.range ? thumb(from, 0) : null,
|
|
1332
|
+
thumb(to, 1)
|
|
1333
|
+
] })
|
|
1334
|
+
] });
|
|
1335
|
+
}
|
|
1336
|
+
function Progress({ element, dir, aligned, colors: c = GRAY }) {
|
|
1337
|
+
const p = element.props;
|
|
1338
|
+
const value = Math.max(0, Math.min(100, p.value ?? 40));
|
|
1339
|
+
if (p.steps) {
|
|
1340
|
+
const current = Math.min(p.step ?? 1, p.steps);
|
|
1341
|
+
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", gap: 10 }, children: [
|
|
1342
|
+
/* @__PURE__ */ jsx(FieldLabel, { text: p.label }),
|
|
1343
|
+
/* @__PURE__ */ jsx(Box, { style: { alignItems: "center" }, children: Array.from({ length: p.steps }, (_, i) => {
|
|
1344
|
+
const n = i + 1;
|
|
1345
|
+
const done = n < current, now = n === current;
|
|
1346
|
+
const dot = /* @__PURE__ */ jsx(
|
|
1347
|
+
Box,
|
|
1348
|
+
{
|
|
1349
|
+
style: {
|
|
1350
|
+
width: 28,
|
|
1351
|
+
height: 28,
|
|
1352
|
+
flexShrink: 0,
|
|
1353
|
+
borderRadius: 14,
|
|
1354
|
+
alignItems: "center",
|
|
1355
|
+
justifyContent: "center",
|
|
1356
|
+
fontSize: 13,
|
|
1357
|
+
fontWeight: 600,
|
|
1358
|
+
backgroundColor: done ? c.accent : theme.paper,
|
|
1359
|
+
border: `2px solid ${done || now ? c.accent : theme.lineStrong}`,
|
|
1360
|
+
color: now ? c.accentText : theme.muted
|
|
1361
|
+
},
|
|
1362
|
+
children: done ? /* @__PURE__ */ jsx(IconGlyph, { name: "check", size: 14, color: c.onAccent, strokeWidth: 3 }) : String(n)
|
|
1363
|
+
},
|
|
1364
|
+
`d${i}`
|
|
1365
|
+
);
|
|
1366
|
+
return n === 1 ? dot : [/* @__PURE__ */ jsx(Box, { style: { flexGrow: 1, height: 2, backgroundColor: n <= current ? c.accent : theme.line } }, `l${i}`), dot];
|
|
1367
|
+
}) })
|
|
1368
|
+
] });
|
|
1369
|
+
}
|
|
1370
|
+
if (p.shape === "circle") {
|
|
1371
|
+
const r = 26, circumference = 2 * Math.PI * r;
|
|
1372
|
+
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", gap: 8, alignItems: "center", ...hug(dir, aligned), ...dir === "row" ? { alignSelf: "flex-start" } : {} }, children: [
|
|
1373
|
+
/* @__PURE__ */ jsxs(Box, { style: { position: "relative", width: 64, height: 64, alignItems: "center", justifyContent: "center" }, children: [
|
|
1374
|
+
/* @__PURE__ */ jsxs("svg", { width: "64", height: "64", viewBox: "0 0 64 64", style: { position: "absolute", top: 0, left: 0 }, children: [
|
|
1375
|
+
/* @__PURE__ */ jsx("circle", { cx: "32", cy: "32", r, fill: "none", stroke: theme.fill2, strokeWidth: "6" }),
|
|
1376
|
+
/* @__PURE__ */ jsx("circle", { cx: "32", cy: "32", r, fill: "none", stroke: c.accent, strokeWidth: "6", strokeLinecap: "round", strokeDasharray: `${circumference * value / 100} ${circumference}`, transform: "rotate(-90 32 32)" })
|
|
1377
|
+
] }),
|
|
1378
|
+
/* @__PURE__ */ jsx(Box, { style: { fontSize: 14, fontWeight: 600, color: theme.ink }, children: `${Math.round(value)}%` })
|
|
1379
|
+
] }),
|
|
1380
|
+
p.label ? /* @__PURE__ */ jsx(Box, { style: { fontSize: 13, color: theme.muted }, children: p.label }) : null
|
|
1381
|
+
] });
|
|
1382
|
+
}
|
|
1383
|
+
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", gap: 8 }, children: [
|
|
1384
|
+
p.label ? /* @__PURE__ */ jsxs(Box, { style: { justifyContent: "space-between", fontSize: 13 }, children: [
|
|
1385
|
+
/* @__PURE__ */ jsx(Box, { style: { fontWeight: 600, color: theme.text }, children: p.label }),
|
|
1386
|
+
/* @__PURE__ */ jsx(Box, { style: { color: theme.muted }, children: `${Math.round(value)}%` })
|
|
1387
|
+
] }) : null,
|
|
1388
|
+
/* @__PURE__ */ jsx(Box, { style: { height: 8, borderRadius: 4, backgroundColor: theme.fill2 }, children: /* @__PURE__ */ jsx(Box, { style: { width: `${value}%`, height: 8, borderRadius: 4, backgroundColor: c.accent } }) })
|
|
1389
|
+
] });
|
|
1390
|
+
}
|
|
1391
|
+
var MONTHS = ["january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"];
|
|
1392
|
+
function monthGrid(month) {
|
|
1393
|
+
const m = String(month ?? "").toLowerCase().match(/([a-z]+)\s+(\d{4})/);
|
|
1394
|
+
const index = m ? MONTHS.findIndex((name) => name.startsWith(m[1].slice(0, 3))) : -1;
|
|
1395
|
+
if (!m || index < 0) return { start: 3, days: 31 };
|
|
1396
|
+
const year = Number(m[2]);
|
|
1397
|
+
return { start: new Date(Date.UTC(year, index, 1)).getUTCDay(), days: new Date(Date.UTC(year, index + 1, 0)).getUTCDate() };
|
|
1398
|
+
}
|
|
1399
|
+
function Calendar({ element, dir, aligned, colors: c = GRAY }) {
|
|
1400
|
+
const p = element.props;
|
|
1401
|
+
const { start, days } = monthGrid(p.month);
|
|
1402
|
+
const [from, to] = p.range ?? [p.selected ?? 0, p.selected ?? 0];
|
|
1403
|
+
const marked = new Set(p.marked ?? []);
|
|
1404
|
+
const cells = [...Array(start).fill(null), ...Array.from({ length: days }, (_, i) => i + 1)];
|
|
1405
|
+
while (cells.length % 7) cells.push(null);
|
|
1406
|
+
const weeks = Array.from({ length: cells.length / 7 }, (_, w) => cells.slice(w * 7, w * 7 + 7));
|
|
1407
|
+
const CELL = 38;
|
|
1408
|
+
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", gap: 4, padding: 12, border: `1.5px solid ${theme.line}`, borderRadius: 12, backgroundColor: theme.paper, ...hug(dir, aligned) }, children: [
|
|
1409
|
+
/* @__PURE__ */ jsxs(Box, { style: { alignItems: "center", justifyContent: "space-between", padding: "2px 4px 8px" }, children: [
|
|
1410
|
+
/* @__PURE__ */ jsx(IconGlyph, { name: "chevron-left", size: 18, color: theme.muted }),
|
|
1411
|
+
/* @__PURE__ */ jsx(Box, { style: { fontSize: 15, fontWeight: 600, color: theme.ink }, children: p.month ?? "Month" }),
|
|
1412
|
+
/* @__PURE__ */ jsx(IconGlyph, { name: "chevron-right", size: 18, color: theme.muted })
|
|
1413
|
+
] }),
|
|
1414
|
+
/* @__PURE__ */ jsx(Box, { children: ["S", "M", "T", "W", "T", "F", "S"].map((d, i) => /* @__PURE__ */ jsx(Box, { style: { width: CELL, justifyContent: "center", fontSize: 12, color: theme.muted }, children: d }, i)) }),
|
|
1415
|
+
weeks.map((week, w) => /* @__PURE__ */ jsx(Box, { children: week.map((d, i) => {
|
|
1416
|
+
const end = d != null && (d === from || d === to) && from > 0;
|
|
1417
|
+
const inside = d != null && from > 0 && d > from && d < to;
|
|
1418
|
+
const band = d != null && from > 0 && to > from && d >= from && d <= to;
|
|
1419
|
+
return /* @__PURE__ */ jsxs(Box, { style: { width: CELL, height: CELL, alignItems: "center", justifyContent: "center", position: "relative" }, children: [
|
|
1420
|
+
band ? /* @__PURE__ */ jsx(Box, { style: { position: "absolute", top: 4, bottom: 4, left: d === from ? CELL / 2 : 0, right: d === to ? CELL / 2 : 0, backgroundColor: theme.fill } }) : null,
|
|
1421
|
+
d != null ? /* @__PURE__ */ jsxs(
|
|
1422
|
+
Box,
|
|
1423
|
+
{
|
|
1424
|
+
style: {
|
|
1425
|
+
width: 32,
|
|
1426
|
+
height: 32,
|
|
1427
|
+
borderRadius: 16,
|
|
1428
|
+
flexDirection: "column",
|
|
1429
|
+
alignItems: "center",
|
|
1430
|
+
justifyContent: "center",
|
|
1431
|
+
fontSize: 14,
|
|
1432
|
+
backgroundColor: end ? c.accent : void 0,
|
|
1433
|
+
color: end ? c.onAccent : inside ? theme.ink : theme.text,
|
|
1434
|
+
fontWeight: end ? 600 : 400
|
|
1435
|
+
},
|
|
1436
|
+
children: [
|
|
1437
|
+
String(d),
|
|
1438
|
+
marked.has(d) ? /* @__PURE__ */ jsx(Box, { style: { position: "absolute", bottom: 3, width: 4, height: 4, borderRadius: 2, backgroundColor: end ? c.onAccent : theme.muted } }) : null
|
|
1439
|
+
]
|
|
1440
|
+
}
|
|
1441
|
+
) : null
|
|
1442
|
+
] }, i);
|
|
1443
|
+
}) }, w))
|
|
1444
|
+
] });
|
|
1445
|
+
}
|
|
1121
1446
|
var LEADING_ICON = { menu: "menu", back: "arrow-left", close: "x" };
|
|
1122
1447
|
function NavBar({ element }) {
|
|
1123
1448
|
const p = element.props;
|
|
@@ -1199,6 +1524,28 @@ function Tabs({ element, colors: c = GRAY }) {
|
|
|
1199
1524
|
i
|
|
1200
1525
|
)) });
|
|
1201
1526
|
}
|
|
1527
|
+
function pageList(pages, current) {
|
|
1528
|
+
if (pages <= 7) return Array.from({ length: pages }, (_, i) => i + 1);
|
|
1529
|
+
const near = [current - 1, current, current + 1].filter((n) => n > 1 && n < pages);
|
|
1530
|
+
const shown = [1, ...near, pages];
|
|
1531
|
+
const out = [];
|
|
1532
|
+
shown.forEach((n, i) => {
|
|
1533
|
+
if (i && n - shown[i - 1] > 1) out.push("\u2026");
|
|
1534
|
+
out.push(n);
|
|
1535
|
+
});
|
|
1536
|
+
return out;
|
|
1537
|
+
}
|
|
1538
|
+
function Pagination({ element, dir, aligned, colors: c = GRAY }) {
|
|
1539
|
+
const p = element.props;
|
|
1540
|
+
const pages = Math.max(1, p.pages ?? 1);
|
|
1541
|
+
const current = Math.min(Math.max(1, p.current ?? 1), pages);
|
|
1542
|
+
const cell = (content, key, on = false) => /* @__PURE__ */ jsx(Box, { style: { minWidth: 34, height: 34, padding: "0 6px", borderRadius: 8, alignItems: "center", justifyContent: "center", fontSize: 14, backgroundColor: on ? c.accent : void 0, color: on ? c.onAccent : theme.text, fontWeight: on ? 600 : 400 }, children: content }, key);
|
|
1543
|
+
return /* @__PURE__ */ jsxs(Box, { style: { gap: 4, alignItems: "center", ...hug(dir, aligned) }, children: [
|
|
1544
|
+
cell(/* @__PURE__ */ jsx(IconGlyph, { name: "chevron-left", size: 18, color: current > 1 ? theme.text : theme.line }), "prev"),
|
|
1545
|
+
pageList(pages, current).map((n, i) => n === "\u2026" ? cell(/* @__PURE__ */ jsx(Box, { style: { color: theme.muted }, children: "\u2026" }), `e${i}`) : cell(String(n), n, n === current)),
|
|
1546
|
+
cell(/* @__PURE__ */ jsx(IconGlyph, { name: "chevron-right", size: 18, color: current < pages ? theme.text : theme.line }), "next")
|
|
1547
|
+
] });
|
|
1548
|
+
}
|
|
1202
1549
|
function List({ element, children, dir, stretch }) {
|
|
1203
1550
|
const dividers = element.props.dividers ?? true;
|
|
1204
1551
|
const items = withDir(children, "column");
|
|
@@ -1343,6 +1690,15 @@ function Drawer({ element, children }) {
|
|
|
1343
1690
|
}
|
|
1344
1691
|
) });
|
|
1345
1692
|
}
|
|
1693
|
+
function Toast({ element, aboveTabBar }) {
|
|
1694
|
+
const p = element.props;
|
|
1695
|
+
const top = p.position === "top";
|
|
1696
|
+
return /* @__PURE__ */ jsx(Box, { style: { position: "absolute", left: 16, right: 16, justifyContent: "center", ...top ? { top: 16 } : { bottom: aboveTabBar ? 92 : 24 } }, children: /* @__PURE__ */ jsxs(Box, { style: { alignItems: "center", gap: 12, padding: "12px 16px", borderRadius: 12, backgroundColor: theme.ink, color: theme.onPrimary, fontSize: 14, boxShadow: "0 6px 20px rgba(0,0,0,0.22)", maxWidth: 420 }, children: [
|
|
1697
|
+
p.icon ? /* @__PURE__ */ jsx(IconGlyph, { name: p.icon, size: 18, color: theme.onPrimary }) : null,
|
|
1698
|
+
/* @__PURE__ */ jsx(Box, { style: { flexShrink: 1 }, children: p.text ?? "" }),
|
|
1699
|
+
p.action ? /* @__PURE__ */ jsx(Box, { style: { fontWeight: 700, marginLeft: 8, flexShrink: 0 }, children: p.action }) : null
|
|
1700
|
+
] }) });
|
|
1701
|
+
}
|
|
1346
1702
|
var registry = {
|
|
1347
1703
|
Board,
|
|
1348
1704
|
Screen,
|
|
@@ -1350,11 +1706,14 @@ var registry = {
|
|
|
1350
1706
|
Stack,
|
|
1351
1707
|
Grid,
|
|
1352
1708
|
Card,
|
|
1709
|
+
Accordion,
|
|
1353
1710
|
Divider,
|
|
1354
1711
|
Spacer,
|
|
1355
1712
|
Heading,
|
|
1356
1713
|
Text,
|
|
1714
|
+
Bullets,
|
|
1357
1715
|
Image,
|
|
1716
|
+
Chart,
|
|
1358
1717
|
Icon,
|
|
1359
1718
|
Avatar,
|
|
1360
1719
|
Badge,
|
|
@@ -1364,14 +1723,19 @@ var registry = {
|
|
|
1364
1723
|
Radio,
|
|
1365
1724
|
Toggle,
|
|
1366
1725
|
Select,
|
|
1726
|
+
Slider,
|
|
1727
|
+
Progress,
|
|
1728
|
+
Calendar,
|
|
1367
1729
|
NavBar,
|
|
1368
1730
|
TabBar,
|
|
1369
1731
|
Tabs,
|
|
1732
|
+
Pagination,
|
|
1370
1733
|
List,
|
|
1371
1734
|
ListItem,
|
|
1372
1735
|
Table,
|
|
1373
1736
|
Modal,
|
|
1374
|
-
Drawer
|
|
1737
|
+
Drawer,
|
|
1738
|
+
Toast
|
|
1375
1739
|
};
|
|
1376
1740
|
|
|
1377
1741
|
// src/text.ts
|
|
@@ -1383,6 +1747,10 @@ var PRIMARY_PROP = {
|
|
|
1383
1747
|
Heading: "text",
|
|
1384
1748
|
Text: "text",
|
|
1385
1749
|
Image: "label",
|
|
1750
|
+
Chart: "title",
|
|
1751
|
+
Slider: "label",
|
|
1752
|
+
Progress: "label",
|
|
1753
|
+
Calendar: "month",
|
|
1386
1754
|
Icon: "name",
|
|
1387
1755
|
Avatar: "initials",
|
|
1388
1756
|
Badge: "label",
|
|
@@ -1395,7 +1763,9 @@ var PRIMARY_PROP = {
|
|
|
1395
1763
|
NavBar: "title",
|
|
1396
1764
|
ListItem: "title",
|
|
1397
1765
|
Modal: "title",
|
|
1398
|
-
Drawer: "title"
|
|
1766
|
+
Drawer: "title",
|
|
1767
|
+
Accordion: "title",
|
|
1768
|
+
Toast: "text"
|
|
1399
1769
|
};
|
|
1400
1770
|
function unwrap(t) {
|
|
1401
1771
|
let cur = t;
|
|
@@ -1455,6 +1825,18 @@ function belongsElsewhere(component, word, asProp) {
|
|
|
1455
1825
|
}
|
|
1456
1826
|
return msg;
|
|
1457
1827
|
}
|
|
1828
|
+
var OBJECT_SWITCHES = /* @__PURE__ */ new Set();
|
|
1829
|
+
for (const info of COMPONENTS.values()) {
|
|
1830
|
+
for (const schema of Object.values(info.props)) {
|
|
1831
|
+
const s = unwrap(schema);
|
|
1832
|
+
if (s?.def?.type !== "array") continue;
|
|
1833
|
+
const el = unwrap(s.def.element);
|
|
1834
|
+
for (const option of el?.def?.type === "union" ? el.def.options.map(unwrap) : [el]) {
|
|
1835
|
+
if (option?.def?.type !== "object") continue;
|
|
1836
|
+
for (const [key, value] of Object.entries(option.shape)) if (unwrap(value)?.def?.type === "boolean") OBJECT_SWITCHES.add(key);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1458
1840
|
function bareWords(component) {
|
|
1459
1841
|
const info = COMPONENTS.get(component.toLowerCase());
|
|
1460
1842
|
if (!info) throw new Error(`unknown component ${component}`);
|
|
@@ -1571,15 +1953,31 @@ function parseValue(c) {
|
|
|
1571
1953
|
}
|
|
1572
1954
|
if (t.value === "{") {
|
|
1573
1955
|
const obj = {};
|
|
1956
|
+
let lastKey = null;
|
|
1574
1957
|
while (!c.isSym("}")) {
|
|
1575
1958
|
c.skipCommas();
|
|
1576
1959
|
if (c.isSym("}")) break;
|
|
1577
1960
|
if (c.done()) throw new Error("unclosed {");
|
|
1578
1961
|
const k = c.next();
|
|
1579
1962
|
if (!k || k.kind === "sym") throw new Error("expected key in {\u2026}");
|
|
1963
|
+
if ((k.kind === "word" || k.kind === "num") && !(c.isSym("=") || c.isSym(":"))) {
|
|
1964
|
+
if (k.kind === "word" && OBJECT_SWITCHES.has(k.value)) {
|
|
1965
|
+
obj[k.value] = true;
|
|
1966
|
+
lastKey = null;
|
|
1967
|
+
continue;
|
|
1968
|
+
}
|
|
1969
|
+
if (lastKey) {
|
|
1970
|
+
obj[lastKey] = `${obj[lastKey]} ${k.value}`;
|
|
1971
|
+
continue;
|
|
1972
|
+
}
|
|
1973
|
+
obj[k.value] = true;
|
|
1974
|
+
continue;
|
|
1975
|
+
}
|
|
1580
1976
|
if (!(c.isSym("=") || c.isSym(":"))) throw new Error(`expected = after ${k.value}`);
|
|
1581
1977
|
c.next();
|
|
1978
|
+
const unquoted = c.peek()?.kind === "word" || c.peek()?.kind === "num";
|
|
1582
1979
|
obj[k.value] = parseValue(c);
|
|
1980
|
+
lastKey = unquoted ? k.value : null;
|
|
1583
1981
|
}
|
|
1584
1982
|
c.next();
|
|
1585
1983
|
return obj;
|
|
@@ -1883,7 +2281,7 @@ function checkSpec(spec) {
|
|
|
1883
2281
|
if (el.type === "Screen" && !(root.children ?? []).includes(id)) {
|
|
1884
2282
|
issues.push(`${id}: Screen must be a direct child of the Board`);
|
|
1885
2283
|
}
|
|
1886
|
-
if (el.type === "Modal" || el.type === "Drawer") {
|
|
2284
|
+
if (el.type === "Modal" || el.type === "Drawer" || el.type === "Toast") {
|
|
1887
2285
|
const parent = Object.entries(spec.elements).find(([, p]) => p.children?.includes(id));
|
|
1888
2286
|
if (parent && parent[1].type !== "Screen") {
|
|
1889
2287
|
issues.push(`${id}: ${el.type} must be a direct child of a Screen (found in ${parent[1].type} "${parent[0]}")`);
|
|
@@ -1897,6 +2295,33 @@ function checkSpec(spec) {
|
|
|
1897
2295
|
if (p.trailingIcon && trailing !== "icon") conflict("trailingIcon", "trailing=icon", "trailing", trailing);
|
|
1898
2296
|
if (p.trailingText && trailing !== "text" && trailing !== "badge") conflict("trailingText", "trailing=text or badge", "trailing", trailing);
|
|
1899
2297
|
}
|
|
2298
|
+
{
|
|
2299
|
+
const p = el.props ?? {};
|
|
2300
|
+
const say = (msg) => issues.push(`${id}: ${msg}`);
|
|
2301
|
+
const backwards = (r) => Array.isArray(r) && r.length === 2 && r[0] > r[1];
|
|
2302
|
+
if (el.type === "Progress") {
|
|
2303
|
+
if (p.step != null && p.steps == null) say(`step only shows with steps (e.g. steps=4 step=${p.step})`);
|
|
2304
|
+
if (p.step != null && p.steps != null && p.step > p.steps) say(`step=${p.step} is past the last step (steps=${p.steps})`);
|
|
2305
|
+
if (p.steps != null && p.shape === "circle") say("a stepper (steps) can't also be a circle; remove one of them");
|
|
2306
|
+
}
|
|
2307
|
+
if (el.type === "Pagination" && p.current != null && p.pages != null && p.current > p.pages) {
|
|
2308
|
+
say(`current=${p.current} is past the last page (pages=${p.pages})`);
|
|
2309
|
+
}
|
|
2310
|
+
if (el.type === "Slider" && backwards(p.range)) say(`range=[${p.range.join(", ")}] goes backwards; write the smaller number first`);
|
|
2311
|
+
if (el.type === "Slider" && p.range && p.value != null) say("a slider has value (one handle) or range (two), not both");
|
|
2312
|
+
if (el.type === "Calendar") {
|
|
2313
|
+
const { days } = monthGrid(p.month);
|
|
2314
|
+
const all = [p.selected, ...p.range ?? [], ...p.marked ?? []].filter((d) => typeof d === "number");
|
|
2315
|
+
const late = all.find((d) => d > days);
|
|
2316
|
+
if (late) say(`day ${late} isn't in ${p.month ?? "the month"} (it has ${days} days)`);
|
|
2317
|
+
if (backwards(p.range)) say(`range=[${p.range.join(", ")}] goes backwards; write the earlier day first`);
|
|
2318
|
+
if (p.range && p.selected != null) say("a calendar has selected (one day) or range (several), not both");
|
|
2319
|
+
}
|
|
2320
|
+
if (el.type === "Input" && p.type === "code" && p.value != null && String(p.value).length > (p.digits ?? 6)) {
|
|
2321
|
+
say(`value has ${String(p.value).length} characters but the code has ${p.digits ?? 6} boxes (digits)`);
|
|
2322
|
+
}
|
|
2323
|
+
if (el.type === "Input" && p.digits != null && p.type !== "code") say("digits only applies to type=code");
|
|
2324
|
+
}
|
|
1900
2325
|
if (el.type === "Table") {
|
|
1901
2326
|
const { columns, data } = el.props ?? {};
|
|
1902
2327
|
if (Array.isArray(columns) && Array.isArray(data)) {
|
|
@@ -2032,7 +2457,7 @@ function fmt(v) {
|
|
|
2032
2457
|
if (typeof v === "number" || typeof v === "boolean" || v === null) return String(v);
|
|
2033
2458
|
if (Array.isArray(v)) return `[${v.map(fmt).join(", ")}]`;
|
|
2034
2459
|
if (typeof v === "object") {
|
|
2035
|
-
return `{${Object.entries(v).map(([k, x]) => `${k}=${fmt(x)}`).join(" ")}}`;
|
|
2460
|
+
return `{${Object.entries(v).map(([k, x]) => x === true ? k : `${k}=${fmt(x)}`).join(" ")}}`;
|
|
2036
2461
|
}
|
|
2037
2462
|
return JSON.stringify(v);
|
|
2038
2463
|
}
|
|
@@ -2146,6 +2571,8 @@ function describeType(t) {
|
|
|
2146
2571
|
return s.def.options.map(describeType).join(" | ");
|
|
2147
2572
|
case "array":
|
|
2148
2573
|
return `array of ${describeType(s.def.element)}`;
|
|
2574
|
+
case "tuple":
|
|
2575
|
+
return `[${s.def.items.map(describeType).join(", ")}]`;
|
|
2149
2576
|
case "object":
|
|
2150
2577
|
return `{ ${Object.entries(s.shape).map(([k, v]) => `${k}: ${describeType(v)}`).join(", ")} }`;
|
|
2151
2578
|
default:
|
|
@@ -2181,7 +2608,7 @@ var RULES = `## Rules
|
|
|
2181
2608
|
2. Each Screen is one view of the product. Use several Screens to show several views or states.
|
|
2182
2609
|
3. A Screen lays out its children top to bottom. Use Stack (direction row or column) and Grid to arrange content.
|
|
2183
2610
|
4. NavBar is pinned to the top of its Screen and TabBar to the bottom.
|
|
2184
|
-
5. Modal and
|
|
2611
|
+
5. Modal, Drawer and Toast are overlays. They must be direct children of a Screen.
|
|
2185
2612
|
6. This is a low-fidelity wireframe. Prefer placeholders (Image boxes, Text with lines) over invented copy unless the copy matters.
|
|
2186
2613
|
7. Only use the components and props listed below. All props are optional unless marked required.
|
|
2187
2614
|
8. Wireframes are grayscale. The only UI colors: Board accent (one color for primary buttons, checked controls, toggles, active tabs and ghost buttons), Badge tone (success, warning, danger) and Input error. Only add an accent if the request asks for color or a brand.`;
|