| 'use client'; | |
| import type { ComponentProps } from 'react'; | |
| import { useCallback } from 'react'; | |
| import { ArrowDownIcon } from 'lucide-react'; | |
| import { StickToBottom, useStickToBottomContext } from 'use-stick-to-bottom'; | |
| import { cn } from '@/lib/utils'; | |
| import { Button } from '@/components/ui/button'; | |
| export type ConversationProps = ComponentProps<typeof StickToBottom>; | |
| export const Conversation = ({ className, ...props }: ConversationProps) => ( | |
| <StickToBottom className={cn('relative flex-1 overflow-y-auto', className)} initial='smooth' resize='smooth' role='log' {...props} /> | |
| ); | |
| export type ConversationContentProps = ComponentProps<typeof StickToBottom.Content>; | |
| export const ConversationContent = ({ className, ...props }: ConversationContentProps) => ( | |
| <StickToBottom.Content className={cn('flex flex-col gap-8 p-4', className)} {...props} /> | |
| ); | |
| export type ConversationEmptyStateProps = ComponentProps<'div'> & { | |
| title?: string; | |
| description?: string; | |
| icon?: React.ReactNode; | |
| }; | |
| export const ConversationEmptyState = ({ | |
| className, | |
| title = 'No messages yet', | |
| description = 'Start a conversation to see messages here', | |
| icon, | |
| children, | |
| ...props | |
| }: ConversationEmptyStateProps) => ( | |
| <div className={cn('flex size-full flex-col items-center justify-center gap-3 p-8 text-center', className)} {...props}> | |
| {children ?? ( | |
| <> | |
| {icon && <div className='text-muted-foreground'>{icon}</div>} | |
| <div className='space-y-1'> | |
| <h3 className='text-sm font-medium'>{title}</h3> | |
| {description && <p className='text-muted-foreground text-sm'>{description}</p>} | |
| </div> | |
| </> | |
| )} | |
| </div> | |
| ); | |
| export type ConversationScrollButtonProps = ComponentProps<typeof Button>; | |
| export const ConversationScrollButton = ({ className, ...props }: ConversationScrollButtonProps) => { | |
| const { isAtBottom, scrollToBottom } = useStickToBottomContext(); | |
| const handleScrollToBottom = useCallback(() => { | |
| scrollToBottom(); | |
| }, [scrollToBottom]); | |
| return ( | |
| !isAtBottom && ( | |
| <Button | |
| className={cn('absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full', className)} | |
| onClick={handleScrollToBottom} | |
| size='icon' | |
| type='button' | |
| variant='outline' | |
| {...props} | |
| > | |
| <ArrowDownIcon className='size-4' /> | |
| </Button> | |
| ) | |
| ); | |
| }; | |