Skip to content

Instantly share code, notes, and snippets.

@MarkRatjens
Created September 12, 2025 03:51
Show Gist options
  • Select an option

  • Save MarkRatjens/9dcd60141d68ab2f25c409bba1442615 to your computer and use it in GitHub Desktop.

Select an option

Save MarkRatjens/9dcd60141d68ab2f25c409bba1442615 to your computer and use it in GitHub Desktop.
ProjectDropdown
class ProjectDropdown extends StatelessWidget {
final VoidCallback? onCreateProject;
final VoidCallback? onOpenProject;
final VoidCallback? onCloseProject;
const ProjectDropdown({
this.onCreateProject,
this.onOpenProject,
this.onCloseProject,
});
Shipment get shipment => ProjectRepository.shipment;
@override
Widget build(BuildContext context) => AnimatedBuilder(
animation: shipment.notifier,
builder: (context, child) => dropdownContent,
);
Widget get dropdownContent => Padding(
padding: dropdownPadding,
child: PopupMenuButton<String>(
offset: dropdownOffset,
itemBuilder: (context) => menuItems,
onSelected: executeProjectAction,
child: dropdownButton,
),
);
EdgeInsets get dropdownPadding => const EdgeInsets.symmetric(horizontal: 8);
Offset get dropdownOffset => const Offset(0, 40);
List<PopupMenuEntry<String>> get menuItems => [
if (currentProject != null) ...[
PopupMenuItem<String>(
value: 'current',
child: currentProjectItem,
),
const PopupMenuDivider(),
],
PopupMenuItem<String>(
value: 'create',
child: createMenuItem,
),
PopupMenuItem<String>(
value: 'open',
child: openMenuItem,
),
if (currentProject != null) ...[
const PopupMenuDivider(),
PopupMenuItem<String>(
value: 'close',
child: closeMenuItem,
),
],
];
Project? get currentProject => shipment.focus as Project?;
Widget get currentProjectItem => Row(
children: [
const Icon(Icons.folder_open, size: 16),
const SizedBox(width: 8),
Expanded(child: currentProjectDetails),
],
);
Widget get currentProjectDetails => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
currentProject!.displayTitle,
style: const TextStyle(fontWeight: FontWeight.w500),
),
if (currentProject!.projectDescription.isNotEmpty)
Text(
currentProject!.projectDescription,
style: TextStyle(
fontSize: 12,
color: WisperaTheme.color('onPrimary').withAlpha(153),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
);
Widget get createMenuItem => menuItem(Icons.add, 'Create New Project');
Widget get openMenuItem => menuItem(Icons.folder_open, 'Open Project...');
Widget get closeMenuItem => menuItem(Icons.close, 'Close Project');
Widget menuItem(IconData icon, String text) => Row(
children: [
Icon(icon, size: 16),
const SizedBox(width: 8),
Text(text),
],
);
Widget get dropdownButton => Container(
padding: buttonPadding,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
currentProject?.displayTitle ?? 'Project',
style: buttonTextStyle,
),
const SizedBox(width: 4),
Icon(
Icons.arrow_drop_down,
color: WisperaTheme.color('onPrimary').withAlpha(179),
size: 16,
),
],
),
);
EdgeInsets get buttonPadding => const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
);
TextStyle get buttonTextStyle => TextStyle(
color: WisperaTheme.color('onPrimary').withAlpha(179),
);
void executeProjectAction(String action) {
switch (action) {
case 'create':
onCreateProject?.call();
break;
case 'open':
onOpenProject?.call();
break;
case 'close':
closeCurrentProject();
break;
case 'current':
// Do nothing - just showing current project info
break;
}
}
void closeCurrentProject() {
shipment.clearFocus(shouldNotify: true);
onCloseProject?.call();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment