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
5 changes: 5 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ Repo-V2 is a dark, quiet school-work surface: compact, direct, and built for rep

### Rules

- 모든 글자 크기, 굵기, 행간은 `--repo-font-*` 토큰 또는 해당 토큰을 사용하는 텍스트 유틸리티만 사용합니다.
- 모든 색상은 이 문서의 color token 또는 `src/shared/styles/colors.css`에 선언된 `--repo-*` 토큰만 사용합니다.
- 기능 구현 중 필요한 글자/색상 값이 토큰에 없으면 CSS에 임의 값을 쓰지 말고 먼저 디자인 토큰을 추가하고 이 문서에 용도를 기록합니다.
- 예외적으로 SVG/이미지 내부 표현, QR/픽셀 패턴, 외부 에셋 원본 색처럼 디자인 토큰으로 제어할 수 없는 값만 raw value를 허용합니다.
- Dark primitives use tonal-shift depth before shadows.
- Green is reserved for submitted/success status.
- Error red appears only with validation feedback.
Expand Down Expand Up @@ -49,6 +53,7 @@ Repo-V2 is a dark, quiet school-work surface: compact, direct, and built for rep

- Letter spacing remains `0`.
- Body text stays at or above 14px.
- 새 UI에서 `font-size`, `font-weight`, `line-height`를 직접 숫자로 지정하는 것은 금지합니다. 반드시 typography token을 참조합니다.

## 4. Spacing & Layout

