# 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.