// pangolin_field.dart — 统一输入框容器组件。 // // 圆角容器(底色 + 可选描边/聚焦光环 + 可选上方标签 + 前置图标 + 后置 widget), // 内层放一个用 [bareInputDecoration] 的 TextField。底色由本容器统一提供,内层不再 // 自铺填充 → 杜绝「容器底色 ≠ TextField filled 底色」的两端一色中间另一色接缝。 // 聚焦态(accent 边 + 光环)也由容器负责。搜索框/兑换码/登录注册输入框共用此组件。 import 'package:flutter/material.dart'; import '../pangolin_theme.dart'; /// 内层 TextField 用的「裸」装饰:无边框、无填充。底色/边框/聚焦都交给 [PangolinFieldBox]。 InputDecoration bareInputDecoration({ EdgeInsetsGeometry contentPadding = const EdgeInsets.symmetric(vertical: 12), String? hintText, TextStyle? hintStyle, String? counterText, }) => kBareInput.copyWith( contentPadding: contentPadding, hintText: hintText, hintStyle: hintStyle, counterText: counterText, ); class PangolinFieldBox extends StatelessWidget { const PangolinFieldBox({ super.key, required this.child, this.fill, this.label, this.leading, this.trailing, this.focused = false, this.bordered = true, this.horizontalPadding = 14, }); /// 内层输入(用 [bareInputDecoration] 构造装饰)。 final Widget child; /// 容器底色;默认 [PangolinScheme.surface]。搜索框传 bgSubtle、兑换码传 bg。 final Color? fill; /// 上方标签(可空)。登录/注册字段用。 final String? label; /// 前置图标(可空)。 final IconData? leading; /// 后置 widget(可空):如密码显示切换、发送验证码。 final Widget? trailing; /// 聚焦态:accent 边 + 光环(仅 [bordered] 时生效)。 final bool focused; /// 是否描边。搜索框传 false(只有底色,无边)。 final bool bordered; final double horizontalPadding; @override Widget build(BuildContext context) { final c = context.pangolin; final box = AnimatedContainer( duration: const Duration(milliseconds: 160), padding: EdgeInsets.symmetric(horizontal: horizontalPadding), decoration: BoxDecoration( color: fill ?? c.surface, borderRadius: BorderRadius.circular(PangolinRadius.md), border: bordered ? Border.all(color: focused ? c.accent : c.borderStrong, width: 1.5) : null, boxShadow: (bordered && focused) ? [BoxShadow(color: c.ring, spreadRadius: 3)] : null, ), child: Row(children: [ if (leading != null) ...[Icon(leading, size: 18, color: c.fg3), const SizedBox(width: 10)], Expanded(child: child), if (trailing != null) trailing!, ]), ); if (label == null) return box; return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label!, style: PangolinText.caption.copyWith(color: c.fg2, fontWeight: FontWeight.w600)), const SizedBox(height: 7), box, ]); } }