Skip to content

Commit 5a5148d

Browse files
committed
feat(terracotta): add TerracottaSidePanel component
1 parent 23b5081 commit 5a5148d

1 file changed

Lines changed: 132 additions & 0 deletions

File tree

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { motion, AnimatePresence } from 'framer-motion';
3+
import { XIcon, InfoIcon } from '@phosphor-icons/react';
4+
import { useSidePanel } from '../context/SidePanelContext';
5+
import GenerativeArt from './GenerativeArt';
6+
7+
const TerracottaSidePanel = () => {
8+
const {
9+
isOpen,
10+
closeSidePanel,
11+
panelTitle,
12+
panelContent,
13+
panelWidth,
14+
setPanelWidth,
15+
} = useSidePanel();
16+
const [isResizing, setIsResizing] = useState(false);
17+
18+
useEffect(() => {
19+
const handleKeyDown = (event) => {
20+
if (event.key === 'Escape' && isOpen) closeSidePanel();
21+
};
22+
window.addEventListener('keydown', handleKeyDown);
23+
return () => window.removeEventListener('keydown', handleKeyDown);
24+
}, [isOpen, closeSidePanel]);
25+
26+
useEffect(() => {
27+
const handleMouseMove = (e) => {
28+
if (!isResizing) return;
29+
const newWidth = window.innerWidth - e.clientX;
30+
if (newWidth > 300 && newWidth < window.innerWidth * 0.9) {
31+
setPanelWidth(newWidth);
32+
}
33+
};
34+
35+
const handleMouseUp = () => setIsResizing(false);
36+
37+
if (isResizing) {
38+
window.addEventListener('mousemove', handleMouseMove);
39+
window.addEventListener('mouseup', handleMouseUp);
40+
document.body.style.cursor = 'ew-resize';
41+
document.body.style.userSelect = 'none';
42+
} else {
43+
document.body.style.cursor = 'default';
44+
document.body.style.userSelect = 'auto';
45+
}
46+
47+
return () => {
48+
window.removeEventListener('mousemove', handleMouseMove);
49+
window.removeEventListener('mouseup', handleMouseUp);
50+
document.body.style.cursor = 'default';
51+
document.body.style.userSelect = 'auto';
52+
};
53+
}, [isResizing, setPanelWidth]);
54+
55+
const variants = {
56+
open: { x: 0, opacity: 1 },
57+
closed: { x: '100%', opacity: 1 },
58+
};
59+
60+
return (
61+
<AnimatePresence>
62+
{isOpen && (
63+
<>
64+
<motion.div
65+
initial={{ opacity: 0 }}
66+
animate={{ opacity: 1 }}
67+
exit={{ opacity: 0 }}
68+
onClick={closeSidePanel}
69+
className="fixed inset-0 bg-[#1A1613]/50 backdrop-blur-md z-[100]"
70+
/>
71+
72+
<motion.div
73+
initial="closed"
74+
animate="open"
75+
exit="closed"
76+
variants={variants}
77+
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
78+
style={{ width: panelWidth }}
79+
className="fixed top-0 right-0 h-full bg-[#F3ECE0] border-l border-[#1A161320] shadow-[0_40px_80px_-40px_#1A161340] z-[110] flex flex-col overflow-hidden"
80+
>
81+
<div className="absolute inset-0 opacity-[0.04] pointer-events-none">
82+
<GenerativeArt seed={panelTitle} className="w-full h-full" />
83+
</div>
84+
85+
<div
86+
onMouseDown={(e) => {
87+
setIsResizing(true);
88+
e.preventDefault();
89+
}}
90+
className="absolute left-0 top-0 bottom-0 w-1 cursor-ew-resize hover:bg-[#C96442]/50 transition-colors z-[120]"
91+
title="DRAG_TO_RESIZE"
92+
/>
93+
94+
<div className="relative z-10 flex items-center justify-between p-8 border-b border-[#1A161320] bg-[#E8DECE]/50">
95+
<div className="flex flex-col gap-1 min-w-0 pr-4">
96+
<span className="font-mono text-[9px] text-[#9E4A2F] uppercase tracking-[0.3em] font-bold">
97+
Side_Panel
98+
</span>
99+
<h2 className="text-3xl font-playfairDisplay italic tracking-tight text-[#1A1613] leading-none pr-2">
100+
{panelTitle}
101+
</h2>
102+
</div>
103+
<button
104+
onClick={closeSidePanel}
105+
className="p-2 text-[#2E2620]/50 hover:text-[#9E4A2F] transition-colors flex-shrink-0"
106+
>
107+
<XIcon size={24} weight="bold" />
108+
</button>
109+
</div>
110+
111+
<div className="relative z-10 flex-1 overflow-y-auto p-8 custom-scrollbar text-[#1A1613]">
112+
<div className="space-y-8">{panelContent}</div>
113+
</div>
114+
115+
<div className="relative z-10 p-6 border-t border-[#1A161320] bg-[#E8DECE]/50 flex items-center justify-between">
116+
<div className="flex items-center gap-3 text-[#2E2620]/50 font-mono text-[9px] uppercase tracking-[0.2em]">
117+
<InfoIcon weight="fill" size={14} className="text-[#C96442]/60" />
118+
<span>Buffer_Active // {Math.round(panelWidth)}PX</span>
119+
</div>
120+
<div className="flex gap-2">
121+
<div className="w-1.5 h-1.5 bg-[#C96442] animate-pulse rounded-full" />
122+
<div className="w-1.5 h-1.5 bg-[#1A1613]/15 rounded-full" />
123+
</div>
124+
</div>
125+
</motion.div>
126+
</>
127+
)}
128+
</AnimatePresence>
129+
);
130+
};
131+
132+
export default TerracottaSidePanel;

0 commit comments

Comments
 (0)