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
11 changes: 11 additions & 0 deletions public/images/logo-light2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions src/app/shared/components/common/sidebar/CreateDashboardButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use client'

import Image from 'next/image'

import { CreateDashboardButtonProps } from '@/app/shared/types/dashboard'

export default function CreateDashboardButton({
onClick,
}: CreateDashboardButtonProps): JSX.Element {
return (
<button
type="button"
onClick={onClick}
className="flex size-20 items-center justify-center rounded-6 transition-colors hover:bg-gray-50"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

background 색상의 경우 global 스타일로 빼서 사용하면 좋지 않을까 합니다!!

Copy link
Author

@LeeCh0129 LeeCh0129 Jun 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니다 :) 중복으로 사용할 일이 생기면 global 스타일로 빼서 올려두겠습니다 👍

aria-label="새 대시보드 생성"
>
<div className="relative size-16">
<Image
src="/images/invitation.png"
alt="대시보드 생성"
fill
className="object-contain"
/>
</div>
</button>
)
}
47 changes: 47 additions & 0 deletions src/app/shared/components/common/sidebar/DashboardItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use client'

import Image from 'next/image'

import { DashboardItemProps } from '@/app/shared/types/dashboard'

export default function DashboardItem({
dashboard,
isActive = false,
onClick,
}: DashboardItemProps): JSX.Element {
const handleClick = () => {
onClick(dashboard.id)
}

return (
<button
type="button"
aria-current={isActive ? 'page' : undefined}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aria-label은 사용해 봤는데 aria-current는 처음 보는 거 같습니다! 이것은 어떤 역할은 하는 건가요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 처음에는 ARIA없이 구현을 했었는데, 따로 AI 코드리뷰 과정에서 스크린 리더 사용자들은 시각적 표현을 인식할 수 없어서 현재 위치를 알기 어렵다는 피드백을 주더라구요. 그래서 따로 웹 접근성을 고려해 한번 추가해봤습니다. 물론 기능적으로는 없어도 동작하지만요. 필수는 아니지만 그냥 습관을 기른다고 생각하는 차원에서 반영해봤어요. 리뷰 덕분에 WAI-ARIA 관련해서 이런것들이 있구나 하고 찾아보게 되었네요 :)
알면 알수록 점점 알아갈게 많아지는거 같네요...

onClick={handleClick}
className={`Text-black flex w-full items-center gap-12 rounded-6 px-12 py-8 text-left text-18 transition-colors hover:bg-gray-50 ${
isActive ? 'BG-currentDashboard font-medium' : ''
}`}
>
{/* 컬러 도트 */}
<div
className="size-8 flex-shrink-0 rounded-full"
style={{ backgroundColor: dashboard.color }}
/>

{/* 대시보드 제목 */}
<span className="flex-1 truncate">{dashboard.title}</span>

{/* 내가 만든 대시보드에 왕관 아이콘 */}
{dashboard.createdByMe && (
<div className="relative h-12 w-14 flex-shrink-0">
<Image
src="/images/crown.png"
alt="내가 만든 대시보드"
fill
className="object-contain"
/>
</div>
)}
</button>
)
}
110 changes: 110 additions & 0 deletions src/app/shared/components/common/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
'use client'

import Image from 'next/image'
import Link from 'next/link'
import { usePathname, useRouter } from 'next/navigation'

import CreateDashboardButton from './CreateDashboardButton'
import DashboardItem from './DashboardItem'

export default function Sidebar(): JSX.Element {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReactNode는 사용해본 기억이 있는데 JSX.Element는 처음 보는 거 같습니다! 어떤 타입인지 설명해주실 수 있을까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSX.Element는 JSX로 작성된 React 엘리먼트만을 나타내는 타입이예요. <div>, <Component /> 같은 JSX 표현식의 결과물을 의미한다고 보시면 됩니다. Sidebar 컴포넌트에서는 항상 <aside> JSX를 반환하고 조건부 렌더링이나 null 변환이 없어서 JSX.Element가 더 명확하다고 생각해서 사용했습니다.

const pathname = usePathname()
const router = useRouter()

// TODO: 목데이터 - API 연동시 삭제예정
const mockDashboards = [
{
id: 1,
title: '비브러리',
color: '#10B981',
createdByMe: true,
createdAt: '',
updatedAt: '',
userId: 1,
},
{
id: 2,
title: '코드잇',
color: '#8B5CF6',
createdByMe: true,
createdAt: '',
updatedAt: '',
userId: 1,
},
{
id: 3,
title: '3분기 계획',
color: '#F59E0B',
createdByMe: false,
createdAt: '',
updatedAt: '',
userId: 2,
},
{
id: 4,
title: '회의록',
color: '#3B82F6',
createdByMe: false,
createdAt: '',
updatedAt: '',
userId: 3,
},
{
id: 5,
title: '중요 문서함',
color: '#EC4899',
createdByMe: false,
createdAt: '',
updatedAt: '',
userId: 4,
},
]

const handleDashboardClick = (dashboardId: number) => {
router.push(`/dashboard/${dashboardId}`)
}

const handleCreateDashboard = () => {
// TODO: 대시보드 생성 모달 열기
console.log('대시보드 생성 모달 열기임')
}
return (
<aside className="BG-white Border-section fixed left-0 top-0 h-screen w-300 overflow-y-auto">
{/* 로고 섹션 */}
<div className="flex h-70 items-center px-20">
<Link href="/" className="flex items-center gap-8">
<div className="relative h-35 w-150">
<Image
src="/images/logo-light2.svg"
alt="Coplan logo"
fill
className="object-contain"
priority
/>
</div>
</Link>
</div>

{/* 대시보드 섹션 */}
<div className="px-20 py-24">
{/* 섹션 헤더 */}
<div className="mb-24 flex items-center justify-between">
<h2 className="Text-gray text-12 font-semibold">Dash Boards</h2>
<CreateDashboardButton onClick={handleCreateDashboard} />
</div>

{/* 대시보드 목록 */}
<div className="space-y-8">
{mockDashboards.map((dashboard) => (
<DashboardItem
key={dashboard.id}
dashboard={dashboard}
isActive={pathname === `/dashboard/${dashboard.id}`}
onClick={handleDashboardClick}
/>
))}
</div>
</div>
Comment on lines +89 to +107

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 추후에 시멘틱까지 고려한다면 nav, ul,li 태그를 활용해볼 수 있을 거 같습니다!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니다 :) 추후에 여유가 생기면 시멘틱까지 고려해서 수정해봐야겠네요.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍

</aside>
)
}
34 changes: 34 additions & 0 deletions src/app/shared/types/dashboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 사이드바용 대시보드 타입
export interface Dashboard {
id: number
title: string
color: string
createdAt: string
updatedAt: string
createdByMe: boolean
userId: number
}

