From 9986e32ffbd3ae22dd9329e81d996c84b85fd14d Mon Sep 17 00:00:00 2001 From: pkdee Date: Sat, 4 Jul 2026 19:41:12 +0900 Subject: [PATCH 1/2] =?UTF-8?q?refactor:=20raw=20=ED=9D=91=EB=B0=B1=20?= =?UTF-8?q?=EB=A6=AC=ED=84=B0=EB=9F=B4=EC=9D=84=20onInk=C2=B7onPrimary=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EA=B4=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - seed onPrimary 신설 벤더링 + DkTokens.onPrimary/byKey 추가 - ink·유채색 히어로 텍스트는 onInk/onInkMuted, 컨트롤 위 고정 흰색은 onPrimary - 불가피 예외(브랜드 마크·장식 알파·색연산)는 token-exempt 주석으로 명시 - onPrimary-primary 대비 4.5:1 단위 테스트 추가 Closes #197 --- lib/parts/dday_hero.dart | 17 +++++---- lib/parts/task_row.dart | 7 +--- lib/screens/calc_sheet.dart | 2 +- lib/screens/debt/debt_screen.dart | 35 ++++++++++--------- lib/screens/focus/focus_screen.dart | 20 +++++------ lib/screens/focus/skins.dart | 9 +++-- lib/screens/login.dart | 8 +++-- lib/screens/me/me_screen.dart | 4 +-- lib/screens/me/settings_section.dart | 4 +-- lib/screens/onboarding.dart | 20 +++++------ lib/screens/plan/calendar_screen.dart | 2 +- lib/screens/plan/week_strip.dart | 2 +- lib/screens/review/review_screen.dart | 13 +++---- lib/screens/sheets/date_picker_sheet.dart | 2 +- .../sheets/date_range_picker_sheet.dart | 2 +- lib/screens/sheets/event_sheet.dart | 19 +++++----- lib/screens/sheets/task_sheet.dart | 2 +- lib/theme/seed_components.dart | 2 +- lib/theme/seed_tokens.dart | 4 +++ lib/theme/tokens.dart | 7 ++++ lib/widgets/dk_badge.dart | 2 +- lib/widgets/dk_brand_mark.dart | 1 + lib/widgets/dk_empty.dart | 4 +-- lib/widgets/dk_fab.dart | 7 +--- lib/widgets/dk_logo.dart | 4 ++- lib/widgets/dk_tab_bar.dart | 4 +-- test/theme_contrast_test.dart | 8 +++++ 27 files changed, 116 insertions(+), 95 deletions(-) diff --git a/lib/parts/dday_hero.dart b/lib/parts/dday_hero.dart index 3c54c06..792ed0a 100644 --- a/lib/parts/dday_hero.dart +++ b/lib/parts/dday_hero.dart @@ -27,7 +27,6 @@ class DdayHero extends StatelessWidget { final DkDdayInfo info = ddayInfo(event); final DkHue h = t.hue(event.color); final bool urgent = info.urgency == DkUrgency.high; - const Color white = Color(0xFFFFFFFF); return GestureDetector( onTap: onOpen == null ? null : () => onOpen!(event), @@ -66,11 +65,11 @@ class DdayHero extends StatelessWidget { children: [ Text( event.category, - style: const TextStyle( + style: TextStyle( fontFamily: 'Pretendard', fontSize: SeedType.caption, fontWeight: FontWeight.w600, - color: Color(0xB3FFFFFF), + color: t.onInkMuted, ), ), if (urgent) ...[ @@ -103,20 +102,20 @@ class DdayHero extends StatelessWidget { event.title, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle( + style: TextStyle( fontFamily: 'Pretendard', fontSize: SeedType.bodyLg, fontWeight: FontWeight.w600, - color: Color(0xE0FFFFFF), + color: t.onInk, ), ), const SizedBox(height: 2), Text( eventDateLabel(event), - style: const TextStyle( + style: TextStyle( fontFamily: 'Pretendard', fontSize: SeedType.label, - color: Color(0x99FFFFFF), + color: t.onInkMuted, ), ), ], @@ -129,13 +128,13 @@ class DdayHero extends StatelessWidget { ? info.label : '${info.pct}%') : info.label, - style: const TextStyle( + style: TextStyle( fontFamily: 'WantedSans', fontSize: 52, fontWeight: FontWeight.w800, letterSpacing: -2.08, height: 0.9, - color: white, + color: t.onInk, ), ), ], diff --git a/lib/parts/task_row.dart b/lib/parts/task_row.dart index 87e25f9..bd1694a 100644 --- a/lib/parts/task_row.dart +++ b/lib/parts/task_row.dart @@ -33,12 +33,7 @@ class DkCheckbox extends StatelessWidget { border: done ? null : Border.all(color: t.borderStrong, width: 2), ), child: done - ? const DkIcon( - 'check', - size: 16, - color: Color(0xFFFFFFFF), - strokeWidth: 3, - ) + ? DkIcon('check', size: 16, color: t.onPrimary, strokeWidth: 3) : null, ), ); diff --git a/lib/screens/calc_sheet.dart b/lib/screens/calc_sheet.dart index 2756702..69dc8c6 100644 --- a/lib/screens/calc_sheet.dart +++ b/lib/screens/calc_sheet.dart @@ -399,7 +399,7 @@ class _CalcButtonState extends State<_CalcButton> { case _KeyKind.op: return (bg: t.primarySubtle, fg: t.primary); case _KeyKind.eq: - return (bg: t.primary, fg: const Color(0xFFFFFFFF)); + return (bg: t.primary, fg: t.onPrimary); case _KeyKind.clr: return (bg: t.dangerSubtle, fg: t.danger); } diff --git a/lib/screens/debt/debt_screen.dart b/lib/screens/debt/debt_screen.dart index 66e5857..cc3777d 100644 --- a/lib/screens/debt/debt_screen.dart +++ b/lib/screens/debt/debt_screen.dart @@ -161,7 +161,6 @@ class _DebtScreenState extends State { } Widget _hero(DkTokens t, int total, int overdue) { - const Color white = Color(0xFFFFFFFF); return ClipRRect( borderRadius: BorderRadius.circular(t.radiusLg), child: Container( @@ -172,7 +171,11 @@ class _DebtScreenState extends State { end: Alignment.bottomRight, colors: [ t.warningFg, - Color.lerp(t.warningFg, const Color(0xFF000000), 0.22)!, + Color.lerp( + t.warningFg, + const Color(0xFF000000), + 0.22, + )!, // token-exempt: warningFg 어둡게 섞는 색연산 ], ), borderRadius: BorderRadius.circular(t.radiusLg), @@ -181,13 +184,13 @@ class _DebtScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( + Text( '아직 못 한 일, 사라지지 않아요', style: TextStyle( fontFamily: 'Pretendard', fontSize: SeedType.label, fontWeight: FontWeight.w600, - color: Color(0xD1FFFFFF), + color: t.onInk, ), ), const SizedBox(height: 6), @@ -197,23 +200,23 @@ class _DebtScreenState extends State { children: [ Text( (total / 60).toStringAsFixed(1), - style: const TextStyle( + style: TextStyle( fontFamily: 'WantedSans', fontSize: 46, fontWeight: FontWeight.w800, letterSpacing: -1.84, height: 1, - color: white, + color: t.onInk, ), ), const SizedBox(width: 6), - const Text( + Text( '시간', style: TextStyle( fontFamily: 'Pretendard', fontSize: SeedType.bodyLg, fontWeight: FontWeight.w600, - color: white, + color: t.onInk, ), ), ], @@ -221,14 +224,14 @@ class _DebtScreenState extends State { const SizedBox(height: 16), Row( children: [ - _heroStat('대기·배정', fmtMins(total - overdue)), + _heroStat(t, '대기·배정', fmtMins(total - overdue)), Container( width: 1, height: 34, margin: const EdgeInsets.symmetric(horizontal: 16), - color: const Color(0x40FFFFFF), + color: const Color(0x40FFFFFF), // token-exempt: 히어로 장식 구분선 ), - _heroStat('계속 밀림', fmtMins(overdue)), + _heroStat(t, '계속 밀림', fmtMins(overdue)), ], ), ], @@ -237,26 +240,26 @@ class _DebtScreenState extends State { ); } - Widget _heroStat(String label, String value) { + Widget _heroStat(DkTokens t, String label, String value) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, - style: const TextStyle( + style: TextStyle( fontFamily: 'Pretendard', fontSize: SeedType.caption, - color: Color(0xB3FFFFFF), + color: t.onInkMuted, ), ), const SizedBox(height: 2), Text( value, - style: const TextStyle( + style: TextStyle( fontFamily: 'Pretendard', fontSize: SeedType.bodyLg, fontWeight: FontWeight.w700, - color: Color(0xFFFFFFFF), + color: t.onInk, ), ), ], diff --git a/lib/screens/focus/focus_screen.dart b/lib/screens/focus/focus_screen.dart index 75bc1c7..aa5caa9 100644 --- a/lib/screens/focus/focus_screen.dart +++ b/lib/screens/focus/focus_screen.dart @@ -360,8 +360,8 @@ class _FocusScreenState extends State { child: DkIcon( _c.running ? 'pause' : 'play', size: 36, - color: const Color(0xFFFFFFFF), - fill: const Color(0xFFFFFFFF), + color: t.onPrimary, + fill: t.onPrimary, ), ), ), @@ -412,10 +412,10 @@ class _FocusScreenState extends State { color: t.success, borderRadius: BorderRadius.circular(SeedRadius.button), ), - child: const DkIcon( + child: DkIcon( 'check', size: 22, - color: Color(0xFFFFFFFF), + color: t.onPrimary, strokeWidth: 2.6, ), ), @@ -454,10 +454,10 @@ class _FocusScreenState extends State { widget.onCompleteTask(task); setState(() => _wasDone = false); }, - leading: const DkIcon( + leading: DkIcon( 'check', size: 16, - color: Color(0xFFFFFFFF), + color: t.onPrimary, strokeWidth: 2.4, ), child: const Text('완료'), @@ -513,11 +513,11 @@ class _FocusScreenState extends State { child: DkIcon( 'focus', size: 18, - color: i < _c.sessions - ? const Color(0xFFFFFFFF) - : t.fgDisabled, + color: i < _c.sessions ? t.onPrimary : t.fgDisabled, strokeWidth: 2, - fill: i < _c.sessions ? const Color(0x40FFFFFF) : null, + fill: i < _c.sessions + ? const Color(0x40FFFFFF) + : null, // token-exempt: 세션 도트 장식 채움 ), ), ), diff --git a/lib/screens/focus/skins.dart b/lib/screens/focus/skins.dart index 2fd129d..23781fd 100644 --- a/lib/screens/focus/skins.dart +++ b/lib/screens/focus/skins.dart @@ -321,13 +321,13 @@ class _FlipDigit extends StatelessWidget { ), child: Text( d, - style: const TextStyle( + style: TextStyle( fontFamily: 'WantedSans', fontSize: 50, fontWeight: FontWeight.w800, height: 1, fontFeatures: [FontFeature.tabularFigures()], - color: Color(0xFFFFFFFF), + color: t.onPrimary, ), ), ), @@ -335,7 +335,10 @@ class _FlipDigit extends StatelessWidget { Positioned( left: 0, right: 0, - child: Container(height: 1, color: const Color(0x59000000)), + child: Container( + height: 1, + color: const Color(0x59000000), + ), // token-exempt: 스킨 장식 구분선 ), // 하단 모드색 바. Positioned( diff --git a/lib/screens/login.dart b/lib/screens/login.dart index 37b80ea..e4c3908 100644 --- a/lib/screens/login.dart +++ b/lib/screens/login.dart @@ -131,8 +131,8 @@ class _LoginScreenState extends State { const _SocialSpec( SocialProvider.apple, 'Apple로 시작하기', - Color(0xFF000000), - Color(0xFFFFFFFF), + Color(0xFF000000), // token-exempt: Apple 브랜드 고정색 + Color(0xFFFFFFFF), // token-exempt: Apple 브랜드 고정색 brand: 'apple', ), ] @@ -373,7 +373,9 @@ class _SocialSpinnerState extends State<_SocialSpinner> /// 부분 호 스피너. 기본 흰색, [color]로 색을 지정할 수 있다. class _SpinnerPainter extends CustomPainter { - _SpinnerPainter({this.color = const Color(0xFFFFFFFF)}); + _SpinnerPainter({ + this.color = const Color(0xFFFFFFFF), + }); // token-exempt: const 기본값(primary 버튼 위 스피너) final Color color; diff --git a/lib/screens/me/me_screen.dart b/lib/screens/me/me_screen.dart index 6868506..2a92608 100644 --- a/lib/screens/me/me_screen.dart +++ b/lib/screens/me/me_screen.dart @@ -215,10 +215,10 @@ class MeScreen extends StatelessWidget { ), ], ), - child: const DkIcon( + child: DkIcon( 'user', size: 30, - color: Color(0xFFFFFFFF), + color: t.onPrimary, strokeWidth: 2, ), ), diff --git a/lib/screens/me/settings_section.dart b/lib/screens/me/settings_section.dart index 7ca9477..8fe7078 100644 --- a/lib/screens/me/settings_section.dart +++ b/lib/screens/me/settings_section.dart @@ -35,11 +35,11 @@ class DkToggle extends StatelessWidget { width: 24, height: 24, decoration: BoxDecoration( - color: const Color(0xFFFFFFFF), + color: t.onPrimary, shape: BoxShape.circle, boxShadow: const [ BoxShadow( - color: Color(0x40000000), + color: Color(0x40000000), // token-exempt: 토글 노브 그림자 blurRadius: 3, offset: Offset(0, 1), ), diff --git a/lib/screens/onboarding.dart b/lib/screens/onboarding.dart index b375dfe..b1ce9ff 100644 --- a/lib/screens/onboarding.dart +++ b/lib/screens/onboarding.dart @@ -180,18 +180,18 @@ class OnbArt extends StatelessWidget { fontFamily: 'Pretendard', fontSize: SeedType.label, fontWeight: FontWeight.w600, - color: const Color(0xFFFFFFFF).withValues(alpha: 0.7), + color: t.onPrimary.withValues(alpha: 0.7), ), ), const SizedBox(height: 2), - const Text( + Text( 'D-28', style: TextStyle( fontFamily: 'WantedSans', fontSize: 64, fontWeight: FontWeight.w800, letterSpacing: -3.2, - color: Color(0xFFFFFFFF), + color: t.onPrimary, ), ), ], @@ -303,13 +303,13 @@ class OnbArt extends StatelessWidget { borderRadius: BorderRadius.circular(20), boxShadow: t.shadows.s2, ), - child: const Column( + child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ DkIcon( 'repeat', size: 20, - color: Color(0xFFFFFFFF), + color: t.onPrimary, strokeWidth: 2.2, ), SizedBox(height: 4), @@ -320,7 +320,7 @@ class OnbArt extends StatelessWidget { fontFamily: 'Pretendard', fontSize: SeedType.caption, fontWeight: FontWeight.w700, - color: Color(0xFFFFFFFF), + color: t.onPrimary, ), ), ], @@ -426,7 +426,7 @@ class OnbArt extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - _providerMark(label, color), + _providerMark(t, label, color), const SizedBox(width: 5), Text( label, @@ -444,7 +444,7 @@ class OnbArt extends StatelessWidget { /// provider 브랜드 마크. 앱아이콘형(브랜드색 라운드 사각형 + 흰 이니셜). /// 공식 로고 자산이 없어 이니셜로 대체한다(이어서/G/A/N). - Widget _providerMark(String label, Color color) { + Widget _providerMark(DkTokens t, String label, Color color) { return Container( width: 15, height: 15, @@ -455,12 +455,12 @@ class OnbArt extends StatelessWidget { ), child: Text( label.substring(0, 1), - style: const TextStyle( + style: TextStyle( fontFamily: 'Pretendard', fontSize: 9, height: 1.0, fontWeight: FontWeight.w800, - color: Color(0xFFFFFFFF), + color: t.onPrimary, ), ), ); diff --git a/lib/screens/plan/calendar_screen.dart b/lib/screens/plan/calendar_screen.dart index 6e05e20..27fe3c5 100644 --- a/lib/screens/plan/calendar_screen.dart +++ b/lib/screens/plan/calendar_screen.dart @@ -456,7 +456,7 @@ class _MonthGrid extends StatelessWidget { fontWeight: on || isToday ? FontWeight.w700 : FontWeight.w500, letterSpacing: -0.3, color: on - ? const Color(0xFFFFFFFF) + ? t.onPrimary : isToday ? t.primary : dow == 0 diff --git a/lib/screens/plan/week_strip.dart b/lib/screens/plan/week_strip.dart index c31a257..affc449 100644 --- a/lib/screens/plan/week_strip.dart +++ b/lib/screens/plan/week_strip.dart @@ -85,7 +85,7 @@ class WeekStrip extends StatelessWidget { fontWeight: FontWeight.w700, letterSpacing: -0.3, color: on - ? const Color(0xFFFFFFFF) + ? t.onPrimary : isToday ? t.primary : t.fg, diff --git a/lib/screens/review/review_screen.dart b/lib/screens/review/review_screen.dart index 6797982..7257f42 100644 --- a/lib/screens/review/review_screen.dart +++ b/lib/screens/review/review_screen.dart @@ -88,7 +88,6 @@ class ReviewScreen extends StatelessWidget { } Widget _hero(DkTokens t, int pct) { - const Color white = Color(0xFFFFFFFF); return ClipRRect( borderRadius: BorderRadius.circular(t.radiusLg), child: Container( @@ -122,20 +121,22 @@ class ReviewScreen extends StatelessWidget { size: 108, stroke: 11, pct: pct.toDouble(), - color: white, - track: const Color(0x29FFFFFF), + color: t.onInk, + track: const Color( + 0x29FFFFFF, + ), // token-exempt: ink 위 링 트랙 장식 child: Column( mainAxisSize: MainAxisSize.min, children: [ DkCountUp( value: pct.toDouble(), suffix: '%', - style: const TextStyle( + style: TextStyle( fontFamily: 'WantedSans', fontSize: 27, fontWeight: FontWeight.w800, letterSpacing: -0.81, - color: white, + color: t.onInk, ), ), Text( @@ -156,7 +157,7 @@ class ReviewScreen extends StatelessWidget { children: [ _heroRow(t, '계획', review.planned, t.onInk, '개'), const SizedBox(height: 10), - _heroRow(t, '완료', review.done, white, '개'), + _heroRow(t, '완료', review.done, t.onInk, '개'), const SizedBox(height: 10), _heroRow(t, '밀린 시간', review.carried, t.warningFg, '시간'), ], diff --git a/lib/screens/sheets/date_picker_sheet.dart b/lib/screens/sheets/date_picker_sheet.dart index 0ea5d6b..05196bb 100644 --- a/lib/screens/sheets/date_picker_sheet.dart +++ b/lib/screens/sheets/date_picker_sheet.dart @@ -187,7 +187,7 @@ class _DatePickerBodyState extends State<_DatePickerBody> { fontWeight: isSelected || isToday ? FontWeight.w700 : FontWeight.w500, - color: isSelected ? const Color(0xFFFFFFFF) : dayColor, + color: isSelected ? t.onPrimary : dayColor, ), ), ), diff --git a/lib/screens/sheets/date_range_picker_sheet.dart b/lib/screens/sheets/date_range_picker_sheet.dart index 606a096..88400ce 100644 --- a/lib/screens/sheets/date_range_picker_sheet.dart +++ b/lib/screens/sheets/date_range_picker_sheet.dart @@ -264,7 +264,7 @@ class _DateRangePickerBodyState extends State<_DateRangePickerBody> { ? FontWeight.w700 : FontWeight.w500, color: isEndpoint - ? const Color(0xFFFFFFFF) + ? t.onPrimary : inRange ? t.fg : dayColor, diff --git a/lib/screens/sheets/event_sheet.dart b/lib/screens/sheets/event_sheet.dart index 7aa27af..563add1 100644 --- a/lib/screens/sheets/event_sheet.dart +++ b/lib/screens/sheets/event_sheet.dart @@ -289,7 +289,6 @@ class _EventSheetBodyState extends State { // 기간 이벤트는 토글 없이 마감 D-Day(큰 값) + 진행바를 한 화면에 통합 표시한다. final DkDdayInfo info = ddayInfo(ev); final DkHue h = t.hue(ev.color); - const Color white = Color(0xFFFFFFFF); // 기간(period)·레거시 진행률(progress) 모두 진행바를 노출한다. final bool showBar = _type == DkEventType.period || info.type == DkEventType.progress; @@ -339,20 +338,20 @@ class _EventSheetBodyState extends State { children: [ Text( '${ev.category} · ${eventDateLabel(ev)}', - style: const TextStyle( + style: TextStyle( fontFamily: 'Pretendard', fontSize: SeedType.label, - color: Color(0xB3FFFFFF), + color: t.onInkMuted, ), ), const SizedBox(height: 4), Text( ev.title, - style: const TextStyle( + style: TextStyle( fontFamily: 'Pretendard', fontSize: SeedType.title, fontWeight: FontWeight.w700, - color: white, + color: t.onInk, ), ), ], @@ -360,12 +359,12 @@ class _EventSheetBodyState extends State { ), Text( big, - style: const TextStyle( + style: TextStyle( fontFamily: 'WantedSans', fontSize: 40, fontWeight: FontWeight.w800, letterSpacing: -1.2, - color: white, + color: t.onInk, ), ), ], @@ -378,10 +377,12 @@ class _EventSheetBodyState extends State { height: 7, child: Stack( children: [ - Container(color: const Color(0x33FFFFFF)), + Container( + color: const Color(0x33FFFFFF), + ), // token-exempt: 장식 진행바 트랙 FractionallySizedBox( widthFactor: info.pct / 100, - child: Container(color: white), + child: Container(color: t.onInk), ), ], ), diff --git a/lib/screens/sheets/task_sheet.dart b/lib/screens/sheets/task_sheet.dart index 1c6cbbb..50b9794 100644 --- a/lib/screens/sheets/task_sheet.dart +++ b/lib/screens/sheets/task_sheet.dart @@ -441,7 +441,7 @@ class _TaskSheetBodyState extends State { leading: DkIcon( 'check', size: 19, - color: done ? t.fg : const Color(0xFFFFFFFF), + color: done ? t.fg : t.onPrimary, strokeWidth: 2.4, ), child: Text(done ? '완료 취소' : '완료 처리'), diff --git a/lib/theme/seed_components.dart b/lib/theme/seed_components.dart index 9f99354..f3da0b3 100644 --- a/lib/theme/seed_components.dart +++ b/lib/theme/seed_components.dart @@ -43,7 +43,7 @@ abstract final class SeedButton { }; static const Map variants = { - 'primary': SeedButtonVariant('primary', '#FFFFFF', 'transparent'), + 'primary': SeedButtonVariant('primary', 'onPrimary', 'transparent'), 'neutral': SeedButtonVariant('fg', 'bg', 'transparent'), 'outline': SeedButtonVariant('transparent', 'fg', 'border'), 'subtle': SeedButtonVariant('primarySubtle', 'primaryFg', 'transparent'), diff --git a/lib/theme/seed_tokens.dart b/lib/theme/seed_tokens.dart index 101abd4..770d93c 100644 --- a/lib/theme/seed_tokens.dart +++ b/lib/theme/seed_tokens.dart @@ -35,6 +35,7 @@ class SeedScheme { required this.ink, required this.onInk, required this.onInkMuted, + required this.onPrimary, required this.primary, required this.primaryHover, required this.primarySubtle, @@ -71,6 +72,7 @@ class SeedScheme { final Color ink; final Color onInk; final Color onInkMuted; + final Color onPrimary; final Color primary; final Color primaryHover; final Color primarySubtle; @@ -107,6 +109,7 @@ class SeedScheme { ink: Color(0xFF17181C), onInk: Color(0xF5FFFFFF), onInkMuted: Color(0x99FFFFFF), + onPrimary: Color(0xFFFFFFFF), primary: Color(0xFF0066FF), primaryHover: Color(0xFF005AE0), primarySubtle: Color(0xFFE3EEFF), @@ -144,6 +147,7 @@ class SeedScheme { ink: Color(0xFF000000), onInk: Color(0xF2FFFFFF), onInkMuted: Color(0x94FFFFFF), + onPrimary: Color(0xFFFFFFFF), primary: Color(0xFF0066FF), primaryHover: Color(0xFF005AE0), primarySubtle: Color(0xFF152F59), diff --git a/lib/theme/tokens.dart b/lib/theme/tokens.dart index a71af71..937497c 100644 --- a/lib/theme/tokens.dart +++ b/lib/theme/tokens.dart @@ -144,6 +144,7 @@ class DkTokens { required this.ink, required this.onInk, required this.onInkMuted, + required this.onPrimary, required this.shadows, required this.fontScale, required this.isDark, @@ -203,6 +204,9 @@ class DkTokens { final Color onInk; final Color onInkMuted; + /// 유채색/브랜드 표면(primary 버튼·선택 상태·스킨) 위 고정 흰색(DQ-15). + final Color onPrimary; + // ── 기타 ──────────────────────────────────────────────── final DkShadows shadows; final double fontScale; @@ -258,6 +262,7 @@ class DkTokens { ink: s.ink, onInk: s.onInk, onInkMuted: s.onInkMuted, + onPrimary: s.onPrimary, shadows: t.dark ? DkShadows.dark : DkShadows.light, fontScale: t.fontScale, isDark: t.dark, @@ -335,6 +340,8 @@ class DkTokens { return violetSubtle; case 'violetFg': return violetFg; + case 'onPrimary': + return onPrimary; default: if (key.startsWith('#')) { final String s = key.substring(1); diff --git a/lib/widgets/dk_badge.dart b/lib/widgets/dk_badge.dart index a4c7181..89a0f5d 100644 --- a/lib/widgets/dk_badge.dart +++ b/lib/widgets/dk_badge.dart @@ -33,7 +33,7 @@ class DkBadge extends StatelessWidget { final DkTokens t = DkTheme.of(context); final colors = dkToneColors(t, tone); final Color bg = solid ? colors.fg : colors.bg; - final Color fg = solid ? const Color(0xFFFFFFFF) : colors.fg; + final Color fg = solid ? t.onPrimary : colors.fg; return Container( padding: const EdgeInsets.symmetric( diff --git a/lib/widgets/dk_brand_mark.dart b/lib/widgets/dk_brand_mark.dart index cb37c2f..0b53ff7 100644 --- a/lib/widgets/dk_brand_mark.dart +++ b/lib/widgets/dk_brand_mark.dart @@ -1,3 +1,4 @@ +// token-exempt(파일 전체): 브랜드 마크 원색 정의 — 로고 스펙 고정값(테마 무관) import 'package:flutter/widgets.dart'; import 'package:flutter_svg/flutter_svg.dart'; diff --git a/lib/widgets/dk_empty.dart b/lib/widgets/dk_empty.dart index 94d29cf..dbcf122 100644 --- a/lib/widgets/dk_empty.dart +++ b/lib/widgets/dk_empty.dart @@ -74,10 +74,10 @@ class DkEmpty extends StatelessWidget { const SizedBox(height: 14), DkButton( onPressed: onCta, - leading: const DkIcon( + leading: DkIcon( 'plus', size: 18, - color: Color(0xFFFFFFFF), + color: t.onPrimary, strokeWidth: 2.2, ), child: Text(cta!), diff --git a/lib/widgets/dk_fab.dart b/lib/widgets/dk_fab.dart index 792a122..a4bca72 100644 --- a/lib/widgets/dk_fab.dart +++ b/lib/widgets/dk_fab.dart @@ -45,12 +45,7 @@ class _DkFabState extends State { ), ], ), - child: const DkIcon( - 'plus', - size: 28, - color: Color(0xFFFFFFFF), - strokeWidth: 2.4, - ), + child: DkIcon('plus', size: 28, color: t.onPrimary, strokeWidth: 2.4), ), ), ); diff --git a/lib/widgets/dk_logo.dart b/lib/widgets/dk_logo.dart index efba98f..cfdb6d3 100644 --- a/lib/widgets/dk_logo.dart +++ b/lib/widgets/dk_logo.dart @@ -54,7 +54,9 @@ class DkLogo extends StatelessWidget { fontSize: size * 0.62, fontWeight: FontWeight.w800, letterSpacing: -0.04 * size * 0.62, - color: light ? const Color(0xFFFFFFFF) : t.fgStrong, + color: light + ? const Color(0xFFFFFFFF) + : t.fgStrong, // token-exempt: 라이트 고정 로고 글리프 ), ), ], diff --git a/lib/widgets/dk_tab_bar.dart b/lib/widgets/dk_tab_bar.dart index 59a1ec9..3d2b5bc 100644 --- a/lib/widgets/dk_tab_bar.dart +++ b/lib/widgets/dk_tab_bar.dart @@ -161,10 +161,10 @@ class _CenterAddState extends State<_CenterAdd> { ), ], ), - child: const DkIcon( + child: DkIcon( 'plus', size: 26, - color: Color(0xFFFFFFFF), + color: t.onPrimary, strokeWidth: 2.4, ), ), diff --git a/test/theme_contrast_test.dart b/test/theme_contrast_test.dart index 9d3d78a..37897b7 100644 --- a/test/theme_contrast_test.dart +++ b/test/theme_contrast_test.dart @@ -80,6 +80,14 @@ void main() { expect(t.dangerFg, isNot(t.fg)); expect(t.primaryFg, isNot(t.fg)); }); + + test('onPrimary 는 primary 위 4.5:1 이상이고 byKey 로 해석된다(DQ-15)', () { + expect(t.byKey('onPrimary'), t.onPrimary); + expect( + _contrast(t.onPrimary, t.primary), + greaterThanOrEqualTo(4.5), + ); + }); }); } From 9e01ddf8346da5891d9dbd2bd27935c24b954bc9 Mon Sep 17 00:00:00 2001 From: pkdee Date: Sat, 4 Jul 2026 19:52:58 +0900 Subject: [PATCH 2/2] =?UTF-8?q?style:=20theme=5Fcontrast=5Ftest=20?= =?UTF-8?q?=ED=8F=AC=EB=A7=B7=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/theme_contrast_test.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/theme_contrast_test.dart b/test/theme_contrast_test.dart index 37897b7..a2d9cd6 100644 --- a/test/theme_contrast_test.dart +++ b/test/theme_contrast_test.dart @@ -83,10 +83,7 @@ void main() { test('onPrimary 는 primary 위 4.5:1 이상이고 byKey 로 해석된다(DQ-15)', () { expect(t.byKey('onPrimary'), t.onPrimary); - expect( - _contrast(t.onPrimary, t.primary), - greaterThanOrEqualTo(4.5), - ); + expect(_contrast(t.onPrimary, t.primary), greaterThanOrEqualTo(4.5)); }); }); }