diff --git a/lib/core/theme/querya_material_theme.dart b/lib/core/theme/querya_material_theme.dart index e7c8d2a..6ab54c6 100644 --- a/lib/core/theme/querya_material_theme.dart +++ b/lib/core/theme/querya_material_theme.dart @@ -21,6 +21,8 @@ material.ThemeData materialThemeFromQuerya(ColorScheme scheme) { return material.ThemeData( useMaterial3: true, + // Safety net: stray Material popups must not ripple. + splashFactory: material.NoSplash.splashFactory, colorScheme: materialScheme, dialogTheme: material.DialogThemeData(backgroundColor: scheme.popover), textTheme: material.TextTheme( diff --git a/lib/features/workspace/data_grid_filter_bar.dart b/lib/features/workspace/data_grid_filter_bar.dart index 117bf43..545e7b2 100644 --- a/lib/features/workspace/data_grid_filter_bar.dart +++ b/lib/features/workspace/data_grid_filter_bar.dart @@ -221,7 +221,9 @@ class _DataGridFilterBarState extends material.State { showWhenUnlinked: false, offset: const material.Offset(24, 32), child: material.Material( - elevation: 4, + elevation: 2, + shadowColor: const material.Color(0x1F000000), + surfaceTintColor: material.Colors.transparent, borderRadius: material.BorderRadius.circular(6), color: cs.popover, child: material.Container( @@ -239,7 +241,8 @@ class _DataGridFilterBarState extends material.State { itemBuilder: (ctx, i) { final s = _suggestions[i]; final isHighlighted = i == _highlightedIndex; - return material.InkWell( + return material.GestureDetector( + behavior: material.HitTestBehavior.opaque, onTap: () => _applySuggestion(s), child: material.Container( padding: const material.EdgeInsets.symmetric( @@ -285,13 +288,13 @@ class _DataGridFilterBarState extends material.State { return material.Icon( material.Icons.code_rounded, size: 13, - color: material.Colors.amber.shade700, + color: cs.mutedForeground, ); case FilterSuggestionKind.keyword: return material.Icon( material.Icons.vpn_key_outlined, size: 13, - color: material.Colors.green.shade600, + color: cs.mutedForeground.withValues(alpha: 0.8), ); } } diff --git a/lib/features/workspace/data_grid_groupings_view.dart b/lib/features/workspace/data_grid_groupings_view.dart index 8d115f6..e9c12c9 100644 --- a/lib/features/workspace/data_grid_groupings_view.dart +++ b/lib/features/workspace/data_grid_groupings_view.dart @@ -5,6 +5,7 @@ import 'package:querya_desktop/core/motion/querya_motion.dart'; import 'package:querya_desktop/core/motion/querya_motion_context.dart'; import 'package:querya_desktop/features/workspace/grid_groupings_engine.dart'; import 'package:querya_desktop/features/workspace/result_grid_view.dart'; +import 'package:querya_desktop/shared/widgets/querya_dropdown.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; /// Groupings / Pivot view tab for tabular data with hierarchical grouping and custom aggregations. @@ -139,21 +140,17 @@ class _DataGridGroupingsViewState const Gap(6), const Text('Group:').small().semiBold(), const Gap(6), - material.DropdownButton( + QueryaDropdown( + compact: true, value: _selectedColIndices.isNotEmpty && _selectedColIndices.first < widget.columns.length ? _selectedColIndices.first : 0, - isDense: true, - underline: const material.SizedBox.shrink(), - style: TextStyle(fontSize: 12, color: cs.foreground), - items: List.generate(widget.columns.length, (i) { - return material.DropdownMenuItem( - value: i, - child: Text(widget.columns[i]), - ); - }), - onChanged: (idx) { + items: [ + for (var i = 0; i < widget.columns.length; i++) + QueryaDropdownItem(value: i, label: widget.columns[i]), + ], + onSelected: (idx) { if (idx != null) { setState(() { _selectedColIndices = [idx]; @@ -177,18 +174,14 @@ class _DataGridGroupingsViewState // Aggregation Selector const Text('Agg:').small().semiBold(), const Gap(6), - material.DropdownButton( + QueryaDropdown( + compact: true, value: _aggType, - isDense: true, - underline: const material.SizedBox.shrink(), - style: TextStyle(fontSize: 12, color: cs.foreground), - items: GroupingAggType.values.map((t) { - return material.DropdownMenuItem( - value: t, - child: Text(t.label), - ); - }).toList(), - onChanged: (val) { + items: [ + for (final t in GroupingAggType.values) + QueryaDropdownItem(value: t, label: t.label), + ], + onSelected: (val) { if (val != null) { setState(() { _aggType = val; @@ -199,21 +192,17 @@ class _DataGridGroupingsViewState ), if (_aggType != GroupingAggType.count) ...[ const Gap(4), - material.DropdownButton( + QueryaDropdown( + compact: true, value: _aggTargetColIndex != null && _aggTargetColIndex! < widget.columns.length - ? _aggTargetColIndex + ? _aggTargetColIndex! : 0, - isDense: true, - underline: const material.SizedBox.shrink(), - style: TextStyle(fontSize: 12, color: cs.foreground), - items: List.generate(widget.columns.length, (i) { - return material.DropdownMenuItem( - value: i, - child: Text(widget.columns[i]), - ); - }), - onChanged: (idx) { + items: [ + for (var i = 0; i < widget.columns.length; i++) + QueryaDropdownItem(value: i, label: widget.columns[i]), + ], + onSelected: (idx) { if (idx != null) { setState(() { _aggTargetColIndex = idx; @@ -237,18 +226,14 @@ class _DataGridGroupingsViewState // Sort Selector const Text('Sort:').small().semiBold(), const Gap(6), - material.DropdownButton( + QueryaDropdown( + compact: true, value: _sortBy, - isDense: true, - underline: const material.SizedBox.shrink(), - style: TextStyle(fontSize: 12, color: cs.foreground), - items: GroupSortBy.values.map((s) { - return material.DropdownMenuItem( - value: s, - child: Text(s.label), - ); - }).toList(), - onChanged: (val) { + items: [ + for (final t in GroupSortBy.values) + QueryaDropdownItem(value: t, label: t.label), + ], + onSelected: (val) { if (val != null) { setState(() { _sortBy = val; diff --git a/lib/features/workspace/data_grid_value_panel.dart b/lib/features/workspace/data_grid_value_panel.dart index e24720f..8ce9113 100644 --- a/lib/features/workspace/data_grid_value_panel.dart +++ b/lib/features/workspace/data_grid_value_panel.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart' as material; import 'package:flutter/services.dart'; import 'package:querya_desktop/core/editor/querya_code_editor.dart'; import 'package:querya_desktop/core/editor/querya_code_language.dart'; +import 'package:querya_desktop/shared/widgets/querya_dropdown.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'xml_html_formatter.dart'; @@ -315,39 +316,22 @@ class _DataGridValuePanelState extends material.State { child: material.Row( children: [ // Language Dropdown / Pill - material.DropdownButton( + QueryaDropdown( + compact: true, value: _selectedLanguage, - isDense: true, - underline: const material.SizedBox(), - icon: const material.Icon(material.Icons.arrow_drop_down, size: 16), - style: TextStyle( - fontSize: 11, - color: cs.foreground, - fontWeight: FontWeight.w600, - ), items: const [ - material.DropdownMenuItem( - value: ValuePanelLanguage.auto, - child: Text('Auto'), - ), - material.DropdownMenuItem( - value: ValuePanelLanguage.json, - child: Text('JSON'), - ), - material.DropdownMenuItem( - value: ValuePanelLanguage.xml, - child: Text('XML/HTML'), - ), - material.DropdownMenuItem( - value: ValuePanelLanguage.sql, - child: Text('SQL'), - ), - material.DropdownMenuItem( - value: ValuePanelLanguage.text, - child: Text('Plain Text'), - ), + QueryaDropdownItem( + value: ValuePanelLanguage.auto, label: 'Auto'), + QueryaDropdownItem( + value: ValuePanelLanguage.json, label: 'JSON'), + QueryaDropdownItem( + value: ValuePanelLanguage.xml, label: 'XML/HTML'), + QueryaDropdownItem( + value: ValuePanelLanguage.sql, label: 'SQL'), + QueryaDropdownItem( + value: ValuePanelLanguage.text, label: 'Plain Text'), ], - onChanged: (val) { + onSelected: (val) { if (val != null) { setState(() => _selectedLanguage = val); _validateContent(); diff --git a/lib/shared/widgets/export_menu_button.dart b/lib/shared/widgets/export_menu_button.dart index 7a89cb3..af5c7a8 100644 --- a/lib/shared/widgets/export_menu_button.dart +++ b/lib/shared/widgets/export_menu_button.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart' as material; import 'package:querya_desktop/shared/services/data_export_service.dart'; +import 'package:querya_desktop/shared/widgets/querya_action_menu.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; class ExportMenuButton extends StatelessWidget { @@ -18,84 +19,37 @@ class ExportMenuButton extends StatelessWidget { @override Widget build(BuildContext context) { - return material.PopupMenuButton( - tooltip: label, + return QueryaActionMenu( onSelected: onSelected, - itemBuilder: (context) => [ - material.PopupMenuItem( + items: [ + QueryaActionMenuItem( value: DataExportFormat.csv, - child: material.Row( - children: [ - const material.Icon( - material.Icons.table_chart_outlined, - size: 16, - ), - const material.SizedBox(width: 8), - material.Text(isSave ? 'CSV (.csv)' : 'Copy as CSV'), - ], - ), + icon: material.Icons.table_chart_outlined, + label: isSave ? 'CSV (.csv)' : 'Copy as CSV', ), - material.PopupMenuItem( + QueryaActionMenuItem( value: DataExportFormat.json, - child: material.Row( - children: [ - const material.Icon( - material.Icons.data_object_rounded, - size: 16, - ), - const material.SizedBox(width: 8), - material.Text(isSave ? 'JSON (.json)' : 'Copy as JSON'), - ], - ), + icon: material.Icons.data_object_rounded, + label: isSave ? 'JSON (.json)' : 'Copy as JSON', ), - material.PopupMenuItem( + QueryaActionMenuItem( value: DataExportFormat.markdown, - child: material.Row( - children: [ - const material.Icon(material.Icons.code_rounded, size: 16), - const material.SizedBox(width: 8), - material.Text( - isSave ? 'Markdown Table (.md)' : 'Copy as Markdown Table', - ), - ], - ), + icon: material.Icons.code_rounded, + label: isSave ? 'Markdown Table (.md)' : 'Copy as Markdown Table', ), - material.PopupMenuItem( + QueryaActionMenuItem( value: DataExportFormat.sqlDump, - child: material.Row( - children: [ - const material.Icon(material.Icons.storage_rounded, size: 16), - const material.SizedBox(width: 8), - material.Text( - isSave ? 'SQL INSERT Dump (.sql)' : 'Copy as SQL Dump', - ), - ], - ), + icon: material.Icons.storage_rounded, + label: isSave ? 'SQL INSERT Dump (.sql)' : 'Copy as SQL Dump', ), ], - child: material.Container( - padding: const material.EdgeInsets.symmetric( - horizontal: 10, - vertical: 6, - ), - decoration: material.BoxDecoration( - border: material.Border.all( - color: Theme.of(context).colorScheme.border, - ), - borderRadius: material.BorderRadius.circular(6), - ), - child: material.Row( - mainAxisSize: material.MainAxisSize.min, - children: [ - material.Icon( - icon, - size: 14, - color: Theme.of(context).colorScheme.foreground, - ), - const material.SizedBox(width: 6), - Text(label).small(), - ], - ), + child: material.Row( + mainAxisSize: material.MainAxisSize.min, + children: [ + material.Icon(icon, size: 14), + const material.SizedBox(width: 6), + Text(label), + ], ), ); } diff --git a/lib/shared/widgets/querya_action_menu.dart b/lib/shared/widgets/querya_action_menu.dart new file mode 100644 index 0000000..0d32c02 --- /dev/null +++ b/lib/shared/widgets/querya_action_menu.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/core/layout/ui_scale.dart'; +import 'package:querya_desktop/shared/widgets/querya_dropdown_tokens.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; + +/// One command row in [QueryaActionMenu]. +class QueryaActionMenuItem { + const QueryaActionMenuItem({ + required this.value, + required this.label, + this.icon, + }); + + final T value; + final String label; + final material.IconData? icon; +} + +/// "Button that opens a list of commands" (Export / Copy). +/// +/// Same popup surface as `QueryaDropdown` (border, radius, soft shadow, +/// compact rows, no ripple); trigger is a regular shadcn [OutlineButton]. +class QueryaActionMenu extends material.StatelessWidget { + const QueryaActionMenu({ + super.key, + required this.items, + required this.onSelected, + required this.child, + }); + + final List> items; + final material.ValueChanged onSelected; + + /// Trigger content (label / icon row). + final material.Widget child; + + @override + material.Widget build(material.BuildContext context) { + final cs = Theme.of(context).colorScheme; + final radius = context.scaled(QueryaDropdownTokens.menuBorderRadius); + return material.MenuAnchor( + alignmentOffset: material.Offset( + 0, + context.scaled(QueryaDropdownTokens.menuAlignmentOffset.dy), + ), + consumeOutsideTap: true, + style: material.MenuStyle( + backgroundColor: material.WidgetStatePropertyAll(cs.popover), + surfaceTintColor: material.WidgetStatePropertyAll(cs.popover), + elevation: const material.WidgetStatePropertyAll( + QueryaDropdownTokens.menuElevation, + ), + shadowColor: const material.WidgetStatePropertyAll( + QueryaDropdownTokens.menuShadowColor, + ), + padding: const material.WidgetStatePropertyAll( + QueryaDropdownTokens.menuPadding, + ), + shape: material.WidgetStatePropertyAll( + material.RoundedRectangleBorder( + borderRadius: material.BorderRadius.circular(radius), + side: material.BorderSide(color: cs.border), + ), + ), + ), + menuChildren: [ + for (final item in items) + material.MenuItemButton( + style: material.MenuItemButton.styleFrom( + minimumSize: material.Size( + 0, + QueryaDropdownTokens.scaledMenuItemHeight(context), + ), + padding: material.EdgeInsets.symmetric( + horizontal: context.scaled(8), + ), + foregroundColor: cs.popoverForeground, + overlayColor: cs.accent.withValues(alpha: 0.14), + shape: material.RoundedRectangleBorder( + borderRadius: material.BorderRadius.circular(radius), + ), + ), + leadingIcon: item.icon == null + ? null + : material.Icon( + item.icon, + size: context.scaled(16), + color: cs.mutedForeground, + ), + onPressed: () => onSelected(item.value), + child: material.Text( + item.label, + style: QueryaDropdownTokens.menuItemTextStyle( + context, + cs.popoverForeground, + selected: false, + ), + ), + ), + ], + builder: (context, controller, _) => OutlineButton( + size: ButtonSize.small, + onPressed: () => + controller.isOpen ? controller.close() : controller.open(), + child: child, + ), + ); + } +} diff --git a/lib/shared/widgets/querya_dropdown.dart b/lib/shared/widgets/querya_dropdown.dart index 166c4a2..79c89ef 100644 --- a/lib/shared/widgets/querya_dropdown.dart +++ b/lib/shared/widgets/querya_dropdown.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart' as material; +import 'package:flutter/services.dart'; import 'package:querya_desktop/core/layout/ui_scale.dart'; import 'package:querya_desktop/core/motion/querya_motion.dart'; import 'package:querya_desktop/core/motion/querya_motion_context.dart'; @@ -45,6 +46,7 @@ class QueryaDropdown extends material.StatefulWidget { this.alignmentOffset = QueryaDropdownTokens.menuAlignmentOffset, this.menuMaxHeight = QueryaDropdownTokens.menuMaxHeight, this.hint, + this.compact = false, }); final T value; @@ -58,6 +60,9 @@ class QueryaDropdown extends material.StatefulWidget { final double menuMaxHeight; final String? hint; + /// Smaller trigger and rows for toolbars / panel headers. + final bool compact; + @override material.State> createState() => _QueryaDropdownState(); } @@ -65,6 +70,7 @@ class QueryaDropdown extends material.StatefulWidget { class _QueryaDropdownState extends material.State> { late material.MenuController _controller; bool _triggerHovered = false; + bool _triggerFocused = false; final material.ValueNotifier _menuOpen = material.ValueNotifier(false); List? _cachedMenuChildren; List>? _cachedMenuItems; @@ -147,7 +153,11 @@ class _QueryaDropdownState extends material.State> { label, maxLines: 1, overflow: material.TextOverflow.ellipsis, - style: QueryaDropdownTokens.triggerTextStyle(context, textColor), + style: QueryaDropdownTokens.triggerTextStyle( + context, + textColor, + compact: widget.compact, + ), ); if (expand) { return material.Expanded(child: text); @@ -168,6 +178,7 @@ class _QueryaDropdownState extends material.State> { selected: item.value == widget.value, enabled: widget.enabled && item.enabled, colorScheme: cs, + compact: widget.compact, onPick: () { widget.onSelected(item.value); unawaited(_closeWithExit()); @@ -183,9 +194,12 @@ class _QueryaDropdownState extends material.State> { required double? fieldWidth, }) { final borderColor = widget.enabled - ? (_triggerHovered ? cs.ring : cs.border) + ? (_triggerHovered || _triggerFocused ? cs.ring : cs.border) : cs.border.withValues(alpha: 0.4); - final triggerHeight = QueryaDropdownTokens.scaledTriggerHeight(context); + final triggerHeight = QueryaDropdownTokens.scaledTriggerHeight( + context, + compact: widget.compact, + ); final chevronGap = context.scaled(QueryaDropdownTokens.triggerChevronGap); final chevronSize = context.scaled(QueryaDropdownTokens.triggerChevronSize); final radius = context.scaled(QueryaDropdownTokens.menuBorderRadius); @@ -203,7 +217,10 @@ class _QueryaDropdownState extends material.State> { duration: context.motionDuration(QueryaMotion.fast), curve: context.motionCurve(QueryaMotion.enter), height: triggerHeight, - padding: QueryaDropdownTokens.scaledTriggerPadding(context), + padding: QueryaDropdownTokens.scaledTriggerPadding( + context, + compact: widget.compact, + ), decoration: material.BoxDecoration( color: _triggerHovered ? cs.muted.withValues(alpha: 0.28) @@ -236,21 +253,38 @@ class _QueryaDropdownState extends material.State> { ), ); - return material.Material( - type: material.MaterialType.transparency, - child: material.InkWell( - onTap: widget.enabled - ? () { - if (_closingWithExit) { - _cancelExit(); - } else if (controller.isOpen) { - unawaited(_closeWithExit()); - } else { - controller.open(); - } - } - : null, - borderRadius: material.BorderRadius.circular(radius), + void toggle() { + if (_closingWithExit) { + _cancelExit(); + } else if (controller.isOpen) { + unawaited(_closeWithExit()); + } else { + controller.open(); + } + } + + // No ink/ripple: hover tint + focus ring only (desktop behaviour). + return material.Focus( + canRequestFocus: widget.enabled, + onFocusChange: (f) => setState(() => _triggerFocused = f), + onKeyEvent: (node, event) { + if (!widget.enabled || event is! KeyDownEvent) { + return KeyEventResult.ignored; + } + final k = event.logicalKey; + if (k == LogicalKeyboardKey.enter || + k == LogicalKeyboardKey.space || + k == LogicalKeyboardKey.arrowDown) { + if (!controller.isOpen) { + controller.open(); + return KeyEventResult.handled; + } + } + return KeyEventResult.ignored; + }, + child: material.GestureDetector( + behavior: material.HitTestBehavior.opaque, + onTap: widget.enabled ? toggle : null, child: fieldWidth != null ? material.SizedBox(width: fieldWidth, child: triggerBody) : triggerBody, @@ -414,6 +448,7 @@ class _QueryaDropdownMenuItem extends material.StatefulWidget { required this.selected, required this.enabled, required this.colorScheme, + required this.compact, required this.onPick, }); @@ -421,6 +456,7 @@ class _QueryaDropdownMenuItem extends material.StatefulWidget { final bool selected; final bool enabled; final ColorScheme colorScheme; + final bool compact; final material.VoidCallback onPick; @override @@ -431,6 +467,7 @@ class _QueryaDropdownMenuItem extends material.StatefulWidget { class _QueryaDropdownMenuItemState extends material.State<_QueryaDropdownMenuItem> { bool _hovered = false; + bool _focused = false; material.Widget _leading(material.BuildContext context, ColorScheme cs) { final slot = context.scaled(QueryaDropdownTokens.selectedCheckSlotWidth); @@ -451,12 +488,15 @@ class _QueryaDropdownMenuItemState @override material.Widget build(material.BuildContext context) { final cs = widget.colorScheme; - final bg = _hovered + final bg = (_hovered || _focused) ? cs.accent.withValues(alpha: 0.14) : widget.selected ? cs.muted.withValues(alpha: 0.32) : material.Colors.transparent; - final itemHeight = QueryaDropdownTokens.scaledMenuItemHeight(context); + final itemHeight = QueryaDropdownTokens.scaledMenuItemHeight( + context, + compact: widget.compact, + ); final radius = context.scaled(QueryaDropdownTokens.menuBorderRadius); final slot = context.scaled(QueryaDropdownTokens.selectedCheckSlotWidth); @@ -469,6 +509,7 @@ class _QueryaDropdownMenuItemState child: material.MenuItemButton( // Keep overlay mounted so parent can play exit fade-slide before close. closeOnActivate: false, + onFocusChange: (f) => setState(() => _focused = f), style: material.MenuItemButton.styleFrom( minimumSize: material.Size(double.infinity, itemHeight), padding: material.EdgeInsets.zero, @@ -507,6 +548,7 @@ class _QueryaDropdownMenuItemState context, cs.popoverForeground, selected: widget.selected, + compact: widget.compact, ), ), ), diff --git a/lib/shared/widgets/querya_dropdown_tokens.dart b/lib/shared/widgets/querya_dropdown_tokens.dart index e29cc70..a40b88c 100644 --- a/lib/shared/widgets/querya_dropdown_tokens.dart +++ b/lib/shared/widgets/querya_dropdown_tokens.dart @@ -20,9 +20,10 @@ abstract final class QueryaDropdownTokens { static const double menuBorderRadius = 6.0; - static const double menuElevation = 8.0; + /// Soft popover shadow (shadcn-like): thin border + low elevation. + static const double menuElevation = 2.0; - static const material.Color menuShadowColor = material.Color(0x42000000); + static const material.Color menuShadowColor = material.Color(0x1F000000); static const material.EdgeInsets menuPadding = material.EdgeInsets.symmetric(vertical: 4.0, horizontal: 4.0); @@ -32,6 +33,15 @@ abstract final class QueryaDropdownTokens { static const material.EdgeInsets menuItemPadding = material.EdgeInsets.symmetric(horizontal: 8.0, vertical: 2.0); + /// Compact variant for toolbars / panel headers. + static const double compactTriggerHeight = 26.0; + + static const double compactTriggerPaddingHorizontal = 8.0; + + static const double compactFontSize = 12.0; + + static const double compactMenuItemHeight = 26.0; + static const double fontSize = 14.0; static const double lineHeight = 1.25; @@ -40,29 +50,36 @@ abstract final class QueryaDropdownTokens { static const double selectedCheckSlotWidth = 18.0; - static double scaledTriggerHeight(material.BuildContext context) => - context.scaled(triggerHeight); + static double scaledTriggerHeight(material.BuildContext context, + {bool compact = false}) => + context.scaled(compact ? compactTriggerHeight : triggerHeight); - static double scaledFontSize(material.BuildContext context) => - context.scaled(fontSize); + static double scaledFontSize(material.BuildContext context, + {bool compact = false}) => + context.scaled(compact ? compactFontSize : fontSize); - static double scaledMenuItemHeight(material.BuildContext context) => - context.scaled(menuItemHeight); + static double scaledMenuItemHeight(material.BuildContext context, + {bool compact = false}) => + context.scaled(compact ? compactMenuItemHeight : menuItemHeight); static double scaledMenuMaxHeight(material.BuildContext context) => context.scaled(menuMaxHeight); static material.EdgeInsets scaledTriggerPadding( - material.BuildContext context) => + material.BuildContext context, + {bool compact = false}) => material.EdgeInsets.symmetric( - horizontal: context.scaled(triggerPaddingHorizontal), + horizontal: context.scaled( + compact ? compactTriggerPaddingHorizontal : triggerPaddingHorizontal, + ), ); static material.TextStyle triggerTextStyle( material.BuildContext context, - material.Color color, - ) { - final size = scaledFontSize(context); + material.Color color, { + bool compact = false, + }) { + final size = scaledFontSize(context, compact: compact); return material.TextStyle( fontSize: size, height: lineHeight, @@ -75,8 +92,9 @@ abstract final class QueryaDropdownTokens { material.BuildContext context, material.Color color, { required bool selected, + bool compact = false, }) { - final size = scaledFontSize(context); + final size = scaledFontSize(context, compact: compact); return material.TextStyle( fontSize: size, height: lineHeight, diff --git a/test/guards/no_material_dropdowns_test.dart b/test/guards/no_material_dropdowns_test.dart new file mode 100644 index 0000000..44ca513 --- /dev/null +++ b/test/guards/no_material_dropdowns_test.dart @@ -0,0 +1,19 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; + +/// #1021: Material dropdown / popup menus must not reappear in `lib/`. +void main() { + test('no Material DropdownButton / PopupMenuButton / showMenu in lib/', () { + final banned = RegExp( + r'\b(DropdownButtonFormField|DropdownButton|PopupMenuButton|showMenu)\b\s*[<(]', + ); + final offenders = []; + for (final f in Directory('lib').listSync(recursive: true)) { + if (f is! File || !f.path.endsWith('.dart')) continue; + if (f.path.contains('lib/shared/widgets/')) continue; + if (banned.hasMatch(f.readAsStringSync())) offenders.add(f.path); + } + expect(offenders, isEmpty); + }); +}