feat: add base widgets with PageStandards (Button, TextField, Card, ListTile)

This commit is contained in:
Developer
2026-04-08 01:34:04 +08:00
parent 53f9697805
commit 4a9d3a19a2
4 changed files with 389 additions and 0 deletions

View File

@@ -0,0 +1,105 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
enum StandardButtonType { primary, secondary, outline, text }
class StandardButton extends StatelessWidget {
final String text;
final VoidCallback? onPressed;
final StandardButtonType type;
final bool isLoading;
final IconData? icon;
final bool isFullWidth;
final double? width;
final double? height;
const StandardButton({
super.key,
required this.text,
this.onPressed,
this.type = StandardButtonType.primary,
this.isLoading = false,
this.icon,
this.isFullWidth = false,
this.width,
this.height,
});
@override
Widget build(BuildContext context) {
final standards = PageStandards.of(context);
return GestureDetector(
onTap: isLoading ? null : onPressed,
child: Container(
width: isFullWidth ? double.infinity : width,
height: height ?? standards.scaledHeight(48),
padding: standards.scaledPadding(
EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
decoration: _buildDecoration(standards),
child: _buildChild(standards),
),
);
}
BoxDecoration _buildDecoration(PageStandards standards) {
switch (type) {
case StandardButtonType.primary:
return BoxDecoration(
color: standards.primaryColor,
borderRadius: BorderRadius.circular(standards.scaledRadius(12)),
);
case StandardButtonType.secondary:
return BoxDecoration(
color: standards.secondaryColor,
borderRadius: BorderRadius.circular(standards.scaledRadius(12)),
);
case StandardButtonType.outline:
return BoxDecoration(
border: Border.all(color: standards.primaryColor, width: 1.5),
borderRadius: BorderRadius.circular(standards.scaledRadius(12)),
);
case StandardButtonType.text:
return const BoxDecoration();
}
}
Widget _buildChild(PageStandards standards) {
if (isLoading) {
return Center(
child: CupertinoActivityIndicator(color: _getTextColor(standards)),
);
}
return Row(
mainAxisSize: isFullWidth ? MainAxisSize.max : MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (icon != null) ...[
Icon(icon, color: _getTextColor(standards), size: standards.fontSize + 4),
SizedBox(width: standards.scaledWidth(8)),
],
Text(
text,
style: standards.textStyle.copyWith(
color: _getTextColor(standards),
fontWeight: FontWeight.w600,
),
),
],
);
}
Color _getTextColor(PageStandards standards) {
switch (type) {
case StandardButtonType.primary:
case StandardButtonType.secondary:
return CupertinoColors.white;
case StandardButtonType.outline:
case StandardButtonType.text:
return standards.primaryColor;
}
}
}

View File

@@ -0,0 +1,62 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
class StandardCard extends StatelessWidget {
final Widget child;
final VoidCallback? onTap;
final EdgeInsetsGeometry? padding;
final EdgeInsetsGeometry? margin;
final Color? backgroundColor;
final double? borderRadius;
final bool showShadow;
final bool showBorder;
final Color? borderColor;
const StandardCard({
super.key,
required this.child,
this.onTap,
this.padding,
this.margin,
this.backgroundColor,
this.borderRadius,
this.showShadow = true,
this.showBorder = false,
this.borderColor,
});
@override
Widget build(BuildContext context) {
final standards = PageStandards.of(context);
return GestureDetector(
onTap: onTap,
child: Container(
margin: margin ?? standards.scaledPadding(EdgeInsets.all(8)),
padding: padding ?? standards.scaledPadding(EdgeInsets.all(16)),
decoration: BoxDecoration(
color: backgroundColor ?? standards.backgroundColor,
borderRadius: BorderRadius.circular(
borderRadius ?? standards.scaledRadius(16),
),
boxShadow: showShadow
? [
BoxShadow(
color: standards.textColor.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
]
: null,
border: showBorder
? Border.all(
color: borderColor ?? standards.textColor.withOpacity(0.1),
)
: null,
),
child: child,
),
);
}
}

View File

@@ -0,0 +1,93 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
class StandardListTile extends StatelessWidget {
final IconData? leadingIcon;
final Widget? leading;
final String title;
final String? subtitle;
final Widget? trailing;
final VoidCallback? onTap;
final bool showChevron;
final EdgeInsetsGeometry? padding;
const StandardListTile({
super.key,
this.leadingIcon,
this.leading,
required this.title,
this.subtitle,
this.trailing,
this.onTap,
this.showChevron = false,
this.padding,
});
@override
Widget build(BuildContext context) {
final standards = PageStandards.of(context);
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: Container(
padding: padding ??
standards.scaledPadding(
EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),
child: Row(
children: [
if (leading != null) leading!,
if (leadingIcon != null)
Container(
width: standards.scaledWidth(40),
height: standards.scaledHeight(40),
decoration: BoxDecoration(
color: standards.primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(standards.scaledRadius(10)),
),
child: Icon(
leadingIcon,
color: standards.primaryColor,
size: standards.fontSize + 8,
),
),
if (leadingIcon != null || leading != null)
SizedBox(width: standards.scaledWidth(12)),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: standards.textStyle.copyWith(
fontWeight: FontWeight.w500,
),
),
if (subtitle != null) ...[
SizedBox(height: standards.scaledHeight(2)),
Text(
subtitle!,
style: standards.textStyle.copyWith(
color: standards.textColor.withOpacity(0.6),
fontSize: standards.fontSize - 2,
),
),
],
],
),
),
if (trailing != null) trailing!,
if (showChevron)
Icon(
CupertinoIcons.chevron_right,
color: standards.textColor.withOpacity(0.4),
size: standards.fontSize + 4,
),
],
),
),
);
}
}

