|
import { AnimatePresence, motion } from 'framer-motion'; |
|
import React, { useState } from 'react'; |
|
import type { ProgressAnnotation } from '~/types/context'; |
|
import { classNames } from '~/utils/classNames'; |
|
import { cubicEasingFn } from '~/utils/easings'; |
|
|
|
export default function ProgressCompilation({ data }: { data?: ProgressAnnotation[] }) { |
|
const [progressList, setProgressList] = React.useState<ProgressAnnotation[]>([]); |
|
const [expanded, setExpanded] = useState(false); |
|
React.useEffect(() => { |
|
if (!data || data.length == 0) { |
|
setProgressList([]); |
|
return; |
|
} |
|
|
|
const progressMap = new Map<string, ProgressAnnotation>(); |
|
data.forEach((x) => { |
|
const existingProgress = progressMap.get(x.label); |
|
|
|
if (existingProgress && existingProgress.status === 'complete') { |
|
return; |
|
} |
|
|
|
progressMap.set(x.label, x); |
|
}); |
|
|
|
const newData = Array.from(progressMap.values()); |
|
newData.sort((a, b) => a.order - b.order); |
|
setProgressList(newData); |
|
}, [data]); |
|
|
|
if (progressList.length === 0) { |
|
return <></>; |
|
} |
|
|
|
return ( |
|
<AnimatePresence> |
|
<div |
|
className={classNames( |
|
'bg-bolt-elements-background-depth-2', |
|
'border border-bolt-elements-borderColor', |
|
'shadow-lg rounded-lg relative w-full max-w-chat mx-auto z-prompt', |
|
'p-1', |
|
)} |
|
> |
|
<div |
|
className={classNames( |
|
'bg-bolt-elements-item-backgroundAccent', |
|
'p-1 rounded-lg text-bolt-elements-item-contentAccent', |
|
'flex ', |
|
)} |
|
> |
|
<div className="flex-1"> |
|
<AnimatePresence> |
|
{expanded ? ( |
|
<motion.div |
|
className="actions" |
|
initial={{ height: 0 }} |
|
animate={{ height: 'auto' }} |
|
exit={{ height: '0px' }} |
|
transition={{ duration: 0.15 }} |
|
> |
|
{progressList.map((x, i) => { |
|
return <ProgressItem key={i} progress={x} />; |
|
})} |
|
</motion.div> |
|
) : ( |
|
<ProgressItem progress={progressList.slice(-1)[0]} /> |
|
)} |
|
</AnimatePresence> |
|
</div> |
|
<motion.button |
|
initial={{ width: 0 }} |
|
animate={{ width: 'auto' }} |
|
exit={{ width: 0 }} |
|
transition={{ duration: 0.15, ease: cubicEasingFn }} |
|
className=" p-1 rounded-lg bg-bolt-elements-item-backgroundAccent hover:bg-bolt-elements-artifacts-backgroundHover" |
|
onClick={() => setExpanded((v) => !v)} |
|
> |
|
<div className={expanded ? 'i-ph:caret-up-bold' : 'i-ph:caret-down-bold'}></div> |
|
</motion.button> |
|
</div> |
|
</div> |
|
</AnimatePresence> |
|
); |
|
} |
|
|
|
const ProgressItem = ({ progress }: { progress: ProgressAnnotation }) => { |
|
return ( |
|
<motion.div |
|
className={classNames('flex text-sm gap-3')} |
|
initial={{ opacity: 0 }} |
|
animate={{ opacity: 1 }} |
|
exit={{ opacity: 0 }} |
|
transition={{ duration: 0.15 }} |
|
> |
|
<div className="flex items-center gap-1.5 "> |
|
<div> |
|
{progress.status === 'in-progress' ? ( |
|
<div className="i-svg-spinners:90-ring-with-bg"></div> |
|
) : progress.status === 'complete' ? ( |
|
<div className="i-ph:check"></div> |
|
) : null} |
|
</div> |
|
{/* {x.label} */} |
|
</div> |
|
{progress.message} |
|
</motion.div> |
|
); |
|
}; |
|
|