conversation.tsx 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. 'use client'
  2. import { Button } from '../ui/button'
  3. import { cn } from '../../utils'
  4. import { ArrowDownIcon } from 'lucide-react'
  5. import type { ComponentProps } from 'react'
  6. import React, { useCallback } from 'react'
  7. import { StickToBottom, useStickToBottomContext } from 'use-stick-to-bottom'
  8. export type ConversationProps = ComponentProps<typeof StickToBottom>
  9. export const Conversation = ({ className, ...props }: ConversationProps) => (
  10. <StickToBottom
  11. className={cn('relative flex-1 overflow-y-auto', className)}
  12. initial="smooth"
  13. resize="smooth"
  14. role="log"
  15. {...props}
  16. />
  17. )
  18. export type ConversationContentProps = ComponentProps<
  19. typeof StickToBottom.Content
  20. >
  21. export const ConversationContent = ({
  22. className,
  23. ...props
  24. }: ConversationContentProps) => (
  25. <StickToBottom.Content className={cn('p-4', className)} {...props} />
  26. )
  27. export type ConversationEmptyStateProps = ComponentProps<'div'> & {
  28. title?: string
  29. description?: string
  30. icon?: React.ReactNode
  31. }
  32. export const ConversationEmptyState = ({
  33. className,
  34. title = 'No messages yet',
  35. description = 'Start a conversation to see messages here',
  36. icon,
  37. children,
  38. ...props
  39. }: ConversationEmptyStateProps) => (
  40. <div
  41. className={cn(
  42. 'flex size-full flex-col items-center justify-center gap-3 p-8 text-center',
  43. className
  44. )}
  45. {...props}
  46. >
  47. {children ?? (
  48. <>
  49. {icon && <div className="text-muted-foreground">{icon}</div>}
  50. <div className="space-y-1">
  51. <h3 className="font-medium text-sm">{title}</h3>
  52. {description && (
  53. <p className="text-muted-foreground text-sm">{description}</p>
  54. )}
  55. </div>
  56. </>
  57. )}
  58. </div>
  59. )
  60. export type ConversationScrollButtonProps = ComponentProps<typeof Button>
  61. export const ConversationScrollButton = ({
  62. className,
  63. ...props
  64. }: ConversationScrollButtonProps) => {
  65. const { isAtBottom, scrollToBottom } = useStickToBottomContext()
  66. const handleScrollToBottom = useCallback(() => {
  67. scrollToBottom()
  68. }, [scrollToBottom])
  69. return (
  70. !isAtBottom && (
  71. <Button
  72. className={cn(
  73. 'absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full',
  74. className
  75. )}
  76. onClick={handleScrollToBottom}
  77. size="icon"
  78. type="button"
  79. variant="outline"
  80. {...props}
  81. >
  82. <ArrowDownIcon className="size-4" />
  83. </Button>
  84. )
  85. )
  86. }