전역 구조 리팩터링 및 테스트 확장
This commit is contained in:
208
lib/core/theme/superport_shad_theme.dart
Normal file
208
lib/core/theme/superport_shad_theme.dart
Normal file
@@ -0,0 +1,208 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
/// Superport UI에서 공통으로 사용하는 Shad 테마 정의.
|
||||
class SuperportShadTheme {
|
||||
const SuperportShadTheme._();
|
||||
|
||||
static const Color primaryColor = Color(0xFF1B4F87);
|
||||
static const Color successColor = Color(0xFF2E8B57);
|
||||
static const Color warningColor = Color(0xFFFFC107);
|
||||
static const Color dangerColor = Color(0xFFDC3545);
|
||||
static const Color infoColor = Color(0xFF17A2B8);
|
||||
|
||||
/// 라이트 모드용 Shad 테마를 반환한다.
|
||||
static ShadThemeData light() {
|
||||
return ShadThemeData(
|
||||
brightness: Brightness.light,
|
||||
colorScheme: ShadColorScheme(
|
||||
background: Color(0xFFFFFFFF),
|
||||
foreground: Color(0xFF09090B),
|
||||
card: Color(0xFFFFFFFF),
|
||||
cardForeground: Color(0xFF09090B),
|
||||
popover: Color(0xFFFFFFFF),
|
||||
popoverForeground: Color(0xFF09090B),
|
||||
primary: primaryColor,
|
||||
primaryForeground: Color(0xFFFAFAFA),
|
||||
secondary: Color(0xFFF4F4F5),
|
||||
secondaryForeground: Color(0xFF18181B),
|
||||
muted: Color(0xFFF4F4F5),
|
||||
mutedForeground: Color(0xFF71717A),
|
||||
accent: Color(0xFFF4F4F5),
|
||||
accentForeground: Color(0xFF18181B),
|
||||
destructive: Color(0xFFEF4444),
|
||||
destructiveForeground: Color(0xFFFAFAFA),
|
||||
border: Color(0xFFE4E4E7),
|
||||
input: Color(0xFFE4E4E7),
|
||||
ring: Color(0xFF18181B),
|
||||
selection: primaryColor,
|
||||
),
|
||||
textTheme: ShadTextTheme(
|
||||
h1: TextStyle(
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.5,
|
||||
height: 1.3,
|
||||
),
|
||||
h2: TextStyle(
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.5,
|
||||
height: 1.3,
|
||||
),
|
||||
h3: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.5,
|
||||
height: 1.3,
|
||||
),
|
||||
h4: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.5,
|
||||
height: 1.3,
|
||||
),
|
||||
p: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: -0.2,
|
||||
height: 1.6,
|
||||
),
|
||||
blockquote: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
table: TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
list: TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
lead: TextStyle(fontSize: 20, fontWeight: FontWeight.w400),
|
||||
large: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
|
||||
small: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
muted: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: -0.2,
|
||||
height: 1.6,
|
||||
),
|
||||
),
|
||||
radius: const BorderRadius.all(Radius.circular(8)),
|
||||
);
|
||||
}
|
||||
|
||||
/// 다크 모드용 Shad 테마를 반환한다.
|
||||
static ShadThemeData dark() {
|
||||
return ShadThemeData(
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: ShadColorScheme(
|
||||
background: Color(0xFF09090B),
|
||||
foreground: Color(0xFFFAFAFA),
|
||||
card: Color(0xFF09090B),
|
||||
cardForeground: Color(0xFFFAFAFA),
|
||||
popover: Color(0xFF09090B),
|
||||
popoverForeground: Color(0xFFFAFAFA),
|
||||
primary: primaryColor,
|
||||
primaryForeground: Color(0xFFFAFAFA),
|
||||
secondary: Color(0xFF27272A),
|
||||
secondaryForeground: Color(0xFFFAFAFA),
|
||||
muted: Color(0xFF27272A),
|
||||
mutedForeground: Color(0xFFA1A1AA),
|
||||
accent: Color(0xFF27272A),
|
||||
accentForeground: Color(0xFFFAFAFA),
|
||||
destructive: Color(0xFF7F1D1D),
|
||||
destructiveForeground: Color(0xFFFAFAFA),
|
||||
border: Color(0xFF27272A),
|
||||
input: Color(0xFF27272A),
|
||||
ring: Color(0xFFD4D4D8),
|
||||
selection: primaryColor,
|
||||
),
|
||||
textTheme: ShadTextTheme(
|
||||
h1: TextStyle(
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.5,
|
||||
height: 1.3,
|
||||
),
|
||||
h2: TextStyle(
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.5,
|
||||
height: 1.3,
|
||||
),
|
||||
h3: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.5,
|
||||
height: 1.3,
|
||||
),
|
||||
h4: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.5,
|
||||
height: 1.3,
|
||||
),
|
||||
p: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: -0.2,
|
||||
height: 1.6,
|
||||
),
|
||||
blockquote: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
table: TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
list: TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
lead: TextStyle(fontSize: 20, fontWeight: FontWeight.w400),
|
||||
large: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
|
||||
small: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
muted: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: -0.2,
|
||||
height: 1.6,
|
||||
),
|
||||
),
|
||||
radius: const BorderRadius.all(Radius.circular(8)),
|
||||
);
|
||||
}
|
||||
|
||||
/// 상태 텍스트 배경을 위한 데코레이션을 반환한다.
|
||||
static BoxDecoration statusDecoration(String status) {
|
||||
Color backgroundColor;
|
||||
Color borderColor;
|
||||
|
||||
switch (status.toLowerCase()) {
|
||||
case 'active':
|
||||
case 'success':
|
||||
backgroundColor = successColor.withValues(alpha: 0.1);
|
||||
borderColor = successColor;
|
||||
break;
|
||||
case 'warning':
|
||||
case 'pending':
|
||||
backgroundColor = warningColor.withValues(alpha: 0.1);
|
||||
borderColor = warningColor;
|
||||
break;
|
||||
case 'danger':
|
||||
case 'error':
|
||||
backgroundColor = dangerColor.withValues(alpha: 0.1);
|
||||
borderColor = dangerColor;
|
||||
break;
|
||||
case 'info':
|
||||
backgroundColor = infoColor.withValues(alpha: 0.1);
|
||||
borderColor = infoColor;
|
||||
break;
|
||||
case 'inactive':
|
||||
case 'disabled':
|
||||
default:
|
||||
backgroundColor = Colors.grey.withValues(alpha: 0.1);
|
||||
borderColor = Colors.grey;
|
||||
}
|
||||
|
||||
return BoxDecoration(
|
||||
color: backgroundColor,
|
||||
border: Border.all(color: borderColor, width: 1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
);
|
||||
}
|
||||
}
|
||||
47
lib/core/theme/theme_controller.dart
Normal file
47
lib/core/theme/theme_controller.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// 전역 테마 모드를 관리하는 컨트롤러.
|
||||
class ThemeController extends ChangeNotifier {
|
||||
ThemeController({ThemeMode initialMode = ThemeMode.system})
|
||||
: _mode = initialMode;
|
||||
|
||||
ThemeMode _mode;
|
||||
|
||||
ThemeMode get mode => _mode;
|
||||
|
||||
void update(ThemeMode mode) {
|
||||
if (_mode == mode) return;
|
||||
_mode = mode;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void cycle() {
|
||||
switch (_mode) {
|
||||
case ThemeMode.system:
|
||||
update(ThemeMode.light);
|
||||
break;
|
||||
case ThemeMode.light:
|
||||
update(ThemeMode.dark);
|
||||
break;
|
||||
case ThemeMode.dark:
|
||||
update(ThemeMode.system);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [ThemeController]를 하위 위젯에 제공하는 Inherited 위젯.
|
||||
class ThemeControllerScope extends InheritedNotifier<ThemeController> {
|
||||
const ThemeControllerScope({
|
||||
super.key,
|
||||
required ThemeController controller,
|
||||
required super.child,
|
||||
}) : super(notifier: controller);
|
||||
|
||||
static ThemeController of(BuildContext context) {
|
||||
final scope = context
|
||||
.dependOnInheritedWidgetOfExactType<ThemeControllerScope>();
|
||||
assert(scope != null, 'ThemeControllerScope가 위젯 트리에 없습니다.');
|
||||
return scope!.notifier!;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user