@easyops-cn/a2ui-react 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +9 -5
- package/CONTRIBUTING.md +1 -1
- package/README.md +12 -8
- package/package.json +7 -2
- package/playground/src/components/Header.tsx +1 -0
- package/playground/src/components/Preview.tsx +2 -2
- package/specs/001-a2ui-renderer/checklists/requirements.md +1 -1
- package/specs/001-a2ui-renderer/plan.md +5 -5
- package/specs/001-a2ui-renderer/quickstart.md +10 -6
- package/specs/001-a2ui-renderer/research.md +12 -8
- package/specs/001-a2ui-renderer/spec.md +13 -13
- package/specs/001-a2ui-renderer/tasks.md +11 -11
- package/specs/002-playground/contracts/components.md +1 -1
- package/specs/002-playground/data-model.md +2 -2
- package/specs/002-playground/plan.md +1 -1
- package/specs/002-playground/quickstart.md +4 -4
- package/specs/002-playground/research.md +3 -3
- package/specs/002-playground/tasks.md +1 -1
- package/src/0.8/{A2UIRender.test.tsx → A2UIRenderer.test.tsx} +24 -24
- package/src/0.8/{A2UIRender.tsx → A2UIRenderer.tsx} +8 -8
- package/src/0.8/index.ts +3 -3
- package/website/content/api.md +108 -0
- package/website/content/index.md +9 -5
- package/website/serve.json +2 -0
- package/website/src/components/Header.jsx +9 -3
package/CLAUDE.md
CHANGED
|
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
4
4
|
|
|
5
5
|
## Project Overview
|
|
6
6
|
|
|
7
|
-
A2UI React Renderer
|
|
7
|
+
A2UI React Renderer (`@easyops-cn/a2ui-react`) - A React implementation for rendering A2UI protocol. This is a library package (not an application) that downstream developers consume.
|
|
8
8
|
|
|
9
9
|
## Monorepo Structure
|
|
10
10
|
|
|
@@ -43,11 +43,15 @@ npm run build -w playground # Build playground
|
|
|
43
43
|
|
|
44
44
|
## Library Usage (Downstream API)
|
|
45
45
|
|
|
46
|
-
The primary entry point is `
|
|
46
|
+
The primary entry point is `A2UIRenderer` component:
|
|
47
47
|
|
|
48
48
|
```tsx
|
|
49
|
-
import {
|
|
50
|
-
|
|
49
|
+
import {
|
|
50
|
+
A2UIRenderer,
|
|
51
|
+
A2UIMessage,
|
|
52
|
+
A2UIAction,
|
|
53
|
+
} from '@easyops-cn/a2ui-react/0.8'
|
|
54
|
+
;<A2UIRenderer messages={messages} onAction={handleAction} />
|
|
51
55
|
```
|
|
52
56
|
|
|
53
57
|
Custom components can override defaults or add new ones via `components` prop (Map<string, React.ComponentType>).
|
|
@@ -58,7 +62,7 @@ Custom components use hooks: `useDispatchAction`, `useDataBinding`, `useFormBind
|
|
|
58
62
|
|
|
59
63
|
```javascript
|
|
60
64
|
import { v0_8 } from '@easyops-cn/a2ui-react' // Main namespace export
|
|
61
|
-
import { ... } from '@easyops-cn/a2ui-react/0.8' // Core module (
|
|
65
|
+
import { ... } from '@easyops-cn/a2ui-react/0.8' // Core module (A2UIRenderer, hooks, types)
|
|
62
66
|
```
|
|
63
67
|
|
|
64
68
|
## Architecture
|
package/CONTRIBUTING.md
CHANGED
|
@@ -4,7 +4,7 @@ Thank you for your interest in contributing to `@easyops-cn/a2ui-react`!
|
|
|
4
4
|
|
|
5
5
|
## Project Overview
|
|
6
6
|
|
|
7
|
-
A2UI React Renderer
|
|
7
|
+
A2UI React Renderer - A React implementation for rendering A2UI protocol. This is a library package that downstream developers consume.
|
|
8
8
|
|
|
9
9
|
## Monorepo Structure
|
|
10
10
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
# A2UI React Renderer
|
|
2
|
-
|
|
3
|
-
https://easyops-cn.github.io/a2ui-react/
|
|
1
|
+
# A2UI React Renderer
|
|
4
2
|
|
|
5
3
|
A React renderer library for [A2UI](https://a2ui.org) protocol.
|
|
6
4
|
|
|
@@ -8,6 +6,8 @@ Supports all components in A2UI standard catalog out of the box. Built with [sha
|
|
|
8
6
|
|
|
9
7
|
Currently A2UI protocol v0.8 is fully supported. Work on v0.9 is in progress.
|
|
10
8
|
|
|
9
|
+
[Docs](https://easyops-cn.github.io/a2ui-react/) | [Playground](https://easyops-cn.github.io/a2ui-react/playground/)
|
|
10
|
+
|
|
11
11
|
## Installation
|
|
12
12
|
|
|
13
13
|
```sh
|
|
@@ -22,11 +22,11 @@ First, use the `@source` directive to tell Tailwind to scan the library code for
|
|
|
22
22
|
@source "../node_modules/@easyops-cn/a2ui-react";
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Next, use the `
|
|
25
|
+
Next, use the `A2UIRenderer` component to render A2UI messages:
|
|
26
26
|
|
|
27
27
|
```tsx
|
|
28
28
|
import {
|
|
29
|
-
|
|
29
|
+
A2UIRenderer,
|
|
30
30
|
type A2UIMessage,
|
|
31
31
|
type A2UIAction,
|
|
32
32
|
} from '@easyops-cn/a2ui-react/0.8'
|
|
@@ -38,7 +38,7 @@ function App() {
|
|
|
38
38
|
console.log('Action received:', action)
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
return <
|
|
41
|
+
return <A2UIRenderer messages={messages} onAction={handleAction} />
|
|
42
42
|
}
|
|
43
43
|
```
|
|
44
44
|
|
|
@@ -47,7 +47,11 @@ function App() {
|
|
|
47
47
|
You can override default components or add new custom components via the `components` prop, which takes a `Map<string, React.ComponentType>`.
|
|
48
48
|
|
|
49
49
|
```tsx
|
|
50
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
A2UIRenderer,
|
|
52
|
+
A2UIMessage,
|
|
53
|
+
A2UIAction,
|
|
54
|
+
} from '@easyops-cn/a2ui-react/0.8'
|
|
51
55
|
|
|
52
56
|
const ComponentsMap = new Map<string, React.ComponentType<any>>([
|
|
53
57
|
// Override default Button component with a custom one
|
|
@@ -59,7 +63,7 @@ const ComponentsMap = new Map<string, React.ComponentType<any>>([
|
|
|
59
63
|
|
|
60
64
|
function App() {
|
|
61
65
|
return (
|
|
62
|
-
<
|
|
66
|
+
<A2UIRenderer
|
|
63
67
|
components={ComponentsMap}
|
|
64
68
|
messages={messages}
|
|
65
69
|
onAction={handleAction}
|
package/package.json
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@easyops-cn/a2ui-react",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "A2UI React Renderer
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A2UI React Renderer",
|
|
5
|
+
"homepage": "https://easyops-cn.github.io/a2ui-react/",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git@github.com:easyops-cn/a2ui-react.git"
|
|
9
|
+
},
|
|
5
10
|
"type": "module",
|
|
6
11
|
"main": "./dist/index.js",
|
|
7
12
|
"types": "./dist/index.d.ts",
|
|
@@ -14,6 +14,7 @@ export function Header({ title, children }: HeaderProps) {
|
|
|
14
14
|
<div className="header-right">
|
|
15
15
|
<nav className="header-nav">
|
|
16
16
|
<a href="/a2ui-react/">Docs</a>
|
|
17
|
+
<a href="/a2ui-react/api/">API Reference</a>
|
|
17
18
|
<a href="/a2ui-react/playground/" className="active">
|
|
18
19
|
Playground
|
|
19
20
|
</a>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Component, type ReactNode } from 'react'
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
A2UIRenderer,
|
|
4
4
|
type A2UIMessage,
|
|
5
5
|
type A2UIAction,
|
|
6
6
|
} from '@easyops-cn/a2ui-react/0.8'
|
|
@@ -72,7 +72,7 @@ export function Preview({ messages, error, onAction }: PreviewProps) {
|
|
|
72
72
|
|
|
73
73
|
return (
|
|
74
74
|
<ErrorBoundary>
|
|
75
|
-
<
|
|
75
|
+
<A2UIRenderer messages={messages} onAction={onAction} />
|
|
76
76
|
</ErrorBoundary>
|
|
77
77
|
)
|
|
78
78
|
}
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
- All items pass validation
|
|
35
35
|
- Specification is ready for `/speckit.clarify` or `/speckit.plan`
|
|
36
36
|
- The spec covers all usage patterns shown in README.md:
|
|
37
|
-
- Basic rendering with
|
|
37
|
+
- Basic rendering with A2UIRenderer component
|
|
38
38
|
- Action handling via onAction callback
|
|
39
39
|
- Custom component overrides via ComponentsMap
|
|
40
40
|
- Custom component creation with hooks (useDispatchAction, useDataBinding, useFormBinding)
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
## Summary
|
|
7
7
|
|
|
8
|
-
Implement the
|
|
8
|
+
Implement the A2UIRenderer component and public API exports for the A2UI React renderer library. The library already has substantial infrastructure implemented (contexts, hooks, component registry, default components). The remaining work focuses on:
|
|
9
9
|
|
|
10
|
-
1. Creating the main `
|
|
10
|
+
1. Creating the main `A2UIRenderer` component that matches the README.md API
|
|
11
11
|
2. Setting up the versioned export path (`@easyops-cn/a2ui-react/0.8`)
|
|
12
12
|
3. Adding development-mode placeholder for unknown components
|
|
13
13
|
4. Ensuring all public types and hooks are properly exported
|
|
@@ -65,7 +65,7 @@ The constitution template is not yet customized for this project. Proceeding wit
|
|
|
65
65
|
|
|
66
66
|
| Component/Module | Status | Required For |
|
|
67
67
|
| ------------------------- | ---------- | ------------------------------------------- |
|
|
68
|
-
| **
|
|
68
|
+
| **A2UIRenderer** | ❌ Missing | Main entry component per README.md |
|
|
69
69
|
| **index.ts exports** | ❌ Missing | Versioned path `@easyops-cn/a2ui-react/0.8` |
|
|
70
70
|
| **Dev-mode placeholder** | ❌ Missing | Unknown component handling per spec |
|
|
71
71
|
| **ComponentsMap support** | ❌ Missing | Custom component override per README.md |
|
|
@@ -91,8 +91,8 @@ src/
|
|
|
91
91
|
├── index.ts # Root export (existing)
|
|
92
92
|
└── 0.8/
|
|
93
93
|
├── index.ts # NEW: Versioned public API exports
|
|
94
|
-
├──
|
|
95
|
-
├──
|
|
94
|
+
├── A2UIRenderer.tsx # NEW: Main render component
|
|
95
|
+
├── A2UIRenderer.test.tsx # NEW: Tests for A2UIRenderer
|
|
96
96
|
├── contexts/ # Existing: Context providers
|
|
97
97
|
│ ├── A2UIProvider.tsx
|
|
98
98
|
│ ├── ActionContext.tsx
|
|
@@ -14,7 +14,11 @@ pnpm add @easyops-cn/a2ui-react
|
|
|
14
14
|
## Basic Usage
|
|
15
15
|
|
|
16
16
|
```tsx
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
A2UIRenderer,
|
|
19
|
+
A2UIMessage,
|
|
20
|
+
A2UIAction,
|
|
21
|
+
} from '@easyops-cn/a2ui-react/0.8'
|
|
18
22
|
|
|
19
23
|
function App() {
|
|
20
24
|
const messages: A2UIMessage[] = [
|
|
@@ -26,7 +30,7 @@ function App() {
|
|
|
26
30
|
// Handle the action (e.g., send to server)
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
return <
|
|
33
|
+
return <A2UIRenderer messages={messages} onAction={handleAction} />
|
|
30
34
|
}
|
|
31
35
|
```
|
|
32
36
|
|
|
@@ -36,7 +40,7 @@ Override default components or add new ones:
|
|
|
36
40
|
|
|
37
41
|
```tsx
|
|
38
42
|
import {
|
|
39
|
-
|
|
43
|
+
A2UIRenderer,
|
|
40
44
|
useDispatchAction,
|
|
41
45
|
ComponentRenderer,
|
|
42
46
|
} from '@easyops-cn/a2ui-react/0.8'
|
|
@@ -63,7 +67,7 @@ const ComponentsMap = new Map([['Button', CustomButton]])
|
|
|
63
67
|
|
|
64
68
|
function App() {
|
|
65
69
|
return (
|
|
66
|
-
<
|
|
70
|
+
<A2UIRenderer
|
|
67
71
|
components={ComponentsMap}
|
|
68
72
|
messages={messages}
|
|
69
73
|
onAction={handleAction}
|
|
@@ -99,7 +103,7 @@ function InputComponent({ surfaceId, value }) {
|
|
|
99
103
|
|
|
100
104
|
## API Reference
|
|
101
105
|
|
|
102
|
-
###
|
|
106
|
+
### A2UIRenderer Props
|
|
103
107
|
|
|
104
108
|
| Prop | Type | Required | Description |
|
|
105
109
|
| ------------ | ------------------------------ | -------- | ------------------------------------- |
|
|
@@ -127,7 +131,7 @@ function InputComponent({ surfaceId, value }) {
|
|
|
127
131
|
## Message Flow
|
|
128
132
|
|
|
129
133
|
```
|
|
130
|
-
Server Client (
|
|
134
|
+
Server Client (A2UIRenderer)
|
|
131
135
|
│ │
|
|
132
136
|
│──── beginRendering ───────────────►│ Initialize surface
|
|
133
137
|
│ │
|
|
@@ -5,18 +5,18 @@
|
|
|
5
5
|
|
|
6
6
|
## Overview
|
|
7
7
|
|
|
8
|
-
This document captures research findings for implementing the
|
|
8
|
+
This document captures research findings for implementing the A2UIRenderer component and public API exports.
|
|
9
9
|
|
|
10
|
-
## Decision 1:
|
|
10
|
+
## Decision 1: A2UIRenderer Component Architecture
|
|
11
11
|
|
|
12
|
-
**Decision**: Compose
|
|
12
|
+
**Decision**: Compose A2UIRenderer as a thin wrapper around existing infrastructure
|
|
13
13
|
|
|
14
14
|
**Rationale**:
|
|
15
15
|
|
|
16
16
|
- The existing `A2UIProvider` already combines all necessary context providers
|
|
17
17
|
- The existing `useA2UIMessageHandler` hook handles message processing
|
|
18
18
|
- The existing `ComponentRenderer` handles component routing
|
|
19
|
-
-
|
|
19
|
+
- A2UIRenderer only needs to:
|
|
20
20
|
1. Accept `messages`, `onAction`, and optional `components` props
|
|
21
21
|
2. Wrap children in A2UIProvider
|
|
22
22
|
3. Process messages and render surfaces
|
|
@@ -41,7 +41,7 @@ This document captures research findings for implementing the A2UIRender compone
|
|
|
41
41
|
|
|
42
42
|
- Global registry mutation: Rejected - not React-friendly, causes side effects
|
|
43
43
|
- Prop drilling: Rejected - would require changes to all container components
|
|
44
|
-
- Module-level configuration: Rejected - not compatible with multiple
|
|
44
|
+
- Module-level configuration: Rejected - not compatible with multiple A2UIRenderer instances
|
|
45
45
|
|
|
46
46
|
## Decision 3: Unknown Component Handling
|
|
47
47
|
|
|
@@ -85,7 +85,7 @@ This document captures research findings for implementing the A2UIRender compone
|
|
|
85
85
|
- Messages can create multiple surfaces
|
|
86
86
|
- Each surface has its own `root` component ID
|
|
87
87
|
- SurfaceContext already maintains `Map<string, Surface>`
|
|
88
|
-
-
|
|
88
|
+
- A2UIRenderer should render all active surfaces
|
|
89
89
|
|
|
90
90
|
**Alternatives considered**:
|
|
91
91
|
|
|
@@ -123,7 +123,7 @@ function useDispatchAction(): (
|
|
|
123
123
|
|
|
124
124
|
- `A2UIMessage` - ✅ exists in types/index.ts
|
|
125
125
|
- `A2UIAction` - ❌ needs alias (currently `ActionPayload`)
|
|
126
|
-
- `
|
|
126
|
+
- `A2UIRenderer` - ❌ needs implementation
|
|
127
127
|
- `ComponentRenderer` - ✅ exists
|
|
128
128
|
- `useDispatchAction` - ✅ exists
|
|
129
129
|
- `useDataBinding` - ✅ exists
|
|
@@ -134,7 +134,11 @@ function useDispatchAction(): (
|
|
|
134
134
|
The README.md shows:
|
|
135
135
|
|
|
136
136
|
```tsx
|
|
137
|
-
import {
|
|
137
|
+
import {
|
|
138
|
+
A2UIRenderer,
|
|
139
|
+
A2UIMessage,
|
|
140
|
+
A2UIAction,
|
|
141
|
+
} from '@easyops-cn/a2ui-react/0.8'
|
|
138
142
|
```
|
|
139
143
|
|
|
140
144
|
Current types use `ActionPayload` instead of `A2UIAction`. The index.ts should export `ActionPayload as A2UIAction` for API consistency.
|
|
@@ -16,17 +16,17 @@
|
|
|
16
16
|
|
|
17
17
|
### User Story 1 - Basic Message Rendering (Priority: P1)
|
|
18
18
|
|
|
19
|
-
As a developer, I want to render A2UI messages using the
|
|
19
|
+
As a developer, I want to render A2UI messages using the A2UIRenderer component so that I can display dynamic UI content from A2UI protocol messages.
|
|
20
20
|
|
|
21
21
|
**Why this priority**: This is the core functionality - without basic rendering, no other features can work. It enables the fundamental use case of displaying A2UI messages.
|
|
22
22
|
|
|
23
|
-
**Independent Test**: Can be fully tested by passing an array of A2UIMessage objects to
|
|
23
|
+
**Independent Test**: Can be fully tested by passing an array of A2UIMessage objects to A2UIRenderer and verifying the UI renders correctly. Delivers immediate value by enabling basic A2UI integration.
|
|
24
24
|
|
|
25
25
|
**Acceptance Scenarios**:
|
|
26
26
|
|
|
27
|
-
1. **Given** an empty messages array, **When**
|
|
28
|
-
2. **Given** a messages array with valid A2UIMessage objects, **When**
|
|
29
|
-
3. **Given** a messages array with nested components, **When**
|
|
27
|
+
1. **Given** an empty messages array, **When** A2UIRenderer is rendered, **Then** no UI components are displayed
|
|
28
|
+
2. **Given** a messages array with valid A2UIMessage objects, **When** A2UIRenderer is rendered, **Then** all message components are displayed in order
|
|
29
|
+
3. **Given** a messages array with nested components, **When** A2UIRenderer is rendered, **Then** nested components are rendered correctly within their parent containers
|
|
30
30
|
|
|
31
31
|
---
|
|
32
32
|
|
|
@@ -56,9 +56,9 @@ As a developer, I want to override default components with custom implementation
|
|
|
56
56
|
|
|
57
57
|
**Acceptance Scenarios**:
|
|
58
58
|
|
|
59
|
-
1. **Given** a ComponentsMap with a custom Button component, **When**
|
|
60
|
-
2. **Given** a ComponentsMap with multiple custom components, **When**
|
|
61
|
-
3. **Given** a ComponentsMap that does not override a component type, **When**
|
|
59
|
+
1. **Given** a ComponentsMap with a custom Button component, **When** A2UIRenderer renders a Button, **Then** the custom Button component is used instead of the default
|
|
60
|
+
2. **Given** a ComponentsMap with multiple custom components, **When** A2UIRenderer renders those component types, **Then** each custom component is used appropriately
|
|
61
|
+
3. **Given** a ComponentsMap that does not override a component type, **When** A2UIRenderer renders that component type, **Then** the default component is used
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
@@ -72,7 +72,7 @@ As a developer, I want to add new custom component types that don't exist in the
|
|
|
72
72
|
|
|
73
73
|
**Acceptance Scenarios**:
|
|
74
74
|
|
|
75
|
-
1. **Given** a ComponentsMap with a new component type "Switch", **When**
|
|
75
|
+
1. **Given** a ComponentsMap with a new component type "Switch", **When** A2UIRenderer encounters a Switch component in messages, **Then** the custom Switch component is rendered
|
|
76
76
|
2. **Given** a custom component using useDispatchAction hook, **When** the component dispatches an action, **Then** the action flows through the onAction callback correctly
|
|
77
77
|
|
|
78
78
|
---
|
|
@@ -120,12 +120,12 @@ As a developer, I want to use form binding hooks in custom components so that I
|
|
|
120
120
|
|
|
121
121
|
### Functional Requirements
|
|
122
122
|
|
|
123
|
-
- **FR-001**: System MUST export
|
|
123
|
+
- **FR-001**: System MUST export A2UIRenderer component that accepts messages and onAction props
|
|
124
124
|
- **FR-002**: System MUST export A2UIMessage type for defining message structures
|
|
125
125
|
- **FR-003**: System MUST export A2UIAction type for defining action payloads
|
|
126
|
-
- **FR-004**:
|
|
127
|
-
- **FR-005**:
|
|
128
|
-
- **FR-006**:
|
|
126
|
+
- **FR-004**: A2UIRenderer MUST render all components defined in the messages array
|
|
127
|
+
- **FR-005**: A2UIRenderer MUST invoke onAction callback when user interactions trigger actions
|
|
128
|
+
- **FR-006**: A2UIRenderer MUST accept an optional components prop (ComponentsMap) for custom component overrides
|
|
129
129
|
- **FR-007**: System MUST export useDispatchAction hook for custom components to dispatch actions
|
|
130
130
|
- **FR-008**: System MUST export ComponentRenderer component for rendering child components within custom components
|
|
131
131
|
- **FR-009**: System MUST export useDataBinding hook for reading data from the A2UI context
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
## Path Conventions
|
|
17
17
|
|
|
18
18
|
- **Single library project**: `src/0.8/` at repository root
|
|
19
|
-
- Tests co-located with source files (e.g., `
|
|
19
|
+
- Tests co-located with source files (e.g., `A2UIRenderer.test.tsx`)
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
@@ -46,30 +46,30 @@
|
|
|
46
46
|
- [x] T011 Add dev-mode placeholder component for unknown types in src/0.8/components/UnknownComponent.tsx
|
|
47
47
|
- [x] T012 Update ComponentRenderer to render UnknownComponent in dev mode, skip in production
|
|
48
48
|
|
|
49
|
-
**Checkpoint**: Foundation ready -
|
|
49
|
+
**Checkpoint**: Foundation ready - A2UIRenderer component can now be implemented
|
|
50
50
|
|
|
51
51
|
---
|
|
52
52
|
|
|
53
53
|
## Phase 3: User Story 1 - Basic Message Rendering (Priority: P1) 🎯 MVP
|
|
54
54
|
|
|
55
|
-
**Goal**: Render A2UI messages using the
|
|
55
|
+
**Goal**: Render A2UI messages using the A2UIRenderer component
|
|
56
56
|
|
|
57
|
-
**Independent Test**: Pass an array of A2UIMessage objects to
|
|
57
|
+
**Independent Test**: Pass an array of A2UIMessage objects to A2UIRenderer and verify the UI renders correctly
|
|
58
58
|
|
|
59
59
|
### Tests for User Story 1
|
|
60
60
|
|
|
61
|
-
- [x] T013 [P] [US1] Create test file src/0.8/
|
|
61
|
+
- [x] T013 [P] [US1] Create test file src/0.8/A2UIRenderer.test.tsx with test setup
|
|
62
62
|
- [x] T014 [P] [US1] Add test: renders nothing for empty messages array
|
|
63
63
|
- [x] T015 [P] [US1] Add test: renders components from valid A2UIMessage objects
|
|
64
64
|
- [x] T016 [P] [US1] Add test: renders nested components correctly
|
|
65
65
|
|
|
66
66
|
### Implementation for User Story 1
|
|
67
67
|
|
|
68
|
-
- [x] T017 [US1] Create
|
|
68
|
+
- [x] T017 [US1] Create A2UIRenderer component in src/0.8/A2UIRenderer.tsx
|
|
69
69
|
- [x] T018 [US1] Implement messages prop handling with useA2UIMessageHandler
|
|
70
70
|
- [x] T019 [US1] Implement surface rendering loop (render all surfaces from context)
|
|
71
71
|
- [x] T020 [US1] Add null/undefined messages handling (graceful empty render)
|
|
72
|
-
- [x] T021 [US1] Export
|
|
72
|
+
- [x] T021 [US1] Export A2UIRenderer from src/0.8/index.ts
|
|
73
73
|
|
|
74
74
|
**Checkpoint**: User Story 1 complete - basic rendering works independently
|
|
75
75
|
|
|
@@ -89,8 +89,8 @@
|
|
|
89
89
|
|
|
90
90
|
### Implementation for User Story 2
|
|
91
91
|
|
|
92
|
-
- [x] T025 [US2] Add onAction prop to
|
|
93
|
-
- [x] T026 [US2] Pass onAction to A2UIProvider in
|
|
92
|
+
- [x] T025 [US2] Add onAction prop to A2UIRenderer component in src/0.8/A2UIRenderer.tsx
|
|
93
|
+
- [x] T026 [US2] Pass onAction to A2UIProvider in A2UIRenderer
|
|
94
94
|
- [x] T027 [US2] Add test: no error when action dispatched without onAction callback
|
|
95
95
|
|
|
96
96
|
**Checkpoint**: User Stories 1 AND 2 complete - rendering and actions work
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
|
|
112
112
|
### Implementation for User Story 3
|
|
113
113
|
|
|
114
|
-
- [x] T031 [US3] Add components prop (ComponentsMap) to
|
|
114
|
+
- [x] T031 [US3] Add components prop (ComponentsMap) to A2UIRenderer in src/0.8/A2UIRenderer.tsx
|
|
115
115
|
- [x] T032 [US3] Wrap rendering with ComponentsMapProvider passing custom components
|
|
116
116
|
- [x] T033 [US3] Merge custom components with defaults in ComponentsMapContext
|
|
117
117
|
|
|
@@ -199,7 +199,7 @@
|
|
|
199
199
|
- **Setup (Phase 1)**: No dependencies - can start immediately
|
|
200
200
|
- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS user stories 3-4
|
|
201
201
|
- **User Story 1 (Phase 3)**: Depends on Setup (Phase 1) only
|
|
202
|
-
- **User Story 2 (Phase 4)**: Depends on User Story 1 (needs
|
|
202
|
+
- **User Story 2 (Phase 4)**: Depends on User Story 1 (needs A2UIRenderer component)
|
|
203
203
|
- **User Story 3 (Phase 5)**: Depends on Foundational (Phase 2) for ComponentsMapContext
|
|
204
204
|
- **User Story 4 (Phase 6)**: Depends on User Story 3 (extends ComponentsMap support)
|
|
205
205
|
- **User Story 5 (Phase 7)**: Depends on Setup (Phase 1) - hooks already exist
|
|
@@ -115,6 +115,6 @@ type ThemeToggleHandler = () => void
|
|
|
115
115
|
### Action Dispatch Event
|
|
116
116
|
|
|
117
117
|
```typescript
|
|
118
|
-
// Triggered when
|
|
118
|
+
// Triggered when A2UIRenderer dispatches an action
|
|
119
119
|
type ActionHandler = (action: A2UIAction) => void
|
|
120
120
|
```
|
|
@@ -72,7 +72,7 @@ JSON.parse() attempted
|
|
|
72
72
|
│ parsedMessages = result │ parsedMessages = null
|
|
73
73
|
│ parseError = null │ parseError = error.message
|
|
74
74
|
│ ↓ │ ↓
|
|
75
|
-
│
|
|
75
|
+
│ A2UIRenderer receives messages │ ErrorDisplay shown
|
|
76
76
|
└─────────────────────────────────────┘
|
|
77
77
|
```
|
|
78
78
|
|
|
@@ -124,7 +124,7 @@ UI re-renders with new theme
|
|
|
124
124
|
│ renders
|
|
125
125
|
▼
|
|
126
126
|
┌─────────────┐
|
|
127
|
-
│
|
|
127
|
+
│ A2UIRenderer │
|
|
128
128
|
└─────────────┘
|
|
129
129
|
```
|
|
130
130
|
|
|
@@ -49,7 +49,7 @@ playground/
|
|
|
49
49
|
│ │ ├── Header.tsx # Header with title and theme toggle
|
|
50
50
|
│ │ ├── ThemeToggle.tsx # Sun/moon theme switch component
|
|
51
51
|
│ │ ├── JsonEditor.tsx # CodeMirror-based JSON editor
|
|
52
|
-
│ │ ├── Preview.tsx #
|
|
52
|
+
│ │ ├── Preview.tsx # A2UIRenderer wrapper with error boundary
|
|
53
53
|
│ │ ├── ExampleSelector.tsx # Dropdown for example selection
|
|
54
54
|
│ │ └── ErrorDisplay.tsx # Error state display component
|
|
55
55
|
│ ├── data/
|
|
@@ -44,7 +44,7 @@ playground/src/
|
|
|
44
44
|
│ ├── Header.tsx # App header with title and theme toggle
|
|
45
45
|
│ ├── ThemeToggle.tsx # Sun/moon theme switch
|
|
46
46
|
│ ├── JsonEditor.tsx # CodeMirror JSON editor
|
|
47
|
-
│ ├── Preview.tsx #
|
|
47
|
+
│ ├── Preview.tsx # A2UIRenderer wrapper
|
|
48
48
|
│ ├── ExampleSelector.tsx # Example dropdown
|
|
49
49
|
│ └── ErrorDisplay.tsx # Error state component
|
|
50
50
|
├── data/
|
|
@@ -87,14 +87,14 @@ import { json } from '@codemirror/lang-json'
|
|
|
87
87
|
/>
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
###
|
|
90
|
+
### A2UIRenderer Integration
|
|
91
91
|
|
|
92
92
|
```typescript
|
|
93
|
-
import {
|
|
93
|
+
import { A2UIRenderer, type A2UIMessage } from '@easyops-cn/a2ui-react/0.8'
|
|
94
94
|
|
|
95
95
|
// Parse JSON and render
|
|
96
96
|
const messages: A2UIMessage[] = JSON.parse(jsonContent)
|
|
97
|
-
<
|
|
97
|
+
<A2UIRenderer messages={messages} onAction={handleAction} />
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
### Error Boundary Pattern
|
|
@@ -69,10 +69,10 @@ Import from workspace dependency `@easyops-cn/a2ui-react/0.8`
|
|
|
69
69
|
### Integration Pattern
|
|
70
70
|
|
|
71
71
|
```typescript
|
|
72
|
-
import {
|
|
72
|
+
import { A2UIRenderer, type A2UIMessage } from '@easyops-cn/a2ui-react/0.8'
|
|
73
73
|
|
|
74
74
|
// Parse JSON string to A2UIMessage[]
|
|
75
|
-
// Pass to
|
|
75
|
+
// Pass to A2UIRenderer component
|
|
76
76
|
// Handle parse errors gracefully
|
|
77
77
|
```
|
|
78
78
|
|
|
@@ -111,7 +111,7 @@ Use React Error Boundary for preview panel with graceful fallback
|
|
|
111
111
|
### Implementation Pattern
|
|
112
112
|
|
|
113
113
|
```typescript
|
|
114
|
-
// ErrorBoundary wraps
|
|
114
|
+
// ErrorBoundary wraps A2UIRenderer
|
|
115
115
|
// Catches render errors, displays ErrorDisplay component
|
|
116
116
|
// JSON parse errors handled separately before render
|
|
117
117
|
```
|
|
@@ -66,7 +66,7 @@ playground/
|
|
|
66
66
|
### Implementation for User Story 1
|
|
67
67
|
|
|
68
68
|
- [x] T011 [P] [US1] Create JsonEditor component with CodeMirror integration in playground/src/components/JsonEditor.tsx
|
|
69
|
-
- [x] T012 [P] [US1] Create Preview component wrapping
|
|
69
|
+
- [x] T012 [P] [US1] Create Preview component wrapping A2UIRenderer with error boundary in playground/src/components/Preview.tsx
|
|
70
70
|
- [x] T013 [US1] Create split-panel layout in playground/src/App.tsx with editor (left) and preview (right)
|
|
71
71
|
- [x] T014 [US1] Add layout styles for split-panel (50/50) in playground/src/App.css
|
|
72
72
|
- [x] T015 [US1] Implement JSON parsing with error handling in App.tsx (parse on change, update preview or show error)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* A2UIRenderer Tests
|
|
3
3
|
*
|
|
4
|
-
* Tests for the main
|
|
4
|
+
* Tests for the main A2UIRenderer component.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { describe, it, expect, vi } from 'vitest'
|
|
8
8
|
import { render, screen } from '@testing-library/react'
|
|
9
9
|
import userEvent from '@testing-library/user-event'
|
|
10
|
-
import {
|
|
10
|
+
import { A2UIRenderer, type ComponentsMap } from './A2UIRenderer'
|
|
11
11
|
import type { A2UIMessage, BaseComponentProps, Action } from './types'
|
|
12
12
|
import { useDispatchAction } from './hooks/useDispatchAction'
|
|
13
13
|
import { useDataBinding, useFormBinding } from './hooks/useDataBinding'
|
|
@@ -79,20 +79,20 @@ function createTestMessages(options: {
|
|
|
79
79
|
return messages
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
describe('
|
|
82
|
+
describe('A2UIRenderer', () => {
|
|
83
83
|
describe('Phase 3: User Story 1 - Basic Message Rendering', () => {
|
|
84
84
|
it('T014: renders nothing for empty messages array', () => {
|
|
85
|
-
const { container } = render(<
|
|
85
|
+
const { container } = render(<A2UIRenderer messages={[]} />)
|
|
86
86
|
expect(container.firstChild).toBeNull()
|
|
87
87
|
})
|
|
88
88
|
|
|
89
89
|
it('T014b: renders nothing for null/undefined messages', () => {
|
|
90
90
|
// @ts-expect-error - testing null handling
|
|
91
|
-
const { container: c1 } = render(<
|
|
91
|
+
const { container: c1 } = render(<A2UIRenderer messages={null} />)
|
|
92
92
|
expect(c1.firstChild).toBeNull()
|
|
93
93
|
|
|
94
94
|
// @ts-expect-error - testing undefined handling
|
|
95
|
-
const { container: c2 } = render(<
|
|
95
|
+
const { container: c2 } = render(<A2UIRenderer messages={undefined} />)
|
|
96
96
|
expect(c2.firstChild).toBeNull()
|
|
97
97
|
})
|
|
98
98
|
|
|
@@ -109,7 +109,7 @@ describe('A2UIRender', () => {
|
|
|
109
109
|
],
|
|
110
110
|
})
|
|
111
111
|
|
|
112
|
-
render(<
|
|
112
|
+
render(<A2UIRenderer messages={messages} />)
|
|
113
113
|
|
|
114
114
|
expect(screen.getByText('Hello World')).toBeInTheDocument()
|
|
115
115
|
})
|
|
@@ -137,7 +137,7 @@ describe('A2UIRender', () => {
|
|
|
137
137
|
],
|
|
138
138
|
})
|
|
139
139
|
|
|
140
|
-
render(<
|
|
140
|
+
render(<A2UIRenderer messages={messages} />)
|
|
141
141
|
|
|
142
142
|
expect(screen.getByText('First')).toBeInTheDocument()
|
|
143
143
|
expect(screen.getByText('Second')).toBeInTheDocument()
|
|
@@ -173,7 +173,7 @@ describe('A2UIRender', () => {
|
|
|
173
173
|
},
|
|
174
174
|
]
|
|
175
175
|
|
|
176
|
-
render(<
|
|
176
|
+
render(<A2UIRenderer messages={messages} />)
|
|
177
177
|
|
|
178
178
|
expect(screen.getByText('Surface 1')).toBeInTheDocument()
|
|
179
179
|
expect(screen.getByText('Surface 2')).toBeInTheDocument()
|
|
@@ -205,7 +205,7 @@ describe('A2UIRender', () => {
|
|
|
205
205
|
],
|
|
206
206
|
})
|
|
207
207
|
|
|
208
|
-
render(<
|
|
208
|
+
render(<A2UIRenderer messages={messages} onAction={onAction} />)
|
|
209
209
|
|
|
210
210
|
const button = screen.getByRole('button')
|
|
211
211
|
await user.click(button)
|
|
@@ -242,7 +242,7 @@ describe('A2UIRender', () => {
|
|
|
242
242
|
],
|
|
243
243
|
})
|
|
244
244
|
|
|
245
|
-
render(<
|
|
245
|
+
render(<A2UIRenderer messages={messages} onAction={onAction} />)
|
|
246
246
|
|
|
247
247
|
const button = screen.getByRole('button')
|
|
248
248
|
await user.click(button)
|
|
@@ -299,7 +299,7 @@ describe('A2UIRender', () => {
|
|
|
299
299
|
],
|
|
300
300
|
})
|
|
301
301
|
|
|
302
|
-
render(<
|
|
302
|
+
render(<A2UIRenderer messages={messages} onAction={onAction} />)
|
|
303
303
|
|
|
304
304
|
const buttons = screen.getAllByRole('button')
|
|
305
305
|
await user.click(buttons[0])
|
|
@@ -347,7 +347,7 @@ describe('A2UIRender', () => {
|
|
|
347
347
|
})
|
|
348
348
|
|
|
349
349
|
// No onAction prop
|
|
350
|
-
render(<
|
|
350
|
+
render(<A2UIRenderer messages={messages} />)
|
|
351
351
|
|
|
352
352
|
const button = screen.getByRole('button')
|
|
353
353
|
|
|
@@ -395,7 +395,7 @@ describe('A2UIRender', () => {
|
|
|
395
395
|
],
|
|
396
396
|
})
|
|
397
397
|
|
|
398
|
-
render(<
|
|
398
|
+
render(<A2UIRenderer messages={messages} components={customComponents} />)
|
|
399
399
|
|
|
400
400
|
expect(screen.getByTestId('custom-button')).toBeInTheDocument()
|
|
401
401
|
expect(screen.getByText('Hello')).toBeInTheDocument()
|
|
@@ -437,7 +437,7 @@ describe('A2UIRender', () => {
|
|
|
437
437
|
],
|
|
438
438
|
})
|
|
439
439
|
|
|
440
|
-
render(<
|
|
440
|
+
render(<A2UIRenderer messages={messages} components={customComponents} />)
|
|
441
441
|
|
|
442
442
|
expect(screen.getByTestId('custom-button')).toBeInTheDocument()
|
|
443
443
|
expect(screen.getByTestId('custom-text')).toBeInTheDocument()
|
|
@@ -471,7 +471,7 @@ describe('A2UIRender', () => {
|
|
|
471
471
|
],
|
|
472
472
|
})
|
|
473
473
|
|
|
474
|
-
render(<
|
|
474
|
+
render(<A2UIRenderer messages={messages} components={customComponents} />)
|
|
475
475
|
|
|
476
476
|
// Custom button
|
|
477
477
|
expect(screen.getByTestId('custom-button')).toBeInTheDocument()
|
|
@@ -509,7 +509,7 @@ describe('A2UIRender', () => {
|
|
|
509
509
|
],
|
|
510
510
|
})
|
|
511
511
|
|
|
512
|
-
render(<
|
|
512
|
+
render(<A2UIRenderer messages={messages} components={customComponents} />)
|
|
513
513
|
|
|
514
514
|
expect(screen.getByTestId('custom-switch')).toBeInTheDocument()
|
|
515
515
|
expect(screen.getByText('Enable feature')).toBeInTheDocument()
|
|
@@ -557,7 +557,7 @@ describe('A2UIRender', () => {
|
|
|
557
557
|
})
|
|
558
558
|
|
|
559
559
|
render(
|
|
560
|
-
<
|
|
560
|
+
<A2UIRenderer
|
|
561
561
|
messages={messages}
|
|
562
562
|
onAction={onAction}
|
|
563
563
|
components={customComponents}
|
|
@@ -615,7 +615,7 @@ describe('A2UIRender', () => {
|
|
|
615
615
|
},
|
|
616
616
|
]
|
|
617
617
|
|
|
618
|
-
render(<
|
|
618
|
+
render(<A2UIRenderer messages={messages} components={customComponents} />)
|
|
619
619
|
|
|
620
620
|
expect(screen.getByTestId('custom-display')).toHaveTextContent(
|
|
621
621
|
'Hello from data model'
|
|
@@ -656,7 +656,7 @@ describe('A2UIRender', () => {
|
|
|
656
656
|
},
|
|
657
657
|
]
|
|
658
658
|
|
|
659
|
-
render(<
|
|
659
|
+
render(<A2UIRenderer messages={messages} components={customComponents} />)
|
|
660
660
|
|
|
661
661
|
expect(screen.getByTestId('custom-display')).toHaveTextContent(
|
|
662
662
|
'fallback value'
|
|
@@ -700,7 +700,7 @@ describe('A2UIRender', () => {
|
|
|
700
700
|
},
|
|
701
701
|
]
|
|
702
702
|
|
|
703
|
-
render(<
|
|
703
|
+
render(<A2UIRenderer messages={messages} components={customComponents} />)
|
|
704
704
|
|
|
705
705
|
expect(screen.getByTestId('custom-input')).toHaveValue('john_doe')
|
|
706
706
|
})
|
|
@@ -745,7 +745,7 @@ describe('A2UIRender', () => {
|
|
|
745
745
|
},
|
|
746
746
|
]
|
|
747
747
|
|
|
748
|
-
render(<
|
|
748
|
+
render(<A2UIRenderer messages={messages} components={customComponents} />)
|
|
749
749
|
|
|
750
750
|
const input = screen.getByTestId('custom-input')
|
|
751
751
|
await user.type(input, 'new_value')
|
|
@@ -785,7 +785,7 @@ describe('A2UIRender', () => {
|
|
|
785
785
|
},
|
|
786
786
|
]
|
|
787
787
|
|
|
788
|
-
render(<
|
|
788
|
+
render(<A2UIRenderer messages={messages} components={customComponents} />)
|
|
789
789
|
|
|
790
790
|
expect(screen.getByTestId('custom-input')).toHaveValue('default_value')
|
|
791
791
|
})
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* A2UIRenderer - Main entry component for rendering A2UI messages.
|
|
3
3
|
*
|
|
4
4
|
* This component processes A2UI messages and renders the resulting UI.
|
|
5
5
|
* It supports custom component overrides via the components prop.
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* ```tsx
|
|
9
|
-
* import {
|
|
9
|
+
* import { A2UIRenderer, A2UIMessage, A2UIAction } from '@easyops-cn/a2ui-react/0.8'
|
|
10
10
|
*
|
|
11
11
|
* function App() {
|
|
12
12
|
* const messages: A2UIMessage[] = [...]
|
|
13
13
|
* const handleAction = (action: A2UIAction) => {
|
|
14
14
|
* console.log('Action:', action)
|
|
15
15
|
* }
|
|
16
|
-
* return <
|
|
16
|
+
* return <A2UIRenderer messages={messages} onAction={handleAction} />
|
|
17
17
|
* }
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
@@ -38,7 +38,7 @@ export type ComponentsMap = Map<
|
|
|
38
38
|
>
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* Props for
|
|
41
|
+
* Props for A2UIRenderer component.
|
|
42
42
|
*/
|
|
43
43
|
export interface A2UIRenderProps {
|
|
44
44
|
/** Array of A2UI messages to render */
|
|
@@ -105,21 +105,21 @@ function A2UIRenderInner({ messages }: { messages: A2UIMessage[] }) {
|
|
|
105
105
|
* @example
|
|
106
106
|
* ```tsx
|
|
107
107
|
* // Basic usage
|
|
108
|
-
* <
|
|
108
|
+
* <A2UIRenderer messages={messages} onAction={handleAction} />
|
|
109
109
|
*
|
|
110
110
|
* // With custom components
|
|
111
111
|
* const customComponents = new Map([
|
|
112
112
|
* ['Button', CustomButton],
|
|
113
113
|
* ['Switch', CustomSwitch],
|
|
114
114
|
* ])
|
|
115
|
-
* <
|
|
115
|
+
* <A2UIRenderer
|
|
116
116
|
* messages={messages}
|
|
117
117
|
* onAction={handleAction}
|
|
118
118
|
* components={customComponents}
|
|
119
119
|
* />
|
|
120
120
|
* ```
|
|
121
121
|
*/
|
|
122
|
-
export function
|
|
122
|
+
export function A2UIRenderer({
|
|
123
123
|
messages,
|
|
124
124
|
onAction,
|
|
125
125
|
components,
|
|
@@ -139,4 +139,4 @@ export function A2UIRender({
|
|
|
139
139
|
)
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
A2UIRenderer.displayName = 'A2UI.Render'
|
package/src/0.8/index.ts
CHANGED
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* ```tsx
|
|
9
|
-
* import {
|
|
9
|
+
* import { A2UIRenderer, A2UIMessage, A2UIAction } from '@easyops-cn/a2ui-react/0.8'
|
|
10
10
|
*
|
|
11
11
|
* function App() {
|
|
12
12
|
* const messages: A2UIMessage[] = [...]
|
|
13
13
|
* const handleAction = (action: A2UIAction) => {
|
|
14
14
|
* console.log('Action:', action)
|
|
15
15
|
* }
|
|
16
|
-
* return <
|
|
16
|
+
* return <A2UIRenderer messages={messages} onAction={handleAction} />
|
|
17
17
|
* }
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
@@ -29,7 +29,7 @@ export type {
|
|
|
29
29
|
|
|
30
30
|
// ============ Components ============
|
|
31
31
|
|
|
32
|
-
export {
|
|
32
|
+
export { A2UIRenderer } from './A2UIRenderer'
|
|
33
33
|
export { ComponentRenderer } from './components/ComponentRenderer'
|
|
34
34
|
|
|
35
35
|
// ============ Hooks ============
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
## Core
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
/**
|
|
7
|
+
* Main entry component for rendering A2UI messages.
|
|
8
|
+
* @param messages - Array of A2UI messages to render
|
|
9
|
+
* @param onAction - Callback when an action is dispatched
|
|
10
|
+
* @param components - Custom component overrides
|
|
11
|
+
*/
|
|
12
|
+
function A2UIRenderer(props: {
|
|
13
|
+
messages: A2UIMessage[]
|
|
14
|
+
onAction?: (action: A2UIAction) => void
|
|
15
|
+
components?: Map<string, React.ComponentType<any>>
|
|
16
|
+
}): React.ReactElement
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A2UI message from server to client.
|
|
20
|
+
* Only one of the fields should be set per message.
|
|
21
|
+
*/
|
|
22
|
+
interface A2UIMessage {
|
|
23
|
+
beginRendering?: BeginRenderingPayload
|
|
24
|
+
surfaceUpdate?: SurfaceUpdatePayload
|
|
25
|
+
dataModelUpdate?: DataModelUpdatePayload
|
|
26
|
+
deleteSurface?: DeleteSurfacePayload
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resolved action payload sent to the action handler.
|
|
31
|
+
*/
|
|
32
|
+
interface A2UIAction {
|
|
33
|
+
surfaceId: string
|
|
34
|
+
name: string
|
|
35
|
+
context: Record<string, unknown>
|
|
36
|
+
sourceComponentId: string
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Hooks
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
/**
|
|
44
|
+
* Returns a function to dispatch actions from custom components.
|
|
45
|
+
*/
|
|
46
|
+
function useDispatchAction(): (
|
|
47
|
+
surfaceId: string,
|
|
48
|
+
componentId: string,
|
|
49
|
+
action: Action
|
|
50
|
+
) => void
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolves a ValueSource to its actual value.
|
|
54
|
+
* @param surfaceId - The surface ID for data model lookup
|
|
55
|
+
* @param source - The value source (literal or path reference)
|
|
56
|
+
* @param defaultValue - Default value if source is undefined or path not found
|
|
57
|
+
*/
|
|
58
|
+
function useDataBinding<T = unknown>(
|
|
59
|
+
surfaceId: string,
|
|
60
|
+
source: ValueSource | undefined,
|
|
61
|
+
defaultValue?: T
|
|
62
|
+
): T
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Hook for two-way data binding in form components.
|
|
66
|
+
* @param surfaceId - The surface ID
|
|
67
|
+
* @param source - The value source (must be a path reference for setting)
|
|
68
|
+
* @param defaultValue - Default value if not found
|
|
69
|
+
* @returns Tuple of [value, setValue]
|
|
70
|
+
*/
|
|
71
|
+
function useFormBinding<T = unknown>(
|
|
72
|
+
surfaceId: string,
|
|
73
|
+
source: ValueSource | undefined,
|
|
74
|
+
defaultValue?: T
|
|
75
|
+
): [T, (value: T) => void]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Others
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
/**
|
|
82
|
+
* Renders a component by ID from the component registry.
|
|
83
|
+
* @param surfaceId - The surface ID
|
|
84
|
+
* @param componentId - The component ID to render
|
|
85
|
+
*/
|
|
86
|
+
function ComponentRenderer(props: {
|
|
87
|
+
surfaceId: string
|
|
88
|
+
componentId: string
|
|
89
|
+
}): React.ReactElement
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Represents a value source - either a literal value or a reference to a data model path.
|
|
93
|
+
*/
|
|
94
|
+
type ValueSource =
|
|
95
|
+
| { literalString: string }
|
|
96
|
+
| { literalNumber: number }
|
|
97
|
+
| { literalBoolean: boolean }
|
|
98
|
+
| { literalArray: string[] }
|
|
99
|
+
| { path: string }
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Action definition (attached to Button components).
|
|
103
|
+
*/
|
|
104
|
+
interface Action {
|
|
105
|
+
name: string
|
|
106
|
+
context?: ActionContextItem[]
|
|
107
|
+
}
|
|
108
|
+
```
|
package/website/content/index.md
CHANGED
|
@@ -18,11 +18,11 @@ First, use the `@source` directive to tell Tailwind to scan the library code for
|
|
|
18
18
|
@source "../node_modules/@easyops-cn/a2ui-react";
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
Next, use the `
|
|
21
|
+
Next, use the `A2UIRenderer` component to render A2UI messages:
|
|
22
22
|
|
|
23
23
|
```tsx
|
|
24
24
|
import {
|
|
25
|
-
|
|
25
|
+
A2UIRenderer,
|
|
26
26
|
type A2UIMessage,
|
|
27
27
|
type A2UIAction,
|
|
28
28
|
} from '@easyops-cn/a2ui-react/0.8'
|
|
@@ -34,7 +34,7 @@ function App() {
|
|
|
34
34
|
console.log('Action received:', action)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
return <
|
|
37
|
+
return <A2UIRenderer messages={messages} onAction={handleAction} />
|
|
38
38
|
}
|
|
39
39
|
```
|
|
40
40
|
|
|
@@ -43,7 +43,11 @@ function App() {
|
|
|
43
43
|
You can override default components or add new custom components via the `components` prop, which takes a `Map<string, React.ComponentType>`.
|
|
44
44
|
|
|
45
45
|
```tsx
|
|
46
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
A2UIRenderer,
|
|
48
|
+
A2UIMessage,
|
|
49
|
+
A2UIAction,
|
|
50
|
+
} from '@easyops-cn/a2ui-react/0.8'
|
|
47
51
|
|
|
48
52
|
const ComponentsMap = new Map<string, React.ComponentType<any>>([
|
|
49
53
|
// Override default Button component with a custom one
|
|
@@ -55,7 +59,7 @@ const ComponentsMap = new Map<string, React.ComponentType<any>>([
|
|
|
55
59
|
|
|
56
60
|
function App() {
|
|
57
61
|
return (
|
|
58
|
-
<
|
|
62
|
+
<A2UIRenderer
|
|
59
63
|
components={ComponentsMap}
|
|
60
64
|
messages={messages}
|
|
61
65
|
onAction={handleAction}
|
package/website/serve.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"rewrites": [
|
|
3
3
|
{ "source": "a2ui-react/assets/:path*", "destination": "/assets/:path*" },
|
|
4
|
+
{ "source": "a2ui-react/api/:path*", "destination": "/api/:path*" },
|
|
5
|
+
{ "source": "a2ui-react/api/**", "destination": "/api/index.html" },
|
|
4
6
|
{ "source": "a2ui-react/**", "destination": "/index.html" }
|
|
5
7
|
]
|
|
6
8
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useSiteContext } from 'plain-blog/SiteContext'
|
|
2
2
|
|
|
3
|
-
export default function Header() {
|
|
4
|
-
const { baseUrl, site } = useSiteContext()
|
|
3
|
+
export default function Header(props) {
|
|
4
|
+
const { baseUrl, site, url } = useSiteContext()
|
|
5
5
|
|
|
6
6
|
return (
|
|
7
7
|
<header>
|
|
@@ -10,9 +10,15 @@ export default function Header() {
|
|
|
10
10
|
</h1>
|
|
11
11
|
<div className="header-right">
|
|
12
12
|
<nav className="header-nav">
|
|
13
|
-
<a href={baseUrl} className=
|
|
13
|
+
<a href={baseUrl} className={url === baseUrl ? 'active' : ''}>
|
|
14
14
|
Docs
|
|
15
15
|
</a>
|
|
16
|
+
<a
|
|
17
|
+
href={`${baseUrl}api/`}
|
|
18
|
+
className={url === `${baseUrl}api/` ? 'active' : ''}
|
|
19
|
+
>
|
|
20
|
+
API Reference
|
|
21
|
+
</a>
|
|
16
22
|
<a href={`${baseUrl}playground/`}>Playground</a>
|
|
17
23
|
<a
|
|
18
24
|
href="https://github.com/easyops-cn/a2ui-react"
|