summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/panels/preview/guide-popover.tsx
blob: bcac8075fe48bacd7c6eff7c59be8f2edd1a163f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
"use client";

import { AnimatePresence, motion } from "motion/react";
import { GUIDE_REGISTRY, getGuideById } from "@/lib/guides";
import { usePreviewStore } from "@/stores/preview-store";
import {
	Popover,
	PopoverContent,
	PopoverTrigger,
} from "@/components/ui/popover";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/utils/ui";

export function GridPopover({ children }: { children: React.ReactNode }) {
	const activeGuide = usePreviewStore((state) => state.activeGuide);
	const toggleGuide = usePreviewStore((state) => state.toggleGuide);
	const activeGuideDef = getGuideById(activeGuide);
	const options = activeGuideDef?.renderOptions?.();

	return (
		<Popover>
			<PopoverTrigger>{children}</PopoverTrigger>
			<PopoverContent sideOffset={8} className="w-60 px-0">
				<div className="flex flex-col gap-2 px-4">
					<Label>Guides</Label>
					<div className="grid grid-cols-3 gap-1">
						{GUIDE_REGISTRY.map((guide) => (
							<GridItem
								key={guide.id}
								label={guide.label}
								preview={guide.renderPreview()}
								isSelected={activeGuide === guide.id}
								onClick={() => toggleGuide(guide.id)}
							/>
						))}
					</div>
				</div>
				<AnimatePresence>
					{options && (
						<motion.div
							initial={{ height: 0, opacity: 0 }}
							animate={{ height: "auto", opacity: 1 }}
							exit={{ height: 0, opacity: 0 }}
							transition={{
								height: { type: "spring", duration: 0.2, bounce: 0 },
								opacity: { duration: 0.2 },
							}}
							className="overflow-hidden"
						>
							<Separator className="my-3" />
							<div className="px-4">{options}</div>
						</motion.div>
					)}
				</AnimatePresence>
			</PopoverContent>
		</Popover>
	);
}

function GridItem({
	label,
	preview,
	isSelected,
	onClick,
}: {
	label: string;
	preview: React.ReactNode;
	isSelected?: boolean;
	onClick: () => void;
}) {
	return (
		<button
			type="button"
			onClick={onClick}
			aria-pressed={isSelected}
			className={cn(
				"flex cursor-pointer rounded-sm flex-col gap-2 px-1.5 py-1.5 hover:bg-foreground/5",
				isSelected && "bg-primary/5! text-primary",
			)}
		>
			<div
				className={cn(
					"aspect-video bg-foreground/5 flex items-center justify-center rounded-sm",
					isSelected && "bg-primary/5!",
				)}
			>
				{preview}
			</div>
			<span
				className={cn(
					"text-xs",
					isSelected ? "text-primary" : "text-muted-foreground",
				)}
			>
				{label}
			</span>
		</button>
	);
}