Skip to main content

everruns_openui/
components.rs

1//! OpenUI Component Definitions
2//!
3//! Static definitions of all OpenUI components, their props, and descriptions.
4//! These mirror the TypeScript `defineComponent()` calls in `@openuidev/react-ui`.
5//!
6//! Ref: packages/react-ui/src/genui-lib/ (each component directory)
7//! Ref: packages/react-ui/src/genui-lib/openuiChatLibrary.tsx (chat library)
8//! Ref: packages/react-ui/src/genui-lib/openuiLibrary.tsx (base library)
9
10use crate::groups::ComponentGroup;
11
12/// A single prop/argument definition for a component.
13/// Maps to a key in the TypeScript Zod schema's `.shape`.
14///
15/// Ref: packages/react-lang/src/parser/prompt.ts `analyzeFields()`
16pub struct PropDef {
17    /// Prop name (matches Zod schema key name, used as positional arg name)
18    pub name: &'static str,
19    /// Type annotation string shown in the prompt signature.
20    /// Matches what the TS `resolveTypeAnnotation()` would produce.
21    pub type_annotation: &'static str,
22    /// Whether this prop is optional (z.optional() in Zod)
23    pub optional: bool,
24}
25
26/// A component definition.
27/// Equivalent to TS `DefinedComponent` from `defineComponent()`.
28///
29/// Ref: packages/react-lang/src/library.ts `defineComponent()`
30pub struct ComponentDef {
31    /// Component name (PascalCase, used in OpenUI Lang as `TypeName(...)`)
32    pub name: &'static str,
33    /// Ordered list of props (order defines positional argument mapping)
34    pub props: &'static [PropDef],
35    /// Human-readable description shown in the prompt
36    pub description: &'static str,
37}
38
39/// The full component library definition.
40///
41/// Ref: packages/react-lang/src/library.ts `Library` interface
42pub struct Library {
43    /// Root component name (entry point for `root = Root(...)`)
44    pub root: &'static str,
45    /// All component definitions
46    pub components: Vec<ComponentDef>,
47    /// Component groups for organized prompt output
48    pub groups: Vec<ComponentGroup>,
49}
50
51/// Returns all component definitions for the chat library.
52///
53/// Ref: packages/react-ui/src/genui-lib/openuiChatLibrary.tsx
54#[allow(clippy::vec_init_then_push)]
55pub fn all_components() -> Vec<ComponentDef> {
56    let mut components = Vec::new();
57
58    // =========================================================================
59    // Layout components
60    // Ref: packages/react-ui/src/genui-lib/Stack/
61    // =========================================================================
62
63    // Ref: packages/react-ui/src/genui-lib/Stack/schema.ts — StackSchema
64    components.push(ComponentDef {
65        name: "Stack",
66        props: &[
67            PropDef { name: "children", type_annotation: "any[]", optional: false },
68            PropDef { name: "direction", type_annotation: "\"row\" | \"column\"", optional: true },
69            PropDef { name: "gap", type_annotation: "\"none\" | \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"2xl\"", optional: true },
70            PropDef { name: "align", type_annotation: "\"start\" | \"center\" | \"end\" | \"stretch\" | \"baseline\"", optional: true },
71            PropDef { name: "justify", type_annotation: "\"start\" | \"center\" | \"end\" | \"between\" | \"around\" | \"evenly\"", optional: true },
72            PropDef { name: "wrap", type_annotation: "boolean", optional: true },
73        ],
74        description: "Flex container. direction: \"row\"|\"column\" (default \"column\"). gap: \"none\"|\"xs\"|\"s\"|\"m\"|\"l\"|\"xl\"|\"2xl\" (default \"m\"). align: \"start\"|\"center\"|\"end\"|\"stretch\"|\"baseline\". justify: \"start\"|\"center\"|\"end\"|\"between\"|\"around\"|\"evenly\".",
75    });
76
77    // Ref: packages/react-ui/src/genui-lib/Tabs/
78    components.push(ComponentDef {
79        name: "Tabs",
80        props: &[PropDef {
81            name: "tabs",
82            type_annotation: "TabItem[]",
83            optional: false,
84        }],
85        description: "Tabbed container with switchable panels",
86    });
87
88    // Ref: packages/react-ui/src/genui-lib/Tabs/ (TabItem sub-component)
89    components.push(ComponentDef {
90        name: "TabItem",
91        props: &[
92            PropDef {
93                name: "label",
94                type_annotation: "string",
95                optional: false,
96            },
97            PropDef {
98                name: "children",
99                type_annotation: "any[]",
100                optional: false,
101            },
102        ],
103        description: "One tab with label and content",
104    });
105
106    // Ref: packages/react-ui/src/genui-lib/Accordion/
107    components.push(ComponentDef {
108        name: "Accordion",
109        props: &[PropDef {
110            name: "items",
111            type_annotation: "AccordionItem[]",
112            optional: false,
113        }],
114        description: "Collapsible sections",
115    });
116
117    components.push(ComponentDef {
118        name: "AccordionItem",
119        props: &[
120            PropDef {
121                name: "title",
122                type_annotation: "string",
123                optional: false,
124            },
125            PropDef {
126                name: "children",
127                type_annotation: "any[]",
128                optional: false,
129            },
130        ],
131        description: "One accordion section with title and content",
132    });
133
134    // Ref: packages/react-ui/src/genui-lib/Steps/
135    components.push(ComponentDef {
136        name: "Steps",
137        props: &[PropDef {
138            name: "steps",
139            type_annotation: "StepsItem[]",
140            optional: false,
141        }],
142        description: "Step-by-step progress indicator with content",
143    });
144
145    components.push(ComponentDef {
146        name: "StepsItem",
147        props: &[
148            PropDef {
149                name: "title",
150                type_annotation: "string",
151                optional: false,
152            },
153            PropDef {
154                name: "children",
155                type_annotation: "any[]",
156                optional: false,
157            },
158        ],
159        description: "One step with title and content",
160    });
161
162    // Ref: packages/react-ui/src/genui-lib/Carousel/
163    components.push(ComponentDef {
164        name: "Carousel",
165        props: &[PropDef {
166            name: "items",
167            type_annotation: "Card[]",
168            optional: false,
169        }],
170        description: "Horizontally scrollable card carousel",
171    });
172
173    // Ref: packages/react-ui/src/genui-lib/Separator/
174    components.push(ComponentDef {
175        name: "Separator",
176        props: &[],
177        description: "Visual divider line",
178    });
179
180    // =========================================================================
181    // Content components
182    // Ref: packages/react-ui/src/genui-lib/{Card,CardHeader,TextContent,...}/
183    // =========================================================================
184
185    // Ref: packages/react-ui/src/genui-lib/Card/schema.ts — CardSchema
186    components.push(ComponentDef {
187        name: "Card",
188        props: &[
189            PropDef { name: "children", type_annotation: "any[]", optional: false },
190            PropDef { name: "variant", type_annotation: "\"card\" | \"sunk\" | \"clear\"", optional: true },
191            PropDef { name: "direction", type_annotation: "\"row\" | \"column\"", optional: true },
192            PropDef { name: "gap", type_annotation: "\"none\" | \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"2xl\"", optional: true },
193            PropDef { name: "align", type_annotation: "\"start\" | \"center\" | \"end\" | \"stretch\" | \"baseline\"", optional: true },
194            PropDef { name: "justify", type_annotation: "\"start\" | \"center\" | \"end\" | \"between\" | \"around\" | \"evenly\"", optional: true },
195            PropDef { name: "wrap", type_annotation: "boolean", optional: true },
196        ],
197        description: "Styled container. variant: card (elevated) | sunk (recessed) | clear (transparent). Always full width. Accepts all Stack flex params (default: direction column). Cards flex to share space in row/wrap layouts.",
198    });
199
200    // Ref: packages/react-ui/src/genui-lib/CardHeader/
201    components.push(ComponentDef {
202        name: "CardHeader",
203        props: &[
204            PropDef {
205                name: "title",
206                type_annotation: "string",
207                optional: false,
208            },
209            PropDef {
210                name: "subtitle",
211                type_annotation: "string",
212                optional: true,
213            },
214        ],
215        description: "Card title and optional subtitle",
216    });
217
218    // Ref: packages/react-ui/src/genui-lib/TextContent/schema.ts
219    components.push(ComponentDef {
220        name: "TextContent",
221        props: &[
222            PropDef { name: "text", type_annotation: "string", optional: false },
223            PropDef { name: "size", type_annotation: "\"small\" | \"default\" | \"large\" | \"small-heavy\" | \"large-heavy\"", optional: true },
224        ],
225        description: "Text block. Supports markdown. Optional size: \"small\" | \"default\" | \"large\" | \"small-heavy\" | \"large-heavy\".",
226    });
227
228    // Ref: packages/react-ui/src/genui-lib/MarkDownRenderer/
229    components.push(ComponentDef {
230        name: "MarkDownRenderer",
231        props: &[PropDef {
232            name: "text",
233            type_annotation: "string",
234            optional: false,
235        }],
236        description: "Renders markdown text",
237    });
238
239    // Ref: packages/react-ui/src/genui-lib/Callout/
240    components.push(ComponentDef {
241        name: "Callout",
242        props: &[
243            PropDef {
244                name: "title",
245                type_annotation: "string",
246                optional: false,
247            },
248            PropDef {
249                name: "children",
250                type_annotation: "any[]",
251                optional: false,
252            },
253            PropDef {
254                name: "variant",
255                type_annotation: "\"info\" | \"warning\" | \"error\" | \"success\"",
256                optional: true,
257            },
258        ],
259        description: "Highlighted block with title, content, and variant",
260    });
261
262    // Ref: packages/react-ui/src/genui-lib/TextCallout/
263    components.push(ComponentDef {
264        name: "TextCallout",
265        props: &[
266            PropDef {
267                name: "text",
268                type_annotation: "string",
269                optional: false,
270            },
271            PropDef {
272                name: "variant",
273                type_annotation: "\"info\" | \"warning\" | \"error\" | \"success\"",
274                optional: true,
275            },
276        ],
277        description: "Simple text callout with variant",
278    });
279
280    // Ref: packages/react-ui/src/genui-lib/CodeBlock/
281    components.push(ComponentDef {
282        name: "CodeBlock",
283        props: &[
284            PropDef {
285                name: "code",
286                type_annotation: "string",
287                optional: false,
288            },
289            PropDef {
290                name: "language",
291                type_annotation: "string",
292                optional: true,
293            },
294        ],
295        description: "Syntax-highlighted code block",
296    });
297
298    // Ref: packages/react-ui/src/genui-lib/Image/
299    components.push(ComponentDef {
300        name: "Image",
301        props: &[
302            PropDef {
303                name: "src",
304                type_annotation: "string",
305                optional: false,
306            },
307            PropDef {
308                name: "alt",
309                type_annotation: "string",
310                optional: true,
311            },
312        ],
313        description: "Single image",
314    });
315
316    // Ref: packages/react-ui/src/genui-lib/ImageBlock/
317    components.push(ComponentDef {
318        name: "ImageBlock",
319        props: &[
320            PropDef {
321                name: "src",
322                type_annotation: "string",
323                optional: false,
324            },
325            PropDef {
326                name: "alt",
327                type_annotation: "string",
328                optional: true,
329            },
330            PropDef {
331                name: "caption",
332                type_annotation: "string",
333                optional: true,
334            },
335        ],
336        description: "Image with optional caption",
337    });
338
339    // Ref: packages/react-ui/src/genui-lib/ImageGallery/
340    components.push(ComponentDef {
341        name: "ImageGallery",
342        props: &[PropDef {
343            name: "images",
344            type_annotation: "Image[]",
345            optional: false,
346        }],
347        description: "Grid of images",
348    });
349
350    // =========================================================================
351    // Tables
352    // Ref: packages/react-ui/src/genui-lib/Table/
353    // =========================================================================
354
355    // Ref: packages/react-ui/src/genui-lib/Table/index.tsx — Table schema inline
356    components.push(ComponentDef {
357        name: "Table",
358        props: &[
359            PropDef {
360                name: "columns",
361                type_annotation: "Col[]",
362                optional: false,
363            },
364            PropDef {
365                name: "rows",
366                type_annotation: "(string | number | boolean)[][]",
367                optional: false,
368            },
369        ],
370        description: "Data table",
371    });
372
373    // Ref: packages/react-ui/src/genui-lib/Table/schema.ts — ColSchema
374    components.push(ComponentDef {
375        name: "Col",
376        props: &[
377            PropDef {
378                name: "label",
379                type_annotation: "string",
380                optional: false,
381            },
382            PropDef {
383                name: "type",
384                type_annotation: "\"string\" | \"number\" | \"action\"",
385                optional: true,
386            },
387        ],
388        description: "Table column definition",
389    });
390
391    // =========================================================================
392    // Charts (2D) — labels + series data
393    // Ref: packages/react-ui/src/genui-lib/Charts/
394    // =========================================================================
395
396    // Ref: Charts/BarChartCondensed.ts
397    components.push(ComponentDef {
398        name: "BarChart",
399        props: &[
400            PropDef { name: "labels", type_annotation: "string[]", optional: false },
401            PropDef { name: "series", type_annotation: "Series[]", optional: false },
402            PropDef { name: "variant", type_annotation: "\"grouped\" | \"stacked\"", optional: true },
403            PropDef { name: "xLabel", type_annotation: "string", optional: true },
404            PropDef { name: "yLabel", type_annotation: "string", optional: true },
405        ],
406        description: "Vertical bars; use for comparing values across categories with one or more series",
407    });
408
409    // Ref: Charts/LineChartCondensed.ts
410    components.push(ComponentDef {
411        name: "LineChart",
412        props: &[
413            PropDef {
414                name: "labels",
415                type_annotation: "string[]",
416                optional: false,
417            },
418            PropDef {
419                name: "series",
420                type_annotation: "Series[]",
421                optional: false,
422            },
423            PropDef {
424                name: "xLabel",
425                type_annotation: "string",
426                optional: true,
427            },
428            PropDef {
429                name: "yLabel",
430                type_annotation: "string",
431                optional: true,
432            },
433        ],
434        description: "Line chart for trends over time or ordered categories",
435    });
436
437    // Ref: Charts/AreaChartCondensed.ts
438    components.push(ComponentDef {
439        name: "AreaChart",
440        props: &[
441            PropDef {
442                name: "labels",
443                type_annotation: "string[]",
444                optional: false,
445            },
446            PropDef {
447                name: "series",
448                type_annotation: "Series[]",
449                optional: false,
450            },
451            PropDef {
452                name: "variant",
453                type_annotation: "\"default\" | \"stacked\"",
454                optional: true,
455            },
456            PropDef {
457                name: "xLabel",
458                type_annotation: "string",
459                optional: true,
460            },
461            PropDef {
462                name: "yLabel",
463                type_annotation: "string",
464                optional: true,
465            },
466        ],
467        description: "Filled area chart; stacked variant shows cumulative totals",
468    });
469
470    // Ref: Charts/RadarChart.ts
471    components.push(ComponentDef {
472        name: "RadarChart",
473        props: &[
474            PropDef {
475                name: "labels",
476                type_annotation: "string[]",
477                optional: false,
478            },
479            PropDef {
480                name: "series",
481                type_annotation: "Series[]",
482                optional: false,
483            },
484        ],
485        description: "Spider/radar chart for multivariate comparison",
486    });
487
488    // Ref: Charts/HorizontalBarChart.ts
489    components.push(ComponentDef {
490        name: "HorizontalBarChart",
491        props: &[
492            PropDef {
493                name: "labels",
494                type_annotation: "string[]",
495                optional: false,
496            },
497            PropDef {
498                name: "series",
499                type_annotation: "Series[]",
500                optional: false,
501            },
502            PropDef {
503                name: "variant",
504                type_annotation: "\"grouped\" | \"stacked\"",
505                optional: true,
506            },
507            PropDef {
508                name: "xLabel",
509                type_annotation: "string",
510                optional: true,
511            },
512            PropDef {
513                name: "yLabel",
514                type_annotation: "string",
515                optional: true,
516            },
517        ],
518        description: "Horizontal bars; good for long category labels",
519    });
520
521    // Ref: Charts/Series.ts — data series sub-component
522    components.push(ComponentDef {
523        name: "Series",
524        props: &[
525            PropDef {
526                name: "category",
527                type_annotation: "string",
528                optional: false,
529            },
530            PropDef {
531                name: "values",
532                type_annotation: "number[]",
533                optional: false,
534            },
535        ],
536        description: "One data series",
537    });
538
539    // =========================================================================
540    // Charts (1D) — slices data
541    // =========================================================================
542
543    // Ref: Charts/PieChart.ts
544    components.push(ComponentDef {
545        name: "PieChart",
546        props: &[
547            PropDef { name: "slices", type_annotation: "Slice[]", optional: false },
548            PropDef { name: "variant", type_annotation: "\"pie\" | \"donut\"", optional: true },
549        ],
550        description: "Circular slices showing part-to-whole proportions; supports pie and donut variants",
551    });
552
553    // Ref: Charts/RadialChart.ts
554    components.push(ComponentDef {
555        name: "RadialChart",
556        props: &[PropDef {
557            name: "slices",
558            type_annotation: "Slice[]",
559            optional: false,
560        }],
561        description: "Radial bar chart for comparing a small number of values",
562    });
563
564    // Ref: Charts/SingleStackedBarChart.ts
565    components.push(ComponentDef {
566        name: "SingleStackedBarChart",
567        props: &[PropDef {
568            name: "slices",
569            type_annotation: "Slice[]",
570            optional: false,
571        }],
572        description: "Single horizontal stacked bar for quick part-to-whole comparison",
573    });
574
575    // Ref: Charts/Slice.ts — data slice sub-component
576    components.push(ComponentDef {
577        name: "Slice",
578        props: &[
579            PropDef {
580                name: "category",
581                type_annotation: "string",
582                optional: false,
583            },
584            PropDef {
585                name: "value",
586                type_annotation: "number",
587                optional: false,
588            },
589        ],
590        description: "One slice with label and numeric value",
591    });
592
593    // =========================================================================
594    // Charts (Scatter)
595    // =========================================================================
596
597    // Ref: Charts/ScatterChart.ts
598    components.push(ComponentDef {
599        name: "ScatterChart",
600        props: &[
601            PropDef {
602                name: "series",
603                type_annotation: "ScatterSeries[]",
604                optional: false,
605            },
606            PropDef {
607                name: "xLabel",
608                type_annotation: "string",
609                optional: true,
610            },
611            PropDef {
612                name: "yLabel",
613                type_annotation: "string",
614                optional: true,
615            },
616        ],
617        description: "Scatter plot for showing correlation between two variables",
618    });
619
620    // Ref: Charts/ScatterSeries.ts
621    components.push(ComponentDef {
622        name: "ScatterSeries",
623        props: &[
624            PropDef {
625                name: "name",
626                type_annotation: "string",
627                optional: false,
628            },
629            PropDef {
630                name: "points",
631                type_annotation: "Point[]",
632                optional: false,
633            },
634        ],
635        description: "One scatter data series",
636    });
637
638    // Ref: Charts/Point.ts
639    components.push(ComponentDef {
640        name: "Point",
641        props: &[
642            PropDef {
643                name: "x",
644                type_annotation: "number",
645                optional: false,
646            },
647            PropDef {
648                name: "y",
649                type_annotation: "number",
650                optional: false,
651            },
652            PropDef {
653                name: "z",
654                type_annotation: "number",
655                optional: true,
656            },
657        ],
658        description: "One data point with x, y coordinates and optional z",
659    });
660
661    // =========================================================================
662    // Forms
663    // Ref: packages/react-ui/src/genui-lib/{Form,FormControl,Input,...}/
664    // =========================================================================
665
666    // Ref: packages/react-ui/src/genui-lib/Form/
667    components.push(ComponentDef {
668        name: "Form",
669        props: &[
670            PropDef {
671                name: "name",
672                type_annotation: "string",
673                optional: false,
674            },
675            PropDef {
676                name: "fields",
677                type_annotation: "FormControl[]",
678                optional: false,
679            },
680            PropDef {
681                name: "submit",
682                type_annotation: "Button",
683                optional: true,
684            },
685        ],
686        description: "Form container with named fields and optional submit button",
687    });
688
689    // Ref: packages/react-ui/src/genui-lib/FormControl/
690    components.push(ComponentDef {
691        name: "FormControl",
692        props: &[
693            PropDef { name: "name", type_annotation: "string", optional: false },
694            PropDef { name: "label", type_annotation: "Label", optional: true },
695            PropDef { name: "input", type_annotation: "Input | TextArea | Select | DatePicker | Slider | CheckBoxGroup | RadioGroup | SwitchGroup", optional: false },
696            PropDef { name: "rules", type_annotation: "{required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}", optional: true },
697        ],
698        description: "Form field wrapper binding a name, label, input, and validation rules",
699    });
700
701    // Ref: packages/react-ui/src/genui-lib/Label/
702    components.push(ComponentDef {
703        name: "Label",
704        props: &[
705            PropDef {
706                name: "text",
707                type_annotation: "string",
708                optional: false,
709            },
710            PropDef {
711                name: "description",
712                type_annotation: "string",
713                optional: true,
714            },
715        ],
716        description: "Field label with optional description",
717    });
718
719    // Ref: packages/react-ui/src/genui-lib/Input/
720    components.push(ComponentDef {
721        name: "Input",
722        props: &[
723            PropDef {
724                name: "placeholder",
725                type_annotation: "string",
726                optional: true,
727            },
728            PropDef {
729                name: "defaultValue",
730                type_annotation: "string",
731                optional: true,
732            },
733        ],
734        description: "Single-line text input",
735    });
736
737    // Ref: packages/react-ui/src/genui-lib/TextArea/
738    components.push(ComponentDef {
739        name: "TextArea",
740        props: &[
741            PropDef {
742                name: "placeholder",
743                type_annotation: "string",
744                optional: true,
745            },
746            PropDef {
747                name: "defaultValue",
748                type_annotation: "string",
749                optional: true,
750            },
751            PropDef {
752                name: "rows",
753                type_annotation: "number",
754                optional: true,
755            },
756        ],
757        description: "Multi-line text area",
758    });
759
760    // Ref: packages/react-ui/src/genui-lib/Select/
761    components.push(ComponentDef {
762        name: "Select",
763        props: &[
764            PropDef {
765                name: "items",
766                type_annotation: "SelectItem[]",
767                optional: false,
768            },
769            PropDef {
770                name: "placeholder",
771                type_annotation: "string",
772                optional: true,
773            },
774            PropDef {
775                name: "defaultValue",
776                type_annotation: "string",
777                optional: true,
778            },
779        ],
780        description: "Dropdown select",
781    });
782
783    components.push(ComponentDef {
784        name: "SelectItem",
785        props: &[
786            PropDef {
787                name: "label",
788                type_annotation: "string",
789                optional: false,
790            },
791            PropDef {
792                name: "value",
793                type_annotation: "string",
794                optional: false,
795            },
796        ],
797        description: "One select option",
798    });
799
800    // Ref: packages/react-ui/src/genui-lib/DatePicker/
801    components.push(ComponentDef {
802        name: "DatePicker",
803        props: &[
804            PropDef {
805                name: "placeholder",
806                type_annotation: "string",
807                optional: true,
808            },
809            PropDef {
810                name: "defaultValue",
811                type_annotation: "string",
812                optional: true,
813            },
814        ],
815        description: "Date picker input",
816    });
817
818    // Ref: packages/react-ui/src/genui-lib/Slider/
819    components.push(ComponentDef {
820        name: "Slider",
821        props: &[
822            PropDef {
823                name: "min",
824                type_annotation: "number",
825                optional: true,
826            },
827            PropDef {
828                name: "max",
829                type_annotation: "number",
830                optional: true,
831            },
832            PropDef {
833                name: "step",
834                type_annotation: "number",
835                optional: true,
836            },
837            PropDef {
838                name: "defaultValue",
839                type_annotation: "number",
840                optional: true,
841            },
842        ],
843        description: "Numeric slider",
844    });
845
846    // Ref: packages/react-ui/src/genui-lib/CheckBoxGroup/
847    components.push(ComponentDef {
848        name: "CheckBoxGroup",
849        props: &[PropDef {
850            name: "items",
851            type_annotation: "CheckBoxItem[]",
852            optional: false,
853        }],
854        description: "Group of checkboxes for multi-select",
855    });
856
857    components.push(ComponentDef {
858        name: "CheckBoxItem",
859        props: &[
860            PropDef {
861                name: "label",
862                type_annotation: "string",
863                optional: false,
864            },
865            PropDef {
866                name: "value",
867                type_annotation: "string",
868                optional: false,
869            },
870            PropDef {
871                name: "defaultChecked",
872                type_annotation: "boolean",
873                optional: true,
874            },
875        ],
876        description: "One checkbox option",
877    });
878
879    // Ref: packages/react-ui/src/genui-lib/RadioGroup/
880    components.push(ComponentDef {
881        name: "RadioGroup",
882        props: &[
883            PropDef {
884                name: "items",
885                type_annotation: "RadioItem[]",
886                optional: false,
887            },
888            PropDef {
889                name: "defaultValue",
890                type_annotation: "string",
891                optional: true,
892            },
893        ],
894        description: "Group of radio buttons for single-select",
895    });
896
897    components.push(ComponentDef {
898        name: "RadioItem",
899        props: &[
900            PropDef {
901                name: "label",
902                type_annotation: "string",
903                optional: false,
904            },
905            PropDef {
906                name: "value",
907                type_annotation: "string",
908                optional: false,
909            },
910        ],
911        description: "One radio option",
912    });
913
914    // Ref: packages/react-ui/src/genui-lib/SwitchGroup/
915    components.push(ComponentDef {
916        name: "SwitchGroup",
917        props: &[PropDef {
918            name: "items",
919            type_annotation: "SwitchItem[]",
920            optional: false,
921        }],
922        description: "Group of toggle switches",
923    });
924
925    components.push(ComponentDef {
926        name: "SwitchItem",
927        props: &[
928            PropDef {
929                name: "label",
930                type_annotation: "string",
931                optional: false,
932            },
933            PropDef {
934                name: "value",
935                type_annotation: "string",
936                optional: false,
937            },
938            PropDef {
939                name: "defaultChecked",
940                type_annotation: "boolean",
941                optional: true,
942            },
943        ],
944        description: "One toggle switch",
945    });
946
947    // =========================================================================
948    // Buttons
949    // Ref: packages/react-ui/src/genui-lib/{Button,Buttons}/
950    // =========================================================================
951
952    // Ref: packages/react-ui/src/genui-lib/Button/schema.ts
953    components.push(ComponentDef {
954        name: "Button",
955        props: &[
956            PropDef {
957                name: "label",
958                type_annotation: "string",
959                optional: false,
960            },
961            PropDef {
962                name: "action",
963                type_annotation: "ContinueConversation | OpenUrl | Custom",
964                optional: true,
965            },
966            PropDef {
967                name: "variant",
968                type_annotation: "\"primary\" | \"secondary\" | \"tertiary\"",
969                optional: true,
970            },
971            PropDef {
972                name: "type",
973                type_annotation: "\"normal\" | \"destructive\"",
974                optional: true,
975            },
976            PropDef {
977                name: "size",
978                type_annotation: "\"extra-small\" | \"small\" | \"medium\" | \"large\"",
979                optional: true,
980            },
981        ],
982        description: "Clickable button with label and optional action",
983    });
984
985    // Ref: packages/react-ui/src/genui-lib/Buttons/
986    components.push(ComponentDef {
987        name: "Buttons",
988        props: &[
989            PropDef {
990                name: "buttons",
991                type_annotation: "Button[]",
992                optional: false,
993            },
994            PropDef {
995                name: "align",
996                type_annotation: "\"start\" | \"center\" | \"end\"",
997                optional: true,
998            },
999        ],
1000        description: "Row of buttons with alignment",
1001    });
1002
1003    // =========================================================================
1004    // Data Display
1005    // Ref: packages/react-ui/src/genui-lib/{TagBlock,Tag}/
1006    // =========================================================================
1007
1008    components.push(ComponentDef {
1009        name: "TagBlock",
1010        props: &[PropDef {
1011            name: "tags",
1012            type_annotation: "Tag[]",
1013            optional: false,
1014        }],
1015        description: "Group of tags",
1016    });
1017
1018    components.push(ComponentDef {
1019        name: "Tag",
1020        props: &[
1021            PropDef {
1022                name: "label",
1023                type_annotation: "string",
1024                optional: false,
1025            },
1026            PropDef {
1027                name: "variant",
1028                type_annotation: "\"default\" | \"success\" | \"warning\" | \"error\" | \"info\"",
1029                optional: true,
1030            },
1031        ],
1032        description: "Single tag/badge",
1033    });
1034
1035    // =========================================================================
1036    // Chat-specific components (only in openuiChatLibrary)
1037    // Ref: packages/react-ui/src/genui-lib/{ListBlock,FollowUpBlock,SectionBlock}/
1038    // =========================================================================
1039
1040    // Ref: packages/react-ui/src/genui-lib/ListBlock/
1041    components.push(ComponentDef {
1042        name: "ListBlock",
1043        props: &[
1044            PropDef {
1045                name: "items",
1046                type_annotation: "ListItem[]",
1047                optional: false,
1048            },
1049            PropDef {
1050                name: "variant",
1051                type_annotation: "\"ordered\" | \"unordered\"",
1052                optional: true,
1053            },
1054        ],
1055        description: "Ordered or unordered list",
1056    });
1057
1058    components.push(ComponentDef {
1059        name: "ListItem",
1060        props: &[
1061            PropDef {
1062                name: "text",
1063                type_annotation: "string",
1064                optional: false,
1065            },
1066            PropDef {
1067                name: "children",
1068                type_annotation: "any[]",
1069                optional: true,
1070            },
1071        ],
1072        description: "One list item with text and optional nested content",
1073    });
1074
1075    // Ref: packages/react-ui/src/genui-lib/FollowUpBlock/
1076    components.push(ComponentDef {
1077        name: "FollowUpBlock",
1078        props: &[PropDef {
1079            name: "items",
1080            type_annotation: "FollowUpItem[]",
1081            optional: false,
1082        }],
1083        description: "Suggested follow-up questions",
1084    });
1085
1086    components.push(ComponentDef {
1087        name: "FollowUpItem",
1088        props: &[
1089            PropDef {
1090                name: "text",
1091                type_annotation: "string",
1092                optional: false,
1093            },
1094            PropDef {
1095                name: "action",
1096                type_annotation: "ContinueConversation | OpenUrl | Custom",
1097                optional: true,
1098            },
1099        ],
1100        description: "One follow-up suggestion with action",
1101    });
1102
1103    // Ref: packages/react-ui/src/genui-lib/SectionBlock/
1104    components.push(ComponentDef {
1105        name: "SectionBlock",
1106        props: &[PropDef {
1107            name: "items",
1108            type_annotation: "SectionItem[]",
1109            optional: false,
1110        }],
1111        description: "Block of titled sections",
1112    });
1113
1114    components.push(ComponentDef {
1115        name: "SectionItem",
1116        props: &[
1117            PropDef {
1118                name: "title",
1119                type_annotation: "string",
1120                optional: false,
1121            },
1122            PropDef {
1123                name: "children",
1124                type_annotation: "any[]",
1125                optional: false,
1126            },
1127        ],
1128        description: "One section with title and content",
1129    });
1130
1131    components
1132}
1133
1134#[cfg(test)]
1135mod tests {
1136    use super::*;
1137
1138    #[test]
1139    fn test_all_components_have_names() {
1140        let components = all_components();
1141        for comp in &components {
1142            assert!(!comp.name.is_empty());
1143            // Component names should be PascalCase
1144            assert!(
1145                comp.name.chars().next().unwrap().is_uppercase(),
1146                "Component name '{}' should start with uppercase",
1147                comp.name
1148            );
1149        }
1150    }
1151
1152    #[test]
1153    fn test_no_duplicate_component_names() {
1154        let components = all_components();
1155        let mut seen = std::collections::HashSet::new();
1156        for comp in &components {
1157            assert!(
1158                seen.insert(comp.name),
1159                "Duplicate component name: {}",
1160                comp.name
1161            );
1162        }
1163    }
1164
1165    #[test]
1166    fn test_all_components_have_descriptions() {
1167        let components = all_components();
1168        for comp in &components {
1169            assert!(
1170                !comp.description.is_empty(),
1171                "Component '{}' has empty description",
1172                comp.name
1173            );
1174        }
1175    }
1176
1177    #[test]
1178    fn test_prop_types_not_empty() {
1179        let components = all_components();
1180        for comp in &components {
1181            for prop in comp.props {
1182                assert!(
1183                    !prop.type_annotation.is_empty(),
1184                    "Component '{}' prop '{}' has empty type annotation",
1185                    comp.name,
1186                    prop.name
1187                );
1188            }
1189        }
1190    }
1191
1192    #[test]
1193    fn test_stack_component_props() {
1194        let components = all_components();
1195        let stack = components.iter().find(|c| c.name == "Stack").unwrap();
1196        assert_eq!(stack.props[0].name, "children");
1197        assert!(!stack.props[0].optional);
1198        assert_eq!(stack.props[1].name, "direction");
1199        assert!(stack.props[1].optional);
1200    }
1201
1202    #[test]
1203    fn test_table_component_props() {
1204        let components = all_components();
1205        let table = components.iter().find(|c| c.name == "Table").unwrap();
1206        assert_eq!(table.props[0].name, "columns");
1207        assert_eq!(table.props[1].name, "rows");
1208        assert!(!table.props[0].optional);
1209    }
1210}