Skip to content

Commit

Permalink
Merge pull request #44 from Team-TenTen/feature/#39/component-sidebar
Browse files Browse the repository at this point in the history
Sidebar 컴포넌트 구현
  • Loading branch information
eeseung authored Nov 5, 2023
2 parents 6e0ed47 + 38ac31a commit eb0cfa2
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 29 deletions.
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

0 comments on commit eb0cfa2

Please sign in to comment.