# 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
// inline, 부모 폭
데이터를 불러오는 중 // 라벨 교체("..."는 자동 추가)
// 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 정렬)
```