diff --git a/apps/test/app/examples/index.ts b/apps/test/app/examples/index.ts index e265c23..c828671 100644 --- a/apps/test/app/examples/index.ts +++ b/apps/test/app/examples/index.ts @@ -8,6 +8,7 @@ export { default as Loader } from './loader.vue' export { default as MessageMarkdown } from './message-markdown.vue' export { default as Message } from './message.vue' export { default as OpenInChat } from './open-in-chat.vue' +export { default as Plan } from './plan.vue' export { default as PromptInput } from './prompt-input.vue' export { default as Response } from './response.vue' export { default as Shimmer } from './shimmer.vue' diff --git a/apps/test/app/examples/plan.vue b/apps/test/app/examples/plan.vue new file mode 100644 index 0000000..d31e0fc --- /dev/null +++ b/apps/test/app/examples/plan.vue @@ -0,0 +1,72 @@ + + + diff --git a/apps/test/app/pages/index.vue b/apps/test/app/pages/index.vue index 49d8f3e..25492a1 100644 --- a/apps/test/app/pages/index.vue +++ b/apps/test/app/pages/index.vue @@ -10,6 +10,7 @@ import Loader from '~/examples/loader.vue' import MessageMarkdown from '~/examples/message-markdown.vue' import Message from '~/examples/message.vue' import OpenInChat from '~/examples/open-in-chat.vue' +import Plan from '~/examples/plan.vue' import PromptInput from '~/examples/prompt-input.vue' import Response from '~/examples/response.vue' import Shimmer from '~/examples/shimmer.vue' @@ -34,6 +35,7 @@ const components = [ { name: 'OpenInChat', Component: OpenInChat }, { name: 'Loader', Component: Loader }, { name: 'Sources', Component: Sources }, + { name: 'Plan', Component: Plan }, ] diff --git a/apps/www/content/2.components/plan.md b/apps/www/content/2.components/plan.md new file mode 100644 index 0000000..038712f --- /dev/null +++ b/apps/www/content/2.components/plan.md @@ -0,0 +1,330 @@ +--- +title: Plan +description: +icon: lucide:map +--- + +The `Plan` component provides a flexible system for displaying AI-generated execution plans with collapsible content. Perfect for showing multi-step workflows, task breakdowns, and implementation strategies with support for streaming content and loading states. + +:::ComponentLoader{label="Preview" componentName="Plan"} +::: + +## Install using CLI + +::tabs{variant="card"} + ::div{label="ai-elements-vue"} + ```sh + npx ai-elements-vue@latest add plan + ``` + :: + ::div{label="shadcn-vue"} + + ```sh + npx shadcn-vue@latest add https://registry.ai-elements-vue.com/plan.json + ``` + :: +:: + +## Install Manually + +Copy and paste the following code in the same folder. + +:::code-group +```vue [Plan.vue] + + + +``` + +```vue [PlanHeader.vue] + + + +``` + +```vue [PlanTitle.vue] + + + +``` + +```vue [PlanDescription.vue] + + + +``` + +```vue [PlanAction.vue] + + + +``` + +```vue [PlanContent.vue] + + + +``` + +```vue [PlanFooter.vue] + + + +``` + +```vue [PlanTrigger.vue] + + + +``` + +```ts [context.ts] +import type { ComputedRef, InjectionKey } from 'vue' +import { inject, provide } from 'vue' + +export interface PlanContextValue { + isStreaming: ComputedRef +} + +export const PlanKey: InjectionKey = Symbol('PlanContext') + +export function providePlan(value: PlanContextValue) { + provide(PlanKey, value) +} + +export function usePlan() { + const context = inject(PlanKey) + if (!context) { + throw new Error('Plan components must be used within a Plan component') + } + return context +} +``` + +```ts [index.ts] +export { default as Plan } from './Plan.vue' +export { default as PlanAction } from './PlanAction.vue' +export { default as PlanContent } from './PlanContent.vue' +export { default as PlanDescription } from './PlanDescription.vue' +export { default as PlanFooter } from './PlanFooter.vue' +export { default as PlanHeader } from './PlanHeader.vue' +export { default as PlanTitle } from './PlanTitle.vue' +export { default as PlanTrigger } from './PlanTrigger.vue' +``` +::: + +## Usage + +```ts +import { + Plan, + PlanAction, + PlanContent, + PlanDescription, + PlanFooter, + PlanHeader, + PlanTitle, + PlanTrigger, +} from '@/components/ai-elements/plan' +``` + +```vue + + +
+ Implement new feature + + Add authentication system with JWT tokens and refresh logic. + +
+ +
+ +
+
+

