Move apps to mobile directory structure

Relocated all app directories (client, design_system_viewer, staff) and their contents under the new 'apps/mobile' path. This change improves project organization and prepares for future platform-specific structuring.
This commit is contained in:
Achintha Isuru
2026-01-22 10:17:19 -05:00
parent 2f992ae5fa
commit cf59935ec8
982 changed files with 3 additions and 2532 deletions

View File

@@ -0,0 +1,31 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.flutter-plugins-dependencies
/build/
/coverage/

View File

@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "3b62efc2a3da49882f43c372e0bc53daef7295a6"
channel: "stable"
project_type: package

View File

@@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

View File

@@ -0,0 +1 @@
TODO: Add your license here.

View File

@@ -0,0 +1,39 @@
<!--
This README describes the package. If you publish this package to pub.dev,
this README's contents appear on the landing page for your package.
For information about how to write a good package README, see the guide for
[writing package pages](https://dart.dev/tools/pub/writing-package-pages).
For general information about developing packages, see the Dart guide for
[creating packages](https://dart.dev/guides/libraries/create-packages)
and the Flutter guide for
[developing packages and plugins](https://flutter.dev/to/develop-packages).
-->
TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.
## Features
TODO: List what your package can do. Maybe include images, gifs, or videos.
## Getting started
TODO: List prerequisites and provide or point to information on how to
start using the package.
## Usage
TODO: Include short and useful examples for package users. Add longer examples
to `/example` folder.
```dart
const like = 'sample';
```
## Additional information
TODO: Tell users more about the package: where to find more information, how to
contribute to the package, how to file issues, what response they can expect
from the package authors, and more.

View File

@@ -0,0 +1 @@
include: ../../analytics_options.yaml

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -0,0 +1,12 @@
export 'src/ui_colors.dart';
export 'src/ui_typography.dart';
export 'src/ui_constants.dart';
export 'src/ui_theme.dart';
export 'src/ui_icons.dart';
export 'src/ui_images_assets.dart';
export 'src/widgets/ui_app_bar.dart';
export 'src/widgets/ui_text_field.dart';
export 'src/widgets/ui_step_indicator.dart';
export 'src/widgets/ui_icon_button.dart';
export 'src/widgets/ui_button.dart';
export 'src/widgets/ui_chip.dart';

View File

@@ -0,0 +1,322 @@
import 'package:flutter/material.dart';
/// Static definitions of color palettes and semantic colors for the Staff Design System.
/// Values are defined in design_tokens_react.md.
class UiColors {
UiColors._();
// ---------------------------------------------------------------------------
// 1. Base Tokens
// ---------------------------------------------------------------------------
/// Background color (#FAFBFC)
static const Color background = Color(0xFFFAFBFC);
/// Foreground color (#121826)
static const Color foreground = Color(0xFF121826);
/// Primary brand color blue (#0A39DF)
static const Color primary = Color(0xFF0A39DF);
/// Foreground color on primary background (#F7FAFC)
static const Color primaryForeground = Color(0xFFF7FAFC);
/// Inverse primary color (#9FABF1)
static const Color primaryInverse = Color(0xFF9FABF1);
/// Secondary background color (#F1F3F5)
static const Color secondary = Color(0xFFF1F3F5);
/// Foreground color on secondary background (#121826)
static const Color secondaryForeground = Color(0xFF121826);
/// Muted background color (#F1F3F5)
static const Color muted = Color(0xFFF1F3F5);
/// Muted foreground color (#6A7382)
static const Color mutedForeground = Color(0xFF6A7382);
/// Accent yellow color (#F9E547)
static const Color accent = Color(0xFFF9E547);
/// Foreground color on accent background (#4C460D)
static const Color accentForeground = Color(0xFF4C460D);
/// Destructive red color (#F04444)
static const Color destructive = Color(0xFFF04444);
/// Foreground color on destructive background (#FAFAFA)
static const Color destructiveForeground = Color(0xFFFAFAFA);
/// Default border color (#D1D5DB)
static const Color border = Color(0xFFD1D5DB);
/// Default input border color (#E7EAEE)
static const Color input = Color(0xFFF5F6F8);
/// Focus ring color (#0A39DF)
static const Color ring = Color(0xFF0A39DF);
// ---------------------------------------------------------------------------
// 2. Semantic Mappings
// ---------------------------------------------------------------------------
// --- Background Colors ---
/// Primary background (#FAFBFC)
static const Color bgPrimary = background;
/// Secondary background (#F1F3F5)
static const Color bgSecondary = secondary;
/// Tertiary background (#EDF0F2)
static const Color bgThird = Color(0xFFEDF0F2);
/// Popup background (#FFFFFF)
static const Color bgPopup = Color(0xFFFFFFFF);
/// Highlighted background (#FEF9C3)
static const Color bgHighlight = Color(0xFFFEF9C3);
/// Menu background (#F8FAFC)
static const Color bgMenu = Color(0xFFF8FAFC);
/// Banner background (#FFFFFF)
static const Color bgBanner = Color(0xFFFFFFFF);
/// Overlay background (#000000 with 50% opacity)
static const Color bgOverlay = Color(0x80000000);
/// Toast background (#121826)
static const Color toastBg = Color(0xFF121826);
/// Input field background (#E3E6E9)
static const Color bgInputField = input;
/// Footer banner background (#F1F5F9)
static const Color bgFooterBanner = Color(0xFFF1F5F9);
// --- Text Colors ---
/// Primary text (#121826)
static const Color textPrimary = foreground;
/// Secondary text (#6A7382)
static const Color textSecondary = mutedForeground;
/// Inactive text (#9CA3AF)
static const Color textInactive = Color(0xFF9CA3AF);
/// Placeholder text (#9CA3AF)
static const Color textPlaceholder = Color(0xFF9CA3AF);
/// Description text (#6A7382)
static const Color textDescription = mutedForeground;
/// Success text (#10B981)
static const Color textSuccess = Color(0xFF10B981);
/// Error text (#F04444)
static const Color textError = destructive;
/// Deep error text for containers (#450A0A)
static const Color textErrorContainer = Color(0xFF450A0A);
/// Warning text (#D97706)
static const Color textWarning = Color(0xFFD97706);
/// Link text (#0A39DF)
static const Color textLink = primary;
/// Filter text (#4B5563)
static const Color textFilter = Color(0xFF4B5563);
// --- Icon Colors ---
/// Primary icon (#121826)
static const Color iconPrimary = foreground;
/// Secondary icon (#6A7382)
static const Color iconSecondary = mutedForeground;
/// Tertiary icon (#9CA3AF)
static const Color iconThird = Color(0xFF9CA3AF);
/// Inactive icon (#D1D5DB)
static const Color iconInactive = Color(0xFFD1D5DB);
/// Active icon (#0A39DF)
static const Color iconActive = primary;
/// Success icon (#10B981)
static const Color iconSuccess = Color(0xFF10B981);
/// Error icon (#F04444)
static const Color iconError = destructive;
// --- Loader Colors ---
/// Active loader (#0A39DF)
static const Color loaderActive = primary;
/// Inactive loader (#E2E8F0)
static const Color loaderInactive = Color(0xFFE2E8F0);
// --- Pin Input Colors ---
/// Unfilled pin (#E2E8F0)
static const Color pinUnfilled = Color(0xFFE2E8F0);
/// Active pin (#0A39DF)
static const Color pinActive = primary;
/// Inactive pin (#94A3B8)
static const Color pinInactive = Color(0xFF94A3B8);
// --- Separator Colors ---
/// Primary separator (#E3E6E9)
static const Color separatorPrimary = border;
/// Secondary separator (#F1F5F9)
static const Color separatorSecondary = Color(0xFFF1F5F9);
/// Special separator (#F9E547)
static const Color separatorSpecial = accent;
// --- Tag Colors ---
/// Default tag background (#F1F5F9)
static const Color tagValue = Color(0xFFF1F5F9);
/// Pending state tag background (#FEF3C7)
static const Color tagPending = Color(0xFFFEF3C7);
/// In-progress state tag background (#DBEAFE)
static const Color tagInProgress = Color(0xFFDBEAFE);
/// Error state tag background (#FEE2E2)
static const Color tagError = Color(0xFFFEE2E2);
/// Active state tag background (#DCFCE7)
static const Color tagActive = Color(0xFFDCFCE7);
/// Frozen state tag background (#F3F4F6)
static const Color tagFreeze = Color(0xFFF3F4F6);
/// Success state tag background (#DCFCE7)
static const Color tagSuccess = Color(0xFFDCFCE7);
/// Refunded state tag background (#E0E7FF)
static const Color tagRefunded = Color(0xFFE0E7FF);
// --- Border Colors ---
/// Static border (#D1D5DB)
static const Color borderStill = border;
/// Primary border (#D1D5DB)
static const Color borderPrimary = border;
/// Error border (#F04444)
static const Color borderError = destructive;
/// Focus border (#0A39DF)
static const Color borderFocus = ring;
/// Inactive border (#F1F5F9)
static const Color borderInactive = Color(0xFFF1F5F9);
// --- Button Colors ---
/// Primary button default (#0A39DF)
static const Color buttonPrimaryStill = primary;
/// Primary button hover (#082EB2)
static const Color buttonPrimaryHover = Color(0xFF082EB2);
/// Primary button inactive (#F1F3F5)
static const Color buttonPrimaryInactive = secondary;
/// Secondary button default (#F1F3F5)
static const Color buttonSecondaryStill = secondary;
/// Secondary button hover (#E2E8F0)
static const Color buttonSecondaryHover = Color(0xFFE2E8F0);
/// Secondary button inactive (#F3F4F6)
static const Color buttonSecondaryInactive = Color(0xFFF3F4F6);
/// Button inactive state (#94A3B8)
static const Color buttonInactive = Color(0xFF94A3B8);
/// Pin button background (#F8FAFC)
static const Color pinButtonBackground = Color(0xFFF8FAFC);
// --- Switch Colors ---
/// Switch active state (#10B981)
static const Color switchActive = Color(0xFF10B981);
/// Switch inactive state (#CBD5E1)
static const Color switchInactive = Color(0xFFCBD5E1);
/// Switch dot inactive state (#FFFFFF)
static const Color dotInactive = Color(0xFFFFFFFF);
// --- Basic Colors ---
/// Standard white (#FFFFFF)
static const Color white = Color(0xFFFFFFFF);
/// Standard black (#000000)
static const Color black = Color(0xFF000000);
/// Transparent color (0x00000000)
static const Color transparent = Color(0x00000000);
/// Card background (#FFFFFF)
static const Color cardViewBackground = Color(0xFFFFFFFF);
// --- Shadows ---
/// Primary popup shadow (#000000 with 10% opacity)
static const Color popupShadow = Color(0x1A000000);
// ---------------------------------------------------------------------------
// 3. ColorScheme
// ---------------------------------------------------------------------------
/// Generates a ColorScheme based on the tokens.
static ColorScheme get colorScheme => const ColorScheme(
brightness: Brightness.light,
primary: primary,
onPrimary: primaryForeground,
primaryContainer: tagRefunded,
onPrimaryContainer: primary,
secondary: secondary,
onSecondary: secondaryForeground,
secondaryContainer: muted,
onSecondaryContainer: secondaryForeground,
tertiary: accent,
onTertiary: accentForeground,
tertiaryContainer: bgHighlight,
onTertiaryContainer: accentForeground,
error: destructive,
onError: destructiveForeground,
errorContainer: tagError,
onErrorContainer: textErrorContainer,
surface: background,
onSurface: foreground,
surfaceContainerHighest: muted,
onSurfaceVariant: mutedForeground,
outline: border,
outlineVariant: separatorSecondary,
shadow: black,
scrim: black,
inverseSurface: foreground,
onInverseSurface: background,
inversePrimary: primaryInverse,
surfaceTint: primary,
);
}

View File

@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
/// Design system constants for spacing, radii, and other layout properties.
class UiConstants {
UiConstants._();
// --- Border Radii ---
/// Base radius: 12px
static const double radiusBase = 12.0;
static final BorderRadius radiusLg = BorderRadius.circular(radiusBase);
/// Medium radius: 6px
static const double radiusMdValue = 6.0;
static final BorderRadius radiusMd = BorderRadius.circular(radiusMdValue);
/// Small radius: 4px
static final BorderRadius radiusSm = BorderRadius.circular(4.0);
/// Extra small radius: 2px
static final BorderRadius radiusXs = BorderRadius.circular(2.0);
/// Large/Full radius
static final BorderRadius radiusFull = BorderRadius.circular(999.0);
// --- Spacing ---
static const double space0 = 0.0;
static const double space1 = 4.0;
static const double space2 = 8.0;
static const double space3 = 12.0;
static const double space4 = 16.0;
static const double space5 = 20.0;
static const double space6 = 24.0;
static const double space8 = 32.0;
static const double space10 = 40.0;
static const double space12 = 48.0;
}

View File

@@ -0,0 +1,177 @@
import 'package:flutter/widgets.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:lucide_icons/lucide_icons.dart';
/// The primary icon library used by the design system.
/// This allows for easier swapping of icon libraries in the future.
typedef _IconLib = LucideIcons;
/// The secondary icon library used by the design system.
/// This allows for easier swapping of icon libraries in the future.
typedef _IconLib2 = FontAwesomeIcons;
/// Static definitions of icons for the UI design system.
/// This class wraps the primary icon library to provide a consistent interface.
///
/// example:
/// ```dart
/// Icon(UiIcons.home)
/// ```
class UiIcons {
UiIcons._();
// --- Navigation ---
/// Home icon
static const IconData home = _IconLib.home;
/// Calendar icon for shifts or schedules
static const IconData calendar = _IconLib.calendar;
/// Briefcase icon for jobs
static const IconData briefcase = _IconLib.briefcase;
/// User icon for profile
static const IconData user = _IconLib.user;
/// Settings icon
static const IconData settings = _IconLib.settings;
// --- Actions ---
/// Search icon
static const IconData search = _IconLib.search;
/// Filter icon
static const IconData filter = _IconLib.filter;
/// Plus/Add icon
static const IconData add = _IconLib.plus;
/// Edit icon
static const IconData edit = _IconLib.edit2;
/// Delete/Trash icon
static const IconData delete = _IconLib.trash2;
/// Checkmark icon
static const IconData check = _IconLib.check;
/// X/Cancel icon
static const IconData close = _IconLib.x;
/// Arrow right icon
static const IconData arrowRight = _IconLib.arrowRight;
/// Arrow left icon
static const IconData arrowLeft = _IconLib.arrowLeft;
/// Swap/Transfer icon
static const IconData swap = _IconLib.arrowLeftRight;
/// Chevron right icon
static const IconData chevronRight = _IconLib.chevronRight;
/// Chevron left icon
static const IconData chevronLeft = _IconLib.chevronLeft;
// --- Status & Feedback ---
/// Info icon
static const IconData info = _IconLib.info;
/// Help/Circle icon
static const IconData help = _IconLib.helpCircle;
/// Alert/Triangle icon for warnings
static const IconData warning = _IconLib.alertTriangle;
/// Alert/Circle icon for errors
static const IconData error = _IconLib.alertCircle;
/// Success/Check circle icon
static const IconData success = _IconLib.checkCircle2;
// --- Miscellaneous ---
/// Clock icon
static const IconData clock = _IconLib.clock;
/// Log in icon
static const IconData logIn = _IconLib.logIn;
/// Break icon (Coffee)
static const IconData breakIcon = _IconLib.coffee;
/// Map pin icon for locations
static const IconData mapPin = _IconLib.mapPin;
/// Dollar sign icon for payments/earnings
static const IconData dollar = _IconLib.dollarSign;
/// Wallet icon
static const IconData wallet = _IconLib.wallet;
/// Credit card icon
static const IconData creditCard = _IconLib.creditCard;
/// Bell icon for notifications
static const IconData bell = _IconLib.bell;
/// Log out icon
static const IconData logOut = _IconLib.logOut;
/// File/Document icon
static const IconData file = _IconLib.fileText;
/// Lock icon
static const IconData lock = _IconLib.lock;
/// Shield check icon for compliance/security
static const IconData shield = _IconLib.shieldCheck;
/// Sparkles icon for features or AI
static const IconData sparkles = _IconLib.sparkles;
/// Star icon for ratings
static const IconData star = _IconLib.star;
/// Camera icon for photo upload
static const IconData camera = _IconLib.camera;
/// Mail icon
static const IconData mail = _IconLib.mail;
/// Eye icon for visibility
static const IconData eye = _IconLib.eye;
/// Eye off icon for hidden visibility
static const IconData eyeOff = _IconLib.eyeOff;
/// Building icon for companies
static const IconData building = _IconLib.building2;
/// Zap icon for rapid actions
static const IconData zap = _IconLib.zap;
/// Grip vertical icon for reordering
static const IconData gripVertical = _IconLib.gripVertical;
/// Trending down icon for insights
static const IconData trendingDown = _IconLib.trendingDown;
/// Target icon for metrics
static const IconData target = _IconLib.target;
/// Rotate CCW icon for reordering
static const IconData rotateCcw = _IconLib.rotateCcw;
/// Apple icon
static const IconData apple = _IconLib2.apple;
/// Google icon
static const IconData google = _IconLib2.google;
/// NFC icon
static const IconData nfc = _IconLib.nfc;
}

View File

@@ -0,0 +1,14 @@
/// Static definitions of image asset paths for the Design System.
///
/// This class provides a centralized way to access image assets
/// stored within the `design_system` package.
class UiImageAssets {
UiImageAssets._();
/// The path to the yellow version of the logo image.
static const String logoYellow =
'packages/design_system/assets/logo-yellow.png';
/// The path to the blue version of the logo image.
static const String logoBlue = 'packages/design_system/assets/logo-blue.png';
}

View File

@@ -0,0 +1,359 @@
import 'package:flutter/material.dart';
import 'ui_colors.dart';
import 'ui_typography.dart';
import 'ui_constants.dart';
/// The main entry point for the Staff Design System theme.
/// Assembles colors, typography, and constants into a comprehensive Material 3 theme.
///
/// Adheres to the tokens defined in design_tokens_react.md.
class UiTheme {
UiTheme._();
/// Returns the light theme for the Staff application.
static ThemeData get light {
final colorScheme = UiColors.colorScheme;
final textTheme = UiTypography.textTheme;
return ThemeData(
useMaterial3: true,
colorScheme: colorScheme,
scaffoldBackgroundColor: UiColors.background,
primaryColor: UiColors.primary,
canvasColor: UiColors.background,
// Typography
textTheme: textTheme,
// Icon Theme
iconTheme: const IconThemeData(color: UiColors.iconPrimary, size: 24),
// Text Selection Theme
textSelectionTheme: const TextSelectionThemeData(
cursorColor: UiColors.primary,
selectionColor: UiColors.primaryInverse,
selectionHandleColor: UiColors.primary,
),
// Divider Theme
dividerTheme: const DividerThemeData(
color: UiColors.separatorPrimary,
space: 1,
thickness: 1,
),
// Card Theme
cardTheme: CardThemeData(
color: UiColors.white,
elevation: 2,
shadowColor: UiColors.popupShadow,
shape: RoundedRectangleBorder(
borderRadius: UiConstants.radiusLg,
side: const BorderSide(color: UiColors.borderStill),
),
margin: EdgeInsets.zero,
),
// Elevated Button Theme (Primary)
elevatedButtonTheme: ElevatedButtonThemeData(
style:
ElevatedButton.styleFrom(
elevation: 0,
backgroundColor: UiColors.buttonPrimaryStill,
foregroundColor: UiColors.primaryForeground,
disabledBackgroundColor: UiColors.buttonPrimaryInactive,
textStyle: UiTypography.buttonXL,
shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusLg),
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space6,
vertical: UiConstants.space3,
),
minimumSize: const Size(double.infinity, 54),
maximumSize: const Size(double.infinity, 54),
).copyWith(
side: WidgetStateProperty.resolveWith<BorderSide?>((states) {
if (states.contains(WidgetState.disabled)) {
return const BorderSide(
color: UiColors.borderPrimary,
width: 0.5,
);
}
return null;
}),
overlayColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.hovered))
return UiColors.buttonPrimaryHover;
return null;
}),
),
),
// Text Button Theme
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: UiColors.textPrimary,
disabledForegroundColor: UiColors.textInactive,
textStyle: UiTypography.buttonXL,
shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusLg),
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
vertical: UiConstants.space2,
),
minimumSize: const Size(double.infinity, 52),
maximumSize: const Size(double.infinity, 52),
),
),
// Outlined Button Theme (Secondary)
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
elevation: 0,
backgroundColor: UiColors.buttonSecondaryStill,
foregroundColor: UiColors.primary,
side: const BorderSide(color: UiColors.borderFocus, width: 0.5),
textStyle: UiTypography.buttonXL,
shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusLg),
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
vertical: UiConstants.space3,
),
minimumSize: const Size(double.infinity, 52),
maximumSize: const Size(double.infinity, 52),
),
),
// Icon Button Theme
iconButtonTheme: IconButtonThemeData(
style: IconButton.styleFrom(
foregroundColor: UiColors.iconPrimary,
disabledForegroundColor: UiColors.iconInactive,
shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusFull),
),
),
// Floating Action Button Theme
floatingActionButtonTheme: const FloatingActionButtonThemeData(
backgroundColor: UiColors.primary,
foregroundColor: UiColors.primaryForeground,
elevation: 4,
shape: CircleBorder(),
),
// Tab Bar Theme
tabBarTheme: TabBarThemeData(
labelColor: UiColors.primary,
unselectedLabelColor: UiColors.textSecondary,
labelStyle: UiTypography.buttonM,
unselectedLabelStyle: UiTypography.buttonM,
indicatorSize: TabBarIndicatorSize.label,
indicator: const UnderlineTabIndicator(
borderSide: BorderSide(color: UiColors.primary, width: 2),
),
),
// Input Theme
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: UiColors.bgInputField,
hintStyle: UiTypography.body2r.textPlaceholder,
labelStyle: UiTypography.body4r.textPrimary,
errorStyle: UiTypography.footnote1r.textError,
contentPadding: const EdgeInsets.symmetric(
horizontal: UiConstants.space3,
vertical: UiConstants.space3,
),
border: OutlineInputBorder(
borderRadius: UiConstants.radiusMd,
borderSide: const BorderSide(color: UiColors.borderStill),
),
enabledBorder: OutlineInputBorder(
borderRadius: UiConstants.radiusMd,
borderSide: const BorderSide(color: UiColors.borderStill),
),
focusedBorder: OutlineInputBorder(
borderRadius: UiConstants.radiusMd,
borderSide: const BorderSide(
color: UiColors.borderFocus,
width: 0.75,
),
),
errorBorder: OutlineInputBorder(
borderRadius: UiConstants.radiusMd,
borderSide: const BorderSide(color: UiColors.textError),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: UiConstants.radiusMd,
borderSide: const BorderSide(color: UiColors.textError, width: 1),
),
),
// List Tile Theme
listTileTheme: ListTileThemeData(
textColor: UiColors.textPrimary,
iconColor: UiColors.iconPrimary,
titleTextStyle: UiTypography.body1m,
subtitleTextStyle: UiTypography.body2r.textSecondary,
contentPadding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
),
tileColor: UiColors.transparent,
),
// Badge Theme
badgeTheme: BadgeThemeData(
backgroundColor: UiColors.primary,
textColor: UiColors.primaryForeground,
textStyle: UiTypography.footnote2m,
padding: const EdgeInsets.symmetric(horizontal: 4),
),
// App Bar Theme
appBarTheme: AppBarTheme(
backgroundColor: UiColors.background,
elevation: 0,
titleTextStyle: UiTypography.headline5m.textPrimary,
iconTheme: const IconThemeData(color: UiColors.iconThird, size: 20),
surfaceTintColor: UiColors.transparent,
),
// Dialog Theme
dialogTheme: DialogThemeData(
backgroundColor: UiColors.bgPopup,
elevation: 8,
shadowColor: UiColors.popupShadow,
shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusLg),
titleTextStyle: UiTypography.headline2r.textPrimary,
contentTextStyle: UiTypography.body2r.textDescription,
),
// Bottom Navigation Bar Theme
bottomNavigationBarTheme: BottomNavigationBarThemeData(
backgroundColor: UiColors.white,
selectedItemColor: UiColors.primary,
unselectedItemColor: UiColors.textInactive,
selectedLabelStyle: UiTypography.footnote2m,
unselectedLabelStyle: UiTypography.footnote2r,
type: BottomNavigationBarType.fixed,
elevation: 8,
),
// Navigation Bar Theme (Modern M3 Bottom Nav)
navigationBarTheme: NavigationBarThemeData(
backgroundColor: UiColors.white,
indicatorColor: UiColors.primaryInverse.withAlpha(51), // 20% of 255
labelTextStyle: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return UiTypography.footnote2m.textPrimary;
}
return UiTypography.footnote2r.textInactive;
}),
),
// Switch Theme
switchTheme: SwitchThemeData(
trackColor: WidgetStateProperty.resolveWith<Color>((states) {
if (states.contains(WidgetState.selected)) {
return UiColors.switchActive;
}
return UiColors.switchInactive;
}),
thumbColor: const WidgetStatePropertyAll(UiColors.white),
),
// Checkbox Theme
checkboxTheme: CheckboxThemeData(
fillColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) return UiColors.primary;
return null;
}),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
),
// Radio Theme
radioTheme: RadioThemeData(
fillColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) return UiColors.primary;
return null;
}),
),
// Slider Theme
sliderTheme: const SliderThemeData(
activeTrackColor: UiColors.primary,
inactiveTrackColor: UiColors.loaderInactive,
thumbColor: UiColors.primary,
overlayColor: UiColors.primaryInverse,
),
// Chip Theme
chipTheme: ChipThemeData(
backgroundColor: UiColors.bgSecondary,
labelStyle: UiTypography.footnote1m,
secondaryLabelStyle: UiTypography.footnote1m.white,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusMd),
side: const BorderSide(color: UiColors.borderStill, width: 0.5),
),
// SnackBar Theme
snackBarTheme: SnackBarThemeData(
backgroundColor: UiColors.toastBg,
contentTextStyle: UiTypography.body2r.white,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusMd),
elevation: 4,
),
// Bottom Sheet Theme
bottomSheetTheme: const BottomSheetThemeData(
backgroundColor: UiColors.bgSecondary,
modalBackgroundColor: UiColors.bgSecondary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(32)),
),
),
// Expansion Tile Theme
expansionTileTheme: ExpansionTileThemeData(
iconColor: UiColors.iconSecondary,
collapsedIconColor: UiColors.iconPrimary,
backgroundColor: UiColors.bgPopup,
collapsedBackgroundColor: UiColors.transparent,
textColor: UiColors.textPrimary,
collapsedTextColor: UiColors.textPrimary,
tilePadding: const EdgeInsets.symmetric(horizontal: UiConstants.space4),
shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusMd),
collapsedShape: RoundedRectangleBorder(
borderRadius: UiConstants.radiusMd,
),
),
// Menu Theme
menuTheme: MenuThemeData(
style: MenuStyle(
backgroundColor: WidgetStateProperty.all(UiColors.bgPopup),
elevation: WidgetStateProperty.all(4),
shape: WidgetStateProperty.all(
RoundedRectangleBorder(borderRadius: UiConstants.radiusMd),
),
),
),
// Tooltip Theme
tooltipTheme: TooltipThemeData(
decoration: BoxDecoration(
color: UiColors.toastBg.withAlpha(230), // ~90% of 255
borderRadius: UiConstants.radiusMd,
),
textStyle: UiTypography.footnote2r.white,
),
// Progress Indicator Theme
progressIndicatorTheme: const ProgressIndicatorThemeData(
color: UiColors.primary,
linearTrackColor: UiColors.loaderInactive,
linearMinHeight: 4,
),
);
}
}

