Files

91 lines
5.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Loading Screen (Pattern)
현재 HM 구현의 기준 문서. 공통 원칙은 `docs/guidelines/프로젝트-지침.md`(§6.10, §6.11)를 따른다.
```text
Layout visual reference: 외부 ui_viz Reference (ZIP 미포함) (dashboard-composition.md §18, platform-mapping/dashboard-loading-web.md)
Implementation: 의존하는 primitive 없음. 24개 막대 스피너 + 텍스트, CSS animation만 사용
```
## 1. 상태
| 항목 | 내용 |
|---|---|
| 구현 상태 | 구현 완료 (2026-10-06) |
| 대표 Story | `Patterns/Layout/Loading Screen` → `All` (Inline, Custom label, Narrow parent, Fullscreen) |
| 경위 | 요청은 "Loading Bar"였으나 ui_viz에 Loading Bar는 없었다. 실제 있는 Dashboard LoadingScreen(스피너)을 사용자가 지정해 구현했다 |
## 2. 역할 구분
| 컴포넌트 | 상황 |
|---|---|
| Progress | 진행률을 안다 (0~100%) |
| Skeleton | 콘텐츠 모양을 안다 |
| Sonner `loading` | 작업 중임을 알리는 toast |
| **Loading Screen** | 진행률도 모양도 모르는 채 영역/화면 전체가 준비 중 |
Progress는 Indeterminate 표현을 여전히 갖지 않는다(`progress.md` §11 유지). 버튼 안 inline spinner는 만들지 않았다(ui_viz §18.1과 동일).
## 3. 구성 / API
| export | 역할 |
|---|---|
| `LoadingScreen` | 초록 면 + 스피너 + 텍스트. 기본 inline, `fullscreen`이면 viewport 전체 |
```tsx
<LoadingScreen /> // inline, 부모 폭
<LoadingScreen>데이터를 불러오는 중</LoadingScreen> // 라벨 교체("..."는 자동 추가)
<LoadingScreen fullscreen /> // position: fixed, inset 0
```
- 추가 prop은 `fullscreen`뿐이다. `speed`, `size` 같은 prop은 만들지 않았다. 값은 `--hm-loading-*` Token으로 바꾼다.
- 닫기 버튼, 취소, async lifecycle, 표시/숨김 fade(0.3s)는 두지 않았다. 소비자가 로딩 동안 mount하고 끝나면 제거한다.
- Fullscreen은 Portal을 쓰지 않는다. transform이 있는 조상 안에 두면 `fixed`가 어긋난다.
## 4. 규격 (`packages/ui/src/theme/loading-screen.css`)
| 항목 | ui_viz | HM | 판정 |
|---|---|---|---|
| 스피너 | 44×44, 24 bars, 막대 2.5×10, 15° 간격, 첫 막대 12시 | 동일 (실측 44×44, 막대 2.5×10, origin 1.25px 22px) | MATCHED |
| 모션 | 막대 opacity 1→0.08, 1.2s linear infinite, 지연 i×0.05s, 회전 없음 | 동일 | MATCHED |
| 텍스트 | 14/500/-0.02em, white 60%, 끝에 정적 "..." | 동일, 폰트는 `--hm-font-family-base` | MATCHED |
| Inline | padding 36/24, radius 6, inset shadow 24px .45, gap 12, 가운데 정렬 | 동일 (실측 높이 149, ui_viz smoke 148은 폰트 line box 차이) | MATCHED |
| Fullscreen | fixed inset 0, assembly는 block·gap 0, 스피너는 묶음 leading edge(텍스트가 44px보다 넓으면 두 중심축이 어긋남), z-index 9999 | fixed inset 0, inline과 같은 column·gap 12로 묶음 전체를 viewport 중앙(스피너·텍스트 중심축 일치) | INTENTIONAL_CHANGE (2026-10-06 사용자 지시: 스피너가 왼쪽으로 치우쳐 보임) |
| 면 색 | `#0a3322` (Dashboard-local) | `--hm-sem-action-primary` (Default `#123328`) | INTENTIONAL_CHANGE (HM Primitive 재사용, 새 색 만들지 않음) |
| 폰트 | Inter → Noto Sans KR | HM 기본 폰트 | INTENTIONAL_CHANGE |
| 막대 끝 | radius 1.5 → 렌더에서 pill | `9999px` | MATCHED |
| 숨김/표시 fade 0.3s | 있음 | 없음 | FOLLOW_UP (소비자 lifecycle) |
| 닫기 버튼 + ESC | demo 전용(REFERENCE_ONLY) | 없음 (Story에 로컬 닫기 Button만) | OLD_TEST_ONLY |
| focus trap, background 차단, task lifecycle | TBD | 만들지 않음 | FOLLOW_UP |
## 5. Token
`--hm-loading-screen-{background, foreground, inline-padding-block, inline-padding-inline, inline-radius, inline-shadow, gap, z-index}`, `--hm-loading-spinner-{size, bar-width, bar-height, bar-count, bar-color, bar-min-opacity, duration, stagger}`, `--hm-loading-text-{font-size, font-weight, letter-spacing, color}`. 면/전경은 `--hm-sem-action-primary` / `--hm-sem-on-action-primary`, 그림자는 `--hm-sem-palette-black`을 참조한다. 신규 Primitive 없음.
## 6. Accessibility / Reduced Motion (INTENTIONAL_CHANGE)
ui_viz는 스피너 semantics와 reduced motion을 TBD로 남겼다. HM은 최소한만 정했다.
- 루트 `role="status"`(polite live region)가 텍스트를 알린다. 막대 컨테이너는 `aria-hidden`.
- `prefers-reduced-motion: reduce`: 애니메이션을 끄고 막대 opacity를 시계 방향으로 줄여 정적 형태로 보여준다.
- 진행률이 없으므로 `aria-valuenow` 등은 쓰지 않는다. focus 이동, 배경 입력 차단, `aria-busy`는 소비자 책임.
## 7. 검증 (2026-10-06)
- `npm run typecheck`, `npm run build`, `npm run build-storybook` 통과. 새 warning 없음(기존 chunk size 안내는 이전부터).
- 실측: inline 면 `rgb(18,51,40)`, padding 36/24, radius 6, shadow inset 24px .45, 스피너 44×44, 막대 24개, animation `hm-loading-fade` 1.2s, 지연 0.05s, spinner→text gap 12.
- Fullscreen(2026-10-06 수정 후): spinner centerX = label centerX = viewport centerX, 묶음 centerY = viewport centerY, gap 12. 1920/768/390 모두 오차 0, 가로 overflow 없음. reduced-motion에서도 위치 동일(animation만 none). inline은 높이 149, gap 12로 변화 없음.
- 1920 / 768 / 390, 일반 / `prefers-reduced-motion: reduce` 모두 Playwright(headless Chromium)로 실측했다.
- 기존 Progress / Skeleton / Sonner / Data Table / Dashboard Layout 코드는 수정하지 않았다.
## 8. 관련 파일
```
packages/ui/src/patterns/loading-screen/{loading-screen.tsx,index.ts}
packages/ui/src/theme/loading-screen.css
packages/ui/src/theme.css (import)
packages/ui/src/index.ts (export)
src/patterns/loading-screen.stories.tsx
.storybook/preview.tsx (Patterns/Layout 정렬)
```