UI: Cyber/IT design overhaul with semantic status tokens (Material 3)

Re-theme TasQ with a refined enterprise "Cyber/IT" identity on Material 3.
Functionality is unchanged — only color/shape tokens and a few state widgets.

Foundation:
- Hand-tuned light + dark ColorSchemes (deep-slate surfaces, azure/teal/violet
  accents), tightened 12-16dp shape language, flat hairline-outlined cards.
- New AppStatusColors theme extension (success/warning/danger/info/neutral,
  per-brightness) replacing scattered raw Colors.green/orange/red/grey so status
  color is consistent and legible in dark mode.
- New TechChip mono primitive; StatusPill.kind; M3Card hairline variants;
  tightened AppSurfaces radii.

Per-screen:
- Tokenized status colors + radii across dashboard, tickets, tasks, IT service
  requests, attendance, whereabouts, network map, reports charts, auth, and
  shared/admin screens. Added _ticketStatusKind/_taskStatusKind/_isrStatusKind
  mappers so status carries meaning consistently.
- Fixed white pie-slice borders for dark mode; dropped ad-hoc dialog radius
  overrides in favor of the theme default. Categorical palettes (leave-type
  icons, team color picker, distribution charts) intentionally preserved.

Verified: flutter analyze lib clean; flutter build web green; visual QA in
light and dark (dashboard, tickets, attendance).

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-09-26 15:13:47 +08:00
parent 3cb980b629
commit 57377b1b21
28 changed files with 986 additions and 470 deletions
+52 -43
View File
@@ -1,26 +1,27 @@
import 'package:flutter/material.dart';
/// M3 Expressive Card variants.
import '../theme/app_surfaces.dart';
enum _M3CardVariant { elevated, filled, outlined }
/// Cyber/IT Card variants (Material 3).
///
/// Use these factory constructors to build semantically correct cards:
/// - [M3Card.elevated] — default, uses tonal surface tint (no hard shadow).
/// - [M3Card.filled] — uses surfaceContainerHighest for emphasis.
/// - [M3Card.outlined] — transparent fill with a subtle outline border.
/// - [M3Card.elevated] — default panel: tonal fill + hairline outline (flat).
/// - [M3Card.filled] — high-emphasis fill (caller passes [color]); no border.
/// - [M3Card.outlined] — transparent fill with a hairline outline border.
class M3Card extends StatelessWidget {
const M3Card._({
const M3Card._(
this._variant, {
required this.child,
this.color,
this.elevation,
this.shadowColor,
this.surfaceTintColor,
this.shape,
this.margin,
this.clipBehavior = Clip.none,
this.onTap,
});
/// Elevated card — tonal surface tint, minimal shadow.
/// Elevated card — default panel with a tonal fill + hairline outline.
/// Best for primary content surfaces (metric cards, detail panels).
factory M3Card.elevated({
required Widget child,
@@ -31,9 +32,8 @@ class M3Card extends StatelessWidget {
VoidCallback? onTap,
}) {
return M3Card._(
_M3CardVariant.elevated,
color: color,
elevation: 1,
shadowColor: Colors.transparent,
shape: shape,
margin: margin,
clipBehavior: clipBehavior,
@@ -42,8 +42,8 @@ class M3Card extends StatelessWidget {
);
}
/// Filled card — uses surfaceContainerHighest for high emphasis.
/// Best for summary cards, status counts, callout panels.
/// Filled card — a high-emphasis fill (caller passes a semantic [color] or a
/// surfaceContainer tone). No border. Best for summary/callout panels.
factory M3Card.filled({
required Widget child,
Color? color,
@@ -53,10 +53,8 @@ class M3Card extends StatelessWidget {
VoidCallback? onTap,
}) {
return M3Card._(
color: color, // caller passes surfaceContainerHighest or semantic color
elevation: 0,
shadowColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
_M3CardVariant.filled,
color: color,
shape: shape,
margin: margin,
clipBehavior: clipBehavior,
@@ -65,7 +63,7 @@ class M3Card extends StatelessWidget {
);
}
/// Outlined card — transparent fill with outline border.
/// Outlined card — transparent fill with a hairline outline border.
/// Best for list items, form sections, grouped content.
factory M3Card.outlined({
required Widget child,
@@ -76,10 +74,8 @@ class M3Card extends StatelessWidget {
VoidCallback? onTap,
}) {
return M3Card._(
_M3CardVariant.outlined,
color: color,
elevation: 0,
shadowColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
shape: shape,
margin: margin,
clipBehavior: clipBehavior,
@@ -88,11 +84,9 @@ class M3Card extends StatelessWidget {
);
}
final _M3CardVariant _variant;
final Widget child;
final Color? color;
final double? elevation;
final Color? shadowColor;
final Color? surfaceTintColor;
final ShapeBorder? shape;
final EdgeInsetsGeometry? margin;
final Clip clipBehavior;
@@ -101,36 +95,51 @@ class M3Card extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final radius = AppSurfaces.of(context).cardRadius;
final isDark = cs.brightness == Brightness.dark;
final Color resolvedColor;
final bool bordered;
switch (_variant) {
case _M3CardVariant.elevated:
resolvedColor =
color ??
(isDark ? cs.surfaceContainer : cs.surfaceContainerLowest);
bordered = true;
case _M3CardVariant.filled:
resolvedColor = color ?? cs.surfaceContainerHighest;
bordered = false;
case _M3CardVariant.outlined:
resolvedColor = color ?? Colors.transparent;
bordered = true;
}
// For outlined, we need the border side
final resolvedShape =
shape ??
(elevation == 0 && surfaceTintColor == Colors.transparent
? RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: (shadowColor == Colors.transparent && color == null)
? BorderSide(color: cs.outlineVariant)
: BorderSide.none,
)
: null);
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(radius),
side: bordered
? BorderSide(color: cs.outlineVariant)
: BorderSide.none,
);
final card = Card(
color: color,
elevation: elevation,
shadowColor: shadowColor,
surfaceTintColor: surfaceTintColor,
return Card(
color: resolvedColor,
elevation: 0,
shadowColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
shape: resolvedShape,
margin: margin ?? EdgeInsets.zero,
clipBehavior: clipBehavior,
clipBehavior: onTap != null && clipBehavior == Clip.none
? Clip.antiAlias
: clipBehavior,
child: onTap != null
? InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(radius),
child: child,
)
: child,
);
return card;
}
}