View File

@@ -0,0 +1,564 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:design_system/design_system.dart';
import 'ui_colors.dart';
/// Static definitions of typography styles for the Staff Design System.
class UiTypography {
UiTypography._();
// ---------------------------------------------------------------------------
// 0. Base Font Styles
// ---------------------------------------------------------------------------
/// The primary font family used throughout the design system.
static final TextStyle _primaryBase = GoogleFonts.instrumentSans();
/// The secondary font family used for display or specialized elements.
static final TextStyle _secondaryBase = GoogleFonts.spaceGrotesk();
// ---------------------------------------------------------------------------
// 1. Primary Typography (Instrument Sans)
// ---------------------------------------------------------------------------
// --- 1.1 Display ---
/// Display Large - Font: Instrument Sans, Size: 36, Height: 1.1 (#121826)
static final TextStyle displayL = _primaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 36,
height: 1.1,
color: UiColors.textPrimary,
);
/// Display medium - Font: Instrument Sans, Size: 32, Height: 1.1 (#121826)
static final TextStyle displayM = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 32,
height: 1.1,
color: UiColors.textPrimary,
);
/// Display small - Font: Instrument Sans, Size: 32, Height: 1.1 (#121826)
static final TextStyle displayMb = _primaryBase.copyWith(
fontWeight: FontWeight.w600,
fontSize: 32,
height: 1.1,
color: UiColors.textPrimary,
);
/// Display 1 Medium - Font: Instrument Sans, Size: 26, Height: 1.1 (#121826)
static final TextStyle display1m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 26,
height: 1.1,
color: UiColors.textPrimary,
);
/// Display 1 Regular - Font: Instrument Sans, Size: 38, Height: 1.3 (#121826)
static final TextStyle display1r = _primaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 26,
height: 1.3,
letterSpacing: -1,
color: UiColors.textPrimary,
);
/// Display 1 Bold - Font: Instrument Sans, Size: 38, Height: 1.3 (#121826)
static final TextStyle display1b = _primaryBase.copyWith(
fontWeight: FontWeight.w600,
fontSize: 26,
height: 1.3,
letterSpacing: -1,
color: UiColors.textPrimary,
);
/// Display 2 Medium - Font: Instrument Sans, Size: 16, Height: 1.1 (#121826)
static final TextStyle display2m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 16,
height: 1.1,
color: UiColors.textPrimary,
);
/// Display 2 Regular - Font: Instrument Sans, Size: 28, Height: 1.5 (#121826)
static final TextStyle display2r = _primaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 16,
height: 1.5,
color: UiColors.textPrimary,
);
/// Display 3 Medium - Font: Instrument Sans, Size: 32, Height: 1.1 (#121826)
static final TextStyle display3m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 14,
height: 1.1,
color: UiColors.textPrimary,
);
/// Display 3 Regular - Font: Instrument Sans, Size: 32, Height: 1.3 (#121826)
static final TextStyle display3r = _primaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
height: 1.3,
color: UiColors.textPrimary,
);
/// Display 3 Bold - Font: Instrument Sans, Size: 32, Height: 1.1 (#121826)
static final TextStyle display3b = _primaryBase.copyWith(
fontWeight: FontWeight.w600,
fontSize: 14,
height: 1.1,
color: UiColors.textPrimary,
);
// --- 1.2 Title ---
/// Title 1 Medium - Font: Instrument Sans, Size: 18, Height: 1.5 (#121826)
static final TextStyle title1m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 18,
height: 1.5,
color: UiColors.textPrimary,
);
/// Title 1 Regular - Font: Instrument Sans, Size: 16, Height: 1.5 (#121826)
static final TextStyle title1r = _primaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 18,
height: 1.5,
color: UiColors.textPrimary,
);
/// Title 2 Bold - Font: Instrument Sans, Size: 20, Height: 1.1 (#121826)
static final TextStyle title2b = _primaryBase.copyWith(
fontWeight: FontWeight.w600,
fontSize: 16,
height: 1.1,
color: UiColors.textPrimary,
);
/// Title 2 Medium - Font: Instrument Sans, Size: 16, Height: 1.5 (#121826)
static final TextStyle title2m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 16,
height: 1.5,
color: UiColors.textPrimary,
);
/// Title 2 Regular - Font: Instrument Sans, Size: 16, Height: 1.5 (#121826)
static final TextStyle title2r = _primaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 16,
height: 1.5,
color: UiColors.textPrimary,
);
// --- 1.3 Headline ---
/// Headline 1 Medium - Font: Instrument Sans, Size: 26, Height: 1.5 (#121826)
static final TextStyle headline1m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 26,
height: 1.5,
color: UiColors.textPrimary,
);
/// Headline 1 Regular - Font: Instrument Sans, Size: 26, Height: 1.5 (#121826)
static final TextStyle headline1r = _primaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 26,
height: 1.5,
color: UiColors.textPrimary,
);
/// Headline 2 Medium - Font: Instrument Sans, Size: 20, Height: 1.5 (#121826)
static final TextStyle headline2m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 22,
height: 1.5,
color: UiColors.textPrimary,
);
/// Headline 2 Regular - Font: Instrument Sans, Size: 20, Height: 1.5 (#121826)
static final TextStyle headline2r = _primaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 22,
height: 1.5,
color: UiColors.textPrimary,
);
/// Headline 3 Medium - Font: Instrument Sans, Size: 22, Height: 1.5 (#121826)
static final TextStyle headline3m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 20,
height: 1.5,
color: UiColors.textPrimary,
);
/// Headline 4 Medium - Font: Instrument Sans, Size: 22, Height: 1.5 (#121826)
static final TextStyle headline4m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 18,
height: 1.5,
color: UiColors.textPrimary,
);
/// Headline 4 Regular - Font: Instrument Sans, Size: 20, Height: 1.5 (#121826)
static final TextStyle headline4r = _primaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 18,
height: 1.5,
color: UiColors.textPrimary,
);
/// Headline 5 Regular - Font: Instrument Sans, Size: 18, Height: 1.5 (#121826)
static final TextStyle headline5r = _primaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
height: 1.5,
color: UiColors.textPrimary,
);
/// Headline 5 Medium - Font: Instrument Sans, Size: 16, Height: 1.5 (#121826)
static final TextStyle headline5m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 16,
height: 1.5,
color: UiColors.textPrimary,
);
// --- 1.4 Title Uppercase ---
/// Title Uppercase 2 Medium - Font: Instrument Sans, Size: 14, Height: 1.5, Spacing: 0.7 (#121826)
static final TextStyle titleUppercase2m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 14,
height: 1.5,
letterSpacing: 0.7,
color: UiColors.textPrimary,
);
/// Title Uppercase 3 Medium - Font: Instrument Sans, Size: 12, Height: 1.5, Spacing: 1.5 (#121826)
static final TextStyle titleUppercase3m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 12,
height: 1.5,
letterSpacing: 1.5,
color: UiColors.textPrimary,
);
/// Title Uppercase 4 Medium - Font: Instrument Sans, Size: 11, Height: 1.5, Spacing: 2.2 (#121826)
static final TextStyle titleUppercase4m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 11,
height: 1.5,
letterSpacing: 2.2,
color: UiColors.textPrimary,
);
// --- 1.5 Body ---
/// Body 1 Bold - Font: Instrument Sans, Size: 16, Height: 1.5 (#121826)
static final TextStyle body1b = _primaryBase.copyWith(
fontWeight: FontWeight.w700,
fontSize: 16,
height: 1.5,
color: UiColors.textPrimary,
);
/// Body 1 Medium - Font: Instrument Sans, Size: 16, Height: 1.5 (#121826)
static final TextStyle body1m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 16,
height: 1.5,
letterSpacing: -0.025,
color: UiColors.textPrimary,
);
/// Body 1 Regular - Font: Instrument Sans, Size: 16, Height: 1.5 (#121826)
static final TextStyle body1r = _primaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 16,
height: 1.5,
letterSpacing: -0.05,
color: UiColors.textPrimary,
);
/// Body 2 Bold - Font: Instrument Sans, Size: 14, Height: 1.5 (#121826)
static final TextStyle body2b = _primaryBase.copyWith(
fontWeight: FontWeight.w700,
fontSize: 14,
height: 1.5,
color: UiColors.textPrimary,
);
/// Body 2 Medium - Font: Instrument Sans, Size: 14, Height: 1.5 (#121826)
static final TextStyle body2m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 14,
height: 1.5,
color: UiColors.textPrimary,
);
/// Body 2 Regular - Font: Instrument Sans, Size: 14, Height: 1.5, Spacing: 0.1 (#121826)
static final TextStyle body2r = _primaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
height: 1.5,
letterSpacing: 0.1,
color: UiColors.textPrimary,
);
/// Body 3 Regular - Font: Instrument Sans, Size: 14, Height: 1.5, Spacing: -0.1 (#121826)
static final TextStyle body3r = _primaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
height: 1.5,
letterSpacing: -0.1,
color: UiColors.textPrimary,
);
/// Body 4 Regular - Font: Instrument Sans, Size: 14, Height: 1.5, Spacing: 0.05 (#121826)
static final TextStyle body4r = _primaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
height: 1.5,
letterSpacing: 0.05,
color: UiColors.textPrimary,
);
/// Body 4 Medium - Font: Instrument Sans, Size: 14, Height: 1.5, Spacing: 0.05 (#121826)
static final TextStyle body4m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 12,
height: 1.5,
letterSpacing: 0.05,
color: UiColors.textPrimary,
);
// --- 1.6 Footnote ---
/// Footnote 1 Medium - Font: Instrument Sans, Size: 12, Height: 1.5 (#121826)
static final TextStyle footnote1m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 12,
height: 1.5,
color: UiColors.textPrimary,
);
/// Footnote 1 Regular - Font: Instrument Sans, Size: 12, Height: 1.5, Spacing: 0.05 (#121826)
static final TextStyle footnote1r = _primaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
height: 1.5,
letterSpacing: 0.05,
color: UiColors.textPrimary,
);
/// Footnote 1 Bold - Font: Instrument Sans, Size: 12, Height: 1.5, Spacing: 0.05 (#121826)
static final TextStyle footnote1b = _primaryBase.copyWith(
fontWeight: FontWeight.w700,
fontSize: 12,
height: 1.5,
letterSpacing: 0.05,
color: UiColors.textPrimary,
);
/// Footnote 2 Medium - Font: Instrument Sans, Size: 10, Height: 1.5 (#121826)
static final TextStyle footnote2m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 10,
height: 1.5,
color: UiColors.textPrimary,
);
/// Footnote 2 Bold - Font: Instrument Sans, Size: 10, Height: 1.5 (#121826)
static final TextStyle footnote2b = _primaryBase.copyWith(
fontWeight: FontWeight.w700,
fontSize: 10,
height: 1.5,
color: UiColors.textPrimary,
);
/// Footnote 2 Regular - Font: Instrument Sans, Size: 10, Height: 1.5 (#121826)
static final TextStyle footnote2r = _primaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 10,
height: 1.5,
color: UiColors.textPrimary,
);
// --- 1.7 Button ---
/// Button S - Font: Instrument Sans, Size: 10, Height: 1.5 (#121826)
static final TextStyle buttonS = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 10,
height: 1.5,
color: UiColors.textPrimary,
);
/// Button Medium - Font: Instrument Sans, Size: 12, Height: 1.5 (#121826)
static final TextStyle buttonM = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 12,
height: 1.5,
color: UiColors.textPrimary,
);
/// Button Large - Font: Instrument Sans, Size: 14, Height: 1.5 (#121826)
static final TextStyle buttonL = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 14,
height: 1.5,
color: UiColors.textPrimary,
);
/// Button XL - Font: Instrument Sans, Size: 16, Height: 1.5 (#121826)
static final TextStyle buttonXL = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 16,
height: 1.5,
color: UiColors.textPrimary,
);
// --- 1.8 Link ---
/// Link 1 Regular - Font: Instrument Sans, Size: 16, Height: 1.5, Underlined (#0A39DF)
static final TextStyle link1r = _primaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 16,
height: 1.5,
color: UiColors.textLink,
decoration: TextDecoration.underline,
decorationColor: UiColors.textLink,
);
/// Link 2 Medium - Font: Instrument Sans, Size: 14, Height: 1.5, Underlined (#0A39DF)
static final TextStyle link2m = _primaryBase.copyWith(
fontWeight: FontWeight.w500,
fontSize: 14,
height: 1.5,
color: UiColors.textLink,
decoration: TextDecoration.underline,
decorationColor: UiColors.textLink,
);
/// Link 2 Regular - Font: Instrument Sans, Size: 14, Height: 1.5, Underlined (#0A39DF)
static final TextStyle link2r = _primaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
height: 1.5,
color: UiColors.textLink,
decoration: TextDecoration.underline,
decorationColor: UiColors.textLink,
);
// ---------------------------------------------------------------------------
// 2. Secondary Typography (Space Grotesk)
// ---------------------------------------------------------------------------
// --- 2.1 Display ---
/// Display 1 Bold (Secondary) - Font: Space Grotesk, Size: 50, Height: 1.1 (#121826)
static final TextStyle secondaryDisplay1b = _secondaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 50,
height: 1.1,
color: UiColors.textPrimary,
);
/// Display 1 Regular (Secondary) - Font: Space Grotesk, Size: 50, Height: 1.1 (#121826)
static final TextStyle secondaryDisplay1r = _secondaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 50,
height: 1.1,
color: UiColors.textPrimary,
);
/// Display 2 Bold (Secondary) - Font: Space Grotesk, Size: 40, Height: 1.1 (#121826)
static final TextStyle secondaryDisplay2b = _secondaryBase.copyWith(
fontWeight: FontWeight.w700,
fontSize: 40,
height: 1.1,
color: UiColors.textPrimary,
);
/// Display 2 Regular (Secondary) - Font: Space Grotesk, Size: 40, Height: 1.1 (#121826)
static final TextStyle secondaryDisplay2r = _secondaryBase.copyWith(
fontWeight: FontWeight.w400,
fontSize: 40,
height: 1.1,
color: UiColors.textPrimary,
);
// ---------------------------------------------------------------------------
// 3. TextTheme Mapping
// ---------------------------------------------------------------------------
/// Primary TextTheme
static TextTheme get textTheme => TextTheme(
displayLarge: display1r,
displayMedium: displayL,
displaySmall: display3m,
headlineLarge: headline1m,
headlineMedium: headline3m,
headlineSmall: headline2m,
titleLarge: title1m,
titleMedium: title2m,
titleSmall: body2m,
bodyLarge: body1r,
bodyMedium: body2r,
bodySmall: footnote1r,
labelLarge: buttonL,
labelMedium: buttonM,
labelSmall: footnote2r,
);
}
/// Extension to easily color text styles using the Staff Design System color palette.
extension TypographyColors on TextStyle {
/// Primary text color (#121826)
TextStyle get textPrimary => copyWith(color: UiColors.textPrimary);
/// Secondary text color (#6A7382)
TextStyle get textSecondary => copyWith(color: UiColors.textSecondary);
/// Inactive text color (#9CA3AF)
TextStyle get textInactive => copyWith(color: UiColors.textInactive);
/// Tertiary text color (#9CA3AF)
TextStyle get textTertiary => copyWith(color: UiColors.textInactive);
/// Placeholder text color (#9CA3AF)
TextStyle get textPlaceholder => copyWith(color: UiColors.textPlaceholder);
/// Description text color (#6A7382)
TextStyle get textDescription => copyWith(color: UiColors.textDescription);
/// Success text color (#10B981)
TextStyle get textSuccess => copyWith(color: UiColors.textSuccess);
/// Error text color (#F04444)
TextStyle get textError => copyWith(color: UiColors.textError);
/// Warning text color (#D97706)
TextStyle get textWarning => copyWith(color: UiColors.textWarning);
/// Link text color (#0A39DF)
TextStyle get textLink => copyWith(color: UiColors.textLink);
/// White text color (#FFFFFF)
TextStyle get white => copyWith(color: UiColors.white);
/// Black text color (#000000)
TextStyle get black => copyWith(color: UiColors.black);
/// Underline decoration
TextStyle get underline => copyWith(decoration: TextDecoration.underline);
/// Active content color
TextStyle get activeContentColor => copyWith(color: UiColors.textPrimary);
}

