Skip to content

Commit 0aa2131

Browse files
committed
Header Adjustments
1 parent bc08883 commit 0aa2131

File tree

8 files changed

+209
-140
lines changed

8 files changed

+209
-140
lines changed

app/home-client.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
5+
import Header from './ui/header';
6+
import SkyboxGrid from './ui/skyboxgrid';
7+
8+
type SortOption = 'alpha' | 'alpha-desc' | 'published-date-desc' | 'published-date-asc';
9+
10+
interface HomeClientProps {
11+
slugs: string[];
12+
meta: Record<string, any>;
13+
}
14+
15+
export default function HomeClient({ slugs, meta }: HomeClientProps) {
16+
const [sort, setSort] = useState<SortOption>('published-date-desc');
17+
const [query, setQuery] = useState('');
18+
19+
return (
20+
<main>
21+
<Header
22+
sort={sort}
23+
setSort={setSort}
24+
query={query}
25+
setQuery={setQuery}
26+
/>
27+
<SkyboxGrid slugs={slugs} meta={meta} sort={sort} query={query} />
28+
</main>
29+
);
30+
}

app/page.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1+
// Server Component (default)
12
import fs from 'fs';
23
import path from 'path';
3-
import SkyboxGrid from './ui/skyboxgrid';
44

5+
import HomeClient from './home-client'; // Import the client component
6+
7+
// Data fetching happens here, on the server
58
const allPath = path.join(process.cwd(), 'public', 'data', 'index.json');
69
const allData = JSON.parse(fs.readFileSync(allPath, 'utf8')) as Record<string, any>;
710
const slugs = Object.keys(allData);
811

