millwork 0.1.0__py3-none-any.whl

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.
Files changed (70) hide show
  1. millwork/__init__.py +45 -0
  2. millwork/_bundle/dist/.vite/manifest.json +92 -0
  3. millwork/_bundle/dist/charts-inner-Cq0vWbjt.js +39 -0
  4. millwork/_bundle/dist/favicon.svg +15 -0
  5. millwork/_bundle/dist/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  6. millwork/_bundle/dist/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  7. millwork/_bundle/dist/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  8. millwork/_bundle/dist/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  9. millwork/_bundle/dist/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  10. millwork/_bundle/dist/icons.svg +24 -0
  11. millwork/_bundle/dist/index.global-cOFus1M1.js +952 -0
  12. millwork/_bundle/dist/index.modern-CITEfQrV.js +135 -0
  13. millwork/_bundle/dist/lib-3DI92Zxk.js +1 -0
  14. millwork/_bundle/dist/main-BoZpFGOH.js +64 -0
  15. millwork/_bundle/dist/main-F2tEXmC5.css +2 -0
  16. millwork/_bundle/dist/react-markdown-DLcbNoI3.js +14 -0
  17. millwork/_bundle/dist/remark-gfm-DoHjwpPg.js +16 -0
  18. millwork/app.py +726 -0
  19. millwork/auth.py +877 -0
  20. millwork/chat.py +397 -0
  21. millwork/cli.py +316 -0
  22. millwork/context.py +42 -0
  23. millwork/files.py +450 -0
  24. millwork/forms.py +349 -0
  25. millwork/manual/__init__.py +449 -0
  26. millwork/manual/components/Hello.jsx +24 -0
  27. millwork/manual/components/Voice.jsx +46 -0
  28. millwork/manual/pages/app/auth.md +188 -0
  29. millwork/manual/pages/app/pages.md +200 -0
  30. millwork/manual/pages/app/running.md +242 -0
  31. millwork/manual/pages/app/state.md +151 -0
  32. millwork/manual/pages/app/testing.md +147 -0
  33. millwork/manual/pages/app/theming.md +122 -0
  34. millwork/manual/pages/components/charts.md +263 -0
  35. millwork/manual/pages/components/chat.md +280 -0
  36. millwork/manual/pages/components/custom.md +148 -0
  37. millwork/manual/pages/components/data.md +112 -0
  38. millwork/manual/pages/components/feedback.md +212 -0
  39. millwork/manual/pages/components/files.md +194 -0
  40. millwork/manual/pages/components/form.md +135 -0
  41. millwork/manual/pages/components/inputs.md +360 -0
  42. millwork/manual/pages/components/layout.md +154 -0
  43. millwork/manual/pages/components/tasks.md +140 -0
  44. millwork/manual/pages/components/text.md +145 -0
  45. millwork/manual/pages/index.md +67 -0
  46. millwork/manual/public/fonts/InterVariable.woff2 +0 -0
  47. millwork/manual/public/fonts/LICENSE.txt +2 -0
  48. millwork/manual/public/mark.svg +15 -0
  49. millwork/manual/public/theme.json +97 -0
  50. millwork/nodes.py +300 -0
  51. millwork/protocol.py +238 -0
  52. millwork/proxy.py +122 -0
  53. millwork/py.typed +0 -0
  54. millwork/session.py +1422 -0
  55. millwork/shell.py +226 -0
  56. millwork/tasks.py +473 -0
  57. millwork/testing.py +336 -0
  58. millwork/theme.py +338 -0
  59. millwork/ui/__init__.py +725 -0
  60. millwork/ui/charts.py +214 -0
  61. millwork/ui/chat.py +97 -0
  62. millwork/ui/files.py +101 -0
  63. millwork/ui/form.py +102 -0
  64. millwork/ui/table.py +145 -0
  65. millwork/ws.py +220 -0
  66. millwork-0.1.0.dist-info/METADATA +131 -0
  67. millwork-0.1.0.dist-info/RECORD +70 -0
  68. millwork-0.1.0.dist-info/WHEEL +4 -0
  69. millwork-0.1.0.dist-info/entry_points.txt +2 -0
  70. millwork-0.1.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,725 @@
