infoalloggi-monorepo/apps/infoalloggi/src/components/chat/chat-bubble.tsx

199 lines
4.3 KiB
TypeScript
Raw Normal View History

2025-08-04 17:45:44 +02:00
import { cva, type VariantProps } from "class-variance-authority";
2025-08-28 18:27:07 +02:00
import { Check, CheckCheck } from "lucide-react";
import * as React from "react";
2025-08-04 17:45:44 +02:00
import MessageLoading from "~/components/chat/message-loading";
import { Button, type ButtonProps } from "~/components/ui/button";
2025-08-28 18:27:07 +02:00
import { cn } from "~/lib/utils";
2025-08-04 17:45:44 +02:00
// ChatBubble
const chatBubbleVariant = cva(
2025-08-28 18:27:07 +02:00
"flex gap-2 max-w-[60%] items-end relative group",
{
2025-08-29 16:18:32 +02:00
defaultVariants: {
layout: "default",
variant: "received",
},
2025-08-28 18:27:07 +02:00
variants: {
2025-08-29 16:18:32 +02:00
layout: {
ai: "max-w-full w-full items-center",
default: "",
},
2025-08-28 18:27:07 +02:00
variant: {
received: "self-start",
sent: "self-end flex-row-reverse",
},
},
},
2025-08-04 17:45:44 +02:00
);
interface ChatBubbleProps
2025-08-28 18:27:07 +02:00
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof chatBubbleVariant> {}
2025-08-04 17:45:44 +02:00
const ChatBubble = React.forwardRef<HTMLDivElement, ChatBubbleProps>(
2025-08-28 18:27:07 +02:00
({ className, variant, layout, children, ...props }, ref) => (
<div
className={cn(
2025-08-29 16:18:32 +02:00
chatBubbleVariant({ className, layout, variant }),
2025-08-28 18:27:07 +02:00
"group relative",
)}
ref={ref}
{...props}
>
{React.Children.map(children, (child) =>
React.isValidElement(child) && typeof child.type !== "string"
? React.cloneElement(child, {
layout,
2025-08-29 16:18:32 +02:00
variant,
2025-08-28 18:27:07 +02:00
} as Partial<React.ComponentProps<typeof child.type>>)
: child,
)}
</div>
),
2025-08-04 17:45:44 +02:00
);
ChatBubble.displayName = "ChatBubble";
type ChatBubbleReadStatusProps = {
2025-08-28 18:27:07 +02:00
isread: boolean;
2025-08-04 17:45:44 +02:00
};
export const ChatBubbleReadStatus = ({ isread }: ChatBubbleReadStatusProps) => {
2025-08-28 18:27:07 +02:00
if (isread) {
return <CheckCheck className="mt-2 size-4 text-green-500" />;
}
2025-10-10 16:18:43 +02:00
return <Check className="mt-2 size-4 text-muted-foreground" />;
2025-08-04 17:45:44 +02:00
};
// ChatBubbleMessage
const chatBubbleMessageVariants = cva("p-2", {
2025-08-29 16:18:32 +02:00
defaultVariants: {
layout: "default",
variant: "received",
},
2025-08-28 18:27:07 +02:00
variants: {
2025-08-29 16:18:32 +02:00
layout: {
ai: "border-t w-full rounded-none bg-transparent",
default: "",
},
2025-08-28 18:27:07 +02:00
variant: {
received:
"bg-secondary text-secondary-foreground rounded-r-lg rounded-tl-lg",
sent: "bg-primary text-primary-foreground rounded-l-lg rounded-tr-lg",
},
},
2025-08-04 17:45:44 +02:00
});
interface ChatBubbleMessageProps
2025-08-28 18:27:07 +02:00
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof chatBubbleMessageVariants> {
isLoading?: boolean;
2025-08-04 17:45:44 +02:00
}
const ChatBubbleMessage = React.forwardRef<
2025-08-28 18:27:07 +02:00
HTMLDivElement,
ChatBubbleMessageProps
2025-08-04 17:45:44 +02:00
>(
2025-08-28 18:27:07 +02:00
(
{ className, variant, layout, isLoading = false, children, ...props },
ref,
) => (
<div
className={cn(
2025-08-29 16:18:32 +02:00
chatBubbleMessageVariants({ className, layout, variant }),
"wrap-break-word max-w-full whitespace-pre-wrap",
2025-08-28 18:27:07 +02:00
)}
ref={ref}
{...props}
>
{isLoading ? (
<div className="flex items-center space-x-2">
<MessageLoading />
</div>
) : (
children
)}
</div>
),
2025-08-04 17:45:44 +02:00
);
ChatBubbleMessage.displayName = "ChatBubbleMessage";
// ChatBubbleTimestamp
interface ChatBubbleTimestampProps
2025-08-28 18:27:07 +02:00
extends React.HTMLAttributes<HTMLDivElement> {
timestamp: string;
2025-08-04 17:45:44 +02:00
}
const ChatBubbleTimestamp: React.FC<ChatBubbleTimestampProps> = ({
2025-08-28 18:27:07 +02:00
timestamp,
className,
...props
2025-08-04 17:45:44 +02:00
}) => (
2025-08-28 18:27:07 +02:00
<div className={cn("mt-2 text-right text-xs", className)} {...props}>
{timestamp}
</div>
2025-08-04 17:45:44 +02:00
);
// ChatBubbleAction
type ChatBubbleActionProps = ButtonProps & {
2025-08-28 18:27:07 +02:00
icon: React.ReactNode;
2025-08-04 17:45:44 +02:00
};
const ChatBubbleAction: React.FC<ChatBubbleActionProps> = ({
2025-08-28 18:27:07 +02:00
icon,
onClick,
className,
variant = "ghost",
size = "icon",
...props
2025-08-04 17:45:44 +02:00
}) => (
2025-08-28 18:27:07 +02:00
<Button
aria-label="Chat Bubble Action"
className={className}
onClick={onClick}
2025-08-29 16:18:32 +02:00
size={size}
variant={variant}
2025-08-28 18:27:07 +02:00
{...props}
>
{icon}
</Button>
2025-08-04 17:45:44 +02:00
);
interface ChatBubbleActionWrapperProps
2025-08-28 18:27:07 +02:00
extends React.HTMLAttributes<HTMLDivElement> {
variant?: "sent" | "received";
className?: string;
show: boolean;
2025-08-04 17:45:44 +02:00
}
const ChatBubbleActionWrapper = React.forwardRef<
2025-08-28 18:27:07 +02:00
HTMLDivElement,
ChatBubbleActionWrapperProps
2025-08-04 17:45:44 +02:00
>(({ variant, className, show, children, ...props }, ref) => (
2025-08-28 18:27:07 +02:00
<>
{show && (
<div
className={cn(
"absolute top-1/2 flex -translate-y-1/2 opacity-0 transition-opacity duration-200 group-hover:opacity-100",
2025-08-28 18:27:07 +02:00
variant === "sent"
? "-left-1 -translate-x-full flex-row-reverse"
: "-right-1 translate-x-full",
className,
)}
2025-08-29 16:18:32 +02:00
ref={ref}
2025-08-28 18:27:07 +02:00
{...props}
>
{children}
</div>
)}
</>
2025-08-04 17:45:44 +02:00
));
ChatBubbleActionWrapper.displayName = "ChatBubbleActionWrapper";
export {
2025-08-28 18:27:07 +02:00
ChatBubble,
ChatBubbleMessage,
ChatBubbleTimestamp,
ChatBubbleAction,
ChatBubbleActionWrapper,
2025-08-04 17:45:44 +02:00
};