From 5b0d35f876a6e4009d036f40036cf3adb1e50008 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 13:30:02 +0000 Subject: [PATCH 1/6] Initial plan From 484a2e782f2c595464d1a280dc9e9c54571707ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 13:42:39 +0000 Subject: [PATCH 2/6] Implement component library protocol with all component schemas Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- .../docs/references/ui/AccordionComponent.mdx | 14 + content/docs/references/ui/AlertComponent.mdx | 14 + content/docs/references/ui/BadgeComponent.mdx | 14 + .../references/ui/BreadcrumbComponent.mdx | 14 + content/docs/references/ui/CardComponent.mdx | 14 + content/docs/references/ui/Component.mdx | 14 + content/docs/references/ui/ComponentType.mdx | 19 + .../docs/references/ui/DrawerComponent.mdx | 14 + content/docs/references/ui/ModalComponent.mdx | 14 + .../docs/references/ui/PopoverComponent.mdx | 14 + .../docs/references/ui/StepperComponent.mdx | 14 + content/docs/references/ui/TabsComponent.mdx | 14 + .../docs/references/ui/TimelineComponent.mdx | 14 + .../docs/references/ui/TooltipComponent.mdx | 14 + .../spec/json-schema/AccordionComponent.json | 173 +++ packages/spec/json-schema/AlertComponent.json | 120 ++ packages/spec/json-schema/BadgeComponent.json | 123 ++ .../spec/json-schema/BreadcrumbComponent.json | 123 ++ packages/spec/json-schema/CardComponent.json | 107 ++ packages/spec/json-schema/Component.json | 55 + packages/spec/json-schema/ComponentType.json | 23 + .../spec/json-schema/DrawerComponent.json | 118 ++ packages/spec/json-schema/ModalComponent.json | 112 ++ .../spec/json-schema/PopoverComponent.json | 116 ++ .../spec/json-schema/StepperComponent.json | 182 +++ packages/spec/json-schema/TabsComponent.json | 170 +++ .../spec/json-schema/TimelineComponent.json | 181 +++ .../spec/json-schema/TooltipComponent.json | 112 ++ packages/spec/src/ui/component.test.ts | 1107 +++++++++++++++++ packages/spec/src/ui/component.zod.ts | 637 ++++++++++ packages/spec/src/ui/index.ts | 2 + 31 files changed, 3662 insertions(+) create mode 100644 content/docs/references/ui/AccordionComponent.mdx create mode 100644 content/docs/references/ui/AlertComponent.mdx create mode 100644 content/docs/references/ui/BadgeComponent.mdx create mode 100644 content/docs/references/ui/BreadcrumbComponent.mdx create mode 100644 content/docs/references/ui/CardComponent.mdx create mode 100644 content/docs/references/ui/Component.mdx create mode 100644 content/docs/references/ui/ComponentType.mdx create mode 100644 content/docs/references/ui/DrawerComponent.mdx create mode 100644 content/docs/references/ui/ModalComponent.mdx create mode 100644 content/docs/references/ui/PopoverComponent.mdx create mode 100644 content/docs/references/ui/StepperComponent.mdx create mode 100644 content/docs/references/ui/TabsComponent.mdx create mode 100644 content/docs/references/ui/TimelineComponent.mdx create mode 100644 content/docs/references/ui/TooltipComponent.mdx create mode 100644 packages/spec/json-schema/AccordionComponent.json create mode 100644 packages/spec/json-schema/AlertComponent.json create mode 100644 packages/spec/json-schema/BadgeComponent.json create mode 100644 packages/spec/json-schema/BreadcrumbComponent.json create mode 100644 packages/spec/json-schema/CardComponent.json create mode 100644 packages/spec/json-schema/Component.json create mode 100644 packages/spec/json-schema/ComponentType.json create mode 100644 packages/spec/json-schema/DrawerComponent.json create mode 100644 packages/spec/json-schema/ModalComponent.json create mode 100644 packages/spec/json-schema/PopoverComponent.json create mode 100644 packages/spec/json-schema/StepperComponent.json create mode 100644 packages/spec/json-schema/TabsComponent.json create mode 100644 packages/spec/json-schema/TimelineComponent.json create mode 100644 packages/spec/json-schema/TooltipComponent.json create mode 100644 packages/spec/src/ui/component.test.ts create mode 100644 packages/spec/src/ui/component.zod.ts diff --git a/content/docs/references/ui/AccordionComponent.mdx b/content/docs/references/ui/AccordionComponent.mdx new file mode 100644 index 0000000..788ceb5 --- /dev/null +++ b/content/docs/references/ui/AccordionComponent.mdx @@ -0,0 +1,14 @@ +--- +title: AccordionComponent +description: AccordionComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/AlertComponent.mdx b/content/docs/references/ui/AlertComponent.mdx new file mode 100644 index 0000000..833ad8e --- /dev/null +++ b/content/docs/references/ui/AlertComponent.mdx @@ -0,0 +1,14 @@ +--- +title: AlertComponent +description: AlertComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/BadgeComponent.mdx b/content/docs/references/ui/BadgeComponent.mdx new file mode 100644 index 0000000..5fcfb69 --- /dev/null +++ b/content/docs/references/ui/BadgeComponent.mdx @@ -0,0 +1,14 @@ +--- +title: BadgeComponent +description: BadgeComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/BreadcrumbComponent.mdx b/content/docs/references/ui/BreadcrumbComponent.mdx new file mode 100644 index 0000000..1f34455 --- /dev/null +++ b/content/docs/references/ui/BreadcrumbComponent.mdx @@ -0,0 +1,14 @@ +--- +title: BreadcrumbComponent +description: BreadcrumbComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/CardComponent.mdx b/content/docs/references/ui/CardComponent.mdx new file mode 100644 index 0000000..99ac7bf --- /dev/null +++ b/content/docs/references/ui/CardComponent.mdx @@ -0,0 +1,14 @@ +--- +title: CardComponent +description: CardComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | optional | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/Component.mdx b/content/docs/references/ui/Component.mdx new file mode 100644 index 0000000..b83d357 --- /dev/null +++ b/content/docs/references/ui/Component.mdx @@ -0,0 +1,14 @@ +--- +title: Component +description: Component Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `Enum<'card' \| 'tabs' \| 'accordion' \| 'modal' \| 'drawer' \| 'timeline' \| 'stepper' \| 'breadcrumb' \| 'alert' \| 'badge' \| 'tooltip' \| 'popover'>` | ✅ | Component type | +| **props** | `Record` | optional | Component properties | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `any[]` | optional | Child components | diff --git a/content/docs/references/ui/ComponentType.mdx b/content/docs/references/ui/ComponentType.mdx new file mode 100644 index 0000000..c0783d2 --- /dev/null +++ b/content/docs/references/ui/ComponentType.mdx @@ -0,0 +1,19 @@ +--- +title: ComponentType +description: ComponentType Schema Reference +--- + +## Allowed Values + +* `card` +* `tabs` +* `accordion` +* `modal` +* `drawer` +* `timeline` +* `stepper` +* `breadcrumb` +* `alert` +* `badge` +* `tooltip` +* `popover` \ No newline at end of file diff --git a/content/docs/references/ui/DrawerComponent.mdx b/content/docs/references/ui/DrawerComponent.mdx new file mode 100644 index 0000000..7698940 --- /dev/null +++ b/content/docs/references/ui/DrawerComponent.mdx @@ -0,0 +1,14 @@ +--- +title: DrawerComponent +description: DrawerComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | optional | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/ModalComponent.mdx b/content/docs/references/ui/ModalComponent.mdx new file mode 100644 index 0000000..40fd0db --- /dev/null +++ b/content/docs/references/ui/ModalComponent.mdx @@ -0,0 +1,14 @@ +--- +title: ModalComponent +description: ModalComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | optional | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/PopoverComponent.mdx b/content/docs/references/ui/PopoverComponent.mdx new file mode 100644 index 0000000..a8f98b2 --- /dev/null +++ b/content/docs/references/ui/PopoverComponent.mdx @@ -0,0 +1,14 @@ +--- +title: PopoverComponent +description: PopoverComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | optional | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/StepperComponent.mdx b/content/docs/references/ui/StepperComponent.mdx new file mode 100644 index 0000000..7215263 --- /dev/null +++ b/content/docs/references/ui/StepperComponent.mdx @@ -0,0 +1,14 @@ +--- +title: StepperComponent +description: StepperComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/TabsComponent.mdx b/content/docs/references/ui/TabsComponent.mdx new file mode 100644 index 0000000..2497a42 --- /dev/null +++ b/content/docs/references/ui/TabsComponent.mdx @@ -0,0 +1,14 @@ +--- +title: TabsComponent +description: TabsComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/TimelineComponent.mdx b/content/docs/references/ui/TimelineComponent.mdx new file mode 100644 index 0000000..6487f2e --- /dev/null +++ b/content/docs/references/ui/TimelineComponent.mdx @@ -0,0 +1,14 @@ +--- +title: TimelineComponent +description: TimelineComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/TooltipComponent.mdx b/content/docs/references/ui/TooltipComponent.mdx new file mode 100644 index 0000000..8d5ca3c --- /dev/null +++ b/content/docs/references/ui/TooltipComponent.mdx @@ -0,0 +1,14 @@ +--- +title: TooltipComponent +description: TooltipComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/packages/spec/json-schema/AccordionComponent.json b/packages/spec/json-schema/AccordionComponent.json new file mode 100644 index 0000000..574cf19 --- /dev/null +++ b/packages/spec/json-schema/AccordionComponent.json @@ -0,0 +1,173 @@ +{ + "$ref": "#/definitions/AccordionComponent", + "definitions": { + "AccordionComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "accordion" + }, + "props": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Section title" + }, + "icon": { + "type": "string", + "description": "Section icon" + }, + "content": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "timeline", + "stepper", + "breadcrumb", + "alert", + "badge", + "tooltip", + "popover" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Section content" + }, + "defaultExpanded": { + "type": "boolean", + "description": "Initially expanded" + } + }, + "required": [ + "title" + ], + "additionalProperties": false + }, + "description": "Accordion items" + }, + "allowMultiple": { + "type": "boolean", + "description": "Allow multiple open sections" + } + }, + "required": [ + "items" + ], + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "timeline", + "stepper", + "breadcrumb", + "alert", + "badge", + "tooltip", + "popover" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type", + "props" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/AlertComponent.json b/packages/spec/json-schema/AlertComponent.json new file mode 100644 index 0000000..81ba845 --- /dev/null +++ b/packages/spec/json-schema/AlertComponent.json @@ -0,0 +1,120 @@ +{ + "$ref": "#/definitions/AlertComponent", + "definitions": { + "AlertComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "alert" + }, + "props": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Alert title" + }, + "message": { + "type": "string", + "description": "Alert message" + }, + "variant": { + "type": "string", + "enum": [ + "info", + "success", + "warning", + "error" + ], + "description": "Alert variant" + }, + "dismissible": { + "type": "boolean", + "description": "Dismissible alert" + }, + "icon": { + "type": "string", + "description": "Alert icon" + } + }, + "required": [ + "message" + ], + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "timeline", + "stepper", + "breadcrumb", + "alert", + "badge", + "tooltip", + "popover" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type", + "props" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/BadgeComponent.json b/packages/spec/json-schema/BadgeComponent.json new file mode 100644 index 0000000..4d18819 --- /dev/null +++ b/packages/spec/json-schema/BadgeComponent.json @@ -0,0 +1,123 @@ +{ + "$ref": "#/definitions/BadgeComponent", + "definitions": { + "BadgeComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "badge" + }, + "props": { + "type": "object", + "properties": { + "label": { + "type": "string", + "description": "Badge label" + }, + "variant": { + "type": "string", + "enum": [ + "primary", + "secondary", + "success", + "warning", + "error", + "info" + ], + "description": "Badge variant" + }, + "icon": { + "type": "string", + "description": "Badge icon" + }, + "size": { + "type": "string", + "enum": [ + "small", + "medium", + "large" + ], + "description": "Badge size" + } + }, + "required": [ + "label" + ], + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "timeline", + "stepper", + "breadcrumb", + "alert", + "badge", + "tooltip", + "popover" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type", + "props" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/BreadcrumbComponent.json b/packages/spec/json-schema/BreadcrumbComponent.json new file mode 100644 index 0000000..5d2dbcb --- /dev/null +++ b/packages/spec/json-schema/BreadcrumbComponent.json @@ -0,0 +1,123 @@ +{ + "$ref": "#/definitions/BreadcrumbComponent", + "definitions": { + "BreadcrumbComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "breadcrumb" + }, + "props": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "description": "Breadcrumb label" + }, + "href": { + "type": "string", + "description": "Breadcrumb link" + }, + "icon": { + "type": "string", + "description": "Breadcrumb icon" + } + }, + "required": [ + "label" + ], + "additionalProperties": false + }, + "description": "Breadcrumb items" + }, + "separator": { + "type": "string", + "description": "Breadcrumb separator" + } + }, + "required": [ + "items" + ], + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "timeline", + "stepper", + "breadcrumb", + "alert", + "badge", + "tooltip", + "popover" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type", + "props" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/CardComponent.json b/packages/spec/json-schema/CardComponent.json new file mode 100644 index 0000000..f638987 --- /dev/null +++ b/packages/spec/json-schema/CardComponent.json @@ -0,0 +1,107 @@ +{ + "$ref": "#/definitions/CardComponent", + "definitions": { + "CardComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "card" + }, + "props": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Card title" + }, + "subtitle": { + "type": "string", + "description": "Card subtitle" + }, + "image": { + "type": "string", + "format": "uri", + "description": "Card image URL" + }, + "actions": { + "type": "array", + "description": "Card action buttons" + } + }, + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "timeline", + "stepper", + "breadcrumb", + "alert", + "badge", + "tooltip", + "popover" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/Component.json b/packages/spec/json-schema/Component.json new file mode 100644 index 0000000..3de372d --- /dev/null +++ b/packages/spec/json-schema/Component.json @@ -0,0 +1,55 @@ +{ + "$ref": "#/definitions/Component", + "definitions": { + "Component": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "timeline", + "stepper", + "breadcrumb", + "alert", + "badge", + "tooltip", + "popover" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ComponentType.json b/packages/spec/json-schema/ComponentType.json new file mode 100644 index 0000000..77d21f8 --- /dev/null +++ b/packages/spec/json-schema/ComponentType.json @@ -0,0 +1,23 @@ +{ + "$ref": "#/definitions/ComponentType", + "definitions": { + "ComponentType": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "timeline", + "stepper", + "breadcrumb", + "alert", + "badge", + "tooltip", + "popover" + ] + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/DrawerComponent.json b/packages/spec/json-schema/DrawerComponent.json new file mode 100644 index 0000000..36a050d --- /dev/null +++ b/packages/spec/json-schema/DrawerComponent.json @@ -0,0 +1,118 @@ +{ + "$ref": "#/definitions/DrawerComponent", + "definitions": { + "DrawerComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "drawer" + }, + "props": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Drawer title" + }, + "position": { + "type": "string", + "enum": [ + "left", + "right", + "top", + "bottom" + ], + "description": "Drawer position" + }, + "size": { + "type": "string", + "enum": [ + "small", + "medium", + "large", + "full" + ], + "description": "Drawer size" + }, + "closeOnOverlay": { + "type": "boolean", + "description": "Close on overlay click" + } + }, + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "timeline", + "stepper", + "breadcrumb", + "alert", + "badge", + "tooltip", + "popover" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ModalComponent.json b/packages/spec/json-schema/ModalComponent.json new file mode 100644 index 0000000..b516168 --- /dev/null +++ b/packages/spec/json-schema/ModalComponent.json @@ -0,0 +1,112 @@ +{ + "$ref": "#/definitions/ModalComponent", + "definitions": { + "ModalComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "modal" + }, + "props": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Modal title" + }, + "size": { + "type": "string", + "enum": [ + "small", + "medium", + "large", + "full" + ], + "description": "Modal size" + }, + "closeOnOverlay": { + "type": "boolean", + "description": "Close on overlay click" + }, + "showClose": { + "type": "boolean", + "description": "Show close button" + } + }, + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "timeline", + "stepper", + "breadcrumb", + "alert", + "badge", + "tooltip", + "popover" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/PopoverComponent.json b/packages/spec/json-schema/PopoverComponent.json new file mode 100644 index 0000000..0ef5e83 --- /dev/null +++ b/packages/spec/json-schema/PopoverComponent.json @@ -0,0 +1,116 @@ +{ + "$ref": "#/definitions/PopoverComponent", + "definitions": { + "PopoverComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "popover" + }, + "props": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Popover title" + }, + "trigger": { + "type": "string", + "enum": [ + "click", + "hover" + ], + "description": "Trigger type" + }, + "position": { + "type": "string", + "enum": [ + "top", + "bottom", + "left", + "right" + ], + "description": "Popover position" + }, + "closeOnOutsideClick": { + "type": "boolean", + "description": "Close on outside click" + } + }, + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "timeline", + "stepper", + "breadcrumb", + "alert", + "badge", + "tooltip", + "popover" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/StepperComponent.json b/packages/spec/json-schema/StepperComponent.json new file mode 100644 index 0000000..8b11d22 --- /dev/null +++ b/packages/spec/json-schema/StepperComponent.json @@ -0,0 +1,182 @@ +{ + "$ref": "#/definitions/StepperComponent", + "definitions": { + "StepperComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "stepper" + }, + "props": { + "type": "object", + "properties": { + "steps": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "description": "Step label" + }, + "description": { + "type": "string", + "description": "Step description" + }, + "icon": { + "type": "string", + "description": "Step icon" + }, + "content": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "timeline", + "stepper", + "breadcrumb", + "alert", + "badge", + "tooltip", + "popover" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Step content" + } + }, + "required": [ + "label" + ], + "additionalProperties": false + }, + "description": "Stepper steps" + }, + "currentStep": { + "type": "integer", + "minimum": 0, + "description": "Current step index" + }, + "orientation": { + "type": "string", + "enum": [ + "horizontal", + "vertical" + ], + "description": "Stepper orientation" + } + }, + "required": [ + "steps" + ], + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "timeline", + "stepper", + "breadcrumb", + "alert", + "badge", + "tooltip", + "popover" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type", + "props" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/TabsComponent.json b/packages/spec/json-schema/TabsComponent.json new file mode 100644 index 0000000..a653cba --- /dev/null +++ b/packages/spec/json-schema/TabsComponent.json @@ -0,0 +1,170 @@ +{ + "$ref": "#/definitions/TabsComponent", + "definitions": { + "TabsComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "tabs" + }, + "props": { + "type": "object", + "properties": { + "tabs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "description": "Tab label" + }, + "icon": { + "type": "string", + "description": "Tab icon" + }, + "content": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "timeline", + "stepper", + "breadcrumb", + "alert", + "badge", + "tooltip", + "popover" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Tab content" + } + }, + "required": [ + "label" + ], + "additionalProperties": false + }, + "description": "Tab items" + }, + "defaultTab": { + "type": "integer", + "minimum": 0, + "description": "Default active tab index" + } + }, + "required": [ + "tabs" + ], + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "timeline", + "stepper", + "breadcrumb", + "alert", + "badge", + "tooltip", + "popover" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type", + "props" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/TimelineComponent.json b/packages/spec/json-schema/TimelineComponent.json new file mode 100644 index 0000000..daacbf9 --- /dev/null +++ b/packages/spec/json-schema/TimelineComponent.json @@ -0,0 +1,181 @@ +{ + "$ref": "#/definitions/TimelineComponent", + "definitions": { + "TimelineComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "timeline" + }, + "props": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Event title" + }, + "timestamp": { + "type": "string", + "description": "Event timestamp" + }, + "description": { + "type": "string", + "description": "Event description" + }, + "icon": { + "type": "string", + "description": "Event icon" + }, + "content": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "timeline", + "stepper", + "breadcrumb", + "alert", + "badge", + "tooltip", + "popover" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Event content" + } + }, + "required": [ + "title" + ], + "additionalProperties": false + }, + "description": "Timeline items" + }, + "orientation": { + "type": "string", + "enum": [ + "vertical", + "horizontal" + ], + "description": "Timeline orientation" + } + }, + "required": [ + "items" + ], + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "timeline", + "stepper", + "breadcrumb", + "alert", + "badge", + "tooltip", + "popover" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type", + "props" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/TooltipComponent.json b/packages/spec/json-schema/TooltipComponent.json new file mode 100644 index 0000000..8cc358c --- /dev/null +++ b/packages/spec/json-schema/TooltipComponent.json @@ -0,0 +1,112 @@ +{ + "$ref": "#/definitions/TooltipComponent", + "definitions": { + "TooltipComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "tooltip" + }, + "props": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "Tooltip content" + }, + "position": { + "type": "string", + "enum": [ + "top", + "bottom", + "left", + "right" + ], + "description": "Tooltip position" + }, + "delay": { + "type": "number", + "description": "Show delay in milliseconds" + } + }, + "required": [ + "content" + ], + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "timeline", + "stepper", + "breadcrumb", + "alert", + "badge", + "tooltip", + "popover" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type", + "props" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/src/ui/component.test.ts b/packages/spec/src/ui/component.test.ts new file mode 100644 index 0000000..628e211 --- /dev/null +++ b/packages/spec/src/ui/component.test.ts @@ -0,0 +1,1107 @@ +import { describe, it, expect } from 'vitest'; +import { + ComponentType, + ComponentSchema, + CardComponentSchema, + TabsComponentSchema, + AccordionComponentSchema, + ModalComponentSchema, + DrawerComponentSchema, + TimelineComponentSchema, + StepperComponentSchema, + BreadcrumbComponentSchema, + AlertComponentSchema, + BadgeComponentSchema, + TooltipComponentSchema, + PopoverComponentSchema, + Component, + type Component as ComponentType, +} from './component.zod'; + +describe('ComponentType', () => { + it('should accept all valid component types', () => { + const validTypes = [ + 'card', + 'tabs', + 'accordion', + 'modal', + 'drawer', + 'timeline', + 'stepper', + 'breadcrumb', + 'alert', + 'badge', + 'tooltip', + 'popover', + ] as const; + + validTypes.forEach(type => { + expect(() => ComponentType.parse(type)).not.toThrow(); + }); + }); + + it('should reject invalid component types', () => { + const invalidTypes = ['button', 'input', 'select', 'invalid']; + + invalidTypes.forEach(type => { + expect(() => ComponentType.parse(type)).toThrow(); + }); + }); +}); + +describe('ComponentSchema', () => { + describe('Basic Component', () => { + it('should accept minimal component', () => { + const component: ComponentType = { + type: 'card', + }; + + expect(() => ComponentSchema.parse(component)).not.toThrow(); + }); + + it('should accept component with props', () => { + const component: ComponentType = { + type: 'card', + props: { + title: 'Test Card', + customProp: 'value', + }, + }; + + expect(() => ComponentSchema.parse(component)).not.toThrow(); + }); + + it('should accept component with style', () => { + const component: ComponentType = { + type: 'card', + style: { + padding: '16px', + borderRadius: '8px', + backgroundColor: '#f5f5f5', + }, + }; + + expect(() => ComponentSchema.parse(component)).not.toThrow(); + }); + }); + + describe('Component Nesting (Children)', () => { + it('should accept component with single child', () => { + const component: ComponentType = { + type: 'card', + children: [ + { + type: 'badge', + props: { label: 'New' }, + }, + ], + }; + + expect(() => ComponentSchema.parse(component)).not.toThrow(); + }); + + it('should accept component with multiple children', () => { + const component: ComponentType = { + type: 'card', + props: { + title: 'Container', + }, + children: [ + { + type: 'badge', + props: { label: 'Status' }, + }, + { + type: 'alert', + props: { message: 'Information' }, + }, + ], + }; + + expect(() => ComponentSchema.parse(component)).not.toThrow(); + }); + + it('should accept deeply nested components', () => { + const component: ComponentType = { + type: 'card', + children: [ + { + type: 'accordion', + children: [ + { + type: 'tabs', + children: [ + { + type: 'badge', + props: { label: 'Deeply nested' }, + }, + ], + }, + ], + }, + ], + }; + + expect(() => ComponentSchema.parse(component)).not.toThrow(); + }); + }); + + describe('Event Binding', () => { + it('should accept component with events', () => { + const component = { + type: 'card' as const, + events: { + onClick: () => {}, + onHover: () => {}, + }, + }; + + expect(() => ComponentSchema.parse(component)).not.toThrow(); + }); + + it('should accept component with multiple event handlers', () => { + const component = { + type: 'modal' as const, + events: { + onOpen: () => console.log('opened'), + onClose: () => console.log('closed'), + onSubmit: () => console.log('submitted'), + }, + }; + + expect(() => ComponentSchema.parse(component)).not.toThrow(); + }); + }); +}); + +describe('CardComponentSchema', () => { + it('should accept minimal card', () => { + const card = { + type: 'card' as const, + }; + + expect(() => CardComponentSchema.parse(card)).not.toThrow(); + }); + + it('should accept card with title and subtitle', () => { + const card = { + type: 'card' as const, + props: { + title: 'Project Overview', + subtitle: 'Q4 2025', + }, + }; + + expect(() => CardComponentSchema.parse(card)).not.toThrow(); + }); + + it('should accept card with image', () => { + const card = { + type: 'card' as const, + props: { + title: 'User Profile', + image: 'https://example.com/avatar.jpg', + }, + }; + + expect(() => CardComponentSchema.parse(card)).not.toThrow(); + }); + + it('should reject card with invalid image URL', () => { + const card = { + type: 'card' as const, + props: { + image: 'not-a-url', + }, + }; + + expect(() => CardComponentSchema.parse(card)).toThrow(); + }); + + it('should accept card with actions', () => { + const card = { + type: 'card' as const, + props: { + title: 'Task Card', + actions: [ + { label: 'Edit', onClick: () => {} }, + { label: 'Delete', onClick: () => {} }, + ], + }, + }; + + expect(() => CardComponentSchema.parse(card)).not.toThrow(); + }); + + it('should accept card with children', () => { + const card = { + type: 'card' as const, + props: { + title: 'Container Card', + }, + children: [ + { + type: 'alert' as const, + props: { message: 'Success!' }, + }, + ], + }; + + expect(() => CardComponentSchema.parse(card)).not.toThrow(); + }); +}); + +describe('TabsComponentSchema', () => { + it('should accept minimal tabs', () => { + const tabs = { + type: 'tabs' as const, + props: { + tabs: [ + { label: 'Tab 1' }, + { label: 'Tab 2' }, + ], + }, + }; + + expect(() => TabsComponentSchema.parse(tabs)).not.toThrow(); + }); + + it('should accept tabs with icons', () => { + const tabs = { + type: 'tabs' as const, + props: { + tabs: [ + { label: 'Home', icon: 'home' }, + { label: 'Settings', icon: 'settings' }, + ], + }, + }; + + expect(() => TabsComponentSchema.parse(tabs)).not.toThrow(); + }); + + it('should accept tabs with content', () => { + const tabs = { + type: 'tabs' as const, + props: { + tabs: [ + { + label: 'Overview', + content: { + type: 'card' as const, + props: { title: 'Overview Content' }, + }, + }, + { + label: 'Details', + content: { + type: 'card' as const, + props: { title: 'Details Content' }, + }, + }, + ], + }, + }; + + expect(() => TabsComponentSchema.parse(tabs)).not.toThrow(); + }); + + it('should accept tabs with default tab', () => { + const tabs = { + type: 'tabs' as const, + props: { + tabs: [ + { label: 'Tab 1' }, + { label: 'Tab 2' }, + ], + defaultTab: 1, + }, + }; + + expect(() => TabsComponentSchema.parse(tabs)).not.toThrow(); + }); + + it('should reject negative default tab index', () => { + const tabs = { + type: 'tabs' as const, + props: { + tabs: [{ label: 'Tab 1' }], + defaultTab: -1, + }, + }; + + expect(() => TabsComponentSchema.parse(tabs)).toThrow(); + }); +}); + +describe('AccordionComponentSchema', () => { + it('should accept minimal accordion', () => { + const accordion = { + type: 'accordion' as const, + props: { + items: [ + { title: 'Section 1' }, + { title: 'Section 2' }, + ], + }, + }; + + expect(() => AccordionComponentSchema.parse(accordion)).not.toThrow(); + }); + + it('should accept accordion with content', () => { + const accordion = { + type: 'accordion' as const, + props: { + items: [ + { + title: 'User Info', + content: { + type: 'card' as const, + props: { title: 'User Details' }, + }, + }, + ], + }, + }; + + expect(() => AccordionComponentSchema.parse(accordion)).not.toThrow(); + }); + + it('should accept accordion with icons and expanded state', () => { + const accordion = { + type: 'accordion' as const, + props: { + items: [ + { + title: 'Account', + icon: 'user', + defaultExpanded: true, + }, + { + title: 'Settings', + icon: 'settings', + defaultExpanded: false, + }, + ], + allowMultiple: true, + }, + }; + + expect(() => AccordionComponentSchema.parse(accordion)).not.toThrow(); + }); +}); + +describe('ModalComponentSchema', () => { + it('should accept minimal modal', () => { + const modal = { + type: 'modal' as const, + }; + + expect(() => ModalComponentSchema.parse(modal)).not.toThrow(); + }); + + it('should accept modal with all properties', () => { + const modal = { + type: 'modal' as const, + props: { + title: 'Confirm Delete', + size: 'medium' as const, + closeOnOverlay: true, + showClose: true, + }, + }; + + expect(() => ModalComponentSchema.parse(modal)).not.toThrow(); + }); + + it('should accept all modal sizes', () => { + const sizes = ['small', 'medium', 'large', 'full'] as const; + + sizes.forEach(size => { + const modal = { + type: 'modal' as const, + props: { size }, + }; + expect(() => ModalComponentSchema.parse(modal)).not.toThrow(); + }); + }); + + it('should accept modal with children', () => { + const modal = { + type: 'modal' as const, + props: { title: 'Warning' }, + children: [ + { + type: 'alert' as const, + props: { message: 'This action cannot be undone' }, + }, + ], + }; + + expect(() => ModalComponentSchema.parse(modal)).not.toThrow(); + }); +}); + +describe('DrawerComponentSchema', () => { + it('should accept minimal drawer', () => { + const drawer = { + type: 'drawer' as const, + }; + + expect(() => DrawerComponentSchema.parse(drawer)).not.toThrow(); + }); + + it('should accept all drawer positions', () => { + const positions = ['left', 'right', 'top', 'bottom'] as const; + + positions.forEach(position => { + const drawer = { + type: 'drawer' as const, + props: { position }, + }; + expect(() => DrawerComponentSchema.parse(drawer)).not.toThrow(); + }); + }); + + it('should accept drawer with all properties', () => { + const drawer = { + type: 'drawer' as const, + props: { + title: 'Filters', + position: 'right' as const, + size: 'medium' as const, + closeOnOverlay: true, + }, + }; + + expect(() => DrawerComponentSchema.parse(drawer)).not.toThrow(); + }); +}); + +describe('TimelineComponentSchema', () => { + it('should accept minimal timeline', () => { + const timeline = { + type: 'timeline' as const, + props: { + items: [ + { title: 'Event 1' }, + { title: 'Event 2' }, + ], + }, + }; + + expect(() => TimelineComponentSchema.parse(timeline)).not.toThrow(); + }); + + it('should accept timeline with full event details', () => { + const timeline = { + type: 'timeline' as const, + props: { + items: [ + { + title: 'Project Started', + timestamp: '2025-01-01T00:00:00Z', + description: 'Project kickoff meeting', + icon: 'play', + }, + { + title: 'Milestone Reached', + timestamp: '2025-02-01T00:00:00Z', + description: 'Completed Phase 1', + icon: 'flag', + }, + ], + orientation: 'vertical' as const, + }, + }; + + expect(() => TimelineComponentSchema.parse(timeline)).not.toThrow(); + }); + + it('should accept timeline with nested content', () => { + const timeline = { + type: 'timeline' as const, + props: { + items: [ + { + title: 'Event with details', + content: { + type: 'card' as const, + props: { title: 'Event Content' }, + }, + }, + ], + }, + }; + + expect(() => TimelineComponentSchema.parse(timeline)).not.toThrow(); + }); + + it('should accept both timeline orientations', () => { + const orientations = ['vertical', 'horizontal'] as const; + + orientations.forEach(orientation => { + const timeline = { + type: 'timeline' as const, + props: { + items: [{ title: 'Event' }], + orientation, + }, + }; + expect(() => TimelineComponentSchema.parse(timeline)).not.toThrow(); + }); + }); +}); + +describe('StepperComponentSchema', () => { + it('should accept minimal stepper', () => { + const stepper = { + type: 'stepper' as const, + props: { + steps: [ + { label: 'Step 1' }, + { label: 'Step 2' }, + ], + }, + }; + + expect(() => StepperComponentSchema.parse(stepper)).not.toThrow(); + }); + + it('should accept stepper with full step details', () => { + const stepper = { + type: 'stepper' as const, + props: { + steps: [ + { + label: 'Account Info', + description: 'Enter your account details', + icon: 'user', + }, + { + label: 'Payment', + description: 'Enter payment information', + icon: 'credit-card', + }, + { + label: 'Confirmation', + description: 'Review and confirm', + icon: 'check', + }, + ], + currentStep: 1, + orientation: 'horizontal' as const, + }, + }; + + expect(() => StepperComponentSchema.parse(stepper)).not.toThrow(); + }); + + it('should accept stepper with step content', () => { + const stepper = { + type: 'stepper' as const, + props: { + steps: [ + { + label: 'Step 1', + content: { + type: 'card' as const, + props: { title: 'Step 1 Content' }, + }, + }, + ], + }, + }; + + expect(() => StepperComponentSchema.parse(stepper)).not.toThrow(); + }); + + it('should accept both stepper orientations', () => { + const orientations = ['horizontal', 'vertical'] as const; + + orientations.forEach(orientation => { + const stepper = { + type: 'stepper' as const, + props: { + steps: [{ label: 'Step 1' }], + orientation, + }, + }; + expect(() => StepperComponentSchema.parse(stepper)).not.toThrow(); + }); + }); +}); + +describe('BreadcrumbComponentSchema', () => { + it('should accept minimal breadcrumb', () => { + const breadcrumb = { + type: 'breadcrumb' as const, + props: { + items: [ + { label: 'Home' }, + { label: 'Projects' }, + ], + }, + }; + + expect(() => BreadcrumbComponentSchema.parse(breadcrumb)).not.toThrow(); + }); + + it('should accept breadcrumb with hrefs', () => { + const breadcrumb = { + type: 'breadcrumb' as const, + props: { + items: [ + { label: 'Home', href: '/' }, + { label: 'Projects', href: '/projects' }, + { label: 'Project 1', href: '/projects/1' }, + ], + }, + }; + + expect(() => BreadcrumbComponentSchema.parse(breadcrumb)).not.toThrow(); + }); + + it('should accept breadcrumb with icons and separator', () => { + const breadcrumb = { + type: 'breadcrumb' as const, + props: { + items: [ + { label: 'Home', href: '/', icon: 'home' }, + { label: 'Settings', href: '/settings', icon: 'settings' }, + ], + separator: '/', + }, + }; + + expect(() => BreadcrumbComponentSchema.parse(breadcrumb)).not.toThrow(); + }); +}); + +describe('AlertComponentSchema', () => { + it('should accept minimal alert', () => { + const alert = { + type: 'alert' as const, + props: { + message: 'This is an alert', + }, + }; + + expect(() => AlertComponentSchema.parse(alert)).not.toThrow(); + }); + + it('should accept all alert variants', () => { + const variants = ['info', 'success', 'warning', 'error'] as const; + + variants.forEach(variant => { + const alert = { + type: 'alert' as const, + props: { + message: 'Test message', + variant, + }, + }; + expect(() => AlertComponentSchema.parse(alert)).not.toThrow(); + }); + }); + + it('should accept alert with all properties', () => { + const alert = { + type: 'alert' as const, + props: { + title: 'Success', + message: 'Your changes have been saved.', + variant: 'success' as const, + dismissible: true, + icon: 'check-circle', + }, + }; + + expect(() => AlertComponentSchema.parse(alert)).not.toThrow(); + }); +}); + +describe('BadgeComponentSchema', () => { + it('should accept minimal badge', () => { + const badge = { + type: 'badge' as const, + props: { + label: 'New', + }, + }; + + expect(() => BadgeComponentSchema.parse(badge)).not.toThrow(); + }); + + it('should accept all badge variants', () => { + const variants = ['primary', 'secondary', 'success', 'warning', 'error', 'info'] as const; + + variants.forEach(variant => { + const badge = { + type: 'badge' as const, + props: { + label: 'Test', + variant, + }, + }; + expect(() => BadgeComponentSchema.parse(badge)).not.toThrow(); + }); + }); + + it('should accept all badge sizes', () => { + const sizes = ['small', 'medium', 'large'] as const; + + sizes.forEach(size => { + const badge = { + type: 'badge' as const, + props: { + label: 'Test', + size, + }, + }; + expect(() => BadgeComponentSchema.parse(badge)).not.toThrow(); + }); + }); + + it('should accept badge with icon', () => { + const badge = { + type: 'badge' as const, + props: { + label: 'Premium', + variant: 'primary' as const, + icon: 'star', + }, + }; + + expect(() => BadgeComponentSchema.parse(badge)).not.toThrow(); + }); +}); + +describe('TooltipComponentSchema', () => { + it('should accept minimal tooltip', () => { + const tooltip = { + type: 'tooltip' as const, + props: { + content: 'Tooltip text', + }, + }; + + expect(() => TooltipComponentSchema.parse(tooltip)).not.toThrow(); + }); + + it('should accept all tooltip positions', () => { + const positions = ['top', 'bottom', 'left', 'right'] as const; + + positions.forEach(position => { + const tooltip = { + type: 'tooltip' as const, + props: { + content: 'Test tooltip', + position, + }, + }; + expect(() => TooltipComponentSchema.parse(tooltip)).not.toThrow(); + }); + }); + + it('should accept tooltip with delay', () => { + const tooltip = { + type: 'tooltip' as const, + props: { + content: 'Delayed tooltip', + delay: 500, + }, + }; + + expect(() => TooltipComponentSchema.parse(tooltip)).not.toThrow(); + }); + + it('should accept tooltip with children', () => { + const tooltip = { + type: 'tooltip' as const, + props: { + content: 'Hover for info', + }, + children: [ + { + type: 'badge' as const, + props: { label: 'Hover me' }, + }, + ], + }; + + expect(() => TooltipComponentSchema.parse(tooltip)).not.toThrow(); + }); +}); + +describe('PopoverComponentSchema', () => { + it('should accept minimal popover', () => { + const popover = { + type: 'popover' as const, + }; + + expect(() => PopoverComponentSchema.parse(popover)).not.toThrow(); + }); + + it('should accept all popover trigger types', () => { + const triggers = ['click', 'hover'] as const; + + triggers.forEach(trigger => { + const popover = { + type: 'popover' as const, + props: { trigger }, + }; + expect(() => PopoverComponentSchema.parse(popover)).not.toThrow(); + }); + }); + + it('should accept all popover positions', () => { + const positions = ['top', 'bottom', 'left', 'right'] as const; + + positions.forEach(position => { + const popover = { + type: 'popover' as const, + props: { position }, + }; + expect(() => PopoverComponentSchema.parse(popover)).not.toThrow(); + }); + }); + + it('should accept popover with all properties', () => { + const popover = { + type: 'popover' as const, + props: { + title: 'More Options', + trigger: 'click' as const, + position: 'bottom' as const, + closeOnOutsideClick: true, + }, + }; + + expect(() => PopoverComponentSchema.parse(popover)).not.toThrow(); + }); + + it('should accept popover with children', () => { + const popover = { + type: 'popover' as const, + props: { title: 'Actions' }, + children: [ + { + type: 'card' as const, + props: { title: 'Popover Content' }, + }, + ], + }; + + expect(() => PopoverComponentSchema.parse(popover)).not.toThrow(); + }); +}); + +describe('Real-World Component Examples', () => { + it('should accept complex nested dashboard layout', () => { + const dashboard = { + type: 'card' as const, + props: { + title: 'Dashboard', + }, + children: [ + { + type: 'tabs' as const, + props: { + tabs: [ + { + label: 'Overview', + icon: 'home', + content: { + type: 'card' as const, + children: [ + { + type: 'alert' as const, + props: { + message: 'Welcome back!', + variant: 'info' as const, + }, + }, + { + type: 'timeline' as const, + props: { + items: [ + { + title: 'Activity 1', + timestamp: '2025-01-22T12:00:00Z', + }, + ], + }, + }, + ], + }, + }, + { + label: 'Settings', + icon: 'settings', + content: { + type: 'accordion' as const, + props: { + items: [ + { + title: 'Profile', + icon: 'user', + }, + ], + }, + }, + }, + ], + }, + }, + ], + }; + + expect(() => ComponentSchema.parse(dashboard)).not.toThrow(); + }); + + it('should accept wizard flow with stepper', () => { + const wizard = { + type: 'modal' as const, + props: { + title: 'Setup Wizard', + size: 'large' as const, + }, + children: [ + { + type: 'stepper' as const, + props: { + steps: [ + { + label: 'Account', + content: { + type: 'card' as const, + props: { title: 'Account Setup' }, + }, + }, + { + label: 'Payment', + content: { + type: 'card' as const, + props: { title: 'Payment Details' }, + }, + }, + ], + currentStep: 0, + }, + }, + ], + }; + + expect(() => ComponentSchema.parse(wizard)).not.toThrow(); + }); + + it('should accept card with badge and tooltip', () => { + const card = { + type: 'card' as const, + props: { + title: 'Premium Feature', + subtitle: 'Unlock advanced capabilities', + }, + children: [ + { + type: 'tooltip' as const, + props: { + content: 'Only for premium users', + }, + children: [ + { + type: 'badge' as const, + props: { + label: 'Premium', + variant: 'primary' as const, + icon: 'star', + }, + }, + ], + }, + ], + }; + + expect(() => ComponentSchema.parse(card)).not.toThrow(); + }); + + it('should accept notification drawer with timeline', () => { + const drawer = { + type: 'drawer' as const, + props: { + title: 'Notifications', + position: 'right' as const, + size: 'medium' as const, + }, + children: [ + { + type: 'timeline' as const, + props: { + items: [ + { + title: 'New message', + timestamp: '2025-01-22T10:30:00Z', + icon: 'mail', + content: { + type: 'card' as const, + props: { title: 'Message details' }, + }, + }, + { + title: 'Task completed', + timestamp: '2025-01-22T09:00:00Z', + icon: 'check', + }, + ], + orientation: 'vertical' as const, + }, + }, + ], + }; + + expect(() => ComponentSchema.parse(drawer)).not.toThrow(); + }); +}); + +describe('Component Factory', () => { + it('should create component via factory', () => { + const component = Component.create({ + type: 'card', + props: { title: 'Test Card' }, + }); + + expect(component.type).toBe('card'); + expect(component.props?.title).toBe('Test Card'); + }); + + it('should validate component via factory', () => { + expect(() => + Component.create({ + type: 'invalid' as any, + }) + ).toThrow(); + + expect(() => + Component.create({ + type: 'card', + }) + ).not.toThrow(); + }); + + it('should create nested component via factory', () => { + const component = Component.create({ + type: 'card', + children: [ + { + type: 'badge', + props: { label: 'New' }, + }, + ], + }); + + expect(component.children).toHaveLength(1); + expect(component.children![0].type).toBe('badge'); + }); +}); diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts new file mode 100644 index 0000000..b52d5ae --- /dev/null +++ b/packages/spec/src/ui/component.zod.ts @@ -0,0 +1,637 @@ +import { z } from 'zod'; + +/** + * Component Type Enum + * + * Defines all reusable UI component types available in the ObjectStack UI system. + * These components can be composed together to build complex user interfaces. + */ +export const ComponentType = z.enum([ + 'card', + 'tabs', + 'accordion', + 'modal', + 'drawer', + 'timeline', + 'stepper', + 'breadcrumb', + 'alert', + 'badge', + 'tooltip', + 'popover', +]); + +/** + * Base Component Schema Definition + * Internal schema object that can be extended for specialized components + */ +const BaseComponentSchema = z.object({ + /** Component type identifier */ + type: ComponentType.describe('Component type'), + + /** Component-specific properties */ + props: z.record(z.any()).optional().describe('Component properties'), + + /** Event handlers */ + events: z.record(z.function()).optional().describe('Event handlers'), + + /** Custom CSS styles */ + style: z.record(z.string()).optional().describe('Custom styles'), +}); + +/** + * Base Component Schema + * + * Defines the structure for reusable UI components with support for: + * - Component nesting via lazy-loaded children + * - Event binding through event handlers + * - Custom styling via style properties + * - Flexible props configuration + * + * @example + * ```typescript + * const card: Component = { + * type: 'card', + * props: { + * title: 'User Profile', + * subtitle: 'Account Details' + * }, + * children: [ + * { + * type: 'badge', + * props: { label: 'Premium', variant: 'success' } + * } + * ], + * style: { + * padding: '16px', + * borderRadius: '8px' + * } + * } + * ``` + */ +export const ComponentSchema: z.ZodType<{ + type: z.infer; + props?: Record; + children?: Array; + events?: Record; + style?: Record; +}> = BaseComponentSchema.extend({ + /** Nested child components */ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Card Component Schema + * + * A container component that displays content in a card layout. + * Commonly used for displaying grouped information with optional header, image, and actions. + * + * @example + * ```typescript + * const card: CardComponent = { + * type: 'card', + * props: { + * title: 'Project Overview', + * subtitle: 'Q4 2025', + * image: 'https://example.com/project.jpg', + * actions: [ + * { label: 'Edit', onClick: () => {} }, + * { label: 'Delete', onClick: () => {} } + * ] + * }, + * children: [ + * { type: 'alert', props: { message: 'Project on track', variant: 'success' } } + * ] + * } + * ``` + */ +export const CardComponentSchema = BaseComponentSchema.extend({ + type: z.literal('card'), + props: z.object({ + /** Card title */ + title: z.string().optional().describe('Card title'), + + /** Card subtitle */ + subtitle: z.string().optional().describe('Card subtitle'), + + /** Header image URL */ + image: z.string().url().optional().describe('Card image URL'), + + /** Action buttons */ + actions: z.array(z.any()).optional().describe('Card action buttons'), + }).optional(), +}).extend({ + /** Nested child components */ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Tabs Component Schema + * + * A tabbed navigation component that allows switching between different content panels. + * + * @example + * ```typescript + * const tabs: TabsComponent = { + * type: 'tabs', + * props: { + * tabs: [ + * { + * label: 'Overview', + * icon: 'home', + * content: { type: 'card', props: { title: 'Overview Content' } } + * }, + * { + * label: 'Settings', + * icon: 'settings', + * content: { type: 'card', props: { title: 'Settings Content' } } + * } + * ], + * defaultTab: 0 + * } + * } + * ``` + */ +export const TabsComponentSchema = BaseComponentSchema.extend({ + type: z.literal('tabs'), + props: z.object({ + /** Tab definitions */ + tabs: z.array(z.object({ + /** Tab label */ + label: z.string().describe('Tab label'), + + /** Tab icon (Lucide icon name) */ + icon: z.string().optional().describe('Tab icon'), + + /** Tab content component */ + content: z.lazy(() => ComponentSchema).optional().describe('Tab content'), + })).describe('Tab items'), + + /** Default active tab index (0-based) */ + defaultTab: z.number().int().min(0).optional().describe('Default active tab index'), + }), +}).extend({ + /** Nested child components */ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Accordion Component Schema + * + * An expandable/collapsible panel component for showing and hiding content. + * + * @example + * ```typescript + * const accordion: AccordionComponent = { + * type: 'accordion', + * props: { + * items: [ + * { + * title: 'Section 1', + * content: { type: 'card', props: { title: 'Content 1' } } + * } + * ], + * allowMultiple: false + * } + * } + * ``` + */ +export const AccordionComponentSchema = BaseComponentSchema.extend({ + type: z.literal('accordion'), + props: z.object({ + /** Accordion items */ + items: z.array(z.object({ + /** Section title */ + title: z.string().describe('Section title'), + + /** Section icon */ + icon: z.string().optional().describe('Section icon'), + + /** Section content */ + content: z.lazy(() => ComponentSchema).optional().describe('Section content'), + + /** Initially expanded state */ + defaultExpanded: z.boolean().optional().describe('Initially expanded'), + })).describe('Accordion items'), + + /** Allow multiple sections to be open simultaneously */ + allowMultiple: z.boolean().optional().describe('Allow multiple open sections'), + }), +}).extend({ + /** Nested child components */ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Modal Component Schema + * + * A dialog overlay component that displays content in a centered popup. + * + * @example + * ```typescript + * const modal: ModalComponent = { + * type: 'modal', + * props: { + * title: 'Confirm Action', + * size: 'medium', + * closeOnOverlay: true + * }, + * children: [ + * { type: 'alert', props: { message: 'Are you sure?' } } + * ] + * } + * ``` + */ +export const ModalComponentSchema = BaseComponentSchema.extend({ + type: z.literal('modal'), + props: z.object({ + /** Modal title */ + title: z.string().optional().describe('Modal title'), + + /** Modal size variant */ + size: z.enum(['small', 'medium', 'large', 'full']).optional().describe('Modal size'), + + /** Close on overlay click */ + closeOnOverlay: z.boolean().optional().describe('Close on overlay click'), + + /** Show close button */ + showClose: z.boolean().optional().describe('Show close button'), + }).optional(), +}).extend({ + /** Nested child components */ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Drawer Component Schema + * + * A slide-in panel component that appears from the edge of the screen. + * + * @example + * ```typescript + * const drawer: DrawerComponent = { + * type: 'drawer', + * props: { + * title: 'Filters', + * position: 'right', + * size: 'medium' + * }, + * children: [ + * { type: 'card', props: { title: 'Filter Options' } } + * ] + * } + * ``` + */ +export const DrawerComponentSchema = BaseComponentSchema.extend({ + type: z.literal('drawer'), + props: z.object({ + /** Drawer title */ + title: z.string().optional().describe('Drawer title'), + + /** Position from which drawer slides in */ + position: z.enum(['left', 'right', 'top', 'bottom']).optional().describe('Drawer position'), + + /** Drawer size */ + size: z.enum(['small', 'medium', 'large', 'full']).optional().describe('Drawer size'), + + /** Close on overlay click */ + closeOnOverlay: z.boolean().optional().describe('Close on overlay click'), + }).optional(), +}).extend({ + /** Nested child components */ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Timeline Component Schema + * + * A chronological display of events or activities. + * + * @example + * ```typescript + * const timeline: TimelineComponent = { + * type: 'timeline', + * props: { + * items: [ + * { + * title: 'Project Started', + * timestamp: '2025-01-01T00:00:00Z', + * description: 'Project kickoff meeting', + * icon: 'play' + * } + * ], + * orientation: 'vertical' + * } + * } + * ``` + */ +export const TimelineComponentSchema = BaseComponentSchema.extend({ + type: z.literal('timeline'), + props: z.object({ + /** Timeline items */ + items: z.array(z.object({ + /** Event title */ + title: z.string().describe('Event title'), + + /** Event timestamp */ + timestamp: z.string().optional().describe('Event timestamp'), + + /** Event description */ + description: z.string().optional().describe('Event description'), + + /** Event icon */ + icon: z.string().optional().describe('Event icon'), + + /** Event content */ + content: z.lazy(() => ComponentSchema).optional().describe('Event content'), + })).describe('Timeline items'), + + /** Timeline orientation */ + orientation: z.enum(['vertical', 'horizontal']).optional().describe('Timeline orientation'), + }), +}).extend({ + /** Nested child components */ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Stepper Component Schema + * + * A step-by-step navigation component for multi-step processes. + * + * @example + * ```typescript + * const stepper: StepperComponent = { + * type: 'stepper', + * props: { + * steps: [ + * { label: 'Account Info', icon: 'user' }, + * { label: 'Payment', icon: 'credit-card' }, + * { label: 'Confirmation', icon: 'check' } + * ], + * currentStep: 0, + * orientation: 'horizontal' + * } + * } + * ``` + */ +export const StepperComponentSchema = BaseComponentSchema.extend({ + type: z.literal('stepper'), + props: z.object({ + /** Step definitions */ + steps: z.array(z.object({ + /** Step label */ + label: z.string().describe('Step label'), + + /** Step description */ + description: z.string().optional().describe('Step description'), + + /** Step icon */ + icon: z.string().optional().describe('Step icon'), + + /** Step content */ + content: z.lazy(() => ComponentSchema).optional().describe('Step content'), + })).describe('Stepper steps'), + + /** Current active step index (0-based) */ + currentStep: z.number().int().min(0).optional().describe('Current step index'), + + /** Stepper orientation */ + orientation: z.enum(['horizontal', 'vertical']).optional().describe('Stepper orientation'), + }), +}).extend({ + /** Nested child components */ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Breadcrumb Component Schema + * + * A navigation component showing the current page's location in the hierarchy. + * + * @example + * ```typescript + * const breadcrumb: BreadcrumbComponent = { + * type: 'breadcrumb', + * props: { + * items: [ + * { label: 'Home', href: '/' }, + * { label: 'Projects', href: '/projects' }, + * { label: 'Project 1', href: '/projects/1' } + * ], + * separator: '/' + * } + * } + * ``` + */ +export const BreadcrumbComponentSchema = BaseComponentSchema.extend({ + type: z.literal('breadcrumb'), + props: z.object({ + /** Breadcrumb items */ + items: z.array(z.object({ + /** Item label */ + label: z.string().describe('Breadcrumb label'), + + /** Item link */ + href: z.string().optional().describe('Breadcrumb link'), + + /** Item icon */ + icon: z.string().optional().describe('Breadcrumb icon'), + })).describe('Breadcrumb items'), + + /** Separator between items */ + separator: z.string().optional().describe('Breadcrumb separator'), + }), +}).extend({ + /** Nested child components */ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Alert Component Schema + * + * A notification component for displaying important messages. + * + * @example + * ```typescript + * const alert: AlertComponent = { + * type: 'alert', + * props: { + * title: 'Success', + * message: 'Your changes have been saved.', + * variant: 'success', + * dismissible: true + * } + * } + * ``` + */ +export const AlertComponentSchema = BaseComponentSchema.extend({ + type: z.literal('alert'), + props: z.object({ + /** Alert title */ + title: z.string().optional().describe('Alert title'), + + /** Alert message */ + message: z.string().describe('Alert message'), + + /** Alert variant/severity */ + variant: z.enum(['info', 'success', 'warning', 'error']).optional().describe('Alert variant'), + + /** Allow dismissing the alert */ + dismissible: z.boolean().optional().describe('Dismissible alert'), + + /** Alert icon */ + icon: z.string().optional().describe('Alert icon'), + }), +}).extend({ + /** Nested child components */ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Badge Component Schema + * + * A small label component for highlighting status or counts. + * + * @example + * ```typescript + * const badge: BadgeComponent = { + * type: 'badge', + * props: { + * label: 'New', + * variant: 'primary', + * icon: 'star' + * } + * } + * ``` + */ +export const BadgeComponentSchema = BaseComponentSchema.extend({ + type: z.literal('badge'), + props: z.object({ + /** Badge label */ + label: z.string().describe('Badge label'), + + /** Badge variant/color scheme */ + variant: z.enum(['primary', 'secondary', 'success', 'warning', 'error', 'info']).optional().describe('Badge variant'), + + /** Badge icon */ + icon: z.string().optional().describe('Badge icon'), + + /** Badge size */ + size: z.enum(['small', 'medium', 'large']).optional().describe('Badge size'), + }), +}).extend({ + /** Nested child components */ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Tooltip Component Schema + * + * A small popup that appears on hover to provide additional context. + * + * @example + * ```typescript + * const tooltip: TooltipComponent = { + * type: 'tooltip', + * props: { + * content: 'Additional information', + * position: 'top' + * }, + * children: [ + * { type: 'badge', props: { label: 'Hover me' } } + * ] + * } + * ``` + */ +export const TooltipComponentSchema = BaseComponentSchema.extend({ + type: z.literal('tooltip'), + props: z.object({ + /** Tooltip content */ + content: z.string().describe('Tooltip content'), + + /** Tooltip position relative to trigger */ + position: z.enum(['top', 'bottom', 'left', 'right']).optional().describe('Tooltip position'), + + /** Delay before showing tooltip (ms) */ + delay: z.number().optional().describe('Show delay in milliseconds'), + }), +}).extend({ + /** Nested child components */ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Popover Component Schema + * + * A popup component that displays rich content on click or hover. + * + * @example + * ```typescript + * const popover: PopoverComponent = { + * type: 'popover', + * props: { + * title: 'More Options', + * trigger: 'click', + * position: 'bottom' + * }, + * children: [ + * { type: 'card', props: { title: 'Popover Content' } } + * ] + * } + * ``` + */ +export const PopoverComponentSchema = BaseComponentSchema.extend({ + type: z.literal('popover'), + props: z.object({ + /** Popover title */ + title: z.string().optional().describe('Popover title'), + + /** Trigger type */ + trigger: z.enum(['click', 'hover']).optional().describe('Trigger type'), + + /** Popover position */ + position: z.enum(['top', 'bottom', 'left', 'right']).optional().describe('Popover position'), + + /** Close on outside click */ + closeOnOutsideClick: z.boolean().optional().describe('Close on outside click'), + }).optional(), +}).extend({ + /** Nested child components */ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * TypeScript Type Exports + */ +export type ComponentTypeEnum = z.infer; +export type Component = z.infer; +export type CardComponent = z.infer; +export type TabsComponent = z.infer; +export type AccordionComponent = z.infer; +export type ModalComponent = z.infer; +export type DrawerComponent = z.infer; +export type TimelineComponent = z.infer; +export type StepperComponent = z.infer; +export type BreadcrumbComponent = z.infer; +export type AlertComponent = z.infer; +export type BadgeComponent = z.infer; +export type TooltipComponent = z.infer; +export type PopoverComponent = z.infer; + +/** + * Component Factory Helper + * + * Provides a convenient way to create validated component instances. + * + * @example + * ```typescript + * const card = Component.create({ + * type: 'card', + * props: { title: 'Hello World' } + * }); + * ``` + */ +export const Component = { + create: (config: z.input): Component => ComponentSchema.parse(config), +} as const; diff --git a/packages/spec/src/ui/index.ts b/packages/spec/src/ui/index.ts index 446da59..535d4ec 100644 --- a/packages/spec/src/ui/index.ts +++ b/packages/spec/src/ui/index.ts @@ -5,6 +5,7 @@ * - App, Page, View (Grid/Kanban) * - Dashboard (Widgets), Report * - Action (Triggers) + * - Component (Reusable UI Components) */ export * from './app.zod'; @@ -15,3 +16,4 @@ export * from './action.zod'; export * from './page.zod'; export * from './widget.zod'; export * from './theme.zod'; +export * from './component.zod'; From 9334715f3b29e5e94f8f3bfe42aaaee61d88d2a4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 13:54:55 +0000 Subject: [PATCH 3/6] Add comprehensive component library documentation with rendering examples Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- packages/spec/src/ui/COMPONENT_GUIDE.md | 742 ++++++++++++++++++++++++ 1 file changed, 742 insertions(+) create mode 100644 packages/spec/src/ui/COMPONENT_GUIDE.md diff --git a/packages/spec/src/ui/COMPONENT_GUIDE.md b/packages/spec/src/ui/COMPONENT_GUIDE.md new file mode 100644 index 0000000..9fe7b91 --- /dev/null +++ b/packages/spec/src/ui/COMPONENT_GUIDE.md @@ -0,0 +1,742 @@ +# Component Library Protocol + +The Component Library Protocol defines reusable UI components for the ObjectStack UI system. These components can be composed together to build complex user interfaces with support for nesting, event binding, and custom styling. + +## Overview + +All components follow a consistent schema pattern: + +```typescript +{ + type: ComponentType, // Component type identifier + props?: Record, // Component-specific properties + children?: Component[], // Nested child components (supports recursion) + events?: Record, // Event handlers + style?: Record // Custom CSS styles +} +``` + +## Component Types + +The following component types are available: + +- **Layout Components**: `card`, `tabs`, `accordion`, `modal`, `drawer` +- **Navigation Components**: `breadcrumb`, `stepper` +- **Display Components**: `timeline`, `alert`, `badge` +- **Overlay Components**: `tooltip`, `popover` + +## Usage Examples + +### Card Component + +A container component that displays content in a card layout: + +```typescript +const projectCard: CardComponent = { + type: 'card', + props: { + title: 'Project Overview', + subtitle: 'Q4 2025 Planning', + image: 'https://example.com/project-thumbnail.jpg', + actions: [ + { label: 'Edit', onClick: () => handleEdit() }, + { label: 'Share', onClick: () => handleShare() } + ] + }, + children: [ + { + type: 'alert', + props: { + message: 'Project is on track', + variant: 'success' + } + }, + { + type: 'badge', + props: { + label: 'High Priority', + variant: 'error', + icon: 'alert-triangle' + } + } + ], + style: { + padding: '24px', + borderRadius: '12px', + boxShadow: '0 2px 8px rgba(0,0,0,0.1)' + } +} +``` + +### Tabs Component + +A tabbed navigation component for switching between content panels: + +```typescript +const dashboardTabs: TabsComponent = { + type: 'tabs', + props: { + tabs: [ + { + label: 'Overview', + icon: 'home', + content: { + type: 'card', + props: { title: 'Dashboard Overview' }, + children: [ + { + type: 'timeline', + props: { + items: [ + { + title: 'Project Kickoff', + timestamp: '2025-01-15T09:00:00Z', + description: 'Project started with team meeting', + icon: 'play' + }, + { + title: 'Milestone 1 Complete', + timestamp: '2025-02-01T15:30:00Z', + description: 'Successfully completed first phase', + icon: 'check-circle' + } + ], + orientation: 'vertical' + } + } + ] + } + }, + { + label: 'Analytics', + icon: 'bar-chart', + content: { + type: 'card', + props: { title: 'Performance Metrics' } + } + }, + { + label: 'Settings', + icon: 'settings', + content: { + type: 'card', + props: { title: 'Configuration' } + } + } + ], + defaultTab: 0 + } +} +``` + +### Accordion Component + +An expandable/collapsible panel component: + +```typescript +const faqAccordion: AccordionComponent = { + type: 'accordion', + props: { + items: [ + { + title: 'Getting Started', + icon: 'help-circle', + defaultExpanded: true, + content: { + type: 'card', + props: { + title: 'How to begin', + subtitle: 'Follow these steps to get started' + } + } + }, + { + title: 'Advanced Features', + icon: 'star', + content: { + type: 'card', + props: { title: 'Power user guide' } + } + } + ], + allowMultiple: false + } +} +``` + +### Modal Component + +A dialog overlay for focused user interactions: + +```typescript +const confirmationModal: ModalComponent = { + type: 'modal', + props: { + title: 'Confirm Deletion', + size: 'medium', + closeOnOverlay: false, + showClose: true + }, + children: [ + { + type: 'alert', + props: { + title: 'Warning', + message: 'This action cannot be undone. Are you sure you want to delete this item?', + variant: 'warning', + icon: 'alert-triangle' + } + } + ], + events: { + onConfirm: () => handleDelete(), + onCancel: () => closeModal() + } +} +``` + +### Drawer Component + +A slide-in panel from the screen edge: + +```typescript +const filtersDrawer: DrawerComponent = { + type: 'drawer', + props: { + title: 'Advanced Filters', + position: 'right', + size: 'medium', + closeOnOverlay: true + }, + children: [ + { + type: 'accordion', + props: { + items: [ + { + title: 'Date Range', + defaultExpanded: true, + content: { + type: 'card', + props: { title: 'Select date range' } + } + }, + { + title: 'Categories', + content: { + type: 'card', + props: { title: 'Filter by category' } + } + } + ], + allowMultiple: true + } + } + ] +} +``` + +### Stepper Component + +Multi-step process navigation: + +```typescript +const wizardStepper: StepperComponent = { + type: 'stepper', + props: { + steps: [ + { + label: 'Account Information', + description: 'Enter your account details', + icon: 'user', + content: { + type: 'card', + props: { title: 'Account Setup' } + } + }, + { + label: 'Payment Method', + description: 'Configure payment details', + icon: 'credit-card', + content: { + type: 'card', + props: { title: 'Payment Information' } + } + }, + { + label: 'Confirmation', + description: 'Review and confirm', + icon: 'check-circle', + content: { + type: 'card', + props: { title: 'Review Your Information' } + } + } + ], + currentStep: 0, + orientation: 'horizontal' + }, + events: { + onStepChange: (step: number) => handleStepChange(step) + } +} +``` + +### Alert Component + +Notification messages with different severity levels: + +```typescript +const successAlert: AlertComponent = { + type: 'alert', + props: { + title: 'Success!', + message: 'Your changes have been saved successfully.', + variant: 'success', + dismissible: true, + icon: 'check-circle' + }, + events: { + onDismiss: () => handleDismiss() + } +} + +const errorAlert: AlertComponent = { + type: 'alert', + props: { + title: 'Error', + message: 'Failed to save changes. Please try again.', + variant: 'error', + dismissible: true, + icon: 'x-circle' + } +} +``` + +### Badge Component + +Small labels for status or counts: + +```typescript +const premiumBadge: BadgeComponent = { + type: 'badge', + props: { + label: 'Premium', + variant: 'primary', + icon: 'star', + size: 'medium' + } +} + +const countBadge: BadgeComponent = { + type: 'badge', + props: { + label: '99+', + variant: 'error', + size: 'small' + } +} +``` + +### Timeline Component + +Chronological event display: + +```typescript +const activityTimeline: TimelineComponent = { + type: 'timeline', + props: { + items: [ + { + title: 'Task Completed', + timestamp: '2025-01-22T10:30:00Z', + description: 'John Doe completed the design review', + icon: 'check-circle', + content: { + type: 'card', + props: { + title: 'Design Review Results', + subtitle: 'All checks passed' + } + } + }, + { + title: 'Comment Added', + timestamp: '2025-01-22T09:15:00Z', + description: 'Jane Smith left a comment', + icon: 'message-circle' + }, + { + title: 'Project Created', + timestamp: '2025-01-20T14:00:00Z', + description: 'Project was initialized', + icon: 'play' + } + ], + orientation: 'vertical' + } +} +``` + +### Breadcrumb Component + +Hierarchical navigation: + +```typescript +const navBreadcrumb: BreadcrumbComponent = { + type: 'breadcrumb', + props: { + items: [ + { label: 'Home', href: '/', icon: 'home' }, + { label: 'Projects', href: '/projects', icon: 'folder' }, + { label: 'Design System', href: '/projects/design-system', icon: 'palette' }, + { label: 'Components', href: '/projects/design-system/components' } + ], + separator: '/' + } +} +``` + +### Tooltip Component + +Contextual information on hover: + +```typescript +const infoTooltip: TooltipComponent = { + type: 'tooltip', + props: { + content: 'This feature is only available for premium users', + position: 'top', + delay: 300 + }, + children: [ + { + type: 'badge', + props: { + label: 'Premium Feature', + variant: 'primary', + icon: 'lock' + } + } + ] +} +``` + +### Popover Component + +Rich content popup on click or hover: + +```typescript +const actionsPopover: PopoverComponent = { + type: 'popover', + props: { + title: 'Quick Actions', + trigger: 'click', + position: 'bottom', + closeOnOutsideClick: true + }, + children: [ + { + type: 'card', + children: [ + { + type: 'alert', + props: { + message: 'Select an action', + variant: 'info' + } + } + ] + } + ] +} +``` + +## Complex Composition Example + +Here's a complex example showing how components can be deeply nested: + +```typescript +const complexDashboard: Component = { + type: 'card', + props: { + title: 'Project Management Dashboard', + subtitle: 'Q4 2025' + }, + children: [ + { + type: 'tabs', + props: { + tabs: [ + { + label: 'Overview', + icon: 'home', + content: { + type: 'card', + children: [ + { + type: 'alert', + props: { + message: 'All systems operational', + variant: 'success' + } + }, + { + type: 'timeline', + props: { + items: [ + { + title: 'Milestone Reached', + timestamp: '2025-01-22T12:00:00Z', + icon: 'flag', + content: { + type: 'card', + props: { title: 'Phase 1 Complete' }, + children: [ + { + type: 'badge', + props: { + label: 'Completed', + variant: 'success' + } + } + ] + } + } + ] + } + } + ] + } + }, + { + label: 'Team', + icon: 'users', + content: { + type: 'accordion', + props: { + items: [ + { + title: 'Active Members', + icon: 'users', + defaultExpanded: true + } + ] + } + } + } + ] + } + } + ] +} +``` + +## Event Binding + +Components support event binding through the `events` property: + +```typescript +const interactiveCard: Component = { + type: 'card', + props: { + title: 'Interactive Component' + }, + events: { + onClick: () => console.log('Card clicked'), + onHover: () => console.log('Card hovered'), + onFocus: () => console.log('Card focused'), + onBlur: () => console.log('Card blurred') + } +} +``` + +## Custom Styling + +All components accept custom CSS styles: + +```typescript +const styledComponent: Component = { + type: 'card', + props: { + title: 'Styled Component' + }, + style: { + backgroundColor: '#f5f5f5', + borderRadius: '12px', + padding: '24px', + boxShadow: '0 4px 12px rgba(0,0,0,0.1)', + border: '1px solid #e0e0e0' + } +} +``` + +## Best Practices + +### 1. Use Semantic Component Types +Choose the most appropriate component type for your use case: +- Use `card` for grouped content containers +- Use `modal` for focused interactions that require user attention +- Use `drawer` for contextual panels and filters +- Use `alert` for important notifications + +### 2. Leverage Component Nesting +Build complex UIs by composing simple components: + +```typescript +// Good: Clear hierarchy +{ + type: 'card', + children: [ + { type: 'alert', props: { message: 'Info' } }, + { type: 'badge', props: { label: 'Status' } } + ] +} +``` + +### 3. Keep Props Focused +Each component should have props specific to its purpose: + +```typescript +// Good: Focused props +const alert = { + type: 'alert', + props: { + title: 'Error', + message: 'Something went wrong', + variant: 'error' + } +} +``` + +### 4. Use Event Handlers Consistently +Bind events using standard naming conventions: + +```typescript +events: { + onClick: () => {}, + onSubmit: () => {}, + onCancel: () => {}, + onDismiss: () => {} +} +``` + +## TypeScript Support + +All components are fully typed using Zod schemas with TypeScript type inference: + +```typescript +import { + Component, + CardComponent, + TabsComponent, + AlertComponent, + // ... other types +} from '@objectstack/spec/ui'; + +// Type-safe component definition +const myCard: CardComponent = { + type: 'card', + props: { + title: 'Hello World' + } +}; + +// Generic component type +const myComponent: Component = { + type: 'badge', + props: { + label: 'New' + } +}; +``` + +## Factory Helper + +Use the Component factory for validated component creation: + +```typescript +import { Component } from '@objectstack/spec/ui'; + +// Creates and validates component +const card = Component.create({ + type: 'card', + props: { title: 'Test' } +}); + +// Throws error for invalid component +const invalid = Component.create({ + type: 'invalid' // Error: Invalid component type +}); +``` + +## Validation + +All component schemas include runtime validation through Zod: + +```typescript +import { ComponentSchema, CardComponentSchema } from '@objectstack/spec/ui'; + +// Validates any component +ComponentSchema.parse(myComponent); + +// Validates specific component type +CardComponentSchema.parse(myCard); +``` + +## Migration Guide + +If migrating from other UI frameworks: + +### From React Components +```typescript +// Before (React) + + + + +// After (ObjectStack) +{ + type: 'card', + props: { title: 'Hello' }, + children: [ + { + type: 'alert', + props: { message: 'Info', variant: 'info' } + } + ] +} +``` + +### From Vue Components +```typescript +// Before (Vue) + + Info + + +// After (ObjectStack) +{ + type: 'card', + props: { title: 'Hello' }, + children: [ + { + type: 'alert', + props: { message: 'Info', variant: 'info' } + } + ] +} +``` + +## Next Steps + +- Explore the [UI Protocol Documentation](../README.md) +- Check out [Page Composition](./page.zod.ts) for building full pages +- Learn about [Theme Customization](./theme.zod.ts) for styling + +## Related Schemas + +- `ActionSchema` - User interaction definitions +- `PageSchema` - Full page composition +- `ThemeSchema` - Visual styling and theming +- `ViewSchema` - Data-driven views (list, form) From 53090e5325dd45384f4531e70ae7df26e68adfed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 14:59:29 +0000 Subject: [PATCH 4/6] Expand component library to 79 enterprise component types with detailed schemas Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- .../docs/references/ui/ButtonComponent.mdx | 14 + content/docs/references/ui/Component.mdx | 2 +- content/docs/references/ui/ComponentType.mdx | 61 ++- content/docs/references/ui/FormComponent.mdx | 14 + content/docs/references/ui/InputComponent.mdx | 14 + content/docs/references/ui/ListComponent.mdx | 14 + content/docs/references/ui/MenuComponent.mdx | 14 + .../references/ui/PaginationComponent.mdx | 14 + .../docs/references/ui/ProgressComponent.mdx | 14 + .../docs/references/ui/SelectComponent.mdx | 14 + content/docs/references/ui/TableComponent.mdx | 14 + content/docs/references/ui/TreeComponent.mdx | 14 + .../docs/references/ui/UploadComponent.mdx | 14 + .../spec/json-schema/AccordionComponent.json | 122 ++++- packages/spec/json-schema/AlertComponent.json | 61 ++- packages/spec/json-schema/BadgeComponent.json | 61 ++- .../spec/json-schema/BreadcrumbComponent.json | 61 ++- .../spec/json-schema/ButtonComponent.json | 210 +++++++++ packages/spec/json-schema/CardComponent.json | 61 ++- packages/spec/json-schema/Component.json | 61 ++- packages/spec/json-schema/ComponentType.json | 61 ++- .../spec/json-schema/DrawerComponent.json | 61 ++- packages/spec/json-schema/FormComponent.json | 255 +++++++++++ packages/spec/json-schema/InputComponent.json | 206 +++++++++ packages/spec/json-schema/ListComponent.json | 194 ++++++++ packages/spec/json-schema/MenuComponent.json | 224 ++++++++++ packages/spec/json-schema/ModalComponent.json | 61 ++- .../spec/json-schema/PaginationComponent.json | 193 ++++++++ .../spec/json-schema/PopoverComponent.json | 61 ++- .../spec/json-schema/ProgressComponent.json | 184 ++++++++ .../spec/json-schema/SelectComponent.json | 211 +++++++++ .../spec/json-schema/StepperComponent.json | 122 ++++- packages/spec/json-schema/TableComponent.json | 274 ++++++++++++ packages/spec/json-schema/TabsComponent.json | 122 ++++- .../spec/json-schema/TimelineComponent.json | 122 ++++- .../spec/json-schema/TooltipComponent.json | 61 ++- packages/spec/json-schema/TreeComponent.json | 218 +++++++++ .../spec/json-schema/UploadComponent.json | 180 ++++++++ packages/spec/src/ui/component.test.ts | 303 ++++++++++++- packages/spec/src/ui/component.zod.ts | 415 +++++++++++++++++- 40 files changed, 4300 insertions(+), 82 deletions(-) create mode 100644 content/docs/references/ui/ButtonComponent.mdx create mode 100644 content/docs/references/ui/FormComponent.mdx create mode 100644 content/docs/references/ui/InputComponent.mdx create mode 100644 content/docs/references/ui/ListComponent.mdx create mode 100644 content/docs/references/ui/MenuComponent.mdx create mode 100644 content/docs/references/ui/PaginationComponent.mdx create mode 100644 content/docs/references/ui/ProgressComponent.mdx create mode 100644 content/docs/references/ui/SelectComponent.mdx create mode 100644 content/docs/references/ui/TableComponent.mdx create mode 100644 content/docs/references/ui/TreeComponent.mdx create mode 100644 content/docs/references/ui/UploadComponent.mdx create mode 100644 packages/spec/json-schema/ButtonComponent.json create mode 100644 packages/spec/json-schema/FormComponent.json create mode 100644 packages/spec/json-schema/InputComponent.json create mode 100644 packages/spec/json-schema/ListComponent.json create mode 100644 packages/spec/json-schema/MenuComponent.json create mode 100644 packages/spec/json-schema/PaginationComponent.json create mode 100644 packages/spec/json-schema/ProgressComponent.json create mode 100644 packages/spec/json-schema/SelectComponent.json create mode 100644 packages/spec/json-schema/TableComponent.json create mode 100644 packages/spec/json-schema/TreeComponent.json create mode 100644 packages/spec/json-schema/UploadComponent.json diff --git a/content/docs/references/ui/ButtonComponent.mdx b/content/docs/references/ui/ButtonComponent.mdx new file mode 100644 index 0000000..8e1d265 --- /dev/null +++ b/content/docs/references/ui/ButtonComponent.mdx @@ -0,0 +1,14 @@ +--- +title: ButtonComponent +description: ButtonComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/Component.mdx b/content/docs/references/ui/Component.mdx index b83d357..8ccca56 100644 --- a/content/docs/references/ui/Component.mdx +++ b/content/docs/references/ui/Component.mdx @@ -7,7 +7,7 @@ description: Component Schema Reference | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **type** | `Enum<'card' \| 'tabs' \| 'accordion' \| 'modal' \| 'drawer' \| 'timeline' \| 'stepper' \| 'breadcrumb' \| 'alert' \| 'badge' \| 'tooltip' \| 'popover'>` | ✅ | Component type | +| **type** | `Enum<'card' \| 'tabs' \| 'accordion' \| 'modal' \| 'drawer' \| 'container' \| 'divider' \| 'space' \| 'grid' \| 'flex' \| 'breadcrumb' \| 'stepper' \| 'menu' \| 'sidebar' \| 'pagination' \| 'dropdown' \| 'table' \| 'list' \| 'tree' \| 'description' \| 'statistic' \| 'tag' \| 'collapse' \| 'carousel' \| 'image' \| 'avatar' \| 'calendar_view' \| 'form' \| 'input' \| 'select' \| 'checkbox' \| 'radio' \| 'switch' \| 'slider' \| 'date_picker' \| 'time_picker' \| 'upload' \| 'autocomplete' \| 'cascader' \| 'transfer' \| 'color_picker' \| 'rate' \| 'alert' \| 'message' \| 'notification' \| 'progress' \| 'skeleton' \| 'spin' \| 'result' \| 'empty' \| 'button' \| 'button_group' \| 'icon_button' \| 'split_button' \| 'tooltip' \| 'popover' \| 'dialog' \| 'confirm' \| 'badge' \| 'timeline' \| 'steps' \| 'anchor' \| 'back_top' \| 'watermark' \| 'qrcode'>` | ✅ | Component type | | **props** | `Record` | optional | Component properties | | **events** | `Record` | optional | Event handlers | | **style** | `Record` | optional | Custom styles | diff --git a/content/docs/references/ui/ComponentType.mdx b/content/docs/references/ui/ComponentType.mdx index c0783d2..5a9d83a 100644 --- a/content/docs/references/ui/ComponentType.mdx +++ b/content/docs/references/ui/ComponentType.mdx @@ -10,10 +10,63 @@ description: ComponentType Schema Reference * `accordion` * `modal` * `drawer` -* `timeline` -* `stepper` +* `container` +* `divider` +* `space` +* `grid` +* `flex` * `breadcrumb` +* `stepper` +* `menu` +* `sidebar` +* `pagination` +* `dropdown` +* `table` +* `list` +* `tree` +* `description` +* `statistic` +* `tag` +* `collapse` +* `carousel` +* `image` +* `avatar` +* `calendar_view` +* `form` +* `input` +* `select` +* `checkbox` +* `radio` +* `switch` +* `slider` +* `date_picker` +* `time_picker` +* `upload` +* `autocomplete` +* `cascader` +* `transfer` +* `color_picker` +* `rate` * `alert` -* `badge` +* `message` +* `notification` +* `progress` +* `skeleton` +* `spin` +* `result` +* `empty` +* `button` +* `button_group` +* `icon_button` +* `split_button` * `tooltip` -* `popover` \ No newline at end of file +* `popover` +* `dialog` +* `confirm` +* `badge` +* `timeline` +* `steps` +* `anchor` +* `back_top` +* `watermark` +* `qrcode` \ No newline at end of file diff --git a/content/docs/references/ui/FormComponent.mdx b/content/docs/references/ui/FormComponent.mdx new file mode 100644 index 0000000..d5f10a1 --- /dev/null +++ b/content/docs/references/ui/FormComponent.mdx @@ -0,0 +1,14 @@ +--- +title: FormComponent +description: FormComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | optional | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/InputComponent.mdx b/content/docs/references/ui/InputComponent.mdx new file mode 100644 index 0000000..8b7e742 --- /dev/null +++ b/content/docs/references/ui/InputComponent.mdx @@ -0,0 +1,14 @@ +--- +title: InputComponent +description: InputComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | optional | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/ListComponent.mdx b/content/docs/references/ui/ListComponent.mdx new file mode 100644 index 0000000..0243c00 --- /dev/null +++ b/content/docs/references/ui/ListComponent.mdx @@ -0,0 +1,14 @@ +--- +title: ListComponent +description: ListComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | optional | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/MenuComponent.mdx b/content/docs/references/ui/MenuComponent.mdx new file mode 100644 index 0000000..751b884 --- /dev/null +++ b/content/docs/references/ui/MenuComponent.mdx @@ -0,0 +1,14 @@ +--- +title: MenuComponent +description: MenuComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/PaginationComponent.mdx b/content/docs/references/ui/PaginationComponent.mdx new file mode 100644 index 0000000..255091e --- /dev/null +++ b/content/docs/references/ui/PaginationComponent.mdx @@ -0,0 +1,14 @@ +--- +title: PaginationComponent +description: PaginationComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/ProgressComponent.mdx b/content/docs/references/ui/ProgressComponent.mdx new file mode 100644 index 0000000..af490b9 --- /dev/null +++ b/content/docs/references/ui/ProgressComponent.mdx @@ -0,0 +1,14 @@ +--- +title: ProgressComponent +description: ProgressComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/SelectComponent.mdx b/content/docs/references/ui/SelectComponent.mdx new file mode 100644 index 0000000..73b14a3 --- /dev/null +++ b/content/docs/references/ui/SelectComponent.mdx @@ -0,0 +1,14 @@ +--- +title: SelectComponent +description: SelectComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/TableComponent.mdx b/content/docs/references/ui/TableComponent.mdx new file mode 100644 index 0000000..c94288d --- /dev/null +++ b/content/docs/references/ui/TableComponent.mdx @@ -0,0 +1,14 @@ +--- +title: TableComponent +description: TableComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/TreeComponent.mdx b/content/docs/references/ui/TreeComponent.mdx new file mode 100644 index 0000000..386e582 --- /dev/null +++ b/content/docs/references/ui/TreeComponent.mdx @@ -0,0 +1,14 @@ +--- +title: TreeComponent +description: TreeComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | optional | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/UploadComponent.mdx b/content/docs/references/ui/UploadComponent.mdx new file mode 100644 index 0000000..07025f8 --- /dev/null +++ b/content/docs/references/ui/UploadComponent.mdx @@ -0,0 +1,14 @@ +--- +title: UploadComponent +description: UploadComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | optional | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/packages/spec/json-schema/AccordionComponent.json b/packages/spec/json-schema/AccordionComponent.json index 574cf19..ef9aba6 100644 --- a/packages/spec/json-schema/AccordionComponent.json +++ b/packages/spec/json-schema/AccordionComponent.json @@ -35,13 +35,66 @@ "accordion", "modal", "drawer", - "timeline", - "stepper", + "container", + "divider", + "space", + "grid", + "flex", "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", "alert", - "badge", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", "tooltip", - "popover" + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" ], "description": "Component type" }, @@ -121,13 +174,66 @@ "accordion", "modal", "drawer", - "timeline", - "stepper", + "container", + "divider", + "space", + "grid", + "flex", "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", "alert", - "badge", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", "tooltip", - "popover" + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" ], "description": "Component type" }, diff --git a/packages/spec/json-schema/AlertComponent.json b/packages/spec/json-schema/AlertComponent.json index 81ba845..ab8f093 100644 --- a/packages/spec/json-schema/AlertComponent.json +++ b/packages/spec/json-schema/AlertComponent.json @@ -68,13 +68,66 @@ "accordion", "modal", "drawer", - "timeline", - "stepper", + "container", + "divider", + "space", + "grid", + "flex", "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", "alert", - "badge", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", "tooltip", - "popover" + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" ], "description": "Component type" }, diff --git a/packages/spec/json-schema/BadgeComponent.json b/packages/spec/json-schema/BadgeComponent.json index 4d18819..9635c88 100644 --- a/packages/spec/json-schema/BadgeComponent.json +++ b/packages/spec/json-schema/BadgeComponent.json @@ -71,13 +71,66 @@ "accordion", "modal", "drawer", - "timeline", - "stepper", + "container", + "divider", + "space", + "grid", + "flex", "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", "alert", - "badge", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", "tooltip", - "popover" + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" ], "description": "Component type" }, diff --git a/packages/spec/json-schema/BreadcrumbComponent.json b/packages/spec/json-schema/BreadcrumbComponent.json index 5d2dbcb..3c53954 100644 --- a/packages/spec/json-schema/BreadcrumbComponent.json +++ b/packages/spec/json-schema/BreadcrumbComponent.json @@ -71,13 +71,66 @@ "accordion", "modal", "drawer", - "timeline", - "stepper", + "container", + "divider", + "space", + "grid", + "flex", "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", "alert", - "badge", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", "tooltip", - "popover" + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" ], "description": "Component type" }, diff --git a/packages/spec/json-schema/ButtonComponent.json b/packages/spec/json-schema/ButtonComponent.json new file mode 100644 index 0000000..b6d06e4 --- /dev/null +++ b/packages/spec/json-schema/ButtonComponent.json @@ -0,0 +1,210 @@ +{ + "$ref": "#/definitions/ButtonComponent", + "definitions": { + "ButtonComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "button" + }, + "props": { + "type": "object", + "properties": { + "label": { + "type": "string", + "description": "Button label" + }, + "variant": { + "type": "string", + "enum": [ + "primary", + "secondary", + "success", + "warning", + "danger", + "text", + "link" + ], + "description": "Button variant" + }, + "icon": { + "type": "string", + "description": "Button icon" + }, + "iconPosition": { + "type": "string", + "enum": [ + "left", + "right" + ], + "description": "Icon position" + }, + "size": { + "type": "string", + "enum": [ + "small", + "medium", + "large" + ], + "description": "Button size" + }, + "loading": { + "type": "boolean", + "description": "Loading state" + }, + "disabled": { + "type": "boolean", + "description": "Disabled state" + }, + "block": { + "type": "boolean", + "description": "Block button (full width)" + }, + "danger": { + "type": "boolean", + "description": "Danger button" + }, + "shape": { + "type": "string", + "enum": [ + "default", + "circle", + "round" + ], + "description": "Button shape" + } + }, + "required": [ + "label" + ], + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "container", + "divider", + "space", + "grid", + "flex", + "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", + "alert", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", + "tooltip", + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type", + "props" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/CardComponent.json b/packages/spec/json-schema/CardComponent.json index f638987..ddd30ab 100644 --- a/packages/spec/json-schema/CardComponent.json +++ b/packages/spec/json-schema/CardComponent.json @@ -56,13 +56,66 @@ "accordion", "modal", "drawer", - "timeline", - "stepper", + "container", + "divider", + "space", + "grid", + "flex", "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", "alert", - "badge", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", "tooltip", - "popover" + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" ], "description": "Component type" }, diff --git a/packages/spec/json-schema/Component.json b/packages/spec/json-schema/Component.json index 3de372d..07c2dcf 100644 --- a/packages/spec/json-schema/Component.json +++ b/packages/spec/json-schema/Component.json @@ -12,13 +12,66 @@ "accordion", "modal", "drawer", - "timeline", - "stepper", + "container", + "divider", + "space", + "grid", + "flex", "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", "alert", - "badge", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", "tooltip", - "popover" + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" ], "description": "Component type" }, diff --git a/packages/spec/json-schema/ComponentType.json b/packages/spec/json-schema/ComponentType.json index 77d21f8..c33dc68 100644 --- a/packages/spec/json-schema/ComponentType.json +++ b/packages/spec/json-schema/ComponentType.json @@ -9,13 +9,66 @@ "accordion", "modal", "drawer", - "timeline", - "stepper", + "container", + "divider", + "space", + "grid", + "flex", "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", "alert", - "badge", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", "tooltip", - "popover" + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" ] } }, diff --git a/packages/spec/json-schema/DrawerComponent.json b/packages/spec/json-schema/DrawerComponent.json index 36a050d..431a29a 100644 --- a/packages/spec/json-schema/DrawerComponent.json +++ b/packages/spec/json-schema/DrawerComponent.json @@ -67,13 +67,66 @@ "accordion", "modal", "drawer", - "timeline", - "stepper", + "container", + "divider", + "space", + "grid", + "flex", "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", "alert", - "badge", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", "tooltip", - "popover" + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" ], "description": "Component type" }, diff --git a/packages/spec/json-schema/FormComponent.json b/packages/spec/json-schema/FormComponent.json new file mode 100644 index 0000000..135074d --- /dev/null +++ b/packages/spec/json-schema/FormComponent.json @@ -0,0 +1,255 @@ +{ + "$ref": "#/definitions/FormComponent", + "definitions": { + "FormComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "form" + }, + "props": { + "type": "object", + "properties": { + "layout": { + "type": "string", + "enum": [ + "horizontal", + "vertical", + "inline" + ], + "description": "Form layout" + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Field name" + }, + "label": { + "type": "string", + "description": "Field label" + }, + "type": { + "type": "string", + "description": "Field type" + }, + "required": { + "type": "boolean", + "description": "Required field" + }, + "placeholder": { + "type": "string", + "description": "Placeholder text" + }, + "defaultValue": { + "description": "Default value" + }, + "validation": { + "type": "object", + "additionalProperties": {}, + "description": "Validation rules" + } + }, + "required": [ + "name", + "label", + "type" + ], + "additionalProperties": false + }, + "description": "Form fields" + }, + "submitButton": { + "type": "object", + "properties": { + "label": { + "type": "string", + "description": "Button label" + }, + "variant": { + "type": "string", + "enum": [ + "primary", + "secondary", + "success", + "danger" + ], + "description": "Button variant" + } + }, + "required": [ + "label" + ], + "additionalProperties": false, + "description": "Submit button configuration" + }, + "cancelButton": { + "type": "object", + "properties": { + "label": { + "type": "string", + "description": "Button label" + }, + "variant": { + "type": "string", + "enum": [ + "primary", + "secondary", + "success", + "danger" + ], + "description": "Button variant" + } + }, + "required": [ + "label" + ], + "additionalProperties": false, + "description": "Cancel button configuration" + }, + "labelWidth": { + "type": "number", + "description": "Label width" + }, + "labelAlign": { + "type": "string", + "enum": [ + "left", + "right" + ], + "description": "Label alignment" + } + }, + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "container", + "divider", + "space", + "grid", + "flex", + "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", + "alert", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", + "tooltip", + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/InputComponent.json b/packages/spec/json-schema/InputComponent.json new file mode 100644 index 0000000..2ce684d --- /dev/null +++ b/packages/spec/json-schema/InputComponent.json @@ -0,0 +1,206 @@ +{ + "$ref": "#/definitions/InputComponent", + "definitions": { + "InputComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "input" + }, + "props": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "text", + "password", + "email", + "number", + "tel", + "url", + "search", + "textarea" + ], + "description": "Input type" + }, + "placeholder": { + "type": "string", + "description": "Placeholder text" + }, + "defaultValue": { + "type": "string", + "description": "Default value" + }, + "size": { + "type": "string", + "enum": [ + "small", + "medium", + "large" + ], + "description": "Input size" + }, + "disabled": { + "type": "boolean", + "description": "Disabled state" + }, + "readonly": { + "type": "boolean", + "description": "Read-only state" + }, + "maxLength": { + "type": "number", + "description": "Maximum length" + }, + "showCount": { + "type": "boolean", + "description": "Show character count" + }, + "prefix": { + "type": "string", + "description": "Prefix icon" + }, + "suffix": { + "type": "string", + "description": "Suffix icon" + }, + "allowClear": { + "type": "boolean", + "description": "Allow clear button" + }, + "rows": { + "type": "number", + "description": "Rows for textarea type" + } + }, + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "container", + "divider", + "space", + "grid", + "flex", + "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", + "alert", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", + "tooltip", + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ListComponent.json b/packages/spec/json-schema/ListComponent.json new file mode 100644 index 0000000..5ed8f05 --- /dev/null +++ b/packages/spec/json-schema/ListComponent.json @@ -0,0 +1,194 @@ +{ + "$ref": "#/definitions/ListComponent", + "definitions": { + "ListComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "list" + }, + "props": { + "type": "object", + "properties": { + "dataSource": { + "type": "string", + "description": "Data source reference" + }, + "itemLayout": { + "type": "string", + "enum": [ + "horizontal", + "vertical" + ], + "description": "Item layout" + }, + "bordered": { + "type": "boolean", + "description": "Show borders" + }, + "size": { + "type": "string", + "enum": [ + "small", + "medium", + "large" + ], + "description": "List size" + }, + "split": { + "type": "boolean", + "description": "Split items with divider" + }, + "loading": { + "type": "boolean", + "description": "Loading state" + }, + "pagination": { + "type": "object", + "properties": { + "pageSize": { + "type": "number", + "description": "Page size" + }, + "total": { + "type": "number", + "description": "Total items" + } + }, + "required": [ + "pageSize" + ], + "additionalProperties": false, + "description": "Pagination configuration" + } + }, + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "container", + "divider", + "space", + "grid", + "flex", + "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", + "alert", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", + "tooltip", + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/MenuComponent.json b/packages/spec/json-schema/MenuComponent.json new file mode 100644 index 0000000..9b08d65 --- /dev/null +++ b/packages/spec/json-schema/MenuComponent.json @@ -0,0 +1,224 @@ +{ + "$ref": "#/definitions/MenuComponent", + "definitions": { + "MenuComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "menu" + }, + "props": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Menu item key" + }, + "label": { + "type": "string", + "description": "Menu item label" + }, + "icon": { + "type": "string", + "description": "Menu item icon" + }, + "href": { + "type": "string", + "description": "Link URL" + }, + "disabled": { + "type": "boolean", + "description": "Disabled state" + }, + "children": { + "type": "array", + "description": "Submenu items" + } + }, + "required": [ + "key", + "label" + ], + "additionalProperties": false + }, + "description": "Menu items" + }, + "mode": { + "type": "string", + "enum": [ + "horizontal", + "vertical", + "inline" + ], + "description": "Menu mode" + }, + "theme": { + "type": "string", + "enum": [ + "light", + "dark" + ], + "description": "Menu theme" + }, + "defaultSelectedKeys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Default selected keys" + }, + "defaultOpenKeys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Default opened keys" + }, + "collapsible": { + "type": "boolean", + "description": "Collapsible menu" + }, + "collapsed": { + "type": "boolean", + "description": "Collapsed state" + } + }, + "required": [ + "items" + ], + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "container", + "divider", + "space", + "grid", + "flex", + "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", + "alert", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", + "tooltip", + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type", + "props" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ModalComponent.json b/packages/spec/json-schema/ModalComponent.json index b516168..695473f 100644 --- a/packages/spec/json-schema/ModalComponent.json +++ b/packages/spec/json-schema/ModalComponent.json @@ -61,13 +61,66 @@ "accordion", "modal", "drawer", - "timeline", - "stepper", + "container", + "divider", + "space", + "grid", + "flex", "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", "alert", - "badge", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", "tooltip", - "popover" + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" ], "description": "Component type" }, diff --git a/packages/spec/json-schema/PaginationComponent.json b/packages/spec/json-schema/PaginationComponent.json new file mode 100644 index 0000000..1f136f5 --- /dev/null +++ b/packages/spec/json-schema/PaginationComponent.json @@ -0,0 +1,193 @@ +{ + "$ref": "#/definitions/PaginationComponent", + "definitions": { + "PaginationComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "pagination" + }, + "props": { + "type": "object", + "properties": { + "total": { + "type": "number", + "description": "Total items" + }, + "pageSize": { + "type": "number", + "default": 10, + "description": "Items per page" + }, + "current": { + "type": "number", + "default": 1, + "description": "Current page" + }, + "showSizeChanger": { + "type": "boolean", + "description": "Show size changer" + }, + "pageSizeOptions": { + "type": "array", + "items": { + "type": "number" + }, + "description": "Page size options" + }, + "showQuickJumper": { + "type": "boolean", + "description": "Show quick jumper" + }, + "showTotal": { + "type": "boolean", + "description": "Show total items" + }, + "simple": { + "type": "boolean", + "description": "Simple mode" + }, + "size": { + "type": "string", + "enum": [ + "small", + "medium", + "large" + ], + "description": "Pagination size" + } + }, + "required": [ + "total" + ], + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "container", + "divider", + "space", + "grid", + "flex", + "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", + "alert", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", + "tooltip", + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type", + "props" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/PopoverComponent.json b/packages/spec/json-schema/PopoverComponent.json index 0ef5e83..573ea3e 100644 --- a/packages/spec/json-schema/PopoverComponent.json +++ b/packages/spec/json-schema/PopoverComponent.json @@ -65,13 +65,66 @@ "accordion", "modal", "drawer", - "timeline", - "stepper", + "container", + "divider", + "space", + "grid", + "flex", "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", "alert", - "badge", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", "tooltip", - "popover" + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" ], "description": "Component type" }, diff --git a/packages/spec/json-schema/ProgressComponent.json b/packages/spec/json-schema/ProgressComponent.json new file mode 100644 index 0000000..12ac5ba --- /dev/null +++ b/packages/spec/json-schema/ProgressComponent.json @@ -0,0 +1,184 @@ +{ + "$ref": "#/definitions/ProgressComponent", + "definitions": { + "ProgressComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "progress" + }, + "props": { + "type": "object", + "properties": { + "percent": { + "type": "number", + "minimum": 0, + "maximum": 100, + "description": "Progress percentage" + }, + "type": { + "type": "string", + "enum": [ + "line", + "circle", + "dashboard" + ], + "description": "Progress type" + }, + "status": { + "type": "string", + "enum": [ + "normal", + "active", + "success", + "exception" + ], + "description": "Progress status" + }, + "showInfo": { + "type": "boolean", + "description": "Show progress info" + }, + "strokeWidth": { + "type": "number", + "description": "Stroke width" + }, + "strokeColor": { + "type": "string", + "description": "Stroke color" + } + }, + "required": [ + "percent" + ], + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "container", + "divider", + "space", + "grid", + "flex", + "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", + "alert", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", + "tooltip", + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type", + "props" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/SelectComponent.json b/packages/spec/json-schema/SelectComponent.json new file mode 100644 index 0000000..af79fd0 --- /dev/null +++ b/packages/spec/json-schema/SelectComponent.json @@ -0,0 +1,211 @@ +{ + "$ref": "#/definitions/SelectComponent", + "definitions": { + "SelectComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "select" + }, + "props": { + "type": "object", + "properties": { + "options": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "description": "Option label" + }, + "value": { + "description": "Option value" + }, + "disabled": { + "type": "boolean", + "description": "Disabled option" + }, + "icon": { + "type": "string", + "description": "Option icon" + } + }, + "required": [ + "label" + ], + "additionalProperties": false + }, + "description": "Select options" + }, + "placeholder": { + "type": "string", + "description": "Placeholder text" + }, + "defaultValue": { + "description": "Default value" + }, + "multiple": { + "type": "boolean", + "description": "Multiple selection" + }, + "searchable": { + "type": "boolean", + "description": "Allow search" + }, + "allowClear": { + "type": "boolean", + "description": "Allow clear" + }, + "size": { + "type": "string", + "enum": [ + "small", + "medium", + "large" + ], + "description": "Select size" + }, + "disabled": { + "type": "boolean", + "description": "Disabled state" + }, + "loading": { + "type": "boolean", + "description": "Loading state" + } + }, + "required": [ + "options" + ], + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "container", + "divider", + "space", + "grid", + "flex", + "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", + "alert", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", + "tooltip", + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type", + "props" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/StepperComponent.json b/packages/spec/json-schema/StepperComponent.json index 8b11d22..3af9afc 100644 --- a/packages/spec/json-schema/StepperComponent.json +++ b/packages/spec/json-schema/StepperComponent.json @@ -39,13 +39,66 @@ "accordion", "modal", "drawer", - "timeline", - "stepper", + "container", + "divider", + "space", + "grid", + "flex", "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", "alert", - "badge", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", "tooltip", - "popover" + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" ], "description": "Component type" }, @@ -130,13 +183,66 @@ "accordion", "modal", "drawer", - "timeline", - "stepper", + "container", + "divider", + "space", + "grid", + "flex", "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", "alert", - "badge", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", "tooltip", - "popover" + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" ], "description": "Component type" }, diff --git a/packages/spec/json-schema/TableComponent.json b/packages/spec/json-schema/TableComponent.json new file mode 100644 index 0000000..7db3641 --- /dev/null +++ b/packages/spec/json-schema/TableComponent.json @@ -0,0 +1,274 @@ +{ + "$ref": "#/definitions/TableComponent", + "definitions": { + "TableComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "table" + }, + "props": { + "type": "object", + "properties": { + "columns": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Column key" + }, + "label": { + "type": "string", + "description": "Column label" + }, + "width": { + "type": "number", + "description": "Column width" + }, + "sortable": { + "type": "boolean", + "description": "Enable sorting" + }, + "filterable": { + "type": "boolean", + "description": "Enable filtering" + }, + "fixed": { + "type": "string", + "enum": [ + "left", + "right" + ], + "description": "Fixed column position" + }, + "dataType": { + "type": "string", + "enum": [ + "text", + "number", + "date", + "boolean", + "currency", + "percent" + ], + "description": "Data type" + } + }, + "required": [ + "key", + "label" + ], + "additionalProperties": false + }, + "description": "Table columns" + }, + "dataSource": { + "type": "string", + "description": "Data source reference or object name" + }, + "pagination": { + "type": "object", + "properties": { + "pageSize": { + "type": "number", + "default": 10, + "description": "Page size" + }, + "showSizeChanger": { + "type": "boolean", + "description": "Show page size changer" + }, + "pageSizeOptions": { + "type": "array", + "items": { + "type": "number" + }, + "description": "Page size options" + } + }, + "additionalProperties": false, + "description": "Pagination configuration" + }, + "selection": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "checkbox", + "radio" + ], + "description": "Selection type" + }, + "selectedKeys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Selected row keys" + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Row selection configuration" + }, + "rowActions": { + "type": "array", + "description": "Actions for each row" + }, + "size": { + "type": "string", + "enum": [ + "small", + "medium", + "large" + ], + "description": "Table size" + }, + "bordered": { + "type": "boolean", + "description": "Show borders" + }, + "striped": { + "type": "boolean", + "description": "Striped rows" + } + }, + "required": [ + "columns" + ], + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "container", + "divider", + "space", + "grid", + "flex", + "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", + "alert", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", + "tooltip", + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type", + "props" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/TabsComponent.json b/packages/spec/json-schema/TabsComponent.json index a653cba..aa76299 100644 --- a/packages/spec/json-schema/TabsComponent.json +++ b/packages/spec/json-schema/TabsComponent.json @@ -35,13 +35,66 @@ "accordion", "modal", "drawer", - "timeline", - "stepper", + "container", + "divider", + "space", + "grid", + "flex", "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", "alert", - "badge", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", "tooltip", - "popover" + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" ], "description": "Component type" }, @@ -118,13 +171,66 @@ "accordion", "modal", "drawer", - "timeline", - "stepper", + "container", + "divider", + "space", + "grid", + "flex", "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", "alert", - "badge", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", "tooltip", - "popover" + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" ], "description": "Component type" }, diff --git a/packages/spec/json-schema/TimelineComponent.json b/packages/spec/json-schema/TimelineComponent.json index daacbf9..36802df 100644 --- a/packages/spec/json-schema/TimelineComponent.json +++ b/packages/spec/json-schema/TimelineComponent.json @@ -43,13 +43,66 @@ "accordion", "modal", "drawer", - "timeline", - "stepper", + "container", + "divider", + "space", + "grid", + "flex", "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", "alert", - "badge", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", "tooltip", - "popover" + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" ], "description": "Component type" }, @@ -129,13 +182,66 @@ "accordion", "modal", "drawer", - "timeline", - "stepper", + "container", + "divider", + "space", + "grid", + "flex", "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", "alert", - "badge", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", "tooltip", - "popover" + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" ], "description": "Component type" }, diff --git a/packages/spec/json-schema/TooltipComponent.json b/packages/spec/json-schema/TooltipComponent.json index 8cc358c..718de3b 100644 --- a/packages/spec/json-schema/TooltipComponent.json +++ b/packages/spec/json-schema/TooltipComponent.json @@ -60,13 +60,66 @@ "accordion", "modal", "drawer", - "timeline", - "stepper", + "container", + "divider", + "space", + "grid", + "flex", "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", "alert", - "badge", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", "tooltip", - "popover" + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" ], "description": "Component type" }, diff --git a/packages/spec/json-schema/TreeComponent.json b/packages/spec/json-schema/TreeComponent.json new file mode 100644 index 0000000..145bfb7 --- /dev/null +++ b/packages/spec/json-schema/TreeComponent.json @@ -0,0 +1,218 @@ +{ + "$ref": "#/definitions/TreeComponent", + "definitions": { + "TreeComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "tree" + }, + "props": { + "type": "object", + "properties": { + "treeData": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Node title" + }, + "key": { + "type": "string", + "description": "Node key" + }, + "icon": { + "type": "string", + "description": "Node icon" + }, + "disabled": { + "type": "boolean", + "description": "Disabled node" + }, + "children": { + "type": "array", + "description": "Child nodes" + } + }, + "required": [ + "title", + "key" + ], + "additionalProperties": false + }, + "description": "Tree data" + }, + "checkable": { + "type": "boolean", + "description": "Show checkbox on nodes" + }, + "selectable": { + "type": "boolean", + "description": "Selectable nodes" + }, + "multiple": { + "type": "boolean", + "description": "Multiple selection" + }, + "defaultExpandedKeys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Default expanded keys" + }, + "defaultSelectedKeys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Default selected keys" + }, + "defaultCheckedKeys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Default checked keys" + }, + "showLine": { + "type": "boolean", + "description": "Show tree line" + }, + "showIcon": { + "type": "boolean", + "description": "Show node icon" + } + }, + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "container", + "divider", + "space", + "grid", + "flex", + "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", + "alert", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", + "tooltip", + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/UploadComponent.json b/packages/spec/json-schema/UploadComponent.json new file mode 100644 index 0000000..adb5f38 --- /dev/null +++ b/packages/spec/json-schema/UploadComponent.json @@ -0,0 +1,180 @@ +{ + "$ref": "#/definitions/UploadComponent", + "definitions": { + "UploadComponent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "upload" + }, + "props": { + "type": "object", + "properties": { + "action": { + "type": "string", + "description": "Upload URL" + }, + "accept": { + "type": "string", + "description": "Accepted file types" + }, + "multiple": { + "type": "boolean", + "description": "Multiple file upload" + }, + "maxSize": { + "type": "number", + "description": "Max file size" + }, + "maxCount": { + "type": "number", + "description": "Max file count" + }, + "listType": { + "type": "string", + "enum": [ + "text", + "picture", + "picture-card" + ], + "description": "Upload list type" + }, + "showUploadList": { + "type": "boolean", + "description": "Show upload list" + }, + "disabled": { + "type": "boolean", + "description": "Disabled state" + } + }, + "additionalProperties": false + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "card", + "tabs", + "accordion", + "modal", + "drawer", + "container", + "divider", + "space", + "grid", + "flex", + "breadcrumb", + "stepper", + "menu", + "sidebar", + "pagination", + "dropdown", + "table", + "list", + "tree", + "description", + "statistic", + "tag", + "collapse", + "carousel", + "image", + "avatar", + "calendar_view", + "form", + "input", + "select", + "checkbox", + "radio", + "switch", + "slider", + "date_picker", + "time_picker", + "upload", + "autocomplete", + "cascader", + "transfer", + "color_picker", + "rate", + "alert", + "message", + "notification", + "progress", + "skeleton", + "spin", + "result", + "empty", + "button", + "button_group", + "icon_button", + "split_button", + "tooltip", + "popover", + "dialog", + "confirm", + "badge", + "timeline", + "steps", + "anchor", + "back_top", + "watermark", + "qrcode" + ], + "description": "Component type" + }, + "props": { + "type": "object", + "additionalProperties": {}, + "description": "Component properties" + }, + "events": { + "type": "object", + "additionalProperties": true, + "description": "Event handlers" + }, + "style": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Custom styles" + }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "description": "Child components" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/src/ui/component.test.ts b/packages/spec/src/ui/component.test.ts index 628e211..7b40e4e 100644 --- a/packages/spec/src/ui/component.test.ts +++ b/packages/spec/src/ui/component.test.ts @@ -14,6 +14,17 @@ import { BadgeComponentSchema, TooltipComponentSchema, PopoverComponentSchema, + TableComponentSchema, + FormComponentSchema, + MenuComponentSchema, + ButtonComponentSchema, + InputComponentSchema, + SelectComponentSchema, + ListComponentSchema, + TreeComponentSchema, + ProgressComponentSchema, + PaginationComponentSchema, + UploadComponentSchema, Component, type Component as ComponentType, } from './component.zod'; @@ -21,6 +32,7 @@ import { describe('ComponentType', () => { it('should accept all valid component types', () => { const validTypes = [ + // Original types 'card', 'tabs', 'accordion', @@ -33,6 +45,60 @@ describe('ComponentType', () => { 'badge', 'tooltip', 'popover', + // New enterprise types + 'table', + 'form', + 'menu', + 'button', + 'input', + 'select', + 'list', + 'tree', + 'progress', + 'pagination', + 'upload', + 'container', + 'divider', + 'space', + 'grid', + 'flex', + 'sidebar', + 'dropdown', + 'description', + 'statistic', + 'tag', + 'collapse', + 'carousel', + 'image', + 'avatar', + 'calendar_view', + 'checkbox', + 'radio', + 'switch', + 'slider', + 'date_picker', + 'time_picker', + 'autocomplete', + 'cascader', + 'transfer', + 'color_picker', + 'rate', + 'message', + 'notification', + 'skeleton', + 'spin', + 'result', + 'empty', + 'button_group', + 'icon_button', + 'split_button', + 'dialog', + 'confirm', + 'steps', + 'anchor', + 'back_top', + 'watermark', + 'qrcode', ] as const; validTypes.forEach(type => { @@ -41,7 +107,7 @@ describe('ComponentType', () => { }); it('should reject invalid component types', () => { - const invalidTypes = ['button', 'input', 'select', 'invalid']; + const invalidTypes = ['invalid', 'custom', 'unknown']; invalidTypes.forEach(type => { expect(() => ComponentType.parse(type)).toThrow(); @@ -1065,6 +1131,241 @@ describe('Real-World Component Examples', () => { }); }); +describe('Enterprise Components', () => { + describe('TableComponentSchema', () => { + it('should accept table with columns', () => { + const table = { + type: 'table' as const, + props: { + columns: [ + { key: 'name', label: 'Name', sortable: true }, + { key: 'email', label: 'Email', filterable: true }, + ], + dataSource: 'users', + pagination: { pageSize: 20 }, + }, + }; + + expect(() => TableComponentSchema.parse(table)).not.toThrow(); + }); + }); + + describe('FormComponentSchema', () => { + it('should accept form with fields', () => { + const form = { + type: 'form' as const, + props: { + layout: 'vertical' as const, + fields: [ + { name: 'username', label: 'Username', type: 'input', required: true }, + ], + submitButton: { label: 'Submit', variant: 'primary' as const }, + }, + }; + + expect(() => FormComponentSchema.parse(form)).not.toThrow(); + }); + }); + + describe('MenuComponentSchema', () => { + it('should accept menu with nested items', () => { + const menu = { + type: 'menu' as const, + props: { + items: [ + { key: 'dashboard', label: 'Dashboard', icon: 'home' }, + { + key: 'users', + label: 'Users', + children: [{ key: 'users-list', label: 'User List' }], + }, + ], + mode: 'vertical' as const, + }, + }; + + expect(() => MenuComponentSchema.parse(menu)).not.toThrow(); + }); + }); + + describe('ButtonComponentSchema', () => { + it('should accept button with all properties', () => { + const button = { + type: 'button' as const, + props: { + label: 'Submit', + variant: 'primary' as const, + icon: 'check', + size: 'large' as const, + loading: false, + }, + }; + + expect(() => ButtonComponentSchema.parse(button)).not.toThrow(); + }); + }); + + describe('InputComponentSchema', () => { + it('should accept input with properties', () => { + const input = { + type: 'input' as const, + props: { + type: 'email' as const, + placeholder: 'Enter email', + maxLength: 100, + allowClear: true, + }, + }; + + expect(() => InputComponentSchema.parse(input)).not.toThrow(); + }); + }); + + describe('SelectComponentSchema', () => { + it('should accept select with options', () => { + const select = { + type: 'select' as const, + props: { + options: [ + { label: 'Option 1', value: '1' }, + { label: 'Option 2', value: '2' }, + ], + multiple: true, + searchable: true, + }, + }; + + expect(() => SelectComponentSchema.parse(select)).not.toThrow(); + }); + }); + + describe('ListComponentSchema', () => { + it('should accept list with configuration', () => { + const list = { + type: 'list' as const, + props: { + dataSource: 'tasks', + itemLayout: 'horizontal' as const, + bordered: true, + }, + }; + + expect(() => ListComponentSchema.parse(list)).not.toThrow(); + }); + }); + + describe('TreeComponentSchema', () => { + it('should accept tree with data', () => { + const tree = { + type: 'tree' as const, + props: { + treeData: [ + { + title: 'Parent', + key: '0', + children: [{ title: 'Child', key: '0-0' }], + }, + ], + checkable: true, + }, + }; + + expect(() => TreeComponentSchema.parse(tree)).not.toThrow(); + }); + }); + + describe('ProgressComponentSchema', () => { + it('should accept progress with percent', () => { + const progress = { + type: 'progress' as const, + props: { + percent: 75, + type: 'circle' as const, + status: 'active' as const, + }, + }; + + expect(() => ProgressComponentSchema.parse(progress)).not.toThrow(); + }); + + it('should reject invalid percent', () => { + const progress = { + type: 'progress' as const, + props: { percent: 150 }, + }; + + expect(() => ProgressComponentSchema.parse(progress)).toThrow(); + }); + }); + + describe('PaginationComponentSchema', () => { + it('should accept pagination with configuration', () => { + const pagination = { + type: 'pagination' as const, + props: { + total: 100, + pageSize: 20, + showSizeChanger: true, + }, + }; + + expect(() => PaginationComponentSchema.parse(pagination)).not.toThrow(); + }); + }); + + describe('UploadComponentSchema', () => { + it('should accept upload with configuration', () => { + const upload = { + type: 'upload' as const, + props: { + action: '/api/upload', + accept: '.jpg,.png', + multiple: true, + maxSize: 5242880, + }, + }; + + expect(() => UploadComponentSchema.parse(upload)).not.toThrow(); + }); + }); + + describe('Enterprise Component Composition', () => { + it('should accept form with input and select', () => { + const form = { + type: 'form' as const, + children: [ + { type: 'input' as const, props: { placeholder: 'Name' } }, + { + type: 'select' as const, + props: { + options: [{ label: 'Active', value: 'active' }], + }, + }, + ], + }; + + expect(() => ComponentSchema.parse(form)).not.toThrow(); + }); + + it('should accept dashboard with table', () => { + const dashboard = { + type: 'card' as const, + props: { title: 'Dashboard' }, + children: [ + { + type: 'table' as const, + props: { + columns: [{ key: 'name', label: 'Name' }], + }, + }, + ], + }; + + expect(() => ComponentSchema.parse(dashboard)).not.toThrow(); + }); + }); +}); + describe('Component Factory', () => { it('should create component via factory', () => { const component = Component.create({ diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index b52d5ae..4d39aef 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -4,21 +4,102 @@ import { z } from 'zod'; * Component Type Enum * * Defines all reusable UI component types available in the ObjectStack UI system. - * These components can be composed together to build complex user interfaces. + * These components can be composed together to build complex user interfaces for + * enterprise management software. + * + * Categories: + * - Layout: card, tabs, accordion, modal, drawer, container, divider, space, grid, flex + * - Navigation: breadcrumb, stepper, menu, sidebar, pagination, dropdown + * - Data Display: table, list, tree, description, statistic, tag, collapse, carousel, image, avatar, calendar_view + * - Data Entry: form, input, select, checkbox, radio, switch, slider, date_picker, time_picker, upload, autocomplete, cascader, transfer, color_picker, rate + * - Feedback: alert, message, notification, progress, skeleton, spin, result, empty + * - Interaction: button, button_group, icon_button, split_button + * - Overlay: tooltip, popover, dialog, confirm + * - Other: badge, timeline, steps, anchor, back_top, watermark, qrcode */ export const ComponentType = z.enum([ + // Layout Components 'card', 'tabs', 'accordion', 'modal', 'drawer', - 'timeline', - 'stepper', + 'container', + 'divider', + 'space', + 'grid', + 'flex', + + // Navigation Components 'breadcrumb', + 'stepper', + 'menu', + 'sidebar', + 'pagination', + 'dropdown', + + // Data Display Components + 'table', + 'list', + 'tree', + 'description', + 'statistic', + 'tag', + 'collapse', + 'carousel', + 'image', + 'avatar', + 'calendar_view', + + // Data Entry Components + 'form', + 'input', + 'select', + 'checkbox', + 'radio', + 'switch', + 'slider', + 'date_picker', + 'time_picker', + 'upload', + 'autocomplete', + 'cascader', + 'transfer', + 'color_picker', + 'rate', + + // Feedback Components 'alert', - 'badge', + 'message', + 'notification', + 'progress', + 'skeleton', + 'spin', + 'result', + 'empty', + + // Interaction Components + 'button', + 'button_group', + 'icon_button', + 'split_button', + + // Overlay Components 'tooltip', 'popover', + 'dialog', + 'confirm', + + // Timeline & Process Components + 'badge', + 'timeline', + 'steps', + + // Other Components + 'anchor', + 'back_top', + 'watermark', + 'qrcode', ]); /** @@ -601,6 +682,321 @@ export const PopoverComponentSchema = BaseComponentSchema.extend({ children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), }); +/** + * Table Component Schema + * + * A data table component for displaying structured data with sorting, filtering, and pagination. + * Essential for enterprise data management. + * + * @example + * ```typescript + * const table: TableComponent = { + * type: 'table', + * props: { + * columns: [ + * { key: 'name', label: 'Name', sortable: true }, + * { key: 'email', label: 'Email' }, + * { key: 'status', label: 'Status', filterable: true } + * ], + * dataSource: 'users', + * pagination: { pageSize: 20, showSizeChanger: true }, + * selection: { type: 'checkbox', selectedKeys: [] } + * } + * } + * ``` + */ +export const TableComponentSchema = BaseComponentSchema.extend({ + type: z.literal('table'), + props: z.object({ + /** Column definitions */ + columns: z.array(z.object({ + key: z.string().describe('Column key'), + label: z.string().describe('Column label'), + width: z.number().optional().describe('Column width'), + sortable: z.boolean().optional().describe('Enable sorting'), + filterable: z.boolean().optional().describe('Enable filtering'), + fixed: z.enum(['left', 'right']).optional().describe('Fixed column position'), + dataType: z.enum(['text', 'number', 'date', 'boolean', 'currency', 'percent']).optional().describe('Data type'), + })).describe('Table columns'), + + dataSource: z.string().optional().describe('Data source reference or object name'), + + pagination: z.object({ + pageSize: z.number().default(10).describe('Page size'), + showSizeChanger: z.boolean().optional().describe('Show page size changer'), + pageSizeOptions: z.array(z.number()).optional().describe('Page size options'), + }).optional().describe('Pagination configuration'), + + selection: z.object({ + type: z.enum(['checkbox', 'radio']).describe('Selection type'), + selectedKeys: z.array(z.string()).optional().describe('Selected row keys'), + }).optional().describe('Row selection configuration'), + + rowActions: z.array(z.any()).optional().describe('Actions for each row'), + size: z.enum(['small', 'medium', 'large']).optional().describe('Table size'), + bordered: z.boolean().optional().describe('Show borders'), + striped: z.boolean().optional().describe('Striped rows'), + }), +}).extend({ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Form Component Schema + * + * A form container for data entry with validation support. + * Critical for enterprise data management. + */ +export const FormComponentSchema = BaseComponentSchema.extend({ + type: z.literal('form'), + props: z.object({ + layout: z.enum(['horizontal', 'vertical', 'inline']).optional().describe('Form layout'), + + fields: z.array(z.object({ + name: z.string().describe('Field name'), + label: z.string().describe('Field label'), + type: z.string().describe('Field type'), + required: z.boolean().optional().describe('Required field'), + placeholder: z.string().optional().describe('Placeholder text'), + defaultValue: z.any().optional().describe('Default value'), + validation: z.record(z.any()).optional().describe('Validation rules'), + })).optional().describe('Form fields'), + + submitButton: z.object({ + label: z.string().describe('Button label'), + variant: z.enum(['primary', 'secondary', 'success', 'danger']).optional().describe('Button variant'), + }).optional().describe('Submit button configuration'), + + cancelButton: z.object({ + label: z.string().describe('Button label'), + variant: z.enum(['primary', 'secondary', 'success', 'danger']).optional().describe('Button variant'), + }).optional().describe('Cancel button configuration'), + + labelWidth: z.number().optional().describe('Label width'), + labelAlign: z.enum(['left', 'right']).optional().describe('Label alignment'), + }).optional(), +}).extend({ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Menu Component Schema + * + * Navigation menu component for application structure. + */ +export const MenuComponentSchema = BaseComponentSchema.extend({ + type: z.literal('menu'), + props: z.object({ + items: z.array(z.lazy(() => z.object({ + key: z.string().describe('Menu item key'), + label: z.string().describe('Menu item label'), + icon: z.string().optional().describe('Menu item icon'), + href: z.string().optional().describe('Link URL'), + disabled: z.boolean().optional().describe('Disabled state'), + children: z.array(z.any()).optional().describe('Submenu items'), + }))).describe('Menu items'), + + mode: z.enum(['horizontal', 'vertical', 'inline']).optional().describe('Menu mode'), + theme: z.enum(['light', 'dark']).optional().describe('Menu theme'), + defaultSelectedKeys: z.array(z.string()).optional().describe('Default selected keys'), + defaultOpenKeys: z.array(z.string()).optional().describe('Default opened keys'), + collapsible: z.boolean().optional().describe('Collapsible menu'), + collapsed: z.boolean().optional().describe('Collapsed state'), + }), +}).extend({ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Button Component Schema + * + * Interactive button component. + */ +export const ButtonComponentSchema = BaseComponentSchema.extend({ + type: z.literal('button'), + props: z.object({ + label: z.string().describe('Button label'), + variant: z.enum(['primary', 'secondary', 'success', 'warning', 'danger', 'text', 'link']).optional().describe('Button variant'), + icon: z.string().optional().describe('Button icon'), + iconPosition: z.enum(['left', 'right']).optional().describe('Icon position'), + size: z.enum(['small', 'medium', 'large']).optional().describe('Button size'), + loading: z.boolean().optional().describe('Loading state'), + disabled: z.boolean().optional().describe('Disabled state'), + block: z.boolean().optional().describe('Block button (full width)'), + danger: z.boolean().optional().describe('Danger button'), + shape: z.enum(['default', 'circle', 'round']).optional().describe('Button shape'), + }), +}).extend({ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Input Component Schema + * + * Text input field for data entry. + */ +export const InputComponentSchema = BaseComponentSchema.extend({ + type: z.literal('input'), + props: z.object({ + type: z.enum(['text', 'password', 'email', 'number', 'tel', 'url', 'search', 'textarea']).optional().describe('Input type'), + placeholder: z.string().optional().describe('Placeholder text'), + defaultValue: z.string().optional().describe('Default value'), + size: z.enum(['small', 'medium', 'large']).optional().describe('Input size'), + disabled: z.boolean().optional().describe('Disabled state'), + readonly: z.boolean().optional().describe('Read-only state'), + maxLength: z.number().optional().describe('Maximum length'), + showCount: z.boolean().optional().describe('Show character count'), + prefix: z.string().optional().describe('Prefix icon'), + suffix: z.string().optional().describe('Suffix icon'), + allowClear: z.boolean().optional().describe('Allow clear button'), + rows: z.number().optional().describe('Rows for textarea type'), + }).optional(), +}).extend({ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Select Component Schema + * + * Dropdown select component for choosing from options. + */ +export const SelectComponentSchema = BaseComponentSchema.extend({ + type: z.literal('select'), + props: z.object({ + options: z.array(z.object({ + label: z.string().describe('Option label'), + value: z.any().describe('Option value'), + disabled: z.boolean().optional().describe('Disabled option'), + icon: z.string().optional().describe('Option icon'), + })).describe('Select options'), + + placeholder: z.string().optional().describe('Placeholder text'), + defaultValue: z.any().optional().describe('Default value'), + multiple: z.boolean().optional().describe('Multiple selection'), + searchable: z.boolean().optional().describe('Allow search'), + allowClear: z.boolean().optional().describe('Allow clear'), + size: z.enum(['small', 'medium', 'large']).optional().describe('Select size'), + disabled: z.boolean().optional().describe('Disabled state'), + loading: z.boolean().optional().describe('Loading state'), + }), +}).extend({ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * List Component Schema + * + * List component for displaying a series of items. + */ +export const ListComponentSchema = BaseComponentSchema.extend({ + type: z.literal('list'), + props: z.object({ + dataSource: z.string().optional().describe('Data source reference'), + itemLayout: z.enum(['horizontal', 'vertical']).optional().describe('Item layout'), + bordered: z.boolean().optional().describe('Show borders'), + size: z.enum(['small', 'medium', 'large']).optional().describe('List size'), + split: z.boolean().optional().describe('Split items with divider'), + loading: z.boolean().optional().describe('Loading state'), + pagination: z.object({ + pageSize: z.number().describe('Page size'), + total: z.number().optional().describe('Total items'), + }).optional().describe('Pagination configuration'), + }).optional(), +}).extend({ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Tree Component Schema + * + * Hierarchical tree structure component. + */ +export const TreeComponentSchema = BaseComponentSchema.extend({ + type: z.literal('tree'), + props: z.object({ + treeData: z.array(z.lazy(() => z.object({ + title: z.string().describe('Node title'), + key: z.string().describe('Node key'), + icon: z.string().optional().describe('Node icon'), + disabled: z.boolean().optional().describe('Disabled node'), + children: z.array(z.any()).optional().describe('Child nodes'), + }))).optional().describe('Tree data'), + + checkable: z.boolean().optional().describe('Show checkbox on nodes'), + selectable: z.boolean().optional().describe('Selectable nodes'), + multiple: z.boolean().optional().describe('Multiple selection'), + defaultExpandedKeys: z.array(z.string()).optional().describe('Default expanded keys'), + defaultSelectedKeys: z.array(z.string()).optional().describe('Default selected keys'), + defaultCheckedKeys: z.array(z.string()).optional().describe('Default checked keys'), + showLine: z.boolean().optional().describe('Show tree line'), + showIcon: z.boolean().optional().describe('Show node icon'), + }).optional(), +}).extend({ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Progress Component Schema + * + * Progress indicator for showing completion status. + */ +export const ProgressComponentSchema = BaseComponentSchema.extend({ + type: z.literal('progress'), + props: z.object({ + percent: z.number().min(0).max(100).describe('Progress percentage'), + type: z.enum(['line', 'circle', 'dashboard']).optional().describe('Progress type'), + status: z.enum(['normal', 'active', 'success', 'exception']).optional().describe('Progress status'), + showInfo: z.boolean().optional().describe('Show progress info'), + strokeWidth: z.number().optional().describe('Stroke width'), + strokeColor: z.string().optional().describe('Stroke color'), + }), +}).extend({ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Pagination Component Schema + * + * Pagination control for navigating through pages. + */ +export const PaginationComponentSchema = BaseComponentSchema.extend({ + type: z.literal('pagination'), + props: z.object({ + total: z.number().describe('Total items'), + pageSize: z.number().default(10).describe('Items per page'), + current: z.number().default(1).describe('Current page'), + showSizeChanger: z.boolean().optional().describe('Show size changer'), + pageSizeOptions: z.array(z.number()).optional().describe('Page size options'), + showQuickJumper: z.boolean().optional().describe('Show quick jumper'), + showTotal: z.boolean().optional().describe('Show total items'), + simple: z.boolean().optional().describe('Simple mode'), + size: z.enum(['small', 'medium', 'large']).optional().describe('Pagination size'), + }), +}).extend({ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + +/** + * Upload Component Schema + * + * File upload component. + */ +export const UploadComponentSchema = BaseComponentSchema.extend({ + type: z.literal('upload'), + props: z.object({ + action: z.string().optional().describe('Upload URL'), + accept: z.string().optional().describe('Accepted file types'), + multiple: z.boolean().optional().describe('Multiple file upload'), + maxSize: z.number().optional().describe('Max file size'), + maxCount: z.number().optional().describe('Max file count'), + listType: z.enum(['text', 'picture', 'picture-card']).optional().describe('Upload list type'), + showUploadList: z.boolean().optional().describe('Show upload list'), + disabled: z.boolean().optional().describe('Disabled state'), + }).optional(), +}).extend({ + children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), +}); + /** * TypeScript Type Exports */ @@ -618,6 +1014,17 @@ export type AlertComponent = z.infer; export type BadgeComponent = z.infer; export type TooltipComponent = z.infer; export type PopoverComponent = z.infer; +export type TableComponent = z.infer; +export type FormComponent = z.infer; +export type MenuComponent = z.infer; +export type ButtonComponent = z.infer; +export type InputComponent = z.infer; +export type SelectComponent = z.infer; +export type ListComponent = z.infer; +export type TreeComponent = z.infer; +export type ProgressComponent = z.infer; +export type PaginationComponent = z.infer; +export type UploadComponent = z.infer; /** * Component Factory Helper From 4f2f04116cad4ccf1481fbf166279db852290776 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 15:55:20 +0000 Subject: [PATCH 5/6] Refactor: Simplify Component Protocol to be implementation-agnostic Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- .../references/misc/AccordionComponent.mdx | 14 + .../docs/references/misc/AlertComponent.mdx | 14 + .../docs/references/misc/BadgeComponent.mdx | 14 + .../references/misc/BreadcrumbComponent.mdx | 14 + .../docs/references/misc/ButtonComponent.mdx | 14 + .../docs/references/misc/CardComponent.mdx | 14 + .../docs/references/misc/ComponentType.mdx | 72 + .../docs/references/misc/DrawerComponent.mdx | 14 + .../docs/references/misc/FormComponent.mdx | 14 + .../docs/references/misc/InputComponent.mdx | 14 + .../docs/references/misc/ListComponent.mdx | 14 + .../docs/references/misc/MenuComponent.mdx | 14 + .../docs/references/misc/ModalComponent.mdx | 14 + .../references/misc/PaginationComponent.mdx | 14 + .../docs/references/misc/PopoverComponent.mdx | 14 + .../references/misc/ProgressComponent.mdx | 14 + .../docs/references/misc/SelectComponent.mdx | 14 + .../docs/references/misc/StepperComponent.mdx | 14 + .../docs/references/misc/TableComponent.mdx | 14 + .../docs/references/misc/TabsComponent.mdx | 14 + .../references/misc/TimelineComponent.mdx | 14 + .../docs/references/misc/TooltipComponent.mdx | 14 + .../docs/references/misc/TreeComponent.mdx | 14 + .../docs/references/misc/UploadComponent.mdx | 14 + content/docs/references/ui/Component.mdx | 4 +- packages/spec/json-schema/Component.json | 79 +- packages/spec/src/ui/component.test.ts | 1429 +++-------------- packages/spec/src/ui/component.zod.ts | 1023 +----------- 28 files changed, 641 insertions(+), 2288 deletions(-) create mode 100644 content/docs/references/misc/AccordionComponent.mdx create mode 100644 content/docs/references/misc/AlertComponent.mdx create mode 100644 content/docs/references/misc/BadgeComponent.mdx create mode 100644 content/docs/references/misc/BreadcrumbComponent.mdx create mode 100644 content/docs/references/misc/ButtonComponent.mdx create mode 100644 content/docs/references/misc/CardComponent.mdx create mode 100644 content/docs/references/misc/ComponentType.mdx create mode 100644 content/docs/references/misc/DrawerComponent.mdx create mode 100644 content/docs/references/misc/FormComponent.mdx create mode 100644 content/docs/references/misc/InputComponent.mdx create mode 100644 content/docs/references/misc/ListComponent.mdx create mode 100644 content/docs/references/misc/MenuComponent.mdx create mode 100644 content/docs/references/misc/ModalComponent.mdx create mode 100644 content/docs/references/misc/PaginationComponent.mdx create mode 100644 content/docs/references/misc/PopoverComponent.mdx create mode 100644 content/docs/references/misc/ProgressComponent.mdx create mode 100644 content/docs/references/misc/SelectComponent.mdx create mode 100644 content/docs/references/misc/StepperComponent.mdx create mode 100644 content/docs/references/misc/TableComponent.mdx create mode 100644 content/docs/references/misc/TabsComponent.mdx create mode 100644 content/docs/references/misc/TimelineComponent.mdx create mode 100644 content/docs/references/misc/TooltipComponent.mdx create mode 100644 content/docs/references/misc/TreeComponent.mdx create mode 100644 content/docs/references/misc/UploadComponent.mdx diff --git a/content/docs/references/misc/AccordionComponent.mdx b/content/docs/references/misc/AccordionComponent.mdx new file mode 100644 index 0000000..788ceb5 --- /dev/null +++ b/content/docs/references/misc/AccordionComponent.mdx @@ -0,0 +1,14 @@ +--- +title: AccordionComponent +description: AccordionComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/AlertComponent.mdx b/content/docs/references/misc/AlertComponent.mdx new file mode 100644 index 0000000..833ad8e --- /dev/null +++ b/content/docs/references/misc/AlertComponent.mdx @@ -0,0 +1,14 @@ +--- +title: AlertComponent +description: AlertComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/BadgeComponent.mdx b/content/docs/references/misc/BadgeComponent.mdx new file mode 100644 index 0000000..5fcfb69 --- /dev/null +++ b/content/docs/references/misc/BadgeComponent.mdx @@ -0,0 +1,14 @@ +--- +title: BadgeComponent +description: BadgeComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/BreadcrumbComponent.mdx b/content/docs/references/misc/BreadcrumbComponent.mdx new file mode 100644 index 0000000..1f34455 --- /dev/null +++ b/content/docs/references/misc/BreadcrumbComponent.mdx @@ -0,0 +1,14 @@ +--- +title: BreadcrumbComponent +description: BreadcrumbComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/ButtonComponent.mdx b/content/docs/references/misc/ButtonComponent.mdx new file mode 100644 index 0000000..8e1d265 --- /dev/null +++ b/content/docs/references/misc/ButtonComponent.mdx @@ -0,0 +1,14 @@ +--- +title: ButtonComponent +description: ButtonComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/CardComponent.mdx b/content/docs/references/misc/CardComponent.mdx new file mode 100644 index 0000000..99ac7bf --- /dev/null +++ b/content/docs/references/misc/CardComponent.mdx @@ -0,0 +1,14 @@ +--- +title: CardComponent +description: CardComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | optional | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/ComponentType.mdx b/content/docs/references/misc/ComponentType.mdx new file mode 100644 index 0000000..5a9d83a --- /dev/null +++ b/content/docs/references/misc/ComponentType.mdx @@ -0,0 +1,72 @@ +--- +title: ComponentType +description: ComponentType Schema Reference +--- + +## Allowed Values + +* `card` +* `tabs` +* `accordion` +* `modal` +* `drawer` +* `container` +* `divider` +* `space` +* `grid` +* `flex` +* `breadcrumb` +* `stepper` +* `menu` +* `sidebar` +* `pagination` +* `dropdown` +* `table` +* `list` +* `tree` +* `description` +* `statistic` +* `tag` +* `collapse` +* `carousel` +* `image` +* `avatar` +* `calendar_view` +* `form` +* `input` +* `select` +* `checkbox` +* `radio` +* `switch` +* `slider` +* `date_picker` +* `time_picker` +* `upload` +* `autocomplete` +* `cascader` +* `transfer` +* `color_picker` +* `rate` +* `alert` +* `message` +* `notification` +* `progress` +* `skeleton` +* `spin` +* `result` +* `empty` +* `button` +* `button_group` +* `icon_button` +* `split_button` +* `tooltip` +* `popover` +* `dialog` +* `confirm` +* `badge` +* `timeline` +* `steps` +* `anchor` +* `back_top` +* `watermark` +* `qrcode` \ No newline at end of file diff --git a/content/docs/references/misc/DrawerComponent.mdx b/content/docs/references/misc/DrawerComponent.mdx new file mode 100644 index 0000000..7698940 --- /dev/null +++ b/content/docs/references/misc/DrawerComponent.mdx @@ -0,0 +1,14 @@ +--- +title: DrawerComponent +description: DrawerComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | optional | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/FormComponent.mdx b/content/docs/references/misc/FormComponent.mdx new file mode 100644 index 0000000..d5f10a1 --- /dev/null +++ b/content/docs/references/misc/FormComponent.mdx @@ -0,0 +1,14 @@ +--- +title: FormComponent +description: FormComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | optional | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/InputComponent.mdx b/content/docs/references/misc/InputComponent.mdx new file mode 100644 index 0000000..8b7e742 --- /dev/null +++ b/content/docs/references/misc/InputComponent.mdx @@ -0,0 +1,14 @@ +--- +title: InputComponent +description: InputComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | optional | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/ListComponent.mdx b/content/docs/references/misc/ListComponent.mdx new file mode 100644 index 0000000..0243c00 --- /dev/null +++ b/content/docs/references/misc/ListComponent.mdx @@ -0,0 +1,14 @@ +--- +title: ListComponent +description: ListComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | optional | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/MenuComponent.mdx b/content/docs/references/misc/MenuComponent.mdx new file mode 100644 index 0000000..751b884 --- /dev/null +++ b/content/docs/references/misc/MenuComponent.mdx @@ -0,0 +1,14 @@ +--- +title: MenuComponent +description: MenuComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/ModalComponent.mdx b/content/docs/references/misc/ModalComponent.mdx new file mode 100644 index 0000000..40fd0db --- /dev/null +++ b/content/docs/references/misc/ModalComponent.mdx @@ -0,0 +1,14 @@ +--- +title: ModalComponent +description: ModalComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | optional | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/PaginationComponent.mdx b/content/docs/references/misc/PaginationComponent.mdx new file mode 100644 index 0000000..255091e --- /dev/null +++ b/content/docs/references/misc/PaginationComponent.mdx @@ -0,0 +1,14 @@ +--- +title: PaginationComponent +description: PaginationComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/PopoverComponent.mdx b/content/docs/references/misc/PopoverComponent.mdx new file mode 100644 index 0000000..a8f98b2 --- /dev/null +++ b/content/docs/references/misc/PopoverComponent.mdx @@ -0,0 +1,14 @@ +--- +title: PopoverComponent +description: PopoverComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | optional | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/ProgressComponent.mdx b/content/docs/references/misc/ProgressComponent.mdx new file mode 100644 index 0000000..af490b9 --- /dev/null +++ b/content/docs/references/misc/ProgressComponent.mdx @@ -0,0 +1,14 @@ +--- +title: ProgressComponent +description: ProgressComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/SelectComponent.mdx b/content/docs/references/misc/SelectComponent.mdx new file mode 100644 index 0000000..73b14a3 --- /dev/null +++ b/content/docs/references/misc/SelectComponent.mdx @@ -0,0 +1,14 @@ +--- +title: SelectComponent +description: SelectComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/StepperComponent.mdx b/content/docs/references/misc/StepperComponent.mdx new file mode 100644 index 0000000..7215263 --- /dev/null +++ b/content/docs/references/misc/StepperComponent.mdx @@ -0,0 +1,14 @@ +--- +title: StepperComponent +description: StepperComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/TableComponent.mdx b/content/docs/references/misc/TableComponent.mdx new file mode 100644 index 0000000..c94288d --- /dev/null +++ b/content/docs/references/misc/TableComponent.mdx @@ -0,0 +1,14 @@ +--- +title: TableComponent +description: TableComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/TabsComponent.mdx b/content/docs/references/misc/TabsComponent.mdx new file mode 100644 index 0000000..2497a42 --- /dev/null +++ b/content/docs/references/misc/TabsComponent.mdx @@ -0,0 +1,14 @@ +--- +title: TabsComponent +description: TabsComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/TimelineComponent.mdx b/content/docs/references/misc/TimelineComponent.mdx new file mode 100644 index 0000000..6487f2e --- /dev/null +++ b/content/docs/references/misc/TimelineComponent.mdx @@ -0,0 +1,14 @@ +--- +title: TimelineComponent +description: TimelineComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/TooltipComponent.mdx b/content/docs/references/misc/TooltipComponent.mdx new file mode 100644 index 0000000..8d5ca3c --- /dev/null +++ b/content/docs/references/misc/TooltipComponent.mdx @@ -0,0 +1,14 @@ +--- +title: TooltipComponent +description: TooltipComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | ✅ | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/TreeComponent.mdx b/content/docs/references/misc/TreeComponent.mdx new file mode 100644 index 0000000..386e582 --- /dev/null +++ b/content/docs/references/misc/TreeComponent.mdx @@ -0,0 +1,14 @@ +--- +title: TreeComponent +description: TreeComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | optional | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/misc/UploadComponent.mdx b/content/docs/references/misc/UploadComponent.mdx new file mode 100644 index 0000000..07025f8 --- /dev/null +++ b/content/docs/references/misc/UploadComponent.mdx @@ -0,0 +1,14 @@ +--- +title: UploadComponent +description: UploadComponent Schema Reference +--- + +## Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `string` | ✅ | | +| **props** | `object` | optional | | +| **events** | `Record` | optional | Event handlers | +| **style** | `Record` | optional | Custom styles | +| **children** | `object[]` | optional | Child components | diff --git a/content/docs/references/ui/Component.mdx b/content/docs/references/ui/Component.mdx index 8ccca56..60c8362 100644 --- a/content/docs/references/ui/Component.mdx +++ b/content/docs/references/ui/Component.mdx @@ -7,8 +7,8 @@ description: Component Schema Reference | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **type** | `Enum<'card' \| 'tabs' \| 'accordion' \| 'modal' \| 'drawer' \| 'container' \| 'divider' \| 'space' \| 'grid' \| 'flex' \| 'breadcrumb' \| 'stepper' \| 'menu' \| 'sidebar' \| 'pagination' \| 'dropdown' \| 'table' \| 'list' \| 'tree' \| 'description' \| 'statistic' \| 'tag' \| 'collapse' \| 'carousel' \| 'image' \| 'avatar' \| 'calendar_view' \| 'form' \| 'input' \| 'select' \| 'checkbox' \| 'radio' \| 'switch' \| 'slider' \| 'date_picker' \| 'time_picker' \| 'upload' \| 'autocomplete' \| 'cascader' \| 'transfer' \| 'color_picker' \| 'rate' \| 'alert' \| 'message' \| 'notification' \| 'progress' \| 'skeleton' \| 'spin' \| 'result' \| 'empty' \| 'button' \| 'button_group' \| 'icon_button' \| 'split_button' \| 'tooltip' \| 'popover' \| 'dialog' \| 'confirm' \| 'badge' \| 'timeline' \| 'steps' \| 'anchor' \| 'back_top' \| 'watermark' \| 'qrcode'>` | ✅ | Component type | +| **type** | `string` | ✅ | Component type identifier | | **props** | `Record` | optional | Component properties | +| **children** | `any[]` | optional | Child components | | **events** | `Record` | optional | Event handlers | | **style** | `Record` | optional | Custom styles | -| **children** | `any[]` | optional | Child components | diff --git a/packages/spec/json-schema/Component.json b/packages/spec/json-schema/Component.json index 07c2dcf..83a8990 100644 --- a/packages/spec/json-schema/Component.json +++ b/packages/spec/json-schema/Component.json @@ -6,80 +6,18 @@ "properties": { "type": { "type": "string", - "enum": [ - "card", - "tabs", - "accordion", - "modal", - "drawer", - "container", - "divider", - "space", - "grid", - "flex", - "breadcrumb", - "stepper", - "menu", - "sidebar", - "pagination", - "dropdown", - "table", - "list", - "tree", - "description", - "statistic", - "tag", - "collapse", - "carousel", - "image", - "avatar", - "calendar_view", - "form", - "input", - "select", - "checkbox", - "radio", - "switch", - "slider", - "date_picker", - "time_picker", - "upload", - "autocomplete", - "cascader", - "transfer", - "color_picker", - "rate", - "alert", - "message", - "notification", - "progress", - "skeleton", - "spin", - "result", - "empty", - "button", - "button_group", - "icon_button", - "split_button", - "tooltip", - "popover", - "dialog", - "confirm", - "badge", - "timeline", - "steps", - "anchor", - "back_top", - "watermark", - "qrcode" - ], - "description": "Component type" + "description": "Component type identifier" }, "props": { "type": "object", "additionalProperties": {}, "description": "Component properties" }, + "children": { + "type": "array", + "items": {}, + "description": "Child components" + }, "events": { "type": "object", "additionalProperties": true, @@ -91,11 +29,6 @@ "type": "string" }, "description": "Custom styles" - }, - "children": { - "type": "array", - "items": {}, - "description": "Child components" } }, "required": [ diff --git a/packages/spec/src/ui/component.test.ts b/packages/spec/src/ui/component.test.ts index 7b40e4e..309f066 100644 --- a/packages/spec/src/ui/component.test.ts +++ b/packages/spec/src/ui/component.test.ts @@ -1,123 +1,13 @@ import { describe, it, expect } from 'vitest'; import { - ComponentType, ComponentSchema, - CardComponentSchema, - TabsComponentSchema, - AccordionComponentSchema, - ModalComponentSchema, - DrawerComponentSchema, - TimelineComponentSchema, - StepperComponentSchema, - BreadcrumbComponentSchema, - AlertComponentSchema, - BadgeComponentSchema, - TooltipComponentSchema, - PopoverComponentSchema, - TableComponentSchema, - FormComponentSchema, - MenuComponentSchema, - ButtonComponentSchema, - InputComponentSchema, - SelectComponentSchema, - ListComponentSchema, - TreeComponentSchema, - ProgressComponentSchema, - PaginationComponentSchema, - UploadComponentSchema, Component, type Component as ComponentType, } from './component.zod'; -describe('ComponentType', () => { - it('should accept all valid component types', () => { - const validTypes = [ - // Original types - 'card', - 'tabs', - 'accordion', - 'modal', - 'drawer', - 'timeline', - 'stepper', - 'breadcrumb', - 'alert', - 'badge', - 'tooltip', - 'popover', - // New enterprise types - 'table', - 'form', - 'menu', - 'button', - 'input', - 'select', - 'list', - 'tree', - 'progress', - 'pagination', - 'upload', - 'container', - 'divider', - 'space', - 'grid', - 'flex', - 'sidebar', - 'dropdown', - 'description', - 'statistic', - 'tag', - 'collapse', - 'carousel', - 'image', - 'avatar', - 'calendar_view', - 'checkbox', - 'radio', - 'switch', - 'slider', - 'date_picker', - 'time_picker', - 'autocomplete', - 'cascader', - 'transfer', - 'color_picker', - 'rate', - 'message', - 'notification', - 'skeleton', - 'spin', - 'result', - 'empty', - 'button_group', - 'icon_button', - 'split_button', - 'dialog', - 'confirm', - 'steps', - 'anchor', - 'back_top', - 'watermark', - 'qrcode', - ] as const; - - validTypes.forEach(type => { - expect(() => ComponentType.parse(type)).not.toThrow(); - }); - }); - - it('should reject invalid component types', () => { - const invalidTypes = ['invalid', 'custom', 'unknown']; - - invalidTypes.forEach(type => { - expect(() => ComponentType.parse(type)).toThrow(); - }); - }); -}); - describe('ComponentSchema', () => { - describe('Basic Component', () => { - it('should accept minimal component', () => { + describe('Basic Structure', () => { + it('should accept minimal component with just type', () => { const component: ComponentType = { type: 'card', }; @@ -125,12 +15,26 @@ describe('ComponentSchema', () => { expect(() => ComponentSchema.parse(component)).not.toThrow(); }); - it('should accept component with props', () => { + it('should accept any string as type (extensible)', () => { + const customComponent: ComponentType = { + type: 'custom-widget', + }; + + expect(() => ComponentSchema.parse(customComponent)).not.toThrow(); + }); + + it('should accept component with arbitrary props', () => { const component: ComponentType = { - type: 'card', + type: 'button', props: { - title: 'Test Card', - customProp: 'value', + label: 'Click me', + variant: 'primary', + size: 'large', + customProp: 'any value', + nestedProp: { + foo: 'bar', + baz: 123, + }, }, }; @@ -139,7 +43,7 @@ describe('ComponentSchema', () => { it('should accept component with style', () => { const component: ComponentType = { - type: 'card', + type: 'div', style: { padding: '16px', borderRadius: '8px', @@ -149,9 +53,22 @@ describe('ComponentSchema', () => { expect(() => ComponentSchema.parse(component)).not.toThrow(); }); + + it('should accept component with events', () => { + const component = { + type: 'button', + events: { + onClick: () => {}, + onHover: () => {}, + onCustomEvent: () => {}, + }, + }; + + expect(() => ComponentSchema.parse(component)).not.toThrow(); + }); }); - describe('Component Nesting (Children)', () => { + describe('Component Nesting (Recursive Structure)', () => { it('should accept component with single child', () => { const component: ComponentType = { type: 'card', @@ -168,18 +85,19 @@ describe('ComponentSchema', () => { it('should accept component with multiple children', () => { const component: ComponentType = { - type: 'card', - props: { - title: 'Container', - }, + type: 'container', children: [ { - type: 'badge', - props: { label: 'Status' }, + type: 'header', + props: { title: 'Title' }, }, { - type: 'alert', - props: { message: 'Information' }, + type: 'content', + props: { text: 'Body' }, + }, + { + type: 'footer', + props: { copyright: '2025' }, }, ], }; @@ -187,19 +105,19 @@ describe('ComponentSchema', () => { expect(() => ComponentSchema.parse(component)).not.toThrow(); }); - it('should accept deeply nested components', () => { + it('should accept deeply nested components (3+ levels)', () => { const component: ComponentType = { - type: 'card', + type: 'page', children: [ { - type: 'accordion', + type: 'section', children: [ { - type: 'tabs', + type: 'card', children: [ { - type: 'badge', - props: { label: 'Deeply nested' }, + type: 'button', + props: { label: 'Deep Button' }, }, ], }, @@ -210,1158 +128,228 @@ describe('ComponentSchema', () => { expect(() => ComponentSchema.parse(component)).not.toThrow(); }); - }); - - describe('Event Binding', () => { - it('should accept component with events', () => { - const component = { - type: 'card' as const, - events: { - onClick: () => {}, - onHover: () => {}, - }, - }; - - expect(() => ComponentSchema.parse(component)).not.toThrow(); - }); - it('should accept component with multiple event handlers', () => { - const component = { - type: 'modal' as const, - events: { - onOpen: () => console.log('opened'), - onClose: () => console.log('closed'), - onSubmit: () => console.log('submitted'), - }, + it('should accept complex nested structure with props at each level', () => { + const component: ComponentType = { + type: 'dashboard', + props: { title: 'My Dashboard' }, + children: [ + { + type: 'grid', + props: { columns: 3 }, + children: [ + { + type: 'widget', + props: { type: 'chart', data: [] }, + }, + { + type: 'widget', + props: { type: 'stats', value: 100 }, + }, + ], + }, + ], }; expect(() => ComponentSchema.parse(component)).not.toThrow(); }); }); -}); - -describe('CardComponentSchema', () => { - it('should accept minimal card', () => { - const card = { - type: 'card' as const, - }; - - expect(() => CardComponentSchema.parse(card)).not.toThrow(); - }); - - it('should accept card with title and subtitle', () => { - const card = { - type: 'card' as const, - props: { - title: 'Project Overview', - subtitle: 'Q4 2025', - }, - }; - - expect(() => CardComponentSchema.parse(card)).not.toThrow(); - }); - - it('should accept card with image', () => { - const card = { - type: 'card' as const, - props: { - title: 'User Profile', - image: 'https://example.com/avatar.jpg', - }, - }; - - expect(() => CardComponentSchema.parse(card)).not.toThrow(); - }); - - it('should reject card with invalid image URL', () => { - const card = { - type: 'card' as const, - props: { - image: 'not-a-url', - }, - }; - - expect(() => CardComponentSchema.parse(card)).toThrow(); - }); - - it('should accept card with actions', () => { - const card = { - type: 'card' as const, - props: { - title: 'Task Card', - actions: [ - { label: 'Edit', onClick: () => {} }, - { label: 'Delete', onClick: () => {} }, - ], - }, - }; - - expect(() => CardComponentSchema.parse(card)).not.toThrow(); - }); - it('should accept card with children', () => { - const card = { - type: 'card' as const, - props: { - title: 'Container Card', - }, - children: [ - { - type: 'alert' as const, - props: { message: 'Success!' }, + describe('Real-World Component Trees', () => { + it('should accept form with input fields', () => { + const form = { + type: 'form', + props: { + action: '/submit', + method: 'POST', }, - ], - }; - - expect(() => CardComponentSchema.parse(card)).not.toThrow(); - }); -}); - -describe('TabsComponentSchema', () => { - it('should accept minimal tabs', () => { - const tabs = { - type: 'tabs' as const, - props: { - tabs: [ - { label: 'Tab 1' }, - { label: 'Tab 2' }, - ], - }, - }; - - expect(() => TabsComponentSchema.parse(tabs)).not.toThrow(); - }); - - it('should accept tabs with icons', () => { - const tabs = { - type: 'tabs' as const, - props: { - tabs: [ - { label: 'Home', icon: 'home' }, - { label: 'Settings', icon: 'settings' }, - ], - }, - }; - - expect(() => TabsComponentSchema.parse(tabs)).not.toThrow(); - }); - - it('should accept tabs with content', () => { - const tabs = { - type: 'tabs' as const, - props: { - tabs: [ + children: [ { - label: 'Overview', - content: { - type: 'card' as const, - props: { title: 'Overview Content' }, + type: 'input', + props: { + name: 'username', + type: 'text', + placeholder: 'Enter username', + required: true, }, }, { - label: 'Details', - content: { - type: 'card' as const, - props: { title: 'Details Content' }, + type: 'input', + props: { + name: 'password', + type: 'password', + placeholder: 'Enter password', }, }, - ], - }, - }; - - expect(() => TabsComponentSchema.parse(tabs)).not.toThrow(); - }); - - it('should accept tabs with default tab', () => { - const tabs = { - type: 'tabs' as const, - props: { - tabs: [ - { label: 'Tab 1' }, - { label: 'Tab 2' }, - ], - defaultTab: 1, - }, - }; - - expect(() => TabsComponentSchema.parse(tabs)).not.toThrow(); - }); - - it('should reject negative default tab index', () => { - const tabs = { - type: 'tabs' as const, - props: { - tabs: [{ label: 'Tab 1' }], - defaultTab: -1, - }, - }; - - expect(() => TabsComponentSchema.parse(tabs)).toThrow(); - }); -}); - -describe('AccordionComponentSchema', () => { - it('should accept minimal accordion', () => { - const accordion = { - type: 'accordion' as const, - props: { - items: [ - { title: 'Section 1' }, - { title: 'Section 2' }, - ], - }, - }; - - expect(() => AccordionComponentSchema.parse(accordion)).not.toThrow(); - }); - - it('should accept accordion with content', () => { - const accordion = { - type: 'accordion' as const, - props: { - items: [ { - title: 'User Info', - content: { - type: 'card' as const, - props: { title: 'User Details' }, + type: 'button', + props: { + type: 'submit', + label: 'Login', }, }, ], - }, - }; - - expect(() => AccordionComponentSchema.parse(accordion)).not.toThrow(); - }); - - it('should accept accordion with icons and expanded state', () => { - const accordion = { - type: 'accordion' as const, - props: { - items: [ - { - title: 'Account', - icon: 'user', - defaultExpanded: true, - }, - { - title: 'Settings', - icon: 'settings', - defaultExpanded: false, - }, - ], - allowMultiple: true, - }, - }; - - expect(() => AccordionComponentSchema.parse(accordion)).not.toThrow(); - }); -}); - -describe('ModalComponentSchema', () => { - it('should accept minimal modal', () => { - const modal = { - type: 'modal' as const, - }; - - expect(() => ModalComponentSchema.parse(modal)).not.toThrow(); - }); - - it('should accept modal with all properties', () => { - const modal = { - type: 'modal' as const, - props: { - title: 'Confirm Delete', - size: 'medium' as const, - closeOnOverlay: true, - showClose: true, - }, - }; - - expect(() => ModalComponentSchema.parse(modal)).not.toThrow(); - }); - - it('should accept all modal sizes', () => { - const sizes = ['small', 'medium', 'large', 'full'] as const; - - sizes.forEach(size => { - const modal = { - type: 'modal' as const, - props: { size }, }; - expect(() => ModalComponentSchema.parse(modal)).not.toThrow(); - }); - }); - - it('should accept modal with children', () => { - const modal = { - type: 'modal' as const, - props: { title: 'Warning' }, - children: [ - { - type: 'alert' as const, - props: { message: 'This action cannot be undone' }, - }, - ], - }; - expect(() => ModalComponentSchema.parse(modal)).not.toThrow(); - }); -}); - -describe('DrawerComponentSchema', () => { - it('should accept minimal drawer', () => { - const drawer = { - type: 'drawer' as const, - }; - - expect(() => DrawerComponentSchema.parse(drawer)).not.toThrow(); - }); - - it('should accept all drawer positions', () => { - const positions = ['left', 'right', 'top', 'bottom'] as const; - - positions.forEach(position => { - const drawer = { - type: 'drawer' as const, - props: { position }, - }; - expect(() => DrawerComponentSchema.parse(drawer)).not.toThrow(); + expect(() => ComponentSchema.parse(form)).not.toThrow(); }); - }); - - it('should accept drawer with all properties', () => { - const drawer = { - type: 'drawer' as const, - props: { - title: 'Filters', - position: 'right' as const, - size: 'medium' as const, - closeOnOverlay: true, - }, - }; - - expect(() => DrawerComponentSchema.parse(drawer)).not.toThrow(); - }); -}); - -describe('TimelineComponentSchema', () => { - it('should accept minimal timeline', () => { - const timeline = { - type: 'timeline' as const, - props: { - items: [ - { title: 'Event 1' }, - { title: 'Event 2' }, - ], - }, - }; - - expect(() => TimelineComponentSchema.parse(timeline)).not.toThrow(); - }); - - it('should accept timeline with full event details', () => { - const timeline = { - type: 'timeline' as const, - props: { - items: [ - { - title: 'Project Started', - timestamp: '2025-01-01T00:00:00Z', - description: 'Project kickoff meeting', - icon: 'play', - }, - { - title: 'Milestone Reached', - timestamp: '2025-02-01T00:00:00Z', - description: 'Completed Phase 1', - icon: 'flag', - }, - ], - orientation: 'vertical' as const, - }, - }; - - expect(() => TimelineComponentSchema.parse(timeline)).not.toThrow(); - }); - - it('should accept timeline with nested content', () => { - const timeline = { - type: 'timeline' as const, - props: { - items: [ - { - title: 'Event with details', - content: { - type: 'card' as const, - props: { title: 'Event Content' }, - }, - }, - ], - }, - }; - expect(() => TimelineComponentSchema.parse(timeline)).not.toThrow(); - }); - - it('should accept both timeline orientations', () => { - const orientations = ['vertical', 'horizontal'] as const; - - orientations.forEach(orientation => { - const timeline = { - type: 'timeline' as const, + it('should accept table component tree', () => { + const table = { + type: 'table', props: { - items: [{ title: 'Event' }], - orientation, + columns: [ + { key: 'name', label: 'Name', sortable: true }, + { key: 'email', label: 'Email' }, + ], + dataSource: 'users', + pagination: { pageSize: 20 }, }, - }; - expect(() => TimelineComponentSchema.parse(timeline)).not.toThrow(); - }); - }); -}); - -describe('StepperComponentSchema', () => { - it('should accept minimal stepper', () => { - const stepper = { - type: 'stepper' as const, - props: { - steps: [ - { label: 'Step 1' }, - { label: 'Step 2' }, - ], - }, - }; - - expect(() => StepperComponentSchema.parse(stepper)).not.toThrow(); - }); - - it('should accept stepper with full step details', () => { - const stepper = { - type: 'stepper' as const, - props: { - steps: [ - { - label: 'Account Info', - description: 'Enter your account details', - icon: 'user', - }, - { - label: 'Payment', - description: 'Enter payment information', - icon: 'credit-card', - }, - { - label: 'Confirmation', - description: 'Review and confirm', - icon: 'check', - }, - ], - currentStep: 1, - orientation: 'horizontal' as const, - }, - }; - - expect(() => StepperComponentSchema.parse(stepper)).not.toThrow(); - }); - - it('should accept stepper with step content', () => { - const stepper = { - type: 'stepper' as const, - props: { - steps: [ + children: [ { - label: 'Step 1', - content: { - type: 'card' as const, - props: { title: 'Step 1 Content' }, - }, + type: 'table-toolbar', + props: { showSearch: true }, }, ], - }, - }; - - expect(() => StepperComponentSchema.parse(stepper)).not.toThrow(); - }); - - it('should accept both stepper orientations', () => { - const orientations = ['horizontal', 'vertical'] as const; - - orientations.forEach(orientation => { - const stepper = { - type: 'stepper' as const, - props: { - steps: [{ label: 'Step 1' }], - orientation, - }, - }; - expect(() => StepperComponentSchema.parse(stepper)).not.toThrow(); - }); - }); -}); - -describe('BreadcrumbComponentSchema', () => { - it('should accept minimal breadcrumb', () => { - const breadcrumb = { - type: 'breadcrumb' as const, - props: { - items: [ - { label: 'Home' }, - { label: 'Projects' }, - ], - }, - }; - - expect(() => BreadcrumbComponentSchema.parse(breadcrumb)).not.toThrow(); - }); - - it('should accept breadcrumb with hrefs', () => { - const breadcrumb = { - type: 'breadcrumb' as const, - props: { - items: [ - { label: 'Home', href: '/' }, - { label: 'Projects', href: '/projects' }, - { label: 'Project 1', href: '/projects/1' }, - ], - }, - }; - - expect(() => BreadcrumbComponentSchema.parse(breadcrumb)).not.toThrow(); - }); - - it('should accept breadcrumb with icons and separator', () => { - const breadcrumb = { - type: 'breadcrumb' as const, - props: { - items: [ - { label: 'Home', href: '/', icon: 'home' }, - { label: 'Settings', href: '/settings', icon: 'settings' }, - ], - separator: '/', - }, - }; - - expect(() => BreadcrumbComponentSchema.parse(breadcrumb)).not.toThrow(); - }); -}); - -describe('AlertComponentSchema', () => { - it('should accept minimal alert', () => { - const alert = { - type: 'alert' as const, - props: { - message: 'This is an alert', - }, - }; - - expect(() => AlertComponentSchema.parse(alert)).not.toThrow(); - }); - - it('should accept all alert variants', () => { - const variants = ['info', 'success', 'warning', 'error'] as const; - - variants.forEach(variant => { - const alert = { - type: 'alert' as const, - props: { - message: 'Test message', - variant, - }, - }; - expect(() => AlertComponentSchema.parse(alert)).not.toThrow(); - }); - }); - - it('should accept alert with all properties', () => { - const alert = { - type: 'alert' as const, - props: { - title: 'Success', - message: 'Your changes have been saved.', - variant: 'success' as const, - dismissible: true, - icon: 'check-circle', - }, - }; - - expect(() => AlertComponentSchema.parse(alert)).not.toThrow(); - }); -}); - -describe('BadgeComponentSchema', () => { - it('should accept minimal badge', () => { - const badge = { - type: 'badge' as const, - props: { - label: 'New', - }, - }; - - expect(() => BadgeComponentSchema.parse(badge)).not.toThrow(); - }); - - it('should accept all badge variants', () => { - const variants = ['primary', 'secondary', 'success', 'warning', 'error', 'info'] as const; - - variants.forEach(variant => { - const badge = { - type: 'badge' as const, - props: { - label: 'Test', - variant, - }, }; - expect(() => BadgeComponentSchema.parse(badge)).not.toThrow(); - }); - }); - it('should accept all badge sizes', () => { - const sizes = ['small', 'medium', 'large'] as const; - - sizes.forEach(size => { - const badge = { - type: 'badge' as const, - props: { - label: 'Test', - size, - }, - }; - expect(() => BadgeComponentSchema.parse(badge)).not.toThrow(); + expect(() => ComponentSchema.parse(table)).not.toThrow(); }); - }); - it('should accept badge with icon', () => { - const badge = { - type: 'badge' as const, - props: { - label: 'Premium', - variant: 'primary' as const, - icon: 'star', - }, - }; - - expect(() => BadgeComponentSchema.parse(badge)).not.toThrow(); - }); -}); - -describe('TooltipComponentSchema', () => { - it('should accept minimal tooltip', () => { - const tooltip = { - type: 'tooltip' as const, - props: { - content: 'Tooltip text', - }, - }; - - expect(() => TooltipComponentSchema.parse(tooltip)).not.toThrow(); - }); - - it('should accept all tooltip positions', () => { - const positions = ['top', 'bottom', 'left', 'right'] as const; - - positions.forEach(position => { - const tooltip = { - type: 'tooltip' as const, + it('should accept navigation menu tree', () => { + const menu = { + type: 'menu', props: { - content: 'Test tooltip', - position, - }, - }; - expect(() => TooltipComponentSchema.parse(tooltip)).not.toThrow(); - }); - }); - - it('should accept tooltip with delay', () => { - const tooltip = { - type: 'tooltip' as const, - props: { - content: 'Delayed tooltip', - delay: 500, - }, - }; - - expect(() => TooltipComponentSchema.parse(tooltip)).not.toThrow(); - }); - - it('should accept tooltip with children', () => { - const tooltip = { - type: 'tooltip' as const, - props: { - content: 'Hover for info', - }, - children: [ - { - type: 'badge' as const, - props: { label: 'Hover me' }, - }, - ], - }; - - expect(() => TooltipComponentSchema.parse(tooltip)).not.toThrow(); - }); -}); - -describe('PopoverComponentSchema', () => { - it('should accept minimal popover', () => { - const popover = { - type: 'popover' as const, - }; - - expect(() => PopoverComponentSchema.parse(popover)).not.toThrow(); - }); - - it('should accept all popover trigger types', () => { - const triggers = ['click', 'hover'] as const; - - triggers.forEach(trigger => { - const popover = { - type: 'popover' as const, - props: { trigger }, - }; - expect(() => PopoverComponentSchema.parse(popover)).not.toThrow(); - }); - }); - - it('should accept all popover positions', () => { - const positions = ['top', 'bottom', 'left', 'right'] as const; - - positions.forEach(position => { - const popover = { - type: 'popover' as const, - props: { position }, - }; - expect(() => PopoverComponentSchema.parse(popover)).not.toThrow(); - }); - }); - - it('should accept popover with all properties', () => { - const popover = { - type: 'popover' as const, - props: { - title: 'More Options', - trigger: 'click' as const, - position: 'bottom' as const, - closeOnOutsideClick: true, - }, - }; - - expect(() => PopoverComponentSchema.parse(popover)).not.toThrow(); - }); - - it('should accept popover with children', () => { - const popover = { - type: 'popover' as const, - props: { title: 'Actions' }, - children: [ - { - type: 'card' as const, - props: { title: 'Popover Content' }, + mode: 'vertical', + theme: 'dark', }, - ], - }; - - expect(() => PopoverComponentSchema.parse(popover)).not.toThrow(); - }); -}); - -describe('Real-World Component Examples', () => { - it('should accept complex nested dashboard layout', () => { - const dashboard = { - type: 'card' as const, - props: { - title: 'Dashboard', - }, - children: [ - { - type: 'tabs' as const, - props: { - tabs: [ - { - label: 'Overview', - icon: 'home', - content: { - type: 'card' as const, - children: [ - { - type: 'alert' as const, - props: { - message: 'Welcome back!', - variant: 'info' as const, - }, - }, - { - type: 'timeline' as const, - props: { - items: [ - { - title: 'Activity 1', - timestamp: '2025-01-22T12:00:00Z', - }, - ], - }, - }, - ], - }, - }, - { - label: 'Settings', - icon: 'settings', - content: { - type: 'accordion' as const, - props: { - items: [ - { - title: 'Profile', - icon: 'user', - }, - ], - }, - }, - }, - ], + children: [ + { + type: 'menu-item', + props: { + key: 'dashboard', + label: 'Dashboard', + icon: 'home', + }, }, - }, - ], - }; - - expect(() => ComponentSchema.parse(dashboard)).not.toThrow(); - }); - - it('should accept wizard flow with stepper', () => { - const wizard = { - type: 'modal' as const, - props: { - title: 'Setup Wizard', - size: 'large' as const, - }, - children: [ - { - type: 'stepper' as const, - props: { - steps: [ + { + type: 'menu-submenu', + props: { + key: 'settings', + label: 'Settings', + }, + children: [ { - label: 'Account', - content: { - type: 'card' as const, - props: { title: 'Account Setup' }, + type: 'menu-item', + props: { + key: 'profile', + label: 'Profile', }, }, { - label: 'Payment', - content: { - type: 'card' as const, - props: { title: 'Payment Details' }, + type: 'menu-item', + props: { + key: 'preferences', + label: 'Preferences', }, }, ], - currentStep: 0, }, - }, - ], - }; + ], + }; - expect(() => ComponentSchema.parse(wizard)).not.toThrow(); - }); + expect(() => ComponentSchema.parse(menu)).not.toThrow(); + }); - it('should accept card with badge and tooltip', () => { - const card = { - type: 'card' as const, - props: { - title: 'Premium Feature', - subtitle: 'Unlock advanced capabilities', - }, - children: [ - { - type: 'tooltip' as const, - props: { - content: 'Only for premium users', - }, - children: [ - { - type: 'badge' as const, - props: { - label: 'Premium', - variant: 'primary' as const, - icon: 'star', - }, - }, - ], + it('should accept modal with complex content', () => { + const modal = { + type: 'modal', + props: { + title: 'Confirm Action', + size: 'medium', + visible: true, }, - ], - }; - - expect(() => ComponentSchema.parse(card)).not.toThrow(); - }); - - it('should accept notification drawer with timeline', () => { - const drawer = { - type: 'drawer' as const, - props: { - title: 'Notifications', - position: 'right' as const, - size: 'medium' as const, - }, - children: [ - { - type: 'timeline' as const, - props: { - items: [ + children: [ + { + type: 'alert', + props: { + type: 'warning', + message: 'This action cannot be undone', + }, + }, + { + type: 'div', + children: [ { - title: 'New message', - timestamp: '2025-01-22T10:30:00Z', - icon: 'mail', - content: { - type: 'card' as const, - props: { title: 'Message details' }, - }, + type: 'button', + props: { label: 'Cancel', variant: 'secondary' }, + events: { onClick: () => {} }, }, { - title: 'Task completed', - timestamp: '2025-01-22T09:00:00Z', - icon: 'check', + type: 'button', + props: { label: 'Confirm', variant: 'primary' }, + events: { onClick: () => {} }, }, ], - orientation: 'vertical' as const, }, - }, - ], - }; - - expect(() => ComponentSchema.parse(drawer)).not.toThrow(); - }); -}); - -describe('Enterprise Components', () => { - describe('TableComponentSchema', () => { - it('should accept table with columns', () => { - const table = { - type: 'table' as const, - props: { - columns: [ - { key: 'name', label: 'Name', sortable: true }, - { key: 'email', label: 'Email', filterable: true }, - ], - dataSource: 'users', - pagination: { pageSize: 20 }, - }, - }; - - expect(() => TableComponentSchema.parse(table)).not.toThrow(); - }); - }); - - describe('FormComponentSchema', () => { - it('should accept form with fields', () => { - const form = { - type: 'form' as const, - props: { - layout: 'vertical' as const, - fields: [ - { name: 'username', label: 'Username', type: 'input', required: true }, - ], - submitButton: { label: 'Submit', variant: 'primary' as const }, - }, - }; - - expect(() => FormComponentSchema.parse(form)).not.toThrow(); - }); - }); - - describe('MenuComponentSchema', () => { - it('should accept menu with nested items', () => { - const menu = { - type: 'menu' as const, - props: { - items: [ - { key: 'dashboard', label: 'Dashboard', icon: 'home' }, - { - key: 'users', - label: 'Users', - children: [{ key: 'users-list', label: 'User List' }], - }, - ], - mode: 'vertical' as const, - }, - }; - - expect(() => MenuComponentSchema.parse(menu)).not.toThrow(); - }); - }); - - describe('ButtonComponentSchema', () => { - it('should accept button with all properties', () => { - const button = { - type: 'button' as const, - props: { - label: 'Submit', - variant: 'primary' as const, - icon: 'check', - size: 'large' as const, - loading: false, - }, - }; - - expect(() => ButtonComponentSchema.parse(button)).not.toThrow(); - }); - }); - - describe('InputComponentSchema', () => { - it('should accept input with properties', () => { - const input = { - type: 'input' as const, - props: { - type: 'email' as const, - placeholder: 'Enter email', - maxLength: 100, - allowClear: true, - }, - }; - - expect(() => InputComponentSchema.parse(input)).not.toThrow(); - }); - }); - - describe('SelectComponentSchema', () => { - it('should accept select with options', () => { - const select = { - type: 'select' as const, - props: { - options: [ - { label: 'Option 1', value: '1' }, - { label: 'Option 2', value: '2' }, - ], - multiple: true, - searchable: true, - }, - }; - - expect(() => SelectComponentSchema.parse(select)).not.toThrow(); - }); - }); - - describe('ListComponentSchema', () => { - it('should accept list with configuration', () => { - const list = { - type: 'list' as const, - props: { - dataSource: 'tasks', - itemLayout: 'horizontal' as const, - bordered: true, - }, + ], }; - expect(() => ListComponentSchema.parse(list)).not.toThrow(); + expect(() => ComponentSchema.parse(modal)).not.toThrow(); }); }); - describe('TreeComponentSchema', () => { - it('should accept tree with data', () => { - const tree = { - type: 'tree' as const, + describe('Prop Flexibility', () => { + it('should accept unknown/custom props without validation', () => { + const component = { + type: 'custom-component', props: { - treeData: [ - { - title: 'Parent', - key: '0', - children: [{ title: 'Child', key: '0-0' }], + customField1: 'value', + customField2: 123, + customField3: true, + customField4: ['array', 'values'], + customField5: { + nested: { + deeply: 'works', }, - ], - checkable: true, - }, - }; - - expect(() => TreeComponentSchema.parse(tree)).not.toThrow(); - }); - }); - - describe('ProgressComponentSchema', () => { - it('should accept progress with percent', () => { - const progress = { - type: 'progress' as const, - props: { - percent: 75, - type: 'circle' as const, - status: 'active' as const, + }, }, }; - expect(() => ProgressComponentSchema.parse(progress)).not.toThrow(); + expect(() => ComponentSchema.parse(component)).not.toThrow(); }); - it('should reject invalid percent', () => { - const progress = { - type: 'progress' as const, - props: { percent: 150 }, + it('should accept components without props', () => { + const component = { + type: 'divider', }; - expect(() => ProgressComponentSchema.parse(progress)).toThrow(); + expect(() => ComponentSchema.parse(component)).not.toThrow(); }); }); - describe('PaginationComponentSchema', () => { - it('should accept pagination with configuration', () => { - const pagination = { - type: 'pagination' as const, - props: { - total: 100, - pageSize: 20, - showSizeChanger: true, + describe('Style and Events', () => { + it('should accept component with both style and events', () => { + const component = { + type: 'interactive-card', + props: { title: 'Click me' }, + style: { + cursor: 'pointer', + border: '1px solid #ccc', }, - }; - - expect(() => PaginationComponentSchema.parse(pagination)).not.toThrow(); - }); - }); - - describe('UploadComponentSchema', () => { - it('should accept upload with configuration', () => { - const upload = { - type: 'upload' as const, - props: { - action: '/api/upload', - accept: '.jpg,.png', - multiple: true, - maxSize: 5242880, + events: { + onClick: () => {}, + onMouseEnter: () => {}, }, - }; - - expect(() => UploadComponentSchema.parse(upload)).not.toThrow(); - }); - }); - - describe('Enterprise Component Composition', () => { - it('should accept form with input and select', () => { - const form = { - type: 'form' as const, children: [ - { type: 'input' as const, props: { placeholder: 'Name' } }, { - type: 'select' as const, - props: { - options: [{ label: 'Active', value: 'active' }], - }, + type: 'text', + props: { content: 'Interactive content' }, }, ], }; - expect(() => ComponentSchema.parse(form)).not.toThrow(); - }); - - it('should accept dashboard with table', () => { - const dashboard = { - type: 'card' as const, - props: { title: 'Dashboard' }, - children: [ - { - type: 'table' as const, - props: { - columns: [{ key: 'name', label: 'Name' }], - }, - }, - ], - }; - - expect(() => ComponentSchema.parse(dashboard)).not.toThrow(); + expect(() => ComponentSchema.parse(component)).not.toThrow(); }); }); }); @@ -1377,23 +365,9 @@ describe('Component Factory', () => { expect(component.props?.title).toBe('Test Card'); }); - it('should validate component via factory', () => { - expect(() => - Component.create({ - type: 'invalid' as any, - }) - ).toThrow(); - - expect(() => - Component.create({ - type: 'card', - }) - ).not.toThrow(); - }); - it('should create nested component via factory', () => { const component = Component.create({ - type: 'card', + type: 'container', children: [ { type: 'badge', @@ -1405,4 +379,13 @@ describe('Component Factory', () => { expect(component.children).toHaveLength(1); expect(component.children![0].type).toBe('badge'); }); + + it('should validate structure via factory', () => { + // Missing required 'type' field + expect(() => + Component.create({ + props: { title: 'No type' }, + } as any) + ).toThrow(); + }); }); diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index 4d39aef..6a32b83 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -1,141 +1,29 @@ import { z } from 'zod'; /** - * Component Type Enum + * Component Schema * - * Defines all reusable UI component types available in the ObjectStack UI system. - * These components can be composed together to build complex user interfaces for - * enterprise management software. + * Defines a generic, implementation-agnostic structure for describing UI component trees. * - * Categories: - * - Layout: card, tabs, accordion, modal, drawer, container, divider, space, grid, flex - * - Navigation: breadcrumb, stepper, menu, sidebar, pagination, dropdown - * - Data Display: table, list, tree, description, statistic, tag, collapse, carousel, image, avatar, calendar_view - * - Data Entry: form, input, select, checkbox, radio, switch, slider, date_picker, time_picker, upload, autocomplete, cascader, transfer, color_picker, rate - * - Feedback: alert, message, notification, progress, skeleton, spin, result, empty - * - Interaction: button, button_group, icon_button, split_button - * - Overlay: tooltip, popover, dialog, confirm - * - Other: badge, timeline, steps, anchor, back_top, watermark, qrcode - */ -export const ComponentType = z.enum([ - // Layout Components - 'card', - 'tabs', - 'accordion', - 'modal', - 'drawer', - 'container', - 'divider', - 'space', - 'grid', - 'flex', - - // Navigation Components - 'breadcrumb', - 'stepper', - 'menu', - 'sidebar', - 'pagination', - 'dropdown', - - // Data Display Components - 'table', - 'list', - 'tree', - 'description', - 'statistic', - 'tag', - 'collapse', - 'carousel', - 'image', - 'avatar', - 'calendar_view', - - // Data Entry Components - 'form', - 'input', - 'select', - 'checkbox', - 'radio', - 'switch', - 'slider', - 'date_picker', - 'time_picker', - 'upload', - 'autocomplete', - 'cascader', - 'transfer', - 'color_picker', - 'rate', - - // Feedback Components - 'alert', - 'message', - 'notification', - 'progress', - 'skeleton', - 'spin', - 'result', - 'empty', - - // Interaction Components - 'button', - 'button_group', - 'icon_button', - 'split_button', - - // Overlay Components - 'tooltip', - 'popover', - 'dialog', - 'confirm', - - // Timeline & Process Components - 'badge', - 'timeline', - 'steps', - - // Other Components - 'anchor', - 'back_top', - 'watermark', - 'qrcode', -]); - -/** - * Base Component Schema Definition - * Internal schema object that can be extended for specialized components - */ -const BaseComponentSchema = z.object({ - /** Component type identifier */ - type: ComponentType.describe('Component type'), - - /** Component-specific properties */ - props: z.record(z.any()).optional().describe('Component properties'), - - /** Event handlers */ - events: z.record(z.function()).optional().describe('Event handlers'), - - /** Custom CSS styles */ - style: z.record(z.string()).optional().describe('Custom styles'), -}); - -/** - * Base Component Schema + * **Design Philosophy:** + * The protocol layer defines "how to describe a UI tree", not "what properties a Button has". + * This allows UI implementations to evolve independently while maintaining structural compatibility. * - * Defines the structure for reusable UI components with support for: - * - Component nesting via lazy-loaded children - * - Event binding through event handlers - * - Custom styling via style properties - * - Flexible props configuration + * **Core Properties:** + * - `type`: Component type identifier (extensible string) + * - `props`: Arbitrary component properties (implementation-defined) + * - `children`: Recursive nesting of child components + * - `events`: Event handler bindings + * - `style`: Custom CSS styling * * @example * ```typescript - * const card: Component = { + * const component: Component = { * type: 'card', * props: { * title: 'User Profile', - * subtitle: 'Account Details' + * subtitle: 'Account Details', + * customProp: 'value' * }, * children: [ * { @@ -146,885 +34,40 @@ const BaseComponentSchema = z.object({ * style: { * padding: '16px', * borderRadius: '8px' + * }, + * events: { + * onClick: () => {} * } * } * ``` */ export const ComponentSchema: z.ZodType<{ - type: z.infer; - props?: Record; + type: string; + props?: Record; children?: Array; events?: Record; style?: Record; -}> = BaseComponentSchema.extend({ - /** Nested child components */ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Card Component Schema - * - * A container component that displays content in a card layout. - * Commonly used for displaying grouped information with optional header, image, and actions. - * - * @example - * ```typescript - * const card: CardComponent = { - * type: 'card', - * props: { - * title: 'Project Overview', - * subtitle: 'Q4 2025', - * image: 'https://example.com/project.jpg', - * actions: [ - * { label: 'Edit', onClick: () => {} }, - * { label: 'Delete', onClick: () => {} } - * ] - * }, - * children: [ - * { type: 'alert', props: { message: 'Project on track', variant: 'success' } } - * ] - * } - * ``` - */ -export const CardComponentSchema = BaseComponentSchema.extend({ - type: z.literal('card'), - props: z.object({ - /** Card title */ - title: z.string().optional().describe('Card title'), - - /** Card subtitle */ - subtitle: z.string().optional().describe('Card subtitle'), - - /** Header image URL */ - image: z.string().url().optional().describe('Card image URL'), - - /** Action buttons */ - actions: z.array(z.any()).optional().describe('Card action buttons'), - }).optional(), -}).extend({ - /** Nested child components */ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Tabs Component Schema - * - * A tabbed navigation component that allows switching between different content panels. - * - * @example - * ```typescript - * const tabs: TabsComponent = { - * type: 'tabs', - * props: { - * tabs: [ - * { - * label: 'Overview', - * icon: 'home', - * content: { type: 'card', props: { title: 'Overview Content' } } - * }, - * { - * label: 'Settings', - * icon: 'settings', - * content: { type: 'card', props: { title: 'Settings Content' } } - * } - * ], - * defaultTab: 0 - * } - * } - * ``` - */ -export const TabsComponentSchema = BaseComponentSchema.extend({ - type: z.literal('tabs'), - props: z.object({ - /** Tab definitions */ - tabs: z.array(z.object({ - /** Tab label */ - label: z.string().describe('Tab label'), - - /** Tab icon (Lucide icon name) */ - icon: z.string().optional().describe('Tab icon'), - - /** Tab content component */ - content: z.lazy(() => ComponentSchema).optional().describe('Tab content'), - })).describe('Tab items'), - - /** Default active tab index (0-based) */ - defaultTab: z.number().int().min(0).optional().describe('Default active tab index'), - }), -}).extend({ - /** Nested child components */ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Accordion Component Schema - * - * An expandable/collapsible panel component for showing and hiding content. - * - * @example - * ```typescript - * const accordion: AccordionComponent = { - * type: 'accordion', - * props: { - * items: [ - * { - * title: 'Section 1', - * content: { type: 'card', props: { title: 'Content 1' } } - * } - * ], - * allowMultiple: false - * } - * } - * ``` - */ -export const AccordionComponentSchema = BaseComponentSchema.extend({ - type: z.literal('accordion'), - props: z.object({ - /** Accordion items */ - items: z.array(z.object({ - /** Section title */ - title: z.string().describe('Section title'), - - /** Section icon */ - icon: z.string().optional().describe('Section icon'), - - /** Section content */ - content: z.lazy(() => ComponentSchema).optional().describe('Section content'), - - /** Initially expanded state */ - defaultExpanded: z.boolean().optional().describe('Initially expanded'), - })).describe('Accordion items'), - - /** Allow multiple sections to be open simultaneously */ - allowMultiple: z.boolean().optional().describe('Allow multiple open sections'), - }), -}).extend({ - /** Nested child components */ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Modal Component Schema - * - * A dialog overlay component that displays content in a centered popup. - * - * @example - * ```typescript - * const modal: ModalComponent = { - * type: 'modal', - * props: { - * title: 'Confirm Action', - * size: 'medium', - * closeOnOverlay: true - * }, - * children: [ - * { type: 'alert', props: { message: 'Are you sure?' } } - * ] - * } - * ``` - */ -export const ModalComponentSchema = BaseComponentSchema.extend({ - type: z.literal('modal'), - props: z.object({ - /** Modal title */ - title: z.string().optional().describe('Modal title'), - - /** Modal size variant */ - size: z.enum(['small', 'medium', 'large', 'full']).optional().describe('Modal size'), - - /** Close on overlay click */ - closeOnOverlay: z.boolean().optional().describe('Close on overlay click'), - - /** Show close button */ - showClose: z.boolean().optional().describe('Show close button'), - }).optional(), -}).extend({ - /** Nested child components */ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Drawer Component Schema - * - * A slide-in panel component that appears from the edge of the screen. - * - * @example - * ```typescript - * const drawer: DrawerComponent = { - * type: 'drawer', - * props: { - * title: 'Filters', - * position: 'right', - * size: 'medium' - * }, - * children: [ - * { type: 'card', props: { title: 'Filter Options' } } - * ] - * } - * ``` - */ -export const DrawerComponentSchema = BaseComponentSchema.extend({ - type: z.literal('drawer'), - props: z.object({ - /** Drawer title */ - title: z.string().optional().describe('Drawer title'), - - /** Position from which drawer slides in */ - position: z.enum(['left', 'right', 'top', 'bottom']).optional().describe('Drawer position'), - - /** Drawer size */ - size: z.enum(['small', 'medium', 'large', 'full']).optional().describe('Drawer size'), - - /** Close on overlay click */ - closeOnOverlay: z.boolean().optional().describe('Close on overlay click'), - }).optional(), -}).extend({ - /** Nested child components */ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Timeline Component Schema - * - * A chronological display of events or activities. - * - * @example - * ```typescript - * const timeline: TimelineComponent = { - * type: 'timeline', - * props: { - * items: [ - * { - * title: 'Project Started', - * timestamp: '2025-01-01T00:00:00Z', - * description: 'Project kickoff meeting', - * icon: 'play' - * } - * ], - * orientation: 'vertical' - * } - * } - * ``` - */ -export const TimelineComponentSchema = BaseComponentSchema.extend({ - type: z.literal('timeline'), - props: z.object({ - /** Timeline items */ - items: z.array(z.object({ - /** Event title */ - title: z.string().describe('Event title'), - - /** Event timestamp */ - timestamp: z.string().optional().describe('Event timestamp'), - - /** Event description */ - description: z.string().optional().describe('Event description'), - - /** Event icon */ - icon: z.string().optional().describe('Event icon'), - - /** Event content */ - content: z.lazy(() => ComponentSchema).optional().describe('Event content'), - })).describe('Timeline items'), - - /** Timeline orientation */ - orientation: z.enum(['vertical', 'horizontal']).optional().describe('Timeline orientation'), - }), -}).extend({ - /** Nested child components */ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Stepper Component Schema - * - * A step-by-step navigation component for multi-step processes. - * - * @example - * ```typescript - * const stepper: StepperComponent = { - * type: 'stepper', - * props: { - * steps: [ - * { label: 'Account Info', icon: 'user' }, - * { label: 'Payment', icon: 'credit-card' }, - * { label: 'Confirmation', icon: 'check' } - * ], - * currentStep: 0, - * orientation: 'horizontal' - * } - * } - * ``` - */ -export const StepperComponentSchema = BaseComponentSchema.extend({ - type: z.literal('stepper'), - props: z.object({ - /** Step definitions */ - steps: z.array(z.object({ - /** Step label */ - label: z.string().describe('Step label'), - - /** Step description */ - description: z.string().optional().describe('Step description'), - - /** Step icon */ - icon: z.string().optional().describe('Step icon'), - - /** Step content */ - content: z.lazy(() => ComponentSchema).optional().describe('Step content'), - })).describe('Stepper steps'), - - /** Current active step index (0-based) */ - currentStep: z.number().int().min(0).optional().describe('Current step index'), - - /** Stepper orientation */ - orientation: z.enum(['horizontal', 'vertical']).optional().describe('Stepper orientation'), - }), -}).extend({ - /** Nested child components */ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Breadcrumb Component Schema - * - * A navigation component showing the current page's location in the hierarchy. - * - * @example - * ```typescript - * const breadcrumb: BreadcrumbComponent = { - * type: 'breadcrumb', - * props: { - * items: [ - * { label: 'Home', href: '/' }, - * { label: 'Projects', href: '/projects' }, - * { label: 'Project 1', href: '/projects/1' } - * ], - * separator: '/' - * } - * } - * ``` - */ -export const BreadcrumbComponentSchema = BaseComponentSchema.extend({ - type: z.literal('breadcrumb'), - props: z.object({ - /** Breadcrumb items */ - items: z.array(z.object({ - /** Item label */ - label: z.string().describe('Breadcrumb label'), - - /** Item link */ - href: z.string().optional().describe('Breadcrumb link'), - - /** Item icon */ - icon: z.string().optional().describe('Breadcrumb icon'), - })).describe('Breadcrumb items'), - - /** Separator between items */ - separator: z.string().optional().describe('Breadcrumb separator'), - }), -}).extend({ - /** Nested child components */ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Alert Component Schema - * - * A notification component for displaying important messages. - * - * @example - * ```typescript - * const alert: AlertComponent = { - * type: 'alert', - * props: { - * title: 'Success', - * message: 'Your changes have been saved.', - * variant: 'success', - * dismissible: true - * } - * } - * ``` - */ -export const AlertComponentSchema = BaseComponentSchema.extend({ - type: z.literal('alert'), - props: z.object({ - /** Alert title */ - title: z.string().optional().describe('Alert title'), - - /** Alert message */ - message: z.string().describe('Alert message'), - - /** Alert variant/severity */ - variant: z.enum(['info', 'success', 'warning', 'error']).optional().describe('Alert variant'), - - /** Allow dismissing the alert */ - dismissible: z.boolean().optional().describe('Dismissible alert'), - - /** Alert icon */ - icon: z.string().optional().describe('Alert icon'), - }), -}).extend({ - /** Nested child components */ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Badge Component Schema - * - * A small label component for highlighting status or counts. - * - * @example - * ```typescript - * const badge: BadgeComponent = { - * type: 'badge', - * props: { - * label: 'New', - * variant: 'primary', - * icon: 'star' - * } - * } - * ``` - */ -export const BadgeComponentSchema = BaseComponentSchema.extend({ - type: z.literal('badge'), - props: z.object({ - /** Badge label */ - label: z.string().describe('Badge label'), - - /** Badge variant/color scheme */ - variant: z.enum(['primary', 'secondary', 'success', 'warning', 'error', 'info']).optional().describe('Badge variant'), - - /** Badge icon */ - icon: z.string().optional().describe('Badge icon'), - - /** Badge size */ - size: z.enum(['small', 'medium', 'large']).optional().describe('Badge size'), - }), -}).extend({ - /** Nested child components */ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Tooltip Component Schema - * - * A small popup that appears on hover to provide additional context. - * - * @example - * ```typescript - * const tooltip: TooltipComponent = { - * type: 'tooltip', - * props: { - * content: 'Additional information', - * position: 'top' - * }, - * children: [ - * { type: 'badge', props: { label: 'Hover me' } } - * ] - * } - * ``` - */ -export const TooltipComponentSchema = BaseComponentSchema.extend({ - type: z.literal('tooltip'), - props: z.object({ - /** Tooltip content */ - content: z.string().describe('Tooltip content'), - - /** Tooltip position relative to trigger */ - position: z.enum(['top', 'bottom', 'left', 'right']).optional().describe('Tooltip position'), - - /** Delay before showing tooltip (ms) */ - delay: z.number().optional().describe('Show delay in milliseconds'), - }), -}).extend({ - /** Nested child components */ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Popover Component Schema - * - * A popup component that displays rich content on click or hover. - * - * @example - * ```typescript - * const popover: PopoverComponent = { - * type: 'popover', - * props: { - * title: 'More Options', - * trigger: 'click', - * position: 'bottom' - * }, - * children: [ - * { type: 'card', props: { title: 'Popover Content' } } - * ] - * } - * ``` - */ -export const PopoverComponentSchema = BaseComponentSchema.extend({ - type: z.literal('popover'), - props: z.object({ - /** Popover title */ - title: z.string().optional().describe('Popover title'), - - /** Trigger type */ - trigger: z.enum(['click', 'hover']).optional().describe('Trigger type'), - - /** Popover position */ - position: z.enum(['top', 'bottom', 'left', 'right']).optional().describe('Popover position'), - - /** Close on outside click */ - closeOnOutsideClick: z.boolean().optional().describe('Close on outside click'), - }).optional(), -}).extend({ - /** Nested child components */ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Table Component Schema - * - * A data table component for displaying structured data with sorting, filtering, and pagination. - * Essential for enterprise data management. - * - * @example - * ```typescript - * const table: TableComponent = { - * type: 'table', - * props: { - * columns: [ - * { key: 'name', label: 'Name', sortable: true }, - * { key: 'email', label: 'Email' }, - * { key: 'status', label: 'Status', filterable: true } - * ], - * dataSource: 'users', - * pagination: { pageSize: 20, showSizeChanger: true }, - * selection: { type: 'checkbox', selectedKeys: [] } - * } - * } - * ``` - */ -export const TableComponentSchema = BaseComponentSchema.extend({ - type: z.literal('table'), - props: z.object({ - /** Column definitions */ - columns: z.array(z.object({ - key: z.string().describe('Column key'), - label: z.string().describe('Column label'), - width: z.number().optional().describe('Column width'), - sortable: z.boolean().optional().describe('Enable sorting'), - filterable: z.boolean().optional().describe('Enable filtering'), - fixed: z.enum(['left', 'right']).optional().describe('Fixed column position'), - dataType: z.enum(['text', 'number', 'date', 'boolean', 'currency', 'percent']).optional().describe('Data type'), - })).describe('Table columns'), - - dataSource: z.string().optional().describe('Data source reference or object name'), - - pagination: z.object({ - pageSize: z.number().default(10).describe('Page size'), - showSizeChanger: z.boolean().optional().describe('Show page size changer'), - pageSizeOptions: z.array(z.number()).optional().describe('Page size options'), - }).optional().describe('Pagination configuration'), - - selection: z.object({ - type: z.enum(['checkbox', 'radio']).describe('Selection type'), - selectedKeys: z.array(z.string()).optional().describe('Selected row keys'), - }).optional().describe('Row selection configuration'), - - rowActions: z.array(z.any()).optional().describe('Actions for each row'), - size: z.enum(['small', 'medium', 'large']).optional().describe('Table size'), - bordered: z.boolean().optional().describe('Show borders'), - striped: z.boolean().optional().describe('Striped rows'), - }), -}).extend({ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Form Component Schema - * - * A form container for data entry with validation support. - * Critical for enterprise data management. - */ -export const FormComponentSchema = BaseComponentSchema.extend({ - type: z.literal('form'), - props: z.object({ - layout: z.enum(['horizontal', 'vertical', 'inline']).optional().describe('Form layout'), - - fields: z.array(z.object({ - name: z.string().describe('Field name'), - label: z.string().describe('Field label'), - type: z.string().describe('Field type'), - required: z.boolean().optional().describe('Required field'), - placeholder: z.string().optional().describe('Placeholder text'), - defaultValue: z.any().optional().describe('Default value'), - validation: z.record(z.any()).optional().describe('Validation rules'), - })).optional().describe('Form fields'), - - submitButton: z.object({ - label: z.string().describe('Button label'), - variant: z.enum(['primary', 'secondary', 'success', 'danger']).optional().describe('Button variant'), - }).optional().describe('Submit button configuration'), - - cancelButton: z.object({ - label: z.string().describe('Button label'), - variant: z.enum(['primary', 'secondary', 'success', 'danger']).optional().describe('Button variant'), - }).optional().describe('Cancel button configuration'), - - labelWidth: z.number().optional().describe('Label width'), - labelAlign: z.enum(['left', 'right']).optional().describe('Label alignment'), - }).optional(), -}).extend({ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Menu Component Schema - * - * Navigation menu component for application structure. - */ -export const MenuComponentSchema = BaseComponentSchema.extend({ - type: z.literal('menu'), - props: z.object({ - items: z.array(z.lazy(() => z.object({ - key: z.string().describe('Menu item key'), - label: z.string().describe('Menu item label'), - icon: z.string().optional().describe('Menu item icon'), - href: z.string().optional().describe('Link URL'), - disabled: z.boolean().optional().describe('Disabled state'), - children: z.array(z.any()).optional().describe('Submenu items'), - }))).describe('Menu items'), - - mode: z.enum(['horizontal', 'vertical', 'inline']).optional().describe('Menu mode'), - theme: z.enum(['light', 'dark']).optional().describe('Menu theme'), - defaultSelectedKeys: z.array(z.string()).optional().describe('Default selected keys'), - defaultOpenKeys: z.array(z.string()).optional().describe('Default opened keys'), - collapsible: z.boolean().optional().describe('Collapsible menu'), - collapsed: z.boolean().optional().describe('Collapsed state'), - }), -}).extend({ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Button Component Schema - * - * Interactive button component. - */ -export const ButtonComponentSchema = BaseComponentSchema.extend({ - type: z.literal('button'), - props: z.object({ - label: z.string().describe('Button label'), - variant: z.enum(['primary', 'secondary', 'success', 'warning', 'danger', 'text', 'link']).optional().describe('Button variant'), - icon: z.string().optional().describe('Button icon'), - iconPosition: z.enum(['left', 'right']).optional().describe('Icon position'), - size: z.enum(['small', 'medium', 'large']).optional().describe('Button size'), - loading: z.boolean().optional().describe('Loading state'), - disabled: z.boolean().optional().describe('Disabled state'), - block: z.boolean().optional().describe('Block button (full width)'), - danger: z.boolean().optional().describe('Danger button'), - shape: z.enum(['default', 'circle', 'round']).optional().describe('Button shape'), - }), -}).extend({ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Input Component Schema - * - * Text input field for data entry. - */ -export const InputComponentSchema = BaseComponentSchema.extend({ - type: z.literal('input'), - props: z.object({ - type: z.enum(['text', 'password', 'email', 'number', 'tel', 'url', 'search', 'textarea']).optional().describe('Input type'), - placeholder: z.string().optional().describe('Placeholder text'), - defaultValue: z.string().optional().describe('Default value'), - size: z.enum(['small', 'medium', 'large']).optional().describe('Input size'), - disabled: z.boolean().optional().describe('Disabled state'), - readonly: z.boolean().optional().describe('Read-only state'), - maxLength: z.number().optional().describe('Maximum length'), - showCount: z.boolean().optional().describe('Show character count'), - prefix: z.string().optional().describe('Prefix icon'), - suffix: z.string().optional().describe('Suffix icon'), - allowClear: z.boolean().optional().describe('Allow clear button'), - rows: z.number().optional().describe('Rows for textarea type'), - }).optional(), -}).extend({ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Select Component Schema - * - * Dropdown select component for choosing from options. - */ -export const SelectComponentSchema = BaseComponentSchema.extend({ - type: z.literal('select'), - props: z.object({ - options: z.array(z.object({ - label: z.string().describe('Option label'), - value: z.any().describe('Option value'), - disabled: z.boolean().optional().describe('Disabled option'), - icon: z.string().optional().describe('Option icon'), - })).describe('Select options'), - - placeholder: z.string().optional().describe('Placeholder text'), - defaultValue: z.any().optional().describe('Default value'), - multiple: z.boolean().optional().describe('Multiple selection'), - searchable: z.boolean().optional().describe('Allow search'), - allowClear: z.boolean().optional().describe('Allow clear'), - size: z.enum(['small', 'medium', 'large']).optional().describe('Select size'), - disabled: z.boolean().optional().describe('Disabled state'), - loading: z.boolean().optional().describe('Loading state'), - }), -}).extend({ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * List Component Schema - * - * List component for displaying a series of items. - */ -export const ListComponentSchema = BaseComponentSchema.extend({ - type: z.literal('list'), - props: z.object({ - dataSource: z.string().optional().describe('Data source reference'), - itemLayout: z.enum(['horizontal', 'vertical']).optional().describe('Item layout'), - bordered: z.boolean().optional().describe('Show borders'), - size: z.enum(['small', 'medium', 'large']).optional().describe('List size'), - split: z.boolean().optional().describe('Split items with divider'), - loading: z.boolean().optional().describe('Loading state'), - pagination: z.object({ - pageSize: z.number().describe('Page size'), - total: z.number().optional().describe('Total items'), - }).optional().describe('Pagination configuration'), - }).optional(), -}).extend({ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Tree Component Schema - * - * Hierarchical tree structure component. - */ -export const TreeComponentSchema = BaseComponentSchema.extend({ - type: z.literal('tree'), - props: z.object({ - treeData: z.array(z.lazy(() => z.object({ - title: z.string().describe('Node title'), - key: z.string().describe('Node key'), - icon: z.string().optional().describe('Node icon'), - disabled: z.boolean().optional().describe('Disabled node'), - children: z.array(z.any()).optional().describe('Child nodes'), - }))).optional().describe('Tree data'), - - checkable: z.boolean().optional().describe('Show checkbox on nodes'), - selectable: z.boolean().optional().describe('Selectable nodes'), - multiple: z.boolean().optional().describe('Multiple selection'), - defaultExpandedKeys: z.array(z.string()).optional().describe('Default expanded keys'), - defaultSelectedKeys: z.array(z.string()).optional().describe('Default selected keys'), - defaultCheckedKeys: z.array(z.string()).optional().describe('Default checked keys'), - showLine: z.boolean().optional().describe('Show tree line'), - showIcon: z.boolean().optional().describe('Show node icon'), - }).optional(), -}).extend({ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Progress Component Schema - * - * Progress indicator for showing completion status. - */ -export const ProgressComponentSchema = BaseComponentSchema.extend({ - type: z.literal('progress'), - props: z.object({ - percent: z.number().min(0).max(100).describe('Progress percentage'), - type: z.enum(['line', 'circle', 'dashboard']).optional().describe('Progress type'), - status: z.enum(['normal', 'active', 'success', 'exception']).optional().describe('Progress status'), - showInfo: z.boolean().optional().describe('Show progress info'), - strokeWidth: z.number().optional().describe('Stroke width'), - strokeColor: z.string().optional().describe('Stroke color'), - }), -}).extend({ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Pagination Component Schema - * - * Pagination control for navigating through pages. - */ -export const PaginationComponentSchema = BaseComponentSchema.extend({ - type: z.literal('pagination'), - props: z.object({ - total: z.number().describe('Total items'), - pageSize: z.number().default(10).describe('Items per page'), - current: z.number().default(1).describe('Current page'), - showSizeChanger: z.boolean().optional().describe('Show size changer'), - pageSizeOptions: z.array(z.number()).optional().describe('Page size options'), - showQuickJumper: z.boolean().optional().describe('Show quick jumper'), - showTotal: z.boolean().optional().describe('Show total items'), - simple: z.boolean().optional().describe('Simple mode'), - size: z.enum(['small', 'medium', 'large']).optional().describe('Pagination size'), - }), -}).extend({ - children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), -}); - -/** - * Upload Component Schema - * - * File upload component. - */ -export const UploadComponentSchema = BaseComponentSchema.extend({ - type: z.literal('upload'), - props: z.object({ - action: z.string().optional().describe('Upload URL'), - accept: z.string().optional().describe('Accepted file types'), - multiple: z.boolean().optional().describe('Multiple file upload'), - maxSize: z.number().optional().describe('Max file size'), - maxCount: z.number().optional().describe('Max file count'), - listType: z.enum(['text', 'picture', 'picture-card']).optional().describe('Upload list type'), - showUploadList: z.boolean().optional().describe('Show upload list'), - disabled: z.boolean().optional().describe('Disabled state'), - }).optional(), -}).extend({ +}> = z.object({ + /** Component type identifier - extensible string to allow custom components */ + type: z.string().describe('Component type identifier'), + + /** Component-specific properties - implementation-defined */ + props: z.record(z.unknown()).optional().describe('Component properties'), + + /** Nested child components - recursive structure */ children: z.lazy(() => z.array(ComponentSchema)).optional().describe('Child components'), + + /** Event handler bindings */ + events: z.record(z.function()).optional().describe('Event handlers'), + + /** Custom CSS styles */ + style: z.record(z.string()).optional().describe('Custom styles'), }); /** * TypeScript Type Exports */ -export type ComponentTypeEnum = z.infer; export type Component = z.infer; -export type CardComponent = z.infer; -export type TabsComponent = z.infer; -export type AccordionComponent = z.infer; -export type ModalComponent = z.infer; -export type DrawerComponent = z.infer; -export type TimelineComponent = z.infer; -export type StepperComponent = z.infer; -export type BreadcrumbComponent = z.infer; -export type AlertComponent = z.infer; -export type BadgeComponent = z.infer; -export type TooltipComponent = z.infer; -export type PopoverComponent = z.infer; -export type TableComponent = z.infer; -export type FormComponent = z.infer; -export type MenuComponent = z.infer; -export type ButtonComponent = z.infer; -export type InputComponent = z.infer; -export type SelectComponent = z.infer; -export type ListComponent = z.infer; -export type TreeComponent = z.infer; -export type ProgressComponent = z.infer; -export type PaginationComponent = z.infer; -export type UploadComponent = z.infer; /** * Component Factory Helper From d5a98d8c470f7eb07f5e0be923953b9f784b371e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 16:15:39 +0000 Subject: [PATCH 6/6] Add id and meta fields to Component schema for plugin systems and editors Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- content/docs/references/ui/Component.mdx | 2 + packages/spec/json-schema/Component.json | 9 +++ packages/spec/src/ui/component.test.ts | 96 ++++++++++++++++++++++++ packages/spec/src/ui/component.zod.ts | 18 +++++ 4 files changed, 125 insertions(+) diff --git a/content/docs/references/ui/Component.mdx b/content/docs/references/ui/Component.mdx index 60c8362..ccef527 100644 --- a/content/docs/references/ui/Component.mdx +++ b/content/docs/references/ui/Component.mdx @@ -7,8 +7,10 @@ description: Component Schema Reference | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | +| **id** | `string` | optional | Component instance identifier | | **type** | `string` | ✅ | Component type identifier | | **props** | `Record` | optional | Component properties | | **children** | `any[]` | optional | Child components | | **events** | `Record` | optional | Event handlers | | **style** | `Record` | optional | Custom styles | +| **meta** | `Record` | optional | Editor/designer metadata | diff --git a/packages/spec/json-schema/Component.json b/packages/spec/json-schema/Component.json index 83a8990..aa0b001 100644 --- a/packages/spec/json-schema/Component.json +++ b/packages/spec/json-schema/Component.json @@ -4,6 +4,10 @@ "Component": { "type": "object", "properties": { + "id": { + "type": "string", + "description": "Component instance identifier" + }, "type": { "type": "string", "description": "Component type identifier" @@ -29,6 +33,11 @@ "type": "string" }, "description": "Custom styles" + }, + "meta": { + "type": "object", + "additionalProperties": {}, + "description": "Editor/designer metadata" } }, "required": [ diff --git a/packages/spec/src/ui/component.test.ts b/packages/spec/src/ui/component.test.ts index 309f066..2fd6a71 100644 --- a/packages/spec/src/ui/component.test.ts +++ b/packages/spec/src/ui/component.test.ts @@ -328,6 +328,102 @@ describe('ComponentSchema', () => { }); }); + describe('ID and Meta Fields', () => { + it('should accept component with id', () => { + const component = { + id: 'user-profile-card', + type: 'card', + props: { title: 'Profile' }, + }; + + expect(() => ComponentSchema.parse(component)).not.toThrow(); + }); + + it('should accept component with meta data', () => { + const component = { + type: 'widget', + props: { data: [] }, + meta: { + x: 100, + y: 200, + locked: false, + label: 'Sales Chart', + bindingPath: 'dashboard.sales', + }, + }; + + expect(() => ComponentSchema.parse(component)).not.toThrow(); + }); + + it('should accept component with both id and meta', () => { + const component = { + id: 'editable-form-001', + type: 'form', + props: { action: '/submit' }, + meta: { + position: { x: 50, y: 100 }, + locked: true, + designerLabel: 'Contact Form', + }, + }; + + expect(() => ComponentSchema.parse(component)).not.toThrow(); + }); + + it('should accept nested components with ids', () => { + const component = { + id: 'dashboard', + type: 'container', + children: [ + { + id: 'header-section', + type: 'header', + props: { title: 'Dashboard' }, + }, + { + id: 'content-section', + type: 'content', + children: [ + { + id: 'widget-1', + type: 'chart', + props: { type: 'bar' }, + meta: { gridPosition: { row: 0, col: 0 } }, + }, + ], + }, + ], + }; + + expect(() => ComponentSchema.parse(component)).not.toThrow(); + }); + + it('should accept meta with complex nested data', () => { + const component = { + type: 'data-grid', + meta: { + editorConfig: { + allowResize: true, + minWidth: 200, + maxWidth: 1000, + }, + dataBinding: { + source: 'api.users', + filters: ['active', 'verified'], + transforms: [{ type: 'sort', field: 'name' }], + }, + layout: { + position: { x: 0, y: 0 }, + size: { width: 400, height: 300 }, + zIndex: 10, + }, + }, + }; + + expect(() => ComponentSchema.parse(component)).not.toThrow(); + }); + }); + describe('Style and Events', () => { it('should accept component with both style and events', () => { const component = { diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index 6a32b83..c0c01fd 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -10,15 +10,18 @@ import { z } from 'zod'; * This allows UI implementations to evolve independently while maintaining structural compatibility. * * **Core Properties:** + * - `id`: Unique identifier for component instance (optional, for rendering engines and diff algorithms) * - `type`: Component type identifier (extensible string) * - `props`: Arbitrary component properties (implementation-defined) * - `children`: Recursive nesting of child components * - `events`: Event handler bindings * - `style`: Custom CSS styling + * - `meta`: Metadata for editors/designers (non-rendering information) * * @example * ```typescript * const component: Component = { + * id: 'user-profile-card', * type: 'card', * props: { * title: 'User Profile', @@ -27,6 +30,7 @@ import { z } from 'zod'; * }, * children: [ * { + * id: 'premium-badge', * type: 'badge', * props: { label: 'Premium', variant: 'success' } * } @@ -37,17 +41,28 @@ import { z } from 'zod'; * }, * events: { * onClick: () => {} + * }, + * meta: { + * x: 100, + * y: 200, + * locked: false, + * bindingPath: 'user.profile' * } * } * ``` */ export const ComponentSchema: z.ZodType<{ + id?: string; type: string; props?: Record; children?: Array; events?: Record; style?: Record; + meta?: Record; }> = z.object({ + /** Unique identifier for component instance - used by rendering engines, diff algorithms, and editors */ + id: z.string().optional().describe('Component instance identifier'), + /** Component type identifier - extensible string to allow custom components */ type: z.string().describe('Component type identifier'), @@ -62,6 +77,9 @@ export const ComponentSchema: z.ZodType<{ /** Custom CSS styles */ style: z.record(z.string()).optional().describe('Custom styles'), + + /** Metadata for editors, designers, and plugin systems - not used in rendering */ + meta: z.record(z.unknown()).optional().describe('Editor/designer metadata'), }); /**