View File

@@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import '../ui_icons.dart';
/// A custom AppBar for the Krow UI design system.
///
/// This widget provides a consistent look and feel for top app bars across the application.
class UiAppBar extends StatelessWidget implements PreferredSizeWidget {
/// The title text to display in the app bar.
final String? title;
/// A widget to display instead of the title text.
final Widget? titleWidget;
/// The widget to display before the title.
/// Usually an [IconButton] for navigation.
final Widget? leading;
/// A list of Widgets to display in a row after the [title] widget.
final List<Widget>? actions;
/// The height of the app bar. Defaults to [kToolbarHeight].
final double height;
/// Whether the title should be centered.
final bool centerTitle;
/// Signature for the callback that is called when the leading button is pressed.
/// If [leading] is null, this callback will be used for a default back button.
final VoidCallback? onLeadingPressed;
/// Whether to show a default back button if [leading] is null.
final bool showBackButton;
/// This widget appears across the bottom of the app bar.
/// Typically a [TabBar]. Only widgets that implement [PreferredSizeWidget] can be used at the bottom of an app bar.
final PreferredSizeWidget? bottom;
const UiAppBar({
super.key,
this.title,
this.titleWidget,
this.leading,
this.actions,
this.height = kToolbarHeight,
this.centerTitle = true,
this.onLeadingPressed,
this.showBackButton = true,
this.bottom,
});
@override
Widget build(BuildContext context) {
return AppBar(
title: titleWidget ??
(title != null
? Text(
title!,
)
: null),
leading: leading ??
(showBackButton
? IconButton(
icon: const Icon(UiIcons.chevronLeft, size: 20),
onPressed: onLeadingPressed ?? () => Navigator.of(context).pop(),
)
: null),
actions: actions,
centerTitle: centerTitle,
bottom: bottom,
);
}
@override
Size get preferredSize => Size.fromHeight(height + (bottom?.preferredSize.height ?? 0.0));
}

