openai/chatkit-python

Public

mirrored from https://github.com/openai/chatkit-pythonAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
daf98ddd3b032b4fd2ac6277845f45c13863e805

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

chatkit/widgets.py

1107lines · modecode

1from __future__ import annotations
2
3from datetime import datetime
4from typing import (
5 Annotated,
6 Literal,
7)
8
9from pydantic import (
10 BaseModel,
11 ConfigDict,
12 Field,
13 model_serializer,
14)
15from typing_extensions import NotRequired, TypedDict
16
17from chatkit.actions import ActionConfig
18
19
20class ThemeColor(TypedDict):
21 """Color values for light and dark themes."""
22
23 dark: str
24 """Color to use when the theme is dark."""
25 light: str
26 """Color to use when the theme is light."""
27
28
29class Spacing(TypedDict):
30 """Shorthand spacing values applied to a widget."""
31
32 top: NotRequired[float | str]
33 """Top spacing; accepts a spacing unit or CSS string."""
34 right: NotRequired[float | str]
35 """Right spacing; accepts a spacing unit or CSS string."""
36 bottom: NotRequired[float | str]
37 """Bottom spacing; accepts a spacing unit or CSS string."""
38 left: NotRequired[float | str]
39 """Left spacing; accepts a spacing unit or CSS string."""
40 x: NotRequired[float | str]
41 """Horizontal spacing; accepts a spacing unit or CSS string."""
42 y: NotRequired[float | str]
43 """Vertical spacing; accepts a spacing unit or CSS string."""
44
45
46class Border(TypedDict):
47 """Border style definition for an edge."""
48
49 size: int
50 """Thickness of the border in px."""
51 color: NotRequired[str | ThemeColor]
52 """Border color; accepts border color token, a primitive color token, a CSS string, or theme-aware `{ light, dark }`.
53
54 Valid tokens: `default` `subtle` `strong`
55
56 Primitive color token: e.g. `red-100`, `blue-900`, `gray-500`
57 """
58 style: NotRequired[
59 Literal[
60 "solid", "dashed", "dotted", "double", "groove", "ridge", "inset", "outset"
61 ]
62 ]
63 """Border line style."""
64
65
66class Borders(TypedDict):
67 """Composite border configuration applied across edges."""
68
69 top: NotRequired[int | Border]
70 """Top border or thickness in px."""
71 right: NotRequired[int | Border]
72 """Right border or thickness in px."""
73 bottom: NotRequired[int | Border]
74 """Bottom border or thickness in px."""
75 left: NotRequired[int | Border]
76 """Left border or thickness in px."""
77 x: NotRequired[int | Border]
78 """Horizontal borders or thickness in px."""
79 y: NotRequired[int | Border]
80 """Vertical borders or thickness in px."""
81
82
83class MinMax(TypedDict):
84 """Integer minimum/maximum bounds."""
85
86 min: NotRequired[int]
87 """Minimum value (inclusive)."""
88 max: NotRequired[int]
89 """Maximum value (inclusive)."""
90
91
92class EditableProps(TypedDict):
93 """Editable field options for text widgets."""
94
95 name: str
96 """The name of the form control field used when submitting forms."""
97 autoFocus: NotRequired[bool]
98 """Autofocus the editable input when it appears."""
99 autoSelect: NotRequired[bool]
100 """Select all text on focus."""
101 autoComplete: NotRequired[str]
102 """Native autocomplete hint for the input."""
103 allowAutofillExtensions: NotRequired[bool]
104 """Allow browser password/autofill extensions."""
105 pattern: NotRequired[str]
106 """Regex pattern for input validation."""
107 placeholder: NotRequired[str]
108 """Placeholder text for the editable input."""
109 required: NotRequired[bool]
110 """Mark the editable input as required."""
111
112
113RadiusValue = Literal[
114 "2xs", "xs", "sm", "md", "lg", "xl", "2xl", "3xl", "4xl", "full", "100%", "none"
115]
116"""Allowed corner radius tokens."""
117
118TextAlign = Literal["start", "center", "end"]
119"""Horizontal text alignment options."""
120
121TextSize = Literal["xs", "sm", "md", "lg", "xl"]
122"""Body text size tokens."""
123
124IconSize = Literal["xs", "sm", "md", "lg", "xl", "2xl", "3xl"]
125"""Icon size tokens."""
126
127TitleSize = Literal["sm", "md", "lg", "xl", "2xl", "3xl", "4xl", "5xl"]
128"""Title text size tokens."""
129
130CaptionSize = Literal["sm", "md", "lg"]
131"""Caption text size tokens."""
132
133Alignment = Literal["start", "center", "end", "baseline", "stretch"]
134"""Flexbox alignment options."""
135Justification = Literal[
136 "start", "center", "end", "between", "around", "evenly", "stretch"
137]
138"""Flexbox justification options."""
139
140ControlVariant = Literal["solid", "soft", "outline", "ghost"]
141"""Button and input style variants."""
142ControlSize = Literal["3xs", "2xs", "xs", "sm", "md", "lg", "xl", "2xl", "3xl"]
143"""Button and input size variants."""
144
145
146def _drop_none(x):
147 """Recursively remove ``None`` values when serializing widgets."""
148 if isinstance(x, dict):
149 return {
150 k: _drop_none(v) for k, v in x.items() if k == "children" or v is not None
151 }
152 if isinstance(x, list):
153 return [_drop_none(v) for v in x if v is not None]
154 return x
155
156
157class WidgetComponentBase(BaseModel):
158 """Base Pydantic model for all ChatKit widget components."""
159
160 model_config = ConfigDict(serialize_by_alias=True)
161
162 key: str | None = None
163 id: str | None = None
164 type: str = Field(...)
165
166 # For nested model dumps (e.g. if Widget is not the top-level model)
167 @model_serializer(mode="wrap")
168 def serialize(self, next_):
169 dumped = next_(self)
170 # Recursively filter out None values when serialized.
171 # Do this explicitly instead of overriding model_dump_json and model_dump;
172 # the overrides will not be invoked unless the widget is the top-level model.
173 dumped = _drop_none(dumped)
174 # include type even when exlude_defaults is True
175 if isinstance(dumped, dict):
176 dumped["type"] = self.type
177
178 return dumped
179
180
181class WidgetStatusWithFavicon(TypedDict):
182 """Widget status representation using a favicon."""
183
184 text: str
185 """Status text to display."""
186 favicon: NotRequired[str]
187 """URL of a favicon to render at the start of the status."""
188 frame: NotRequired[bool]
189 """Show a frame around the favicon for contrast."""
190
191
192class WidgetStatusWithIcon(TypedDict):
193 """Widget status representation using an icon."""
194
195 text: str
196 """Status text to display."""
197 icon: NotRequired[WidgetIcon]
198 """Icon to render at the start of the status."""
199
200
201WidgetStatus = WidgetStatusWithFavicon | WidgetStatusWithIcon
202"""Union for representing widget status messaging."""
203
204
205class ListViewItem(WidgetComponentBase):
206 """Single row inside a ``ListView`` component."""
207
208 type: Literal["ListViewItem"] = Field(default="ListViewItem", frozen=True) # pyright: ignore
209 children: list["WidgetComponent"]
210 """Content for the list item."""
211 onClickAction: ActionConfig | None = None
212 """Optional action triggered when the list item is clicked."""
213 gap: int | str | None = None
214 """Gap between children within the list item; spacing unit or CSS string."""
215 align: Alignment | None = None
216 """Y-axis alignment for content within the list item."""
217
218
219class ListView(WidgetComponentBase):
220 """Container component for rendering collections of list items."""
221
222 type: Literal["ListView"] = Field(default="ListView", frozen=True) # pyright: ignore
223 children: list[ListViewItem]
224 """Items to render in the list."""
225 limit: int | Literal["auto"] | None = None
226 """Max number of items to show before a "Show more" control."""
227 status: WidgetStatus | None = None
228 """Optional status header displayed above the list."""
229 theme: Literal["light", "dark"] | None = None
230 """Force light or dark theme for this subtree."""
231
232
233class CardAction(TypedDict):
234 """Configuration for confirm/cancel actions within a card."""
235
236 label: str
237 """Button label shown in the card footer."""
238 action: ActionConfig
239 """Declarative action dispatched to the host application."""
240
241
242class Card(WidgetComponentBase):
243 """Versatile container used for structuring widget content."""
244
245 type: Literal["Card"] = Field(default="Card", frozen=True) # pyright: ignore
246 asForm: bool | None = None
247 """Treat the card as an HTML form so confirm/cancel capture form data."""
248 children: list["WidgetComponent"]
249 """Child components rendered inside the card."""
250 background: str | ThemeColor | None = None
251 """Background color; accepts background color token, a primitive color token, a CSS string, or theme-aware `{ light, dark }`.
252
253 Valid tokens: `surface` `surface-secondary` `surface-tertiary` `surface-elevated` `surface-elevated-secondary`
254
255 Primitive color token: e.g. `red-100`, `blue-900`, `gray-500`
256 """
257 size: Literal["sm", "md", "lg", "full"] | None = None
258 """Visual size of the card; accepts a size token. No preset default is documented."""
259 padding: float | str | Spacing | None = None
260 """Inner spacing of the card; spacing unit, CSS string, or padding object."""
261 status: WidgetStatus | None = None
262 """Optional status header displayed above the card."""
263 collapsed: bool | None = None
264 """Collapse card body after the main action has completed."""
265 confirm: CardAction | None = None
266 """Confirmation action button shown in the card footer."""
267 cancel: CardAction | None = None
268 """Cancel action button shown in the card footer."""
269 theme: Literal["light", "dark"] | None = None
270 """Force light or dark theme for this subtree."""
271
272
273class Markdown(WidgetComponentBase):
274 """Widget rendering Markdown content, optionally streamed."""
275
276 type: Literal["Markdown"] = Field(default="Markdown", frozen=True) # pyright: ignore
277 value: str
278 """Markdown source string to render."""
279 streaming: bool | None = None
280 """Applies streaming-friendly transitions for incremental updates."""
281
282
283class Text(WidgetComponentBase):
284 """Widget rendering plain text with typography controls."""
285
286 type: Literal["Text"] = Field(default="Text", frozen=True) # pyright: ignore
287 value: str
288 """Text content to display."""
289 streaming: bool | None = None
290 """Enables streaming-friendly transitions for incremental updates."""
291 italic: bool | None = None
292 """Render text in italic style."""
293 lineThrough: bool | None = None
294 """Render text with a line-through decoration."""
295 color: str | ThemeColor | None = None
296 """
297 Text color; accepts a text color token, a primitive color token, a CSS color string, or a theme-aware `{ light, dark }`.
298
299 Text color tokens: `prose` `primary` `emphasis` `secondary` `tertiary` `success` `warning` `danger`
300
301 Primitive color token: e.g. `red-100`, `blue-900`, `gray-500`
302 """
303 weight: Literal["normal", "medium", "semibold", "bold"] | None = None
304 """Font weight; accepts a font weight token."""
305 width: float | str | None = None
306 """Constrain the text container width; px or CSS string."""
307 size: TextSize | None = None
308 """Size of the text; accepts a text size token."""
309 textAlign: TextAlign | None = None
310 """Horizontal text alignment."""
311 truncate: bool | None = None
312 """Truncate overflow with ellipsis."""
313 minLines: int | None = None
314 """Reserve space for a minimum number of lines."""
315 maxLines: int | None = None
316 """Limit text to a maximum number of lines (line clamp)."""
317 editable: Literal[False] | EditableProps | None = None
318 """Enable inline editing for this text node."""
319
320
321class Title(WidgetComponentBase):
322 """Widget rendering prominent headline text."""
323
324 type: Literal["Title"] = Field(default="Title", frozen=True) # pyright: ignore
325 value: str
326 """Text content to display."""
327 color: str | ThemeColor | None = None
328 """
329 Text color; accepts a text color token, a primitive color token, a CSS color string, or a theme-aware `{ light, dark }`.
330
331 Text color tokens: `prose` `primary` `emphasis` `secondary` `tertiary` `success` `warning` `danger`
332
333 Primitive color token: e.g. `red-100`, `blue-900`, `gray-500`
334 """
335 weight: Literal["normal", "medium", "semibold", "bold"] | None = None
336 """Font weight; accepts a font weight token."""
337 size: TitleSize | None = None
338 """Size of the title text; accepts a title size token."""
339 textAlign: TextAlign | None = None
340 """Horizontal text alignment."""
341 truncate: bool | None = None
342 """Truncate overflow with ellipsis."""
343 maxLines: int | None = None
344 """Limit text to a maximum number of lines (line clamp)."""
345
346
347class Caption(WidgetComponentBase):
348 """Widget rendering supporting caption text."""
349
350 type: Literal["Caption"] = Field(default="Caption", frozen=True) # pyright: ignore
351 value: str
352 """Text content to display."""
353 color: str | ThemeColor | None = None
354 """
355 Text color; accepts a text color token, a primitive color token, a CSS color string, or a theme-aware `{ light, dark }`.
356
357 Text color tokens: `prose` `primary` `emphasis` `secondary` `tertiary` `success` `warning` `danger`
358
359 Primitive color token: e.g. `red-100`, `blue-900`, `gray-500`
360 """
361 weight: Literal["normal", "medium", "semibold", "bold"] | None = None
362 """Font weight; accepts a font weight token."""
363 size: CaptionSize | None = None
364 """Size of the caption text; accepts a caption size token."""
365 textAlign: TextAlign | None = None
366 """Horizontal text alignment."""
367 truncate: bool | None = None
368 """Truncate overflow with ellipsis."""
369 maxLines: int | None = None
370 """Limit text to a maximum number of lines (line clamp)."""
371
372
373class Badge(WidgetComponentBase):
374 """Small badge indicating status or categorization."""
375
376 type: Literal["Badge"] = Field(default="Badge", frozen=True) # pyright: ignore
377 label: str
378 """Text to display inside the badge."""
379 color: (
380 Literal["secondary", "success", "danger", "warning", "info", "discovery"] | None
381 ) = None
382 """Color of the badge; accepts a badge color token."""
383 variant: Literal["solid", "soft", "outline"] | None = None
384 """Visual style of the badge."""
385 size: Literal["sm", "md", "lg"] | None = None
386 """Size of the badge."""
387 pill: bool | None = None
388 """Determines if the badge should be fully rounded (pill)."""
389
390
391class BoxBase(BaseModel):
392 """Shared layout props for flexible container widgets."""
393
394 children: list["WidgetComponent"] | None = None
395 """Child components to render inside the container."""
396 align: Alignment | None = None
397 """Cross-axis alignment of children."""
398 justify: Justification | None = None
399 """Main-axis distribution of children."""
400 wrap: Literal["nowrap", "wrap", "wrap-reverse"] | None = None
401 """Wrap behavior for flex items."""
402 flex: int | str | None = None
403 """Flex growth/shrink factor."""
404 gap: int | str | None = None
405 """Gap between direct children; spacing unit or CSS string."""
406 height: float | str | None = None
407 """Explicit height; px or CSS string."""
408 width: float | str | None = None
409 """Explicit width; px or CSS string."""
410 size: float | str | None = None
411 """Shorthand to set both width and height; px or CSS string."""
412 minHeight: int | str | None = None
413 """Minimum height; px or CSS string."""
414 minWidth: int | str | None = None
415 """Minimum width; px or CSS string."""
416 minSize: int | str | None = None
417 """Shorthand to set both minWidth and minHeight; px or CSS string."""
418 maxHeight: int | str | None = None
419 """Maximum height; px or CSS string."""
420 maxWidth: int | str | None = None
421 """Maximum width; px or CSS string."""
422 maxSize: int | str | None = None
423 """Shorthand to set both maxWidth and maxHeight; px or CSS string."""
424 padding: float | str | Spacing | None = None
425 """Inner padding; spacing unit, CSS string, or padding object."""
426 margin: float | str | Spacing | None = None
427 """Outer margin; spacing unit, CSS string, or margin object."""
428 border: int | Border | Borders | None = None
429 """Border applied to the container; px or border object/shorthand."""
430 radius: RadiusValue | None = None
431 """Border radius; accepts a radius token."""
432 background: str | ThemeColor | None = None
433 """Background color; accepts background color token, a primitive color token, a CSS string, or theme-aware `{ light, dark }`.
434
435 Valid tokens: `surface` `surface-secondary` `surface-tertiary` `surface-elevated` `surface-elevated-secondary`
436
437 Primitive color token: e.g. `red-100`, `blue-900`, `gray-500`
438 """
439 aspectRatio: float | str | None = None
440 """Aspect ratio of the box (e.g., 16/9); number or CSS string."""
441
442
443class Box(WidgetComponentBase, BoxBase):
444 """Generic flex container with direction control."""
445
446 type: Literal["Box"] = Field(default="Box", frozen=True) # pyright: ignore
447 direction: Literal["row", "col"] | None = None
448 """Flex direction for content within this container."""
449
450
451class Row(WidgetComponentBase, BoxBase):
452 """Horizontal flex container."""
453
454 type: Literal["Row"] = Field(default="Row", frozen=True) # pyright: ignore
455
456
457class Col(WidgetComponentBase, BoxBase):
458 """Vertical flex container."""
459
460 type: Literal["Col"] = Field(default="Col", frozen=True) # pyright: ignore
461
462
463class Form(WidgetComponentBase, BoxBase):
464 """Form wrapper capable of submitting ``onSubmitAction``."""
465
466 type: Literal["Form"] = Field(default="Form", frozen=True) # pyright: ignore
467 onSubmitAction: ActionConfig | None = None
468 """Action dispatched when the form is submitted."""
469 direction: Literal["row", "col"] | None = None
470 """Flex direction for laying out form children."""
471
472
473class Divider(WidgetComponentBase):
474 """Visual divider separating content sections."""
475
476 type: Literal["Divider"] = Field(default="Divider", frozen=True) # pyright: ignore
477 color: str | ThemeColor | None = None
478 """Divider color; accepts border color token, a primitive color token, a CSS string, or theme-aware `{ light, dark }`.
479
480 Valid tokens: `default` `subtle` `strong`
481
482 Primitive color token: e.g. `red-100`, `blue-900`, `gray-500`
483 """
484 size: int | str | None = None
485 """Thickness of the divider line; px or CSS string."""
486 spacing: int | str | None = None
487 """Outer spacing above and below the divider; spacing unit or CSS string."""
488 flush: bool | None = None
489 """Flush the divider to the container edge, removing surrounding padding."""
490
491
492class Icon(WidgetComponentBase):
493 """Icon component referencing a built-in icon name."""
494
495 type: Literal["Icon"] = Field(default="Icon", frozen=True) # pyright: ignore
496 name: WidgetIcon
497 """Name of the icon to display."""
498 color: str | ThemeColor | None = None
499 """
500 Icon color; accepts a text color token, a primitive color token, a CSS color string, or a theme-aware `{ light, dark }`.
501
502 Text color tokens: `prose` `primary` `emphasis` `secondary` `tertiary` `success` `warning` `danger`
503
504 Primitive color token: e.g. `red-100`, `blue-900`, `gray-500`
505 """
506 size: IconSize | None = None
507 """Size of the icon; accepts an icon size token."""
508
509
510class Image(WidgetComponentBase):
511 """Image component with sizing and fitting controls."""
512
513 type: Literal["Image"] = Field(default="Image", frozen=True) # pyright: ignore
514 src: str
515 """Image URL source."""
516 alt: str | None = None
517 """Alternate text for accessibility."""
518 fit: Literal["cover", "contain", "fill", "scale-down", "none"] | None = None
519 """How the image should fit within the container."""
520 position: (
521 Literal[
522 "top left",
523 "top",
524 "top right",
525 "left",
526 "center",
527 "right",
528 "bottom left",
529 "bottom",
530 "bottom right",
531 ]
532 | None
533 ) = None
534 """Focal position of the image within the container."""
535 radius: RadiusValue | None = None
536 """Border radius; accepts a radius token."""
537 frame: bool | None = None
538 """Draw a subtle frame around the image."""
539 flush: bool | None = None
540 """Flush the image to the container edge, removing surrounding padding."""
541 height: int | str | None = None
542 """Explicit height; px or CSS string."""
543 width: int | str | None = None
544 """Explicit width; px or CSS string."""
545 size: int | str | None = None
546 """Shorthand to set both width and height; px or CSS string."""
547 minHeight: int | str | None = None
548 """Minimum height; px or CSS string."""
549 minWidth: int | str | None = None
550 """Minimum width; px or CSS string."""
551 minSize: int | str | None = None
552 """Shorthand to set both minWidth and minHeight; px or CSS string."""
553 maxHeight: int | str | None = None
554 """Maximum height; px or CSS string."""
555 maxWidth: int | str | None = None
556 """Maximum width; px or CSS string."""
557 maxSize: int | str | None = None
558 """Shorthand to set both maxWidth and maxHeight; px or CSS string."""
559 margin: int | str | Spacing | None = None
560 """Outer margin; spacing unit, CSS string, or margin object."""
561 background: str | ThemeColor | None = None
562 """Background color; accepts background color token, a primitive color token, a CSS string, or theme-aware `{ light, dark }`.
563
564 Valid tokens: `surface` `surface-secondary` `surface-tertiary` `surface-elevated` `surface-elevated-secondary`
565
566 Primitive color token: e.g. `red-100`, `blue-900`, `gray-500`
567 """
568 aspectRatio: float | str | None = None
569 """Aspect ratio of the box (e.g., 16/9); number or CSS string."""
570 flex: int | str | None = None
571 """Flex growth/shrink factor."""
572
573
574class Button(WidgetComponentBase):
575 """Button component optionally wired to an action."""
576
577 type: Literal["Button"] = Field(default="Button", frozen=True) # pyright: ignore
578 submit: bool | None = None
579 """Configure the button as a submit button for the nearest form."""
580 label: str | None = None
581 """Text to display inside the button."""
582 onClickAction: ActionConfig | None = None
583 """Action dispatched on click."""
584 iconStart: WidgetIcon | None = None
585 """Icon shown before the label; can be used for icon-only buttons."""
586 iconEnd: WidgetIcon | None = None
587 """Optional icon shown after the label."""
588 style: Literal["primary", "secondary"] | None = None
589 """Convenience preset for button style."""
590 iconSize: Literal["sm", "md", "lg", "xl", "2xl"] | None = None
591 """Controls the size of icons within the button; accepts an icon size token."""
592 color: (
593 Literal[
594 "primary",
595 "secondary",
596 "info",
597 "discovery",
598 "success",
599 "caution",
600 "warning",
601 "danger",
602 ]
603 | None
604 ) = None
605 """Color of the button; accepts a button color token."""
606 variant: ControlVariant | None = None
607 """Visual variant of the button; accepts a control variant token."""
608 size: ControlSize | None = None
609 """Controls the overall size of the button."""
610 pill: bool | None = None
611 """Determines if the button should be fully rounded (pill)."""
612 uniform: bool | None = None
613 """Determines if the button should have matching width and height."""
614 block: bool | None = None
615 """Extend the button to 100% of the available width."""
616 disabled: bool | None = None
617 """Disable interactions and apply disabled styles."""
618
619
620class Spacer(WidgetComponentBase):
621 """Flexible spacer used to push content apart."""
622
623 type: Literal["Spacer"] = Field(default="Spacer", frozen=True) # pyright: ignore
624 minSize: int | str | None = None
625 """Minimum size the spacer should occupy along the flex direction."""
626
627
628class SelectOption(TypedDict):
629 """Selectable option used by the ``Select`` widget."""
630
631 value: str
632 """Option value submitted with the form."""
633 label: str
634 """Human-readable label for the option."""
635 disabled: NotRequired[bool]
636 """Disable the option."""
637 description: NotRequired[str]
638 """Displayed as secondary text below the option `label`."""
639
640
641class Select(WidgetComponentBase):
642 """Select dropdown component."""
643
644 type: Literal["Select"] = Field(default="Select", frozen=True) # pyright: ignore
645 name: str
646 """The name of the form control field used when submitting forms."""
647 options: list[SelectOption]
648 """List of selectable options."""
649 onChangeAction: ActionConfig | None = None
650 """Action dispatched when the value changes."""
651 placeholder: str | None = None
652 """Placeholder text shown when no value is selected."""
653 defaultValue: str | None = None
654 """Initial value of the select."""
655 variant: ControlVariant | None = None
656 """Visual style of the select; accepts a control variant token."""
657 size: ControlSize | None = None
658 """Controls the size of the select control."""
659 pill: bool | None = None
660 """Determines if the select should be fully rounded (pill)."""
661 block: bool | None = None
662 """Extend the select to 100% of the available width."""
663 clearable: bool | None = None
664 """Show a clear control to unset the value."""
665 disabled: bool | None = None
666 """Disable interactions and apply disabled styles."""
667
668
669class DatePicker(WidgetComponentBase):
670 """Date picker input component."""
671
672 type: Literal["DatePicker"] = Field(default="DatePicker", frozen=True) # pyright: ignore
673 name: str
674 """The name of the form control field used when submitting forms."""
675 onChangeAction: ActionConfig | None = None
676 """Action dispatched when the date value changes."""
677 placeholder: str | None = None
678 """Placeholder text shown when no date is selected."""
679 defaultValue: datetime | None = None
680 """Initial value of the date picker."""
681 min: datetime | None = None
682 """Earliest selectable date (inclusive)."""
683 max: datetime | None = None
684 """Latest selectable date (inclusive)."""
685 variant: ControlVariant | None = None
686 """Visual variant of the datepicker control."""
687 size: ControlSize | None = None
688 """Controls the size of the datepicker control."""
689 side: Literal["top", "bottom", "left", "right"] | None = None
690 """Preferred side to render the calendar."""
691 align: Literal["start", "center", "end"] | None = None
692 """Preferred alignment of the calendar relative to the control."""
693 pill: bool | None = None
694 """Determines if the datepicker should be fully rounded (pill)."""
695 block: bool | None = None
696 """Extend the datepicker to 100% of the available width."""
697 clearable: bool | None = None
698 """Show a clear control to unset the value."""
699 disabled: bool | None = None
700 """Disable interactions and apply disabled styles."""
701
702
703class Checkbox(WidgetComponentBase):
704 """Checkbox input component."""
705
706 type: Literal["Checkbox"] = Field(default="Checkbox", frozen=True) # pyright: ignore
707 name: str
708 """The name of the form control field used when submitting forms."""
709 label: str | None = None
710 """Optional label text rendered next to the checkbox."""
711 defaultChecked: str | None = None
712 """The initial checked state of the checkbox."""
713 onChangeAction: ActionConfig | None = None
714 """Action dispatched when the checked state changes."""
715 disabled: bool | None = None
716 """Disable interactions and apply disabled styles."""
717 required: bool | None = None
718 """Mark the checkbox as required for form submission."""
719
720
721class Input(WidgetComponentBase):
722 """Single-line text input component."""
723
724 type: Literal["Input"] = Field(default="Input", frozen=True) # pyright: ignore
725 name: str
726 """The name of the form control field used when submitting forms."""
727 inputType: Literal["number", "email", "text", "password", "tel", "url"] | None = (
728 None
729 )
730 """Native input type."""
731 defaultValue: str | None = None
732 """Initial value of the input."""
733 required: bool | None = None
734 """Mark the input as required for form submission."""
735 pattern: str | None = None
736 """Regex pattern for input validation."""
737 placeholder: str | None = None
738 """Placeholder text shown when empty."""
739 allowAutofillExtensions: bool | None = None
740 """Allow password managers / autofill extensions to appear."""
741 autoSelect: bool | None = None
742 """Select all contents of the input when it mounts."""
743 autoFocus: bool | None = None
744 """Autofocus the input when it mounts."""
745 disabled: bool | None = None
746 """Disable interactions and apply disabled styles."""
747 variant: Literal["soft", "outline"] | None = None
748 """Visual style of the input."""
749 size: ControlSize | None = None
750 """Controls the size of the input control."""
751 gutterSize: Literal["2xs", "xs", "sm", "md", "lg", "xl"] | None = None
752 """Controls gutter on the edges of the input; overrides value from `size`."""
753 pill: bool | None = None
754 """Determines if the input should be fully rounded (pill)."""
755
756
757class Label(WidgetComponentBase):
758 """Form label associated with a field."""
759
760 type: Literal["Label"] = Field(default="Label", frozen=True) # pyright: ignore
761 value: str
762 """Text content of the label."""
763 fieldName: str
764 """Name of the field this label describes."""
765 size: TextSize | None = None
766 """Size of the label text; accepts a text size token."""
767 weight: Literal["normal", "medium", "semibold", "bold"] | None = None
768 """Font weight; accepts a font weight token."""
769 textAlign: TextAlign | None = None
770 """Horizontal text alignment."""
771 color: str | ThemeColor | None = None
772 """
773 Text color; accepts a text color token, a primitive color token, a CSS color string, or a theme-aware `{ light, dark }`.
774
775 Text color tokens: `prose` `primary` `emphasis` `secondary` `tertiary` `success` `warning` `danger`
776
777 Primitive color token: e.g. `red-100`, `blue-900`, `gray-500`
778 """
779
780
781class RadioOption(TypedDict):
782 """Option inside a ``RadioGroup`` widget."""
783
784 label: str
785 """Label displayed next to the radio option."""
786 value: str
787 """Value submitted when the radio option is selected."""
788 disabled: NotRequired[bool]
789 """Disables a specific radio option."""
790
791
792class RadioGroup(WidgetComponentBase):
793 """Grouped radio input control."""
794
795 type: Literal["RadioGroup"] = Field(default="RadioGroup", frozen=True) # pyright: ignore
796 name: str
797 """The name of the form control field used when submitting forms."""
798 options: list[RadioOption] | None = None
799 """Array of options to render as radio items."""
800 ariaLabel: str | None = None
801 """Accessible label for the radio group; falls back to `name`."""
802 onChangeAction: ActionConfig | None = None
803 """Action dispatched when the selected value changes."""
804 defaultValue: str | None = None
805 """Initial selected value of the radio group."""
806 direction: Literal["row", "col"] | None = None
807 """Layout direction of the radio items."""
808 disabled: bool | None = None
809 """Disable interactions and apply disabled styles for the entire group."""
810 required: bool | None = None
811 """Mark the group as required for form submission."""
812
813
814class Textarea(WidgetComponentBase):
815 """Multiline text input component."""
816
817 type: Literal["Textarea"] = Field(default="Textarea", frozen=True) # pyright: ignore
818 name: str
819 """The name of the form control field used when submitting forms."""
820 defaultValue: str | None = None
821 """Initial value of the textarea."""
822 required: bool | None = None
823 """Mark the textarea as required for form submission."""
824 pattern: str | None = None
825 """Regex pattern for input validation."""
826 placeholder: str | None = None
827 """Placeholder text shown when empty."""
828 autoSelect: bool | None = None
829 """Select all contents of the textarea when it mounts."""
830 autoFocus: bool | None = None
831 """Autofocus the textarea when it mounts."""
832 disabled: bool | None = None
833 """Disable interactions and apply disabled styles."""
834 variant: Literal["soft", "outline"] | None = None
835 """Visual style of the textarea."""
836 size: ControlSize | None = None
837 """Controls the size of the textarea control."""
838 gutterSize: Literal["2xs", "xs", "sm", "md", "lg", "xl"] | None = None
839 """Controls gutter on the edges of the textarea; overrides value from `size`."""
840 rows: int | None = None
841 """Initial number of visible rows."""
842 autoResize: bool | None = None
843 """Automatically grow/shrink to fit content."""
844 maxRows: int | None = None
845 """Maximum number of rows when auto-resizing."""
846 allowAutofillExtensions: bool | None = None
847 """Allow password managers / autofill extensions to appear."""
848
849
850class Transition(WidgetComponentBase):
851 """Wrapper enabling transitions for a child component."""
852
853 type: Literal["Transition"] = Field(default="Transition", frozen=True) # pyright: ignore
854 children: WidgetComponent | None
855 """The child component to animate layout changes for."""
856
857
858class Chart(WidgetComponentBase):
859 """Data visualization component for simple bar/line/area charts."""
860
861 type: Literal["Chart"] = Field(default="Chart", frozen=True) # pyright: ignore
862 data: list[dict[str, str | int | float]]
863 """Tabular data for the chart, where each row maps field names to values."""
864 series: list[Series]
865 """One or more series definitions that describe how to visualize data fields."""
866 xAxis: str | XAxisConfig
867 """X-axis configuration; either a `dataKey` string or a config object."""
868 showYAxis: bool | None = None
869 """Controls whether the Y axis is rendered."""
870 showLegend: bool | None = None
871 """Controls whether a legend is rendered."""
872 showTooltip: bool | None = None
873 """Controls whether a tooltip is rendered when hovering over a datapoint."""
874 barGap: int | None = None
875 """Gap between bars within the same category (in px)."""
876 barCategoryGap: int | None = None
877 """Gap between bar categories/groups (in px)."""
878 flex: int | str | None = None
879 """Flex growth/shrink factor for layout."""
880 height: int | str | None = None
881 """Explicit height; px or CSS string."""
882 width: int | str | None = None
883 """Explicit width; px or CSS string."""
884 size: int | str | None = None
885 """Shorthand to set both width and height; px or CSS string."""
886 minHeight: int | str | None = None
887 """Minimum height; px or CSS string."""
888 minWidth: int | str | None = None
889 """Minimum width; px or CSS string."""
890 minSize: int | str | None = None
891 """Shorthand to set both minWidth and minHeight; px or CSS string."""
892 maxHeight: int | str | None = None
893 """Maximum height; px or CSS string."""
894 maxWidth: int | str | None = None
895 """Maximum width; px or CSS string."""
896 maxSize: int | str | None = None
897 """Shorthand to set both maxWidth and maxHeight; px or CSS string."""
898 aspectRatio: float | str | None = None
899 """Aspect ratio of the chart area (e.g., 16/9); number or CSS string."""
900
901
902class XAxisConfig(TypedDict):
903 """Configuration object for the X axis."""
904
905 dataKey: str
906 """Field name from each data row to use for X-axis categories."""
907 hide: NotRequired[bool]
908 """Hide the X axis line, ticks, and labels when true."""
909 labels: NotRequired[dict[str, str]]
910 """Custom mapping of tick values to display labels."""
911
912
913CurveType = Literal[
914 "basis",
915 "basisClosed",
916 "basisOpen",
917 "bumpX",
918 "bumpY",
919 "bump",
920 "linear",
921 "linearClosed",
922 "natural",
923 "monotoneX",
924 "monotoneY",
925 "monotone",
926 "step",
927 "stepBefore",
928 "stepAfter",
929]
930"""Interpolation curve types for `area` and `line` series."""
931
932
933class BarSeries(BaseModel):
934 """A bar series plotted from a numeric `dataKey`. Supports stacking."""
935
936 type: Literal["bar"] = Field(default="bar", frozen=True)
937 label: str | None
938 """Legend label for the series."""
939 dataKey: str
940 """Field name from each data row that contains the numeric value."""
941 stack: str | None = None
942 """Optional stack group ID. Series with the same ID stack together."""
943 color: str | ThemeColor | None = None
944 """
945 Color for the series; accepts chart color token, a primitive color token, a CSS string, or theme-aware { light, dark }.
946
947 Chart color tokens: `blue` `purple` `orange` `green` `red` `yellow` `pink`
948
949 Primitive color token, e.g., `red-100`, `blue-900`, `gray-500`
950
951 Note: By default, a color will be sequentially assigned from the chart series colors.
952 """
953
954
955class AreaSeries(BaseModel):
956 """An area series plotted from a numeric `dataKey`. Supports stacking and curves."""
957
958 type: Literal["area"] = Field(default="area", frozen=True)
959 label: str | None
960 """Legend label for the series."""
961 dataKey: str
962 """Field name from each data row that contains the numeric value."""
963 stack: str | None = None
964 """Optional stack group ID. Series with the same ID stack together."""
965 color: str | ThemeColor | None = None
966 """
967 Color for the series; accepts chart color token, a primitive color token, a CSS string, or theme-aware { light, dark }.
968
969 Chart color tokens: `blue` `purple` `orange` `green` `red` `yellow` `pink`
970
971 Primitive color token, e.g., `red-100`, `blue-900`, `gray-500`
972
973 Note: By default, a color will be sequentially assigned from the chart series colors.
974 """
975 curveType: None | Literal[CurveType] = None
976 """Interpolation curve type used to connect points."""
977
978
979class LineSeries(BaseModel):
980 """A line series plotted from a numeric `dataKey`. Supports curves."""
981
982 type: Literal["line"] = Field(default="line", frozen=True)
983 label: str | None
984 """Legend label for the series."""
985 dataKey: str
986 """Field name from each data row that contains the numeric value."""
987 color: str | ThemeColor | None = None
988 """
989 Color for the series; accepts chart color token, a primitive color token, a CSS string, or theme-aware { light, dark }.
990
991 Chart color tokens: `blue` `purple` `orange` `green` `red` `yellow` `pink`
992
993 Primitive color token, e.g., `red-100`, `blue-900`, `gray-500`
994
995 Note: By default, a color will be sequentially assigned from the chart series colors.
996 """
997 curveType: None | Literal[CurveType] = None
998 """Interpolation curve type used to connect points."""
999
1000
1001Series = Annotated[
1002 BarSeries | AreaSeries | LineSeries,
1003 Field(discriminator="type"),
1004]
1005"""Union of all supported chart series types."""
1006
1007WidgetRoot = Annotated[
1008 Card | ListView,
1009 Field(discriminator="type"),
1010]
1011
1012WidgetComponent = Annotated[
1013 Text
1014 | Title
1015 | Caption
1016 | Chart
1017 | Badge
1018 | Markdown
1019 | Box
1020 | Row
1021 | Col
1022 | Divider
1023 | Icon
1024 | Image
1025 | ListViewItem
1026 | Button
1027 | Checkbox
1028 | Spacer
1029 | Select
1030 | DatePicker
1031 | Form
1032 | Input
1033 | Label
1034 | RadioGroup
1035 | Textarea
1036 | Transition,
1037 Field(discriminator="type"),
1038]
1039"""Union of all renderable widget components."""
1040
1041
1042WidgetIcon = Literal[
1043 "agent",
1044 "analytics",
1045 "atom",
1046 "batch",
1047 "bolt",
1048 "book-open",
1049 "book-clock",
1050 "book-closed",
1051 "bug",
1052 "calendar",
1053 "chart",
1054 "check",
1055 "check-circle",
1056 "check-circle-filled",
1057 "chevron-left",
1058 "chevron-right",
1059 "circle-question",
1060 "compass",
1061 "confetti",
1062 "cube",
1063 "desktop",
1064 "document",
1065 "dot",
1066 "dots-horizontal",
1067 "dots-vertical",
1068 "empty-circle",
1069 "external-link",
1070 "globe",
1071 "keys",
1072 "lab",
1073 "images",
1074 "info",
1075 "lifesaver",
1076 "lightbulb",
1077 "mail",
1078 "map-pin",
1079 "maps",
1080 "mobile",
1081 "name",
1082 "notebook",
1083 "notebook-pencil",
1084 "page-blank",
1085 "phone",
1086 "play",
1087 "plus",
1088 "profile",
1089 "profile-card",
1090 "reload",
1091 "star",
1092 "star-filled",
1093 "search",
1094 "sparkle",
1095 "sparkle-double",
1096 "square-code",
1097 "square-image",
1098 "square-text",
1099 "suitcase",
1100 "settings-slider",
1101 "user",
1102 "wreath",
1103 "write",
1104 "write-alt",
1105 "write-alt2",
1106]
1107"""Icon names accepted by widgets that render icons."""
1108