137 lines
3.6 KiB
Dart
137 lines
3.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
/// M3 Expressive Card variants.
|
|
///
|
|
/// 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.
|
|
|
|
class M3Card extends StatelessWidget {
|
|
const M3Card._({
|
|
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.
|
|
/// Best for primary content surfaces (metric cards, detail panels).
|
|
factory M3Card.elevated({
|
|
required Widget child,
|
|
Color? color,
|
|
ShapeBorder? shape,
|
|
EdgeInsetsGeometry? margin,
|
|
Clip clipBehavior = Clip.none,
|
|
VoidCallback? onTap,
|
|
}) {
|
|
return M3Card._(
|
|
color: color,
|
|
elevation: 1,
|
|
shadowColor: Colors.transparent,
|
|
shape: shape,
|
|
margin: margin,
|
|
clipBehavior: clipBehavior,
|
|
onTap: onTap,
|
|
child: child,
|
|
);
|
|
}
|
|
|
|
/// Filled card — uses surfaceContainerHighest for high emphasis.
|
|
/// Best for summary cards, status counts, callout panels.
|
|
factory M3Card.filled({
|
|
required Widget child,
|
|
Color? color,
|
|
ShapeBorder? shape,
|
|
EdgeInsetsGeometry? margin,
|
|
Clip clipBehavior = Clip.none,
|
|
VoidCallback? onTap,
|
|
}) {
|
|
return M3Card._(
|
|
color: color, // caller passes surfaceContainerHighest or semantic color
|
|
elevation: 0,
|
|
shadowColor: Colors.transparent,
|
|
surfaceTintColor: Colors.transparent,
|
|
shape: shape,
|
|
margin: margin,
|
|
clipBehavior: clipBehavior,
|
|
onTap: onTap,
|
|
child: child,
|
|
);
|
|
}
|
|
|
|
/// Outlined card — transparent fill with outline border.
|
|
/// Best for list items, form sections, grouped content.
|
|
factory M3Card.outlined({
|
|
required Widget child,
|
|
Color? color,
|
|
ShapeBorder? shape,
|
|
EdgeInsetsGeometry? margin,
|
|
Clip clipBehavior = Clip.none,
|
|
VoidCallback? onTap,
|
|
}) {
|
|
return M3Card._(
|
|
color: color,
|
|
elevation: 0,
|
|
shadowColor: Colors.transparent,
|
|
surfaceTintColor: Colors.transparent,
|
|
shape: shape,
|
|
margin: margin,
|
|
clipBehavior: clipBehavior,
|
|
onTap: onTap,
|
|
child: child,
|
|
);
|
|
}
|
|
|
|
final Widget child;
|
|
final Color? color;
|
|
final double? elevation;
|
|
final Color? shadowColor;
|
|
final Color? surfaceTintColor;
|
|
final ShapeBorder? shape;
|
|
final EdgeInsetsGeometry? margin;
|
|
final Clip clipBehavior;
|
|
final VoidCallback? onTap;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final cs = Theme.of(context).colorScheme;
|
|
|
|
// 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);
|
|
|
|
final card = Card(
|
|
color: color,
|
|
elevation: elevation,
|
|
shadowColor: shadowColor,
|
|
surfaceTintColor: surfaceTintColor,
|
|
shape: resolvedShape,
|
|
margin: margin ?? EdgeInsets.zero,
|
|
clipBehavior: clipBehavior,
|
|
child: onTap != null
|
|
? InkWell(
|
|
onTap: onTap,
|
|
borderRadius: BorderRadius.circular(16),
|
|
child: child,
|
|
)
|
|
: child,
|
|
);
|
|
|
|
return card;
|
|
}
|
|
}
|