Skip to content

Commit 8e5bcf9

Browse files
committed
feat: add WebPreview documentation and installation instructions
1 parent b1da1f0 commit 8e5bcf9

File tree

4 files changed

+253
-2
lines changed

4 files changed

+253
-2
lines changed
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
---
2+
title: Web Preview
3+
description: A composable component for previewing the result of a generated UI, with support for live examples and code display.
4+
icon: lucide:globe
5+
---
6+
7+
The `WebPreview` component provides a flexible way to showcase the result of a generated UI component, along with its source code. It is designed for documentation and demo purposes, allowing users to interact with live examples and view the underlying implementation.
8+
9+
:::ComponentLoader{label="Web Preview" componentName="WebPreview"}
10+
:::
11+
12+
## Install using CLI
13+
14+
:::tabs{variant="card"}
15+
::div{label="ai-elements-vue"}
16+
```sh
17+
npx ai-elements-vue@latest add web-preview
18+
```
19+
::
20+
::div{label="shadcn-vue"}
21+
22+
```sh
23+
npx shadcn-vue@latest add https://registry.ai-elements-vue.com/web-preview.json
24+
```
25+
::
26+
:::
27+
28+
## Install Manually
29+
<!-- TODO: Add code snippets for manual installation -->
30+
31+
## Usage with AI SDK
32+
33+
Build a simple v0 clone using the [v0 Platform API](https://v0.dev/docs/api/platform).
34+
35+
Install the `v0-sdk` package:
36+
37+
```package-install
38+
npm i v0-sdk
39+
```
40+
41+
Add the following component to your frontend:
42+
<!-- TOOD: Using Vue 3 example -->
43+
```tsx title="app/page.tsx"
44+
'use client'
45+
46+
import { useState } from 'react'
47+
import {
48+
Input,
49+
PromptInputSubmit,
50+
PromptInputTextarea,
51+
} from '@/components/ai-elements/prompt-input'
52+
import {
53+
WebPreview,
54+
WebPreviewBody,
55+
WebPreviewNavigation,
56+
WebPreviewUrl,
57+
} from '@/components/ai-elements/web-preview'
58+
import { Loader } from '../ai-elements/loader'
59+
60+
function WebPreviewDemo() {
61+
const [previewUrl, setPreviewUrl] = useState('')
62+
const [prompt, setPrompt] = useState('')
63+
const [isGenerating, setIsGenerating] = useState(false)
64+
65+
const handleSubmit = async (e: React.FormEvent) => {
66+
e.preventDefault()
67+
if (!prompt.trim())
68+
return
69+
setPrompt('')
70+
71+
setIsGenerating(true)
72+
try {
73+
const response = await fetch('/api/v0', {
74+
method: 'POST',
75+
headers: { 'Content-Type': 'application/json' },
76+
body: JSON.stringify({ prompt }),
77+
})
78+
79+
const data = await response.json()
80+
setPreviewUrl(data.demo || '/')
81+
console.log('Generation finished:', data)
82+
}
83+
catch (error) {
84+
console.error('Generation failed:', error)
85+
}
86+
finally {
87+
setIsGenerating(false)
88+
}
89+
}
90+
91+
return (
92+
<div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
93+
<div className="flex flex-col h-full">
94+
<div className="flex-1 mb-4">
95+
{isGenerating
96+
? (
97+
<div className="flex flex-col items-center justify-center h-full">
98+
<Loader />
99+
<p className="mt-4 text-muted-foreground">
100+
Generating app, this may take a few seconds...
101+
</p>
102+
</div>
103+
)
104+
: previewUrl
105+
? (
106+
<WebPreview defaultUrl={previewUrl}>
107+
<WebPreviewNavigation>
108+
<WebPreviewUrl />
109+
</WebPreviewNavigation>
110+
<WebPreviewBody src={previewUrl} />
111+
</WebPreview>
112+
)
113+
: (
114+
<div className="flex items-center justify-center h-full text-muted-foreground">
115+
Your generated app will appear here
116+
</div>
117+
)}
118+
</div>
119+
120+
<Input
121+
onSubmit={handleSubmit}
122+
className="w-full max-w-2xl mx-auto relative"
123+
>
124+
<PromptInputTextarea
125+
value={prompt}
126+
placeholder="Describe the app you want to build..."
127+
onChange={e => setPrompt(e.currentTarget.value)}
128+
className="pr-12 min-h-[60px]"
129+
/>
130+
<PromptInputSubmit
131+
status={isGenerating ? 'streaming' : 'ready'}
132+
disabled={!prompt.trim()}
133+
className="absolute bottom-1 right-1"
134+
/>
135+
</Input>
136+
</div>
137+
</div>
138+
)
139+
}
140+
141+
export default WebPreviewDemo
142+
```
143+
144+
Add the following route to your backend:
145+
146+
<!-- TOOD: Using Nuxt example -->
147+
148+
```ts title="app/api/v0/route.ts"
149+
import { v0 } from 'v0-sdk'
150+
151+
export async function POST(req: Request) {
152+
const { prompt }: { prompt: string } = await req.json()
153+
154+
const result = await v0.chats.create({
155+
system: 'You are an expert coder',
156+
message: prompt,
157+
modelConfiguration: {
158+
modelId: 'v0-1.5-sm',
159+
imageGenerations: false,
160+
thinking: false,
161+
},
162+
})
163+
164+
return Response.json({
165+
demo: result.demo,
166+
webUrl: result.webUrl,
167+
})
168+
}
169+
```
170+
171+
## Features
172+
173+
- Live preview of UI components
174+
- Composable architecture with dedicated sub-components
175+
- Responsive design modes (Desktop, Tablet, Mobile)
176+
- Navigation controls with back/forward functionality
177+
- URL input and example selector
178+
- Full screen mode support
179+
- Console logging with timestamps
180+
- Context-based state management
181+
- Consistent styling with the design system
182+
- Easy integration into documentation pages
183+
184+
## Props
185+
186+
### `<WebPreview />`
187+
188+
::::field-group
189+
::field{name="defaultUrl" type="string" defaultValue="''"}
190+
The initial URL to load in the preview.
191+
::
192+
::field{name="onUrlChange" type="(url: string) => void"}
193+
Callback fired when the URL changes.
194+
::
195+
::field{name="...props" type="HTMLAttributes"}
196+
Any other props are spread to the root div.
197+
::
198+
::::
199+
200+
### `<WebPreviewNavigation />`
201+
202+
::::field-group
203+
::field{name="...props" type="HTMLAttributes"}
204+
Any other props are spread to the navigation container.
205+
::
206+
::::
207+
208+
### `<WebPreviewNavigationButton />`
209+
210+
::::field-group
211+
::field{name="tooltip" type="string"}
212+
Tooltip text to display on hover.
213+
::
214+
::field{name="...props" type="typeof Button"}
215+
Any other props are spread to the underlying shadcn/ui Button component.
216+
::
217+
::::
218+
219+
### `<WebPreviewUrl />`
220+
221+
::::field-group
222+
::field{name="...props" type="typeof Input"}
223+
Any other props are spread to the underlying shadcn/ui Input component.
224+
::
225+
::::
226+
227+
### `<WebPreviewBody />`
228+
229+
::::field-group
230+
::field{name="loading" type="React.ReactNode"}
231+
Optional loading indicator to display over the preview.
232+
::
233+
::field{name="...props" type="IframeHTMLAttributes"}
234+
Any other props are spread to the underlying iframe.
235+
::
236+
::::
237+
238+
### `<WebPreviewConsole />`
239+
240+
::::field-group
241+
::field{name="logs" type='Array<{ level: "log" | "warn" | "error"; message: string; timestamp: Date }>'}
242+
Console log entries to display in the console panel.
243+
::
244+
::field{name="...props" type="HTMLAttributes"}
245+
Any other props are spread to the root div.
246+
::
247+
::::

apps/www/plugins/ai-elements.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import {
77
Checkpoint,
88
CodeBlock,
99
CodeBlockDark,
10-
Context,
1110
Confirmation,
1211
ConfirmationAccepted,
1312
ConfirmationRejected,
1413
ConfirmationRequest,
14+
Context,
1515
Conversation,
1616
Image,
1717
InlineCitation,
@@ -36,6 +36,7 @@ import {
3636
Suggestion,
3737
SuggestionAiInput,
3838
Task,
39+
WebPreview,
3940
Workflow,
4041
} from '@repo/examples'
4142

@@ -86,4 +87,5 @@ export default defineNuxtPlugin((nuxtApp) => {
8687
vueApp.component('ConfirmationAccepted', ConfirmationAccepted)
8788
vueApp.component('ConfirmationRejected', ConfirmationRejected)
8889
vueApp.component('ConfirmationRequest', ConfirmationRequest)
90+
vueApp.component('WebPreview', WebPreview)
8991
})

packages/elements/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ export * from './shimmer'
2121
export * from './sources'
2222
export * from './suggestion'
2323
export * from './task'
24+
export * from './web-preview'

packages/examples/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ export { default as ChainOfThought } from './chain-of-thought.vue'
66
export { default as Checkpoint } from './checkpoint.vue'
77
export { default as CodeBlockDark } from './code-block-dark.vue'
88
export { default as CodeBlock } from './code-block.vue'
9-
export { default as Context } from './context.vue'
109
export { default as ConfirmationAccepted } from './confirmation-accepted.vue'
1110
export { default as ConfirmationRejected } from './confirmation-rejected.vue'
1211
export { default as ConfirmationRequest } from './confirmation-request.vue'
1312
export { default as Confirmation } from './confirmation.vue'
13+
export { default as Context } from './context.vue'
1414
export { default as Conversation } from './conversation.vue'
1515
export { default as Image } from './image.vue'
1616
export { default as InlineCitation } from './inline-citation.vue'
@@ -35,4 +35,5 @@ export { default as Sources } from './sources.vue'
3535
export { default as SuggestionAiInput } from './suggestion-ai-input.vue'
3636
export { default as Suggestion } from './suggestion.vue'
3737
export { default as Task } from './task.vue'
38+
export { default as WebPreview } from './web-preview.vue'
3839
export { default as Workflow } from './workflow.vue'

0 commit comments

Comments
 (0)