Skip to content

Commit 083086f

Browse files
committed
refactor: use sidebar.piml to create BrutalistSidebar
1 parent ecc5384 commit 083086f

File tree

2 files changed

+189
-175
lines changed

2 files changed

+189
-175
lines changed

public/sidebar.piml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
(sidebar)
2+
> (section)
3+
(id) isMainOpen
4+
(label) Main
5+
(content)
6+
> (item)
7+
(label) Home
8+
(to) /
9+
(icon) HouseIcon
10+
> (item)
11+
(label) About
12+
(to) /about
13+
(icon) UserIcon
14+
> (item)
15+
(label) Achievements
16+
(to) /achievements
17+
(icon) TrophyIcon
18+
19+
> (section)
20+
(id) isContentOpen
21+
(label) Feed
22+
(content)
23+
> (item)
24+
(label) Blogposts
25+
(to) /blog
26+
(icon) BookOpenIcon
27+
> (item)
28+
(label) Projects
29+
(to) /projects
30+
(icon) WrenchIcon
31+
> (item)
32+
(label) Discovery Logs
33+
(to) /logs
34+
(icon) ArticleIcon
35+
36+
> (section)
37+
(id) isAppsOpen
38+
(label) Utilities
39+
(content)
40+
> (item)
41+
(label) Favorites
42+
(to) /pinned-apps
43+
(icon) PushPinIcon
44+
> (item)
45+
(label) App Center
46+
(to) /apps
47+
(icon) SquaresFourIcon
48+
> (item)
49+
(label) Manuals
50+
(to) /commands
51+
(icon) MagnifyingGlassIcon
52+
53+
> (section)
54+
(id) isStatusOpen
55+
(label) System
56+
(content)
57+
> (item)
58+
(label) History
59+
(to) /timeline
60+
(icon) TimerIcon
61+
> (item)
62+
(label) Fezilla
63+
(to) /roadmap
64+
(icon) BugBeetleIcon
65+
> (item)
66+
(label) Brufez Spec
67+
(to) /brufez
68+
(icon) FlaskIcon
69+
70+
> (section)
71+
(id) isExtrasOpen
72+
(label) External Nodes
73+
(content)
74+
> (item)
75+
(label) Neural Net
76+
(to) /graph
77+
(icon) GraphIcon
78+
> (item)
79+
(label) Serfs & Frauds
80+
(to) /stories
81+
(icon) SwordIcon
82+
> (item)
83+
(label) RSS_Feed
84+
(url) /rss.xml
85+
(icon) RssIcon
86+
(external) true
87+

src/components/BrutalistSidebar.js

Lines changed: 102 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,31 @@ import usePersistentState from '../hooks/usePersistentState';
3030
import { KEY_SIDEBAR_STATE } from '../utils/LocalStorageManager';
3131
import { useAchievements } from '../context/AchievementContext';
3232
import { useSiteConfig } from '../context/SiteConfigContext';
33+
import piml from 'piml';
34+
35+
const ICON_MAP = {
36+
HouseIcon,
37+
UserIcon,
38+
BookOpenIcon,
39+
WrenchIcon,
40+
ArticleIcon,
41+
SquaresFourIcon,
42+
GearSixIcon,
43+
MagnifyingGlassIcon,
44+
TimerIcon,
45+
PushPinIcon,
46+
TrophyIcon,
47+
ShuffleIcon,
48+
EnvelopeSimpleIcon,
49+
BugBeetleIcon,
50+
ArrowRightIcon,
51+
SwordIcon,
52+
RssIcon,
53+
GraphIcon,
54+
CaretDoubleDownIcon,
55+
CaretDoubleUpIcon,
56+
FlaskIcon,
57+
};
3358

