Skip to content

Commit 3ad1be9

Browse files
committed
feat: sidepanel
1 parent 91ed6af commit 3ad1be9

File tree

6 files changed

+434
-224
lines changed

6 files changed

+434
-224
lines changed

src/App.js

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { CommandPaletteProvider } from './context/CommandPaletteContext';
1313
import { VisualSettingsProvider } from './context/VisualSettingsContext';
1414
import { AchievementProvider } from './context/AchievementContext';
1515
import AchievementListeners from './components/AchievementListeners';
16+
import { SidePanelProvider } from './context/SidePanelContext';
1617

1718
function App() {
1819
const [isModalOpen, setIsModalOpen] = useState(false);
@@ -60,16 +61,18 @@ function App() {
6061
<BSOD isActive={isBSODActive} toggleBSOD={toggleBSOD} />
6162
<ScrollToTop />
6263
<CommandPaletteProvider>
63-
<Layout
64-
toggleModal={toggleModal}
65-
isSearchVisible={isSearchVisible}
66-
toggleSearch={toggleSearch}
67-
openGenericModal={openGenericModal}
68-
toggleDigitalRain={toggleDigitalRain}
69-
toggleBSOD={toggleBSOD}
70-
>
71-
<AnimatedRoutes />
72-
</Layout>
64+
<SidePanelProvider>
65+
<Layout
66+
toggleModal={toggleModal}
67+
isSearchVisible={isSearchVisible}
68+
toggleSearch={toggleSearch}
69+
openGenericModal={openGenericModal}
70+
toggleDigitalRain={toggleDigitalRain}
71+
toggleBSOD={toggleBSOD}
72+
>
73+
<AnimatedRoutes />
74+
</Layout>
75+
</SidePanelProvider>
7376
</CommandPaletteProvider>
7477
<ContactModal isOpen={isModalOpen} onClose={toggleModal} />
7578
<GenericModal

src/components/Layout.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useVisualSettings } from '../context/VisualSettingsContext';
1212
import DigitalFlowers from './DigitalFlowers';
1313
import DigitalLeaves from './DigitalLeaves';
1414
import NaturalRain from './NaturalRain';
15+
import SidePanel from './SidePanel';
1516

1617
import { DndProvider } from '../context/DndContext';
1718

@@ -71,6 +72,7 @@ const Layout = ({
7172
toggleDigitalRain={toggleDigitalRain}
7273
toggleBSOD={toggleBSOD}
7374
/>
75+
<SidePanel />
7476
<div className="bg-gray-950 min-h-screen font-sans flex">
7577
<Sidebar
7678
isOpen={isSidebarOpen}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import React from 'react';
2+
import {StarIcon} from '@phosphor-icons/react';
3+
4+
const RatingSystemDetail = () => {
5+
const renderStars = (rating) => {
6+
return (
7+
<div className="flex gap-0.5">
8+
{[...Array(5)].map((_, i) => (
9+
<StarIcon
10+
key={i}
11+
size={14}
12+
weight="fill"
13+
className={i < rating ? 'text-yellow-500' : 'text-gray-700'}
14+
/>
15+
))}
16+
</div>
17+
);
18+
};
19+
20+
return (
21+
<div className="space-y-6">
22+
<div className="bg-gray-800/50 p-4 rounded-lg border border-gray-700">
23+
<h3 className="text-white font-bold mb-2">Philosophy</h3>
24+
<p className="text-sm text-gray-400">
25+
Ratings are subjective and reflect personal enjoyment at the time of
26+
consumption. Context matters—a "bad" movie might be a 5-star
27+
experience with the right friends.
28+
</p>
29+
</div>
30+
31+
<div className="space-y-4">
32+
<h3 className="text-white font-bold border-b border-gray-800 pb-2">
33+
The Scale
34+
</h3>
35+
36+
<div className="space-y-1">
37+
<div className="flex items-center justify-between">
38+
<span className="text-yellow-400 font-bold">Masterpiece</span>
39+
{renderStars(5)}
40+
</div>
41+
<p className="text-xs text-gray-500">
42+
Life-changing, genre-defining, or simply perfect in its execution.
43+
These are rare and cherished.
44+
</p>
45+
</div>
46+
47+
<div className="space-y-1">
48+
<div className="flex items-center justify-between">
49+
<span className="text-green-400 font-bold">Excellent</span>
50+
{renderStars(4)}
51+
</div>
52+
<p className="text-xs text-gray-500">
53+
Highly recommended. Thoroughly enjoyable with minor flaws or just
54+
missing that "spark" of perfection.
55+
</p>
56+
</div>
57+
58+
<div className="space-y-1">
59+
<div className="flex items-center justify-between">
60+
<span className="text-blue-400 font-bold">Good</span>
61+
{renderStars(3)}
62+
</div>
63+
<p className="text-xs text-gray-500">
64+
Worth your time. Solid, entertaining, but perhaps forgettable or
65+
flawed in noticeable ways.
66+
</p>
67+
</div>
68+
69+
<div className="space-y-1">
70+
<div className="flex items-center justify-between">
71+
<span className="text-orange-400 font-bold">Mediocre</span>
72+
{renderStars(2)}
73+
</div>
74+
<p className="text-xs text-gray-500">
75+
Has moments of merit but largely disappoints. Frustrating or simply
76+
boring.
77+
</p>
78+
</div>
79+
80+
<div className="space-y-1">
81+
<div className="flex items-center justify-between">
82+
<span className="text-red-400 font-bold">Poor</span>
83+
{renderStars(1)}
84+
</div>
85+
<p className="text-xs text-gray-500">
86+
Active dislike. A waste of time. Avoid unless you enjoy suffering.
87+
</p>
88+
</div>
89+
</div>
90+
<div className="space-y-2 pt-4">
91+
<h3 className="text-white font-bold border-b border-gray-800 pb-2">Inspiration</h3>
92+
<p className="text-sm text-gray-400">
93+
This rating system is heavily inspired by the classic <strong>X-Play (G4TV)</strong> scale.
94+
We believe in the sanctity of the 3-star review: a 3 is not bad, it
95+
is <strong>average</strong> or <strong>solid</strong>.
96+
Grade inflation has no place here.
97+
</p>
98+
</div>
99+
<div className="mt-8 pt-6 border-t border-gray-800 text-xs text-gray-600 italic">
100+
* Ratings may change upon re-evaluation.
101+
</div>
102+
</div>
103+
);
104+
};
105+
export default RatingSystemDetail;

src/components/SidePanel.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React, {useEffect} from 'react';
2+
import {motion, AnimatePresence} from 'framer-motion';
3+
import {XIcon} from '@phosphor-icons/react';
4+
import {useSidePanel} from '../context/SidePanelContext';
5+
6+
const SidePanel = () => {
7+
const {isOpen, closeSidePanel, panelTitle, panelContent} = useSidePanel();
8+
9+
// Close on escape key
10+
useEffect(() => {
11+
const handleKeyDown = (event) => {
12+
if (event.key === 'Escape' && isOpen) {
13+
closeSidePanel();
14+
}
15+
};
16+
window.addEventListener('keydown', handleKeyDown);
17+
return () => window.removeEventListener('keydown', handleKeyDown);
18+
}, [isOpen, closeSidePanel]);
19+
20+
const variants = {
21+
open: {x: 0, opacity: 1}, closed: {x: '100%', opacity: 1},
22+
};
23+
24+
return (<AnimatePresence>
25+
{isOpen && (<>
26+
{/* Overlay (optional, clicks close panel?) - Let's allow clicking outside to close */}
27+
<motion.div
28+
initial={{opacity: 0}}
29+
animate={{opacity: 1}}
30+
exit={{opacity: 0}}
31+
onClick={closeSidePanel}
32+
className="fixed inset-0 bg-black/50 z-[60] backdrop-blur-sm"
33+
/>
34+
35+
<motion.div
36+
initial="closed"
37+
animate="open"
38+
exit="closed"
39+
variants={variants}
40+
transition={{type: 'spring', damping: 25, stiffness: 200}}
41+
className="fixed top-0 right-0 h-full w-full max-w-md bg-gray-900 border-l border-gray-700 shadow-[-10px_0_30px_-10px_rgba(0,0,0,0.5)] z-[70] flex flex-col overflow-hidden"
42+
>
43+
{/* Header */}
44+
<div className="flex items-center justify-between p-6 border-b border-gray-800">
45+
<h2 className="text-xl font-mono font-bold text-gray-100 truncate">
46+
{panelTitle}
47+
</h2>
48+
<button
49+
onClick={closeSidePanel}
50+
className="p-2 text-gray-400 hover:text-white hover:bg-gray-800 rounded-full transition-colors"
51+
>
52+
<XIcon size={20}/>
53+
</button>
54+
</div>
55+
56+
{/* Content */}
57+
<div className="flex-1 overflow-y-auto p-6 text-gray-300 font-mono">
58+
{panelContent}
59+
</div>
60+
</motion.div>
61+
</>)}
62+
</AnimatePresence>);
63+
};
64+
65+
export default SidePanel;

src/context/SidePanelContext.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React, { createContext, useContext, useState } from 'react';
2+
3+
const SidePanelContext = createContext();
4+
5+
export const useSidePanel = () => {
6+
return useContext(SidePanelContext);
7+
};
8+
9+
export const SidePanelProvider = ({ children }) => {
10+
const [isOpen, setIsOpen] = useState(false);
11+
const [panelContent, setPanelContent] = useState(null);
12+
const [panelTitle, setPanelTitle] = useState('');
13+
14+
const openSidePanel = (title, content) => {
15+
setPanelTitle(title);
16+
setPanelContent(content);
17+
setIsOpen(true);
18+
};
19+
20+
const closeSidePanel = () => {
21+
setIsOpen(false);
22+
// Optional: clear content after animation, but keeping it simple for now
23+
};
24+
25+
return (
26+
<SidePanelContext.Provider
27+
value={{
28+
isOpen,
29+
panelTitle,
30+
panelContent,
31+
openSidePanel,
32+
closeSidePanel,
33+
}}
34+
>
35+
{children}
36+
</SidePanelContext.Provider>
37+
);
38+
};

0 commit comments

Comments
 (0)