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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ out
*.njsproj
*.sln
*.sw?
.omo/
.omx/
.codex/*
!.codex/agents/
Expand Down
158 changes: 158 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Repo-V2 Design System

## 1. Atmosphere & Identity

Repo-V2 is a dark, quiet school-work surface: compact, direct, and built for repeated student and teacher workflows. The signature is layered charcoal UI with bright submission status accents, where rows and cards feel tappable without decorative noise.

## 2. Color

### Palette

| Role | Token | Value | Usage |
| --- | --- | --- | --- |
| Surface/page | `--repo-gray-100` | `#F8F8F8` | App background |
| Surface/dark | `--repo-bg-main` | `#181819` | Header, footer, primary rows |
| Surface/dark-muted | `--repo-bg-main-lighter` | `#212124` | Active dark rows, selected nav |
| Surface/dark-raised | `--repo-bg-main-point` | `#43434B` | Secondary rows, filled controls |
| Text/inverse | `--repo-gray-50` | `#FFFFFF` | Text on dark surfaces |
| Text/muted | `--repo-gray-500` | `#919191` | Secondary dark-surface labels |
| Text/disabled | `--repo-gray-400` | `#ADADAD` | Placeholder and inactive labels |
| Accent/success | `--repo-main` | `#37E517` | Submitted state |
| Status/error | `--repo-state-error` | `#EB5757` | Error text |

### Rules

- Dark primitives use tonal-shift depth before shadows.
- Green is reserved for submitted/success status.
- Error red appears only with validation feedback.

## 3. Typography

### Scale

| Level | Token | Usage |
| --- | --- | --- |
| Title/large | `--repo-font-title-large-*` | Page-level emphasis only |
| Title/medium | `--repo-font-title-medium-*` | Large modal emphasis and rare showcase headings |
| Title/small | `--repo-font-title-small-*` | Primary row labels, card titles, modal headings |
| Title/tiny | `--repo-font-title-tiny-*` | Navigation, buttons |
| Body/large | `--repo-font-body-large-*` | Prominent row metadata |
| Body/medium | `--repo-font-body-medium-*` | Input text and row actions |
| Body/small | `--repo-font-body-small-*` | Default form text |
| Body/tiny | `--repo-font-body-tiny-*` | Footer/legal text |

### Font Stack

- Primary: Pretendard, system UI fallback.

### Rules

- Letter spacing remains `0`.
- Body text stays at or above 14px.

## 4. Spacing & Layout

### Base Unit

All spacing derives from 4px.

| Token | Value | Usage |
| --- | --- | --- |
| `--space-2` | 8px | Tight icon and text clusters |
| `--space-3` | 12px | Compact row gaps |
| `--space-4` | 16px | Form and row internal spacing |
| `--space-5` | 20px | Default inset |
| `--space-6` | 24px | Card inset |
| `--space-8` | 32px | Component groups |
| `--space-10` | 40px | Large cards and modal rhythm |

### Grid

- Desktop publishing target: 1440px to 1920px wide.
- Components are fluid by default and receive width from their parent.
- Reusable controls target app-scale CSS pixels, not exported screenshot pixels: rows 56px, headers 64px, inputs 56px by default.
- Mobile support is basic reflow protection, not final polish for this milestone.

## 5. Components

### AppHeader
- **Structure**: `header > Logo + nav + action`
- **Variants**: student, teacher, public
- **States**: nav default, selected, focus
- **Accessibility**: landmark header, current page conveyed through `aria-current`
- **Motion**: 120ms background and color transition

### LinkRow
- **Structure**: row button/link with primary text, optional status/date, optional trailing text, chevron
- **Variants**: submitted, missing, neutral
- **States**: default, active, focus
- **Accessibility**: interactive element supplies an accessible label through visible text

### ClassCard
- **Structure**: large rounded button with class name, count, chevron
- **Variants**: default, selected
- **States**: default, hover, focus

### TextSlot
- **Structure**: label, display text, optional editable input-like slot, optional error
- **Variants**: compact, wide
- **States**: read, edit, error

### MajorInputGroup
- **Structure**: stacked input rows with validation text
- **States**: default, error

### SearchField
- **Structure**: rounded input with search icon button
- **States**: default, focus

### MajorList
- **Structure**: section label plus full-width major rows
- **States**: row default, selected, focus

### AppFooter
- **Structure**: footer with product name, address, phone lines, legal links
- **Accessibility**: footer landmark

### LibraryBook
- **Structure**: fixed-ratio book cover link using `/assets/library-book-cover.svg` as the cover source, with year, generation, grade, and action text layered above it
- **States**: default, hover, focus

### PortfolioUrlModal
- **Structure**: dialog panel with title, description, input and actions
- **States**: default, error, pending through disabled actions
- **Accessibility**: dialog role and labelled title

## 6. Motion & Interaction

| Type | Duration | Easing | Usage |
| --- | --- | --- | --- |
| Micro | 120ms | ease-out | Button and row feedback |
| Standard | 200ms | ease-in-out | Modal and selected state shifts |

Only `transform`, `opacity`, background, border, and color transitions are used in this component pass.

## 7. Depth & Surface

### Strategy

Mixed tonal-shift with restrained shadows for raised cards and modal surfaces.

| Level | Value | Usage |
| --- | --- | --- |
| Book | `0 14px 32px rgba(17, 17, 17, 0.28)` | Library book card |
| Modal | `0 20px 60px rgba(17, 17, 17, 0.18)` | URL modal |

## 8. Accessibility Constraints & Accepted Debt

### Constraints

- Target WCAG 2.2 AA.
- Every interactive primitive exposes visible focus.
- Components must tolerate long Korean labels without overlap.

### Accepted Debt

| Item | Location | Why accepted | Owner / Exit |
| --- | --- | --- | --- |
| Pixel-perfect reference matching | Shared component showcase | User requested final visual drift to be handled in later QA | Final publishing QA |
30 changes: 30 additions & 0 deletions public/assets/auth-bg.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions src/app/component-showcase/page.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
.page {
display: grid;
gap: 32px;
min-height: 100vh;
padding: 20px;
background: var(--repo-gray-700);
}

.section {
display: grid;
gap: var(--space-5);
width: 100%;
}

.sectionTitle {
margin: 0;
color: var(--repo-gray-50);
font-size: var(--repo-font-body-large-size);
font-weight: var(--repo-font-body-large-weight);
line-height: var(--repo-font-body-large-line-height);
letter-spacing: 0;
}

.stack {
display: grid;
gap: var(--space-5);
}

.grid,
.gridWide,
.libraryArea {
display: grid;
gap: var(--space-6);
align-items: start;
}

.grid {
grid-template-columns: repeat(2, minmax(240px, 1fr));
}

.gridWide {
grid-template-columns: minmax(220px, 300px) 1fr;
}

.libraryArea {
grid-template-columns: 240px 240px 1fr;
}

@media (max-width: 980px) {
.grid,
.gridWide,
.libraryArea {
grid-template-columns: 1fr;
}
}
107 changes: 107 additions & 0 deletions src/app/component-showcase/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {
AppFooter,
AppHeader,
ClassCard,
LibraryBook,
LinkRow,
MajorInputGroup,
MajorList,
OptionList,
PortfolioUrlModal,
SearchField,
TextSlot,
} from '@/shared/ui'

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

const studentNavItems = [
{ href: '/', label: '홈', value: 'home' },
{ href: '/student/resume', label: '이력서 관리', value: 'resume' },
{ href: '/library', label: '도서관', value: 'library' },
] as const

const teacherNavItems = [
{ href: '/teacher/majors', label: '전공 관리', value: 'major' },
{ href: '/teacher/students', label: '학생 관리', value: 'students' },
{ href: '/library', label: '도서관', value: 'library' },
] as const

const majors = [
{ id: 'frontend', name: 'Frontend Developer' },
{ id: 'backend', name: 'Backend Developer' },
] as const

const options = [
{ id: 'frontend-1', label: 'Frontend Developer' },
{ id: 'backend-1', label: 'Backend Developer' },
{ id: 'frontend-2', label: 'Frontend Developer' },
{ id: 'backend-2', label: 'Backend Developer' },
{ id: 'frontend-3', label: 'Frontend Developer' },
{ id: 'backend-3', label: 'Backend Developer' },
] as const

export default function ComponentShowcasePage() {
return (
<main className={styles.page}>
<section className={styles.section}>
<h1 className={styles.sectionTitle}>Rows</h1>
<div className={styles.stack}>
<LinkRow actionLabel="레주메 보러가기" href="/student/choi-haeun" status="제출됨" title="2415 최하은" tone="submitted" />
<LinkRow actionLabel="레주메 보러가기" href="/student/choi-haeun" status="미제출" title="2415 최하은" tone="missing" />
<LinkRow href="/notice/1" meta="202X.XX.XX" title="알림내용" />
<LinkRow href="/notice/2" meta="202X.XX.XX" surface="muted" title="알림내용" />
</div>
</section>

<section className={styles.section}>
<h2 className={styles.sectionTitle}>Cards</h2>
<div className={styles.grid}>
<ClassCard count={16} selected title="1반" />
<ClassCard count={16} title="1반" />
</div>
</section>

<section className={styles.section}>
<h2 className={styles.sectionTitle}>Text Slots</h2>
<div className={styles.gridWide}>
<TextSlot label="전체" value="전체" />
<TextSlot editableValue="Frontend Developer" label="Frontend Developer" value="Frontend Developer" variant="wide" />
</div>
</section>

<section className={styles.section}>
<h2 className={styles.sectionTitle}>Major Controls</h2>
<SearchField />
<MajorInputGroup
errorMessage="에러메시지를 띄워줍니다."
inputs={[
{ id: 'major-1', placeholder: '추가할 전공을 입력해주세요.' },
{ id: 'major-2', placeholder: '추가할 전공을 입력해주세요.' },
]}
/>
<MajorList items={majors} selectedId="frontend" />
</section>

<section className={styles.section}>
<h2 className={styles.sectionTitle}>Header</h2>
<AppHeader activeItem="home" items={studentNavItems} />
<AppHeader activeItem="resume" items={studentNavItems} />
<AppHeader showLogin items={studentNavItems} />
<AppHeader activeItem="major" items={teacherNavItems} />
<AppHeader activeItem="students" items={teacherNavItems} />
<AppHeader activeItem="library" items={teacherNavItems} />
</section>

<section className={styles.section}>
<h2 className={styles.sectionTitle}>Library And Modal</h2>
<div className={styles.libraryArea}>
<OptionList items={options} />
<LibraryBook generation="9기" grade="2학년" href="/resume-books/2022" year="2022" />
<PortfolioUrlModal />
</div>
</section>

<AppFooter />
</main>
)
}
23 changes: 23 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,29 @@
--border: var(--repo-gray-200);
--brand: var(--repo-main);
--brand-soft: var(--repo-bg-main-point);
--repo-radius-control: 10px;
--repo-radius-panel: 16px;
--repo-radius-card: 14px;
--repo-radius-pill: 999px;
--repo-size-row-min-height: 56px;
--repo-size-header-min-height: 64px;
--repo-size-input-min-height: 56px;
--repo-shadow-book: 0 14px 32px rgba(17, 17, 17, 0.28);
--repo-shadow-modal: 0 20px 60px rgba(17, 17, 17, 0.18);
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-7: 28px;
--space-8: 32px;
--space-9: 36px;
--space-10: 40px;
--space-12: 48px;
--space-14: 56px;
--space-16: 64px;
--space-18: 72px;
--space-20: 80px;

color: var(--text-primary);
background: var(--background);
Expand Down
Loading
Loading