View File

@@ -0,0 +1,129 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mom_kitchen/src/standards/page_standards.dart';
class StandardTextField extends StatelessWidget {
final String? placeholder;
final String? initialValue;
final ValueChanged<String>? onChanged;
final VoidCallback? onEditingComplete;
final ValueChanged<String>? onSubmitted;
final bool obscureText;
final TextInputType? keyboardType;
final TextEditingController? controller;
final FocusNode? focusNode;
final IconData? prefixIcon;
final IconData? suffixIcon;
final VoidCallback? onSuffixIconPressed;
final String? errorText;
final int? maxLines;
final bool enabled;
final bool autofocus;
const StandardTextField({
super.key,
this.placeholder,
this.initialValue,
this.onChanged,
this.onEditingComplete,
this.onSubmitted,
this.obscureText = false,
this.keyboardType,
this.controller,
this.focusNode,
this.prefixIcon,
this.suffixIcon,
this.onSuffixIconPressed,
this.errorText,
this.maxLines = 1,
this.enabled = true,
this.autofocus = false,
});
@override
Widget build(BuildContext context) {
final standards = PageStandards.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: standards.backgroundColor,
borderRadius: BorderRadius.circular(standards.scaledRadius(12)),
border: Border.all(
color: errorText != null
? standards.secondaryColor
: standards.textColor.withOpacity(0.2),
),
),
child: Row(
children: [
if (prefixIcon != null)
Padding(
padding: EdgeInsets.only(left: standards.scaledWidth(12)),
child: Icon(
prefixIcon,
color: standards.textColor.withOpacity(0.5),
size: standards.fontSize + 4,
),
),
Expanded(
child: CupertinoTextField(
controller: controller,
focusNode: focusNode,
placeholder: placeholder,
placeholderStyle: TextStyle(
color: standards.textColor.withOpacity(0.5),
fontSize: standards.fontSize,
),
style: TextStyle(
color: standards.textColor,
fontSize: standards.fontSize,
),
onChanged: onChanged,
onEditingComplete: onEditingComplete,
onSubmitted: onSubmitted,
obscureText: obscureText,
keyboardType: keyboardType,
maxLines: maxLines,
enabled: enabled,
autofocus: autofocus,
decoration: const BoxDecoration(),
padding: standards.scaledPadding(
EdgeInsets.symmetric(horizontal: 12, vertical: 12),
),
),
),
if (suffixIcon != null)
GestureDetector(
onTap: onSuffixIconPressed,
child: Padding(
padding: EdgeInsets.only(right: standards.scaledWidth(12)),
child: Icon(
suffixIcon,
color: standards.textColor.withOpacity(0.5),
size: standards.fontSize + 4,
),
),
),
],
),
),
if (errorText != null) ...[
SizedBox(height: standards.scaledHeight(4)),
Padding(
padding: EdgeInsets.only(left: standards.scaledWidth(12)),
child: Text(
errorText!,
style: TextStyle(
color: standards.secondaryColor,
fontSize: standards.fontSize - 2,
),
),
),
],
],
);
}
}