View File

@@ -0,0 +1,191 @@
import 'package:flutter/material.dart';
import '../ui_constants.dart';
/// A custom button widget with different variants and icon support.
class UiButton extends StatelessWidget {
/// The text to display on the button.
final String? text;
/// Optional custom child widget. If provided, overrides text and icons.
final Widget? child;
/// Callback when the button is tapped.
final VoidCallback? onPressed;
/// Optional leading icon.
final IconData? leadingIcon;
/// Optional trailing icon.
final IconData? trailingIcon;
/// Optional Style
final ButtonStyle? style;
/// The size of the icons. Defaults to 20.
final double iconSize;
/// The size of the button.
final UiButtonSize size;
/// The button widget to use (ElevatedButton, OutlinedButton, or TextButton).
final Widget Function(
BuildContext context,
VoidCallback? onPressed,
ButtonStyle? style,
Widget child,
)
buttonBuilder;
/// Creates a [UiButton] with a custom button builder.
const UiButton({
super.key,
this.text,
this.child,
required this.buttonBuilder,
this.onPressed,
this.leadingIcon,
this.trailingIcon,
this.style,
this.iconSize = 20,
this.size = UiButtonSize.medium,
}) : assert(
text != null || child != null,
'Either text or child must be provided',
);
/// Creates a primary button using [ElevatedButton].
UiButton.primary({
super.key,
this.text,
this.child,
this.onPressed,
this.leadingIcon,
this.trailingIcon,
this.style,
this.iconSize = 20,
this.size = UiButtonSize.medium,
}) : buttonBuilder = _elevatedButtonBuilder,
assert(
text != null || child != null,
'Either text or child must be provided',
);
/// Creates a secondary button using [OutlinedButton].
UiButton.secondary({
super.key,
this.text,
this.child,
this.onPressed,
this.leadingIcon,
this.trailingIcon,
this.style,
this.iconSize = 20,
this.size = UiButtonSize.medium,
}) : buttonBuilder = _outlinedButtonBuilder,
assert(
text != null || child != null,
'Either text or child must be provided',
);
/// Creates a text button using [TextButton].
UiButton.text({
super.key,
this.text,
this.child,
this.onPressed,
this.leadingIcon,
this.trailingIcon,
this.style,
this.iconSize = 20,
this.size = UiButtonSize.medium,
}) : buttonBuilder = _textButtonBuilder,
assert(
text != null || child != null,
'Either text or child must be provided',
);
@override
/// Builds the button UI.
Widget build(BuildContext context) {
return buttonBuilder(context, onPressed, style, _buildButtonContent());
}
/// Builds the button content with optional leading and trailing icons.
Widget _buildButtonContent() {
if (child != null) {
return child!;
}
// Single icon or text case
if (leadingIcon == null && trailingIcon == null) {
return Text(text!);
}
if (leadingIcon != null && text == null && trailingIcon == null) {
return Icon(leadingIcon, size: iconSize);
}
// Multiple elements case
final List<Widget> children = [];
if (leadingIcon != null) {
children.add(Icon(leadingIcon, size: iconSize));
children.add(const SizedBox(width: UiConstants.space2));
}
children.add(Text(text!));
if (trailingIcon != null) {
children.add(const SizedBox(width: UiConstants.space2));
children.add(Icon(trailingIcon, size: iconSize));
}
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: children,
);
}
/// Builder for ElevatedButton.
static Widget _elevatedButtonBuilder(
BuildContext context,
VoidCallback? onPressed,
ButtonStyle? style,
Widget child,
) {
return ElevatedButton(onPressed: onPressed, style: style, child: child);
}
/// Builder for OutlinedButton.
static Widget _outlinedButtonBuilder(
BuildContext context,
VoidCallback? onPressed,
ButtonStyle? style,
Widget child,
) {
return OutlinedButton(onPressed: onPressed, style: style, child: child);
}
/// Builder for TextButton.
static Widget _textButtonBuilder(
BuildContext context,
VoidCallback? onPressed,
ButtonStyle? style,
Widget child,
) {
return TextButton(onPressed: onPressed, style: style, child: child);
}
}
/// Defines the size of a [UiButton].
enum UiButtonSize {
/// Small button (compact)
small,
/// Medium button (standard)
medium,
/// Large button (prominent)
large,
}

