Skip to content

Commit 630fb3f

Browse files
committed
refactor: sidebar v2
1 parent 53e6750 commit 630fb3f

File tree

4 files changed

+285
-10
lines changed

4 files changed

+285
-10
lines changed

src/components/BrutalistSidebar.js

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
import React, { useRef } from 'react';
2+
import { NavLink, Link, useLocation, useNavigate } from 'react-router-dom';
3+
import { motion } from 'framer-motion';
4+
import {
5+
House,
6+
User,
7+
BookOpen,
8+
Wrench,
9+
Article,
10+
SquaresFour,
11+
GearSix,
12+
MagnifyingGlass,
13+
Timer,
14+
PushPin,
15+
Trophy,
16+
Shuffle,
17+
EnvelopeSimple,
18+
BugBeetle,
19+
ArrowRight,
20+
Sword,
21+
Rss,
22+
} from '@phosphor-icons/react';
23+
24+
import Fez from './Fez';
25+
import { version } from '../version';
26+
import usePersistentState from '../hooks/usePersistentState';
27+
import { KEY_SIDEBAR_STATE } from '../utils/LocalStorageManager';
28+
import { useAchievements } from '../context/AchievementContext';
29+
30+
const BrutalistSidebar = ({ isOpen, toggleSidebar, toggleModal, setIsPaletteOpen }) => {
31+
const [sidebarState, setSidebarState] = usePersistentState(
32+
KEY_SIDEBAR_STATE,
33+
{
34+
isMainOpen: true,
35+
isContentOpen: true,
36+
isAppsOpen: true,
37+
isStatusOpen: false,
38+
isExtrasOpen: false,
39+
},
40+
);
41+
42+
const { unlockAchievement } = useAchievements();
43+
const scrollRef = useRef(null);
44+
const location = useLocation();
45+
const navigate = useNavigate();
46+
47+
const toggleSection = (section) => {
48+
setSidebarState((prevState) => ({
49+
...prevState,
50+
[section]: !prevState[section],
51+
}));
52+
};
53+
54+
const getLinkClass = ({ isActive }) =>
55+
`group flex items-center justify-between px-6 py-3 transition-all duration-300 border-b border-white/5 ${
56+
isActive
57+
? 'bg-emerald-500/10 text-white'
58+
: 'text-gray-300 hover:text-white hover:bg-white/5'
59+
}`;
60+
61+
const SectionHeader = ({ id, label, isOpen, active }) => (
62+
<button
63+
onClick={() => toggleSection(id)}
64+
className={`flex items-center justify-between w-full px-6 py-4 border-b border-white/10 transition-all duration-300 ${
65+
active
66+
? 'bg-emerald-500/5 text-emerald-400 border-l-2 border-emerald-500'
67+
: 'text-gray-600 hover:text-gray-400 border-l-2 border-transparent'
68+
}`}
69+
>
70+
<span className="font-mono text-[10px] font-black uppercase tracking-[0.3em]">
71+
// {label}
72+
</span>
73+
<span className={`transform transition-transform duration-300 ${isOpen ? 'rotate-180' : ''}`}>
74+
75+
</span>
76+
</button>
77+
);
78+
79+
const sidebarVariants = {
80+
open: { x: 0, transition: { type: 'circOut', duration: 0.4 } },
81+
closed: { x: '-100%', transition: { type: 'circIn', duration: 0.3 } },
82+
};
83+
84+
return (
85+
<>
86+
<div
87+
className={`fixed inset-0 bg-black/80 backdrop-blur-sm z-40 md:hidden transition-opacity ${
88+
isOpen ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'
89+
}`}
90+
onClick={toggleSidebar}
91+
/>
92+
93+
<motion.aside
94+
initial={false}
95+
animate={isOpen ? 'open' : 'closed'}
96+
variants={sidebarVariants}
97+
className="fixed top-0 left-0 h-screen w-72 bg-[#050505] z-50 flex flex-col border-r border-white/10 shadow-2xl"
98+
>
99+
<div className="p-8 border-b border-white/10 flex flex-col gap-2 bg-black/50">
100+
<Link to="/" className="flex items-center gap-3 group" onClick={isOpen && window.innerWidth < 768 ? toggleSidebar : undefined}>
101+
<span className="text-xl font-black uppercase tracking-tighter text-white">
102+
Fez<span className="text-emerald-500">codex</span>
103+
</span>
104+
</Link>
105+
<span className="font-mono text-[9px] text-gray-500 uppercase tracking-widest font-bold">
106+
Digital Archive Kernel v{version}
107+
</span>
108+
</div>
109+
110+
{/* Scrollable Content */}
111+
<div ref={scrollRef} className="flex-grow overflow-y-auto no-scrollbar">
112+
113+
{/* Section: Main */}
114+
<SectionHeader
115+
id="isMainOpen"
116+
label="Main"
117+
isOpen={sidebarState.isMainOpen}
118+
active={location.pathname === '/' || location.pathname === '/about' || location.pathname === '/achievements'}
119+
/>
120+
{sidebarState.isMainOpen && (
121+
<nav className="flex flex-col">
122+
<SidebarLink to="/" icon={House} label="Home" getLinkClass={getLinkClass} />
123+
<SidebarLink to="/about" icon={User} label="About" getLinkClass={getLinkClass} />
124+
<SidebarLink to="/achievements" icon={Trophy} label="Achievements" getLinkClass={getLinkClass} />
125+
</nav>
126+
)}
127+
128+
{/* Section: Content */}
129+
<SectionHeader
130+
id="isContentOpen"
131+
label="Feed"
132+
isOpen={sidebarState.isContentOpen}
133+
active={location.pathname.startsWith('/blog') || location.pathname.startsWith('/projects') || location.pathname.startsWith('/logs')}
134+
/>
135+
{sidebarState.isContentOpen && (
136+
<nav className="flex flex-col">
137+
<SidebarLink to="/blog" icon={BookOpen} label="Blogposts" getLinkClass={getLinkClass} />
138+
<SidebarLink to="/projects" icon={Wrench} label="Projects" getLinkClass={getLinkClass} />
139+
<SidebarLink to="/logs" icon={Article} label="Discovery Logs" getLinkClass={getLinkClass} />
140+
</nav>
141+
)}
142+
143+
{/* Section: Tools */}
144+
<SectionHeader
145+
id="isAppsOpen"
146+
label="Utilities"
147+
isOpen={sidebarState.isAppsOpen}
148+
active={location.pathname.startsWith('/apps') || location.pathname.startsWith('/pinned-apps') || location.pathname.startsWith('/commands')}
149+
/>
150+
{sidebarState.isAppsOpen && (
151+
<nav className="flex flex-col">
152+
<SidebarLink to="/pinned-apps" icon={PushPin} label="Favorites" getLinkClass={getLinkClass} />
153+
<SidebarLink to="/apps" icon={SquaresFour} label="App Center" getLinkClass={getLinkClass} />
154+
<SidebarLink to="/commands" icon={MagnifyingGlass} label="Manuals" getLinkClass={getLinkClass} />
155+
</nav>
156+
)}
157+
158+
{/* Section: Status */}
159+
<SectionHeader
160+
id="isStatusOpen"
161+
label="System_Status"
162+
isOpen={sidebarState.isStatusOpen}
163+
active={location.pathname.startsWith('/roadmap') || location.pathname.startsWith('/timeline')}
164+
/>
165+
{sidebarState.isStatusOpen && (
166+
<nav className="flex flex-col">
167+
<SidebarLink to="/timeline" icon={Timer} label="History" getLinkClass={getLinkClass} />
168+
<SidebarLink to="/roadmap" icon={BugBeetle} label="Fezzilla" getLinkClass={getLinkClass} />
169+
</nav>
170+
)}
171+
172+
{/* Section: Extras */}
173+
<SectionHeader
174+
id="isExtrasOpen"
175+
label="External_Nodes"
176+
isOpen={sidebarState.isExtrasOpen}
177+
active={location.pathname.startsWith('/stories')}
178+
/>
179+
{sidebarState.isExtrasOpen && (
180+
<nav className="flex flex-col">
181+
<SidebarLink to="/stories" icon={Sword} label="S_&_F" getLinkClass={getLinkClass} />
182+
<a
183+
href="/rss.xml"
184+
target="_blank"
185+
rel="noopener noreferrer"
186+
className="group flex items-center justify-between px-6 py-3 transition-all duration-300 border-b border-white/5 text-gray-300 hover:text-white hover:bg-white/5"
187+
>
188+
<div className="flex items-center gap-4">
189+
<Rss size={18} weight="bold" />
190+
<span className="font-mono text-[11px] font-bold uppercase tracking-widest">RSS_Feed</span>
191+
</div>
192+
<ArrowRight size={14} className="opacity-0 group-hover:opacity-100 -translate-x-2 group-hover:translate-x-0 transition-all" />
193+
</a>
194+
</nav>
195+
)}
196+
</div>
197+
<div className="p-6 border-t border-white/10 bg-black/50">
198+
<div className="grid grid-cols-2 gap-2 mb-6">
199+
<FooterButton onClick={() => setIsPaletteOpen(true)} icon={MagnifyingGlass} label="CMDS" />
200+
<FooterButton onClick={() => navigate('/settings')} icon={GearSix} label="SETT" />
201+
<FooterButton onClick={() => { navigate('/random'); unlockAchievement('feeling_lucky'); }} icon={Shuffle} label="RAND" />
202+
<FooterButton onClick={toggleModal} icon={EnvelopeSimple} label="CONT" />
203+
</div>
204+
<div className="text-center">
205+
<p className="font-mono text-[8px] text-gray-600 uppercase tracking-widest font-bold">
206+
{${new Date().getFullYear()} Fezcode // End of Segment`}
207+
</p>
208+
</div>
209+
</div>
210+
</motion.aside>
211+
</>
212+
);
213+
};
214+
215+
const SidebarLink = ({ to, icon: Icon, label, getLinkClass }) => (
216+
<NavLink to={to} className={getLinkClass}>
217+
<div className="flex items-center gap-4">
218+
<Icon size={18} weight="bold" />
219+
<span className="font-mono text-[11px] font-bold uppercase tracking-widest">{label}</span>
220+
</div>
221+
<ArrowRight size={14} className="opacity-0 group-hover:opacity-100 -translate-x-2 group-hover:translate-x-0 transition-all" />
222+
</NavLink>
223+
);
224+
225+
const FooterButton = ({ onClick, icon: Icon, label }) => (
226+
<button
227+
onClick={onClick}
228+
className="group flex flex-col items-center gap-2 p-2 border border-white/5 bg-white/5 hover:bg-white hover:border-white transition-all rounded-sm"
229+
>
230+
<div className="p-2 text-white group-hover:text-black transition-all">
231+
<Icon size={18} weight="bold" />
232+
</div>
233+
<span className="font-mono text-[8px] font-bold tracking-widest text-gray-500 group-hover:text-black transition-colors">
234+
{label}
235+
</span>
236+
</button>
237+
);
238+
export default BrutalistSidebar;

src/components/Layout.js

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useState, useEffect } from 'react';
22
import Navbar from './Navbar';
33
import Sidebar from './Sidebar';
4+
import BrutalistSidebar from './BrutalistSidebar';
45
import Footer from './Footer';
56
import DndNavbar from './dnd/DndNavbar';
67
import DndFooter from './dnd/DndFooter';
@@ -27,7 +28,7 @@ const Layout = ({
2728
}) => {
2829
const [isSidebarOpen, setIsSidebarOpen] = useState(window.innerWidth > 768);
2930
const { isPaletteOpen, setIsPaletteOpen } = useCommandPalette();
30-
const { isGarden, isAutumn, isRain, sidebarColor } = useVisualSettings();
31+
const { isGarden, isAutumn, isRain, sidebarColor, sidebarMode } = useVisualSettings();
3132
const location = useLocation();
3233

3334
useEffect(() => {
@@ -74,15 +75,24 @@ const Layout = ({
7475
/>
7576
<SidePanel />
7677
<div className="bg-gray-950 min-h-screen font-sans flex">
77-
<Sidebar
78-
isOpen={isSidebarOpen}
79-
toggleSidebar={toggleSidebar}
80-
toggleModal={toggleModal}
81-
setIsPaletteOpen={setIsPaletteOpen} // Pass setIsPaletteOpen to Sidebar
82-
sidebarColor={sidebarColor} // Pass sidebarColor to Sidebar
83-
/>{' '}
78+
{sidebarMode === 'classic' ? (
79+
<Sidebar
80+
isOpen={isSidebarOpen}
81+
toggleSidebar={toggleSidebar}
82+
toggleModal={toggleModal}
83+
setIsPaletteOpen={setIsPaletteOpen}
84+
sidebarColor={sidebarColor}
85+
/>
86+
) : (
87+
<BrutalistSidebar
88+
isOpen={isSidebarOpen}
89+
toggleSidebar={toggleSidebar}
90+
toggleModal={toggleModal}
91+
setIsPaletteOpen={setIsPaletteOpen}
92+
/>
93+
)}
8494
<div
85-
className={`flex-1 flex flex-col transition-all duration-300 ${isSidebarOpen ? 'md:ml-64' : 'md:ml-0'}`}
95+
className={`flex-1 flex flex-col transition-all duration-300 ${isSidebarOpen ? (sidebarMode === 'classic' ? 'md:ml-64' : 'md:ml-72') : 'md:ml-0'}`}
8696
>
8797
<Navbar
8898
toggleSidebar={toggleSidebar}
@@ -99,4 +109,4 @@ const Layout = ({
99109
);
100110
};
101111

102-
export default Layout;
112+
export default Layout;

src/context/VisualSettingsContext.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const VisualSettingsProvider = ({ children }) => {
4343
const [isAutumn, setIsAutumn] = usePersistentState('is-autumn', false);
4444
const [isRain, setIsRain] = usePersistentState('is-rain', false);
4545
const [blogPostViewMode, setBlogPostViewMode] = usePersistentState('blog-post-view-mode','standard');
46+
const [sidebarMode, setSidebarMode] = usePersistentState('sidebar-mode', 'brutalist');
4647
const [sidebarColor, setSidebarColor] = usePersistentState('sidebar-color','default');
4748

4849
// Chaos Theory Achievement Tracker
@@ -276,6 +277,8 @@ export const VisualSettingsProvider = ({ children }) => {
276277
toggleRain,
277278
blogPostViewMode,
278279
setBlogPostViewMode,
280+
sidebarMode,
281+
setSidebarMode,
279282
sidebarColor,
280283
setSidebarColor,
281284
}}

src/pages/SettingsPage.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ const SettingsPage = () => {
112112
toggleRain,
113113
blogPostViewMode,
114114
setBlogPostViewMode,
115+
sidebarMode,
116+
setSidebarMode,
115117
sidebarColor,
116118
setSidebarColor,
117119
} = useVisualSettings();
@@ -491,6 +493,28 @@ const SettingsPage = () => {
491493
{/* Interface & Layout */}
492494
<Section title="Interface & Layout" icon={<Layout />} delay={0.4}>
493495
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
496+
<div className="bg-gray-800/30 rounded-xl p-6 border border-white/5 flex flex-col justify-between">
497+
<div>
498+
<div className="flex items-center gap-2 mb-3 text-emerald-500">
499+
<Sidebar size={20} weight="duotone" />
500+
<h3 className="font-medium text-white">Sidebar Style</h3>
501+
</div>
502+
<p className="text-sm text-gray-400 mb-6">
503+
Toggle between the modern Brutalist look or the original Classic design.
504+
</p>
505+
</div>
506+
<CustomDropdown
507+
label="Select Style"
508+
options={[
509+
{ label: 'Brutalist', value: 'brutalist' },
510+
{ label: 'Classic', value: 'classic' },
511+
]}
512+
value={sidebarMode}
513+
onChange={setSidebarMode}
514+
icon={Sidebar}
515+
/>
516+
</div>
517+
494518
<div className="bg-gray-800/30 rounded-xl p-6 border border-white/5 flex flex-col justify-between">
495519
<div>
496520
<div className="flex items-center gap-2 mb-3 text-rose-500">

0 commit comments

Comments
 (0)