openai/chatkit-python

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
6df9ee60da4de3f063e752e60bd3ceef920f4e78

Branches

Tags

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

Clone

HTTPS

Download ZIP

chatkit/widgets.py

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