@easyops-cn/a2ui-react 0.0.0

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.
Files changed (161) hide show
  1. package/.claude/commands/speckit.analyze.md +184 -0
  2. package/.claude/commands/speckit.checklist.md +294 -0
  3. package/.claude/commands/speckit.clarify.md +181 -0
  4. package/.claude/commands/speckit.constitution.md +82 -0
  5. package/.claude/commands/speckit.implement.md +135 -0
  6. package/.claude/commands/speckit.plan.md +89 -0
  7. package/.claude/commands/speckit.specify.md +256 -0
  8. package/.claude/commands/speckit.tasks.md +137 -0
  9. package/.claude/commands/speckit.taskstoissues.md +30 -0
  10. package/.github/workflows/deploy.yml +69 -0
  11. package/.husky/pre-commit +1 -0
  12. package/.prettierignore +4 -0
  13. package/.prettierrc +7 -0
  14. package/.specify/memory/constitution.md +73 -0
  15. package/.specify/scripts/bash/check-prerequisites.sh +166 -0
  16. package/.specify/scripts/bash/common.sh +156 -0
  17. package/.specify/scripts/bash/create-new-feature.sh +297 -0
  18. package/.specify/scripts/bash/setup-plan.sh +61 -0
  19. package/.specify/scripts/bash/update-agent-context.sh +799 -0
  20. package/.specify/templates/agent-file-template.md +28 -0
  21. package/.specify/templates/checklist-template.md +40 -0
  22. package/.specify/templates/plan-template.md +105 -0
  23. package/.specify/templates/spec-template.md +115 -0
  24. package/.specify/templates/tasks-template.md +250 -0
  25. package/CLAUDE.md +105 -0
  26. package/CONTRIBUTING.md +97 -0
  27. package/README.md +126 -0
  28. package/components.json +21 -0
  29. package/eslint.config.js +25 -0
  30. package/netlify.toml +50 -0
  31. package/package.json +94 -0
  32. package/playground/README.md +75 -0
  33. package/playground/index.html +22 -0
  34. package/playground/package.json +32 -0
  35. package/playground/public/favicon.svg +8 -0
  36. package/playground/src/App.css +256 -0
  37. package/playground/src/App.tsx +115 -0
  38. package/playground/src/assets/react.svg +1 -0
  39. package/playground/src/components/ErrorDisplay.tsx +13 -0
  40. package/playground/src/components/ExampleSelector.tsx +64 -0
  41. package/playground/src/components/Header.tsx +47 -0
  42. package/playground/src/components/JsonEditor.tsx +32 -0
  43. package/playground/src/components/Preview.tsx +78 -0
  44. package/playground/src/components/ThemeToggle.tsx +19 -0
  45. package/playground/src/data/examples.ts +1571 -0
  46. package/playground/src/hooks/useTheme.ts +55 -0
  47. package/playground/src/index.css +220 -0
  48. package/playground/src/main.tsx +10 -0
  49. package/playground/tsconfig.app.json +34 -0
  50. package/playground/tsconfig.json +13 -0
  51. package/playground/tsconfig.node.json +26 -0
  52. package/playground/vite.config.ts +31 -0
  53. package/specs/001-a2ui-renderer/checklists/requirements.md +41 -0
  54. package/specs/001-a2ui-renderer/data-model.md +140 -0
  55. package/specs/001-a2ui-renderer/plan.md +123 -0
  56. package/specs/001-a2ui-renderer/quickstart.md +141 -0
  57. package/specs/001-a2ui-renderer/research.md +140 -0
  58. package/specs/001-a2ui-renderer/spec.md +165 -0
  59. package/specs/001-a2ui-renderer/tasks.md +310 -0
  60. package/specs/002-playground/checklists/requirements.md +37 -0
  61. package/specs/002-playground/contracts/components.md +120 -0
  62. package/specs/002-playground/data-model.md +149 -0
  63. package/specs/002-playground/plan.md +73 -0
  64. package/specs/002-playground/quickstart.md +158 -0
  65. package/specs/002-playground/research.md +117 -0
  66. package/specs/002-playground/spec.md +109 -0
  67. package/specs/002-playground/tasks.md +224 -0
  68. package/src/0.8/A2UIRender.test.tsx +793 -0
  69. package/src/0.8/A2UIRender.tsx +142 -0
  70. package/src/0.8/components/ComponentRenderer.test.tsx +373 -0
  71. package/src/0.8/components/ComponentRenderer.tsx +163 -0
  72. package/src/0.8/components/UnknownComponent.tsx +49 -0
  73. package/src/0.8/components/display/AudioPlayerComponent.tsx +37 -0
  74. package/src/0.8/components/display/DividerComponent.tsx +23 -0
  75. package/src/0.8/components/display/IconComponent.tsx +137 -0
  76. package/src/0.8/components/display/ImageComponent.tsx +57 -0
  77. package/src/0.8/components/display/TextComponent.tsx +56 -0
  78. package/src/0.8/components/display/VideoComponent.tsx +31 -0
  79. package/src/0.8/components/display/display.test.tsx +660 -0
  80. package/src/0.8/components/display/index.ts +10 -0
  81. package/src/0.8/components/index.ts +14 -0
  82. package/src/0.8/components/interactive/ButtonComponent.tsx +44 -0
  83. package/src/0.8/components/interactive/CheckBoxComponent.tsx +45 -0
  84. package/src/0.8/components/interactive/DateTimeInputComponent.tsx +176 -0
  85. package/src/0.8/components/interactive/MultipleChoiceComponent.tsx +157 -0
  86. package/src/0.8/components/interactive/SliderComponent.tsx +53 -0
  87. package/src/0.8/components/interactive/TextFieldComponent.tsx +65 -0
  88. package/src/0.8/components/interactive/index.ts +10 -0
  89. package/src/0.8/components/interactive/interactive.test.tsx +618 -0
  90. package/src/0.8/components/layout/CardComponent.tsx +30 -0
  91. package/src/0.8/components/layout/ColumnComponent.tsx +93 -0
  92. package/src/0.8/components/layout/ListComponent.tsx +81 -0
  93. package/src/0.8/components/layout/ModalComponent.tsx +41 -0
  94. package/src/0.8/components/layout/RowComponent.tsx +94 -0
  95. package/src/0.8/components/layout/TabsComponent.tsx +59 -0
  96. package/src/0.8/components/layout/index.ts +10 -0
  97. package/src/0.8/components/layout/layout.test.tsx +558 -0
  98. package/src/0.8/contexts/A2UIProvider.test.tsx +226 -0
  99. package/src/0.8/contexts/A2UIProvider.tsx +54 -0
  100. package/src/0.8/contexts/ActionContext.test.tsx +242 -0
  101. package/src/0.8/contexts/ActionContext.tsx +105 -0
  102. package/src/0.8/contexts/ComponentsMapContext.tsx +125 -0
  103. package/src/0.8/contexts/DataModelContext.test.tsx +335 -0
  104. package/src/0.8/contexts/DataModelContext.tsx +184 -0
  105. package/src/0.8/contexts/SurfaceContext.test.tsx +339 -0
  106. package/src/0.8/contexts/SurfaceContext.tsx +197 -0
  107. package/src/0.8/hooks/useA2UIMessageHandler.test.tsx +399 -0
  108. package/src/0.8/hooks/useA2UIMessageHandler.ts +123 -0
  109. package/src/0.8/hooks/useComponent.test.tsx +148 -0
  110. package/src/0.8/hooks/useComponent.ts +39 -0
  111. package/src/0.8/hooks/useDataBinding.test.tsx +334 -0
  112. package/src/0.8/hooks/useDataBinding.ts +99 -0
  113. package/src/0.8/hooks/useDispatchAction.test.tsx +83 -0
  114. package/src/0.8/hooks/useDispatchAction.ts +35 -0
  115. package/src/0.8/hooks/useSurface.test.tsx +114 -0
  116. package/src/0.8/hooks/useSurface.ts +34 -0
  117. package/src/0.8/index.ts +38 -0
  118. package/src/0.8/schemas/client_to_server.json +50 -0
  119. package/src/0.8/schemas/server_to_client.json +148 -0
  120. package/src/0.8/schemas/standard_catalog_definition.json +661 -0
  121. package/src/0.8/types/index.ts +448 -0
  122. package/src/0.8/utils/dataBinding.test.ts +443 -0
  123. package/src/0.8/utils/dataBinding.ts +212 -0
  124. package/src/0.8/utils/pathUtils.test.ts +353 -0
  125. package/src/0.8/utils/pathUtils.ts +200 -0
  126. package/src/components/ui/button.tsx +62 -0
  127. package/src/components/ui/calendar.tsx +220 -0
  128. package/src/components/ui/card.tsx +92 -0
  129. package/src/components/ui/checkbox.tsx +30 -0
  130. package/src/components/ui/dialog.tsx +141 -0
  131. package/src/components/ui/input.tsx +21 -0
  132. package/src/components/ui/label.tsx +22 -0
  133. package/src/components/ui/native-select.tsx +53 -0
  134. package/src/components/ui/popover.tsx +46 -0
  135. package/src/components/ui/select.tsx +188 -0
  136. package/src/components/ui/separator.tsx +26 -0
  137. package/src/components/ui/slider.tsx +61 -0
  138. package/src/components/ui/tabs.tsx +64 -0
  139. package/src/components/ui/textarea.tsx +18 -0
  140. package/src/index.ts +1 -0
  141. package/src/lib/utils.ts +6 -0
  142. package/tsconfig.json +28 -0
  143. package/vite.config.ts +29 -0
  144. package/vitest.config.ts +22 -0
  145. package/vitest.setup.ts +8 -0
  146. package/website/README.md +4 -0
  147. package/website/assets/favicon.svg +8 -0
  148. package/website/content/.gitkeep +0 -0
  149. package/website/content/index.md +122 -0
  150. package/website/global.d.ts +9 -0
  151. package/website/package.json +17 -0
  152. package/website/plain.config.js +28 -0
  153. package/website/serve.json +6 -0
  154. package/website/src/client/color-mode-switch.css +47 -0
  155. package/website/src/client/index.js +61 -0
  156. package/website/src/client/moon.svg +1 -0
  157. package/website/src/client/sun.svg +1 -0
  158. package/website/src/components/Footer.jsx +9 -0
  159. package/website/src/components/Header.jsx +44 -0
  160. package/website/src/components/Page.jsx +28 -0
  161. package/website/src/global.css +423 -0
