tsquare 0.3.1 → 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 +124 -0
- package/README.md +1 -1
- package/dist/catalog.d.ts +194 -0
- package/dist/chunks/{chunk-YCIRLKKD.js → chunk-2IDCJVKK.js} +378 -6
- package/dist/cli.js +1 -1
- package/dist/components.d.ts +20 -1
- package/dist/index.js +1 -1
- package/dist/playground/index.js +64 -11
- package/examples/dashboard.tsq +6 -1
- package/package.json +5 -3
- package/skill/tsquare/reference.md +43 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Changelog
|
|
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
|
+
|
|
36
|
+
## 0.3.2
|
|
37
|
+
|
|
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.
|
|
39
|
+
- **Docs:** a new [Embedding](docs/embedding.md) page for Notion, GitHub and your own site, and the Radio page shows radios side by side (`stack row` + `radio`).
|
|
40
|
+
- **Tests and CI:** a test suite (`npm test`), run on Node 20, 22 and 24 for every pull request, plus a check that eval scores, docs and the skill match what's committed.
|
|
41
|
+
- `CHANGELOG.md` is now included in the npm package.
|
|
42
|
+
|
|
43
|
+
## 0.3.1
|
|
44
|
+
|
|
45
|
+
### Older wireframes are upgraded in one step
|
|
46
|
+
|
|
47
|
+
- All support for older syntax now lives in one module (`upgradeWireframe`, `upgradeSpec`), run before parsing. The parser, catalog and renderer only know the current language.
|
|
48
|
+
- **Links made before 0.3.0 render again** when a comment follows an element on the same line (`screen phone "Home" # main`): the comment moves onto its own line. Text you write or paste still gets the error, so new comments go on their own line.
|
|
49
|
+
- `decodeWireframe` returns the upgraded text, so a decoded link always means what it did when it was made.
|
|
50
|
+
|
|
51
|
+
### Better suggestions for unknown icons
|
|
52
|
+
|
|
53
|
+
- Common guesses that aren't Lucide names now suggest what was meant: `compose` → `square-pen`, `call` → `phone`, `person` → `user`, `bag` → `shopping-bag`, `notification` → `bell`, `close` → `x`.
|
|
54
|
+
- Among equally close matches, the usual directions come first: `chevron` suggests `chevron-right, chevron-left, chevron-down, chevron-up`.
|
|
55
|
+
- On a list item, `trailingIcon=chevron` (or `toggle`, `avatar`…) says to write `trailing=chevron`, since that's a kind, not an icon.
|
|
56
|
+
|
|
57
|
+
## 0.3.0
|
|
58
|
+
|
|
59
|
+
### Renamed: `icon` is now `leadingIcon` on Button and ListItem
|
|
60
|
+
|
|
61
|
+
Buttons and list items take an icon on either side, so the prop names now say which side:
|
|
62
|
+
|
|
63
|
+
| Before | Now |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `button "Download" icon=download` | `button "Download" leadingIcon=download` |
|
|
66
|
+
| (not possible) | `button "Next" trailingIcon=chevron-right` |
|
|
67
|
+
| `listitem "Inbox" leading=icon icon=inbox` | `listitem "Inbox" leadingIcon=inbox` |
|
|
68
|
+
| `listitem "Delete" trailing=icon trailingIcon=trash-2` | `listitem "Delete" trailingIcon=trash-2` |
|
|
69
|
+
|
|
70
|
+
- **Nothing breaks.** The old `icon=` spelling still works on both components, so existing files and shared render links render the same. `tsquare fmt` rewrites it to `leadingIcon=`. The prompt, the skill and the docs only teach the new names.
|
|
71
|
+
- **Buttons can have a trailing icon**, for Next, external links or dropdowns. Both sides work together.
|
|
72
|
+
- `leading` and `trailing` on ListItem stay as before. They pick what fills each side (`avatar`, `image`, `checkbox`, `chevron`, `badge`, `toggle`, …). An icon or text value implies its kind, so `leading=icon` and `trailing=icon` are optional.
|
|
73
|
+
- `icon` stays as it is on TabBar items (`{label=Home icon=house}`) and on the Icon component.
|
|
74
|
+
|
|
75
|
+
### Changed: comments go on their own line
|
|
76
|
+
|
|
77
|
+
A line that starts with `#` is a comment. Anywhere else, `#` is ordinary text, so order numbers, tags and hex colors need no quotes:
|
|
78
|
+
|
|
79
|
+
```tsquare
|
|
80
|
+
# the orders page
|
|
81
|
+
screen desktop "Orders"
|
|
82
|
+
table columns=[Order, Customer] data=[[#1001, Ana Torres], [#1002, Ben Lee]]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- **Why:** comments used to start at any `#` after a space. That cut off `[#1001, Ana Torres]` and broke the table, which happened to Sonnet in 3 of 4 eval runs. Across 160 eval outputs, models never wrote a comment.
|
|
86
|
+
- **Upgrading:** a comment after an element on the same line (`screen phone "Home" # main`) is now an error that tells you to move it to its own line. Nothing is dropped silently.
|
|
87
|
+
|
|
88
|
+
### Clearer errors for props on the wrong component
|
|
89
|
+
|
|
90
|
+
When a word or prop belongs to a different component, the error now says which one:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
Input: fullWidth is a Button prop, not an Input one; an Input already fills its width (width=… sets a fixed one), so remove it
|
|
94
|
+
Card has no prop "border": border is a Stack prop, not a Card one
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
A prop or list wrapped onto its own line says where it belongs, instead of "unknown component":
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
trailing=…: props go on the same line as their component; move it to the end of line 15
|
|
101
|
+
a list must stay on one line; join this to line 6
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Playground
|
|
105
|
+
|
|
106
|
+
- A CodeMirror editor: highlighting, autocomplete for components, options, props, Lucide icons and accent colors, and problems underlined on their line.
|
|
107
|
+
- Light and dark themes.
|
|
108
|
+
- Zoom and pan, zoom to a single screen, full screen and a resizable split.
|
|
109
|
+
- Copy link: share link, SVG/PNG image links, Markdown and HTML snippets.
|
|
110
|
+
- An examples gallery with thumbnails.
|
|
111
|
+
|
|
112
|
+
### Library
|
|
113
|
+
|
|
114
|
+
- `encodeWireframe` / `decodeWireframe` for share and render links.
|
|
115
|
+
- `boardLayout()` returns the board size and each screen's position.
|
|
116
|
+
|
|
117
|
+
### List items: a value implies its kind
|
|
118
|
+
|
|
119
|
+
- `trailingIcon=trash-2` without `trailing=icon` used to render nothing, silently. Now the value implies the kind.
|
|
120
|
+
- A value that contradicts an explicit kind is an error that names the fix, for example: `leadingIcon only shows with leading=icon (this item has leading=avatar); remove one of them`.
|
|
121
|
+
|
|
122
|
+
### Examples
|
|
123
|
+
|
|
124
|
+
- New: `sign-in`, `dashboard`, `checkout` and `states`.
|
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ A wireframe can be embedded anywhere an image works (Notion, GitHub, docs sites)
|
|
|
50
50
|

