Skip to content
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
12 changes: 12 additions & 0 deletions src/app/mydashboard/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Sidebar from '@/components/Sidebar/Sidebar';
import { ReactNode } from 'react';

export default function Layout({ children }: { children: ReactNode }) {
return (
<div className='flex'>
<Sidebar />

<main className='flex-1'>{children}</main>
</div>
);
}
3 changes: 3 additions & 0 deletions src/app/mydashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Page() {
return <main></main>;
}
Binary file added src/assets/images/sidebar_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions src/components/Sidebar/ArrowLeft.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<svg className={`${baseStyle} ${colorStyle}`} width='10' height='16' viewBox='0 0 10 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
d='M2.641 7.99933L8.91824 1.72208C9.08384 1.55648 9.16451 1.35937 9.16024 1.13074C9.15596 0.902106 9.07103 0.704996 8.90543 0.539412C8.73983 0.373815 8.54272 0.291016 8.31409 0.291016C8.08547 0.291016 7.88836 0.373815 7.72276 0.539412L1.34295 6.93204C1.19231 7.08268 1.08066 7.25147 1.00801 7.43843C0.93537 7.62541 0.899048 7.81237 0.899048 7.99933C0.899048 8.18629 0.93537 8.37325 1.00801 8.56022C1.08066 8.74718 1.19231 8.91598 1.34295 9.06662L7.73557 15.4592C7.90117 15.6248 8.09615 15.7055 8.32051 15.7012C8.54486 15.697 8.73983 15.612 8.90543 15.4464C9.07103 15.2808 9.15383 15.0837 9.15383 14.8551C9.15383 14.6265 9.07103 14.4294 8.90543 14.2638L2.641 7.99933Z'
fill='currentColor'
/>
</svg>
);
}
17 changes: 17 additions & 0 deletions src/components/Sidebar/ArrowRight.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<svg className={`${baseStyle} ${colorStyle}`} width='10' height='16' viewBox='0 0 10 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
d='M7.35901 7.99933L1.08176 1.72208C0.916163 1.55648 0.835496 1.35937 0.83976 1.13074C0.844038 0.902106 0.928975 0.704996 1.09457 0.539412C1.26017 0.373815 1.45728 0.291016 1.68591 0.291016C1.91453 0.291016 2.11164 0.373815 2.27724 0.539412L8.65705 6.93204C8.80769 7.08268 8.91934 7.25147 8.99199 7.43843C9.06463 7.62541 9.10095 7.81237 9.10095 7.99933C9.10095 8.18629 9.06463 8.37325 8.99199 8.56022C8.91934 8.74718 8.80769 8.91598 8.65705 9.06662L2.26443 15.4592C2.09883 15.6248 1.90385 15.7055 1.67949 15.7012C1.45514 15.697 1.26017 15.612 1.09457 15.4464C0.928975 15.2808 0.846176 15.0837 0.846176 14.8551C0.846176 14.6265 0.928975 14.4294 1.09457 14.2638L7.35901 7.99933Z'
fill='currentColor'
/>
</svg>
);
}
32 changes: 32 additions & 0 deletions src/components/Sidebar/Dot.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<svg className={`fill-current ${className}`} width='8' height='8' viewBox='0 0 8 8' xmlns='http://www.w3.org/2000/svg'>
<circle cx='4' cy='4' r='4' fill='currentColor' />
</svg>
);
}
79 changes: 79 additions & 0 deletions src/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<aside className='h-screen w-[67px] px-2 py-5 md:w-[160px] lg:w-[300px]'>
<div className='flex flex-col items-center gap-[14px] md:items-center md:gap-14 lg:items-stretch'>
<SidebarLogo />

<div className='flex flex-col gap-8'>
<div className='flex flex-col gap-4'>
<div className='flex justify-center md:justify-between'>
<p className='hidden text-xs font-semibold text-gray-50 md:block'>Dash Boards</p>
<Image src={add_box} width={14} height={14} alt='add_button' className='cursor-pointer' />
</div>
<SidebarItemList currentGroups={currentGroups} />
</div>

{totalPages > 1 && <SidebarPaginationControls canGoPrev={canGoPrev} canGoNext={canGoNext} handlePrev={handlePrev} handleNext={handleNext} />}
</div>
</div>
</aside>
);
}
37 changes: 37 additions & 0 deletions src/components/Sidebar/SidebarItemList.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className='flex flex-col gap-4'>
{currentGroups.map((group, groupIndex) => (
<div key={groupIndex} className='flex flex-col gap-2'>
{group.map((item) => (
<div key={item.id} className='flex justify-center gap-2.5 rounded-md p-3 md:justify-start md:hover:bg-violet-10'>
<div className='flex items-center gap-4'>
<Dot colorClass={item.colorClass} />

<div className='hidden gap-1.5 md:flex'>
<span className='text-2lg font-medium text-gray-50'>{item.name}</span>
{item.hasCrown && <Image src={crown} width={17} height={14} alt='crown' />}
</div>
</div>
</div>
))}
</div>
))}
</div>
);
}
13 changes: 13 additions & 0 deletions src/components/Sidebar/SidebarLogo.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Image src={logo} width={108} height={33} alt='logo' className='hidden md:flex' />

<Image src={logo_ci} width={23} height={25} alt='logo' className='h-[25px] w-[23px] md:hidden' />
</>
);
}
22 changes: 22 additions & 0 deletions src/components/Sidebar/SidebarPaginationControls.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className='hidden md:flex'>
<button className='flex h-10 w-10 items-center justify-center rounded-bl-[4px] rounded-tl-[4px] border border-gray-30' onClick={handlePrev}>
<ArrowLeft disabled={!canGoPrev} />
</button>
<button className='flex h-10 w-10 items-center justify-center rounded-br-[4px] rounded-tr-[4px] border border-gray-30' onClick={handleNext}>
<ArrowRight disabled={!canGoNext} />
</button>
</div>
);
}
7 changes: 7 additions & 0 deletions src/utils/chunkArray.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function chunkArray<T>(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;
}