Overview

+

This plan outlines the implementation strategy...

+
+
+
+ + + +
+``` + +## Features + +- Collapsible content with smooth animations +- Streaming support with shimmer loading states +- Built on shadcn-vue Card and Collapsible components +- TypeScript support with comprehensive type definitions +- Customizable styling with Tailwind CSS +- Responsive design with mobile-friendly interactions +- Keyboard navigation and accessibility support +- Theme-aware with automatic dark mode support +- Context-based state management for streaming + +## Props + +### `` + +:::field-group + ::field{name="isStreaming" type="boolean" defaultValue="false"} + Whether content is currently streaming. Enables shimmer animations on title and description. Defaults to false. + :: + + ::field{name="defaultOpen" type="boolean" defaultValue="false"} + Whether the plan is expanded by default. + :: + + ::field{name="class" type="string"} + Additional classes applied to the component. + :: +::: + +### `` + +:::field-group + ::field{name="class" type="string"} + Additional classes applied to the component. + :: +::: + +### `` + +:::field-group + ::field{name="class" type="string"} + Additional classes applied to the component. + :: +::: + +### `` + +:::field-group + ::field{name="class" type="string"} + Additional classes applied to the component. + :: +::: +::: diff --git a/apps/www/plugins/ai-elements.ts b/apps/www/plugins/ai-elements.ts index dbe6625..7496b48 100644 --- a/apps/www/plugins/ai-elements.ts +++ b/apps/www/plugins/ai-elements.ts @@ -10,6 +10,7 @@ import { Message, MessageMarkdown, OpenInChat, + Plan, PromptInput, Response, Shimmer, @@ -52,4 +53,5 @@ export default defineNuxtPlugin((nuxtApp) => { vueApp.component('Loader', Loader) vueApp.component('LoaderCustomStyling', LoaderCustomStyling) vueApp.component('LoaderSizes', LoaderSizes) + vueApp.component('Plan', Plan) }) diff --git a/packages/elements/src/index.ts b/packages/elements/src/index.ts index d225185..9ef7cf9 100644 --- a/packages/elements/src/index.ts +++ b/packages/elements/src/index.ts @@ -6,6 +6,7 @@ export * from './image' export * from './loader' export * from './message' export * from './open-in-chat' +export * from './plan' export * from './prompt-input' export * from './response' export * from './shimmer' diff --git a/packages/elements/src/plan/Plan.vue b/packages/elements/src/plan/Plan.vue new file mode 100644 index 0000000..294a666 --- /dev/null +++ b/packages/elements/src/plan/Plan.vue @@ -0,0 +1,32 @@ + + + diff --git a/packages/elements/src/plan/PlanAction.vue b/packages/elements/src/plan/PlanAction.vue new file mode 100644 index 0000000..bdf9587 --- /dev/null +++ b/packages/elements/src/plan/PlanAction.vue @@ -0,0 +1,9 @@ + + + diff --git a/packages/elements/src/plan/PlanContent.vue b/packages/elements/src/plan/PlanContent.vue new file mode 100644 index 0000000..229d921 --- /dev/null +++ b/packages/elements/src/plan/PlanContent.vue @@ -0,0 +1,12 @@ + + + diff --git a/packages/elements/src/plan/PlanDescription.vue b/packages/elements/src/plan/PlanDescription.vue new file mode 100644 index 0000000..93d4085 --- /dev/null +++ b/packages/elements/src/plan/PlanDescription.vue @@ -0,0 +1,25 @@ + + + diff --git a/packages/elements/src/plan/PlanFooter.vue b/packages/elements/src/plan/PlanFooter.vue new file mode 100644 index 0000000..cd7b3ad --- /dev/null +++ b/packages/elements/src/plan/PlanFooter.vue @@ -0,0 +1,9 @@ + + + diff --git a/packages/elements/src/plan/PlanHeader.vue b/packages/elements/src/plan/PlanHeader.vue new file mode 100644 index 0000000..49fe463 --- /dev/null +++ b/packages/elements/src/plan/PlanHeader.vue @@ -0,0 +1,18 @@ + + + diff --git a/packages/elements/src/plan/PlanTitle.vue b/packages/elements/src/plan/PlanTitle.vue new file mode 100644 index 0000000..05e9ab8 --- /dev/null +++ b/packages/elements/src/plan/PlanTitle.vue @@ -0,0 +1,16 @@ + + + diff --git a/packages/elements/src/plan/PlanTrigger.vue b/packages/elements/src/plan/PlanTrigger.vue new file mode 100644 index 0000000..2f48d6e --- /dev/null +++ b/packages/elements/src/plan/PlanTrigger.vue @@ -0,0 +1,24 @@ + + + diff --git a/packages/elements/src/plan/context.ts b/packages/elements/src/plan/context.ts new file mode 100644 index 0000000..f276035 --- /dev/null +++ b/packages/elements/src/plan/context.ts @@ -0,0 +1,20 @@ +import type { ComputedRef, InjectionKey } from 'vue' +import { inject, provide } from 'vue' + +export interface PlanContextValue { + isStreaming: ComputedRef +} + +export const PlanKey: InjectionKey = Symbol('PlanContext') + +export function providePlan(value: PlanContextValue) { + provide(PlanKey, value) +} + +export function usePlan() { + const context = inject(PlanKey) + if (!context) { + throw new Error('Plan components must be used within a Plan component') + } + return context +} diff --git a/packages/elements/src/plan/index.ts b/packages/elements/src/plan/index.ts new file mode 100644 index 0000000..702cd7a --- /dev/null +++ b/packages/elements/src/plan/index.ts @@ -0,0 +1,8 @@ +export { default as Plan } from './Plan.vue' +export { default as PlanAction } from './PlanAction.vue' +export { default as PlanContent } from './PlanContent.vue' +export { default as PlanDescription } from './PlanDescription.vue' +export { default as PlanFooter } from './PlanFooter.vue' +export { default as PlanHeader } from './PlanHeader.vue' +export { default as PlanTitle } from './PlanTitle.vue' +export { default as PlanTrigger } from './PlanTrigger.vue' diff --git a/packages/examples/src/index.ts b/packages/examples/src/index.ts index 8848e7b..7b2c3f1 100644 --- a/packages/examples/src/index.ts +++ b/packages/examples/src/index.ts @@ -9,6 +9,7 @@ export { default as Loader } from './loader.vue' export { default as MessageMarkdown } from './message-markdown.vue' export { default as Message } from './message.vue' export { default as OpenInChat } from './open-in-chat.vue' +export { default as Plan } from './plan.vue' export { default as PromptInput } from './prompt-input.vue' export { default as Response } from './response.vue' export { default as ShimmerCustomElements } from './shimmer-custom-elements.vue' diff --git a/packages/examples/src/plan.vue b/packages/examples/src/plan.vue new file mode 100644 index 0000000..d31e0fc --- /dev/null +++ b/packages/examples/src/plan.vue @@ -0,0 +1,72 @@ + + + diff --git a/packages/shadcn-vue/components/ui/kbd/Kbd.vue b/packages/shadcn-vue/components/ui/kbd/Kbd.vue new file mode 100644 index 0000000..bb9b449 --- /dev/null +++ b/packages/shadcn-vue/components/ui/kbd/Kbd.vue @@ -0,0 +1,21 @@ + + + diff --git a/packages/shadcn-vue/components/ui/kbd/KbdGroup.vue b/packages/shadcn-vue/components/ui/kbd/KbdGroup.vue new file mode 100644 index 0000000..415be1d --- /dev/null +++ b/packages/shadcn-vue/components/ui/kbd/KbdGroup.vue @@ -0,0 +1,17 @@ + + + diff --git a/packages/shadcn-vue/components/ui/kbd/index.ts b/packages/shadcn-vue/components/ui/kbd/index.ts new file mode 100644 index 0000000..ea722ab --- /dev/null +++ b/packages/shadcn-vue/components/ui/kbd/index.ts @@ -0,0 +1,2 @@ +export { default as Kbd } from './Kbd.vue' +export { default as KbdGroup } from './KbdGroup.vue'