Phase1 Ui Design System Spec
jhj
2026년 08월 14일
Phase 1 UI 디자인 시스템 및 스켈레톤 구현 설계 명세서 (Design Spec)
- 작성일: 2026-08-14
- 대상: Phase 1 — 기본 구조 수립 및 UI 디자인 시스템 구축
- 문서 위치:
dev/superpower/specs/2026-08-14-phase1-ui-design-system-spec.md - 관련 기준 문서:
dev/voxa/01_PRD.md,dev/voxa/02_아키텍처_디자인시스템_코딩컨벤션.md,dev/voxa/04_AGENT.md,dev/voxa/05_단계별_기능구현계획.md,dev/voxa/06_UI_화면설계명세.md
1. 개요 및 설계 목표
1.1 목표
05_단계별_기능구현계획.md의 Phase 1 요구사항에 따라, 하드코딩된 색상/치수 없이 토큰 기반 디자인 시스템(Soft Paper Minimal)을 구축하고, 기능 없이도 빌드/실행되며 다크모드와 네비게이션이 동작하는 스켈레톤 앱을 완성한다.- 모든 UI 컴포넌트와 화면에 라이트/다크 모드 Compose
@Preview를 제공하고, 런타임에서 토큰을 시각적으로 검증할 수 있는 Design System Showcase 화면을 구축한다. build-logic컨벤션 플러그인을 활용하여 멀티모듈 빌드 설정을 표준화한다.
2. 모듈 구조 및 의존성 설계
2.1 모듈 그래프
┌──────────────┐
│ :app │
└──────┬───────┘
┌────────────────────┼───────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ :feature:camera │ │ :feature:result │ │ :feature:pdf │ (Phase 2/3)
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
└──────────────┐ │ ┌─────────────┘
▼ ▼ ▼
┌─────────────────┐
│ :core:ui │ (공통 컴포넌트/쇼케이스)
└────────┬────────┘
│
▼
┌─────────────────┐
│:core:designsystem│ (토큰/테마/폰트)
└─────────────────┘
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ :domain │ │ :core:common │ │ :data:document │ (Phase 2/3)
└─────────────────┘ └─────────────────┘ └─────────────────┘
2.2 모듈별 책임 및 Gradle 플러그인 구성
| 모듈 | 책임 | 적용 Convention Plugins | 주요 의존성 |
|---|---|---|---|
:core:designsystem |
Raw/Semantic 색상, Spacing, Radius, Typography, Pretendard 폰트, AppTheme |
voxa.android.library, voxa.android.library.compose |
compose-ui, compose-material3, compose-ui-tooling-preview |
:core:ui |
공통 UI 컴포넌트(버튼, 칩, 카드, 바텀시트 컨테이너), Design System Showcase | voxa.android.library, voxa.android.library.compose |
:core:designsystem, material-icons-extended |
:core:common |
MVI 베이스 클래스(MviViewModel, State/Intent/Effect), Coroutine/Flow 유틸 |
voxa.android.library, voxa.android.hilt |
kotlinx-coroutines-core, lifecycle-viewmodel-ktx |
:domain |
순수 Kotlin 도메인 모델, DomainError, UseCase 인터페이스 |
voxa.jvm.library (Android 무의존) |
kotlinx-coroutines-core |
:feature:camera |
카메라 프리뷰 화면 스켈레톤, 오버레이 가이드, 스크림 툴바 | voxa.android.feature |
:core:designsystem, :core:ui, :core:common, :domain |
:feature:result |
결과 바텀시트 UI(4단계 상태), ResultViewModel 골격 |
voxa.android.feature |
:core:designsystem, :core:ui, :core:common, :domain |
:app |
진입점(MainActivity), Hilt Application, Navigation Graph, 테마 루트 |
voxa.android.application, voxa.android.application.compose, voxa.android.hilt |
모든 모듈 |
3. 디자인 시스템 토큰 명세 (:core:designsystem)
3.1 컨셉: Soft Paper Minimal
따뜻한 종이 질감의 톤과 정갈한 기능적 UI를 결합하여 눈의 피로를 최소화.
3.2 컬러 토큰
① Raw 컬러 팔레트 (ColorPalette)
object ColorPalette {
// Light Palette
val WarmCream = Color(0xFFFDFBF7) // 전체 배경 (Warm White)
val ParchmentBeige = Color(0xFFF7F1E4) // 카드/패널 surface
val SageTeal = Color(0xFF3F7768) // Primary CTA
val DeepTerracotta = Color(0xFFD96847) // Secondary / Accent
val Charcoal = Color(0xFF2B2D42) // Primary Text
val SoftGray = Color(0xFF6B7690) // Secondary Text
val OutlineBeige = Color(0xFFEDE6D8) // Outline 테두리
val Red600 = Color(0xFFB00020) // Error (Light)
// Dark Palette
val DarkCocoa = Color(0xFF1E1A15) // 전체 배경 (Dark)
val DarkSurface = Color(0xFF2A241C) // 카드/패널 surface (Dark)
val SageTealDark = Color(0xFF4F9C87) // Primary (Dark)
val LightTerracotta = Color(0xFFF0997B) // Accent (Dark)
val IvoryWhite = Color(0xFFEDEAE3) // Primary Text (Dark)
val SoftGrayLight = Color(0xFF9AA6BC) // Secondary Text (Dark)
val DarkOutline = Color(0xFF3A342B) // Outline 테두리 (Dark)
val OnPrimaryDark = Color(0xFF14211D) // onPrimary Text (Dark)
val Red300 = Color(0xFFCF6679) // Error (Dark)
val White = Color(0xFFFFFFFF)
}
② Semantic 컬러 (AppColors)
@Immutable
data class AppColors(
val primary: Color,
val onPrimary: Color,
val background: Color,
val surface: Color,
val onSurface: Color,
val accent: Color,
val onAccent: Color,
val error: Color,
val onError: Color,
val outline: Color,
val textPrimary: Color,
val textSecondary: Color,
val isDark: Boolean,
)
val LightColors = AppColors(
primary = ColorPalette.SageTeal,
onPrimary = ColorPalette.White,
background = ColorPalette.WarmCream,
surface = ColorPalette.ParchmentBeige,
onSurface = ColorPalette.Charcoal,
accent = ColorPalette.DeepTerracotta,
onAccent = ColorPalette.White,
error = ColorPalette.Red600,
onError = ColorPalette.White,
outline = ColorPalette.OutlineBeige,
textPrimary = ColorPalette.Charcoal,
textSecondary = ColorPalette.SoftGray,
isDark = false,
)
val DarkColors = AppColors(
primary = ColorPalette.SageTealDark,
onPrimary = ColorPalette.OnPrimaryDark,
background = ColorPalette.DarkCocoa,
surface = ColorPalette.DarkSurface,
onSurface = ColorPalette.IvoryWhite,
accent = ColorPalette.LightTerracotta,
onAccent = ColorPalette.DarkCocoa,
error = ColorPalette.Red300,
onError = ColorPalette.OnPrimaryDark,
outline = ColorPalette.DarkOutline,
textPrimary = ColorPalette.IvoryWhite,
textSecondary = ColorPalette.SoftGrayLight,
isDark = true,
)
3.3 치수 및 곡률 토큰 (Spacing, Radius)
object Spacing {
val none = 0.dp
val xs = 4.dp
val sm = 8.dp
val md = 16.dp
val lg = 24.dp
val xl = 32.dp
val xxl = 48.dp
}
object Radius {
val sm = 12.dp // 칩, 작은 인풋
val md = 16.dp // 카드, 패널
val lg = 20.dp // 바텀시트 상단 모서리
val pill = 28.dp // 주요 버튼, 토글 칩
val full = 999.dp // 원형 FAB
}
3.4 타이포그래피 토큰 (AppTypography)
- 폰트 패밀리:
Pretendard(Regular, Medium, SemiBold, Bold) 에셋 번들 + FallbackFontFamily.SansSerif - 텍스트 스타일 위계:
displayLarge: 28.sp, SemiBold, line-height 36.sptitleLarge: 20.sp, SemiBold, line-height 28.sptitleMedium: 16.sp, Medium, line-height 24.spbodyLarge: 16.sp, Regular, line-height 24.spbodyMedium: 14.sp, Regular, line-height 20.splabelSmall: 12.sp, Medium, line-height 16.sp
3.5 테마 제공 (AppTheme)
val LocalAppColors = staticCompositionLocalOf { LightColors }
val LocalAppTypography = staticCompositionLocalOf { AppTypographyDefault }
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colors = if (darkTheme) DarkColors else LightColors
CompositionLocalProvider(
LocalAppColors provides colors,
LocalAppTypography provides AppTypographyDefault,
content = content
)
}
object AppTheme {
val colors: AppColors
@Composable
get() = LocalAppColors.current
val typography: AppTypography
@Composable
get() = LocalAppTypography.current
val spacing: Spacing = Spacing
val radius: Radius = Radius
}
4. 공통 UI 컴포넌트 명세 (:core:ui)
모든 컴포넌트는 오직 AppTheme 토큰만을 참조하며, 독립적인 @Preview(name = "Light") 및 @Preview(name = "Dark", uiMode = UI_MODE_NIGHT_YES)를 포함한다.
4.1 버튼 세트
PrimaryPillButton:- 높이: 48dp, 모양:
Radius.pill - 배경:
AppTheme.colors.primary, 텍스트:AppTheme.colors.onPrimary - 인터랙션: 활성/비활성/로딩 인디케이터 지원
- 높이: 48dp, 모양:
SecondaryPillButton:- 높이: 48dp, 모양:
Radius.pill - 배경:
AppTheme.colors.surface, 테두리:1.dp solid AppTheme.colors.outline, 텍스트:AppTheme.colors.textPrimary
- 높이: 48dp, 모양:
AppIconButton:- 크기: 44dp 원형 터치 타겟, 아이콘: 20dp, 틴트:
AppTheme.colors.textPrimary
- 크기: 44dp 원형 터치 타겟, 아이콘: 20dp, 틴트:
ShutterFab:- 크기: 56dp 원형 FAB, 배경:
AppTheme.colors.primary, 아이콘:AppTheme.colors.onPrimary(촬영 아이콘)
- 크기: 56dp 원형 FAB, 배경:
4.2 칩 및 카드
LanguageSelectChip:- Source ↔ Target 언어 전환 토글
- 선택 상태:
AppTheme.colors.primary배경 +onPrimary텍스트 - 비선택/중간 화살표:
AppTheme.colors.textSecondary
BaseCard:- 모양:
Radius.md, 배경:AppTheme.colors.surface, 테두리:AppTheme.colors.outline1.dp
- 모양:
AccentCard:- 번역 결과 강조용 카드. 모양:
Radius.md, 배경:AppTheme.colors.accent.copy(alpha = 0.12f), 테두리:AppTheme.colors.accent.copy(alpha = 0.3f)
- 번역 결과 강조용 카드. 모양:
4.3 바텀시트 프레임 (VoxaBottomSheetContainer)
- 드래그 핸들 (32dp × 4dp,
AppTheme.colors.outline) - 상단 모서리
Radius.lg, 배경AppTheme.colors.surface - Collapsed (~25%) 및 Expanded (~80%) 부드러운 스냅 애니메이션 지원
4.4 Design System Showcase 화면 (DesignSystemCatalog)
- Light/Dark 토큰 시각 확인 그리드
- 모든 버튼, 칩, 카드, 타이포그래피 샘플을 한 화면에 렌더링하여 토큰 누락 및 다크모드 대비 확인
5. 화면 스켈레톤 및 네비게이션 설계
5.1 네비게이션 라우트 (:app)
sealed interface ScreenRoute {
@Serializable data object Camera : ScreenRoute
@Serializable data object PdfReader : ScreenRoute
@Serializable data object Showcase : ScreenRoute
}
5.2 화면별 스켈레톤 구성
CameraScreen(:feature:camera):- 전체 화면 Mock 카메라 뷰파인더
- 상단 스크림: 닫기 / 안내 텍스트(“문서 가장자리를 인식하고 있어요”) / 플래시
- 문서 감지 동적 아웃라인 Mock Overlay (
AppTheme.colors.accent2.dp) - 하단 스크림: 갤러리 버튼(좌), Shutter FAB(중앙), PDF 선택 버튼(우)
- Result 바텀시트 결합 슬롯
PdfReaderScreen(:feature:result/:app):- 몰입형 리더 스켈레톤 (기본 상태: 툴바 숨김, 탭 시 상/하단 바 200ms Fade-in)
- 상단바: 뒤로가기 / 파일명 / 페이지 인덱스
- 하단바: 이전 / “이 페이지 번역” PrimaryPillButton / 다음
ResultSheetContent(:feature:result):- 4단계 상태(Collapsed, Expanded, Loading, Error) 전환 지원
6. MVI 베이스 구조 (:core:common)
interface UiState
interface UiIntent
interface UiEffect
abstract class MviViewModel<S : UiState, I : UiIntent, E : UiEffect>(
initialState: S
) : ViewModel() {
private val _uiState = MutableStateFlow(initialState)
val uiState: StateFlow<S> = _uiState.asStateFlow()
private val _effect = Channel<E>(Channel.BUFFERED)
val effect: Flow<E> = _effect.receiveAsFlow()
abstract fun onIntent(intent: I)
protected fun updateState(reducer: S.() -> S) {
_uiState.update(reducer)
}
protected fun sendEffect(effect: E) {
viewModelScope.launch {
_effect.send(effect)
}
}
}
7. 검증 계획 및 DoD (Definition of Done)
7.1 검증 항목
- 빌드 검증:
./gradlew build및./gradlew check에러 없이 통과 - 정적 분석: 하드코딩된
Color(0xFF...)나dp리터럴이 컴포넌트 코드에 없고 오직AppTheme토큰만 사용하는지 검사 - Compose Preview 검증: 모든 공통 컴포넌트 및 화면의 Light/Dark Preview 렌더링 확인
- 네비게이션 동작: 앱 실행 시 Camera ↔ PDF ↔ Showcase 간 전환 정상 동작 확인
- 다크모드 전환: 시스템 테마 변경 시 모든 화면이 즉시 다크모드 토큰으로 리컴포지션되는지 확인
7.2 Git 커밋 정책 준수
- 사용자 요청에 따라 작업 중 임의 커밋을 수행하지 않으며, 전체 구현 및 확인 완료 후 사용자의 단계별 커밋 요청 시 진행.
C
Contents
