feat(client): tablet 外壳 + 视觉 diff 工作流文档
- tablet_shell: 侧栏 232·4 项触控(minHeight 48, 字号 15) + 顶栏无主题切换 + 连接页双栏 对照 ui_kits/tablet/tabapp.jsx;home_shell 分发 tablet→TabletShell - nav_sidebar 加 dense 参数(desktop 紧凑 / tablet 触控) - content_top_bar 加 showThemeToggle + titleSize 参数 - tool/visual-diff.md: 截图 diff 工作流(design-distill 工具)+ 实现侧三路径 现状(golden 被 google_fonts 阻塞 / screencapture 受 macOS chrome 限制)+ 推荐 注: tablet 形态在 macOS 桌面平台不触发(只 desktop/mobile),运行验证需 iPad。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,10 +12,12 @@ import '../state/nodes_provider.dart';
|
||||
import 'pangolin_icons.dart';
|
||||
|
||||
class ContentTopBar extends ConsumerWidget {
|
||||
const ContentTopBar({super.key, required this.title, this.onBack});
|
||||
const ContentTopBar({super.key, required this.title, this.onBack, this.showThemeToggle = true, this.titleSize = 17});
|
||||
|
||||
final String title;
|
||||
final VoidCallback? onBack;
|
||||
final bool showThemeToggle;
|
||||
final double titleSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -40,7 +42,7 @@ class ContentTopBar extends ConsumerWidget {
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
Text(title,
|
||||
style: PangolinText.h3.copyWith(color: c.fg1, fontWeight: FontWeight.w700, fontSize: 17)),
|
||||
style: PangolinText.h3.copyWith(color: c.fg1, fontWeight: FontWeight.w700, fontSize: titleSize)),
|
||||
const Spacer(),
|
||||
Text(
|
||||
conn.phase == VpnPhase.on ? '● ${node.code}' : '○',
|
||||
@@ -50,16 +52,18 @@ class ContentTopBar extends ConsumerWidget {
|
||||
color: conn.phase == VpnPhase.on ? c.success : c.fg3,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
InkWell(
|
||||
onTap: () => ref.read(themeModeProvider.notifier).state =
|
||||
isDark ? ThemeMode.light : ThemeMode.dark,
|
||||
borderRadius: BorderRadius.circular(PangolinRadius.sm),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: Icon(isDark ? PangolinIcons.sun : PangolinIcons.moon, size: 18, color: c.fg2),
|
||||
if (showThemeToggle) ...[
|
||||
const SizedBox(width: 12),
|
||||
InkWell(
|
||||
onTap: () => ref.read(themeModeProvider.notifier).state =
|
||||
isDark ? ThemeMode.light : ThemeMode.dark,
|
||||
borderRadius: BorderRadius.circular(PangolinRadius.sm),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: Icon(isDark ? PangolinIcons.sun : PangolinIcons.moon, size: 18, color: c.fg2),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,11 +15,14 @@ import 'plan_badge_card.dart';
|
||||
typedef NavSidebarItem = ({IconData icon, String label, NavView view});
|
||||
|
||||
class NavSidebar extends ConsumerWidget {
|
||||
const NavSidebar({super.key, required this.items, this.width = 204});
|
||||
const NavSidebar({super.key, required this.items, this.width = 204, this.dense = true});
|
||||
|
||||
final List<NavSidebarItem> items;
|
||||
final double width;
|
||||
|
||||
/// dense=true 桌面紧凑(行高~36);dense=false 平板触控(行高≥48, 字号更大)。
|
||||
final bool dense;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final c = context.pangolin;
|
||||
@@ -32,17 +35,18 @@ class NavSidebar extends ConsumerWidget {
|
||||
color: c.bgSubtle,
|
||||
border: Border(right: BorderSide(color: c.border)),
|
||||
),
|
||||
padding: const EdgeInsets.fromLTRB(12, 14, 12, 14),
|
||||
padding: EdgeInsets.fromLTRB(dense ? 12 : 14, dense ? 14 : 18, dense ? 12 : 14, dense ? 14 : 16),
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||
// 品牌区
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(6, 2, 6, 16),
|
||||
padding: EdgeInsets.fromLTRB(6, 2, 6, dense ? 16 : 20),
|
||||
child: Row(children: [
|
||||
const PangolinMark(size: 28),
|
||||
const SizedBox(width: 9),
|
||||
PangolinMark(size: dense ? 28 : 30),
|
||||
SizedBox(width: dense ? 9 : 10),
|
||||
Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [
|
||||
Text(t.brand,
|
||||
style: PangolinText.h3.copyWith(color: c.fg1, fontWeight: FontWeight.w700, fontSize: 16, height: 1)),
|
||||
style: PangolinText.h3.copyWith(
|
||||
color: c.fg1, fontWeight: FontWeight.w700, fontSize: dense ? 16 : 17, height: 1)),
|
||||
const SizedBox(height: 3),
|
||||
Text('PANGOLIN',
|
||||
style: PangolinText.caption.copyWith(
|
||||
@@ -53,11 +57,12 @@ class NavSidebar extends ConsumerWidget {
|
||||
// Nav 列表
|
||||
for (final item in items)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 3),
|
||||
padding: EdgeInsets.only(bottom: dense ? 3 : 4),
|
||||
child: _NavItem(
|
||||
icon: item.icon,
|
||||
label: item.label,
|
||||
active: current == item.view,
|
||||
dense: dense,
|
||||
onTap: () => ref.read(navViewProvider.notifier).state = item.view,
|
||||
),
|
||||
),
|
||||
@@ -69,11 +74,12 @@ class NavSidebar extends ConsumerWidget {
|
||||
}
|
||||
|
||||
class _NavItem extends StatelessWidget {
|
||||
const _NavItem({required this.icon, required this.label, required this.active, required this.onTap});
|
||||
const _NavItem({required this.icon, required this.label, required this.active, required this.onTap, this.dense = true});
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final bool active;
|
||||
final VoidCallback onTap;
|
||||
final bool dense;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -84,17 +90,20 @@ class _NavItem extends StatelessWidget {
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 9),
|
||||
child: Row(children: [
|
||||
Icon(icon, size: 18, color: active ? c.accent : c.fg3),
|
||||
const SizedBox(width: 11),
|
||||
Text(label,
|
||||
style: PangolinText.sm.copyWith(
|
||||
color: active ? c.accent : c.fg2,
|
||||
fontSize: 13.5,
|
||||
fontWeight: active ? FontWeight.w600 : FontWeight.w500)),
|
||||
]),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minHeight: dense ? 0 : 48),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: dense ? 12 : 14, vertical: dense ? 9 : 13),
|
||||
child: Row(children: [
|
||||
Icon(icon, size: dense ? 18 : 20, color: active ? c.accent : c.fg3),
|
||||
SizedBox(width: dense ? 11 : 12),
|
||||
Text(label,
|
||||
style: PangolinText.sm.copyWith(
|
||||
color: active ? c.accent : c.fg2,
|
||||
fontSize: dense ? 13.5 : 15,
|
||||
fontWeight: active ? (dense ? FontWeight.w600 : FontWeight.w700) : FontWeight.w500)),
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user