depor_os/lib/widgets/sidebar/nav_item_tile.dart

150 lines
4.7 KiB
Dart
Raw Normal View History

2026-03-18 11:47:06 +00:00
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../models/nav_item.dart';
class NavItemTile extends StatelessWidget {
final NavItem item;
final String currentRoute;
const NavItemTile({
super.key,
required this.item,
required this.currentRoute,
});
@override
Widget build(BuildContext context) {
if (item.hasChildren) {
return _GroupTile(item: item, currentRoute: currentRoute);
}
return _LeafTile(item: item, currentRoute: currentRoute);
}
}
// ── Leaf (item sin hijos) ──────────────────────────────────────────────────
class _LeafTile extends StatelessWidget {
final NavItem item;
final String currentRoute;
const _LeafTile({required this.item, required this.currentRoute});
bool get _isActive =>
item.route != null && currentRoute == item.route;
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final tt = Theme.of(context).textTheme;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 1),
child: Material(
color: _isActive ? cs.secondaryContainer : Colors.transparent,
borderRadius: BorderRadius.circular(12),
child: InkWell(
borderRadius: BorderRadius.circular(12),
onTap: () => context.go(item.route!),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Row(
children: [
Icon(
item.icon,
size: 20,
color: _isActive ? cs.onSecondaryContainer : cs.onSurfaceVariant,
),
const SizedBox(width: 12),
Expanded(
child: Text(
item.label,
style: tt.bodyMedium?.copyWith(
color: _isActive ? cs.onSecondaryContainer : cs.onSurface,
fontWeight: _isActive ? FontWeight.w600 : FontWeight.normal,
),
),
),
],
),
),
),
),
);
}
}
// ── Group (item con hijos, expansible) ────────────────────────────────────
class _GroupTile extends StatefulWidget {
final NavItem item;
final String currentRoute;
const _GroupTile({required this.item, required this.currentRoute});
@override
State<_GroupTile> createState() => _GroupTileState();
}
class _GroupTileState extends State<_GroupTile> {
late bool _expanded;
@override
void initState() {
super.initState();
_expanded = widget.item.isActiveOrAncestor(widget.currentRoute);
}
@override
void didUpdateWidget(_GroupTile old) {
super.didUpdateWidget(old);
if (old.currentRoute != widget.currentRoute) {
if (widget.item.isActiveOrAncestor(widget.currentRoute)) {
_expanded = true;
}
}
}
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final tt = Theme.of(context).textTheme;
final isAncestor = widget.item.isActiveOrAncestor(widget.currentRoute);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 1),
child: Theme(
// Quita el separador por defecto del ExpansionTile
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
child: ExpansionTile(
initiallyExpanded: _expanded,
onExpansionChanged: (v) => setState(() => _expanded = v),
tilePadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 0),
childrenPadding: const EdgeInsets.only(left: 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
collapsedShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
backgroundColor: isAncestor ? cs.surfaceContainerHigh : Colors.transparent,
collapsedBackgroundColor: Colors.transparent,
leading: Icon(
widget.item.icon,
size: 20,
color: isAncestor ? cs.primary : cs.onSurfaceVariant,
),
title: Text(
widget.item.label,
style: tt.bodyMedium?.copyWith(
color: isAncestor ? cs.primary : cs.onSurface,
fontWeight: isAncestor ? FontWeight.w600 : FontWeight.normal,
),
),
children: widget.item.children
.map((child) => NavItemTile(
item: child,
currentRoute: widget.currentRoute,
))
.toList(),
),
),
);
}
}