|
|
|
|
| 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> |
| ); |
|
|