# Frontend Design Patterns
라온누리 프로젝트의 프론트엔드 디자인 패턴 가이드입니다.
`STYLE_GUIDE.md`(컬러, 아이콘, 테마 규칙)와 함께, **어떻게 느끼게 할 것인가**를 정의합니다.
---
## 1. 원칙
### 콘텐츠가 주인공이다
UI 크롬(타이틀 바, 보더, 패딩)은 최소화한다.
미디어 중심 UI(카메라, 이미지 뷰어, 크로퍼)는 풀블리드로, 컨트롤은 콘텐츠 위에 오버레이한다.
폼/설정 같은 도구 UI에서만 일반 크롬을 유지한다.
### 상태는 뚝 바뀌지 않는다
조건부 렌더링(`{ready && }`)은 레이아웃이 변하는 경우에만 쓴다.
그 외에는 `opacity + transition`으로 부드럽게 드러낸다.
```tsx
// ✅ opacity 전환
// ❌ 갑자기 나타남
{ready && }
```
### 아이콘은 맥락에 맞게
로딩/에러 상태에 제네릭 Spinner 대신, 해당 기능의 아이콘을 사용한다.
카메라 로딩엔 `LuCamera`, 업로드 로딩엔 `LuUpload`, 에러엔 해당 아이콘의 Off 변형(`LuCameraOff`).
### 비슷한 아이콘은 나란히 두지 않는다
같은 도구 모음 안에서 시각적으로 유사한 아이콘이 인접하면 혼란을 준다.
의미가 다른 아이콘이 비슷하게 보이면 아이콘 자체를 바꾼다.
```
✅ 회전 LuRotateCw | 초기화 LuUndo2 — 형태가 다름
❌ 회전 LuRotateCw | 초기화 LuRefreshCw — 둘 다 회전 화살표
```
---
## 2. 애니메이션
모든 애니메이션은 CSS `@keyframes` + Chakra `css` prop으로 구현한다.
### 수치 기준
| 용도 | duration | easing |
|------|----------|--------|
| 등장 (fadeIn) | 0.3s | ease-out |
| 호버 전환 | 0.2s | — |
| 상태 전환 (색상, 크기) | 0.3s | — |
| 오버레이 reveal | 0.4s | ease |
| 호흡 (pulse) | 2s | ease-in-out |
### 등장 — scale + fade
상태 변화(에러/성공/새 요소)에 사용. 0.3초, 살짝 작게 시작.
```tsx
css={{
animation: "fadeIn 0.3s ease-out",
"@keyframes fadeIn": {
from: {opacity: 0, transform: "scale(0.9)"},
to: {opacity: 1, transform: "scale(1)"},
},
}}
```
순차 등장이 필요하면 `animation-delay`를 `0.1s` 간격으로 준다.
### 호흡 — 준비 상태 표현
"사용 가능"을 은은하게 알린다. 2초 주기, 미세한 변화.
```tsx
// boxShadow 펄스 (버튼 주변 링)
"@keyframes pulse": {
"0%, 100%": {boxShadow: "0 0 0 0px {colors.brand.solid/30}"},
"50%": {boxShadow: "0 0 0 6px {colors.brand.solid/0}"},
}
// opacity + scale (아이콘)
"@keyframes pulse": {
"0%, 100%": {opacity: 0.4, transform: "scale(1)"},
"50%": {opacity: 1, transform: "scale(1.1)"},
}
```
### 촉각 피드백
모든 커스텀 버튼(`Box as="button"`)에 적용한다.
```tsx
css={{
"&:hover": { /* 배경/테두리 변화 */ },
"&:active": { transform: "scale(0.9)" }, // 눌림 느낌
}}
transition="all 0.2s"
```
### keyframe 네이밍
`{컴포넌트}{동작}` camelCase. 범용은 접두사 없이.
예: `cameraPulse`, `shutterReady`, `fadeIn`
---
## 3. 컴포넌트 패턴
### Frosted Glass Pill
미디어 위에 텍스트를 올릴 때 사용. 반투명 배경 + 블러.
```tsx
```
### 플로팅 툴바
미디어 위 도구 버튼을 frosted glass bar로 그룹화. 아이콘 전용, `title`로 레이블.
**성격이 다른 도구 그룹 사이에는 세로 디바이더**를 넣는다.
```tsx
{/* 편집 도구 */}
} />
} />
} />
{/* 디바이더 — 파괴적 도구 분리 */}
} />
```
ToolButton 스펙: `w={9} h={9}`, `borderRadius="full"`, hover `rgba(255,255,255,0.2)`, active `scale(0.9)`
### 은은한 글로우
어두운 배경 위 흰색 요소에 빛 번짐. `boxShadow="0 0 6px rgba(255,255,255,0.4)"`
### 안정적 비율 유지
비동기 콘텐츠(카메라, 이미지 로딩)는 `aspectRatio`를 고정해 레이아웃 시프트를 방지한다.
```tsx
```
### 절대 위치 3단 레이아웃
컨트롤 바에서 좌/중앙/우 배치. 중앙은 자연 중앙, 좌우는 `position: absolute`.
```tsx
{/* 보조 버튼 */}
{/* 주요 버튼 — 항상 정중앙 */}
{/* 균형 */}
```
---
## 4. 상태 표현
### 색상으로 구분
| 상태 | 색상 | cursor |
|------|------|--------|
| 비활성/로딩 | `border.muted` | `not-allowed` |
| 활성/준비 | `brand.solid` | `pointer` |
| 에러 | `red.subtle` / `red.fg` | — |
비활성 → 활성 전환에 `transition: "all 0.3s"`를 적용한다.
### 에러 상태
관련 아이콘을 `subtle` 배경 원 안에 배치 + fadeIn 등장. 아래에 설명 텍스트.
```tsx
{errorMessage}
```
---
## 5. 체크리스트
새 컴포넌트를 만들 때:
- [ ] 비동기 콘텐츠 영역에 `aspectRatio` 고정했는가?
- [ ] 로딩/에러 상태에 맥락 부합 아이콘을 사용했는가?
- [ ] 커스텀 버튼에 hover 전환 + active `scale(0.9)` 피드백이 있는가?
- [ ] 상태 전환에 `transition` 또는 `animation`을 적용했는가?
- [ ] 미디어 위 텍스트에 frosted glass pill을 사용했는가?
- [ ] 도구 모음에서 성격이 다른 그룹을 디바이더로 분리했는가?
- [ ] 인접 아이콘이 시각적으로 혼동되지 않는가?
---
© 2024 BlueNovaLab. All rights reserved.