-
Notifications
You must be signed in to change notification settings - Fork 434
Expand file tree
/
Copy pathplayground-component-spec.json
More file actions
372 lines (372 loc) · 20 KB
/
playground-component-spec.json
File metadata and controls
372 lines (372 loc) · 20 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
{
"root": "Stack",
"components": {
"Card": {
"signature": "Card(children: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps | Tabs | Carousel | Stack)[], variant?: \"card\" | \"sunk\" | \"clear\", direction?: \"row\" | \"column\", gap?: \"none\" | \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"2xl\", align?: \"start\" | \"center\" | \"end\" | \"stretch\" | \"baseline\", justify?: \"start\" | \"center\" | \"end\" | \"between\" | \"around\" | \"evenly\", wrap?: boolean)",
"description": "Styled container. variant: \"card\" (default, elevated) | \"sunk\" (recessed) | \"clear\" (transparent). Always full width. Accepts all Stack flex params (default: direction \"column\"). Cards flex to share space in row/wrap layouts."
},
"CardHeader": {
"signature": "CardHeader(title?: string, subtitle?: string)",
"description": "Header with optional title and subtitle"
},
"TextContent": {
"signature": "TextContent(text: string, size?: \"small\" | \"default\" | \"large\" | \"small-heavy\" | \"large-heavy\")",
"description": "Text block. Supports markdown. Optional size: \"small\" | \"default\" | \"large\" | \"small-heavy\" | \"large-heavy\"."
},
"MarkDownRenderer": {
"signature": "MarkDownRenderer(textMarkdown: string, variant?: \"clear\" | \"card\" | \"sunk\")",
"description": "Renders markdown text with optional container variant"
},
"Callout": {
"signature": "Callout(variant: \"info\" | \"warning\" | \"error\" | \"success\" | \"neutral\", title: string, description: string, visible?: $binding<boolean>)",
"description": "Callout banner. Optional visible is a reactive $boolean — auto-dismisses after 3s by setting $visible to false."
},
"TextCallout": {
"signature": "TextCallout(variant?: \"neutral\" | \"info\" | \"warning\" | \"success\" | \"danger\", title?: string, description?: string)",
"description": "Text callout with variant, title, and description"
},
"Image": {
"signature": "Image(alt: string, src?: string)",
"description": "Image with alt text and optional URL"
},
"ImageBlock": {
"signature": "ImageBlock(src: string, alt?: string)",
"description": "Image block with loading state"
},
"ImageGallery": {
"signature": "ImageGallery(images: {src: string, alt?: string, details?: string}[])",
"description": "Gallery grid of images with modal preview"
},
"CodeBlock": {
"signature": "CodeBlock(language: string, codeString: string)",
"description": "Syntax-highlighted code block"
},
"Table": {
"signature": "Table(columns: Col[])",
"description": "Data table — column-oriented. Each Col holds its own data array."
},
"Col": {
"signature": "Col(label: string, data: any, type?: \"string\" | \"number\" | \"action\")",
"description": "Column definition — holds label + data array"
},
"BarChart": {
"signature": "BarChart(labels: string[], series: Series[], variant?: \"grouped\" | \"stacked\", xLabel?: string, yLabel?: string)",
"description": "Vertical bars; use for comparing values across categories with one or more series"
},
"LineChart": {
"signature": "LineChart(labels: string[], series: Series[], variant?: \"linear\" | \"natural\" | \"step\", xLabel?: string, yLabel?: string)",
"description": "Lines over categories; use for trends and continuous data over time"
},
"AreaChart": {
"signature": "AreaChart(labels: string[], series: Series[], variant?: \"linear\" | \"natural\" | \"step\", xLabel?: string, yLabel?: string)",
"description": "Filled area under lines; use for cumulative totals or volume trends over time"
},
"RadarChart": {
"signature": "RadarChart(labels: string[], series: Series[])",
"description": "Spider/web chart; use for comparing multiple variables across one or more entities"
},
"HorizontalBarChart": {
"signature": "HorizontalBarChart(labels: string[], series: Series[], variant?: \"grouped\" | \"stacked\", xLabel?: string, yLabel?: string)",
"description": "Horizontal bars; prefer when category labels are long or for ranked lists"
},
"Series": {
"signature": "Series(category: string, values: number[])",
"description": "One data series"
},
"PieChart": {
"signature": "PieChart(labels: string[], values: number[], variant?: \"pie\" | \"donut\")",
"description": "Circular slices; use plucked arrays: PieChart(data.categories, data.values)"
},
"RadialChart": {
"signature": "RadialChart(labels: string[], values: number[])",
"description": "Radial bars; use plucked arrays: RadialChart(data.categories, data.values)"
},
"SingleStackedBarChart": {
"signature": "SingleStackedBarChart(labels: string[], values: number[])",
"description": "Single horizontal stacked bar; use plucked arrays: SingleStackedBarChart(data.categories, data.values)"
},
"Slice": {
"signature": "Slice(category: string, value: number)",
"description": "One slice with label and numeric value"
},
"ScatterChart": {
"signature": "ScatterChart(datasets: ScatterSeries[], xLabel?: string, yLabel?: string)",
"description": "X/Y scatter plot; use for correlations, distributions, and clustering"
},
"ScatterSeries": {
"signature": "ScatterSeries(name: string, points: Point[])",
"description": "Named dataset"
},
"Point": {
"signature": "Point(x: number, y: number, z?: number)",
"description": "Data point with numeric coordinates"
},
"Form": {
"signature": "Form(name: string, buttons: Buttons, fields?: FormControl[])",
"description": "Form container with fields and explicit action buttons"
},
"FormControl": {
"signature": "FormControl(label: string, input: Input | TextArea | Select | DatePicker | Slider | CheckBoxGroup | RadioGroup, hint?: string)",
"description": "Field with label, input component, and optional hint text"
},
"Label": {
"signature": "Label(text: string)",
"description": "Text label"
},
"Input": {
"signature": "Input(name: string, placeholder?: string, type?: \"text\" | \"email\" | \"password\" | \"number\" | \"url\", rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<string>)",
"description": ""
},
"TextArea": {
"signature": "TextArea(name: string, placeholder?: string, rows?: number, rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<string>)",
"description": ""
},
"Select": {
"signature": "Select(name: string, items: SelectItem[], placeholder?: string, rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<string>)",
"description": ""
},
"SelectItem": {
"signature": "SelectItem(value: string, label: string)",
"description": "Option for Select"
},
"DatePicker": {
"signature": "DatePicker(name: string, mode?: \"single\" | \"range\", rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<any>)",
"description": ""
},
"Slider": {
"signature": "Slider(name: string, variant: \"continuous\" | \"discrete\", min: number, max: number, step?: number, defaultValue?: number[], label?: string, rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<number[]>)",
"description": "Numeric slider input; supports continuous and discrete (stepped) variants"
},
"CheckBoxGroup": {
"signature": "CheckBoxGroup(name: string, items: CheckBoxItem[], rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<Record<string, boolean>>)",
"description": ""
},
"CheckBoxItem": {
"signature": "CheckBoxItem(label: string, description: string, name: string, defaultChecked?: boolean)",
"description": ""
},
"RadioGroup": {
"signature": "RadioGroup(name: string, items: RadioItem[], defaultValue?: string, rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}, value?: $binding<string>)",
"description": ""
},
"RadioItem": {
"signature": "RadioItem(label: string, description: string, value: string)",
"description": ""
},
"SwitchGroup": {
"signature": "SwitchGroup(name: string, items: SwitchItem[], variant?: \"clear\" | \"card\" | \"sunk\", value?: $binding<Record<string, boolean>>)",
"description": "Group of switch toggles"
},
"SwitchItem": {
"signature": "SwitchItem(label?: string, description?: string, name: string, defaultChecked?: boolean)",
"description": "Individual switch toggle"
},
"Button": {
"signature": "Button(label: string, action?: ActionExpression, variant?: \"primary\" | \"secondary\" | \"tertiary\", type?: \"normal\" | \"destructive\", size?: \"extra-small\" | \"small\" | \"medium\" | \"large\")",
"description": "Clickable button"
},
"Buttons": {
"signature": "Buttons(buttons: Button[], direction?: \"row\" | \"column\")",
"description": "Group of Button components. direction: \"row\" (default) | \"column\"."
},
"Stack": {
"signature": "Stack(children: any[], direction?: \"row\" | \"column\", gap?: \"none\" | \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"2xl\", align?: \"start\" | \"center\" | \"end\" | \"stretch\" | \"baseline\", justify?: \"start\" | \"center\" | \"end\" | \"between\" | \"around\" | \"evenly\", wrap?: boolean)",
"description": "Flex container. direction: \"row\"|\"column\" (default \"column\"). gap: \"none\"|\"xs\"|\"s\"|\"m\"|\"l\"|\"xl\"|\"2xl\" (default \"m\"). align: \"start\"|\"center\"|\"end\"|\"stretch\"|\"baseline\". justify: \"start\"|\"center\"|\"end\"|\"between\"|\"around\"|\"evenly\"."
},
"Tabs": {
"signature": "Tabs(items: TabItem[])",
"description": "Tabbed container"
},
"TabItem": {
"signature": "TabItem(value: string, trigger: string, content: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[])",
"description": "value is unique id, trigger is tab label, content is array of components"
},
"Accordion": {
"signature": "Accordion(items: AccordionItem[])",
"description": "Collapsible sections"
},
"AccordionItem": {
"signature": "AccordionItem(value: string, trigger: string, content: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[])",
"description": "value is unique id, trigger is section title"
},
"Steps": {
"signature": "Steps(items: StepsItem[])",
"description": "Step-by-step guide"
},
"StepsItem": {
"signature": "StepsItem(title: string, details: string)",
"description": "title and details text for one step"
},
"Carousel": {
"signature": "Carousel(children: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[][], variant?: \"card\" | \"sunk\")",
"description": "Horizontal scrollable carousel"
},
"Separator": {
"signature": "Separator(orientation?: \"horizontal\" | \"vertical\", decorative?: boolean)",
"description": "Visual divider between content sections"
},
"TagBlock": {
"signature": "TagBlock(tags: string[])",
"description": "tags is an array of strings"
},
"Tag": {
"signature": "Tag(text: string, icon?: string, size?: \"sm\" | \"md\" | \"lg\", variant?: \"neutral\" | \"info\" | \"success\" | \"warning\" | \"danger\")",
"description": "Styled tag/badge with optional icon and variant"
},
"Modal": {
"signature": "Modal(title: string, open?: $binding<boolean>, children: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[], size?: \"sm\" | \"md\" | \"lg\")",
"description": "Modal dialog. open is a reactive $boolean binding — set to true to open, X/Escape/backdrop auto-closes. Put Form with buttons inside children."
}
},
"componentGroups": [
{
"name": "Layout",
"components": [
"Stack",
"Tabs",
"TabItem",
"Accordion",
"AccordionItem",
"Steps",
"StepsItem",
"Carousel",
"Separator",
"Modal"
],
"notes": [
"- For grid-like layouts, use Stack with direction \"row\" and wrap set to true.",
"- Prefer justify \"start\" (or omit justify) with wrap=true for stable columns instead of uneven gutters.",
"- Use nested Stacks when you need explicit rows/sections.",
"- Show/hide sections: $editId != \"\" ? Card([editForm]) : null",
"- Modal: Modal(\"Title\", $showModal, [content]) — $showModal is boolean, X/Escape auto-closes. Put Form with its own buttons inside children.",
"- Use Tabs for alternative views (chart types, data sections) — no $variable needed",
"- Shared filter across Tabs: same $days binding in Query args works across all TabItems"
]
},
{
"name": "Content",
"components": [
"Card",
"CardHeader",
"TextContent",
"MarkDownRenderer",
"Callout",
"TextCallout",
"Image",
"ImageBlock",
"ImageGallery",
"CodeBlock"
],
"notes": [
"- Use Cards to group related KPIs or sections. Stack with direction \"row\" for side-by-side layouts.",
"- Success toast: Callout(\"success\", \"Saved\", \"Done.\", $showSuccess) — use @Set($showSuccess, true) in save action, auto-dismisses after 3s. For errors: result.status == \"error\" ? Callout(\"error\", \"Failed\", result.error) : null",
"- KPI card: Card([TextContent(\"Label\", \"small\"), TextContent(\"\" + @Count(@Filter(data.rows, \"field\", \"==\", \"value\")), \"large-heavy\")])"
]
},
{
"name": "Tables",
"components": [
"Table",
"Col"
],
"notes": [
"- Table is COLUMN-oriented: Table([Col(\"Label\", dataArray), Col(\"Count\", countArray, \"number\")]). Use array pluck for data: data.rows.fieldName",
"- Col data can be component arrays for styled cells: Col(\"Status\", @Each(data.rows, \"item\", Tag(item.status, null, \"sm\", item.status == \"open\" ? \"success\" : \"danger\")))",
"- Row actions: Col(\"Actions\", @Each(data.rows, \"t\", Button(\"Edit\", Action([@Set($showEdit, true), @Set($editId, t.id)]))))",
"- Sortable: sorted = @Sort(data.rows, $sortField, \"desc\"). Bind $sortField to Select. Use sorted.fieldName for Col data",
"- Searchable: filtered = @Filter(data.rows, \"title\", \"contains\", $search). Bind $search to Input",
"- Chain sort + filter: filtered = @Filter(...) then sorted = @Sort(filtered, ...) — use sorted for both Table and Charts",
"- Empty state: @Count(data.rows) > 0 ? Table([...]) : TextContent(\"No data yet\")"
]
},
{
"name": "Charts (2D)",
"components": [
"BarChart",
"LineChart",
"AreaChart",
"RadarChart",
"HorizontalBarChart",
"Series"
],
"notes": [
"- Charts accept column arrays: LineChart(labels, [Series(\"Name\", values)]). Use array pluck: LineChart(data.rows.day, [Series(\"Views\", data.rows.views)])",
"- Use Cards to wrap charts with CardHeader for titled sections",
"- Chart + Table from same source: use @Sort or @Filter result for both LineChart and Table Col data",
"- Multiple chart views: use Tabs — Tabs([TabItem(\"line\", \"Line\", [LineChart(...)]), TabItem(\"bar\", \"Bar\", [BarChart(...)])])"
]
},
{
"name": "Charts (1D)",
"components": [
"PieChart",
"RadialChart",
"SingleStackedBarChart",
"Slice"
],
"notes": [
"- PieChart and BarChart need NUMBERS, not objects. For list data, use @Count(@Filter(...)) to aggregate:",
"- PieChart from list: `PieChart([\"Low\", \"Med\", \"High\"], [@Count(@Filter(data.rows, \"priority\", \"==\", \"low\")), @Count(@Filter(data.rows, \"priority\", \"==\", \"medium\")), @Count(@Filter(data.rows, \"priority\", \"==\", \"high\"))], \"donut\")`",
"- KPI from count: `TextContent(\"\" + @Count(@Filter(data.rows, \"status\", \"==\", \"open\")), \"large-heavy\")`"
]
},
{
"name": "Charts (Scatter)",
"components": [
"ScatterChart",
"ScatterSeries",
"Point"
]
},
{
"name": "Forms",
"components": [
"Form",
"FormControl",
"Label",
"Input",
"TextArea",
"Select",
"SelectItem",
"DatePicker",
"Slider",
"CheckBoxGroup",
"CheckBoxItem",
"RadioGroup",
"RadioItem",
"SwitchGroup",
"SwitchItem"
],
"notes": [
"- For Form fields, define EACH FormControl as its own reference — do NOT inline all controls in one array. This allows progressive field-by-field streaming.",
"- NEVER nest Form inside Form — each Form should be a standalone container.",
"- Form requires explicit buttons. Always pass a Buttons(...) reference as the third Form argument.",
"- rules is an optional object: {required: true, email: true, minLength: 8, maxLength: 100}",
"- Available rules: required, email, min, max, minLength, maxLength, pattern, url, numeric",
"- The renderer shows error messages automatically — do NOT generate error text in the UI",
"- Conditional fields: $country == \"US\" ? stateField : $country == \"UK\" ? postcodeField : addressField",
"- Edit form in Modal: Modal(\"Edit\", $showEdit, [Form(\"edit\", Buttons([saveBtn, cancelBtn]), [fields...])]). Save button should include @Set($showEdit, false) to close modal."
]
},
{
"name": "Buttons",
"components": [
"Button",
"Buttons"
],
"notes": [
"- Toggle in @Each: @Each(rows, \"t\", Button(t.status == \"open\" ? \"Close\" : \"Reopen\", Action([...])))"
]
},
{
"name": "Data Display",
"components": [
"TagBlock",
"Tag"
],
"notes": [
"- Color-mapped Tag: Tag(value, null, \"sm\", value == \"high\" ? \"danger\" : value == \"medium\" ? \"warning\" : \"neutral\")"
]
}
]
}