View File

@@ -0,0 +1,190 @@
import 'package:flutter/material.dart';
import '../ui_colors.dart';
import '../ui_constants.dart';
import '../ui_typography.dart';
/// Sizes for the [UiChip] widget.
enum UiChipSize {
/// Small size (e.g. for tags in tight spaces).
small,
/// Medium size (default).
medium,
/// Large size (e.g. for standalone filters).
large,
}
/// Themes for the [UiChip] widget.
enum UiChipVariant {
/// Primary style with solid background.
primary,
/// Secondary style with light background.
secondary,
/// Accent style with highlight background.
accent,
}
/// A custom chip widget with supports for different sizes, themes, and icons.
class UiChip extends StatelessWidget {
/// The text label to display.
final String label;
/// The size of the chip. Defaults to [UiChipSize.medium].
final UiChipSize size;
/// The theme variant of the chip. Defaults to [UiChipVariant.secondary].
final UiChipVariant variant;
/// Optional leading icon.
final IconData? leadingIcon;
/// Optional trailing icon.
final IconData? trailingIcon;
/// Callback when the chip is tapped.
final VoidCallback? onTap;
/// Callback when the trailing icon is tapped (e.g. for removal).
final VoidCallback? onTrailingIconTap;
/// Whether the chip is currently selected/active.
final bool isSelected;
/// Creates a [UiChip].
const UiChip({
super.key,
required this.label,
this.size = UiChipSize.medium,
this.variant = UiChipVariant.secondary,
this.leadingIcon,
this.trailingIcon,
this.onTap,
this.onTrailingIconTap,
this.isSelected = false,
});
@override
Widget build(BuildContext context) {
final backgroundColor = _getBackgroundColor();
final contentColor = _getContentColor();
final textStyle = _getTextStyle().copyWith(color: contentColor);
final padding = _getPadding();
final iconSize = _getIconSize();
final content = Row(
mainAxisSize: MainAxisSize.min,
children: [
if (leadingIcon != null) ...[
Icon(leadingIcon, size: iconSize, color: contentColor),
SizedBox(width: _getGap()),
],
Text(label, style: textStyle),
if (trailingIcon != null) ...[
SizedBox(width: _getGap()),
GestureDetector(
onTap: onTrailingIconTap,
child: Icon(trailingIcon, size: iconSize, color: contentColor),
),
],
],
);
return GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: padding,
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: UiConstants.radiusFull,
border: _getBorder(),
),
child: content,
),
);
}
Color _getBackgroundColor() {
if (!isSelected && variant == UiChipVariant.primary) {
return UiColors.white;
}
switch (variant) {
case UiChipVariant.primary:
return UiColors.primary;
case UiChipVariant.secondary:
return UiColors.tagInProgress;
case UiChipVariant.accent:
return UiColors.accent;
}
}
Color _getContentColor() {
if (!isSelected && variant == UiChipVariant.primary) {
return UiColors.textSecondary;
}
switch (variant) {
case UiChipVariant.primary:
return UiColors.white;
case UiChipVariant.secondary:
return UiColors.primary;
case UiChipVariant.accent:
return UiColors.accentForeground;
}
}
TextStyle _getTextStyle() {
switch (size) {
case UiChipSize.small:
return UiTypography.body3r;
case UiChipSize.medium:
return UiTypography.body2m;
case UiChipSize.large:
return UiTypography.body1m;
}
}
EdgeInsets _getPadding() {
switch (size) {
case UiChipSize.small:
return const EdgeInsets.symmetric(horizontal: 10, vertical: 6);
case UiChipSize.medium:
return const EdgeInsets.symmetric(horizontal: 12, vertical: 8);
case UiChipSize.large:
return const EdgeInsets.symmetric(horizontal: 16, vertical: 10);
}
}
double _getIconSize() {
switch (size) {
case UiChipSize.small:
return 12;
case UiChipSize.medium:
return 16;
case UiChipSize.large:
return 20;
}
}
double _getGap() {
switch (size) {
case UiChipSize.small:
return UiConstants.space1;
case UiChipSize.medium:
return UiConstants.space1 + 2;
case UiChipSize.large:
return UiConstants.space2;
}
}
BoxBorder? _getBorder() {
if (!isSelected && variant == UiChipVariant.primary) {
return Border.all(color: UiColors.border);
}
return null;
}
}

