| 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> |
| ); |
| }; |
|
|