Prepare product layout and reusable Flutter theme
Relocate the existing Astro website and preserve its assets and hosting configuration. Add the consumer/backend scaffold and themed Flutter package with consumer tests. Related to #1.
This commit is contained in:
1 parent
ef65a40213
commit
d9318053e8
46 files changed
+703
-1
No files matched your search
@@ -0,0 +1,19 @@
|
||||
// Brand snapshot from website/design/tokens.json (version 1).
|
||||
// Keep these values aligned when deliberately updating the package's brand.
|
||||
import 'dart:ui';
|
||||
|
||||
abstract final class SxchngeBrand {
|
||||
static const ink = Color(0xFF14221D);
|
||||
static const mint = Color(0xFFC4F9CE);
|
||||
static const lime = Color(0xFFD7FF5F);
|
||||
static const paper = Color(0xFFF9FAF6);
|
||||
static const muted = Color(0xFF637168);
|
||||
static const line = Color(0xFFD9E0D8);
|
||||
static const forest = Color(0xFF396A4B);
|
||||
static const focus = Color(0xFF168850);
|
||||
static const darkField = Color(0xFF25372E);
|
||||
static const darkReceive = Color(0xFF203D2D);
|
||||
static const sageSurface = Color(0xFFEAF0E6);
|
||||
static const blueSurface = Color(0xFFEDF0F9);
|
||||
static const fontFamily = 'Onest';
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'brand.dart';
|
||||
|
||||
/// Install with `MaterialApp(theme: SxchngeTheme.light(),
|
||||
/// darkTheme: SxchngeTheme.dark())`.
|
||||
abstract final class SxchngeTheme {
|
||||
static const packageName = 'sxchnge_theme';
|
||||
|
||||
static ThemeData light() => _build(Brightness.light);
|
||||
static ThemeData dark() => _build(Brightness.dark);
|
||||
|
||||
static ThemeData _build(Brightness brightness) {
|
||||
final dark = brightness == Brightness.dark;
|
||||
// Seed supplies complete Material roles; explicit overrides preserve brand
|
||||
// colors and readable foregrounds rather than approximating the palette.
|
||||
final scheme =
|
||||
ColorScheme.fromSeed(
|
||||
seedColor: SxchngeBrand.forest,
|
||||
brightness: brightness,
|
||||
).copyWith(
|
||||
primary: dark ? SxchngeBrand.mint : SxchngeBrand.ink,
|
||||
onPrimary: dark ? SxchngeBrand.ink : SxchngeBrand.mint,
|
||||
primaryContainer: SxchngeBrand.mint,
|
||||
onPrimaryContainer: SxchngeBrand.ink,
|
||||
secondary: dark ? SxchngeBrand.mint : SxchngeBrand.forest,
|
||||
onSecondary: dark ? SxchngeBrand.ink : SxchngeBrand.paper,
|
||||
secondaryContainer: dark
|
||||
? SxchngeBrand.darkReceive
|
||||
: SxchngeBrand.sageSurface,
|
||||
onSecondaryContainer: dark ? SxchngeBrand.mint : SxchngeBrand.ink,
|
||||
tertiary: dark ? SxchngeBrand.lime : SxchngeBrand.forest,
|
||||
onTertiary: dark ? SxchngeBrand.ink : SxchngeBrand.paper,
|
||||
tertiaryContainer: SxchngeBrand.lime,
|
||||
onTertiaryContainer: SxchngeBrand.ink,
|
||||
surface: dark ? SxchngeBrand.ink : SxchngeBrand.paper,
|
||||
onSurface: dark ? SxchngeBrand.paper : SxchngeBrand.ink,
|
||||
onSurfaceVariant: dark ? const Color(0xFFB6C6B9) : SxchngeBrand.muted,
|
||||
surfaceContainerLowest: dark ? const Color(0xFF101B17) : Colors.white,
|
||||
surfaceContainerLow: dark
|
||||
? const Color(0xFF1B2B23)
|
||||
: SxchngeBrand.paper,
|
||||
surfaceContainer: dark
|
||||
? SxchngeBrand.darkField
|
||||
: SxchngeBrand.sageSurface,
|
||||
surfaceContainerHigh: dark
|
||||
? SxchngeBrand.darkReceive
|
||||
: SxchngeBrand.line,
|
||||
surfaceContainerHighest: dark
|
||||
? const Color(0xFF324A3C)
|
||||
: const Color(0xFFCFD9CE),
|
||||
outline: dark ? const Color(0xFF8CA291) : SxchngeBrand.muted,
|
||||
outlineVariant: dark ? const Color(0xFF405A48) : SxchngeBrand.line,
|
||||
inverseSurface: dark ? SxchngeBrand.paper : SxchngeBrand.ink,
|
||||
onInverseSurface: dark ? SxchngeBrand.ink : SxchngeBrand.paper,
|
||||
inversePrimary: dark ? SxchngeBrand.forest : SxchngeBrand.mint,
|
||||
surfaceTint: Colors.transparent,
|
||||
);
|
||||
final base = ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: scheme,
|
||||
fontFamily: SxchngeBrand.fontFamily,
|
||||
package: packageName,
|
||||
scaffoldBackgroundColor: scheme.surface,
|
||||
visualDensity: VisualDensity.standard,
|
||||
materialTapTargetSize: MaterialTapTargetSize.padded,
|
||||
);
|
||||
final text = base.textTheme.copyWith(
|
||||
displayLarge: base.textTheme.displayLarge!.copyWith(
|
||||
fontSize: 48,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -2,
|
||||
height: 1.1,
|
||||
),
|
||||
displayMedium: base.textTheme.displayMedium!.copyWith(
|
||||
fontSize: 40,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -1.5,
|
||||
height: 1.15,
|
||||
),
|
||||
displaySmall: base.textTheme.displaySmall!.copyWith(
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -1,
|
||||
height: 1.2,
|
||||
),
|
||||
headlineLarge: base.textTheme.headlineLarge!.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -1,
|
||||
),
|
||||
headlineMedium: base.textTheme.headlineMedium!.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.5,
|
||||
),
|
||||
headlineSmall: base.textTheme.headlineSmall!.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
bodyLarge: base.textTheme.bodyLarge!.copyWith(fontSize: 16, height: 1.65),
|
||||
bodyMedium: base.textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 16,
|
||||
height: 1.5,
|
||||
),
|
||||
bodySmall: base.textTheme.bodySmall!.copyWith(fontSize: 14, height: 1.5),
|
||||
labelLarge: base.textTheme.labelLarge!.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
labelMedium: base.textTheme.labelMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
labelSmall: base.textTheme.labelSmall!.copyWith(fontSize: 14),
|
||||
);
|
||||
final border = OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderSide: BorderSide(color: scheme.outline),
|
||||
);
|
||||
return base.copyWith(
|
||||
textTheme: text,
|
||||
appBarTheme: AppBarThemeData(
|
||||
backgroundColor: scheme.surface,
|
||||
foregroundColor: scheme.onSurface,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
centerTitle: false,
|
||||
titleTextStyle: text.titleLarge,
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
color: scheme.surfaceContainer,
|
||||
elevation: 0,
|
||||
margin: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
||||
),
|
||||
filledButtonTheme: FilledButtonThemeData(
|
||||
style: FilledButton.styleFrom(
|
||||
minimumSize: const Size(64, 48),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
|
||||
shape: const StadiumBorder(),
|
||||
textStyle: text.labelLarge,
|
||||
),
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(64, 48),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
|
||||
shape: const StadiumBorder(),
|
||||
textStyle: text.labelLarge,
|
||||
elevation: 0,
|
||||
),
|
||||
),
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
style: OutlinedButton.styleFrom(
|
||||
minimumSize: const Size(64, 48),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
|
||||
shape: const StadiumBorder(),
|
||||
textStyle: text.labelLarge,
|
||||
),
|
||||
),
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
minimumSize: const Size(48, 48),
|
||||
textStyle: text.labelLarge,
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationThemeData(
|
||||
filled: true,
|
||||
fillColor: scheme.surfaceContainerLowest,
|
||||
contentPadding: const EdgeInsets.all(16),
|
||||
border: border,
|
||||
enabledBorder: border,
|
||||
focusedBorder: border.copyWith(
|
||||
borderSide: BorderSide(
|
||||
color: dark ? SxchngeBrand.mint : SxchngeBrand.focus,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
errorBorder: border.copyWith(
|
||||
borderSide: BorderSide(color: scheme.error),
|
||||
),
|
||||
focusedErrorBorder: border.copyWith(
|
||||
borderSide: BorderSide(color: scheme.error, width: 2),
|
||||
),
|
||||
),
|
||||
dialogTheme: DialogThemeData(
|
||||
backgroundColor: scheme.surface,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
||||
),
|
||||
dividerTheme: DividerThemeData(
|
||||
color: scheme.outlineVariant,
|
||||
thickness: 1,
|
||||
),
|
||||
navigationBarTheme: NavigationBarThemeData(
|
||||
backgroundColor: scheme.surface,
|
||||
indicatorColor: scheme.primaryContainer,
|
||||
),
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
cursorColor: scheme.primary,
|
||||
selectionColor: SxchngeBrand.lime.withValues(alpha: dark ? 0.3 : 0.5),
|
||||
selectionHandleColor: scheme.primary,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/// SXCHNGE brand colors, typography, and Material themes.
|
||||
library;
|
||||
|
||||
export 'src/brand.dart';
|
||||
export 'src/theme.dart';
|
||||
Reference in new issue
Block a user