Expand Down
11 changes: 11 additions & 0 deletions docs/GIT_WORKFLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ type (#이슈번호) :: 커밋 메세지

일반 작업은 이슈 생성 후 진행하므로 이슈 번호를 포함합니다. 이슈가 없는 예외 작업은 사용자가 명시적으로 허용한 경우에만 `type :: 커밋 메세지` 형식을 사용합니다.

### 강제 규칙

- 모든 작업 커밋은 `type (#이슈번호) :: 커밋 메세지` 형식을 반드시 사용합니다.
- 제목을 명사형 또는 결과 중심 구문으로 쓰더라도 `type`, 이슈 번호, `::` 구분자는 생략하지 않습니다.
- 이슈 기반 브랜치에서 작업 중이면 브랜치명의 이슈 번호를 커밋 제목에 그대로 사용합니다.
- 커밋 전 `git log -1 --pretty=format:%s` 또는 staged commit message 확인으로 형식을 검증합니다.
- 컨벤션을 벗어난 커밋은 PR 생성 전 `git commit --amend` 또는 interactive rebase로 메시지를 고친 뒤 올립니다.
- 사용자가 별도 문구를 요청해도 형식 prefix는 유지하고, 요청받은 문구는 `커밋 메세지` 영역에만 반영합니다.

### 예시

```txt
Expand Down Expand Up @@ -45,6 +54,7 @@ chore (#22) :: ESLint ignore 범위 정리
- 제목은 가능한 한 짧고 명확하게 쓴다.
- 커밋 내용은 명사형 또는 작업 결과가 바로 드러나는 짧은 구문으로 쓴다.
- `~한다`, `~했다`, `~하기` 같은 서술형 제목은 피한다.
- 명사형 제목 규칙은 `type (#이슈번호) ::` 형식보다 우선하지 않는다.
- 마침표로 끝내지 않는다.
- 한국어를 우선 사용하되, 타입/파일명/API/컴포넌트명 등 영어가 자연스러운 부분은 영어를 사용한다.

Expand Down Expand Up @@ -115,6 +125,7 @@ Repo-V2의 일반 작업은 아래 순서를 기본으로 합니다.

- 브랜치명은 `type/#이슈번호-브랜치네임` 형식을 사용한다.
- 커밋 제목 형식은 `type (#이슈번호) :: 커밋 메세지`를 사용한다.
- 커밋 제목 형식은 필수이며, 명사형 요약 요청이 있어도 `type (#이슈번호) ::` prefix를 생략하지 않는다.
- type은 `feat`, `fix`, `build`, `chore`, `ci`, `docs`, `style`, `refactor`, `test`, `release`로 한정한다.
- 제목은 명사형 또는 작업 결과 중심의 짧은 구문으로 작성한다.
- 본문은 선택이며, 필요할 때만 짧게 작성한다.
Expand Down
17 changes: 17 additions & 0 deletions docs/UI_UX_DIRECTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

Repo-V2의 UI/UX 핵심은 학생이 실제 취업에도 쓰고 싶은 포트폴리오 작성 경험을 제공하는 것입니다.

## 디자인 토큰 강제 기준

- 모든 신규/수정 UI의 글자 크기, 굵기, 행간은 `DESIGN.md`의 typography token만 사용합니다.
- 모든 신규/수정 UI의 색상은 `DESIGN.md`와 `src/shared/styles/colors.css`에 선언된 color token만 사용합니다.
- Figma에 있는 값이라도 토큰에 없으면 CSS에 바로 쓰지 않고, 먼저 토큰과 문서 용도를 추가한 뒤 사용합니다.
- raw `px` typography, raw hex/rgb/hsl 색상은 리뷰에서 수정 대상으로 봅니다.

## 참고 자료

- UI 참고 영상: https://www.youtube.com/watch?v=fR8tsJ2r7Eg
Expand Down Expand Up @@ -46,6 +53,15 @@ Repo-V2의 UI/UX 핵심은 학생이 실제 취업에도 쓰고 싶은 포트폴
- 전공/반 필터
- 이름 검색

레주메북 상세 필터 기준:

- 필터 버튼을 누르면 현재 문서 뷰어 위에 오른쪽 사이드 패널을 띄웁니다.
- 필터 패널이 열린 동안 문서 뷰어와 검색 영역은 어둡게 흐려지고, 패널만 명확하게 보여야 합니다.
- 필터 패널의 액션은 기존 공용 `Button`, 선택지는 기존 공용 `CheckboxOption`, 적용된 조건은 기존 공용 `Tag`, 검색은 기존 공용 `SearchField`를 사용합니다.
- 전공/반 섹션은 접힘/펼침이 가능해야 하며, 선택 변경은 `적용하기` 전까지 임시 상태로 유지합니다.
- `초기화`는 선택 조건을 모두 비우고, `적용하기`는 패널을 닫은 뒤 선택된 조건을 검색창 아래 태그로 노출합니다.
- 적용된 태그의 닫기 버튼을 누르면 해당 조건만 바로 제거됩니다.

## 이번 단계에서 하지 않을 것

- Figma 화면별 세부 퍼블리싱 계획 확정
Expand All @@ -59,6 +75,7 @@ Repo-V2의 UI/UX 핵심은 학생이 실제 취업에도 쓰고 싶은 포트폴
- 기준 범위는 `1440 × 900`부터 `1920 × 1080`까지입니다.
- `1440 × 900`에서 핵심 레이아웃이 가장 안정적으로 보이는 것을 우선합니다.
- `1920 × 1080`에서는 콘텐츠 max-width와 여백 설계로 과도한 확장을 방지합니다.
- 도서관 레주메북 상세처럼 한 화면 안에서 열람하는 화면은 화면 높이가 커질 때 아래 여백만 늘리지 않고, 툴바·문서·페이지 표시 묶음 전체를 세로 기준으로 균형 있게 배치합니다.

## TODO

Expand Down
108 changes: 108 additions & 0 deletions src/app/(public)/library/page.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
.page {
position: relative;
min-height: 100vh;
color: var(--repo-gray-50);
background: var(--repo-bg-main);
}

.content {
width: min(100%, 1008px);
margin: 0 auto;
padding: 77px 29px 36px;
}

.hero {
display: grid;
justify-items: center;
gap: 14px;
text-align: center;
}

.title {
margin: 0;
color: var(--repo-gray-50);
font-size: 36px;
font-weight: 700;
line-height: 1.2;
letter-spacing: 0;
}

.description {
margin: 0;
color: var(--repo-gray-400);
font-size: 20px;
font-weight: 500;
line-height: 1.45;
letter-spacing: 0;
}

.books {
display: grid;
grid-template-columns: repeat(4, 166px);
justify-content: center;
gap: 52px 48px;
margin-top: 49px;
}

.toastLayer {
position: absolute;
z-index: 10;
top: 119px;
left: 50%;
width: min(calc(100% - 48px), 600px);
display: flex;
justify-content: center;
transform: translateX(-50%);
}

@media (max-width: 980px) {
.content {
padding-top: 72px;
}

.books {
grid-template-columns: repeat(3, 166px);
gap: 47px 50px;
}
}

@media (max-width: 720px) {
.content {
padding: 56px 24px 80px;
}

.title {
font-size: 32px;
}

.description {
font-size: 18px;
}

.books {
grid-template-columns: repeat(2, 166px);
gap: 40px 32px;
margin-top: 47px;
}

.toastLayer {
top: 148px;
}
}

@media (max-width: 520px) {
.content {
padding-inline: 18px;
}

.books {
grid-template-columns: 166px;
}
}

@media (min-width: 981px) {
.page {
height: 100dvh;
overflow: hidden;
}
}
53 changes: 53 additions & 0 deletions src/app/(public)/library/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { AppHeaderItem, LibraryBookCardProps } from '@/shared/ui'
import { AppHeader, LibraryBookCard, Toast } from '@/shared/ui'

import styles from './page.module.css'

const navigationItems = [
{ href: '/majors', label: '전공 관리', value: 'majors' },
{ href: '/students', label: '학생 관리', value: 'students' },
{ href: '/library', label: '도서관', value: 'library' },
] satisfies readonly AppHeaderItem[]

const libraryBooks = Array.from({ length: 8 }, (_, index) => ({
ariaLabel: `2022 9기 2학년 ${index + 1}번째 포트폴리오 열람`,
batchLabel: '9기',
gradeLabel: '2학년',
href: `/resume-books/${index + 1}` as const,
title: '2022',
})) satisfies readonly LibraryBookCardProps[]

type LibraryPageProps = {
readonly searchParams?: Promise<{
readonly error?: string
}>
}

export default async function LibraryPage({ searchParams }: LibraryPageProps) {
const params = await searchParams
const showsLoadError = params?.error === 'portfolio'

return (
<main className={styles.page}>
<AppHeader activeItem="library" items={navigationItems} />
{showsLoadError ? (
<div className={styles.toastLayer}>
<Toast variant="error">포트폴리오 문서를 불러오는데 실패하였습니다. 잠시 후 다시 시도해 주세요.</Toast>
</div>
) : null}
<section className={styles.content} aria-labelledby="library-title">
<div className={styles.hero}>
<h1 className={styles.title} id="library-title">
도서관
</h1>
<p className={styles.description}>다양한 학생들의 포트폴리오를 둘러보세요.</p>
</div>
<div className={styles.books} aria-label="포트폴리오 책 목록">
{libraryBooks.map((book) => (
<LibraryBookCard key={book.ariaLabel} {...book} />
))}
</div>
</section>
</main>
)
}
Loading
Loading