@@ -0,0 +1,188 @@
1
+ import * as React from 'react'
2
+ import * as SelectPrimitive from '@radix-ui/react-select'
3
+ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react'
4
+
5
+ import { cn } from '@/lib/utils'
6
+
7
+ function Select({
8
+ ...props
9
+ }: React.ComponentProps<typeof SelectPrimitive.Root>) {
10
+ return <SelectPrimitive.Root data-slot="select" {...props} />
11
+ }
12
+
13
+ function SelectGroup({
14
+ ...props
15
+ }: React.ComponentProps<typeof SelectPrimitive.Group>) {
16
+ return <SelectPrimitive.Group data-slot="select-group" {...props} />
17
+ }
18
+
19
+ function SelectValue({
20
+ ...props
21
+ }: React.ComponentProps<typeof SelectPrimitive.Value>) {
22
+ return <SelectPrimitive.Value data-slot="select-value" {...props} />
23
+ }
24
+
25
+ function SelectTrigger({
26
+ className,
27
+ size = 'default',
28
+ children,
29
+ ...props
30
+ }: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
31
+ size?: 'sm' | 'default'
32
+ }) {
33
+ return (
34
+ <SelectPrimitive.Trigger
35
+ data-slot="select-trigger"
36
+ data-size={size}
37
+ className={cn(
38
+ "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
39
+ className
40
+ )}
41
+ {...props}
42
+ >
43
+ {children}
44
+ <SelectPrimitive.Icon asChild>
45
+ <ChevronDownIcon className="size-4 opacity-50" />
46
+ </SelectPrimitive.Icon>
47
+ </SelectPrimitive.Trigger>
48
+ )
49
+ }
50
+
51
+ function SelectContent({
52
+ className,
53
+ children,
54
+ position = 'item-aligned',
55
+ align = 'center',
56
+ ...props
57
+ }: React.ComponentProps<typeof SelectPrimitive.Content>) {
58
+ return (
59
+ <SelectPrimitive.Portal>
60
+ <SelectPrimitive.Content
61
+ data-slot="select-content"
62
+ className={cn(
63
+ 'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md',
64
+ position === 'popper' &&
65
+ 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
66
+ className
67
+ )}
68
+ position={position}
69
+ align={align}
70
+ {...props}
71
+ >
72
+ <SelectScrollUpButton />
73
+ <SelectPrimitive.Viewport
74
+ className={cn(
75
+ 'p-1',
76
+ position === 'popper' &&
77
+ 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1'
78
+ )}
79
+ >
80
+ {children}
81
+ </SelectPrimitive.Viewport>
82
+ <SelectScrollDownButton />
83
+ </SelectPrimitive.Content>
84
+ </SelectPrimitive.Portal>
85
+ )
86
+ }
87
+
88
+ function SelectLabel({
89
+ className,
90
+ ...props
91
+ }: React.ComponentProps<typeof SelectPrimitive.Label>) {
92
+ return (
93
+ <SelectPrimitive.Label
94
+ data-slot="select-label"
95
+ className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}
96
+ {...props}
97
+ />
98
+ )
99
+ }
100
+
101
+ function SelectItem({
102
+ className,
103
+ children,
104
+ ...props
105
+ }: React.ComponentProps<typeof SelectPrimitive.Item>) {
106
+ return (
107
+ <SelectPrimitive.Item
108
+ data-slot="select-item"
109
+ className={cn(
110
+ "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
111
+ className
112
+ )}
113
+ {...props}
114
+ >
115
+ <span
116
+ data-slot="select-item-indicator"
117
+ className="absolute right-2 flex size-3.5 items-center justify-center"
118
+ >
119
+ <SelectPrimitive.ItemIndicator>
120
+ <CheckIcon className="size-4" />
121
+ </SelectPrimitive.ItemIndicator>
122
+ </span>
123
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
124
+ </SelectPrimitive.Item>
125
+ )
126
+ }
127
+
128
+ function SelectSeparator({
129
+ className,
130
+ ...props
131
+ }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
132
+ return (
133
+ <SelectPrimitive.Separator
134
+ data-slot="select-separator"
135
+ className={cn('bg-border pointer-events-none -mx-1 my-1 h-px', className)}
136
+ {...props}
137
+ />
138
+ )
139
+ }
140
+
141
+ function SelectScrollUpButton({
142
+ className,
143
+ ...props
144
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
145
+ return (
146
+ <SelectPrimitive.ScrollUpButton
147
+ data-slot="select-scroll-up-button"
148
+ className={cn(
149
+ 'flex cursor-default items-center justify-center py-1',
150
+ className
151
+ )}
152
+ {...props}
153
+ >
154
+ <ChevronUpIcon className="size-4" />
155
+ </SelectPrimitive.ScrollUpButton>
156
+ )
157
+ }
158
+
159
+ function SelectScrollDownButton({
160
+ className,
161
+ ...props
162
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
163
+ return (
164
+ <SelectPrimitive.ScrollDownButton
165
+ data-slot="select-scroll-down-button"
166
+ className={cn(
167
+ 'flex cursor-default items-center justify-center py-1',
168
+ className
169
+ )}
170
+ {...props}
171
+ >
172
+ <ChevronDownIcon className="size-4" />
173
+ </SelectPrimitive.ScrollDownButton>
174
+ )
175
+ }
176
+
177
+ export {
178
+ Select,
179
+ SelectContent,
180
+ SelectGroup,
181
+ SelectItem,
182
+ SelectLabel,
183
+ SelectScrollDownButton,
184
+ SelectScrollUpButton,
185
+ SelectSeparator,
186
+ SelectTrigger,
187
+ SelectValue,
188
+ }
@@ -0,0 +1,26 @@
1
+ import * as React from 'react'
2
+ import * as SeparatorPrimitive from '@radix-ui/react-separator'
3
+
4
+ import { cn } from '@/lib/utils'
5
+
6
+ function Separator({
7
+ className,
8
+ orientation = 'horizontal',
9
+ decorative = true,
10
+ ...props
11
+ }: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
12
+ return (
13
+ <SeparatorPrimitive.Root
14
+ data-slot="separator"
15
+ decorative={decorative}
16
+ orientation={orientation}
17
+ className={cn(
18
+ 'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',
19
+ className
20
+ )}
21
+ {...props}
22
+ />
23
+ )
24
+ }
25
+
26
+ export { Separator }
@@ -0,0 +1,61 @@
1
+ import * as React from 'react'
2
+ import * as SliderPrimitive from '@radix-ui/react-slider'
3
+
4
+ import { cn } from '@/lib/utils'
5
+
6
+ function Slider({
7
+ className,
8
+ defaultValue,
9
+ value,
10
+ min = 0,
11
+ max = 100,
12
+ ...props
13
+ }: React.ComponentProps<typeof SliderPrimitive.Root>) {
14
+ const _values = React.useMemo(
15
+ () =>
16
+ Array.isArray(value)
17
+ ? value
18
+ : Array.isArray(defaultValue)
19
+ ? defaultValue
20
+ : [min, max],
21
+ [value, defaultValue, min, max]
22
+ )
23
+
24
+ return (
25
+ <SliderPrimitive.Root
26
+ data-slot="slider"
27
+ defaultValue={defaultValue}
28
+ value={value}
29
+ min={min}
30
+ max={max}
31
+ className={cn(
32
+ 'relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col',
33
+ className
34
+ )}
35
+ {...props}
36
+ >
37
+ <SliderPrimitive.Track
38
+ data-slot="slider-track"
39
+ className={cn(
40
+ 'bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5'
41
+ )}
42
+ >
43
+ <SliderPrimitive.Range
44
+ data-slot="slider-range"
45
+ className={cn(
46
+ 'bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full'
47
+ )}
48
+ />
49
+ </SliderPrimitive.Track>
50
+ {Array.from({ length: _values.length }, (_, index) => (
51
+ <SliderPrimitive.Thumb
52
+ data-slot="slider-thumb"
53
+ key={index}
54
+ className="border-primary ring-ring/50 block size-4 shrink-0 rounded-full border bg-white shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
55
+ />
56
+ ))}
57
+ </SliderPrimitive.Root>
58
+ )
59
+ }
60
+
61
+ export { Slider }
@@ -0,0 +1,64 @@
1
+ import * as React from 'react'
2
+ import * as TabsPrimitive from '@radix-ui/react-tabs'
3
+
4
+ import { cn } from '@/lib/utils'
5
+
6
+ function Tabs({
7
+ className,
8
+ ...props
9
+ }: React.ComponentProps<typeof TabsPrimitive.Root>) {
10
+ return (
11
+ <TabsPrimitive.Root
12
+ data-slot="tabs"
13
+ className={cn('flex flex-col gap-2', className)}
14
+ {...props}
15
+ />
16
+ )
17
+ }
18
+
19
+ function TabsList({
20
+ className,
21
+ ...props
22
+ }: React.ComponentProps<typeof TabsPrimitive.List>) {
23
+ return (
24
+ <TabsPrimitive.List
25
+ data-slot="tabs-list"
26
+ className={cn(
27
+ 'bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]',
28
+ className
29
+ )}
30
+ {...props}
31
+ />
32
+ )
33
+ }
34
+
35
+ function TabsTrigger({
36
+ className,
37
+ ...props
38
+ }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
39
+ return (
40
+ <TabsPrimitive.Trigger
41
+ data-slot="tabs-trigger"
42
+ className={cn(
43
+ "data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
44
+ className
45
+ )}
46
+ {...props}
47
+ />
48
+ )
49
+ }
50
+
51
+ function TabsContent({
52
+ className,
53
+ ...props
54
+ }: React.ComponentProps<typeof TabsPrimitive.Content>) {
55
+ return (
56
+ <TabsPrimitive.Content
57
+ data-slot="tabs-content"
58
+ className={cn('flex-1 outline-none', className)}
59
+ {...props}
60
+ />
61
+ )
62
+ }
63
+
64
+ export { Tabs, TabsList, TabsTrigger, TabsContent }
@@ -0,0 +1,18 @@
1
+ import * as React from 'react'
2
+
3
+ import { cn } from '@/lib/utils'
4
+
5
+ function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {
6
+ return (
7
+ <textarea
8
+ data-slot="textarea"
9
+ className={cn(
10
+ 'border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
11
+ className
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ export { Textarea }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * as v0_8 from './0.8'
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from 'clsx'
2
+ import { twMerge } from 'tailwind-merge'
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+
9
+ "moduleResolution": "bundler",
10
+ "allowImportingTsExtensions": true,
11
+ "isolatedModules": true,
12
+ "moduleDetection": "force",
13
+ "noEmit": true,
14
+ "jsx": "react-jsx",
15
+
16
+ "strict": true,
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "noFallthroughCasesInSwitch": true,
20
+ "noUncheckedSideEffectImports": true,
21
+
22
+ "baseUrl": ".",
23
+ "paths": {
24
+ "@/*": ["./src/*"]
25
+ }
26
+ },
27
+ "include": ["src", "vite.config.ts", "vitest.setup.ts"]
28
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { resolve } from 'node:path'
2
+ import { defineConfig } from 'vite'
3
+ import react from '@vitejs/plugin-react'
4
+ import dts from 'vite-plugin-dts'
5
+ import tsconfigPaths from 'vite-tsconfig-paths'
6
+
7
+ export default defineConfig({
8
+ plugins: [
9
+ react(),
10
+ tsconfigPaths(),
11
+ dts({
12
+ include: ['src'],
13
+ outDir: 'dist',
14
+ }),
15
+ ],
16
+ build: {
17
+ lib: {
18
+ entry: resolve(__dirname, 'src/index.ts'),
19
+ formats: ['es'],
20
+ },
21
+ rollupOptions: {
22
+ external: ['react', 'react-dom', 'react/jsx-runtime'],
23
+ output: {
24
+ preserveModules: true,
25
+ preserveModulesRoot: 'src',
26
+ },
27
+ },
28
+ },
29
+ })
@@ -0,0 +1,22 @@
1
+ import { defineConfig } from 'vitest/config'
2
+ import react from '@vitejs/plugin-react'
3
+ import { resolve } from 'path'
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ resolve: {
8
+ alias: {
9
+ '@': resolve(__dirname, './src'),
10
+ },
11
+ },
12
+ test: {
13
+ globals: true,
14
+ environment: 'jsdom',
15
+ setupFiles: ['./vitest.setup.ts'],
16
+ include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
17
+ coverage: {
18
+ provider: 'v8',
19
+ reporter: ['text', 'json', 'html'],
20
+ },
21
+ },
22
+ })
@@ -0,0 +1,8 @@
1
+ import '@testing-library/jest-dom/vitest'
2
+
3
+ // Mock ResizeObserver for Radix UI components (Slider, etc.)
4
+ global.ResizeObserver = class ResizeObserver {
5
+ observe() {}
6
+ unobserve() {}
7
+ disconnect() {}
8
+ }
@@ -0,0 +1,4 @@
1
+ ```sh
2
+ npm run build -w website
3
+ npm run serve -w website
4
+ ```
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
2
+ <!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
3
+ <!-- <rect x="0" y="0" width="512" height="512" fill="white" stroke="none"></rect> -->
4
+ <circle cx="256" cy="256" r="256" fill="white" stroke="none"></circle>
5
+ <g transform="translate(112 64) scale(0.75)">
6
+ <path d="M64 464c-8.8 0-16-7.2-16-16L48 64c0-8.8 7.2-16 16-16l160 0 0 80c0 17.7 14.3 32 32 32l80 0 0 288c0 8.8-7.2 16-16 16L64 464zM64 0C28.7 0 0 28.7 0 64L0 448c0 35.3 28.7 64 64 64l256 0c35.3 0 64-28.7 64-64l0-293.5c0-17-6.7-33.3-18.7-45.3L274.7 18.7C262.7 6.7 246.5 0 229.5 0L64 0zm56 256c-13.3 0-24 10.7-24 24s10.7 24 24 24l144 0c13.3 0 24-10.7 24-24s-10.7-24-24-24l-144 0zm0 96c-13.3 0-24 10.7-24 24s10.7 24 24 24l144 0c13.3 0 24-10.7 24-24s-10.7-24-24-24l-144 0z" fill="#009488" stroke="none" />
7
+ </g>
8
+ </svg>
File without changes
@@ -0,0 +1,122 @@
1
+ A React renderer library for [A2UI](https://a2ui.org) protocol.
2
+
3
+ Supports all components in A2UI standard catalog out of the box. Built with [shadcn/ui](https://ui.shadcn.com/) and [Tailwind CSS](https://tailwindcss.com/).
4
+
5
+ Currently A2UI protocol v0.8 is fully supported. Work on v0.9 is in progress.
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ npm install @easyops-cn/a2ui-react
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ First, use the `@source` directive to tell Tailwind to scan the library code for class names in your global CSS:
16
+
17
+ ```css
18
+ @source "../node_modules/@easyops-cn/a2ui-react";
19
+ ```
20
+
21
+ Next, use the `A2UIRender` component to render A2UI messages:
22
+
23
+ ```tsx
24
+ import {
25
+ A2UIRender,
26
+ type A2UIMessage,
27
+ type A2UIAction,
28
+ } from '@easyops-cn/a2ui-react/0.8'
29
+
30
+ function App() {
31
+ const messages: A2UIMessage[] = []
32
+
33
+ const handleAction = (action: A2UIAction) => {
34
+ console.log('Action received:', action)
35
+ }
36
+
37
+ return <A2UIRender messages={messages} onAction={handleAction} />
38
+ }
39
+ ```
40
+
41
+ ### Custom components
42
+
43
+ You can override default components or add new custom components via the `components` prop, which takes a `Map<string, React.ComponentType>`.
44
+
45
+ ```tsx
46
+ import { A2UIRender, A2UIMessage, A2UIAction } from '@easyops-cn/a2ui-react/0.8'
47
+
48
+ const ComponentsMap = new Map<string, React.ComponentType<any>>([
49
+ // Override default Button component with a custom one
50
+ ['Button', CustomButtonComponent],
51
+
52
+ // Add a new custom Switch component
53
+ ['Switch', CustomSwitchComponent],
54
+ ])
55
+
56
+ function App() {
57
+ return (
58
+ <A2UIRender
59
+ components={ComponentsMap}
60
+ messages={messages}
61
+ onAction={handleAction}
62
+ />
63
+ )
64
+ }
65
+ ```
66
+
67
+ Custom button component with action dispatch:
68
+
69
+ ```tsx
70
+ import {
71
+ useDispatchAction,
72
+ ComponentRenderer,
73
+ type ButtonComponentProps,
74
+ } from '@easyops-cn/a2ui-react/0.8'
75
+
76
+ export function CustomButtonComponent({
77
+ surfaceId,
78
+ componentId,
79
+ child,
80
+ action,
81
+ }: ButtonComponentProps) {
82
+ const dispatchAction = useDispatchAction()
83
+
84
+ const handleClick = () => {
85
+ if (action) {
86
+ dispatchAction(surfaceId, componentId, action)
87
+ }
88
+ }
89
+
90
+ return (
91
+ <button onClick={handleClick}>
92
+ <ComponentRenderer surfaceId={surfaceId} componentId={child} />
93
+ </button>
94
+ )
95
+ }
96
+ ```
97
+
98
+ Custom switch component with data binding:
99
+
100
+ ```tsx
101
+ import { useDataBinding, useFormBinding } from '@easyops-cn/a2ui-react/0.8'
102
+
103
+ export function CustomSwitchComponent({
104
+ surfaceId,
105
+ componentId,
106
+ label,
107
+ value,
108
+ }: SwitchComponentProps) {
109
+ const labelText = useDataBinding<string>(surfaceId, label, '')
110
+ const [checked, setChecked] = useFormBinding<boolean>(surfaceId, value, false)
111
+
112
+ const handleChange = (newChecked: boolean) => {
113
+ setChecked(newChecked)
114
+ }
115
+
116
+ return (
117
+ <Switch checked={checked} onChange={handleChange}>
118
+ {labelText}
119
+ </Switch>
120
+ )
121
+ }
122
+ ```
@@ -0,0 +1,9 @@
1
+ declare module '*.svg' {
2
+ const content: string
3
+ export default content
4
+ }
5
+
6
+ declare module '*.css' {
7
+ const content: string
8
+ export default content
9
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "website",
3
+ "type": "module",
4
+ "private": true,
5
+ "scripts": {
6
+ "build": "plain-blog",
7
+ "watch": "node --watch ../node_modules/.bin/plain-blog",
8
+ "serve": "serve ./dist -c ../serve.json"
9
+ },
10
+ "dependencies": {
11
+ "react": "^19.2.3"
12
+ },
13
+ "devDependencies": {
14
+ "plain-blog": "^0.6.5",
15
+ "serve": "^14.2.5"
16
+ }
17
+ }
@@ -0,0 +1,28 @@
1
+ // @ts-check
2
+ /** @type {import("plain-blog").SiteConfig} */
3
+ export default {
4
+ baseUrl: '/a2ui-react/',
5
+ site: {
6
+ title: 'A2UI React Renderer',
7
+ description: 'A React renderer for A2UI protocol',
8
+ favicon: 'assets/favicon.svg',
9
+ url: 'https://a2ui-react.js.org',
10
+ },
11
+ locales: ['en'],
12
+ toc: true,
13
+ shiki: {
14
+ // theme: "light-plus",
15
+ themes: {
16
+ light: 'light-plus',
17
+ dark: 'dark-plus',
18
+ },
19
+ },
20
+ components: {
21
+ // Home: 'src/components/Home.jsx',
22
+ Page: 'src/components/Page.jsx',
23
+ Header: 'src/components/Header.jsx',
24
+ Footer: 'src/components/Footer.jsx',
25
+ },
26
+ styles: ['src/global.css'],
27
+ scripts: ['src/client/index.js'],
28
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "rewrites": [
3
+ { "source": "a2ui-react/assets/:path*", "destination": "/assets/:path*" },
4
+ { "source": "a2ui-react/**", "destination": "/index.html" }
5
+ ]
6
+ }