Files
platforms/apps/heqi_design_system/lib/src/components.dart

205 lines
5.6 KiB
Dart
Raw Normal View History

import 'package:flutter/material.dart';
import 'tokens.dart';
enum HeqiStatusTone { neutral, info, success, warning, danger }
class HeqiGutter extends StatelessWidget {
const HeqiGutter({required this.child, super.key});
final Widget child;
@override
Widget build(BuildContext context) => Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: HeqiSize.contentMaxWidth),
child: Padding(
padding: EdgeInsets.symmetric(
horizontal:
MediaQuery.sizeOf(context).width >= HeqiBreakpoints.compact
? HeqiSpacing.x7
: HeqiSpacing.x5,
),
child: child,
),
),
);
}
class HeqiSurfaceSection extends StatelessWidget {
const HeqiSurfaceSection({
required this.child,
this.padding = const EdgeInsets.all(HeqiSpacing.x5),
super.key,
});
final Widget child;
final EdgeInsetsGeometry padding;
@override
Widget build(BuildContext context) => DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(HeqiRadius.large),
border: Border.all(color: Theme.of(context).colorScheme.outlineVariant),
),
child: Padding(padding: padding, child: child),
);
}
class HeqiSectionHeader extends StatelessWidget {
const HeqiSectionHeader({
required this.title,
this.description,
this.trailing,
super.key,
});
final String title;
final String? description;
final Widget? trailing;
@override
Widget build(BuildContext context) => Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.titleLarge),
if (description != null) ...[
const SizedBox(height: HeqiSpacing.x1),
Text(
description!,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
],
),
),
?trailing,
],
);
}
class HeqiStatusPill extends StatelessWidget {
const HeqiStatusPill({
required this.label,
this.tone = HeqiStatusTone.neutral,
this.icon,
super.key,
});
final String label;
final HeqiStatusTone tone;
final IconData? icon;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final (foreground, background) = switch (tone) {
HeqiStatusTone.info => (
scheme.onPrimaryContainer,
scheme.primaryContainer,
),
HeqiStatusTone.success => (
scheme.onSecondaryContainer,
scheme.secondaryContainer,
),
HeqiStatusTone.warning => (
scheme.onTertiaryContainer,
scheme.tertiaryContainer,
),
HeqiStatusTone.danger => (scheme.onErrorContainer, scheme.errorContainer),
HeqiStatusTone.neutral => (
scheme.onSurfaceVariant,
scheme.surfaceContainerHighest,
),
};
return Semantics(
label: '状态:$label',
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: background,
borderRadius: BorderRadius.circular(HeqiRadius.small),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) ...[
Icon(icon, size: HeqiSize.iconSmall, color: foreground),
const SizedBox(width: HeqiSpacing.x1),
],
Text(
label,
style: Theme.of(
context,
).textTheme.labelSmall?.copyWith(color: foreground),
),
],
),
),
);
}
}
class HeqiMessageState extends StatelessWidget {
const HeqiMessageState({
required this.title,
required this.description,
this.onRetry,
this.icon = Icons.info_outline_rounded,
super.key,
});
final String title;
final String description;
final VoidCallback? onRetry;
final IconData icon;
@override
Widget build(BuildContext context) => Center(
child: Padding(
padding: const EdgeInsets.all(HeqiSpacing.x8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
shape: BoxShape.circle,
),
child: Icon(
icon,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: HeqiSpacing.x5),
Text(
title,
style: Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center,
),
const SizedBox(height: HeqiSpacing.x2),
Text(
description,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
if (onRetry != null) ...[
const SizedBox(height: HeqiSpacing.x5),
OutlinedButton.icon(
onPressed: onRetry,
icon: const Icon(Icons.refresh_rounded),
label: const Text('重新加载'),
),
],
],
),
),
);
}