llzai's picture
Upload 1793 files
9853396 verified
Raw
History Blame Contribute Delete
3.4 kB
'use client';
import type { ComponentProps } from 'react';
import { createContext, useContext } from 'react';
import { ChevronsUpDownIcon } from 'lucide-react';
import { cn } from '@/lib/utils';
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 { 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>
);