// 대시보드 목록 조회 응답
export interface DashboardListResponse {
cursorId: number
totalCount: number
dashboards: Dashboard[]
}

// 사이드바 컴포넌트 Props
export interface DashboardItemProps {
dashboard: Dashboard
isActive?: boolean
onClick: (dashboardId: number) => void
}

export interface CreateDashboardButtonProps {
onClick: () => void
}

// 대시보드 생성 요청 타입
export interface CreateDashboardRequest {
title: string
color: string
}
81 changes: 52 additions & 29 deletions src/app/tester/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import ThemeToggle from '@components/ThemeToggle'
import Image from 'next/image'

import Sidebar from '@/app/shared/components/common/sidebar/Sidebar'
import ThemeToggle from '@/app/shared/components/ThemeToggle'

//<초기 설정 안내>

// 이미지 파일에 접근할 때: /images/파일명
Expand All @@ -16,35 +18,56 @@ import Image from 'next/image'

export default function Home() {
return (
<>
<div>
<h1 className="BG-blue">- test page -</h1>
<ThemeToggle />
<div className="relative h-[200px] w-[300px]">
<Image
src="/images/logo-light.svg"
alt="Logo"
fill
className="object-contain"
priority
/>
<div className="flex">
{/* 사이드바 */}
<Sidebar />

{/* 메인 콘텐츠 영역 */}
<div className="ml-300 p-20">
{/* 헤더 영역 */}
<div className="mb-24">
<h1 className="mb-16 text-24 font-bold">Sidebar 테스트 페이지</h1>
<p className="Text-gray mb-20">왼쪽에 사이드바 만들어보자잇!</p>
<ThemeToggle />
</div>

{/* 기존 테스트 요소들 */}
<div className="space-y-24">
<div>
<h2 className="mb-12 text-20 font-semibold">로고 테스트</h2>
<div className="Border-section relative h-[200px] w-[300px] rounded-8 p-16">
<Image
src="/images/logo-light2.svg"
alt="Logo"
fill
className="object-contain"
priority
/>
</div>
</div>

{/* pxr 단위 테스트 */}
<div>
<h2 className="mb-12 text-20 font-semibold">pxr 단위 테스트</h2>
<div className="mb-8 rounded-6 bg-blue-100 p-[32px] text-[16px]">
<p>This text should be 16px (일반 px 단위)</p>
</div>
<div className="rounded-6 bg-blue-100 p-32 text-16">
<p>This text should be 1rem → converted 16 to 1rem: using pxr</p>
</div>
</div>

{/* Gap 테스트 */}
<div>
<h2 className="mb-12 text-20 font-semibold">Gap 테스트</h2>
<div className="flex gap-16 rounded-6 bg-gray-100 p-16">
<div className="rounded-4 bg-blue-300 p-16">AAA</div>
<div className="rounded-4 bg-green-300 p-16">BBB</div>
<div className="rounded-4 bg-red-300 p-16">CCC</div>
</div>
</div>
</div>
</div>
{/* <pxr 단위 사용>
- [300px] -> 300으로 작성하면 브라우저에서는 알아서 rem으로 변환하여 적용됨 */}
<div className="bg-blue-100 p-[32px] text-[16px]">
<p>This text should be 16px</p>
</div>
<div className="bg-blue-100 p-32 text-16">
<p>This text should be 1rem → converted 16 to 1rem: using pxr</p>
</div>
{/* <pxr 단위 사용>
- 원래 gap-4는 16px인데, pxr적용 시에는 gap-16으로 작성*/}
<div className="gap-16 bg-gray-100 p-4 marker:flex">
<div className="bg-blue-300 p-4">AAA</div>
<div className="bg-green-300 p-4">BBB</div>
<div className="bg-red-300 p-4">CCC</div>
</div>
</>
</div>
)
}