diff --git a/src/app/mydashboard/layout.tsx b/src/app/mydashboard/layout.tsx new file mode 100644 index 0000000..371f2f8 --- /dev/null +++ b/src/app/mydashboard/layout.tsx @@ -0,0 +1,12 @@ +import Sidebar from '@/components/Sidebar/Sidebar'; +import { ReactNode } from 'react'; + +export default function Layout({ children }: { children: ReactNode }) { + return ( +
+ + +
{children}
+
+ ); +} diff --git a/src/app/mydashboard/page.tsx b/src/app/mydashboard/page.tsx new file mode 100644 index 0000000..4dfdfab --- /dev/null +++ b/src/app/mydashboard/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
; +} diff --git a/src/assets/images/sidebar_logo.png b/src/assets/images/sidebar_logo.png new file mode 100644 index 0000000..8d8b4f2 Binary files /dev/null and b/src/assets/images/sidebar_logo.png differ diff --git a/src/components/Sidebar/ArrowLeft.tsx b/src/components/Sidebar/ArrowLeft.tsx new file mode 100644 index 0000000..cfef1ac --- /dev/null +++ b/src/components/Sidebar/ArrowLeft.tsx @@ -0,0 +1,21 @@ +interface ArrowProps { + disabled?: boolean; +} + +export default function ArrowLeft({ disabled = false }: ArrowProps) { + const baseStyle = 'w-4 h-4 fill-current'; + + // disabled 여부에 따른 색상 및 hover + // - 비활성화(disabled=true)일 땐 text-gray-30, 클릭 불가(cursor-default) + // - 활성화(disabled=false)일 땐 text-gray-80, hover:text-gray-60, cursor-pointer + const colorStyle = disabled ? 'text-gray-30 cursor-default' : 'text-gray-50 hover:text-gray-60 cursor-pointer'; + + return ( + + + + ); +} diff --git a/src/components/Sidebar/ArrowRight.tsx b/src/components/Sidebar/ArrowRight.tsx new file mode 100644 index 0000000..cdc7589 --- /dev/null +++ b/src/components/Sidebar/ArrowRight.tsx @@ -0,0 +1,17 @@ +interface ArrowProps { + disabled?: boolean; +} + +export default function ArrowRight({ disabled = false }: ArrowProps) { + const baseStyle = 'w-4 h-4 fill-current'; + const colorStyle = disabled ? 'text-gray-30 cursor-default' : 'text-gray-50 hover:text-gray-70 cursor-pointer'; + + return ( + + + + ); +} diff --git a/src/components/Sidebar/Dot.tsx b/src/components/Sidebar/Dot.tsx new file mode 100644 index 0000000..86d8cb1 --- /dev/null +++ b/src/components/Sidebar/Dot.tsx @@ -0,0 +1,32 @@ +interface DotProps { + colorClass: 'green-30' | 'blue-20' | 'orange-20' | 'purple' | 'pink-20'; +} + +export default function Dot({ colorClass }: DotProps) { + let className = ''; + switch (colorClass) { + case 'green-30': + className = 'text-green-30'; + break; + case 'blue-20': + className = 'text-blue-20'; + break; + case 'orange-20': + className = 'text-orange-20'; + break; + case 'pink-20': + className = 'text-pink-20'; + break; + case 'purple': + className = 'text-purple'; + break; + default: + className = 'text-gray-50'; + } + + return ( + + + + ); +} diff --git a/src/components/Sidebar/Sidebar.tsx b/src/components/Sidebar/Sidebar.tsx new file mode 100644 index 0000000..058bece --- /dev/null +++ b/src/components/Sidebar/Sidebar.tsx @@ -0,0 +1,79 @@ +'use client'; + +import { useState } from 'react'; +import Image from 'next/image'; + +import { chunkArray } from '@/utils/chunkArray'; +import SidebarLogo from './SidebarLogo'; +import SidebarItemList from './SidebarItemList'; +import SidebarPaginationControls from './SidebarPaginationControls'; + +import add_box from '@/assets/icons/add_box.svg'; + +interface ItemList { + id: number; + name: string; + colorClass: 'green-30' | 'blue-20' | 'orange-20' | 'purple' | 'pink-20'; + hasCrown?: boolean; +} + +const mockBoardData: ItemList[] = [ + { id: 1, name: '비브리지', colorClass: 'green-30', hasCrown: true }, + { id: 2, name: '코드잇', colorClass: 'blue-20', hasCrown: true }, + { id: 3, name: '3분기 계획', colorClass: 'green-30' }, + { id: 4, name: '회의록', colorClass: 'blue-20' }, + { id: 5, name: '중요 문서함', colorClass: 'pink-20' }, + { id: 6, name: '비브리지', colorClass: 'blue-20', hasCrown: true }, + { id: 7, name: '코드잇', colorClass: 'purple', hasCrown: true }, + { id: 8, name: '3분기 계획', colorClass: 'blue-20' }, + { id: 9, name: '회의록', colorClass: 'purple' }, + { id: 10, name: '중요 문서함', colorClass: 'blue-20' }, + { id: 11, name: '비브리지', colorClass: 'blue-20', hasCrown: true }, + { id: 12, name: '코드잇', colorClass: 'pink-20', hasCrown: true }, + { id: 13, name: '3분기 계획', colorClass: 'orange-20' }, + { id: 14, name: '회의록', colorClass: 'blue-20' }, + { id: 15, name: '중요 문서함', colorClass: 'blue-20' }, + { id: 16, name: '중요 테스트', colorClass: 'blue-20' }, +]; + +export default function Sidebar() { + const MAX_GROUPS_PER_PAGE = 3; + const [page, setPage] = useState(1); + const chunkedData = chunkArray(mockBoardData, 5); + + const totalPages = Math.ceil(chunkedData.length / MAX_GROUPS_PER_PAGE); + + const startIndex = (page - 1) * MAX_GROUPS_PER_PAGE; + const endIndex = page * MAX_GROUPS_PER_PAGE; + const currentGroups = chunkedData.slice(startIndex, endIndex); + + const canGoPrev = page > 1; + const canGoNext = page < totalPages; + + const handlePrev = () => { + if (canGoPrev) setPage((prev) => prev - 1); + }; + const handleNext = () => { + if (canGoNext) setPage((prev) => prev + 1); + }; + + return ( + + ); +} diff --git a/src/components/Sidebar/SidebarItemList.tsx b/src/components/Sidebar/SidebarItemList.tsx new file mode 100644 index 0000000..ace406b --- /dev/null +++ b/src/components/Sidebar/SidebarItemList.tsx @@ -0,0 +1,37 @@ +import Image from 'next/image'; +import Dot from './Dot'; +import crown from '@/assets/icons/crown.svg'; + +interface ItemList { + id: number; + name: string; + colorClass: 'green-30' | 'blue-20' | 'orange-20' | 'purple' | 'pink-20'; + hasCrown?: boolean; +} + +interface SidebarItemListProps { + currentGroups: ItemList[][]; +} + +export default function SidebarItemList({ currentGroups }: SidebarItemListProps) { + return ( +
+ {currentGroups.map((group, groupIndex) => ( +
+ {group.map((item) => ( +
+
+ + +
+ {item.name} + {item.hasCrown && crown} +
+
+
+ ))} +
+ ))} +
+ ); +} diff --git a/src/components/Sidebar/SidebarLogo.tsx b/src/components/Sidebar/SidebarLogo.tsx new file mode 100644 index 0000000..1b1e4b4 --- /dev/null +++ b/src/components/Sidebar/SidebarLogo.tsx @@ -0,0 +1,13 @@ +import Image from 'next/image'; +import logo from '@/assets/images/sidebar_logo.png'; +import logo_ci from '@/assets/images/logo_ci.png'; + +export default function SidebarLogo() { + return ( + <> + logo + + logo + + ); +} diff --git a/src/components/Sidebar/SidebarPaginationControls.tsx b/src/components/Sidebar/SidebarPaginationControls.tsx new file mode 100644 index 0000000..4f1cacd --- /dev/null +++ b/src/components/Sidebar/SidebarPaginationControls.tsx @@ -0,0 +1,22 @@ +import ArrowLeft from './ArrowLeft'; +import ArrowRight from './ArrowRight'; + +interface PaginationControlsProps { + canGoPrev: boolean; + canGoNext: boolean; + handlePrev: () => void; + handleNext: () => void; +} + +export default function SidebarPaginationControls({ canGoPrev, canGoNext, handlePrev, handleNext }: PaginationControlsProps) { + return ( +
+ + +
+ ); +} diff --git a/src/utils/chunkArray.ts b/src/utils/chunkArray.ts new file mode 100644 index 0000000..c8e4ccc --- /dev/null +++ b/src/utils/chunkArray.ts @@ -0,0 +1,7 @@ +export function chunkArray(array: T[], size: number): T[][] { + const result: T[][] = []; + for (let i = 0; i < array.length; i += size) { + result.push(array.slice(i, i + size)); + } + return result; +}