projects/deliberation_at_scale/packages/frontend/components/ChatMessageList.tsx (65 lines of code) (raw):
"use client";
import { motion, AnimatePresence } from 'framer-motion';
import { Message } from '@/types/flows';
import ChatMessage from './ChatMessage';
import { MESSAGES_SCROLL_CONTAINER_ID } from '@/utilities/constants';
import classNames from 'classnames';
interface Props {
messages: (Message | null | undefined)[] | undefined;
className?: string;
}
export default function ChatMessageList(props: Props) {
const { messages, className } = props;
const variants = {
visible: {
transition: {
duration: 0.1,
when: 'beforeChildren',
staggerChildren: 0.05,
},
},
hidden: {
transition: {
duration: 0.1,
when: 'afterChildren',
},
},
};
// guard: skip when messages are invalid
if (!messages) {
return null;
}
// <div className="min-h-[12px] md:min-h-[50px] w-full top-0 bg-gradient-to-t from-transparent to-white"></div>
return (
<motion.div
id={MESSAGES_SCROLL_CONTAINER_ID}
className={classNames(
"flex flex-col gap-1 overflow-y-scroll min-h-0 shrink",
className
)}
variants={variants}
initial="hidden"
animate="visible"
>
<AnimatePresence>
{messages.map((message, index) => {
const { id = index, date, nameId } = message ?? {};
const key = `${id}-${date}`;
const previousMessage = messages?.[index - 1];
const nextMessage = messages?.[index + 1];
const nameKey = nameId ? 'nameId' : 'name';
// guard: check if message is valid
if (!message) {
return null;
}
const isFirst = previousMessage?.[nameKey] !== message?.[nameKey];
const isLast = nextMessage?.[nameKey] !== message?.[nameKey];
return (
<ChatMessage
className={index === 0 ? 'mt-auto' : ''}
key={key}
message={message}
first={isFirst}
last={isLast}
/>
);
})}
</AnimatePresence>
</motion.div>
);
}