Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sidebar 컴포넌트 구현 #44

Merged
merged 8 commits into from
Nov 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Providers, ThemeButton } from '@/components'
import { Providers } from '@/components'
import Header from '@/components/common/Header/Header'
import type { Metadata } from 'next'
import './globals.css'
Expand All @@ -17,11 +17,12 @@ export default function RootLayout({
<html lang="ko">
<body className="bg-bgColor">
<Providers>
<div className="mx-auto w-full max-w-[500px]">
<div
id="root"
className="relative mx-auto w-full max-w-[500px]">
<Header />
<main>{children}</main>
<main className="pt-[53px]">{children}</main>
</div>
<ThemeButton />
</Providers>
</body>
</html>
Expand Down
56 changes: 32 additions & 24 deletions src/components/common/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,53 @@
'use client'

import { useState } from 'react'
import { LinkIcon } from '@heroicons/react/20/solid'
import { BellIcon } from '@heroicons/react/24/outline'
import { MagnifyingGlassCircleIcon } from '@heroicons/react/24/outline'
import { Bars3Icon } from '@heroicons/react/24/solid'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import Button from '../Button/Button'
import Sidebar from '../Sidebar/Sidebar'

const Header = () => {
const [isSidebarOpen, setIsSidebarOpen] = useState(false)
const pathName = usePathname()
const currentPage = pathName
.split(/[^a-zA-Z]/)[1] // 라우터명
.replace(/^[a-z]/, (char) => char.toUpperCase()) // 첫글자 대문자 치환

return (
<div className="flex items-center justify-between bg-bgColor">
<div className="flex items-center justify-center">
<Button>
<Link href="/">
<LinkIcon className="h-8 w-8" />
</Link>
</Button>
<>
<div className="fixed z-10 flex w-full max-w-[500px] items-center justify-between border-b border-slate-100 bg-bgColor px-4 py-2.5 dark:border-slate-800">
<div className="flex items-center justify-center">
<Button>
<Link href="/">
<LinkIcon className="h-8 w-8 text-slate9" />
</Link>
</Button>
</div>
<div className="absolute left-1/2 flex -translate-x-1/2 items-center justify-center font-bold text-gray9">
{currentPage || 'Home'}
</div>
<div className="flex items-center justify-center gap-x-1">
<Button className="flex h-8 w-8 items-center justify-center">
<Link href="/notification">
<BellIcon className="h-6 w-6 text-slate9" />
</Link>
</Button>
<Button className="flex h-8 w-8 items-center justify-center">
<MagnifyingGlassCircleIcon className="h-6 w-6 text-slate9" />
</Button>
<Button
className="flex h-8 w-8 items-center justify-center"
onClick={() => setIsSidebarOpen(true)}>
<Bars3Icon className="h-6 w-6 text-slate9" />
</Button>
</div>
</div>
<div className="absolute left-1/2 flex -translate-x-1/2 items-center justify-center">
{currentPage || 'Home'}
</div>
<div className="flex items-center justify-center">
<Button className="flex h-8 w-8 items-center justify-center">
<Link href="/notification">
<BellIcon className="h-6 w-6" />
</Link>
</Button>
<Button className="flex h-8 w-8 items-center justify-center">
<MagnifyingGlassCircleIcon className="h-6 w-6" />
</Button>
<Button className="flex h-8 w-8 items-center justify-center">
<Bars3Icon className="h-6 w-6" />
</Button>
</div>
</div>
{isSidebarOpen && <Sidebar onClose={() => setIsSidebarOpen(false)} />}
</>
)
}

Expand Down
134 changes: 134 additions & 0 deletions src/components/common/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
'use client'

import { useRef } from 'react'
import { mock_userData } from '@/data'
import { XMarkIcon } from '@heroicons/react/24/outline'
import { ArchiveBoxIcon, StarIcon } from '@heroicons/react/24/solid'
import Link from 'next/link'
import Avatar from '../Avatar/Avatar'
import Button from '../Button/Button'
import ThemeButton from '../ThemeButton/ThemeButton'
import useSidebar from './hooks/useSidebar'

export interface SidebarProps {
onClose: () => void
}

const Sidebar = ({ onClose }: SidebarProps) => {
const user = mock_userData
const sidebarRef = useRef<HTMLDivElement>(null)
const { spaceType, handleSpaceType, handleOverlayClick, logout } = useSidebar(
{
sidebarRef,
onClose,
},
)

return (
<div
ref={sidebarRef}
onClick={handleOverlayClick}
className="fixed left-0 right-0 top-0 z-10 mx-auto flex h-screen w-full max-w-[500px] flex-col justify-center bg-black/40 shadow-xl">
<div className="absolute right-0 flex h-full w-full max-w-[300px] flex-col justify-between rounded-l-xl bg-bgColor px-2 pb-1 pt-6">
<div className="flex flex-col">
{user ? (
<>
<div className="flex items-center px-2">
<Avatar
src={user.profile}
width={40}
height={40}
alt={user.name}
/>
<p className="font-sm ml-3 w-full overflow-hidden text-ellipsis whitespace-nowrap font-medium text-gray9">
{user.name}
</p>
<Button onClick={onClose}>
<XMarkIcon className="h-6 w-6" />
</Button>
</div>
<Link
href={`/user/${user.id}`}
className="my-2 px-2 py-2 text-base font-bold text-gray9"
onClick={onClose}>
프로필
</Link>
<div className="border-y border-slate3 px-2">
<div className="mt-2 flex justify-between py-2 text-base font-bold text-gray9">
{spaceType}
{spaceType === '내 스페이스' ? (
<Button
className="button button-round button-white"
onClick={handleSpaceType}>
<StarIcon className="h-4 w-4 text-yellow-300" />
즐겨찾기
</Button>
) : (
<Button
className="button button-round button-white"
onClick={handleSpaceType}>
<ArchiveBoxIcon className="h-4 w-4 text-emerald-500" />내
스페이스
</Button>
)}
</div>
<ul>
{user[
spaceType === '내 스페이스' ? 'mySpaces' : 'favoriteSpaces'
].map(({ name, id }) => (
<li
className="border-b border-slate3 last:border-none"
key={id}>
<Link
href={`/space/${id}`}
className="block px-3 py-2.5 text-sm text-gray9"
onClick={onClose}>
{name}
</Link>
</li>
))}
</ul>
<Link
href={`/user/${user.id}/space`}
className="font-gray6 my-2 block w-full rounded-3xl border border-slate6 px-3 py-2.5 text-center text-sm font-medium text-slate6"
onClick={onClose}>
스페이스 전체보기
</Link>
</div>
<Link
href="/space/create"
className="my-2 px-2 py-2 text-base font-bold text-gray9"
onClick={onClose}>
스페이스 생성
</Link>
</>
) : (
<div className="flex flex-col items-end border-b border-slate3 px-2 pb-2">
<Button onClick={onClose}>
<XMarkIcon className="h-6 w-6" />
</Button>
<Link
href={`/login`}
className="w-full py-2 text-base font-bold text-gray9"
onClick={onClose}>
로그인
</Link>
</div>
)}
</div>
<div className="flex flex-col gap-y-2">
<ThemeButton />
{user && (
<button
className="border-t border-slate3 px-2 py-3 text-left text-sm text-gray9"
onClick={logout}>
로그아웃
</button>
)}
</div>
</div>
</div>
)
}

export default Sidebar
36 changes: 36 additions & 0 deletions src/components/common/Sidebar/hooks/useSidebar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useState } from 'react'

type SpaceType = '내 스페이스' | '즐겨찾기'

export interface useSidebarProps {
sidebarRef: React.RefObject<HTMLDivElement | null>
onClose: () => void
}

const useSidebar = ({ sidebarRef, onClose }: useSidebarProps) => {
const [spaceType, setSpaceType] = useState<SpaceType>('내 스페이스')

const logout = () => {
// TODO: 로그아웃
onClose()
}

const handleSpaceType = (e?: React.MouseEvent<HTMLButtonElement>) => {
setSpaceType(spaceType === '내 스페이스' ? '즐겨찾기' : '내 스페이스')
}

const handleOverlayClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (e.target === sidebarRef.current) {
onClose()
}
}

return {
spaceType,
handleSpaceType,
handleOverlayClick,
logout,
}
}