1
+ """The catalogue, as Python classes.
2
+
3
+ Every component is a Pydantic model whose props the bundle understands.
4
+ Stateful components take ``value`` (server-owned) or ``default``
5
+ (client-owned), never both; ``_owner`` enforces that.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import datetime as dt
11
+ from typing import Any, ClassVar, Literal
12
+
13
+ from pydantic import BaseModel, ConfigDict, field_validator, model_validator
14
+
15
+ from millwork.files import FileRef
16
+ from millwork.forms import FormIssues, Issue
17
+ from millwork.nodes import Component, Text
18
+ from millwork.ui.charts import AreaChart, BarChart, LineChart
19
+ from millwork.ui.chat import Accept, Action, Chat, Command
20
+ from millwork.ui.files import Download, FileCard, FileUpload, Image, Pdf
21
+ from millwork.ui.form import Form
22
+ from millwork.ui.table import Column, MenuItem, Table
23
+
24
+ Tone = Literal["default", "muted", "primary", "accent", "success", "warning", "destructive"]
25
+ Variant = Literal["primary", "secondary", "outline", "ghost", "link", "destructive"]
26
+ Size = Literal["sm", "md", "lg"]
27
+ Align = Literal["start", "center", "end", "stretch", "baseline"]
28
+ Justify = Literal["start", "center", "end", "between", "around", "evenly"]
29
+
30
+
31
+ class Option(BaseModel):
32
+ """One choice in a Select, RadioGroup, CheckboxGroup, ToggleGroup."""
33
+
34
+ model_config = ConfigDict(extra="forbid")
35
+
36
+ value: str
37
+ label: str | None = None
38
+ description: str | None = None
39
+ icon: str | None = None
40
+ disabled: bool = False
41
+ group: str | None = None
42
+
43
+
44
+ def as_options(values: Any) -> list[Option]:
45
+ """Accept strings, ``(value, label)`` pairs, dicts or ``Option``s."""
46
+ out: list[Option] = []
47
+ for v in values or []:
48
+ if isinstance(v, Option):
49
+ out.append(v)
50
+ elif isinstance(v, str):
51
+ out.append(Option(value=v, label=v))
52
+ elif isinstance(v, tuple | list) and len(v) == 2:
53
+ out.append(Option(value=str(v[0]), label=str(v[1])))
54
+ elif isinstance(v, dict):
55
+ out.append(Option(**v))
56
+ else:
57
+ raise TypeError(f"not an option: {v!r}")
58
+ return out
59
+
60
+
61
+ class _Owned(Component):
62
+ """A component with interaction state and one owner."""
63
+
64
+ owner_pairs: ClassVar[tuple[tuple[str, str], ...]] = (
65
+ ("value", "default"),
66
+ ("checked", "default_checked"),
67
+ ("open", "default_open"),
68
+ )
69
+
70
+ @model_validator(mode="after")
71
+ def _owner(self) -> _Owned:
72
+ for server, client in self.owner_pairs:
73
+ if server in self.model_fields_set and client in self.model_fields_set:
74
+ raise ValueError(
75
+ f"{type(self).__name__} takes {server} (server-owned) or {client} "
76
+ "(client-owned), not both"
77
+ )
78
+ return self
79
+
80
+
81
+ # ------------------------------------------------------------------ layout
82
+
83
+
84
+ class Header(Component):
85
+ """The app bar: the theme's logo, a title, links, the page's actions (children)
86
+ and the user menu with the theme choice and sign-out."""
87
+
88
+ type: ClassVar[str] = "Header"
89
+ title: str | None = None
90
+ logo: bool = True
91
+ links: list[Link] = []
92
+ user_menu: bool = True
93
+
94
+
95
+ class Logo(Component):
96
+ """The theme's logo, the light or dark file with the scheme; nothing when the
97
+ theme has none. For a page outside the app bar: a login-like page, an error page."""
98
+
99
+ type: ClassVar[str] = "Logo"
100
+
101
+
102
+ class Page(Component):
103
+ type: ClassVar[str] = "Page"
104
+ title: str | None = None
105
+ header: Header | None = None
106
+ padding: bool = True
107
+ max_width: str | None = None
108
+
109
+
110
+ class Stack(Component):
111
+ type: ClassVar[str] = "Stack"
112
+ gap: int = 4
113
+ align: Align = "stretch"
114
+ justify: Justify = "start"
115
+ grow: bool = False
116
+ divided: bool = False
117
+
118
+
119
+ class Row(Component):
120
+ type: ClassVar[str] = "Row"
121
+ gap: int = 4
122
+ align: Align = "center"
123
+ justify: Justify = "start"
124
+ wrap: bool = False
125
+
126
+
127
+ class Grid(Component):
128
+ type: ClassVar[str] = "Grid"
129
+ cols: int | dict[str, int] = 2
130
+ gap: int = 4
131
+
132
+
133
+ class Card(Component):
134
+ type: ClassVar[str] = "Card"
135
+ title: str | None = None
136
+ description: str | None = None
137
+ footer: Component | None = None
138
+ padding: bool = True
139
+ tone: Tone | None = None
140
+ on_click: Any = None
141
+
142
+
143
+ class Split(Component):
144
+ """Resizable panes. Sizes are percentages; the user's drag is client-owned."""
145
+
146
+ type: ClassVar[str] = "Split"
147
+ sizes: list[int] | None = None
148
+ min_sizes: list[int] | None = None
149
+ direction: Literal["horizontal", "vertical"] = "horizontal"
150
+ # True: every pane collapses when dragged under its minimum; a list says which.
151
+ collapsible: bool | list[bool] = False
152
+
153
+
154
+ class Sidebar(_Owned):
155
+ """A collapsible side panel. ``Page`` with a ``Sidebar`` child lays out around it."""
156
+
157
+ type: ClassVar[str] = "Sidebar"
158
+ side: Literal["left", "right"] = "left"
159
+ width: str | None = None
160
+ collapsible: Literal["offcanvas", "icon", "none"] = "offcanvas"
161
+ open: bool | None = None
162
+ default_open: bool | None = None
163
+ title: str | None = None
164
+ on_toggle: Any = None
165
+
166
+
167
+ class Tabs(_Owned):
168
+ type: ClassVar[str] = "Tabs"
169
+ value: str | None = None
170
+ default: str | None = None
171
+ variant: Literal["line", "pill"] = "line"
172
+ on_change: Any = None
173
+
174
+
175
+ class Tab(Component):
176
+ type: ClassVar[str] = "Tab"
177
+ id: str
178
+ label: str
179
+ icon: str | None = None
180
+ badge: str | None = None
181
+ disabled: bool = False
182
+
183
+
184
+ class Accordion(_Owned):
185
+ type: ClassVar[str] = "Accordion"
186
+ multiple: bool = False
187
+ value: list[str] | None = None
188
+ default: list[str] | None = None
189
+ on_change: Any = None
190
+
191
+
192
+ class AccordionItem(Component):
193
+ type: ClassVar[str] = "AccordionItem"
194
+ id: str
195
+ title: str
196
+
197
+
198
+ class Collapsible(_Owned):
199
+ type: ClassVar[str] = "Collapsible"
200
+ title: str
201
+ open: bool | None = None
202
+ default_open: bool | None = None
203
+ on_toggle: Any = None
204
+
205
+
206
+ class Dialog(Component):
207
+ """``open`` is always server-owned: a dialog is state the app reasons about."""
208
+
209
+ type: ClassVar[str] = "Dialog"
210
+ open: bool = False
211
+ title: str | None = None
212
+ description: str | None = None
213
+ footer: Component | None = None
214
+ size: Literal["sm", "md", "lg", "xl", "full"] = "md"
215
+ dismissable: bool = True
216
+ on_close: Any = None
217
+
218
+
219
+ class Sheet(Component):
220
+ type: ClassVar[str] = "Sheet"
221
+ open: bool = False
222
+ side: Literal["left", "right", "top", "bottom"] = "right"
223
+ title: str | None = None
224
+ description: str | None = None
225
+ on_close: Any = None
226
+
227
+
228
+ class ScrollArea(Component):
229
+ type: ClassVar[str] = "ScrollArea"
230
+ height: str | None = None
231
+
232
+
233
+ class Separator(Component):
234
+ type: ClassVar[str] = "Separator"
235
+ orientation: Literal["horizontal", "vertical"] = "horizontal"
236
+ label: str | None = None
237
+
238
+
239
+ class Empty(Component):
240
+ type: ClassVar[str] = "Empty"
241
+ title: str
242
+ description: str | None = None
243
+ icon: str | None = None
244
+ action: Component | None = None
245
+
246
+
247
+ # ------------------------------------------------------------------ text
248
+
249
+
250
+ class Markdown(Component):
251
+ type: ClassVar[str] = "Markdown"
252
+ content: str = ""
253
+ tight: bool = False
254
+
255
+ def __init__(self, content: str = "", **props: Any) -> None:
256
+ super().__init__(content=content, **props)
257
+
258
+
259
+ class Code(Component):
260
+ type: ClassVar[str] = "Code"
261
+ content: str = ""
262
+ language: str | None = None
263
+ title: str | None = None
264
+ copyable: bool = True
265
+ wrap: bool = False
266
+ max_height: str | None = None
267
+
268
+ def __init__(self, content: str = "", **props: Any) -> None:
269
+ super().__init__(content=content, **props)
270
+
271
+
272
+ class Badge(Component):
273
+ type: ClassVar[str] = "Badge"
274
+ label: str = ""
275
+ tone: Tone = "default"
276
+ icon: str | None = None
277
+
278
+ def __init__(self, label: str = "", **props: Any) -> None:
279
+ super().__init__(label=label, **props)
280
+
281
+
282
+ class Kbd(Component):
283
+ type: ClassVar[str] = "Kbd"
284
+ keys: list[str]
285
+
286
+
287
+ def safe_href(value: str) -> str:
288
+ """Only http(s), mailto and same-origin paths: nothing a browser would execute."""
289
+ v = value.strip()
290
+ lower = v.lower()
291
+ if v.startswith("/") and not v.startswith("//") and "\\" not in v:
292
+ return v
293
+ if lower.startswith(("http://", "https://", "mailto:")):
294
+ return v
295
+ raise ValueError(f"unsafe href {value!r}: use http(s), mailto or a path")
296
+
297
+
298
+ class Link(Component):
299
+ type: ClassVar[str] = "Link"
300
+ label: str
301
+ href: str
302
+ target: Literal["_self", "_blank"] = "_self"
303
+ icon: str | None = None
304
+
305
+ _href = field_validator("href")(staticmethod(safe_href))
306
+
307
+
308
+ Header.model_rebuild()
309
+
310
+
311
+ class Stat(Component):
312
+ """A headline number. ``delta`` is the change, shown with its direction
313
+ and coloured by whether up is ``good``; ``trend`` draws a sparkline."""
314
+
315
+ type: ClassVar[str] = "Stat"
316
+ label: str
317
+ value: str
318
+ hint: str | None = None
319
+ tone: Tone | None = None
320
+ icon: str | None = None
321
+ delta: float | None = None
322
+ delta_format: Literal["percent", "number"] = "percent"
323
+ good: Literal["up", "down"] = "up"
324
+ trend: list[float] | None = None
325
+
326
+
327
+ class Alert(Component):
328
+ type: ClassVar[str] = "Alert"
329
+ title: str | None = None
330
+ description: str | None = None
331
+ tone: Tone = "default"
332
+ icon: str | None = None
333
+ dismissable: bool = False
334
+ actions: list[Component] = []
335
+ on_dismiss: Any = None
336
+
337
+
338
+ # ------------------------------------------------------------------ inputs
339
+
340
+
341
+ class Button(Component):
342
+ type: ClassVar[str] = "Button"
343
+ label: str = ""
344
+ variant: Variant = "primary"
345
+ size: Size = "md"
346
+ icon: str | None = None
347
+ icon_position: Literal["start", "end"] = "start"
348
+ disabled: bool = False
349
+ loading: bool = False
350
+ tooltip: str | None = None
351
+ confirm: str | None = None
352
+ # The key of a Form on the page: the button submits it, from anywhere.
353
+ form: str | None = None
354
+ on_click: Any = None
355
+
356
+ def __init__(self, label: str = "", **props: Any) -> None:
357
+ super().__init__(label=label, **props)
358
+
359
+
360
+ class ButtonGroup(Component):
361
+ """Buttons joined into one control."""
362
+
363
+ type: ClassVar[str] = "ButtonGroup"
364
+
365
+
366
+ class DropdownMenu(Component):
367
+ """A menu behind a trigger button. ``on_select`` receives the item's id."""
368
+
369
+ type: ClassVar[str] = "DropdownMenu"
370
+ trigger: Component
371
+ items: list[MenuItem]
372
+ align: Literal["start", "end"] = "start"
373
+ on_select: Any = None
374
+
375
+
376
+ class Field(Component):
377
+ """A label, help text and an error around one control, for a form built by hand."""
378
+
379
+ type: ClassVar[str] = "Field"
380
+ label: str
381
+ help: str | None = None
382
+ error: str | None = None
383
+ required: bool = False
384
+
385
+
386
+ class ToggleGroup(_Owned):
387
+ type: ClassVar[str] = "ToggleGroup"
388
+ options: list[Option]
389
+ value: str | list[str] | None = None
390
+ default: str | list[str] | None = None
391
+ multiple: bool = False
392
+ size: Size = "md"
393
+ on_change: Any = None
394
+
395
+ _options = field_validator("options", mode="before")(staticmethod(as_options))
396
+
397
+
398
+ class Input(_Owned):
399
+ type: ClassVar[str] = "Input"
400
+ value: str | None = None
401
+ default: str | None = None
402
+ placeholder: str | None = None
403
+ kind: Literal["text", "password", "email", "number", "search", "url"] = "text"
404
+ prefix_icon: str | None = None
405
+ suffix: str | None = None
406
+ disabled: bool = False
407
+ readonly: bool = False
408
+ clearable: bool = False
409
+ debounce_ms: int = 250
410
+ on_change: Any = None
411
+ on_submit: Any = None
412
+
413
+
414
+ class Textarea(_Owned):
415
+ type: ClassVar[str] = "Textarea"
416
+ value: str | None = None
417
+ default: str | None = None
418
+ placeholder: str | None = None
419
+ rows: int = 3
420
+ autosize: bool = False
421
+ max_length: int | None = None
422
+ disabled: bool = False
423
+ readonly: bool = False
424
+ debounce_ms: int = 250
425
+ on_change: Any = None
426
+ on_submit: Any = None
427
+
428
+
429
+ class NumberInput(_Owned):
430
+ type: ClassVar[str] = "NumberInput"
431
+ value: float | None = None
432
+ default: float | None = None
433
+ min: float | None = None
434
+ max: float | None = None
435
+ step: float | None = None
436
+ suffix: str | None = None
437
+ placeholder: str | None = None
438
+ disabled: bool = False
439
+ on_change: Any = None
440
+
441
+
442
+ class Combobox(_Owned):
443
+ """A searchable single choice. ``options`` filters in the tab; ``on_search(query)``
444
+ returns the options for what was typed, answered over the socket without a render.
445
+ ``allow_custom`` lets typed text that matches nothing be the value."""
446
+
447
+ type: ClassVar[str] = "Combobox"
448
+ options: list[Option] = []
449
+ value: str | None = None
450
+ default: str | None = None
451
+ placeholder: str | None = None
452
+ allow_custom: bool = False
453
+ clearable: bool = False
454
+ disabled: bool = False
455
+ on_change: Any = None
456
+ on_search: Any = None
457
+
458
+ _options = field_validator("options", mode="before")(staticmethod(as_options))
459
+
460
+
461
+ class MultiSelect(_Owned):
462
+ """Several choices from a list, shown as chips; typing filters the list."""
463
+
464
+ type: ClassVar[str] = "MultiSelect"
465
+ options: list[Option]
466
+ value: list[str] | None = None
467
+ default: list[str] | None = None
468
+ placeholder: str | None = None
469
+ searchable: bool = True
470
+ # At most this many chosen; None is no limit.
471
+ max: int | None = None
472
+ # The chips can be dragged into order, or moved with Alt and the arrow keys.
473
+ sortable: bool = False
474
+ disabled: bool = False
475
+ on_change: Any = None
476
+
477
+ _options = field_validator("options", mode="before")(staticmethod(as_options))
478
+
479
+
480
+ def _iso_date(value: Any) -> Any:
481
+ if isinstance(value, dt.datetime):
482
+ return value.date().isoformat()
483
+ if isinstance(value, dt.date):
484
+ return value.isoformat()
485
+ if isinstance(value, list | tuple):
486
+ return [_iso_date(v) for v in value]
487
+ return value
488
+
489
+
490
+ class DatePicker(_Owned):
491
+ """A date, or with ``range`` a ``[start, end]`` pair, as ISO strings (``date``
492
+ objects are accepted and converted). ``min`` and ``max`` bound what can be picked."""
493
+
494
+ type: ClassVar[str] = "DatePicker"
495
+ value: str | list[str | None] | None = None
496
+ default: str | list[str | None] | None = None
497
+ min: str | None = None
498
+ max: str | None = None
499
+ range: bool = False
500
+ placeholder: str | None = None
501
+ clearable: bool = False
502
+ disabled: bool = False
503
+ on_change: Any = None
504
+
505
+ _dates = field_validator("value", "default", "min", "max", mode="before")(
506
+ staticmethod(_iso_date)
507
+ )
508
+
509
+
510
+ class Select(_Owned):
511
+ type: ClassVar[str] = "Select"
512
+ options: list[Option]
513
+ value: str | None = None
514
+ default: str | None = None
515
+ placeholder: str | None = None
516
+ clearable: bool = False
517
+ disabled: bool = False
518
+ on_change: Any = None
519
+
520
+ _options = field_validator("options", mode="before")(staticmethod(as_options))
521
+
522
+
523
+ class Checkbox(_Owned):
524
+ type: ClassVar[str] = "Checkbox"
525
+ label: str | None = None
526
+ description: str | None = None
527
+ checked: bool | None = None
528
+ default_checked: bool | None = None
529
+ disabled: bool = False
530
+ on_change: Any = None
531
+
532
+
533
+ class CheckboxGroup(_Owned):
534
+ type: ClassVar[str] = "CheckboxGroup"
535
+ options: list[Option]
536
+ value: list[str] | None = None
537
+ default: list[str] | None = None
538
+ label: str | None = None
539
+ disabled: bool = False
540
+ on_change: Any = None
541
+
542
+ _options = field_validator("options", mode="before")(staticmethod(as_options))
543
+
544
+
545
+ class RadioGroup(_Owned):
546
+ type: ClassVar[str] = "RadioGroup"
547
+ options: list[Option]
548
+ value: str | None = None
549
+ default: str | None = None
550
+ label: str | None = None
551
+ orientation: Literal["vertical", "horizontal"] = "vertical"
552
+ disabled: bool = False
553
+ on_change: Any = None
554
+
555
+ _options = field_validator("options", mode="before")(staticmethod(as_options))
556
+
557
+
558
+ class Switch(_Owned):
559
+ type: ClassVar[str] = "Switch"
560
+ label: str | None = None
561
+ description: str | None = None
562
+ checked: bool | None = None
563
+ default_checked: bool | None = None
564
+ disabled: bool = False
565
+ on_change: Any = None
566
+
567
+
568
+ class Slider(_Owned):
569
+ type: ClassVar[str] = "Slider"
570
+ min: float = 0
571
+ max: float = 100
572
+ step: float = 1
573
+ value: float | None = None
574
+ default: float | None = None
575
+ show_value: bool = True
576
+ # Labelled points under the track: values, or value to label.
577
+ marks: list[float] | dict[float, str] | None = None
578
+ disabled: bool = False
579
+ on_change: Any = None
580
+
581
+ def wire_extra(self) -> dict[str, Any]:
582
+ if self.marks is None:
583
+ return {}
584
+ if isinstance(self.marks, dict):
585
+ return {"marks": [{"value": v, "label": label} for v, label in self.marks.items()]}
586
+ return {"marks": [{"value": v, "label": None} for v in self.marks]}
587
+
588
+
589
+ class Tooltip(Component):
590
+ type: ClassVar[str] = "Tooltip"
591
+ content: str
592
+
593
+
594
+ class Custom(Component):
595
+ """An app's own component: ``components/<name>.jsx``, compiled in the tab.
596
+
597
+ ``props`` arrive as React props next to ``emit`` and ``children``; events
598
+ the component emits reach ``on_event`` as ``(session, (name, payload))``,
599
+ or a handler per event name through ``events``.
600
+ """
601
+
602
+ type: ClassVar[str] = "Custom"
603
+ wire_exclude: ClassVar[frozenset[str]] = frozenset({"name", "props", "events"})
604
+
605
+ name: str
606
+ props: dict[str, Any] = {}
607
+ events: dict[str, Any] = {}
608
+ on_event: Any = None
609
+
610
+ def __init__(self, name: str, *children: Any, **kw: Any) -> None:
611
+ super().__init__(*children, name=name, **kw)
612
+
613
+ def wire_type(self) -> str:
614
+ return self.name
615
+
616
+ def wire_extra(self) -> dict[str, Any]:
617
+ return dict(self.props)
618
+
619
+
620
+ # ------------------------------------------------------------------ data
621
+
622
+
623
+ class Progress(Component):
624
+ """A bar; with ``task`` the value, label and message come from the task."""
625
+
626
+ type: ClassVar[str] = "Progress"
627
+ value: float | None = None
628
+ label: str | None = None
629
+ description: str | None = None
630
+ task: Any = None
631
+
632
+
633
+ class Steps(Component):
634
+ """A step tree on Trace: the task's steps, or ``items`` given directly."""
635
+
636
+ type: ClassVar[str] = "Steps"
637
+ items: list[Any] | None = None
638
+ task: Any = None
639
+ compact: bool = False
640
+
641
+
642
+ class Skeleton(Component):
643
+ type: ClassVar[str] = "Skeleton"
644
+ rows: int = 3
645
+ shape: Literal["text", "card", "table"] = "text"
646
+
647
+
648
+ class Spinner(Component):
649
+ type: ClassVar[str] = "Spinner"
650
+ size: Size = "md"
651
+ label: str | None = None
652
+
653
+
654
+ __all__ = [
655
+ "Accept",
656
+ "Action",
657
+ "Accordion",
658
+ "AccordionItem",
659
+ "Alert",
660
+ "AreaChart",
661
+ "BarChart",
662
+ "Badge",
663
+ "Button",
664
+ "Card",
665
+ "Column",
666
+ "Chat",
667
+ "Checkbox",
668
+ "Code",
669
+ "CheckboxGroup",
670
+ "Collapsible",
671
+ "Command",
672
+ "Custom",
673
+ "Component",
674
+ "Dialog",
675
+ "Download",
676
+ "FileCard",
677
+ "FileRef",
678
+ "FileUpload",
679
+ "Empty",
680
+ "Form",
681
+ "FormIssues",
682
+ "Grid",
683
+ "Image",
684
+ "Input",
685
+ "Issue",
686
+ "Kbd",
687
+ "LineChart",
688
+ "Link",
689
+ "Markdown",
690
+ "MenuItem",
691
+ "NumberInput",
692
+ "Option",
693
+ "Page",
694
+ "Pdf",
695
+ "Progress",
696
+ "RadioGroup",
697
+ "Row",
698
+ "ScrollArea",
699
+ "Select",
700
+ "Field",
701
+ "DropdownMenu",
702
+ "ButtonGroup",
703
+ "DatePicker",
704
+ "MultiSelect",
705
+ "Separator",
706
+ "Sheet",
707
+ "Sidebar",
708
+ "Skeleton",
709
+ "Slider",
710
+ "Spinner",
711
+ "Split",
712
+ "Stack",
713
+ "Stat",
714
+ "Steps",
715
+ "Switch",
716
+ "Tab",
717
+ "Table",
718
+ "Tabs",
719
+ "Text",
720
+ "Logo",
721
+ "Textarea",
722
+ "ToggleGroup",
723
+ "Tooltip",
724
+ "as_options",
725
+ ]