bolt.diy/app/components/sidebar/HistoryItem.tsx
2025-01-28 11:39:12 +01:00

133 lines
4.7 KiB
TypeScript

import { useParams } from '@remix-run/react';
import { classNames } from '~/utils/classNames';
import * as Dialog from '@radix-ui/react-dialog';
import { type ChatHistoryItem } from '~/lib/persistence';
import WithTooltip from '~/components/ui/Tooltip';
import { useEditChatDescription } from '~/lib/hooks';
import { forwardRef, type ForwardedRef } from 'react';
interface HistoryItemProps {
item: ChatHistoryItem;
onDelete?: (event: React.UIEvent) => void;
onDuplicate?: (id: string) => void;
exportChat: (id?: string) => void;
}
export function HistoryItem({ item, onDelete, onDuplicate, exportChat }: HistoryItemProps) {
const { id: urlId } = useParams();
const isActiveChat = urlId === item.urlId;
const { editing, handleChange, handleBlur, handleSubmit, handleKeyDown, currentDescription, toggleEditMode } =
useEditChatDescription({
initialDescription: item.description,
customChatId: item.id,
syncWithGlobalStore: isActiveChat,
});
return (
<div
className={classNames(
'group rounded-lg text-sm text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50/80 dark:hover:bg-gray-800/30 overflow-hidden flex justify-between items-center px-3 py-2 transition-colors',
{ 'text-gray-900 dark:text-white bg-gray-50/80 dark:bg-gray-800/30': isActiveChat },
)}
>
{editing ? (
<form onSubmit={handleSubmit} className="flex-1 flex items-center gap-2">
<input
type="text"
className="flex-1 bg-white dark:bg-gray-900 text-gray-900 dark:text-white rounded-md px-3 py-1.5 text-sm border border-gray-200 dark:border-gray-800 focus:outline-none focus:ring-1 focus:ring-purple-500/50"
autoFocus
value={currentDescription}
onChange={handleChange}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
/>
<button
type="submit"
className="i-ph:check h-4 w-4 text-gray-500 hover:text-purple-500 transition-colors"
onMouseDown={handleSubmit}
/>
</form>
) : (
<a href={`/chat/${item.urlId}`} className="flex w-full relative truncate block">
<WithTooltip tooltip={currentDescription}>
<span className="truncate pr-24">{currentDescription}</span>
</WithTooltip>
<div
className={classNames(
'absolute right-0 top-0 bottom-0 flex items-center bg-white dark:bg-gray-950 group-hover:bg-gray-50/80 dark:group-hover:bg-gray-800/30 px-2',
{ 'bg-gray-50/80 dark:bg-gray-800/30': isActiveChat },
)}
>
<div className="flex items-center gap-2.5 text-gray-400 dark:text-gray-500 opacity-0 group-hover:opacity-100 transition-opacity">
<ChatActionButton
toolTipContent="Export"
icon="i-ph:download-simple h-4 w-4"
onClick={(event) => {
event.preventDefault();
exportChat(item.id);
}}
/>
{onDuplicate && (
<ChatActionButton
toolTipContent="Duplicate"
icon="i-ph:copy h-4 w-4"
onClick={() => onDuplicate?.(item.id)}
/>
)}
<ChatActionButton
toolTipContent="Rename"
icon="i-ph:pencil-fill h-4 w-4"
onClick={(event) => {
event.preventDefault();
toggleEditMode();
}}
/>
<Dialog.Trigger asChild>
<ChatActionButton
toolTipContent="Delete"
icon="i-ph:trash h-4 w-4"
className="hover:text-red-500"
onClick={(event) => {
event.preventDefault();
onDelete?.(event);
}}
/>
</Dialog.Trigger>
</div>
</div>
</a>
)}
</div>
);
}
const ChatActionButton = forwardRef(
(
{
toolTipContent,
icon,
className,
onClick,
}: {
toolTipContent: string;
icon: string;
className?: string;
onClick: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
btnTitle?: string;
},
ref: ForwardedRef<HTMLButtonElement>,
) => {
return (
<WithTooltip tooltip={toolTipContent} position="bottom" sideOffset={4}>
<button
ref={ref}
type="button"
className={`text-gray-400 dark:text-gray-500 hover:text-purple-500 dark:hover:text-purple-400 transition-colors ${icon} ${className ? className : ''}`}
onClick={onClick}
/>
</WithTooltip>
);
},
);