타입 스케일·공통 크롬을 Figma 기준으로 정렬 — 상단 내비게이션 · 하단 탭 아이콘 · 버튼 · 시트

This commit is contained in:
Sn-Kinos
2026-09-27 19:59:33 +09:00
parent 65ba44a1d1
commit 388ea4227c
19 changed files with 153 additions and 69 deletions
+55 -30
View File
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import '../ui/app_icons.dart';
import 'tokens.dart';
/// design-tokens.md → ThemeData 조립.
@@ -43,21 +44,36 @@ ThemeData buildAppTheme() {
return ThemeData(
useMaterial3: true,
colorScheme: scheme,
// 글꼴을 지정하지 않은 글자도 Figma 본문 글꼴(42dot Sans)로 그린다.
fontFamily: AppFonts.body,
// Figma Iconset(Lucide) — 뒤로가기 chevron-left · 닫기 x.
actionIconTheme: ActionIconThemeData(
backButtonIconBuilder: (_) => const ImageIcon(AppIcons.chevronLeft, size: 24),
closeButtonIconBuilder: (_) => const ImageIcon(AppIcons.x, size: 24),
),
scaffoldBackgroundColor: AppColors.cosmicVoid,
textTheme: _textTheme(scheme),
appBarTheme: const AppBarTheme(
// Figma Top Navigation(알림 229:11265 · 설정 354:222008 등) — 높이 56, 바탕 #141414,
// 제목 42dot Sans 16/600 가운데, 아이콘 24 를 56×56 칸에.
appBarTheme: AppBarTheme(
backgroundColor: AppColors.cosmicVoid,
foregroundColor: AppColors.onCosmic,
surfaceTintColor: Colors.transparent,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
toolbarHeight: 56,
titleTextStyle: _style(AppType.navTitle, AppColors.onCosmic),
iconTheme: const IconThemeData(color: AppColors.onCosmic, size: 24),
actionsIconTheme: const IconThemeData(color: AppColors.onCosmic, size: 24),
),
// 시안 Bottom Navigation(350:215853) — 바탕 #141414, 알약 표시자 없음, 아이콘 24,
// 라벨 ABeeZee 10. 활성은 흰색, 비활성은 #777777.
// 라벨 ABeeZee 10/16. 활성은 흰색, 비활성은 #777777. 탭 칸 높이 64(홈 인디케이터 34 별도).
navigationBarTheme: NavigationBarThemeData(
backgroundColor: AppColors.cosmicVoid,
surfaceTintColor: Colors.transparent,
indicatorColor: Colors.transparent,
// 탭 항목 높이는 touch-min-app(48) 아래로 내리지 않는다.
height: AppSizing.touchMinApp + AppSpace.s5,
height: 64,
iconTheme: WidgetStateProperty.resolveWith(
(states) => IconThemeData(
size: 24,
@@ -67,11 +83,9 @@ ThemeData buildAppTheme() {
),
),
labelTextStyle: WidgetStateProperty.resolveWith(
(states) => TextStyle(
fontFamily: AppFonts.display,
fontSize: 10,
letterSpacing: 0.2,
color: states.contains(WidgetState.selected)
(states) => _style(
AppType.navLabel,
states.contains(WidgetState.selected)
? AppColors.onCosmic
: AppColors.onCosmicMuted,
),
@@ -81,30 +95,30 @@ ThemeData buildAppTheme() {
// ── 아래는 테마를 주지 않으면 Material 3 기본값(보라 시드)으로 칠해지는 것들이다.
// 코스믹 다크 위에서 눈에 띄게 어긋나므로 전부 토큰으로 고정한다.
// Figma 정렬 칩(Inside_최신순 196:2673) — 바탕 #1a1a1c · 테두리 #232324 · 모서리 8 ·
// 안쪽 6/10 · 글자 12/500. 선택은 흰 바탕에 검은 글자다.
chipTheme: ChipThemeData(
backgroundColor: AppColors.cosmicSurfaceContainer,
selectedColor: AppColors.cosmicSurfaceRaised,
// 선택 상태는 색면이 아니라 테두리와 글자색으로 알린다 — 악센트는 행동에만 쓴다.
side: const BorderSide(color: AppColors.cosmicOutline),
selectedColor: AppColors.onCosmic,
side: const BorderSide(color: AppColors.cosmicSurfaceRaised),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppRadius.full),
borderRadius: BorderRadius.circular(AppRadius.sm),
),
labelStyle: _style(AppType.labelL, AppColors.onCosmicVariant),
secondaryLabelStyle: _style(AppType.labelL, AppColors.cosmicAccent),
checkmarkColor: AppColors.cosmicAccent,
labelStyle: _style(AppType.caption, const Color(0xFFD5D7D9)),
secondaryLabelStyle: _style(AppType.caption, AppColors.onCosmicAccent),
checkmarkColor: AppColors.onCosmicAccent,
showCheckmark: false,
padding: const EdgeInsets.symmetric(
horizontal: AppSpace.s3,
vertical: AppSpace.s2,
),
labelPadding: EdgeInsets.zero,
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
),
cardTheme: CardThemeData(
color: AppColors.cosmicSurfaceContainer,
surfaceTintColor: Colors.transparent,
elevation: 0,
margin: EdgeInsets.zero,
// Figma 카드(MY 373:632962 등 36곳) — 모서리 16.
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppRadius.md),
borderRadius: BorderRadius.circular(AppRadius.lg),
),
),
floatingActionButtonTheme: FloatingActionButtonThemeData(
@@ -137,18 +151,20 @@ ThemeData buildAppTheme() {
linearTrackColor: AppColors.cosmicSurfaceRaised,
circularTrackColor: Colors.transparent,
),
// Figma 액션 시트(343:212939) #2C2C2E · 모서리 16.
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: AppColors.cosmicSurfaceContainer,
backgroundColor: AppColors.cosmicSheet,
surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(AppRadius.lg)),
),
),
// Figma 확인 팝업(차단하기 373:442417) #2D2D2D · 모서리 12.
dialogTheme: DialogThemeData(
backgroundColor: AppColors.cosmicSurfaceContainer,
backgroundColor: AppColors.cosmicDialog,
surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppRadius.lg),
borderRadius: BorderRadius.circular(AppRadius.md),
),
),
snackBarTheme: SnackBarThemeData(
@@ -174,34 +190,41 @@ ThemeData buildAppTheme() {
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.onCosmic,
side: const BorderSide(color: AppColors.cosmicOutline),
minimumSize: const Size.fromHeight(AppSizing.touchMinApp),
minimumSize: const Size.fromHeight(_buttonHeight),
padding: _buttonPadding,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppRadius.md),
borderRadius: BorderRadius.circular(AppRadius.sm),
),
textStyle: _styleNoColor(AppType.titleM),
textStyle: _styleNoColor(AppType.navTitle),
),
),
iconTheme: const IconThemeData(color: AppColors.onCosmicVariant),
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight(AppSizing.touchMinApp),
minimumSize: const Size.fromHeight(_buttonHeight),
padding: _buttonPadding,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppRadius.md),
borderRadius: BorderRadius.circular(AppRadius.sm),
),
// 글자색은 여기서 정하지 않는다 — foregroundColor 가 결정한다.
// textStyle 에 색을 넣으면 소셜 버튼처럼 제공자별 색을 쓰는 곳과 지정이 두 곳으로 갈린다.
textStyle: _styleNoColor(AppType.titleM),
textStyle: _styleNoColor(AppType.navTitle),
),
),
);
}
/// Figma 주 버튼(퀘스트 상세 264:48835 등) — 343×56, 모서리 8, 안쪽 16/24, 글자 16/600.
const _buttonHeight = 56.0;
const _buttonPadding = EdgeInsets.symmetric(horizontal: 24, vertical: 16);
TextTheme _textTheme(ColorScheme scheme) => TextTheme(
displayLarge: _style(AppType.displayL, scheme.onSurface),
displayMedium: _style(AppType.displayM, scheme.onSurface),
headlineSmall: _style(AppType.titleL, scheme.onSurface),
titleLarge: _style(AppType.titleL, scheme.onSurface),
titleMedium: _style(AppType.titleM, scheme.onSurface),
titleSmall: _style(AppType.labelL, scheme.onSurface),
bodyLarge: _style(AppType.bodyL, scheme.onSurface),
bodyMedium: _style(AppType.bodyM, scheme.onSurface),
bodySmall: _style(AppType.bodyS, scheme.onSurfaceVariant),
@@ -219,6 +242,8 @@ TextStyle _style(TypeToken t, Color color) => _styleNoColor(t).copyWith(color: c
/// 색 없는 타입 스타일. 색을 다른 속성(foregroundColor 등)이 정하는 자리에 쓴다.
TextStyle _styleNoColor(TypeToken t) => TextStyle(
fontFamily: t.family,
// ABeeZee 는 라틴 전용이다. 한글은 본문 글꼴로 이어 그린다(Figma 도 같은 대체를 한다).
fontFamilyFallback: const [AppFonts.body],
fontSize: t.size,
fontWeight: _weight(t.weight),
height: t.height,