feat(ui): 헤더 대분류/소분류 렌더링 및 hover 드롭다운 구현
docs(todo): 헤더 네비 4.1, 4.2, 5.1 완료 표시
This commit is contained in:
@@ -12,12 +12,12 @@
|
|||||||
- [x] 소분류(보드) 매핑 규칙 정의(현재 시드 트리 기준)
|
- [x] 소분류(보드) 매핑 규칙 정의(현재 시드 트리 기준)
|
||||||
|
|
||||||
4) 대분류 네비 구현
|
4) 대분류 네비 구현
|
||||||
- [ ] 상단에 대분류 탭 렌더링(정렬/활성 상태 반영)
|
- [x] 상단에 대분류 탭 렌더링(정렬/활성 상태 반영)
|
||||||
- [ ] hover 시 소분류 드롭다운 표시(키보드 포커스 접근성 포함)
|
- [x] hover 시 소분류 드롭다운 표시(키보드 포커스 접근성 포함)
|
||||||
- [ ] 현재 페이지 경로와 활성 대분류 동기화
|
- [ ] 현재 페이지 경로와 활성 대분류 동기화
|
||||||
|
|
||||||
5) 소분류(보드) 드롭다운 구현
|
5) 소분류(보드) 드롭다운 구현
|
||||||
- [ ] 소분류 리스트 렌더링(이름/slug 링크)
|
- [x] 소분류 리스트 렌더링(이름/slug 링크)
|
||||||
- [ ] 보드 타입/승인필요 등 뱃지 표기 여부 결정 및 적용
|
- [ ] 보드 타입/승인필요 등 뱃지 표기 여부 결정 및 적용
|
||||||
|
|
||||||
6) 검색창 구현
|
6) 검색창 구현
|
||||||
|
|||||||
@@ -9,12 +9,18 @@ import React from "react";
|
|||||||
|
|
||||||
export function AppHeader() {
|
export function AppHeader() {
|
||||||
const [user, setUser] = React.useState<{ nickname: string } | null>(null);
|
const [user, setUser] = React.useState<{ nickname: string } | null>(null);
|
||||||
|
const [categories, setCategories] = React.useState<Array<{ id: string; name: string; slug: string; boards: Array<{ id: string; name: string; slug: string }> }>>([]);
|
||||||
|
const [openSlug, setOpenSlug] = React.useState<string | null>(null);
|
||||||
// 헤더 마운트 시 세션 존재 여부를 조회해 로그인/로그아웃 UI를 제어합니다.
|
// 헤더 마운트 시 세션 존재 여부를 조회해 로그인/로그아웃 UI를 제어합니다.
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
fetch("/api/auth/session")
|
fetch("/api/auth/session")
|
||||||
.then((r) => r.json())
|
.then((r) => r.json())
|
||||||
.then((d) => setUser(d?.ok ? d.user : null))
|
.then((d) => setUser(d?.ok ? d.user : null))
|
||||||
.catch(() => setUser(null));
|
.catch(() => setUser(null));
|
||||||
|
fetch("/api/categories", { cache: "no-store" })
|
||||||
|
.then((r) => r.json())
|
||||||
|
.then((d) => setCategories(d?.categories || []))
|
||||||
|
.catch(() => setCategories([]));
|
||||||
}, []);
|
}, []);
|
||||||
const onLogout = async () => {
|
const onLogout = async () => {
|
||||||
await fetch("/api/auth/session", { method: "DELETE" });
|
await fetch("/api/auth/session", { method: "DELETE" });
|
||||||
@@ -29,8 +35,25 @@ export function AppHeader() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<nav style={{ display: "flex", gap: 12, alignItems: "center" }}>
|
<nav style={{ display: "flex", gap: 12, alignItems: "center" }}>
|
||||||
{/* 대분류/소분류 네비는 이후 단계에서 데이터 연동 포함 확장 */}
|
{categories.map((cat) => (
|
||||||
<Link href="/boards">게시판</Link>
|
<div
|
||||||
|
key={cat.id}
|
||||||
|
onMouseEnter={() => setOpenSlug(cat.slug)}
|
||||||
|
onMouseLeave={() => setOpenSlug((s) => (s === cat.slug ? null : s))}
|
||||||
|
style={{ position: "relative" }}
|
||||||
|
>
|
||||||
|
<Link href={`/boards?category=${cat.slug}`}>{cat.name}</Link>
|
||||||
|
{openSlug === cat.slug && (
|
||||||
|
<div style={{ position: "absolute", top: "100%", left: 0, background: "white", border: "1px solid #eee", padding: 8, minWidth: 200, zIndex: 50 }}>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
||||||
|
{cat.boards.map((b) => (
|
||||||
|
<Link key={b.id} href={`/boards/${b.id}`}>{b.name}</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
<SearchBar />
|
<SearchBar />
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
{user ? (
|
{user ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user