119 lines
3.1 KiB
Dart
119 lines
3.1 KiB
Dart
|
|
/// 轻量分段选择器(UI06 对齐 app-preview .tabs)。
|
|||
|
|
///
|
|||
|
|
/// 形态:#EDF1F6 轨道、padding 4、整体圆角 9;选中实底、未选次文字色。
|
|||
|
|
/// 两种选中风格:
|
|||
|
|
/// - [SegmentTabsStyle.solid]:主蓝实底白字(上传中心筛选等状态筛选);
|
|||
|
|
/// - [SegmentTabsStyle.light]:#DBEAFF 底 + #145BB9 字(Details/Access 类
|
|||
|
|
/// 内容页签——与状态筛选区分,见 app-preview .tabs.light)。
|
|||
|
|
///
|
|||
|
|
/// 替换默认 [SegmentedButton](M3 黑边胶囊形态与示例不符)。
|
|||
|
|
library;
|
|||
|
|
|
|||
|
|
import 'package:flutter/material.dart';
|
|||
|
|
|
|||
|
|
import '../tokens.dart';
|
|||
|
|
|
|||
|
|
enum SegmentTabsStyle { solid, light }
|
|||
|
|
|
|||
|
|
class SegmentTabs<T> extends StatelessWidget {
|
|||
|
|
const SegmentTabs({
|
|||
|
|
required this.value,
|
|||
|
|
required this.onChanged,
|
|||
|
|
required this.segments,
|
|||
|
|
this.style = SegmentTabsStyle.solid,
|
|||
|
|
super.key,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/// 当前值;null = 无高亮(次级筛选接管时的中间态)。
|
|||
|
|
final T? value;
|
|||
|
|
final ValueChanged<T> onChanged;
|
|||
|
|
|
|||
|
|
/// 选项:值 + 本地化标签。
|
|||
|
|
final List<(T, String)> segments;
|
|||
|
|
|
|||
|
|
final SegmentTabsStyle style;
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
Widget build(BuildContext context) {
|
|||
|
|
final theme = Theme.of(context);
|
|||
|
|
final light = style == SegmentTabsStyle.light;
|
|||
|
|
return Container(
|
|||
|
|
padding: const EdgeInsets.all(4),
|
|||
|
|
decoration: BoxDecoration(
|
|||
|
|
color: kColorSegTrack,
|
|||
|
|
borderRadius: BorderRadius.circular(9),
|
|||
|
|
),
|
|||
|
|
child: Row(
|
|||
|
|
children: [
|
|||
|
|
for (final (v, label) in segments)
|
|||
|
|
Expanded(
|
|||
|
|
child: _TabButton(
|
|||
|
|
label: label,
|
|||
|
|
selected: v == value,
|
|||
|
|
light: light,
|
|||
|
|
primary: theme.colorScheme.primary,
|
|||
|
|
onTap: () => onChanged(v),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class _TabButton extends StatelessWidget {
|
|||
|
|
const _TabButton({
|
|||
|
|
required this.label,
|
|||
|
|
required this.selected,
|
|||
|
|
required this.light,
|
|||
|
|
required this.primary,
|
|||
|
|
required this.onTap,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
final String label;
|
|||
|
|
final bool selected;
|
|||
|
|
final bool light;
|
|||
|
|
final Color primary;
|
|||
|
|
final VoidCallback onTap;
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
Widget build(BuildContext context) {
|
|||
|
|
final Color bg;
|
|||
|
|
final Color fg;
|
|||
|
|
if (selected) {
|
|||
|
|
bg = light ? kColorSegLightActive : primary;
|
|||
|
|
fg = light ? kColorSegLightActiveText : Colors.white;
|
|||
|
|
} else {
|
|||
|
|
bg = Colors.transparent;
|
|||
|
|
fg = kColorTextSecondary;
|
|||
|
|
}
|
|||
|
|
return Semantics(
|
|||
|
|
button: true,
|
|||
|
|
selected: selected,
|
|||
|
|
child: InkWell(
|
|||
|
|
onTap: onTap,
|
|||
|
|
borderRadius: BorderRadius.circular(7),
|
|||
|
|
child: Container(
|
|||
|
|
alignment: Alignment.center,
|
|||
|
|
height: 36,
|
|||
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
|
|||
|
|
decoration: BoxDecoration(
|
|||
|
|
color: bg,
|
|||
|
|
borderRadius: BorderRadius.circular(7),
|
|||
|
|
),
|
|||
|
|
child: Text(
|
|||
|
|
label,
|
|||
|
|
maxLines: 1,
|
|||
|
|
overflow: TextOverflow.ellipsis,
|
|||
|
|
style: TextStyle(
|
|||
|
|
fontSize: 12,
|
|||
|
|
fontWeight: selected ? FontWeight.w600 : FontWeight.w500,
|
|||
|
|
color: fg,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|