Sandbox
Code execution sandbox: state header with tabs for code and output.
1const r = await quote("NVDAx") 2console.log(r.price)
188.20 ✓ done in 412 ms
1const r = await quote("NVDAx") 2console.log(r.price)
188.20 ✓ done in 412 ms
1const r = await quote("NVDAx") 2console.log(r.price)
Waiting for output…
const r = await quote("NVDAx")188.20 ✓ done in 412 ms
import { Sandbox, SandboxContent, SandboxHeader, SandboxTabContent, SandboxTabs, SandboxTabsBar, SandboxTabsList, SandboxTabsTrigger,} from "@/components/ai/sandbox";
const Code = () => ( <pre className="m-0 overflow-hidden rounded-md border border-border bg-card py-2.5 font-mono text-[12.5px] leading-[1.7]"> <span className="mr-3.5 inline-block w-[38px] pr-3.5 text-right text-muted-foreground-2 select-none"> 1 </span> <span className="text-chart-2">const</span> r ={" "} <span className="text-chart-2">await</span>{" "} <span className="text-chart-4">quote</span>( <span className="text-success-text">"NVDAx"</span>){"\n"} <span className="mr-3.5 inline-block w-[38px] pr-3.5 text-right text-muted-foreground-2 select-none"> 2 </span> <span className="text-chart-4">console</span>.log(r.price) </pre>);
export default function Demo() { return ( <div className="flex w-full max-w-xl flex-col gap-4"> <Sandbox> <SandboxHeader state="input-available" title="quote.ts" /> <SandboxContent> <SandboxTabs defaultValue="code"> <SandboxTabsBar> <SandboxTabsList> <SandboxTabsTrigger value="code">Code</SandboxTabsTrigger> <SandboxTabsTrigger value="output">Output</SandboxTabsTrigger> </SandboxTabsList> </SandboxTabsBar> <SandboxTabContent value="code"> <Code /> </SandboxTabContent> <SandboxTabContent value="output"> <pre className="m-0 rounded-md bg-muted px-3 py-2.5 font-mono text-xs leading-[1.6]"> Waiting for output… </pre> </SandboxTabContent> </SandboxTabs> </SandboxContent> </Sandbox> <Sandbox> <SandboxHeader state="output-available" title="quote.ts" /> <SandboxContent> <SandboxTabs defaultValue="output"> <SandboxTabsBar> <SandboxTabsList> <SandboxTabsTrigger value="code">Code</SandboxTabsTrigger> <SandboxTabsTrigger value="output">Output</SandboxTabsTrigger> </SandboxTabsList> </SandboxTabsBar> <SandboxTabContent value="code"> <Code /> </SandboxTabContent> <SandboxTabContent value="output"> <pre className="m-0 rounded-md bg-muted px-3 py-2.5 font-mono text-xs leading-[1.6]"> {"188.20\n✓ done in 412 ms"} </pre> </SandboxTabContent> </SandboxTabs> </SandboxContent> </Sandbox> </div> );}<script setup lang="ts">import { Sandbox, SandboxContent, SandboxHeader, SandboxTabContent, SandboxTabs, SandboxTabsBar, SandboxTabsList, SandboxTabsTrigger,} from "@/components/ai/sandbox";</script>
<template> <div class="flex w-full max-w-xl flex-col gap-4"> <Sandbox> <SandboxHeader state="input-available" title="quote.ts" /> <SandboxContent> <SandboxTabs default-value="code"> <SandboxTabsBar> <SandboxTabsList> <SandboxTabsTrigger value="code">Code</SandboxTabsTrigger> <SandboxTabsTrigger value="output">Output</SandboxTabsTrigger> </SandboxTabsList> </SandboxTabsBar> <SandboxTabContent value="code"> <pre class="m-0 overflow-hidden rounded-md border border-border bg-card py-2.5 font-mono text-[12.5px] leading-[1.7]"><span class="mr-3.5 inline-block w-[38px] pr-3.5 text-right text-muted-foreground-2 select-none">1</span><span class="text-chart-2">const</span> r = <span class="text-chart-2">await</span> <span class="text-chart-4">quote</span>(<span class="text-success-text">"NVDAx"</span>)<span class="mr-3.5 inline-block w-[38px] pr-3.5 text-right text-muted-foreground-2 select-none">2</span><span class="text-chart-4">console</span>.log(r.price)</pre> </SandboxTabContent> <SandboxTabContent value="output"> <pre class="m-0 rounded-md bg-muted px-3 py-2.5 font-mono text-xs leading-[1.6]">Waiting for output…</pre> </SandboxTabContent> </SandboxTabs> </SandboxContent> </Sandbox> <Sandbox> <SandboxHeader state="output-available" title="quote.ts" /> <SandboxContent> <SandboxTabs default-value="output"> <SandboxTabsBar> <SandboxTabsList> <SandboxTabsTrigger value="code">Code</SandboxTabsTrigger> <SandboxTabsTrigger value="output">Output</SandboxTabsTrigger> </SandboxTabsList> </SandboxTabsBar> <SandboxTabContent value="code"> <pre class="m-0 rounded-md bg-muted px-3 py-2.5 font-mono text-xs leading-[1.6]">const r = await quote("NVDAx")</pre> </SandboxTabContent> <SandboxTabContent value="output"> <pre class="m-0 rounded-md bg-muted px-3 py-2.5 font-mono text-xs leading-[1.6]">188.20✓ done in 412 ms</pre> </SandboxTabContent> </SandboxTabs> </SandboxContent> </Sandbox> </div></template><script lang="ts"> import { Sandbox, SandboxContent, SandboxHeader, SandboxTabContent, SandboxTabs, SandboxTabsBar, SandboxTabsList, SandboxTabsTrigger, } from "$lib/components/ai/sandbox";</script>
<div class="flex w-full max-w-xl flex-col gap-4"> <Sandbox> <SandboxHeader state="input-available" title="quote.ts" /> <SandboxContent> <SandboxTabs value="code"> <SandboxTabsBar> <SandboxTabsList> <SandboxTabsTrigger value="code">Code</SandboxTabsTrigger> <SandboxTabsTrigger value="output">Output</SandboxTabsTrigger> </SandboxTabsList> </SandboxTabsBar> <SandboxTabContent value="code"> <pre class="m-0 overflow-hidden rounded-md border border-border bg-card py-2.5 font-mono text-[12.5px] leading-[1.7]"><span class="mr-3.5 inline-block w-[38px] pr-3.5 text-right text-muted-foreground-2 select-none" >1</span ><span class="text-chart-2">const</span> r = <span class="text-chart-2">await</span> <span class="text-chart-4">quote</span >(<span class="text-success-text">"NVDAx"</span>)<span class="mr-3.5 inline-block w-[38px] pr-3.5 text-right text-muted-foreground-2 select-none">2</span><span class="text-chart-4">console</span >.log(r.price)</pre> </SandboxTabContent> <SandboxTabContent value="output"> <pre class="m-0 rounded-md bg-muted px-3 py-2.5 font-mono text-xs leading-[1.6]">Waiting for output…</pre> </SandboxTabContent> </SandboxTabs> </SandboxContent> </Sandbox> <Sandbox> <SandboxHeader state="output-available" title="quote.ts" /> <SandboxContent> <SandboxTabs value="output"> <SandboxTabsBar> <SandboxTabsList> <SandboxTabsTrigger value="code">Code</SandboxTabsTrigger> <SandboxTabsTrigger value="output">Output</SandboxTabsTrigger> </SandboxTabsList> </SandboxTabsBar> <SandboxTabContent value="code"> <pre class="m-0 rounded-md bg-muted px-3 py-2.5 font-mono text-xs leading-[1.6]">const r = await quote("NVDAx")</pre> </SandboxTabContent> <SandboxTabContent value="output"> <pre class="m-0 rounded-md bg-muted px-3 py-2.5 font-mono text-xs leading-[1.6]">188.20✓ done in 412 ms</pre> </SandboxTabContent> </SandboxTabs> </SandboxContent> </Sandbox></div>Built on collapsible, tabs and badge.
Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/ai-sandboxpnpm dlx shadcn@latest add @edmi-ui/ai-sandboxyarn dlx shadcn@latest add @edmi-ui/ai-sandboxbunx shadcn@latest add @edmi-ui/ai-sandboxPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install aiTerminal window pnpm add aiTerminal window yarn add aiTerminal window bun add aiAdd the Edmi components this one builds on:
badge,collapsible,tabs.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/ai-sandboxpnpm dlx shadcn-vue@latest add @edmi-ui/ai-sandboxyarn dlx shadcn-vue@latest add @edmi-ui/ai-sandboxbunx shadcn-vue@latest add @edmi-ui/ai-sandboxPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install ai @lucide/vue @vueuse/coreTerminal window pnpm add ai @lucide/vue @vueuse/coreTerminal window yarn add ai @lucide/vue @vueuse/coreTerminal window bun add ai @lucide/vue @vueuse/coreAdd the Edmi components this one builds on:
badge,collapsible,tabs.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-sandbox.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-sandbox.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-sandbox.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-sandbox.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
badge,collapsible,tabs.Copy the code from Component source into your project and update the import paths to match your aliases.
import { Sandbox, SandboxContent, SandboxHeader, SandboxTabContent, SandboxTabs, SandboxTabsBar, SandboxTabsList, SandboxTabsTrigger,} from "@/components/ai/sandbox";
<div className="flex w-full max-w-xl flex-col gap-4"> <Sandbox> <SandboxHeader state="input-available" title="quote.ts" /> <SandboxContent> <SandboxTabs defaultValue="code"> <SandboxTabsBar> <SandboxTabsList> <SandboxTabsTrigger value="code">Code</SandboxTabsTrigger> <SandboxTabsTrigger value="output">Output</SandboxTabsTrigger> </SandboxTabsList> </SandboxTabsBar> <SandboxTabContent value="code"> <Code /> </SandboxTabContent> <SandboxTabContent value="output"> <pre className="m-0 rounded-md bg-muted px-3 py-2.5 font-mono text-xs leading-[1.6]"> Waiting for output… </pre> </SandboxTabContent> </SandboxTabs> </SandboxContent> </Sandbox> {/* … */}</div><script setup lang="ts">import { Sandbox, SandboxContent, SandboxHeader, SandboxTabContent, SandboxTabs, SandboxTabsBar, SandboxTabsList, SandboxTabsTrigger,} from "@/components/ai/sandbox";</script>
<template> <div class="flex w-full max-w-xl flex-col gap-4"> <Sandbox> <SandboxHeader state="input-available" title="quote.ts" /> <SandboxContent> <SandboxTabs default-value="code"> <SandboxTabsBar> <SandboxTabsList> <SandboxTabsTrigger value="code">Code</SandboxTabsTrigger> <SandboxTabsTrigger value="output">Output</SandboxTabsTrigger> </SandboxTabsList> </SandboxTabsBar> <SandboxTabContent value="code"> <pre class="m-0 overflow-hidden rounded-md border border-border bg-card py-2.5 font-mono text-[12.5px] leading-[1.7]"><span class="mr-3.5 inline-block w-[38px] pr-3.5 text-right text-muted-foreground-2 select-none">1</span><span class="text-chart-2">const</span> r = <span class="text-chart-2">await</span> <span class="text-chart-4">quote</span>(<span class="text-success-text">"NVDAx"</span>) <!-- … --></template><script lang="ts"> import { Sandbox, SandboxContent, SandboxHeader, SandboxTabContent, SandboxTabs, SandboxTabsBar, SandboxTabsList, SandboxTabsTrigger, } from "$lib/components/ai/sandbox";</script>
<div class="flex w-full max-w-xl flex-col gap-4"> <Sandbox> <SandboxHeader state="input-available" title="quote.ts" /> <SandboxContent> <SandboxTabs value="code"> <SandboxTabsBar> <SandboxTabsList> <SandboxTabsTrigger value="code">Code</SandboxTabsTrigger> <SandboxTabsTrigger value="output">Output</SandboxTabsTrigger> </SandboxTabsList> </SandboxTabsBar> <SandboxTabContent value="code"> <pre class="m-0 overflow-hidden rounded-md border border-border bg-card py-2.5 font-mono text-[12.5px] leading-[1.7]"><span class="mr-3.5 inline-block w-[38px] pr-3.5 text-right text-muted-foreground-2 select-none" >1</span ><span class="text-chart-2">const</span> r = <span class="text-chart-2">await</span> <span class="text-chart-4">quote</span >(<span class="text-success-text">"NVDAx"</span>)<!-- … -->Component source
Section titled “Component source”"use client";
import type { ToolUIPart } from "ai";import { cn } from "cn";import type { ComponentProps, ReactNode } from "react";import { ClockIcon, CheckCircleIcon, TerminalIcon, CaretDownIcon } from "@phosphor-icons/react";import { Badge } from "@/registry/edmi/ui/badge";import { Collapsible, CollapsibleContent, CollapsibleTrigger,} from "@/registry/edmi/ui/collapsible";import { Tabs, TabsContent, TabsList, TabsTrigger,} from "@/registry/edmi/ui/tabs";
const CircleIcon = () => ( <svg aria-hidden="true" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.7" viewBox="0 0 24 24" > <circle cx="12" cy="12" r="9" /> </svg>);
const CircleXIcon = () => ( <svg aria-hidden="true" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.7" viewBox="0 0 24 24" > <circle cx="12" cy="12" r="9" /> <path d="m9 9 6 6M15 9l-6 6" /> </svg>);
type SandboxState = ToolUIPart["state"];
const statusLabels: Record<SandboxState, string> = { "approval-requested": "Awaiting Approval", "approval-responded": "Responded", "input-available": "Running", "input-streaming": "Pending", "output-available": "Completed", "output-denied": "Denied", "output-error": "Error",};
const statusVariants: Record< SandboxState, ComponentProps<typeof Badge>["variant"]> = { "approval-requested": "warning", "approval-responded": "info", "input-available": "info", "input-streaming": "secondary", "output-available": "success", "output-denied": "warning", "output-error": "destructive",};
const statusIcons: Record<SandboxState, ReactNode> = { "approval-requested": ( <ClockIcon /> ), "approval-responded": ( <CheckCircleIcon /> ), "input-available": ( <ClockIcon /> ), "input-streaming": <CircleIcon />, "output-available": ( <CheckCircleIcon /> ), "output-denied": <CircleXIcon />, "output-error": <CircleXIcon />,};
const getStatusBadge = (status: SandboxState) => ( <Badge shape="pill" variant={statusVariants[status]}> {statusIcons[status]} {statusLabels[status]} </Badge>);
export type SandboxRootProps = ComponentProps<typeof Collapsible>;
export const Sandbox = ({ className, ...props }: SandboxRootProps) => ( <Collapsible data-slot="ai-sandbox" className={cn( "not-prose group/ai-sandbox w-full overflow-hidden rounded-[calc(var(--radius)*1.2)] border border-border bg-card", className, )} defaultOpen {...props} />);
export interface SandboxHeaderProps { title?: string; state: ToolUIPart["state"]; className?: string;}
export const SandboxHeader = ({ className, title, state, ...props}: SandboxHeaderProps) => ( <CollapsibleTrigger data-slot="ai-sandbox-header" className={cn( "group/ai-sandbox-trigger flex w-full items-center gap-2.5 px-3.5 py-2.5 text-left outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring", className, )} {...props} > <TerminalIcon className="size-[15px] shrink-0 text-muted-foreground" /> <span className="font-mono text-[12.5px]">{title}</span> {getStatusBadge(state)} <CaretDownIcon className="ml-auto size-3.5 shrink-0 text-muted-foreground transition-transform group-data-[panel-open]/ai-sandbox-trigger:rotate-180" /> </CollapsibleTrigger>);
export type SandboxContentProps = ComponentProps<typeof CollapsibleContent>;
export const SandboxContent = ({ className, ...props}: SandboxContentProps) => ( <CollapsibleContent data-slot="ai-sandbox-content" className={cn( "h-(--collapsible-panel-height) overflow-hidden border-t border-border outline-none transition-[height] duration-200 ease-out data-[ending-style]:h-0 data-[starting-style]:h-0", className, )} {...props} />);
export type SandboxTabsProps = ComponentProps<typeof Tabs>;
export const SandboxTabs = ({ className, ...props }: SandboxTabsProps) => ( <Tabs data-slot="ai-sandbox-tabs" className={cn("w-full gap-0", className)} {...props} />);
export type SandboxTabsBarProps = ComponentProps<"div">;
export const SandboxTabsBar = ({ className, ...props}: SandboxTabsBarProps) => ( <div data-slot="ai-sandbox-tabs-bar" className={cn("flex w-full items-center px-3 pt-2.5", className)} {...props} />);
export type SandboxTabsListProps = ComponentProps<typeof TabsList>;
export const SandboxTabsList = ({ ...props }: SandboxTabsListProps) => ( <TabsList {...props} />);
export type SandboxTabsTriggerProps = ComponentProps<typeof TabsTrigger>;
export const SandboxTabsTrigger = ({ className, ...props}: SandboxTabsTriggerProps) => ( <TabsTrigger className={cn("h-[26px] px-3.5 text-[12.5px]", className)} {...props} />);
export type SandboxTabContentProps = ComponentProps<typeof TabsContent>;
export const SandboxTabContent = ({ className, ...props}: SandboxTabContentProps) => ( <TabsContent data-slot="ai-sandbox-tab-content" className={cn("px-3 pt-2.5 pb-3 text-sm", className)} {...props} />);<script setup lang="ts">import type { CollapsibleRootEmits, CollapsibleRootProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { reactiveOmit } from "@vueuse/core"import { useForwardPropsEmits } from "reka-ui"import { cn } from "@/registry/edmi/lib/utils"import { Collapsible } from "@/registry/edmi/ui/collapsible"
const props = withDefaults(defineProps<CollapsibleRootProps & { class?: HTMLAttributes["class"] }>(), { defaultOpen: true,})const emits = defineEmits<CollapsibleRootEmits>()
const delegatedProps = reactiveOmit(props, "class")const forwarded = useForwardPropsEmits(delegatedProps, emits)</script>
<template> <Collapsible data-slot="ai-sandbox" v-bind="forwarded" :class="cn( 'not-prose group/ai-sandbox w-full overflow-hidden rounded-[calc(var(--radius)*1.2)] border border-border bg-card', props.class, )" > <slot /> </Collapsible></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"import { CollapsibleContent } from "@/registry/edmi/ui/collapsible"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()</script>
<template> <CollapsibleContent data-slot="ai-sandbox-content" :class="cn('overflow-hidden border-t border-border outline-none', props.class)" > <slot /> </CollapsibleContent></template><script setup lang="ts">import type { ToolUIPart } from "ai"import type { HTMLAttributes } from "vue"import { ChevronDownIcon, Terminal } from "@lucide/vue"import { cn } from "@/registry/edmi/lib/utils"import { CollapsibleTrigger } from "@/registry/edmi/ui/collapsible"import SandboxStatusBadge from "./SandboxStatusBadge.vue"
const props = defineProps<{ title?: string state: ToolUIPart["state"] class?: HTMLAttributes["class"]}>()</script>
<template> <CollapsibleTrigger data-slot="ai-sandbox-header" :class="cn( 'group/ai-sandbox-trigger flex w-full items-center gap-2.5 px-3.5 py-2.5 text-left outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring', props.class, )" > <Terminal class="size-[15px] shrink-0 text-muted-foreground" /> <span class="font-mono text-[12.5px]">{{ props.title }}</span> <SandboxStatusBadge :state="props.state" /> <ChevronDownIcon class="ml-auto size-3.5 shrink-0 text-muted-foreground transition-transform group-data-[state=open]/ai-sandbox-trigger:rotate-180" /> </CollapsibleTrigger></template><script setup lang="ts">import type { ToolUIPart } from "ai"import type { BadgeVariants } from "@/registry/edmi/ui/badge"import { CircleCheckIcon, ClockIcon } from "@lucide/vue"import { Badge } from "@/registry/edmi/ui/badge"
type SandboxState = ToolUIPart["state"]
const props = defineProps<{ state: SandboxState}>()
const labels: Record<SandboxState, string> = { "approval-requested": "Awaiting Approval", "approval-responded": "Responded", "input-available": "Running", "input-streaming": "Pending", "output-available": "Completed", "output-denied": "Denied", "output-error": "Error",}
const variants: Record<SandboxState, BadgeVariants["variant"]> = { "approval-requested": "warning", "approval-responded": "info", "input-available": "info", "input-streaming": "secondary", "output-available": "success", "output-denied": "warning", "output-error": "destructive",}</script>
<template> <Badge shape="pill" :variant="variants[props.state]"> <svg v-if="props.state === 'input-streaming'" aria-hidden="true" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.7" viewBox="0 0 24 24" > <circle cx="12" cy="12" r="9" /> </svg> <svg v-else-if="props.state === 'output-denied' || props.state === 'output-error'" aria-hidden="true" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.7" viewBox="0 0 24 24" > <circle cx="12" cy="12" r="9" /> <path d="m9 9 6 6M15 9l-6 6" /> </svg> <ClockIcon v-else-if="props.state === 'input-available' || props.state === 'approval-requested'" /> <CircleCheckIcon v-else /> {{ labels[props.state] }} </Badge></template><script setup lang="ts">import type { TabsContentProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { reactiveOmit } from "@vueuse/core"import { cn } from "@/registry/edmi/lib/utils"import { TabsContent } from "@/registry/edmi/ui/tabs"
const props = defineProps<TabsContentProps & { class?: HTMLAttributes["class"] }>()
const delegatedProps = reactiveOmit(props, "class")</script>
<template> <TabsContent data-slot="ai-sandbox-tab-content" v-bind="delegatedProps" :class="cn('px-3 pt-2.5 pb-3 text-sm', props.class)" > <slot /> </TabsContent></template><script setup lang="ts">import type { TabsRootEmits, TabsRootProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { reactiveOmit } from "@vueuse/core"import { useForwardPropsEmits } from "reka-ui"import { cn } from "@/registry/edmi/lib/utils"import { Tabs } from "@/registry/edmi/ui/tabs"
const props = defineProps<TabsRootProps & { class?: HTMLAttributes["class"] }>()const emits = defineEmits<TabsRootEmits>()
const delegatedProps = reactiveOmit(props, "class")const forwarded = useForwardPropsEmits(delegatedProps, emits)</script>
<template> <Tabs data-slot="ai-sandbox-tabs" v-bind="forwarded" :class="cn('w-full gap-0', props.class)" > <slot /> </Tabs></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()</script>
<template> <div data-slot="ai-sandbox-tabs-bar" :class="cn('flex w-full items-center px-3 pt-2.5', props.class)" > <slot /> </div></template><script setup lang="ts">import type { TabsListProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { TabsList } from "@/registry/edmi/ui/tabs"
const props = defineProps<TabsListProps & { class?: HTMLAttributes["class"] }>()</script>
<template> <TabsList v-bind="props"> <slot /> </TabsList></template><script setup lang="ts">import type { TabsTriggerProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { reactiveOmit } from "@vueuse/core"import { cn } from "@/registry/edmi/lib/utils"import { TabsTrigger } from "@/registry/edmi/ui/tabs"
const props = defineProps<TabsTriggerProps & { class?: HTMLAttributes["class"] }>()
const delegatedProps = reactiveOmit(props, "class")</script>
<template> <TabsTrigger v-bind="delegatedProps" :class="cn('h-[26px] px-3.5 text-[12.5px]', props.class)" > <slot /> </TabsTrigger></template>export { default as Sandbox } from "./Sandbox.vue";export { default as SandboxContent } from "./SandboxContent.vue";export { default as SandboxHeader } from "./SandboxHeader.vue";export { default as SandboxTabContent } from "./SandboxTabContent.vue";export { default as SandboxTabs } from "./SandboxTabs.vue";export { default as SandboxTabsBar } from "./SandboxTabsBar.vue";export { default as SandboxTabsList } from "./SandboxTabsList.vue";export { default as SandboxTabsTrigger } from "./SandboxTabsTrigger.vue";import Sandbox from "./sandbox.svelte";import SandboxContent from "./sandbox-content.svelte";import SandboxHeader from "./sandbox-header.svelte";import SandboxTabContent from "./sandbox-tab-content.svelte";import SandboxTabs from "./sandbox-tabs.svelte";import SandboxTabsBar from "./sandbox-tabs-bar.svelte";import SandboxTabsList from "./sandbox-tabs-list.svelte";import SandboxTabsTrigger from "./sandbox-tabs-trigger.svelte";
export { Sandbox, SandboxContent, SandboxHeader, SandboxTabContent, SandboxTabs, SandboxTabsBar, SandboxTabsList, SandboxTabsTrigger,};<script lang="ts"> import * as Collapsible from "$lib/registry/ui/collapsible/index.js"; import { cn } from "$lib/utils.js"; import type { ComponentProps } from "svelte";
let { ref = $bindable(null), class: className, children, ...restProps }: ComponentProps<typeof Collapsible.Content> = $props();</script>
<Collapsible.Content bind:ref data-slot="ai-sandbox-content" class={cn("overflow-hidden border-t border-border outline-none", className)} {...restProps}> {@render children?.()}</Collapsible.Content><script lang="ts"> import TerminalIcon from "phosphor-svelte/lib/Terminal"; import CaretDownIcon from "phosphor-svelte/lib/CaretDown"; import * as Collapsible from "$lib/registry/ui/collapsible/index.js"; import { cn } from "$lib/utils.js"; import type { ToolUIPart } from "ai"; import type { ComponentProps } from "svelte"; import SandboxStatusBadge from "./sandbox-status-badge.svelte";
let { ref = $bindable(null), title, state: status, class: className, ...restProps }: Omit<ComponentProps<typeof Collapsible.Trigger>, "children"> & { title?: string; state: ToolUIPart["state"]; } = $props();</script>
<Collapsible.Trigger bind:ref data-slot="ai-sandbox-header" class={cn( "group/ai-sandbox-trigger flex w-full items-center gap-2.5 px-3.5 py-2.5 text-left outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring", className )} {...restProps}> <TerminalIcon class="size-[15px] shrink-0 text-muted-foreground" /> <span class="font-mono text-[12.5px]">{title}</span> <SandboxStatusBadge state={status} /> <CaretDownIcon class="ml-auto size-3.5 shrink-0 text-muted-foreground transition-transform group-data-[state=open]/ai-sandbox-trigger:rotate-180" /></Collapsible.Trigger><script lang="ts"> import ClockIcon from "phosphor-svelte/lib/Clock"; import CheckCircleIcon from "phosphor-svelte/lib/CheckCircle"; import { Badge, type BadgeVariant } from "$lib/registry/ui/badge/index.js"; import type { ToolUIPart } from "ai";
type SandboxState = ToolUIPart["state"];
let { state: status }: { state: SandboxState } = $props();
const labels: Record<SandboxState, string> = { "approval-requested": "Awaiting Approval", "approval-responded": "Responded", "input-available": "Running", "input-streaming": "Pending", "output-available": "Completed", "output-denied": "Denied", "output-error": "Error", };
const variants: Record<SandboxState, BadgeVariant> = { "approval-requested": "warning", "approval-responded": "info", "input-available": "info", "input-streaming": "secondary", "output-available": "success", "output-denied": "warning", "output-error": "destructive", };</script>
<Badge shape="pill" variant={variants[status]}> {#if status === "input-streaming"} <svg aria-hidden="true" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.7" viewBox="0 0 24 24" > <circle cx="12" cy="12" r="9" /> </svg> {:else if status === "output-denied" || status === "output-error"} <svg aria-hidden="true" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.7" viewBox="0 0 24 24" > <circle cx="12" cy="12" r="9" /> <path d="m9 9 6 6M15 9l-6 6" /> </svg> {:else if status === "input-available" || status === "approval-requested"} <ClockIcon /> {:else} <CheckCircleIcon /> {/if} {labels[status]}</Badge><script lang="ts"> import * as Tabs from "$lib/registry/ui/tabs/index.js"; import { cn } from "$lib/utils.js"; import type { ComponentProps } from "svelte";
let { ref = $bindable(null), class: className, ...restProps }: ComponentProps<typeof Tabs.Content> = $props();</script>
<Tabs.Content bind:ref data-slot="ai-sandbox-tab-content" class={cn("px-3 pt-2.5 pb-3 text-sm", className)} {...restProps}/><script lang="ts"> import { cn, type WithElementRef } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();</script>
<div bind:this={ref} data-slot="ai-sandbox-tabs-bar" class={cn("flex w-full items-center px-3 pt-2.5", className)} {...restProps}> {@render children?.()}</div><script lang="ts"> import * as Tabs from "$lib/registry/ui/tabs/index.js"; import type { ComponentProps } from "svelte";
let { ref = $bindable(null), ...restProps }: ComponentProps<typeof Tabs.List> = $props();</script>
<Tabs.List bind:ref {...restProps} /><script lang="ts"> import * as Tabs from "$lib/registry/ui/tabs/index.js"; import { cn } from "$lib/utils.js"; import type { ComponentProps } from "svelte";
let { ref = $bindable(null), class: className, ...restProps }: ComponentProps<typeof Tabs.Trigger> = $props();</script>
<Tabs.Trigger bind:ref class={cn("h-[26px] px-3.5 text-[12.5px]", className)} {...restProps} /><script lang="ts"> import * as Tabs from "$lib/registry/ui/tabs/index.js"; import { cn } from "$lib/utils.js"; import type { ComponentProps } from "svelte";
let { ref = $bindable(null), value = $bindable(""), class: className, children, ...restProps }: ComponentProps<typeof Tabs.Root> = $props();</script>
<Tabs.Root bind:ref bind:value data-slot="ai-sandbox-tabs" class={cn("w-full gap-0", className)} {...restProps}> {@render children?.()}</Tabs.Root><script lang="ts"> import * as Collapsible from "$lib/registry/ui/collapsible/index.js"; import { cn } from "$lib/utils.js"; import type { ComponentProps } from "svelte";
let { ref = $bindable(null), open = $bindable(true), class: className, children, ...restProps }: ComponentProps<typeof Collapsible.Root> = $props();</script>
<Collapsible.Root bind:ref bind:open data-slot="ai-sandbox" class={cn( "not-prose group/ai-sandbox w-full overflow-hidden rounded-[calc(var(--radius)*1.2)] border border-border bg-card", className )} {...restProps}> {@render children?.()}</Collapsible.Root>| Part | Prop | Type | Default |
|---|---|---|---|
| Sandbox | collapsible props | Collapsible |
defaultOpen |
| SandboxHeader | title, state |
string, ToolUIPart["state"] |
|
| SandboxContent, SandboxTabs, SandboxTabsBar, SandboxTabsList, SandboxTabsTrigger, SandboxTabContent |
state is the AI SDK tool state: input-streaming (Pending), input-available (Running), output-available (Completed), output-error (Error), plus the approval states.
✦ Edmi additions
Section titled “✦ Edmi additions”- The state badge is built in (soft
badgevariants with an icon), so the item does not depend onai-tool. - Tabs use the flat
tabstrack at 26px; the content region is a plain slot, so any code block or output can go in it.