export default useSidebar
2 changes: 1 addition & 1 deletion src/components/common/ThemeButton/ThemeButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const ThemeButton = () => {

return (
<button
className="fixed bottom-2 right-2 rounded-md border p-2 text-xs"
className="rounded-md border p-2 text-xs"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
테마 버튼
</button>
Expand Down
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export { default as CategoryList } from './common/CategoryList/CategoryList'
export { default as DropdownItem } from './common/Dropdown/DropdownItem'
export { default as Dropdown } from './common/Dropdown/Dropdown'
export { default as SpaceMemberList } from './common/SpaceMemberList/SpaceMemberList'
export { default as Sidebar } from './common/Sidebar/Sidebar'
export { default as Comment } from './Comment/Comment'
50 changes: 50 additions & 0 deletions src/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,56 @@ export const mock_memberData = [
},
]

export const mock_userData = {
id: 'frong',
name: '프롱이',
profile: '/duck.jpg',
mySpaces: [
{
name: 'My Space 1',
id: 'my-space-1',
},
{
name: 'My Space 2',
id: 'my-space-2',
},
{
name: 'My Space 3',
id: 'my-space-3',
},
{
name: 'My Space 4',
id: 'my-space-4',
},
{
name: 'My Space 5',
id: 'my-space-5',
},
],
favoriteSpaces: [
{
name: 'Favorite Space 1',
id: 'favorite-space-1',
},
{
name: 'Favorite Space 2',
id: 'favorite-space-2',
},
{
name: 'Favorite Space 3',
id: 'favorite-space-3',
},
{
name: 'Favorite Space 4',
id: 'favorite-space-4',
},
{
name: 'Favorite Space 5',
id: 'favorite-space-5',
},
],
}

export const mock_spaceData = {
userName: 'dudwns',
spaceId: 123,
Expand Down