tsquare 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/dist/catalog.d.ts +192 -0
- package/dist/chunks/{chunk-X46EDSCQ.js → chunk-2IDCJVKK.js} +376 -5
- package/dist/cli.js +1 -1
- package/dist/components.d.ts +20 -1
- package/dist/index.js +1 -1
- package/dist/playground/index.js +52 -7
- package/examples/dashboard.tsq +6 -1
- package/package.json +1 -1
- package/skill/tsquare/reference.md +42 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### New components
|
|
6
|
+
|
|
7
|
+
```tsquare
|
|
8
|
+
chart line "Revenue" # line | bar | area | pie | donut, a placeholder shape
|
|
9
|
+
calendar "October 2026" range=[12, 18] marked=[3, 9]
|
|
10
|
+
input "Check-in" type=date value="Oct 14, 2026" # calendar icon
|
|
11
|
+
input "Verification code" type=code value="4821" # one box per digit (digits=6)
|
|
12
|
+
slider "Price" range=[50, 400] # any amounts; the track scales
|
|
13
|
+
progress "Uploading" value=40 # also: progress circle, progress steps=4 step=2
|
|
14
|
+
pagination pages=12 current=3
|
|
15
|
+
bullets items=[Fast setup, No credit card] # also: numbered, icon=check
|
|
16
|
+
bullets icon=check items=[Boards, {label="SSO" icon=x muted}]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- **Charts are placeholders:** they show the kind of chart, not data.
|
|
20
|
+
- **The calendar draws the real month** from its title ("October 2026" starts on a Thursday).
|
|
21
|
+
- **Bullets** take any Lucide icon. One item can be an object that changes its icon or greys it out (`muted`).
|
|
22
|
+
- **Inside `{…}`:**
|
|
23
|
+
- a bare word turns an option on (`{label="SSO" muted}`);
|
|
24
|
+
- an unquoted value can contain spaces (`{label=Custom domain icon=x}`).
|
|
25
|
+
- **Clear errors for values that contradict each other:** a step past the last step, a page past the last page, a day that isn't in the month, a range written backwards.
|
|
26
|
+
- **The accent color** also fills sliders, progress, completed steps, and the selected day and page. Charts stay gray.
|
|
27
|
+
- **The prompt** grew by about 480 tokens (2,670 → 3,150).
|
|
28
|
+
|
|
29
|
+
### Measured
|
|
30
|
+
|
|
31
|
+
- **The new components:** 7 requests that describe a screen without naming components, two runs each.
|
|
32
|
+
- Sonnet: valid 14 of 14, and picked the right components every time (32 of 32 checks).
|
|
33
|
+
- Haiku: valid 12 of 14, 30 of 32 checks.
|
|
34
|
+
- **The original 20 requests, with the new prompt:** Sonnet 100%, Haiku 65%, within its usual range. None of Haiku's failures involve the new components.
|
|
35
|
+
|
|
3
36
|
## 0.3.2
|
|
4
37
|
|
|
5
38
|
- **`grow` on Select**, like Input: `select "State" grow` fills the rest of a row. Sonnet wrote it in 5 of 9 eval runs, its only remaining mistake; with it, every stored Sonnet text output is valid.
|
package/dist/catalog.d.ts
CHANGED
|
@@ -192,6 +192,22 @@ export declare const componentDefinitions: {
|
|
|
192
192
|
lines: number;
|
|
193
193
|
};
|
|
194
194
|
};
|
|
195
|
+
Bullets: {
|
|
196
|
+
props: z.ZodObject<{
|
|
197
|
+
items: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
198
|
+
label: z.ZodString;
|
|
199
|
+
icon: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
200
|
+
muted: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
201
|
+
}, z.core.$strict>]>>;
|
|
202
|
+
numbered: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
203
|
+
icon: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
204
|
+
}, z.core.$strip>;
|
|
205
|
+
slots: never[];
|
|
206
|
+
description: string;
|
|
207
|
+
example: {
|
|
208
|
+
items: string[];
|
|
209
|
+
};
|
|
210
|
+
};
|
|
195
211
|
Image: {
|
|
196
212
|
props: z.ZodObject<{
|
|
197
213
|
height: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
@@ -206,6 +222,26 @@ export declare const componentDefinitions: {
|
|
|
206
222
|
label: string;
|
|
207
223
|
};
|
|
208
224
|
};
|
|
225
|
+
Chart: {
|
|
226
|
+
props: z.ZodObject<{
|
|
227
|
+
kind: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
228
|
+
area: "area";
|
|
229
|
+
bar: "bar";
|
|
230
|
+
donut: "donut";
|
|
231
|
+
line: "line";
|
|
232
|
+
pie: "pie";
|
|
233
|
+
}>>>;
|
|
234
|
+
title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
235
|
+
height: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
236
|
+
width: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>>;
|
|
237
|
+
}, z.core.$strip>;
|
|
238
|
+
slots: never[];
|
|
239
|
+
description: string;
|
|
240
|
+
example: {
|
|
241
|
+
kind: string;
|
|
242
|
+
title: string;
|
|
243
|
+
};
|
|
244
|
+
};
|
|
209
245
|
Icon: {
|
|
210
246
|
props: z.ZodObject<{
|
|
211
247
|
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -282,11 +318,14 @@ export declare const componentDefinitions: {
|
|
|
282
318
|
placeholder: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
283
319
|
value: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
284
320
|
type: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
321
|
+
code: "code";
|
|
322
|
+
date: "date";
|
|
285
323
|
email: "email";
|
|
286
324
|
password: "password";
|
|
287
325
|
search: "search";
|
|
288
326
|
text: "text";
|
|
289
327
|
}>>>;
|
|
328
|
+
digits: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
290
329
|
multiline: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
291
330
|
helper: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
292
331
|
error: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
@@ -351,6 +390,51 @@ export declare const componentDefinitions: {
|
|
|
351
390
|
value: string;
|
|
352
391
|
};
|
|
353
392
|
};
|
|
393
|
+
Slider: {
|
|
394
|
+
props: z.ZodObject<{
|
|
395
|
+
label: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
396
|
+
value: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
397
|
+
range: z.ZodOptional<z.ZodNullable<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>;
|
|
398
|
+
}, z.core.$strip>;
|
|
399
|
+
slots: never[];
|
|
400
|
+
description: string;
|
|
401
|
+
example: {
|
|
402
|
+
label: string;
|
|
403
|
+
value: number;
|
|
404
|
+
};
|
|
405
|
+
};
|
|
406
|
+
Progress: {
|
|
407
|
+
props: z.ZodObject<{
|
|
408
|
+
label: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
409
|
+
value: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
410
|
+
shape: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
411
|
+
bar: "bar";
|
|
412
|
+
circle: "circle";
|
|
413
|
+
}>>>;
|
|
414
|
+
steps: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
415
|
+
step: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
416
|
+
}, z.core.$strip>;
|
|
417
|
+
slots: never[];
|
|
418
|
+
description: string;
|
|
419
|
+
example: {
|
|
420
|
+
label: string;
|
|
421
|
+
value: number;
|
|
422
|
+
};
|
|
423
|
+
};
|
|
424
|
+
Calendar: {
|
|
425
|
+
props: z.ZodObject<{
|
|
426
|
+
month: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
427
|
+
selected: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
428
|
+
range: z.ZodOptional<z.ZodNullable<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>;
|
|
429
|
+
marked: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodNumber>>>;
|
|
430
|
+
}, z.core.$strip>;
|
|
431
|
+
slots: never[];
|
|
432
|
+
description: string;
|
|
433
|
+
example: {
|
|
434
|
+
month: string;
|
|
435
|
+
selected: number;
|
|
436
|
+
};
|
|
437
|
+
};
|
|
354
438
|
NavBar: {
|
|
355
439
|
props: z.ZodObject<{
|
|
356
440
|
title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -405,6 +489,18 @@ export declare const componentDefinitions: {
|
|
|
405
489
|
active: number;
|
|
406
490
|
};
|
|
407
491
|
};
|
|
492
|
+
Pagination: {
|
|
493
|
+
props: z.ZodObject<{
|
|
494
|
+
pages: z.ZodNumber;
|
|
495
|
+
current: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
496
|
+
}, z.core.$strip>;
|
|
497
|
+
slots: never[];
|
|
498
|
+
description: string;
|
|
499
|
+
example: {
|
|
500
|
+
pages: number;
|
|
501
|
+
current: number;
|
|
502
|
+
};
|
|
503
|
+
};
|
|
408
504
|
List: {
|
|
409
505
|
props: z.ZodObject<{
|
|
410
506
|
dividers: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
@@ -716,6 +812,22 @@ export declare const catalog: import("@json-render/core").Catalog<{
|
|
|
716
812
|
lines: number;
|
|
717
813
|
};
|
|
718
814
|
};
|
|
815
|
+
Bullets: {
|
|
816
|
+
props: z.ZodObject<{
|
|
817
|
+
items: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
818
|
+
label: z.ZodString;
|
|
819
|
+
icon: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
820
|
+
muted: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
821
|
+
}, z.core.$strict>]>>;
|
|
822
|
+
numbered: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
823
|
+
icon: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
824
|
+
}, z.core.$strip>;
|
|
825
|
+
slots: never[];
|
|
826
|
+
description: string;
|
|
827
|
+
example: {
|
|
828
|
+
items: string[];
|
|
829
|
+
};
|
|
830
|
+
};
|
|
719
831
|
Image: {
|
|
720
832
|
props: z.ZodObject<{
|
|
721
833
|
height: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
@@ -730,6 +842,26 @@ export declare const catalog: import("@json-render/core").Catalog<{
|
|
|
730
842
|
label: string;
|
|
731
843
|
};
|
|
732
844
|
};
|
|
845
|
+
Chart: {
|
|
846
|
+
props: z.ZodObject<{
|
|
847
|
+
kind: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
848
|
+
area: "area";
|
|
849
|
+
bar: "bar";
|
|
850
|
+
donut: "donut";
|
|
851
|
+
line: "line";
|
|
852
|
+
pie: "pie";
|
|
853
|
+
}>>>;
|
|
854
|
+
title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
855
|
+
height: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
856
|
+
width: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>>;
|
|
857
|
+
}, z.core.$strip>;
|
|
858
|
+
slots: never[];
|
|
859
|
+
description: string;
|
|
860
|
+
example: {
|
|
861
|
+
kind: string;
|
|
862
|
+
title: string;
|
|
863
|
+
};
|
|
864
|
+
};
|
|
733
865
|
Icon: {
|
|
734
866
|
props: z.ZodObject<{
|
|
735
867
|
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -806,11 +938,14 @@ export declare const catalog: import("@json-render/core").Catalog<{
|
|
|
806
938
|
placeholder: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
807
939
|
value: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
808
940
|
type: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
941
|
+
code: "code";
|
|
942
|
+
date: "date";
|
|
809
943
|
email: "email";
|
|
810
944
|
password: "password";
|
|
811
945
|
search: "search";
|
|
812
946
|
text: "text";
|
|
813
947
|
}>>>;
|
|
948
|
+
digits: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
814
949
|
multiline: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
815
950
|
helper: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
816
951
|
error: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
@@ -875,6 +1010,51 @@ export declare const catalog: import("@json-render/core").Catalog<{
|
|
|
875
1010
|
value: string;
|
|
876
1011
|
};
|
|
877
1012
|
};
|
|
1013
|
+
Slider: {
|
|
1014
|
+
props: z.ZodObject<{
|
|
1015
|
+
label: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1016
|
+
value: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
1017
|
+
range: z.ZodOptional<z.ZodNullable<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>;
|
|
1018
|
+
}, z.core.$strip>;
|
|
1019
|
+
slots: never[];
|
|
1020
|
+
description: string;
|
|
1021
|
+
example: {
|
|
1022
|
+
label: string;
|
|
1023
|
+
value: number;
|
|
1024
|
+
};
|
|
1025
|
+
};
|
|
1026
|
+
Progress: {
|
|
1027
|
+
props: z.ZodObject<{
|
|
1028
|
+
label: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1029
|
+
value: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
1030
|
+
shape: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
1031
|
+
bar: "bar";
|
|
1032
|
+
circle: "circle";
|
|
1033
|
+
}>>>;
|
|
1034
|
+
steps: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
1035
|
+
step: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
1036
|
+
}, z.core.$strip>;
|
|
1037
|
+
slots: never[];
|
|
1038
|
+
description: string;
|
|
1039
|
+
example: {
|
|
1040
|
+
label: string;
|
|
1041
|
+
value: number;
|
|
1042
|
+
};
|
|
1043
|
+
};
|
|
1044
|
+
Calendar: {
|
|
1045
|
+
props: z.ZodObject<{
|
|
1046
|
+
month: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1047
|
+
selected: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
1048
|
+
range: z.ZodOptional<z.ZodNullable<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>;
|
|
1049
|
+
marked: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodNumber>>>;
|
|
1050
|
+
}, z.core.$strip>;
|
|
1051
|
+
slots: never[];
|
|
1052
|
+
description: string;
|
|
1053
|
+
example: {
|
|
1054
|
+
month: string;
|
|
1055
|
+
selected: number;
|
|
1056
|
+
};
|
|
1057
|
+
};
|
|
878
1058
|
NavBar: {
|
|
879
1059
|
props: z.ZodObject<{
|
|
880
1060
|
title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -929,6 +1109,18 @@ export declare const catalog: import("@json-render/core").Catalog<{
|
|
|
929
1109
|
active: number;
|
|
930
1110
|
};
|
|
931
1111
|
};
|
|
1112
|
+
Pagination: {
|
|
1113
|
+
props: z.ZodObject<{
|
|
1114
|
+
pages: z.ZodNumber;
|
|
1115
|
+
current: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
1116
|
+
}, z.core.$strip>;
|
|
1117
|
+
slots: never[];
|
|
1118
|
+
description: string;
|
|
1119
|
+
example: {
|
|
1120
|
+
pages: number;
|
|
1121
|
+
current: number;
|
|
1122
|
+
};
|
|
1123
|
+
};
|
|
932
1124
|
List: {
|
|
933
1125
|
props: z.ZodObject<{
|
|
934
1126
|
dividers: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
@@ -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"],
|
|
@@ -341,6 +348,16 @@ var componentDefinitions = {
|
|
|
341
348
|
description: "Body text, or placeholder lines when `lines` is set and `text` is not.",
|
|
342
349
|
example: { lines: 3 }
|
|
343
350
|
},
|
|
351
|
+
Bullets: {
|
|
352
|
+
props: z.object({
|
|
353
|
+
items: z.array(bulletItem).describe("Text, or {label icon muted} to change one line, e.g. {label=SSO icon=x muted}"),
|
|
354
|
+
numbered: o(z.boolean()).describe("1. 2. 3. instead of dots"),
|
|
355
|
+
icon: o(iconName).describe("A Lucide icon instead of dots, e.g. check for a feature list")
|
|
356
|
+
}),
|
|
357
|
+
slots: [],
|
|
358
|
+
description: "Bulleted, numbered or icon list of short lines.",
|
|
359
|
+
example: { items: ["Fast setup", "No credit card"] }
|
|
360
|
+
},
|
|
344
361
|
Image: {
|
|
345
362
|
props: z.object({
|
|
346
363
|
height: o(z.number()),
|
|
@@ -352,6 +369,17 @@ var componentDefinitions = {
|
|
|
352
369
|
description: "Image placeholder: a box with an X through it.",
|
|
353
370
|
example: { height: 180, label: "Hero photo" }
|
|
354
371
|
},
|
|
372
|
+
Chart: {
|
|
373
|
+
props: z.object({
|
|
374
|
+
kind: o(z.enum(["line", "bar", "area", "pie", "donut"])).describe("Default line"),
|
|
375
|
+
title: o(z.string()),
|
|
376
|
+
height: o(z.number()).describe("Default 180"),
|
|
377
|
+
width: o(z.union([z.number(), z.string()])).describe("Default fills the container width")
|
|
378
|
+
}),
|
|
379
|
+
slots: [],
|
|
380
|
+
description: "Chart placeholder: a generic line, bar, area, pie or donut shape, no data.",
|
|
381
|
+
example: { kind: "bar", title: "Signups" }
|
|
382
|
+
},
|
|
355
383
|
Icon: {
|
|
356
384
|
props: z.object({
|
|
357
385
|
name: o(iconName),
|
|
@@ -399,7 +427,8 @@ var componentDefinitions = {
|
|
|
399
427
|
label: o(z.string()),
|
|
400
428
|
placeholder: o(z.string()),
|
|
401
429
|
value: o(z.string()),
|
|
402
|
-
type: o(z.enum(["text", "password", "search", "email"])),
|
|
430
|
+
type: o(z.enum(["text", "password", "search", "email", "date", "code"])).describe("date shows a calendar icon; code draws one box per digit"),
|
|
431
|
+
digits: o(z.number().int().min(2).max(10)).describe("Boxes for type=code, default 6"),
|
|
403
432
|
multiline: o(z.number().int().min(1)).describe("Number of rows; 2 or more makes a textarea"),
|
|
404
433
|
helper: o(z.string()),
|
|
405
434
|
error: o(z.boolean()).describe("Validation error: red border and red helper text"),
|
|
@@ -440,6 +469,39 @@ var componentDefinitions = {
|
|
|
440
469
|
description: "Dropdown field (closed state).",
|
|
441
470
|
example: { label: "Country", value: "United States" }
|
|
442
471
|
},
|
|
472
|
+
Slider: {
|
|
473
|
+
props: z.object({
|
|
474
|
+
label: o(z.string()),
|
|
475
|
+
value: o(z.number().min(0)).describe("Handle position: 0 to 100, or any amount (the track scales to fit)"),
|
|
476
|
+
range: o(z.tuple([z.number().min(0), z.number().min(0)])).describe("Two handles instead, e.g. [20, 80] or [50, 400]")
|
|
477
|
+
}),
|
|
478
|
+
slots: [],
|
|
479
|
+
description: "Slider with one handle, or two with `range`.",
|
|
480
|
+
example: { label: "Volume", value: 30 }
|
|
481
|
+
},
|
|
482
|
+
Progress: {
|
|
483
|
+
props: z.object({
|
|
484
|
+
label: o(z.string()),
|
|
485
|
+
value: o(percent).describe("Percent done"),
|
|
486
|
+
shape: o(z.enum(["bar", "circle"])).describe("Default bar; circle is a ring with the percent inside"),
|
|
487
|
+
steps: o(z.number().int().min(2).max(10)).describe("Draw a stepper with this many steps instead"),
|
|
488
|
+
step: o(z.number().int().min(1)).describe("The current step, with steps")
|
|
489
|
+
}),
|
|
490
|
+
slots: [],
|
|
491
|
+
description: "Progress bar, ring (circle), or stepper (steps).",
|
|
492
|
+
example: { label: "Uploading", value: 40 }
|
|
493
|
+
},
|
|
494
|
+
Calendar: {
|
|
495
|
+
props: z.object({
|
|
496
|
+
month: o(z.string()).describe('Month and year, e.g. "October 2026"; the days match that month'),
|
|
497
|
+
selected: o(day),
|
|
498
|
+
range: o(z.tuple([day, day])).describe("Selected days, e.g. [12, 18]"),
|
|
499
|
+
marked: o(z.array(day)).describe("Days with a dot, e.g. events")
|
|
500
|
+
}),
|
|
501
|
+
slots: [],
|
|
502
|
+
description: "Month calendar.",
|
|
503
|
+
example: { month: "October 2026", selected: 14 }
|
|
504
|
+
},
|
|
443
505
|
// ── Navigation & data ─────────────────────────────────────────────────
|
|
444
506
|
NavBar: {
|
|
445
507
|
props: z.object({
|
|
@@ -470,6 +532,15 @@ var componentDefinitions = {
|
|
|
470
532
|
description: "In-page tabs with an underline on the active one.",
|
|
471
533
|
example: { items: ["Overview", "Activity", "Settings"], active: 0 }
|
|
472
534
|
},
|
|
535
|
+
Pagination: {
|
|
536
|
+
props: z.object({
|
|
537
|
+
pages: z.number().int().min(1),
|
|
538
|
+
current: o(z.number().int().min(1)).describe("Default 1")
|
|
539
|
+
}),
|
|
540
|
+
slots: [],
|
|
541
|
+
description: "Page numbers with previous and next.",
|
|
542
|
+
example: { pages: 12, current: 3 }
|
|
543
|
+
},
|
|
473
544
|
List: {
|
|
474
545
|
props: z.object({ dividers: o(z.boolean()), grow: o(z.boolean()) }),
|
|
475
546
|
slots: ["default"],
|
|
@@ -876,6 +947,20 @@ function Text({ element }) {
|
|
|
876
947
|
}
|
|
877
948
|
);
|
|
878
949
|
}
|
|
950
|
+
function Bullets({ element }) {
|
|
951
|
+
const p = element.props;
|
|
952
|
+
const items = p.items ?? [];
|
|
953
|
+
return /* @__PURE__ */ jsx(Box, { style: { flexDirection: "column", gap: 8 }, children: items.map((item, i) => {
|
|
954
|
+
const { label, icon, muted } = typeof item === "string" ? { label: item, icon: null, muted: false } : item;
|
|
955
|
+
const color = muted ? theme.muted : theme.text;
|
|
956
|
+
const glyph = icon ?? p.icon;
|
|
957
|
+
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 } }) });
|
|
958
|
+
return /* @__PURE__ */ jsxs(Box, { style: { gap: 10, alignItems: glyph ? "center" : "flex-start", fontSize: 15, lineHeight: 1.45, color }, children: [
|
|
959
|
+
marker,
|
|
960
|
+
/* @__PURE__ */ jsx(Box, { style: { flexShrink: 1 }, children: label })
|
|
961
|
+
] }, i);
|
|
962
|
+
}) });
|
|
963
|
+
}
|
|
879
964
|
function Image({ element, dir }) {
|
|
880
965
|
const p = element.props;
|
|
881
966
|
const h = p.height ?? 160;
|
|
@@ -931,6 +1016,48 @@ function Icon({ element, dir, aligned }) {
|
|
|
931
1016
|
const p = element.props;
|
|
932
1017
|
return /* @__PURE__ */ jsx(Box, { style: { flexShrink: 0, ...hug(dir, aligned) }, children: /* @__PURE__ */ jsx(IconGlyph, { name: p.name, size: p.size ?? 20 }) });
|
|
933
1018
|
}
|
|
1019
|
+
var CHART_LINE = [62, 48, 55, 30, 38, 22, 34, 12];
|
|
1020
|
+
var CHART_BARS = [55, 72, 40, 86, 64, 48, 78];
|
|
1021
|
+
function Chart({ element, dir }) {
|
|
1022
|
+
const p = element.props;
|
|
1023
|
+
const kind = p.kind ?? "line";
|
|
1024
|
+
const h = p.height ?? 180;
|
|
1025
|
+
const w = p.width ?? (dir === "row" ? void 0 : "100%");
|
|
1026
|
+
const round = kind === "pie" || kind === "donut";
|
|
1027
|
+
const plot = (() => {
|
|
1028
|
+
if (round) {
|
|
1029
|
+
const slice = (from, to, fill, k) => {
|
|
1030
|
+
const a0 = from / 100 * 2 * Math.PI - Math.PI / 2, a1 = to / 100 * 2 * Math.PI - Math.PI / 2;
|
|
1031
|
+
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);
|
|
1032
|
+
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);
|
|
1033
|
+
};
|
|
1034
|
+
return /* @__PURE__ */ jsxs("svg", { width: h - 32, height: h - 32, viewBox: "0 0 100 100", children: [
|
|
1035
|
+
slice(0, 45, theme.lineStrong, 0),
|
|
1036
|
+
slice(45, 75, theme.line, 1),
|
|
1037
|
+
slice(75, 100, theme.fill2, 2),
|
|
1038
|
+
kind === "donut" ? /* @__PURE__ */ jsx("circle", { cx: "50", cy: "50", r: "26", fill: theme.paper }) : null
|
|
1039
|
+
] });
|
|
1040
|
+
}
|
|
1041
|
+
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));
|
|
1042
|
+
if (kind === "bar") {
|
|
1043
|
+
const bw = 300 / CHART_BARS.length;
|
|
1044
|
+
return /* @__PURE__ */ jsxs("svg", { width: "100%", height: "100%", viewBox: "0 0 300 100", preserveAspectRatio: "none", children: [
|
|
1045
|
+
grid,
|
|
1046
|
+
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))
|
|
1047
|
+
] });
|
|
1048
|
+
}
|
|
1049
|
+
const pts = CHART_LINE.map((y, i) => `${i / (CHART_LINE.length - 1) * 300},${y}`).join(" ");
|
|
1050
|
+
return /* @__PURE__ */ jsxs("svg", { width: "100%", height: "100%", viewBox: "0 0 300 100", preserveAspectRatio: "none", children: [
|
|
1051
|
+
grid,
|
|
1052
|
+
kind === "area" ? /* @__PURE__ */ jsx("polygon", { points: `0,100 ${pts} 300,100`, fill: theme.fill2 }) : null,
|
|
1053
|
+
/* @__PURE__ */ jsx("polyline", { points: pts, fill: "none", stroke: theme.lineStrong, strokeWidth: "2.5", strokeLinejoin: "round", vectorEffect: "non-scaling-stroke" })
|
|
1054
|
+
] });
|
|
1055
|
+
})();
|
|
1056
|
+
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", gap: 10, width: w, height: h, flexShrink: 0, ...dir === "row" ? { flexGrow: 1, flexBasis: "0px" } : {} }, children: [
|
|
1057
|
+
p.title ? /* @__PURE__ */ jsx(Box, { style: { fontSize: 13, fontWeight: 600, color: theme.text }, children: p.title }) : null,
|
|
1058
|
+
/* @__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 })
|
|
1059
|
+
] });
|
|
1060
|
+
}
|
|
934
1061
|
function AvatarCircle({ initials, size = 40 }) {
|
|
935
1062
|
return /* @__PURE__ */ jsx(
|
|
936
1063
|
Box,
|
|
@@ -1036,8 +1163,35 @@ function Field({ children, height = 42, top = false, error = false }) {
|
|
|
1036
1163
|
}
|
|
1037
1164
|
);
|
|
1038
1165
|
}
|
|
1039
|
-
function
|
|
1166
|
+
function CodeBoxes({ digits, value, c = GRAY }) {
|
|
1167
|
+
return /* @__PURE__ */ jsx(Box, { style: { gap: 8 }, children: Array.from({ length: digits }, (_, i) => /* @__PURE__ */ jsx(
|
|
1168
|
+
Box,
|
|
1169
|
+
{
|
|
1170
|
+
style: {
|
|
1171
|
+
width: 44,
|
|
1172
|
+
height: 52,
|
|
1173
|
+
borderRadius: 8,
|
|
1174
|
+
border: `1.5px solid ${i === value.length ? c.accent : theme.lineStrong}`,
|
|
1175
|
+
alignItems: "center",
|
|
1176
|
+
justifyContent: "center",
|
|
1177
|
+
fontSize: 22,
|
|
1178
|
+
fontWeight: 600,
|
|
1179
|
+
color: theme.ink
|
|
1180
|
+
},
|
|
1181
|
+
children: value[i] ?? ""
|
|
1182
|
+
},
|
|
1183
|
+
i
|
|
1184
|
+
)) });
|
|
1185
|
+
}
|
|
1186
|
+
function Input({ element, dir, colors }) {
|
|
1040
1187
|
const p = element.props;
|
|
1188
|
+
if (p.type === "code") {
|
|
1189
|
+
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", gap: 6, ...hug(dir) }, children: [
|
|
1190
|
+
/* @__PURE__ */ jsx(FieldLabel, { text: p.label }),
|
|
1191
|
+
/* @__PURE__ */ jsx(CodeBoxes, { digits: p.digits ?? 6, value: String(p.value ?? "").slice(0, p.digits ?? 6), c: colors }),
|
|
1192
|
+
p.helper ? /* @__PURE__ */ jsx(Box, { style: { fontSize: 12, color: p.error ? TONES.danger : theme.muted }, children: p.helper }) : null
|
|
1193
|
+
] });
|
|
1194
|
+
}
|
|
1041
1195
|
const rows = p.multiline ?? 1;
|
|
1042
1196
|
const isPassword = p.type === "password";
|
|
1043
1197
|
const shown = p.value ?? null;
|
|
@@ -1047,7 +1201,8 @@ function Input({ element, dir }) {
|
|
|
1047
1201
|
/* @__PURE__ */ jsxs(Field, { height: rows > 1 ? rows * 22 + 20 : 42, top: rows > 1, error: !!p.error, children: [
|
|
1048
1202
|
p.type === "search" ? /* @__PURE__ */ jsx(IconGlyph, { name: "search", size: 18, color: theme.muted }) : null,
|
|
1049
1203
|
/* @__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
|
|
1204
|
+
isPassword ? /* @__PURE__ */ jsx(IconGlyph, { name: "eye-off", size: 18, color: theme.muted }) : null,
|
|
1205
|
+
p.type === "date" ? /* @__PURE__ */ jsx(IconGlyph, { name: "calendar", size: 18, color: theme.muted }) : null
|
|
1051
1206
|
] }),
|
|
1052
1207
|
p.helper ? /* @__PURE__ */ jsx(Box, { style: { fontSize: 12, color: p.error ? TONES.danger : theme.muted }, children: p.helper }) : null
|
|
1053
1208
|
] });
|
|
@@ -1118,6 +1273,133 @@ function Toggle({ element, colors }) {
|
|
|
1118
1273
|
/* @__PURE__ */ jsx(Switch, { on: p.on, c: colors })
|
|
1119
1274
|
] });
|
|
1120
1275
|
}
|
|
1276
|
+
function Slider({ element, colors: c = GRAY }) {
|
|
1277
|
+
const p = element.props;
|
|
1278
|
+
const [a, b] = p.range ?? [0, p.value ?? 50];
|
|
1279
|
+
const top = Math.max(a, b);
|
|
1280
|
+
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))));
|
|
1281
|
+
const from = a / max * 100, to = b / max * 100;
|
|
1282
|
+
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);
|
|
1283
|
+
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", gap: 10 }, children: [
|
|
1284
|
+
/* @__PURE__ */ jsx(FieldLabel, { text: p.label }),
|
|
1285
|
+
/* @__PURE__ */ jsxs(Box, { style: { position: "relative", height: 20, marginLeft: 10, marginRight: 10 }, children: [
|
|
1286
|
+
/* @__PURE__ */ jsx(Box, { style: { position: "absolute", left: 0, right: 0, top: 8, height: 4, borderRadius: 2, backgroundColor: theme.fill2 } }),
|
|
1287
|
+
/* @__PURE__ */ jsx(Box, { style: { position: "absolute", left: `${from}%`, width: `${to - from}%`, top: 8, height: 4, borderRadius: 2, backgroundColor: c.accent } }),
|
|
1288
|
+
p.range ? thumb(from, 0) : null,
|
|
1289
|
+
thumb(to, 1)
|
|
1290
|
+
] })
|
|
1291
|
+
] });
|
|
1292
|
+
}
|
|
1293
|
+
function Progress({ element, dir, aligned, colors: c = GRAY }) {
|
|
1294
|
+
const p = element.props;
|
|
1295
|
+
const value = Math.max(0, Math.min(100, p.value ?? 40));
|
|
1296
|
+
if (p.steps) {
|
|
1297
|
+
const current = Math.min(p.step ?? 1, p.steps);
|
|
1298
|
+
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", gap: 10 }, children: [
|
|
1299
|
+
/* @__PURE__ */ jsx(FieldLabel, { text: p.label }),
|
|
1300
|
+
/* @__PURE__ */ jsx(Box, { style: { alignItems: "center" }, children: Array.from({ length: p.steps }, (_, i) => {
|
|
1301
|
+
const n = i + 1;
|
|
1302
|
+
const done = n < current, now = n === current;
|
|
1303
|
+
const dot = /* @__PURE__ */ jsx(
|
|
1304
|
+
Box,
|
|
1305
|
+
{
|
|
1306
|
+
style: {
|
|
1307
|
+
width: 28,
|
|
1308
|
+
height: 28,
|
|
1309
|
+
flexShrink: 0,
|
|
1310
|
+
borderRadius: 14,
|
|
1311
|
+
alignItems: "center",
|
|
1312
|
+
justifyContent: "center",
|
|
1313
|
+
fontSize: 13,
|
|
1314
|
+
fontWeight: 600,
|
|
1315
|
+
backgroundColor: done ? c.accent : theme.paper,
|
|
1316
|
+
border: `2px solid ${done || now ? c.accent : theme.lineStrong}`,
|
|
1317
|
+
color: now ? c.accentText : theme.muted
|
|
1318
|
+
},
|
|
1319
|
+
children: done ? /* @__PURE__ */ jsx(IconGlyph, { name: "check", size: 14, color: c.onAccent, strokeWidth: 3 }) : String(n)
|
|
1320
|
+
},
|
|
1321
|
+
`d${i}`
|
|
1322
|
+
);
|
|
1323
|
+
return n === 1 ? dot : [/* @__PURE__ */ jsx(Box, { style: { flexGrow: 1, height: 2, backgroundColor: n <= current ? c.accent : theme.line } }, `l${i}`), dot];
|
|
1324
|
+
}) })
|
|
1325
|
+
] });
|
|
1326
|
+
}
|
|
1327
|
+
if (p.shape === "circle") {
|
|
1328
|
+
const r = 26, circumference = 2 * Math.PI * r;
|
|
1329
|
+
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", gap: 8, alignItems: "center", ...hug(dir, aligned), ...dir === "row" ? { alignSelf: "flex-start" } : {} }, children: [
|
|
1330
|
+
/* @__PURE__ */ jsxs(Box, { style: { position: "relative", width: 64, height: 64, alignItems: "center", justifyContent: "center" }, children: [
|
|
1331
|
+
/* @__PURE__ */ jsxs("svg", { width: "64", height: "64", viewBox: "0 0 64 64", style: { position: "absolute", top: 0, left: 0 }, children: [
|
|
1332
|
+
/* @__PURE__ */ jsx("circle", { cx: "32", cy: "32", r, fill: "none", stroke: theme.fill2, strokeWidth: "6" }),
|
|
1333
|
+
/* @__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)" })
|
|
1334
|
+
] }),
|
|
1335
|
+
/* @__PURE__ */ jsx(Box, { style: { fontSize: 14, fontWeight: 600, color: theme.ink }, children: `${Math.round(value)}%` })
|
|
1336
|
+
] }),
|
|
1337
|
+
p.label ? /* @__PURE__ */ jsx(Box, { style: { fontSize: 13, color: theme.muted }, children: p.label }) : null
|
|
1338
|
+
] });
|
|
1339
|
+
}
|
|
1340
|
+
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", gap: 8 }, children: [
|
|
1341
|
+
p.label ? /* @__PURE__ */ jsxs(Box, { style: { justifyContent: "space-between", fontSize: 13 }, children: [
|
|
1342
|
+
/* @__PURE__ */ jsx(Box, { style: { fontWeight: 600, color: theme.text }, children: p.label }),
|
|
1343
|
+
/* @__PURE__ */ jsx(Box, { style: { color: theme.muted }, children: `${Math.round(value)}%` })
|
|
1344
|
+
] }) : null,
|
|
1345
|
+
/* @__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 } }) })
|
|
1346
|
+
] });
|
|
1347
|
+
}
|
|
1348
|
+
var MONTHS = ["january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"];
|
|
1349
|
+
function monthGrid(month) {
|
|
1350
|
+
const m = String(month ?? "").toLowerCase().match(/([a-z]+)\s+(\d{4})/);
|
|
1351
|
+
const index = m ? MONTHS.findIndex((name) => name.startsWith(m[1].slice(0, 3))) : -1;
|
|
1352
|
+
if (!m || index < 0) return { start: 3, days: 31 };
|
|
1353
|
+
const year = Number(m[2]);
|
|
1354
|
+
return { start: new Date(Date.UTC(year, index, 1)).getUTCDay(), days: new Date(Date.UTC(year, index + 1, 0)).getUTCDate() };
|
|
1355
|
+
}
|
|
1356
|
+
function Calendar({ element, dir, aligned, colors: c = GRAY }) {
|
|
1357
|
+
const p = element.props;
|
|
1358
|
+
const { start, days } = monthGrid(p.month);
|
|
1359
|
+
const [from, to] = p.range ?? [p.selected ?? 0, p.selected ?? 0];
|
|
1360
|
+
const marked = new Set(p.marked ?? []);
|
|
1361
|
+
const cells = [...Array(start).fill(null), ...Array.from({ length: days }, (_, i) => i + 1)];
|
|
1362
|
+
while (cells.length % 7) cells.push(null);
|
|
1363
|
+
const weeks = Array.from({ length: cells.length / 7 }, (_, w) => cells.slice(w * 7, w * 7 + 7));
|
|
1364
|
+
const CELL = 38;
|
|
1365
|
+
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: [
|
|
1366
|
+
/* @__PURE__ */ jsxs(Box, { style: { alignItems: "center", justifyContent: "space-between", padding: "2px 4px 8px" }, children: [
|
|
1367
|
+
/* @__PURE__ */ jsx(IconGlyph, { name: "chevron-left", size: 18, color: theme.muted }),
|
|
1368
|
+
/* @__PURE__ */ jsx(Box, { style: { fontSize: 15, fontWeight: 600, color: theme.ink }, children: p.month ?? "Month" }),
|
|
1369
|
+
/* @__PURE__ */ jsx(IconGlyph, { name: "chevron-right", size: 18, color: theme.muted })
|
|
1370
|
+
] }),
|
|
1371
|
+
/* @__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)) }),
|
|
1372
|
+
weeks.map((week, w) => /* @__PURE__ */ jsx(Box, { children: week.map((d, i) => {
|
|
1373
|
+
const end = d != null && (d === from || d === to) && from > 0;
|
|
1374
|
+
const inside = d != null && from > 0 && d > from && d < to;
|
|
1375
|
+
const band = d != null && from > 0 && to > from && d >= from && d <= to;
|
|
1376
|
+
return /* @__PURE__ */ jsxs(Box, { style: { width: CELL, height: CELL, alignItems: "center", justifyContent: "center", position: "relative" }, children: [
|
|
1377
|
+
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,
|
|
1378
|
+
d != null ? /* @__PURE__ */ jsxs(
|
|
1379
|
+
Box,
|
|
1380
|
+
{
|
|
1381
|
+
style: {
|
|
1382
|
+
width: 32,
|
|
1383
|
+
height: 32,
|
|
1384
|
+
borderRadius: 16,
|
|
1385
|
+
flexDirection: "column",
|
|
1386
|
+
alignItems: "center",
|
|
1387
|
+
justifyContent: "center",
|
|
1388
|
+
fontSize: 14,
|
|
1389
|
+
backgroundColor: end ? c.accent : void 0,
|
|
1390
|
+
color: end ? c.onAccent : inside ? theme.ink : theme.text,
|
|
1391
|
+
fontWeight: end ? 600 : 400
|
|
1392
|
+
},
|
|
1393
|
+
children: [
|
|
1394
|
+
String(d),
|
|
1395
|
+
marked.has(d) ? /* @__PURE__ */ jsx(Box, { style: { position: "absolute", bottom: 3, width: 4, height: 4, borderRadius: 2, backgroundColor: end ? c.onAccent : theme.muted } }) : null
|
|
1396
|
+
]
|
|
1397
|
+
}
|
|
1398
|
+
) : null
|
|
1399
|
+
] }, i);
|
|
1400
|
+
}) }, w))
|
|
1401
|
+
] });
|
|
1402
|
+
}
|
|
1121
1403
|
var LEADING_ICON = { menu: "menu", back: "arrow-left", close: "x" };
|
|
1122
1404
|
function NavBar({ element }) {
|
|
1123
1405
|
const p = element.props;
|
|
@@ -1199,6 +1481,28 @@ function Tabs({ element, colors: c = GRAY }) {
|
|
|
1199
1481
|
i
|
|
1200
1482
|
)) });
|
|
1201
1483
|
}
|
|
1484
|
+
function pageList(pages, current) {
|
|
1485
|
+
if (pages <= 7) return Array.from({ length: pages }, (_, i) => i + 1);
|
|
1486
|
+
const near = [current - 1, current, current + 1].filter((n) => n > 1 && n < pages);
|
|
1487
|
+
const shown = [1, ...near, pages];
|
|
1488
|
+
const out = [];
|
|
1489
|
+
shown.forEach((n, i) => {
|
|
1490
|
+
if (i && n - shown[i - 1] > 1) out.push("\u2026");
|
|
1491
|
+
out.push(n);
|
|
1492
|
+
});
|
|
1493
|
+
return out;
|
|
1494
|
+
}
|
|
1495
|
+
function Pagination({ element, dir, aligned, colors: c = GRAY }) {
|
|
1496
|
+
const p = element.props;
|
|
1497
|
+
const pages = Math.max(1, p.pages ?? 1);
|
|
1498
|
+
const current = Math.min(Math.max(1, p.current ?? 1), pages);
|
|
1499
|
+
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);
|
|
1500
|
+
return /* @__PURE__ */ jsxs(Box, { style: { gap: 4, alignItems: "center", ...hug(dir, aligned) }, children: [
|
|
1501
|
+
cell(/* @__PURE__ */ jsx(IconGlyph, { name: "chevron-left", size: 18, color: current > 1 ? theme.text : theme.line }), "prev"),
|
|
1502
|
+
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)),
|
|
1503
|
+
cell(/* @__PURE__ */ jsx(IconGlyph, { name: "chevron-right", size: 18, color: current < pages ? theme.text : theme.line }), "next")
|
|
1504
|
+
] });
|
|
1505
|
+
}
|
|
1202
1506
|
function List({ element, children, dir, stretch }) {
|
|
1203
1507
|
const dividers = element.props.dividers ?? true;
|
|
1204
1508
|
const items = withDir(children, "column");
|
|
@@ -1354,7 +1658,9 @@ var registry = {
|
|
|
1354
1658
|
Spacer,
|
|
1355
1659
|
Heading,
|
|
1356
1660
|
Text,
|
|
1661
|
+
Bullets,
|
|
1357
1662
|
Image,
|
|
1663
|
+
Chart,
|
|
1358
1664
|
Icon,
|
|
1359
1665
|
Avatar,
|
|
1360
1666
|
Badge,
|
|
@@ -1364,9 +1670,13 @@ var registry = {
|
|
|
1364
1670
|
Radio,
|
|
1365
1671
|
Toggle,
|
|
1366
1672
|
Select,
|
|
1673
|
+
Slider,
|
|
1674
|
+
Progress,
|
|
1675
|
+
Calendar,
|
|
1367
1676
|
NavBar,
|
|
1368
1677
|
TabBar,
|
|
1369
1678
|
Tabs,
|
|
1679
|
+
Pagination,
|
|
1370
1680
|
List,
|
|
1371
1681
|
ListItem,
|
|
1372
1682
|
Table,
|
|
@@ -1383,6 +1693,10 @@ var PRIMARY_PROP = {
|
|
|
1383
1693
|
Heading: "text",
|
|
1384
1694
|
Text: "text",
|
|
1385
1695
|
Image: "label",
|
|
1696
|
+
Chart: "title",
|
|
1697
|
+
Slider: "label",
|
|
1698
|
+
Progress: "label",
|
|
1699
|
+
Calendar: "month",
|
|
1386
1700
|
Icon: "name",
|
|
1387
1701
|
Avatar: "initials",
|
|
1388
1702
|
Badge: "label",
|
|
@@ -1455,6 +1769,18 @@ function belongsElsewhere(component, word, asProp) {
|
|
|
1455
1769
|
}
|
|
1456
1770
|
return msg;
|
|
1457
1771
|
}
|
|
1772
|
+
var OBJECT_SWITCHES = /* @__PURE__ */ new Set();
|
|
1773
|
+
for (const info of COMPONENTS.values()) {
|
|
1774
|
+
for (const schema of Object.values(info.props)) {
|
|
1775
|
+
const s = unwrap(schema);
|
|
1776
|
+
if (s?.def?.type !== "array") continue;
|
|
1777
|
+
const el = unwrap(s.def.element);
|
|
1778
|
+
for (const option of el?.def?.type === "union" ? el.def.options.map(unwrap) : [el]) {
|
|
1779
|
+
if (option?.def?.type !== "object") continue;
|
|
1780
|
+
for (const [key, value] of Object.entries(option.shape)) if (unwrap(value)?.def?.type === "boolean") OBJECT_SWITCHES.add(key);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1458
1784
|
function bareWords(component) {
|
|
1459
1785
|
const info = COMPONENTS.get(component.toLowerCase());
|
|
1460
1786
|
if (!info) throw new Error(`unknown component ${component}`);
|
|
@@ -1571,15 +1897,31 @@ function parseValue(c) {
|
|
|
1571
1897
|
}
|
|
1572
1898
|
if (t.value === "{") {
|
|
1573
1899
|
const obj = {};
|
|
1900
|
+
let lastKey = null;
|
|
1574
1901
|
while (!c.isSym("}")) {
|
|
1575
1902
|
c.skipCommas();
|
|
1576
1903
|
if (c.isSym("}")) break;
|
|
1577
1904
|
if (c.done()) throw new Error("unclosed {");
|
|
1578
1905
|
const k = c.next();
|
|
1579
1906
|
if (!k || k.kind === "sym") throw new Error("expected key in {\u2026}");
|
|
1907
|
+
if ((k.kind === "word" || k.kind === "num") && !(c.isSym("=") || c.isSym(":"))) {
|
|
1908
|
+
if (k.kind === "word" && OBJECT_SWITCHES.has(k.value)) {
|
|
1909
|
+
obj[k.value] = true;
|
|
1910
|
+
lastKey = null;
|
|
1911
|
+
continue;
|
|
1912
|
+
}
|
|
1913
|
+
if (lastKey) {
|
|
1914
|
+
obj[lastKey] = `${obj[lastKey]} ${k.value}`;
|
|
1915
|
+
continue;
|
|
1916
|
+
}
|
|
1917
|
+
obj[k.value] = true;
|
|
1918
|
+
continue;
|
|
1919
|
+
}
|
|
1580
1920
|
if (!(c.isSym("=") || c.isSym(":"))) throw new Error(`expected = after ${k.value}`);
|
|
1581
1921
|
c.next();
|
|
1922
|
+
const unquoted = c.peek()?.kind === "word" || c.peek()?.kind === "num";
|
|
1582
1923
|
obj[k.value] = parseValue(c);
|
|
1924
|
+
lastKey = unquoted ? k.value : null;
|
|
1583
1925
|
}
|
|
1584
1926
|
c.next();
|
|
1585
1927
|
return obj;
|
|
@@ -1897,6 +2239,33 @@ function checkSpec(spec) {
|
|
|
1897
2239
|
if (p.trailingIcon && trailing !== "icon") conflict("trailingIcon", "trailing=icon", "trailing", trailing);
|
|
1898
2240
|
if (p.trailingText && trailing !== "text" && trailing !== "badge") conflict("trailingText", "trailing=text or badge", "trailing", trailing);
|
|
1899
2241
|
}
|
|
2242
|
+
{
|
|
2243
|
+
const p = el.props ?? {};
|
|
2244
|
+
const say = (msg) => issues.push(`${id}: ${msg}`);
|
|
2245
|
+
const backwards = (r) => Array.isArray(r) && r.length === 2 && r[0] > r[1];
|
|
2246
|
+
if (el.type === "Progress") {
|
|
2247
|
+
if (p.step != null && p.steps == null) say(`step only shows with steps (e.g. steps=4 step=${p.step})`);
|
|
2248
|
+
if (p.step != null && p.steps != null && p.step > p.steps) say(`step=${p.step} is past the last step (steps=${p.steps})`);
|
|
2249
|
+
if (p.steps != null && p.shape === "circle") say("a stepper (steps) can't also be a circle; remove one of them");
|
|
2250
|
+
}
|
|
2251
|
+
if (el.type === "Pagination" && p.current != null && p.pages != null && p.current > p.pages) {
|
|
2252
|
+
say(`current=${p.current} is past the last page (pages=${p.pages})`);
|
|
2253
|
+
}
|
|
2254
|
+
if (el.type === "Slider" && backwards(p.range)) say(`range=[${p.range.join(", ")}] goes backwards; write the smaller number first`);
|
|
2255
|
+
if (el.type === "Slider" && p.range && p.value != null) say("a slider has value (one handle) or range (two), not both");
|
|
2256
|
+
if (el.type === "Calendar") {
|
|
2257
|
+
const { days } = monthGrid(p.month);
|
|
2258
|
+
const all = [p.selected, ...p.range ?? [], ...p.marked ?? []].filter((d) => typeof d === "number");
|
|
2259
|
+
const late = all.find((d) => d > days);
|
|
2260
|
+
if (late) say(`day ${late} isn't in ${p.month ?? "the month"} (it has ${days} days)`);
|
|
2261
|
+
if (backwards(p.range)) say(`range=[${p.range.join(", ")}] goes backwards; write the earlier day first`);
|
|
2262
|
+
if (p.range && p.selected != null) say("a calendar has selected (one day) or range (several), not both");
|
|
2263
|
+
}
|
|
2264
|
+
if (el.type === "Input" && p.type === "code" && p.value != null && String(p.value).length > (p.digits ?? 6)) {
|
|
2265
|
+
say(`value has ${String(p.value).length} characters but the code has ${p.digits ?? 6} boxes (digits)`);
|
|
2266
|
+
}
|
|
2267
|
+
if (el.type === "Input" && p.digits != null && p.type !== "code") say("digits only applies to type=code");
|
|
2268
|
+
}
|
|
1900
2269
|
if (el.type === "Table") {
|
|
1901
2270
|
const { columns, data } = el.props ?? {};
|
|
1902
2271
|
if (Array.isArray(columns) && Array.isArray(data)) {
|
|
@@ -2032,7 +2401,7 @@ function fmt(v) {
|
|
|
2032
2401
|
if (typeof v === "number" || typeof v === "boolean" || v === null) return String(v);
|
|
2033
2402
|
if (Array.isArray(v)) return `[${v.map(fmt).join(", ")}]`;
|
|
2034
2403
|
if (typeof v === "object") {
|
|
2035
|
-
return `{${Object.entries(v).map(([k, x]) => `${k}=${fmt(x)}`).join(" ")}}`;
|
|
2404
|
+
return `{${Object.entries(v).map(([k, x]) => x === true ? k : `${k}=${fmt(x)}`).join(" ")}}`;
|
|
2036
2405
|
}
|
|
2037
2406
|
return JSON.stringify(v);
|
|
2038
2407
|
}
|
|
@@ -2146,6 +2515,8 @@ function describeType(t) {
|
|
|
2146
2515
|
return s.def.options.map(describeType).join(" | ");
|
|
2147
2516
|
case "array":
|
|
2148
2517
|
return `array of ${describeType(s.def.element)}`;
|
|
2518
|
+
case "tuple":
|
|
2519
|
+
return `[${s.def.items.map(describeType).join(", ")}]`;
|
|
2149
2520
|
case "object":
|
|
2150
2521
|
return `{ ${Object.entries(s.shape).map(([k, v]) => `${k}: ${describeType(v)}`).join(", ")} }`;
|
|
2151
2522
|
default:
|
package/dist/cli.js
CHANGED
package/dist/components.d.ts
CHANGED
|
@@ -32,19 +32,32 @@ declare function Divider({ element, dir }: Props): React.JSX.Element;
|
|
|
32
32
|
declare function Spacer({ element }: Props): React.JSX.Element;
|
|
33
33
|
declare function Heading({ element }: Props): React.JSX.Element;
|
|
34
34
|
declare function Text({ element }: Props): React.JSX.Element;
|
|
35
|
+
declare function Bullets({ element }: Props): React.JSX.Element;
|
|
35
36
|
declare function Image({ element, dir }: Props): React.JSX.Element;
|
|
36
37
|
declare function Icon({ element, dir, aligned }: Props): React.JSX.Element;
|
|
38
|
+
declare function Chart({ element, dir }: Props): React.JSX.Element;
|
|
37
39
|
declare function Avatar({ element, dir, aligned }: Props): React.JSX.Element;
|
|
38
40
|
declare function Badge({ element, dir, aligned, colors }: Props): React.JSX.Element;
|
|
39
41
|
declare function Button({ element, dir, aligned, colors: c }: Props): React.JSX.Element;
|
|
40
|
-
declare function Input({ element, dir }: Props): React.JSX.Element;
|
|
42
|
+
declare function Input({ element, dir, colors }: Props): React.JSX.Element;
|
|
41
43
|
declare function Select({ element, dir }: Props): React.JSX.Element;
|
|
42
44
|
declare function Checkbox({ element, dir, aligned, colors }: Props): React.JSX.Element;
|
|
43
45
|
declare function Radio({ element, dir, aligned, colors }: Props): React.JSX.Element;
|
|
44
46
|
declare function Toggle({ element, colors }: Props): React.JSX.Element;
|
|
47
|
+
declare function Slider({ element, colors: c }: Props): React.JSX.Element;
|
|
48
|
+
declare function Progress({ element, dir, aligned, colors: c }: Props): React.JSX.Element;
|
|
49
|
+
/** The weekday (0 = Sunday) the month starts on and its length, from text like "October 2026". Generic if it can't tell. */
|
|
50
|
+
export declare function monthGrid(month?: string | null): {
|
|
51
|
+
start: number;
|
|
52
|
+
days: number;
|
|
53
|
+
};
|
|
54
|
+
declare function Calendar({ element, dir, aligned, colors: c }: Props): React.JSX.Element;
|
|
45
55
|
declare function NavBar({ element }: Props): React.JSX.Element;
|
|
46
56
|
declare function TabBar({ element, colors: c }: Props): React.JSX.Element;
|
|
47
57
|
declare function Tabs({ element, colors: c }: Props): React.JSX.Element;
|
|
58
|
+
/** The page numbers to show: all of them up to 7, otherwise the ends and the current page's neighbours. */
|
|
59
|
+
export declare function pageList(pages: number, current: number): (number | "…")[];
|
|
60
|
+
declare function Pagination({ element, dir, aligned, colors: c }: Props): React.JSX.Element;
|
|
48
61
|
declare function List({ element, children, dir, stretch }: Props): React.JSX.Element;
|
|
49
62
|
declare function ListItem({ element, colors }: Props): React.JSX.Element;
|
|
50
63
|
declare function Table({ element }: Props): React.JSX.Element;
|
|
@@ -61,7 +74,9 @@ export declare const registry: {
|
|
|
61
74
|
Spacer: typeof Spacer;
|
|
62
75
|
Heading: typeof Heading;
|
|
63
76
|
Text: typeof Text;
|
|
77
|
+
Bullets: typeof Bullets;
|
|
64
78
|
Image: typeof Image;
|
|
79
|
+
Chart: typeof Chart;
|
|
65
80
|
Icon: typeof Icon;
|
|
66
81
|
Avatar: typeof Avatar;
|
|
67
82
|
Badge: typeof Badge;
|
|
@@ -71,9 +86,13 @@ export declare const registry: {
|
|
|
71
86
|
Radio: typeof Radio;
|
|
72
87
|
Toggle: typeof Toggle;
|
|
73
88
|
Select: typeof Select;
|
|
89
|
+
Slider: typeof Slider;
|
|
90
|
+
Progress: typeof Progress;
|
|
91
|
+
Calendar: typeof Calendar;
|
|
74
92
|
NavBar: typeof NavBar;
|
|
75
93
|
TabBar: typeof TabBar;
|
|
76
94
|
Tabs: typeof Tabs;
|
|
95
|
+
Pagination: typeof Pagination;
|
|
77
96
|
List: typeof List;
|
|
78
97
|
ListItem: typeof ListItem;
|
|
79
98
|
Table: typeof Table;
|
package/dist/index.js
CHANGED
package/dist/playground/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
renderWireframePng,
|
|
12
12
|
renderWireframeSvg,
|
|
13
13
|
wireframePrompt
|
|
14
|
-
} from "../chunks/chunk-
|
|
14
|
+
} from "../chunks/chunk-2IDCJVKK.js";
|
|
15
15
|
|
|
16
16
|
// src/playground/index.ts
|
|
17
17
|
import { createServer } from "node:http";
|
|
@@ -38,7 +38,8 @@ function describe(name, schema) {
|
|
|
38
38
|
return { name, kind: "number", description };
|
|
39
39
|
case "array": {
|
|
40
40
|
const el = unwrap(s.def.element);
|
|
41
|
-
const
|
|
41
|
+
const objects = el?.def?.type === "union" ? el.def.options.map(unwrap) : [el];
|
|
42
|
+
const hasIcon = objects.some((x) => x?.def?.type === "object" && "icon" in x.shape);
|
|
42
43
|
return { name, kind: "list", icon: iconish || hasIcon, description };
|
|
43
44
|
}
|
|
44
45
|
case "object":
|
|
@@ -73,9 +74,9 @@ function languageData() {
|
|
|
73
74
|
var GROUPS = [
|
|
74
75
|
{ name: "Canvas", components: ["Board", "Screen", "Note"] },
|
|
75
76
|
{ name: "Layout", components: ["Stack", "Grid", "Card", "Divider", "Spacer"] },
|
|
76
|
-
{ name: "Content", components: ["Heading", "Text", "Image", "Icon", "Avatar", "Badge"] },
|
|
77
|
-
{ name: "Controls", components: ["Button", "Input", "Checkbox", "Radio", "Toggle", "Select"] },
|
|
78
|
-
{ name: "Navigation & data", components: ["NavBar", "TabBar", "Tabs", "List", "ListItem", "Table"] },
|
|
77
|
+
{ name: "Content", components: ["Heading", "Text", "Bullets", "Image", "Chart", "Icon", "Avatar", "Badge"] },
|
|
78
|
+
{ name: "Controls", components: ["Button", "Input", "Checkbox", "Radio", "Toggle", "Select", "Slider", "Progress", "Calendar"] },
|
|
79
|
+
{ name: "Navigation & data", components: ["NavBar", "TabBar", "Tabs", "Pagination", "List", "ListItem", "Table"] },
|
|
79
80
|
{ name: "Overlays", components: ["Modal", "Drawer"] }
|
|
80
81
|
];
|
|
81
82
|
var EXAMPLES = {
|
|
@@ -178,6 +179,16 @@ var EXAMPLES = {
|
|
|
178
179
|
text "Bold" bold
|
|
179
180
|
text "Placeholder lines when lines is set and there's no text:" sm muted
|
|
180
181
|
text lines=3`,
|
|
182
|
+
Bullets: `board
|
|
183
|
+
screen custom width=420 height=600
|
|
184
|
+
text "Bullets" bold
|
|
185
|
+
bullets items=[Fast setup, No credit card, Cancel anytime]
|
|
186
|
+
text "numbered" bold
|
|
187
|
+
bullets numbered items=[Create an account, Verify your email, Invite your team]
|
|
188
|
+
text "icon= any Lucide icon" bold
|
|
189
|
+
bullets icon=check items=[Unlimited boards, Share links, Version history]
|
|
190
|
+
text "One item can change its icon, or be muted" bold
|
|
191
|
+
bullets icon=check items=[Unlimited boards, Share links, {label="SSO" icon=x muted}]`,
|
|
181
192
|
Image: `board
|
|
182
193
|
screen custom width=480 height=210
|
|
183
194
|
stack row gap=12
|
|
@@ -185,6 +196,16 @@ var EXAMPLES = {
|
|
|
185
196
|
image height=120 width=120 rounded
|
|
186
197
|
image height=120
|
|
187
198
|
text "In a row, an image without a width is square." sm muted`,
|
|
199
|
+
Chart: `board
|
|
200
|
+
screen custom width=640 height=430
|
|
201
|
+
stack row gap=16
|
|
202
|
+
chart line "line (default)"
|
|
203
|
+
chart bar "bar"
|
|
204
|
+
stack row gap=16
|
|
205
|
+
chart area "area" height=150
|
|
206
|
+
chart pie "pie" height=150
|
|
207
|
+
chart donut "donut" height=150
|
|
208
|
+
text "Charts are placeholders: they show the kind of chart, not data." sm muted`,
|
|
188
209
|
Icon: `board
|
|
189
210
|
screen custom width=360 height=120
|
|
190
211
|
stack row gap=16 align=center
|
|
@@ -228,7 +249,7 @@ var EXAMPLES = {
|
|
|
228
249
|
button primary "Full width" fullWidth
|
|
229
250
|
text "With a board accent, primary buttons fill with it and ghost buttons use it for text." sm muted`,
|
|
230
251
|
Input: `board
|
|
231
|
-
screen custom width=420 height=
|
|
252
|
+
screen custom width=420 height=760
|
|
232
253
|
input "Email" placeholder="you@example.com" helper="We never share it"
|
|
233
254
|
input "Username" value="dana moore" error helper="No spaces allowed"
|
|
234
255
|
input password "Password" value=secret
|
|
@@ -236,7 +257,9 @@ var EXAMPLES = {
|
|
|
236
257
|
input "Message" multiline=3
|
|
237
258
|
stack row gap=8
|
|
238
259
|
input placeholder="grow fills the row" grow
|
|
239
|
-
button primary "Send"
|
|
260
|
+
button primary "Send"
|
|
261
|
+
input "Check-in" type=date value="Oct 14, 2026"
|
|
262
|
+
input "Verification code" type=code value="4821" helper="Sent to (555) 010-2400"`,
|
|
240
263
|
Checkbox: `board
|
|
241
264
|
screen custom width=300 height=100
|
|
242
265
|
checkbox "Checked" checked
|
|
@@ -262,6 +285,24 @@ var EXAMPLES = {
|
|
|
262
285
|
stack row gap=12
|
|
263
286
|
input "City" placeholder="Monterrey" grow
|
|
264
287
|
select "State" value="Nuevo Le\xF3n" grow`,
|
|
288
|
+
Slider: `board
|
|
289
|
+
screen custom width=380 height=200
|
|
290
|
+
slider "Volume" value=30
|
|
291
|
+
slider "Price range" range=[20, 80]`,
|
|
292
|
+
Progress: `board
|
|
293
|
+
screen custom width=420 height=330
|
|
294
|
+
progress "Uploading" value=40
|
|
295
|
+
progress value=75
|
|
296
|
+
stack row gap=24
|
|
297
|
+
progress circle "Storage" value=72
|
|
298
|
+
progress circle value=15
|
|
299
|
+
progress "Setup" steps=4 step=2`,
|
|
300
|
+
Calendar: `board
|
|
301
|
+
screen custom width=660 height=400
|
|
302
|
+
stack row gap=24 align=start
|
|
303
|
+
calendar "October 2026" selected=14
|
|
304
|
+
calendar "October 2026" range=[12, 18] marked=[3, 9, 22]
|
|
305
|
+
text "range selects several days; marked adds a dot, e.g. for events." sm muted`,
|
|
265
306
|
NavBar: `board
|
|
266
307
|
screen custom width=380 height=240 padding=0 gap=0
|
|
267
308
|
navbar "leading=menu" leading=menu actions=[search, bell]
|
|
@@ -277,6 +318,10 @@ var EXAMPLES = {
|
|
|
277
318
|
screen custom width=400 height=150
|
|
278
319
|
tabs items=[Overview, Activity, Settings] active=0
|
|
279
320
|
text lines=2`,
|
|
321
|
+
Pagination: `board
|
|
322
|
+
screen custom width=420 height=160
|
|
323
|
+
pagination pages=5 current=2
|
|
324
|
+
pagination pages=12 current=6`,
|
|
280
325
|
List: `board
|
|
281
326
|
screen custom width=380 height=420
|
|
282
327
|
list
|
package/examples/dashboard.tsq
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
board "Analytics" accent=blue
|
|
2
|
-
screen desktop "Dashboard" padding=0 height=
|
|
2
|
+
screen desktop "Dashboard" padding=0 height=760
|
|
3
3
|
stack row grow
|
|
4
4
|
stack width=220 border=right fill padding=16 gap=6
|
|
5
5
|
text "Acme" bold
|
|
@@ -24,4 +24,9 @@ board "Analytics" accent=blue
|
|
|
24
24
|
heading "932" level=2
|
|
25
25
|
card "Refunds"
|
|
26
26
|
heading "12" level=2
|
|
27
|
+
stack row gap=12
|
|
28
|
+
card "Revenue" grow
|
|
29
|
+
chart area height=170
|
|
30
|
+
card "Traffic sources" width=300
|
|
31
|
+
chart donut height=170
|
|
27
32
|
table columns=[Order, Customer, Status, Total] data=[["#1042", Ana Torres, Paid, "$120"], ["#1041", Ben Cho, Pending, "$80"], ["#1040", Priya Nair, Paid, "$45"]]
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@ Root canvas (artboard). Holds Screens side by side, plus optional Notes. Must be
|
|
|
9
9
|
- columns: number
|
|
10
10
|
- gap: number
|
|
11
11
|
- padding: number
|
|
12
|
-
- accent: string — The one UI color: primary buttons, solid badges, checked controls, toggles, active tabs, ghost buttons. blue, indigo, violet, pink, red, orange, green, teal, or a hex color like #1a73e8. Omit for grayscale.
|
|
12
|
+
- accent: string — The one UI color: primary buttons, solid badges, checked controls, toggles, sliders, progress, selected days and pages, active tabs, ghost buttons. blue, indigo, violet, pink, red, orange, green, teal, or a hex color like #1a73e8. Omit for grayscale.
|
|
13
13
|
|
|
14
14
|
### Screen
|
|
15
15
|
One view of the product in a device frame. Children stack vertically.
|
|
@@ -78,6 +78,12 @@ Body text, or placeholder lines when `lines` is set and `text` is not.
|
|
|
78
78
|
- bold: boolean
|
|
79
79
|
- align: "left" | "center" | "right"
|
|
80
80
|
|
|
81
|
+
### Bullets
|
|
82
|
+
Bulleted, numbered or icon list of short lines.
|
|
83
|
+
- items (required): array of string | { label: string, icon: string, muted: boolean } — Text, or {label icon muted} to change one line, e.g. {label=SSO icon=x muted}
|
|
84
|
+
- numbered: boolean — 1. 2. 3. instead of dots
|
|
85
|
+
- icon: string — A Lucide icon instead of dots, e.g. check for a feature list
|
|
86
|
+
|
|
81
87
|
### Image
|
|
82
88
|
Image placeholder: a box with an X through it.
|
|
83
89
|
- height: number
|
|
@@ -85,6 +91,13 @@ Image placeholder: a box with an X through it.
|
|
|
85
91
|
- label: string (main text)
|
|
86
92
|
- rounded: boolean
|
|
87
93
|
|
|
94
|
+
### Chart
|
|
95
|
+
Chart placeholder: a generic line, bar, area, pie or donut shape, no data.
|
|
96
|
+
- kind: "line" | "bar" | "area" | "pie" | "donut" — Default line
|
|
97
|
+
- title: string (main text)
|
|
98
|
+
- height: number — Default 180
|
|
99
|
+
- width: number | string — Default fills the container width
|
|
100
|
+
|
|
88
101
|
### Icon
|
|
89
102
|
Line icon from Lucide.
|
|
90
103
|
- name: string (main text) — Lucide icon name in kebab-case, e.g. menu, search, arrow-left, settings, bell, user
|
|
@@ -115,7 +128,8 @@ Text field with optional label. Set multiline for a textarea.
|
|
|
115
128
|
- label: string (main text)
|
|
116
129
|
- placeholder: string
|
|
117
130
|
- value: string
|
|
118
|
-
- type: "text" | "password" | "search" | "email"
|
|
131
|
+
- type: "text" | "password" | "search" | "email" | "date" | "code" — date shows a calendar icon; code draws one box per digit
|
|
132
|
+
- digits: number — Boxes for type=code, default 6
|
|
119
133
|
- multiline: number — Number of rows; 2 or more makes a textarea
|
|
120
134
|
- helper: string
|
|
121
135
|
- error: boolean — Validation error: red border and red helper text
|
|
@@ -145,6 +159,27 @@ Dropdown field (closed state).
|
|
|
145
159
|
- grow: boolean — Fill the remaining space in a row
|
|
146
160
|
- width: number | string — Fixed width, e.g. 320. Default fills the space.
|
|
147
161
|
|
|
162
|
+
### Slider
|
|
163
|
+
Slider with one handle, or two with `range`.
|
|
164
|
+
- label: string (main text)
|
|
165
|
+
- value: number — Handle position: 0 to 100, or any amount (the track scales to fit)
|
|
166
|
+
- range: [number, number] — Two handles instead, e.g. [20, 80] or [50, 400]
|
|
167
|
+
|
|
168
|
+
### Progress
|
|
169
|
+
Progress bar, ring (circle), or stepper (steps).
|
|
170
|
+
- label: string (main text)
|
|
171
|
+
- value: number — Percent done
|
|
172
|
+
- shape: "bar" | "circle" — Default bar; circle is a ring with the percent inside
|
|
173
|
+
- steps: number — Draw a stepper with this many steps instead
|
|
174
|
+
- step: number — The current step, with steps
|
|
175
|
+
|
|
176
|
+
### Calendar
|
|
177
|
+
Month calendar.
|
|
178
|
+
- month: string (main text) — Month and year, e.g. "October 2026"; the days match that month
|
|
179
|
+
- selected: number
|
|
180
|
+
- range: [number, number] — Selected days, e.g. [12, 18]
|
|
181
|
+
- marked: array of number — Days with a dot, e.g. events
|
|
182
|
+
|
|
148
183
|
### NavBar
|
|
149
184
|
Top app bar. Place first in a Screen.
|
|
150
185
|
- title: string (main text)
|
|
@@ -162,6 +197,11 @@ In-page tabs with an underline on the active one.
|
|
|
162
197
|
- items (required): array of string
|
|
163
198
|
- active: number
|
|
164
199
|
|
|
200
|
+
### Pagination
|
|
201
|
+
Page numbers with previous and next.
|
|
202
|
+
- pages (required): number
|
|
203
|
+
- current: number — Default 1
|
|
204
|
+
|
|
165
205
|
### List
|
|
166
206
|
Vertical list of ListItems.
|
|
167
207
|
- dividers: boolean
|