This commit is contained in:
@@ -25,12 +25,13 @@ import { Category, Profile } from '@/types';
|
||||
interface CategoryItemProps {
|
||||
category: Category;
|
||||
depth: number;
|
||||
onNavigate: () => void;
|
||||
pathname: string;
|
||||
}
|
||||
|
||||
const defaultProfile: Profile = {
|
||||
name: 'Dev Park',
|
||||
bio: '풀스택을 꿈꾸는 개발자\n"코드로 세상을 바꾸고 싶은 박개발의 기술 블로그입니다."',
|
||||
bio: '개발자',
|
||||
imageUrl: 'https://api.dicebear.com/7.x/notionists/svg?seed=Felix',
|
||||
githubUrl: 'https://github.com',
|
||||
email: 'user@example.com',
|
||||
@@ -40,7 +41,7 @@ const sortCategories = (categories: Category[] = []) => {
|
||||
return [...categories].sort((a, b) => a.id - b.id);
|
||||
};
|
||||
|
||||
function CategoryItem({ category, depth, pathname }: CategoryItemProps) {
|
||||
function CategoryItem({ category, depth, onNavigate, pathname }: CategoryItemProps) {
|
||||
const sortedChildren = useMemo(() => sortCategories(category.children), [category.children]);
|
||||
const hasChildren = sortedChildren.length > 0;
|
||||
const isActive = decodeURIComponent(pathname) === `/category/${category.name}`;
|
||||
@@ -64,14 +65,18 @@ function CategoryItem({ category, depth, pathname }: CategoryItemProps) {
|
||||
<div className="mb-1">
|
||||
<div
|
||||
className={clsx(
|
||||
'flex items-center justify-between rounded-lg px-4 py-2 text-sm transition-all',
|
||||
'flex items-center justify-between rounded-lg px-3 py-2 text-sm transition-all',
|
||||
isActive
|
||||
? 'bg-[var(--color-accent-soft)] font-semibold text-[var(--color-accent)]'
|
||||
: 'text-[var(--color-text-muted)] hover:bg-black/[0.04] hover:text-[var(--color-text)] dark:hover:bg-white/10',
|
||||
)}
|
||||
style={{ marginLeft: `${depth * 10}px` }}
|
||||
style={{ marginLeft: `${depth * 8}px` }}
|
||||
>
|
||||
<Link href={`/category/${category.name}`} className="flex min-w-0 flex-1 items-center gap-2.5">
|
||||
<Link
|
||||
href={`/category/${category.name}`}
|
||||
onClick={onNavigate}
|
||||
className="flex min-w-0 flex-1 items-center gap-2.5"
|
||||
>
|
||||
{isActive ? <FolderOpen size={16} /> : <Folder size={16} />}
|
||||
<span className="truncate">{category.name}</span>
|
||||
</Link>
|
||||
@@ -102,6 +107,7 @@ function CategoryItem({ category, depth, pathname }: CategoryItemProps) {
|
||||
key={child.id}
|
||||
category={child}
|
||||
depth={depth + 1}
|
||||
onNavigate={onNavigate}
|
||||
pathname={pathname}
|
||||
/>
|
||||
))}
|
||||
@@ -112,7 +118,7 @@ function CategoryItem({ category, depth, pathname }: CategoryItemProps) {
|
||||
}
|
||||
|
||||
function SidebarContent() {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
@@ -134,14 +140,18 @@ function SidebarContent() {
|
||||
|
||||
const displayProfile = profile ? { ...defaultProfile, ...profile } : defaultProfile;
|
||||
|
||||
const closeSidebar = () => setIsOpen(false);
|
||||
|
||||
const handleSearch = (newKeyword: string) => {
|
||||
const trimmedKeyword = newKeyword.trim();
|
||||
if (trimmedKeyword) {
|
||||
router.push(`/?keyword=${encodeURIComponent(trimmedKeyword)}`);
|
||||
closeSidebar();
|
||||
return;
|
||||
}
|
||||
|
||||
router.push('/');
|
||||
closeSidebar();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -155,15 +165,25 @@ function SidebarContent() {
|
||||
{isOpen ? <X size={20} /> : <Menu size={20} />}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
aria-label="사이드바 닫기"
|
||||
onClick={closeSidebar}
|
||||
className={clsx(
|
||||
'fixed inset-0 z-30 bg-black/35 backdrop-blur-sm transition-opacity md:hidden',
|
||||
isOpen ? 'opacity-100' : 'pointer-events-none opacity-0',
|
||||
)}
|
||||
/>
|
||||
|
||||
<aside
|
||||
className={clsx(
|
||||
'fixed left-0 top-0 z-40 flex h-screen flex-col overflow-hidden border-r border-[var(--color-line)] bg-[var(--color-surface-strong)]/95 backdrop-blur-xl transition-all duration-300 ease-in-out',
|
||||
isOpen ? 'w-72 translate-x-0' : 'w-0 -translate-x-full md:w-20 md:translate-x-0',
|
||||
'fixed left-0 top-0 z-40 flex h-screen w-72 flex-col overflow-hidden border-r border-[var(--color-line)] bg-[var(--color-surface-strong)]/96 backdrop-blur-xl transition-transform duration-300 ease-out',
|
||||
isOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0',
|
||||
)}
|
||||
>
|
||||
<div className={clsx('shrink-0 p-6 text-center transition-opacity duration-200', !isOpen && 'md:hidden md:opacity-0')}>
|
||||
<Link href="/" className="block transition-opacity hover:opacity-80">
|
||||
<div className="relative mx-auto mb-4 h-24 w-24 overflow-hidden rounded-full bg-black/[0.04] shadow-inner ring-4 ring-white/70 dark:bg-white/10 dark:ring-white/10">
|
||||
<div className="shrink-0 px-6 pb-5 pt-8 text-center">
|
||||
<Link href="/" onClick={closeSidebar} className="block transition-opacity hover:opacity-80">
|
||||
<div className="relative mx-auto mb-4 h-20 w-20 overflow-hidden rounded-full bg-black/[0.04] shadow-inner ring-1 ring-[var(--color-line)] dark:bg-white/10">
|
||||
{isProfileLoading ? (
|
||||
<div className="h-full w-full animate-pulse bg-black/[0.06] dark:bg-white/10" />
|
||||
) : (
|
||||
@@ -186,21 +206,17 @@ function SidebarContent() {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<h2 className="text-xl font-bold tracking-normal text-[var(--color-text)]">{displayProfile.name}</h2>
|
||||
<p className="mt-2 whitespace-pre-line text-sm leading-relaxed text-[var(--color-text-muted)]">{displayProfile.bio}</p>
|
||||
<h2 className="text-lg font-bold tracking-normal text-[var(--color-text)]">{displayProfile.name}</h2>
|
||||
<p className="mt-1 whitespace-pre-line text-sm leading-relaxed text-[var(--color-text-muted)]">{displayProfile.bio}</p>
|
||||
</>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<nav className="flex min-h-0 flex-1 flex-col px-4 py-2">
|
||||
<div className={clsx('mt-4 flex shrink-0 flex-col items-center gap-4', isOpen && 'hidden')}>
|
||||
<Folder size={24} className="text-[var(--color-text-subtle)]" />
|
||||
</div>
|
||||
|
||||
<div className={clsx('flex h-full flex-col', !isOpen && 'md:hidden')}>
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="shrink-0 space-y-1">
|
||||
<div id="blog-search" className="mb-6 mt-2 px-1">
|
||||
<div id="blog-search" className="mb-5 mt-2 px-1">
|
||||
<PostSearch
|
||||
onSearch={handleSearch}
|
||||
placeholder="검색..."
|
||||
@@ -211,8 +227,9 @@ function SidebarContent() {
|
||||
<div className="mb-4">
|
||||
<Link
|
||||
href="/archive"
|
||||
onClick={closeSidebar}
|
||||
className={clsx(
|
||||
'flex items-center gap-2.5 rounded-lg px-4 py-2 text-sm transition-all',
|
||||
'flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm transition-all',
|
||||
pathname === '/archive'
|
||||
? 'bg-[var(--color-accent-soft)] font-semibold text-[var(--color-accent)]'
|
||||
: 'text-[var(--color-text-muted)] hover:bg-black/[0.04] hover:text-[var(--color-text)] dark:hover:bg-white/10',
|
||||
@@ -225,8 +242,8 @@ function SidebarContent() {
|
||||
|
||||
<div className="mb-4 border-t border-[var(--color-line)]" />
|
||||
|
||||
<div className="mb-3 flex h-8 items-center justify-between px-4">
|
||||
<p className="text-xs font-bold text-[var(--color-text-subtle)]">카테고리</p>
|
||||
<div className="mb-3 flex h-8 items-center justify-between px-3">
|
||||
<p className="text-xs font-semibold uppercase text-[var(--color-text-subtle)]">카테고리</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -245,6 +262,7 @@ function SidebarContent() {
|
||||
key={category.id}
|
||||
category={category}
|
||||
depth={0}
|
||||
onNavigate={closeSidebar}
|
||||
pathname={pathname}
|
||||
/>
|
||||
))}
|
||||
@@ -252,8 +270,9 @@ function SidebarContent() {
|
||||
<div className="mb-1 mt-2">
|
||||
<Link
|
||||
href="/category/uncategorized"
|
||||
onClick={closeSidebar}
|
||||
className={clsx(
|
||||
'flex items-center gap-2.5 rounded-lg px-4 py-2 text-sm transition-all',
|
||||
'flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm transition-all',
|
||||
pathname === '/category/uncategorized'
|
||||
? 'bg-[var(--color-accent-soft)] font-semibold text-[var(--color-accent)]'
|
||||
: 'text-[var(--color-text-muted)] hover:bg-black/[0.04] hover:text-[var(--color-text)] dark:hover:bg-white/10',
|
||||
@@ -268,7 +287,7 @@ function SidebarContent() {
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div className={clsx('shrink-0 border-t border-[var(--color-line)] bg-[var(--color-surface-strong)]/80 p-6', !isOpen && 'hidden')}>
|
||||
<div className="shrink-0 border-t border-[var(--color-line)] bg-[var(--color-surface-strong)]/80 p-5">
|
||||
<div className="flex justify-center gap-3">
|
||||
<a
|
||||
href={displayProfile.githubUrl || '#'}
|
||||
@@ -288,7 +307,7 @@ function SidebarContent() {
|
||||
</a>
|
||||
</div>
|
||||
<p className="mt-4 text-center text-[10px] font-light text-[var(--color-text-subtle)]">
|
||||
© {new Date().getFullYear()} {displayProfile.name}. All rights reserved.
|
||||
© {new Date().getFullYear()} {displayProfile.name}
|
||||
</p>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
Reference in New Issue
Block a user