3459
const BrutalistSidebar = ({
3560
isOpen,
@@ -38,6 +63,24 @@ const BrutalistSidebar = ({
3863
setIsPaletteOpen,
3964
}) => {
4065
const { config } = useSiteConfig();
66+
const [sidebarConfig, setSidebarConfig] = useState(null);
67+
68+
useEffect(() => {
69+
const fetchSidebarConfig = async () => {
70+
try {
71+
const response = await fetch('/sidebar.piml');
72+
if (response.ok) {
73+
const text = await response.text();
74+
const parsed = piml.parse(text);
75+
setSidebarConfig(parsed.sidebar);
76+
}
77+
} catch (error) {
78+
console.error('Failed to load sidebar config:', error);
79+
}
80+
};
81+
fetchSidebarConfig();
82+
}, []);
83+
4184
const [sidebarState, setSidebarState] = usePersistentState(
4285
KEY_SIDEBAR_STATE,
4386
{
@@ -179,182 +222,66 @@ const BrutalistSidebar = ({
179222
ref={scrollRef}
180223
className="h-full overflow-y-auto scrollbar-hide no-scrollbar"
181224
>
182-
{/* Section: Main */}
183-
<SectionHeader
184-
id="isMainOpen"
185-
label="Main"
186-
isOpen={sidebarState.isMainOpen}
187-
active={
188-
location.pathname === '/' ||
189-
location.pathname === '/about' ||
190-
location.pathname === '/achievements'
191-
}
192-
/>
193-
{sidebarState.isMainOpen && (
194-
<nav className="flex flex-col">
195-
<SidebarLink
196-
to="/"
197-
icon={HouseIcon}
198-
label="Home"
199-
getLinkClass={getLinkClass}
200-
/>
201-
<SidebarLink
202-
to="/about"
203-
icon={UserIcon}
204-
label="About"
205-
getLinkClass={getLinkClass}
206-
/>
207-
<SidebarLink
208-
to="/achievements"
209-
icon={TrophyIcon}
210-
label="Achievements"
211-
getLinkClass={getLinkClass}
212-
/>
213-
</nav>
214-
)}
225+
{Array.isArray(sidebarConfig) &&
226+
sidebarConfig.map((section, sectionIdx) => {
227+
const items = Array.isArray(section.content)
228+
? section.content
229+
: [];
230+
const isActive = items.some((item) =>
231+
item.to === '/'
232+
? location.pathname === '/'
233+
: item.to && location.pathname.startsWith(item.to),
234+
);
215235

216-
{/* Section: Content */}
217-
<SectionHeader
218-
id="isContentOpen"
219-
label="Feed"
220-
isOpen={sidebarState.isContentOpen}
221-
active={
222-
location.pathname.startsWith('/blog') ||
223-
location.pathname.startsWith('/projects') ||
224-
location.pathname.startsWith('/logs')
225-
}
226-
/>
227-
{sidebarState.isContentOpen && (
228-
<nav className="flex flex-col">
229-
<SidebarLink
230-
to="/blog"
231-
icon={BookOpenIcon}
232-
label="Blogposts"
233-
getLinkClass={getLinkClass}
234-
/>
235-
<SidebarLink
236-
to="/projects"
237-
icon={WrenchIcon}
238-
label="Projects"
239-
getLinkClass={getLinkClass}
240-
/>
241-
<SidebarLink
242-
to="/logs"
243-
icon={ArticleIcon}
244-
label="Discovery Logs"
245-
getLinkClass={getLinkClass}
246-
/>
247-
</nav>
248-
)}
249-
250-
{/* Section: Tools */}
251-
<SectionHeader
252-
id="isAppsOpen"
253-
label="Utilities"
254-
isOpen={sidebarState.isAppsOpen}
255-
active={
256-
location.pathname.startsWith('/apps') ||
257-
location.pathname.startsWith('/pinned-apps') ||
258-
location.pathname.startsWith('/commands')
259-
}
260-
/>
261-
{sidebarState.isAppsOpen && (
262-
<nav className="flex flex-col">
263-
<SidebarLink
264-
to="/pinned-apps"
265-
icon={PushPinIcon}
266-
label="Favorites"
267-
getLinkClass={getLinkClass}
268-
/>
269-
<SidebarLink
270-
to="/apps"
271-
icon={SquaresFourIcon}
272-
label="App Center"
273-
getLinkClass={getLinkClass}
274-
/>
275-
<SidebarLink
276-
to="/commands"
277-
icon={MagnifyingGlassIcon}
278-
label="Manuals"
279-
getLinkClass={getLinkClass}
280-
/>
281-
</nav>
282-
)}
283-
284-
{/* Section: Status */}
285-
<SectionHeader
286-
id="isStatusOpen"
287-
label="System"
288-
isOpen={sidebarState.isStatusOpen}
289-
active={
290-
location.pathname.startsWith('/roadmap') ||
291-
location.pathname.startsWith('/timeline') ||
292-
location.pathname.startsWith('/brufez')
293-
}
294-
/>
295-
{sidebarState.isStatusOpen && (
296-
<nav className="flex flex-col">
297-
<SidebarLink
298-
to="/timeline"
299-
icon={TimerIcon}
300-
label="History"
301-
getLinkClass={getLinkClass}
302-
/>
303-
<SidebarLink
304-
to="/roadmap"
305-
icon={BugBeetleIcon}
306-
label="Fezzilla"
307-
getLinkClass={getLinkClass}
308-
/>
309-
<SidebarLink
310-
to="/brufez"
311-
icon={FlaskIcon}
312-
label="Brufez Spec"
313-
getLinkClass={getLinkClass}
314-
/>
315-
</nav>
316-
)}
317-
318-
{/* Section: Extras */}
319-
<SectionHeader
320-
id="isExtrasOpen"
321-
label="External Nodes"
322-
isOpen={sidebarState.isExtrasOpen}
323-
active={location.pathname.startsWith('/stories')}
324-
/>
325-
{sidebarState.isExtrasOpen && (
326-
<nav className="flex flex-col">
327-
<SidebarLink
328-
to="/graph"
329-
icon={GraphIcon}
330-
label="Neural Net"
331-
getLinkClass={getLinkClass}
332-
/>
333-
<SidebarLink
334-
to="/stories"
335-
icon={SwordIcon}
336-
label="Serfs & Frauds"
337-
getLinkClass={getLinkClass}
338-
/>
339-
<a
340-
href="/rss.xml"
341-
target="_blank"
342-
rel="noopener noreferrer"
343-
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"
344-
>
345-
<div className="flex items-center gap-4">
346-
<RssIcon size={18} weight="bold" />
347-
<span className="font-arvo text-sm font-medium uppercase tracking-widest">
348-
RSS_Feed
349-
</span>
350-
</div>
351-
<ArrowRightIcon
352-
size={14}
353-
className="opacity-0 group-hover:opacity-100 -translate-x-2 group-hover:translate-x-0 transition-all"
354-
/>
355-
</a>
356-
</nav>
357-
)}
236+
return (
237+
<React.Fragment key={section.id || sectionIdx}>
238+
<SectionHeader
239+
id={section.id}
240+
label={section.label}
241+
isOpen={sidebarState[section.id]}
242+
active={isActive}
243+
/>
244+
{sidebarState[section.id] && (
245+
<nav className="flex flex-col">
246+
{items.map((item, idx) => {
247+
const Icon = ICON_MAP[item.icon] || ArrowRightIcon;
248+
if (item.external === 'true' || item.url) {
249+
return (
250+
<a
251+
key={idx}
252+
href={item.url || item.to}
253+
target="_blank"
254+
rel="noopener noreferrer"
255+
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"
256+
>
257+
<div className="flex items-center gap-4">
258+
<Icon size={18} weight="bold" />
259+
<span className="font-arvo text-sm font-medium uppercase tracking-widest">
260+
{item.label}
261+
</span>
262+
</div>
263+
<ArrowRightIcon
264+
size={14}
265+
className="opacity-0 group-hover:opacity-100 -translate-x-2 group-hover:translate-x-0 transition-all"
266+
/>
267+
</a>
268+
);
269+
}
270+
return (
271+
<SidebarLink
272+
key={idx}
273+
to={item.to}
274+
icon={Icon}
275+
label={item.label}
276+
getLinkClass={getLinkClass}
277+
/>
278+
);
279+
})}
280+
</nav>
281+
)}
282+
</React.Fragment>
283+
);
284+
})}
358285
</div>
359286

360287
{showScrollGradient.bottom && (

0 commit comments

Comments
 (0)