204 lines
7.2 KiB
TypeScript

'use client';
import { getTutorial } from '@/utils/tutorial';
import { useQuery } from '@tanstack/react-query';
import { buttonVariants } from '@workspace/airo-ui/components/AiroButton';
import { Avatar, AvatarFallback, AvatarImage } from '@workspace/airo-ui/components/avatar';
import { Markdown } from '@workspace/airo-ui/custom-components/markdown';
import { useOutsideClick } from '@workspace/airo-ui/hooks/use-outside-click';
import { cn } from '@workspace/airo-ui/lib/utils';
import { formatDate } from '@workspace/airo-ui/utils';
import { AnimatePresence, motion } from 'framer-motion';
import { useTranslations } from 'next-intl';
import { RefObject, useEffect, useId, useRef, useState } from 'react';
import { CloseIcon } from './close-icon';
interface Item {
path: string;
title: string;
updated_at?: string;
icon?: string;
download?: string;
}
export function TutorialButton({ items }: { items: Item[] }) {
const t = useTranslations('document');
const [active, setActive] = useState<Item | boolean | null>(null);
const id = useId();
const ref = useRef<HTMLDivElement>(null);
const { data } = useQuery({
enabled: !!(active as Item)?.path,
queryKey: ['getTutorial', (active as Item)?.path],
queryFn: async () => {
const markdown = await getTutorial((active as Item)?.path);
return markdown;
},
});
useEffect(() => {
function onKeyDown(event: KeyboardEvent) {
if (event.key === 'Escape') {
setActive(false);
}
}
if (active && typeof active === 'object') {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'auto';
}
window.addEventListener('keydown', onKeyDown);
return () => window.removeEventListener('keydown', onKeyDown);
}, [active]);
useOutsideClick(ref as RefObject<HTMLDivElement>, () => setActive(null));
function openPopupWindow(item) {
// features 字符串用于控制窗口的特性
const pageWidth = 600; // 弹出窗口的宽度
const pageHeight = 550; // 弹出窗口的高度
const {
availLeft, // 返回浏览器可用空间左边距离屏幕(系统桌面)左边界的距离。
availHeight, // 浏览器在显示屏上的可用高度,即当前屏幕高度
availWidth, // 浏览器在显示屏上的可用宽度,即当前屏幕宽度
} = window.screen;
const pageTop = (availHeight - pageHeight) / 2; // 窗口的垂直位置
let pageLeft = (availWidth - pageWidth) / 2; // 窗口的水平位置;
pageLeft += availLeft; // 加上屏幕偏移值
const features = `width=${pageWidth},height=${pageHeight},left=${pageLeft},top=${pageTop},toolbar=no,location=no,menubar=no`;
console.log(features);
window.open(item.download, item.title, features);
}
return (
<>
<AnimatePresence>
{active && typeof active === 'object' && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className='fixed inset-0 z-10 h-full w-full bg-black/20'
/>
)}
</AnimatePresence>
<AnimatePresence>
{active && typeof active === 'object' ? (
<div className='fixed inset-0 z-[100] grid place-items-center'>
<motion.button
key={`button-${active.title}-${id}`}
layout
initial={{
opacity: 0,
}}
animate={{
opacity: 1,
}}
exit={{
opacity: 0,
transition: {
duration: 0.05,
},
}}
className='bg-foreground absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full text-white dark:text-black'
onClick={() => setActive(null)}
>
<CloseIcon />
</motion.button>
<motion.div
layoutId={`card-${active.title}-${id}`}
ref={ref}
className='bg-muted flex size-full flex-col overflow-auto p-6 sm:rounded'
>
<Markdown
components={{
img: ({ node, className, ...props }) => {
return (
// eslint-disable-next-line @next/next/no-img-element
<img
{...props}
width={800}
height={384}
className='my-4 inline-block size-auto max-h-96'
/>
);
},
}}
>
{data?.content || ''}
</Markdown>
</motion.div>
</div>
) : null}
</AnimatePresence>
<ul className='flex w-full flex-col gap-4'>
{items.map((item, index) => (
<motion.div
layoutId={`card-${item.title}-${id}`}
key={`card-${item.title}-${id}`}
className='bg-background hover:bg-accent flex cursor-pointer items-center justify-between rounded-[40px] border p-1 sm:p-4'
>
<div className='flex flex-row items-center gap-4'>
<motion.div layoutId={`image-${item.title}-${id}`}>
<Avatar className='size-7 sm:size-12'>
<AvatarImage alt={item.title ?? ''} src={item.icon ?? ''} />
<AvatarFallback className='bg-primary/80 text-white'>
{item.title.split('')[0]}
</AvatarFallback>
</Avatar>
</motion.div>
<div className=''>
<motion.h3
layoutId={`title-${item.title}-${id}`}
className='text-[10px] font-medium text-[#225BA9] sm:text-base'
>
{item.title}
</motion.h3>
{item.updated_at && (
<motion.p
layoutId={`description-${item.title}-${id}`}
className='text-left text-[10px] font-bold text-[#848484] sm:text-base'
>
{formatDate(new Date(item.updated_at), false)}
</motion.p>
)}
</div>
</div>
<div className={'flex'}>
{item.download ? (
<button
className={cn(
buttonVariants({
variant: 'default',
}),
'mr-2 min-w-0 bg-[#EAEAEA] px-3 text-[#225BA9] hover:text-white sm:min-w-[80px]',
)}
onClick={() => openPopupWindow(item)}
>
</button>
) : null}
<motion.button
layoutId={`button-${item.title}-${id}`}
onClick={() => setActive(item)}
className={cn(
buttonVariants({
variant: 'primary',
}),
'min-w-0 px-3 sm:min-w-[80px]',
)}
>
{t('read')}
</motion.button>
</div>
</motion.div>
))}
</ul>
</>
);
}