Spaces:
No application file
No application file
| 'use client'; | |
| import { Button } from '@/components/ui/button'; | |
| import { | |
| Card, | |
| CardAction, | |
| CardContent, | |
| CardDescription, | |
| CardFooter, | |
| CardHeader, | |
| CardTitle, | |
| } from '@/components/ui/card'; | |
| import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; | |
| import { cn } from '@/lib/utils'; | |
| import { ChevronsUpDownIcon } from 'lucide-react'; | |
| import type { ComponentProps } from 'react'; | |
| import { createContext, useContext } from 'react'; | |
| import { Shimmer } from './shimmer'; | |
| type PlanContextValue = { | |
| isStreaming: boolean; | |
| }; | |
| const PlanContext = createContext<PlanContextValue | null>(null); | |
| const usePlan = () => { | |
| const context = useContext(PlanContext); | |
| if (!context) { | |
| throw new Error('Plan components must be used within Plan'); | |
| } | |
| return context; | |
| }; | |
| export type PlanProps = ComponentProps<typeof Collapsible> & { | |
| isStreaming?: boolean; | |
| }; | |
| export const Plan = ({ className, isStreaming = false, children, ...props }: PlanProps) => ( | |
| <PlanContext.Provider value={{ isStreaming }}> | |
| <Collapsible asChild data-slot="plan" {...props}> | |
| <Card className={cn('shadow-none', className)}>{children}</Card> | |
| </Collapsible> | |
| </PlanContext.Provider> | |
| ); | |
| export type PlanHeaderProps = ComponentProps<typeof CardHeader>; | |
| export const PlanHeader = ({ className, ...props }: PlanHeaderProps) => ( | |
| <CardHeader | |
| className={cn('flex items-start justify-between', className)} | |
| data-slot="plan-header" | |
| {...props} | |
| /> | |
| ); | |
| export type PlanTitleProps = Omit<ComponentProps<typeof CardTitle>, 'children'> & { | |
| children: string; | |
| }; | |
| export const PlanTitle = ({ children, ...props }: PlanTitleProps) => { | |
| const { isStreaming } = usePlan(); | |
| return ( | |
| <CardTitle data-slot="plan-title" {...props}> | |
| {isStreaming ? <Shimmer>{children}</Shimmer> : children} | |
| </CardTitle> | |
| ); | |
| }; | |
| export type PlanDescriptionProps = Omit<ComponentProps<typeof CardDescription>, 'children'> & { | |
| children: string; | |
| }; | |
| export const PlanDescription = ({ className, children, ...props }: PlanDescriptionProps) => { | |
| const { isStreaming } = usePlan(); | |
| return ( | |
| <CardDescription | |
| className={cn('text-balance', className)} | |
| data-slot="plan-description" | |
| {...props} | |
| > | |
| {isStreaming ? <Shimmer>{children}</Shimmer> : children} | |
| </CardDescription> | |
| ); | |
| }; | |
| export type PlanActionProps = ComponentProps<typeof CardAction>; | |
| export const PlanAction = (props: PlanActionProps) => ( | |
| <CardAction data-slot="plan-action" {...props} /> | |
| ); | |
| export type PlanContentProps = ComponentProps<typeof CardContent>; | |
| export const PlanContent = (props: PlanContentProps) => ( | |
| <CollapsibleContent asChild> | |
| <CardContent data-slot="plan-content" {...props} /> | |
| </CollapsibleContent> | |
| ); | |
| export type PlanFooterProps = ComponentProps<'div'>; | |
| export const PlanFooter = (props: PlanFooterProps) => ( | |
| <CardFooter data-slot="plan-footer" {...props} /> | |
| ); | |
| export type PlanTriggerProps = ComponentProps<typeof CollapsibleTrigger>; | |
| export const PlanTrigger = ({ className, ...props }: PlanTriggerProps) => ( | |
| <CollapsibleTrigger asChild> | |
| <Button | |
| className={cn('size-8', className)} | |
| data-slot="plan-trigger" | |
| size="icon" | |
| variant="ghost" | |
| {...props} | |
| > | |
| <ChevronsUpDownIcon className="size-4" /> | |
| <span className="sr-only">Toggle plan</span> | |
| </Button> | |
| </CollapsibleTrigger> | |
| ); | |