9-
export default function Home() {
10-
return <SkyboxGrid slugs={slugs} meta={allData} />;
12+
export default function Page() {
13+
// Render the Client Component, passing the server-fetched data as props
14+
return <HomeClient slugs={slugs} meta={allData} />;
1115
}

app/ui/header.tsx

Lines changed: 38 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,67 @@
1+
/* app/ui/header.tsx */
12
'use client';
23

3-
import { useState, useEffect } from 'react';
4-
import Image from 'next/image';
4+
import type { Dispatch, SetStateAction } from 'react';
55

6-
const profileLinks = [
7-
{
8-
href: "https://github.com/Jacobdeanr",
9-
label: "GitHub",
10-
svgPath: "/Source_Skyboxes_NextJS/icons/github-mark-white.svg",
11-
},
12-
{
13-
href: "https://discord.gg/grqAfezMVs",
14-
label: "Discord",
15-
svgPath: "/Source_Skyboxes_NextJS/icons/discord-symbol-white.svg",
16-
},
17-
{
18-
href: "https://steamcommunity.com/id/Jakobi_OBrien",
19-
label: "Steam",
20-
svgPath: "/Source_Skyboxes_NextJS/icons/steam-logo.svg",
21-
},
22-
];
6+
import IconLink from './iconlink';
7+
import MoreMenu from './moremenu';
8+
import { profileLinks } from './profile-links';
239

24-
function IconLink({
25-
href,
26-
label,
27-
svgPath,
28-
}: {
29-
href: string;
30-
label: string;
31-
svgPath: string;
32-
}) {
33-
return (
34-
<a
35-
href={href}
36-
aria-label={label}
37-
target="_blank"
38-
rel="noreferrer"
39-
className="
40-
flex items-center justify-center
41-
p-2 rounded-md
42-
bg-neutral-800/70 hover:bg-neutral-800
43-
focus:outline-none focus:ring-2 focus:ring-amber-500
44-
"
45-
>
46-
<Image src={svgPath} alt={label} width={24} height={24} />
47-
</a>
48-
);
49-
}
10+
import SortSelect from './sort-select';
5011

51-
function MoreMenu() {
52-
const [open, setOpen] = useState(false);
53-
54-
return (
55-
<div className="relative flex-shrink-0">
56-
<button
57-
onClick={() => setOpen(!open)}
58-
aria-label="Open links"
59-
className="sm:hidden p-2 rounded-md bg-neutral-800/70 hover:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-amber-500"
60-
>
61-
<svg viewBox="0 0 24 24" className="w-4 h-4 fill-neutral-300">
62-
<circle cx="5" cy="12" r="2" />
63-
<circle cx="12" cy="12" r="2" />
64-
<circle cx="19" cy="12" r="2" />
65-
</svg>
66-
</button>
67-
68-
{open && (
69-
<div className="absolute right-0 mt-2 w-40 rounded-md bg-neutral-900 shadow-lg ring-1 ring-neutral-700/60">
70-
<nav className="flex flex-col divide-y divide-neutral-700/60">
71-
{profileLinks.map((link) => (
72-
<IconLink key={link.label} href={link.href} label={link.label} svgPath={link.svgPath} />
73-
))}
74-
</nav>
75-
</div>
76-
)}
77-
</div>
78-
);
79-
}
12+
type SortOption = 'alpha' | 'alpha-desc' | 'published-date-desc' | 'published-date-asc';
8013

14+
/* ------------------ main header ------------------ */
8115
interface HeaderProps {
82-
onChange: (opts: {
83-
sort: 'alpha' | 'alpha-desc' | 'published-date-desc' | 'published-date-asc';
84-
query: string;
85-
}) => void;
16+
sort: SortOption;
17+
setSort: Dispatch<SetStateAction<SortOption>>;
18+
query: string;
19+
setQuery: Dispatch<SetStateAction<string>>;
8620
}
8721

88-
export default function Header({ onChange }: HeaderProps) {
89-
const [sort, setSort] = useState<'alpha' | 'alpha-desc' | 'published-date-desc' | 'published-date-asc'>('published-date-desc');
90-
const [query, setQuery] = useState('');
91-
92-
useEffect(() => onChange({ sort, query }), [sort, query, onChange]);
22+
export default function Header({ sort, setSort, query, setQuery }: HeaderProps) {
9323

9424
return (
9525
<header className="sticky top-0 z-40 backdrop-blur-md bg-neutral-900/70 border-b border-neutral-800/60">
9626
<div className="mx-auto max-w-6xl flex items-center justify-between px-4 sm:px-6 h-16">
97-
{/* title */}
98-
<h1 className="text-lg sm:text-2xl font-bold tracking-wide whitespace-nowrap select-none">
99-
Jacob Robbins&rsquo; <span className="font-light">Skybox&nbsp;Library</span>
100-
</h1>
27+
{/* title */}
28+
<h1 className="text-lg sm:text-2xl font-bold tracking-wide whitespace-nowrap select-none">
29+
Jacob Robbins&rsquo; <span className="font-light">Skybox&nbsp;Library</span>
30+
</h1>
10131

102-
{/* right side (search/sort/icons) */}
32+
{/* right-side cluster */}
10333
<div className="flex items-center gap-2 sm:gap-3">
104-
{/* search + sort: desktop only */}
34+
{/* Search + Sort: Desktop (>= sm) */}
10535
<div className="hidden sm:flex items-center gap-3">
10636
<input
10737
value={query}
10838
onChange={(e) => setQuery(e.target.value)}
10939
placeholder="Search…"
110-
className="w-48 rounded-md bg-neutral-800/70 px-3 py-1.5 text-sm placeholder:text-neutral-500 focus:outline-none focus:ring-2 focus:ring-amber-500"
40+
className="
41+
flex-auto min-w-0 max-w-[40vw]
42+
rounded-md bg-neutral-800/70
43+
px-3 py-1.5 text-sm placeholder:text-neutral-500
44+
focus:outline-none focus:ring-2 focus:ring-amber-500
45+
"
11146
/>
112-
<select
113-
value={sort}
114-
onChange={(e) => setSort(e.target.value as any)}
115-
className="rounded-md bg-neutral-800/70 px-3 py-1.5 focus:outline-none focus:ring-2 focus:ring-amber-500"
116-
>
117-
<option value="published-date-desc">Newest</option>
118-
<option value="alpha">Name A → Z</option>
119-
<option value="alpha-desc">Name Z → A</option>
120-
<option value="published-date-asc">Oldest</option>
121-
</select>
47+
{/* SortSelect visible md and up */}
48+
<div className="hidden md:block">
49+
<SortSelect value={sort} onChange={setSort} />
50+
</div>
12251
</div>
12352

124-
{/* inline icons ≥640 px */}
125-
<nav className="hidden sm:flex items-center gap-2">
126-
{profileLinks.map((link) => (
127-
<IconLink key={link.label} href={link.href} label={link.label} svgPath={link.svgPath} />
53+
{/* Icons: Desktop (lg and up) */}
54+
<nav className="hidden lg:flex items-center gap-2">
55+
{profileLinks.map((l) => (
56+
<IconLink key={l.label} {...l} />
12857
))}
12958
</nav>
13059

131-
{/* 3-dot menu on mobile */}
132-
<MoreMenu />
60+
{/* More Menu Button: Mobile-only (< sm) */}
61+
{/* TODO: Consider moving Search/Sort/Profile Links into MoreMenu content for mobile */}
62+
<div className="sm:hidden">
63+
<MoreMenu />
64+
</div>
13365
</div>
13466
</div>
13567
</header>

app/ui/iconlink.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/* app/ui/iconlink.tsx */
2+
import Image from 'next/image';
3+
4+
export default function IconLink({ href, label, svg }: { href: string; label: string; svg: string }) {
5+
return (
6+
<a
7+
href={href}
8+
aria-label={label}
9+
target="_blank"
10+
rel="noreferrer"
11+
className="
12+
flex-none /* never shrink */
13+
p-2 rounded-md
14+
bg-neutral-800/70 hover:bg-neutral-800
15+
focus:outline-none focus:ring-2 focus:ring-amber-500
16+
"
17+
>
18+
<Image src={svg} alt={label} width={20} height={20} />
19+
</a>
20+
);
21+
}

app/ui/moremenu.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/* app/ui/moremenu.tsx */
2+
'use client';
3+
4+
import { useState } from 'react';
5+
import IconLink from './iconlink';
6+
import { profileLinks } from './profile-links';
7+
8+
export default function MoreMenu() {
9+
const [open, setOpen] = useState(false);
10+
11+
return (
12+
<div className="relative flex-shrink-0">
13+
<button
14+
onClick={() => setOpen(!open)}
15+
aria-label="Open links"
16+
className="sm:hidden p-2 rounded-md bg-neutral-800/70 hover:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-amber-500"
17+
>
18+
<svg viewBox="0 0 24 24" className="w-4 h-4 fill-neutral-300">
19+
<circle cx="5" cy="12" r="2" />
20+
<circle cx="12" cy="12" r="2" />
21+
<circle cx="19" cy="12" r="2" />
22+
</svg>
23+
</button>
24+
25+
{open && (
26+
<div className="absolute right-0 mt-2 w-40 rounded-md bg-neutral-900 shadow-lg ring-1 ring-neutral-700/60">
27+
<nav className="flex flex-col divide-y divide-neutral-700/60">
28+
{profileLinks.map((l) => (
29+
<IconLink key={l.label} {...l} />
30+
))}
31+
</nav>
32+
</div>
33+
)}
34+
</div>
35+
);
36+
}

app/ui/profile-links.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/* app/ui/profile-links.ts */
2+
export const profileLinks = [
3+
{
4+
href: 'https://github.com/Jacobdeanr',
5+
label: 'GitHub',
6+
svg: '/Source_Skyboxes_NextJS/icons/github-mark-white.svg',
7+
},
8+
{
9+
href: 'https://discord.gg/grqAfezMVs',
10+
label: 'Discord',
11+
svg: '/Source_Skyboxes_NextJS/icons/discord-symbol-white.svg',
12+
},
13+
{
14+
href: 'https://steamcommunity.com/id/Jakobi_OBrien',
15+
label: 'Steam',
16+
svg: '/Source_Skyboxes_NextJS/icons/steam-logo.svg',
17+
},
18+
];

app/ui/skyboxgrid.tsx

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,31 @@
1-
'use client';
21

3-
import { useMemo, useState } from 'react';
2+
import { useMemo } from 'react';
43
import SkyboxCard from './skyboxcard';
5-
import Header from './header';
4+
5+
// Define the SortOption type matching the one in header.tsx / page.tsx
6+
type SortOption = 'alpha' | 'alpha-desc' | 'published-date-desc' | 'published-date-asc';
67

78
export default function SkyboxGrid({
89
slugs,
910
meta,
11+
sort,
12+
query,
1013
}: {
1114
slugs: string[];
1215
meta: Record<string, any>;
16+
sort: SortOption;
17+
query: string;
1318
}) {
14-
const [filter, setFilter] = useState<{ sort: 'alpha' | 'alpha-desc' | 'published-date-desc' | 'published-date-asc'; query: string }>({
15-
sort: 'published-date-desc',
16-
query: '',
17-
});
1819

1920
const visible = useMemo(() => {
2021
let list = [...slugs];
21-
if (filter.query) {
22-
const q = filter.query.toLowerCase();
23-
list = list.filter((s) => s.includes(q));
22+
// Filter based on query prop
23+
if (query) {
24+
const q = query.toLowerCase();
25+
list = list.filter((s) => s.includes(q) || meta[s]?.title?.toLowerCase().includes(q));
2426
}
25-
switch (filter.sort) {
27+
// Sort based on sort prop
28+
switch (sort) {
2629
case 'alpha-desc':
2730
list.sort().reverse(); // Z-A
2831
break;
@@ -46,27 +49,24 @@ export default function SkyboxGrid({
4649
break;
4750
}
4851

49-
return list;
50-
}, [slugs, filter]);
52+
return list; // Add this line back
53+
}, [slugs, meta, query, sort]); // Update dependencies
5154

5255
return (
53-
<>
54-
<Header onChange={setFilter} />
55-
56-
<section
57-
className="
58-
grid gap-4 pt-6
59-
grid-cols-[repeat(auto-fill,minmax(16rem,1fr))]
60-
sm:grid-cols-[repeat(auto-fill,minmax(18rem,1fr))]
61-
lg:grid-cols-[repeat(auto-fill,minmax(22rem,1fr))]
62-
2xl:grid-cols-[repeat(auto-fill,minmax(26rem,1fr))]
63-
px-4 sm:px-6 lg:px-8
64-
"
65-
>
66-
{visible.map((slug) => (
67-
<SkyboxCard key={slug} slug={slug} meta={meta[slug]} />
68-
))}
69-
</section>
70-
</>
56+
// Correctly render only the grid section
57+
<section
58+
className="
59+
grid gap-4 pt-6
60+
grid-cols-[repeat(auto-fill,minmax(16rem,1fr))]
61+
sm:grid-cols-[repeat(auto-fill,minmax(18rem,1fr))]
62+
lg:grid-cols-[repeat(auto-fill,minmax(22rem,1fr))]
63+
2xl:grid-cols-[repeat(auto-fill,minmax(26rem,1fr))]
64+
px-4 sm:px-6 lg:px-8
65+
"
66+
>
67+
{visible.map((slug) => (
68+
<SkyboxCard key={slug} slug={slug} meta={meta[slug]} />
69+
))}
70+
</section>
7171
);
7272
}

0 commit comments

Comments
 (0)