View File

@@ -0,0 +1,89 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import '../ui_colors.dart';
import '../ui_constants.dart';
/// A custom icon button with blur effect and different variants.
class UiIconButton extends StatelessWidget {
/// The icon to display.
final IconData icon;
/// The size of the icon button.
final double size;
/// The size of the icon.
final double iconSize;
/// The background color of the button.
final Color backgroundColor;
/// The color of the icon.
final Color iconColor;
/// Whether to apply blur effect.
final bool useBlur;
/// Callback when the button is tapped.
final VoidCallback? onTap;
/// Creates a [UiIconButton] with custom properties.
const UiIconButton({
super.key,
required this.icon,
this.size = 40,
this.iconSize = 20,
required this.backgroundColor,
required this.iconColor,
this.useBlur = false,
this.onTap,
});
/// Creates a primary variant icon button with solid background.
const UiIconButton.primary({
super.key,
required this.icon,
this.size = 40,
this.iconSize = 20,
this.onTap,
}) : backgroundColor = UiColors.primary,
iconColor = UiColors.white,
useBlur = false;
/// Creates a secondary variant icon button with blur effect.
UiIconButton.secondary({
super.key,
required this.icon,
this.size = 40,
this.iconSize = 20,
this.onTap,
}) : backgroundColor = UiColors.primary.withAlpha(96),
iconColor = UiColors.primary,
useBlur = true;
@override
/// Builds the icon button UI.
Widget build(BuildContext context) {
final Widget button = Container(
width: size,
height: size,
decoration: BoxDecoration(color: backgroundColor, shape: BoxShape.circle),
child: Icon(icon, color: iconColor, size: iconSize),
);
final Widget content = useBlur
? ClipRRect(
borderRadius: UiConstants.radiusFull,
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: button,
),
)
: button;
if (onTap != null) {
return GestureDetector(onTap: onTap, child: content);
}
return content;
}
}