|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
The playground's **Copy link** menu makes these. The text is compressed into the URL, not encrypted, so anyone with the link can read it; see [privacy](https://tsquare.dev/privacy). `encodeWireframe` and `decodeWireframe` in the library produce and read the same links.
|
|
53
|
+
The playground's **Copy link** menu makes these; [Embedding](docs/embedding.md) has the steps for Notion, GitHub and your own site. The text is compressed into the URL, not encrypted, so anyone with the link can read it; see [privacy](https://tsquare.dev/privacy). `encodeWireframe` and `decodeWireframe` in the library produce and read the same links.
|
|
54
54
|
|
|
55
55
|
## The language
|
|
56
56
|
|
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>>;
|
|
@@ -341,6 +380,7 @@ export declare const componentDefinitions: {
|
|
|
341
380
|
label: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
342
381
|
value: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
343
382
|
placeholder: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
383
|
+
grow: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
344
384
|
width: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>>;
|
|
345
385
|
}, z.core.$strip>;
|
|
346
386
|
slots: never[];
|
|
@@ -350,6 +390,51 @@ export declare const componentDefinitions: {
|
|
|
350
390
|
value: string;
|
|
351
391
|
};
|
|
352
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
|
+
};
|
|
353
438
|
NavBar: {
|
|
354
439
|
props: z.ZodObject<{
|
|
355
440
|
title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -404,6 +489,18 @@ export declare const componentDefinitions: {
|
|
|
404
489
|
active: number;
|
|
405
490
|
};
|
|
406
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
|
+
};
|
|
407
504
|
List: {
|
|
408
505
|
props: z.ZodObject<{
|
|
409
506
|
dividers: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
@@ -715,6 +812,22 @@ export declare const catalog: import("@json-render/core").Catalog<{
|
|
|
715
812
|
lines: number;
|
|
716
813
|
};
|
|
717
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
|
+
};
|
|
718
831
|
Image: {
|
|
719
832
|
props: z.ZodObject<{
|
|
720
833
|
height: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
@@ -729,6 +842,26 @@ export declare const catalog: import("@json-render/core").Catalog<{
|
|
|
729
842
|
label: string;
|
|
730
843
|
};
|
|
731
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
|
+
};
|
|
732
865
|
Icon: {
|
|
733
866
|
props: z.ZodObject<{
|
|
734
867
|
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -805,11 +938,14 @@ export declare const catalog: import("@json-render/core").Catalog<{
|
|
|
805
938
|
placeholder: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
806
939
|
value: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
807
940
|
type: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
941
|
+
code: "code";
|
|
942
|
+
date: "date";
|
|
808
943
|
email: "email";
|
|
809
944
|
password: "password";
|
|
810
945
|
search: "search";
|
|
811
946
|
text: "text";
|
|
812
947
|
}>>>;
|
|
948
|
+
digits: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
813
949
|
multiline: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
814
950
|
helper: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
815
951
|
error: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
@@ -864,6 +1000,7 @@ export declare const catalog: import("@json-render/core").Catalog<{
|
|
|
864
1000
|
label: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
865
1001
|
value: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
866
1002
|
placeholder: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1003
|
+
grow: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
867
1004
|
width: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>>;
|
|
868
1005
|
}, z.core.$strip>;
|
|
869
1006
|
slots: never[];
|
|
@@ -873,6 +1010,51 @@ export declare const catalog: import("@json-render/core").Catalog<{
|
|
|
873
1010
|
value: string;
|
|
874
1011
|
};
|
|
875
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
|
+
};
|
|
876
1058
|
NavBar: {
|
|
877
1059
|
props: z.ZodObject<{
|
|
878
1060
|
title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -927,6 +1109,18 @@ export declare const catalog: import("@json-render/core").Catalog<{
|
|
|
927
1109
|
active: number;
|
|
928
1110
|
};
|
|
929
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
|
+
};
|
|
930
1124
|
List: {
|
|
931
1125
|
props: z.ZodObject<{
|
|
932
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"),
|
|
@@ -433,12 +462,46 @@ var componentDefinitions = {
|
|
|
433
462
|
label: o(z.string()),
|
|
434
463
|
value: o(z.string()),
|
|
435
464
|
placeholder: o(z.string()),
|
|
465
|
+
grow: o(z.boolean()).describe("Fill the remaining space in a row"),
|
|
436
466
|
width: o(z.union([z.number(), z.string()])).describe("Fixed width, e.g. 320. Default fills the space.")
|
|
437
467
|
}),
|
|
438
468
|
slots: [],
|
|
439
469
|
description: "Dropdown field (closed state).",
|
|
440
470
|
example: { label: "Country", value: "United States" }
|
|
441
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
|
+
},
|
|
442
505
|
// ── Navigation & data ─────────────────────────────────────────────────
|
|
443
506
|
NavBar: {
|
|
444
507
|
props: z.object({
|
|
@@ -469,6 +532,15 @@ var componentDefinitions = {
|
|
|
469
532
|
description: "In-page tabs with an underline on the active one.",
|
|
470
533
|
example: { items: ["Overview", "Activity", "Settings"], active: 0 }
|
|
471
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
|
+
},
|
|
472
544
|
List: {
|
|
473
545
|
props: z.object({ dividers: o(z.boolean()), grow: o(z.boolean()) }),
|
|
474
546
|
slots: ["default"],
|
|
@@ -875,6 +947,20 @@ function Text({ element }) {
|
|
|
875
947
|
}
|
|
876
948
|
);
|
|
877
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
|
+
}
|
|
878
964
|
function Image({ element, dir }) {
|
|
879
965
|
const p = element.props;
|
|
880
966
|
const h = p.height ?? 160;
|
|
@@ -930,6 +1016,48 @@ function Icon({ element, dir, aligned }) {
|
|
|
930
1016
|
const p = element.props;
|
|
931
1017
|
return /* @__PURE__ */ jsx(Box, { style: { flexShrink: 0, ...hug(dir, aligned) }, children: /* @__PURE__ */ jsx(IconGlyph, { name: p.name, size: p.size ?? 20 }) });
|
|
932
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
|
+
}
|
|
933
1061
|
function AvatarCircle({ initials, size = 40 }) {
|
|
934
1062
|
return /* @__PURE__ */ jsx(
|
|
935
1063
|
Box,
|
|
@@ -1035,8 +1163,35 @@ function Field({ children, height = 42, top = false, error = false }) {
|
|
|
1035
1163
|
}
|
|
1036
1164
|
);
|
|
1037
1165
|
}
|
|
1038
|
-
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 }) {
|
|
1039
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
|
+
}
|
|
1040
1195
|
const rows = p.multiline ?? 1;
|
|
1041
1196
|
const isPassword = p.type === "password";
|
|
1042
1197
|
const shown = p.value ?? null;
|
|
@@ -1046,14 +1201,15 @@ function Input({ element, dir }) {
|
|
|
1046
1201
|
/* @__PURE__ */ jsxs(Field, { height: rows > 1 ? rows * 22 + 20 : 42, top: rows > 1, error: !!p.error, children: [
|
|
1047
1202
|
p.type === "search" ? /* @__PURE__ */ jsx(IconGlyph, { name: "search", size: 18, color: theme.muted }) : null,
|
|
1048
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" : "") }),
|
|
1049
|
-
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
|
|
1050
1206
|
] }),
|
|
1051
1207
|
p.helper ? /* @__PURE__ */ jsx(Box, { style: { fontSize: 12, color: p.error ? TONES.danger : theme.muted }, children: p.helper }) : null
|
|
1052
1208
|
] });
|
|
1053
1209
|
}
|
|
1054
1210
|
function Select({ element, dir }) {
|
|
1055
1211
|
const p = element.props;
|
|
1056
|
-
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", gap: 6, ...fieldWidth(p.width, dir) }, children: [
|
|
1212
|
+
return /* @__PURE__ */ jsxs(Box, { style: { flexDirection: "column", gap: 6, ...fieldWidth(p.width, dir), ...p.grow ? { flexGrow: 1 } : {} }, children: [
|
|
1057
1213
|
/* @__PURE__ */ jsx(FieldLabel, { text: p.label }),
|
|
1058
1214
|
/* @__PURE__ */ jsxs(Field, { children: [
|
|
1059
1215
|
/* @__PURE__ */ jsx(Box, { style: { flexGrow: 1, color: p.value ? theme.ink : theme.muted }, children: p.value ?? p.placeholder ?? "Select\u2026" }),
|
|
@@ -1117,6 +1273,133 @@ function Toggle({ element, colors }) {
|
|
|
1117
1273
|
/* @__PURE__ */ jsx(Switch, { on: p.on, c: colors })
|
|
1118
1274
|
] });
|
|
1119
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
|
+
}
|
|
1120
1403
|
var LEADING_ICON = { menu: "menu", back: "arrow-left", close: "x" };
|
|
1121
1404
|
function NavBar({ element }) {
|
|
1122
1405
|
const p = element.props;
|
|
@@ -1198,6 +1481,28 @@ function Tabs({ element, colors: c = GRAY }) {
|
|
|
1198
1481
|
i
|
|
1199
1482
|
)) });
|
|
1200
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
|
+
}
|
|
1201
1506
|
function List({ element, children, dir, stretch }) {
|
|
1202
1507
|
const dividers = element.props.dividers ?? true;
|
|
1203
1508
|
const items = withDir(children, "column");
|
|
@@ -1353,7 +1658,9 @@ var registry = {
|
|
|
1353
1658
|
Spacer,
|
|
1354
1659
|
Heading,
|
|
1355
1660
|
Text,
|
|
1661
|
+
Bullets,
|
|
1356
1662
|
Image,
|
|
1663
|
+
Chart,
|
|
1357
1664
|
Icon,
|
|
1358
1665
|
Avatar,
|
|
1359
1666
|
Badge,
|
|
@@ -1363,9 +1670,13 @@ var registry = {
|
|
|
1363
1670
|
Radio,
|
|
1364
1671
|
Toggle,
|
|
1365
1672
|
Select,
|
|
1673
|
+
Slider,
|
|
1674
|
+
Progress,
|
|
1675
|
+
Calendar,
|
|
1366
1676
|
NavBar,
|
|
1367
1677
|
TabBar,
|
|
1368
1678
|
Tabs,
|
|
1679
|
+
Pagination,
|
|
1369
1680
|
List,
|
|
1370
1681
|
ListItem,
|
|
1371
1682
|
Table,
|
|
@@ -1382,6 +1693,10 @@ var PRIMARY_PROP = {
|
|
|
1382
1693
|
Heading: "text",
|
|
1383
1694
|
Text: "text",
|
|
1384
1695
|
Image: "label",
|
|
1696
|
+
Chart: "title",
|
|
1697
|
+
Slider: "label",
|
|
1698
|
+
Progress: "label",
|
|
1699
|
+
Calendar: "month",
|
|
1385
1700
|
Icon: "name",
|
|
1386
1701
|
Avatar: "initials",
|
|
1387
1702
|
Badge: "label",
|
|
@@ -1454,6 +1769,18 @@ function belongsElsewhere(component, word, asProp) {
|
|
|
1454
1769
|
}
|
|
1455
1770
|
return msg;
|
|
1456
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
|
+
}
|
|
1457
1784
|
function bareWords(component) {
|
|
1458
1785
|
const info = COMPONENTS.get(component.toLowerCase());
|
|
1459
1786
|
if (!info) throw new Error(`unknown component ${component}`);
|
|
@@ -1570,15 +1897,31 @@ function parseValue(c) {
|
|
|
1570
1897
|
}
|
|
1571
1898
|
if (t.value === "{") {
|
|
1572
1899
|
const obj = {};
|
|
1900
|
+
let lastKey = null;
|
|
1573
1901
|
while (!c.isSym("}")) {
|
|
1574
1902
|
c.skipCommas();
|
|
1575
1903
|
if (c.isSym("}")) break;
|
|
1576
1904
|
if (c.done()) throw new Error("unclosed {");
|
|
1577
1905
|
const k = c.next();
|
|
1578
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
|
+
}
|
|
1579
1920
|
if (!(c.isSym("=") || c.isSym(":"))) throw new Error(`expected = after ${k.value}`);
|
|
1580
1921
|
c.next();
|
|
1922
|
+
const unquoted = c.peek()?.kind === "word" || c.peek()?.kind === "num";
|
|
1581
1923
|
obj[k.value] = parseValue(c);
|
|
1924
|
+
lastKey = unquoted ? k.value : null;
|
|
1582
1925
|
}
|
|
1583
1926
|
c.next();
|
|
1584
1927
|
return obj;
|
|
@@ -1896,6 +2239,33 @@ function checkSpec(spec) {
|
|
|
1896
2239
|
if (p.trailingIcon && trailing !== "icon") conflict("trailingIcon", "trailing=icon", "trailing", trailing);
|
|
1897
2240
|
if (p.trailingText && trailing !== "text" && trailing !== "badge") conflict("trailingText", "trailing=text or badge", "trailing", trailing);
|
|
1898
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
|
+
}
|
|
1899
2269
|
if (el.type === "Table") {
|
|
1900
2270
|
const { columns, data } = el.props ?? {};
|
|
1901
2271
|
if (Array.isArray(columns) && Array.isArray(data)) {
|
|
@@ -2031,7 +2401,7 @@ function fmt(v) {
|
|
|
2031
2401
|
if (typeof v === "number" || typeof v === "boolean" || v === null) return String(v);
|
|
2032
2402
|
if (Array.isArray(v)) return `[${v.map(fmt).join(", ")}]`;
|
|
2033
2403
|
if (typeof v === "object") {
|
|
2034
|
-
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(" ")}}`;
|
|
2035
2405
|
}
|
|
2036
2406
|
return JSON.stringify(v);
|
|
2037
2407
|
}
|
|
@@ -2145,6 +2515,8 @@ function describeType(t) {
|
|
|
2145
2515
|
return s.def.options.map(describeType).join(" | ");
|
|
2146
2516
|
case "array":
|
|
2147
2517
|
return `array of ${describeType(s.def.element)}`;
|
|
2518
|
+
case "tuple":
|
|
2519
|
+
return `[${s.def.items.map(describeType).join(", ")}]`;
|
|
2148
2520
|
case "object":
|
|
2149
2521
|
return `{ ${Object.entries(s.shape).map(([k, v]) => `${k}: ${describeType(v)}`).join(", ")} }`;
|
|
2150
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,24 +257,52 @@ 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
|
|
243
266
|
checkbox "Unchecked"`,
|
|
244
267
|
Radio: `board
|
|
245
|
-
screen custom width=
|
|
268
|
+
screen custom width=380 height=230
|
|
246
269
|
radio "Selected" checked
|
|
247
270
|
radio "Not selected"
|
|
248
|
-
radio "Not selected"
|
|
271
|
+
radio "Not selected"
|
|
272
|
+
text "Side by side: put them in a row" sm muted
|
|
273
|
+
stack row gap=20
|
|
274
|
+
radio "Standard" checked
|
|
275
|
+
radio "Express"
|
|
276
|
+
radio "Pickup"`,
|
|
249
277
|
Toggle: `board
|
|
250
278
|
screen custom width=300 height=100
|
|
251
279
|
toggle "On" on
|
|
252
280
|
toggle "Off" off`,
|
|
253
281
|
Select: `board
|
|
254
|
-
screen custom width=
|
|
282
|
+
screen custom width=380 height=260
|
|
255
283
|
select "Country" value="Mexico"
|
|
256
|
-
select "Size" placeholder="Choose one" width=160
|
|
284
|
+
select "Size" placeholder="Choose one" width=160
|
|
285
|
+
stack row gap=12
|
|
286
|
+
input "City" placeholder="Monterrey" grow
|
|
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`,
|
|
257
306
|
NavBar: `board
|
|
258
307
|
screen custom width=380 height=240 padding=0 gap=0
|
|
259
308
|
navbar "leading=menu" leading=menu actions=[search, bell]
|
|
@@ -269,6 +318,10 @@ var EXAMPLES = {
|
|
|
269
318
|
screen custom width=400 height=150
|
|
270
319
|
tabs items=[Overview, Activity, Settings] active=0
|
|
271
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`,
|
|
272
325
|
List: `board
|
|
273
326
|
screen custom width=380 height=420
|
|
274
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tsquare",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Wireframes in plain text: easy for LLMs to write, fast to render as SVG or PNG.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wireframe",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"examples/*.tsq",
|
|
45
45
|
"assets/mark.png",
|
|
46
46
|
"LICENSE",
|
|
47
|
-
"README.md"
|
|
47
|
+
"README.md",
|
|
48
|
+
"CHANGELOG.md"
|
|
48
49
|
],
|
|
49
50
|
"engines": {
|
|
50
51
|
"node": ">=20"
|
|
@@ -62,7 +63,8 @@
|
|
|
62
63
|
"eval:prompts": "tsx eval/prompts.ts",
|
|
63
64
|
"eval:score": "tsx eval/score.ts",
|
|
64
65
|
"build": "tsx scripts/build.ts",
|
|
65
|
-
"prepare": "npm run build"
|
|
66
|
+
"prepare": "npm run build",
|
|
67
|
+
"test": "tsx --test test/*.test.ts"
|
|
66
68
|
},
|
|
67
69
|
"dependencies": {
|
|
68
70
|
"@fontsource/inter": "5.3.0",
|
|
@@ -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
|
|
@@ -142,8 +156,30 @@ Dropdown field (closed state).
|
|
|
142
156
|
- label: string (main text)
|
|
143
157
|
- value: string
|
|
144
158
|
- placeholder: string
|
|
159
|
+
- grow: boolean — Fill the remaining space in a row
|
|
145
160
|
- width: number | string — Fixed width, e.g. 320. Default fills the space.
|
|
146
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
|
+
|
|
147
183
|
### NavBar
|
|
148
184
|
Top app bar. Place first in a Screen.
|
|
149
185
|
- title: string (main text)
|
|
@@ -161,6 +197,11 @@ In-page tabs with an underline on the active one.
|
|
|
161
197
|
- items (required): array of string
|
|
162
198
|
- active: number
|
|
163
199
|
|
|
200
|
+
### Pagination
|
|
201
|
+
Page numbers with previous and next.
|
|
202
|
+
- pages (required): number
|
|
203
|
+
- current: number — Default 1
|
|
204
|
+
|
|
164
205
|
### List
|
|
165
206
|
Vertical list of ListItems.
|
|
166
207
|
- dividers: boolean
|