showNiceModal<T> function
Generic modal dialog with header, body, and footer actions.
Implementation
Future<T?> showNiceModal<T>({
required BuildContext context,
required String title,
required Widget body,
List<Widget>? actions,
bool dismissible = true,
double maxWidth = 520,
IconData? icon,
}) {
final theme = NiceTheme.of(context);
return showDialog<T>(
context: context,
barrierDismissible: dismissible,
builder: (ctx) => Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(theme.borderRadius * 2),
),
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: maxWidth),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Header
Container(
padding: EdgeInsets.all(theme.spacing * 2),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: theme.colors.border, width: 0.5),
),
),
child: Row(
children: [
if (icon != null) ...[
Icon(icon, color: theme.colors.primary, size: 22),
const SizedBox(width: 8),
],
Expanded(
child: Text(title, style: theme.typography.label),
),
if (dismissible)
IconButton(
onPressed: () => Navigator.of(ctx).pop(),
icon: const Icon(Icons.close, size: 20),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
],
),
),
// Body
Flexible(
child: SingleChildScrollView(
padding: EdgeInsets.all(theme.spacing * 2),
child: body,
),
),
// Footer
if (actions != null && actions.isNotEmpty)
Container(
padding: EdgeInsets.all(theme.spacing * 1.5),
decoration: BoxDecoration(
border: Border(
top: BorderSide(color: theme.colors.border, width: 0.5),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
for (int i = 0; i < actions.length; i++) ...[
if (i > 0) const SizedBox(width: 8),
actions[i],
],
],
),
),
],
),
),
),
);
}