View File

@@ -0,0 +1,87 @@
import 'package:flutter/material.dart';
import '../ui_colors.dart';
import '../ui_constants.dart';
import '../ui_icons.dart';
/// A widget that displays a horizontal step indicator with icons.
///
/// This widget shows a series of circular step indicators connected by lines,
/// with different visual states for completed, active, and inactive steps.
class UiStepIndicator extends StatelessWidget {
/// The list of icons to display for each step.
final List<IconData> stepIcons;
/// The index of the currently active step (0-based).
final int currentStep;
/// Creates a [UiStepIndicator].
const UiStepIndicator({
super.key,
required this.stepIcons,
required this.currentStep,
});
@override
/// Builds the step indicator UI.
Widget build(BuildContext context) {
// active step color
const Color activeColor = UiColors.primary;
// completed step color
const Color completedColor = UiColors.textSuccess;
// inactive step color
const Color inactiveColor = UiColors.iconSecondary;
return Padding(
padding: const EdgeInsets.symmetric(vertical: UiConstants.space2),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(stepIcons.length, (index) {
final bool isActive = index == currentStep;
final bool isCompleted = index < currentStep;
Color bgColor;
Color iconColor;
if (isCompleted) {
bgColor = completedColor.withAlpha(24);
iconColor = completedColor;
} else if (isActive) {
bgColor = activeColor.withAlpha(24);
iconColor = activeColor;
} else {
bgColor = inactiveColor.withAlpha(24);
iconColor = inactiveColor.withAlpha(128);
}
return Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: bgColor,
shape: BoxShape.circle,
),
child: Icon(
isCompleted ? UiIcons.check : stepIcons[index],
size: 20,
color: iconColor,
),
),
if (index < stepIcons.length - 1)
Container(
width: 30,
height: 2,
margin: const EdgeInsets.symmetric(
horizontal: UiConstants.space1,
),
color: isCompleted
? completedColor.withAlpha(96)
: inactiveColor.withAlpha(96),
),
],
);
}),
),
);
}
}

View File

@@ -0,0 +1,115 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../ui_typography.dart';
import '../ui_constants.dart';
import '../ui_colors.dart';
/// A custom TextField for the Krow UI design system.
///
/// This widget combines a label and a [TextField] with consistent styling.
class UiTextField extends StatelessWidget {
/// The label text to display above the text field.
final String? label;
/// The hint text to display inside the text field when empty.
final String? hintText;
/// Signature for the callback that is called when the text in the field changes.
final ValueChanged<String>? onChanged;
/// The controller for the text field.
final TextEditingController? controller;
/// The type of keyboard to use for editing the text.
final TextInputType? keyboardType;
/// The maximum number of lines for the text field. Defaults to 1.
final int? maxLines;
/// Whether to hide the text being edited (e.g., for passwords). Defaults to false.
final bool obscureText;
/// The type of action button to use for the keyboard.
final TextInputAction? textInputAction;
/// Signature for the callback that is called when the user submits the text field.
final ValueChanged<String>? onSubmitted;
/// Whether the text field should be focused automatically. Defaults to false.
final bool autofocus;
/// Optional input formatters to validate or format the text as it is typed.
final List<TextInputFormatter>? inputFormatters;
/// Optional prefix icon to display at the start of the text field.
final IconData? prefixIcon;
/// Optional suffix icon to display at the end of the text field.
final IconData? suffixIcon;
/// Optional custom suffix widget to display at the end (e.g., password toggle).
final Widget? suffix;
/// Whether the text field should be read-only.
final bool readOnly;
/// Callback when the text field is tapped.
final VoidCallback? onTap;
const UiTextField({
super.key,
this.label,
this.hintText,
this.onChanged,
this.controller,
this.keyboardType,
this.maxLines = 1,
this.obscureText = false,
this.textInputAction,
this.onSubmitted,
this.autofocus = false,
this.inputFormatters,
this.prefixIcon,
this.suffixIcon,
this.suffix,
this.readOnly = false,
this.onTap,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (label != null) ...[
Text(label!, style: UiTypography.body4m.textSecondary),
const SizedBox(height: UiConstants.space1),
],
TextField(
controller: controller,
onChanged: onChanged,
keyboardType: keyboardType,
maxLines: maxLines,
obscureText: obscureText,
textInputAction: textInputAction,
onSubmitted: onSubmitted,
autofocus: autofocus,
inputFormatters: inputFormatters,
readOnly: readOnly,
onTap: onTap,
style: UiTypography.body1r.textPrimary,
decoration: InputDecoration(
hintText: hintText,
prefixIcon: prefixIcon != null
? Icon(prefixIcon, size: 20, color: UiColors.iconSecondary)
: null,
suffixIcon: suffixIcon != null
? Icon(suffixIcon, size: 20, color: UiColors.iconSecondary)
: suffix,
),
),
],
);
}
}

View File

@@ -0,0 +1,31 @@
name: design_system
description: "A new Flutter package project."
version: 0.0.1
homepage:
resolution: workspace
environment:
sdk: ^3.10.7
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
google_fonts: ^7.0.2
lucide_icons: ^0.257.0
font_awesome_flutter: ^10.7.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# To add assets to your package, add an assets section, like this:
assets:
- assets/

View File

@@ -0,0 +1,12 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:design_system/design_system.dart';
void main() {
test('adds one to input values', () {
final calculator = Calculator();
expect(calculator.addOne(2), 3);
expect(calculator.addOne(-7), -6);
expect(calculator.addOne(0), 1);
});
}