Skip to content

Commit 4d491fe

Browse files
Merge pull request #1022 from QueryaHub/issue/1021-desktop-dropdown-popovers
feat(ui): desktop popovers for dropdowns and menus (#1021)
2 parents 9255e0c + a4bdef7 commit 4d491fe

9 files changed

Lines changed: 298 additions & 182 deletions

‎lib/core/theme/querya_material_theme.dart‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ material.ThemeData materialThemeFromQuerya(ColorScheme scheme) {
2121

2222
return material.ThemeData(
2323
useMaterial3: true,
24+
// Safety net: stray Material popups must not ripple.
25+
splashFactory: material.NoSplash.splashFactory,
2426
colorScheme: materialScheme,
2527
dialogTheme: material.DialogThemeData(backgroundColor: scheme.popover),
2628
textTheme: material.TextTheme(

‎lib/features/workspace/data_grid_filter_bar.dart‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,9 @@ class _DataGridFilterBarState extends material.State<DataGridFilterBar> {
221221
showWhenUnlinked: false,
222222
offset: const material.Offset(24, 32),
223223
child: material.Material(
224-
elevation: 4,
224+
elevation: 2,
225+
shadowColor: const material.Color(0x1F000000),
226+
surfaceTintColor: material.Colors.transparent,
225227
borderRadius: material.BorderRadius.circular(6),
226228
color: cs.popover,
227229
child: material.Container(
@@ -239,7 +241,8 @@ class _DataGridFilterBarState extends material.State<DataGridFilterBar> {
239241
itemBuilder: (ctx, i) {
240242
final s = _suggestions[i];
241243
final isHighlighted = i == _highlightedIndex;
242-
return material.InkWell(
244+
return material.GestureDetector(
245+
behavior: material.HitTestBehavior.opaque,
243246
onTap: () => _applySuggestion(s),
244247
child: material.Container(
245248
padding: const material.EdgeInsets.symmetric(
@@ -285,13 +288,13 @@ class _DataGridFilterBarState extends material.State<DataGridFilterBar> {
285288
return material.Icon(
286289
material.Icons.code_rounded,
287290
size: 13,
288-
color: material.Colors.amber.shade700,
291+
color: cs.mutedForeground,
289292
);
290293
case FilterSuggestionKind.keyword:
291294
return material.Icon(
292295
material.Icons.vpn_key_outlined,
293296
size: 13,
294-
color: material.Colors.green.shade600,
297+
color: cs.mutedForeground.withValues(alpha: 0.8),
295298
);
296299
}
297300
}

‎lib/features/workspace/data_grid_groupings_view.dart‎

Lines changed: 30 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:querya_desktop/core/motion/querya_motion.dart';
55
import 'package:querya_desktop/core/motion/querya_motion_context.dart';
66
import 'package:querya_desktop/features/workspace/grid_groupings_engine.dart';
77
import 'package:querya_desktop/features/workspace/result_grid_view.dart';
8+
import 'package:querya_desktop/shared/widgets/querya_dropdown.dart';
89
import 'package:shadcn_flutter/shadcn_flutter.dart';
910

1011
/// Groupings / Pivot view tab for tabular data with hierarchical grouping and custom aggregations.
@@ -139,21 +140,17 @@ class _DataGridGroupingsViewState
139140
const Gap(6),
140141
const Text('Group:').small().semiBold(),
141142
const Gap(6),
142-
material.DropdownButton<int>(
143+
QueryaDropdown<int>(
144+
compact: true,
143145
value: _selectedColIndices.isNotEmpty &&
144146
_selectedColIndices.first < widget.columns.length
145147
? _selectedColIndices.first
146148
: 0,
147-
isDense: true,
148-
underline: const material.SizedBox.shrink(),
149-
style: TextStyle(fontSize: 12, color: cs.foreground),
150-
items: List.generate(widget.columns.length, (i) {
151-
return material.DropdownMenuItem<int>(
152-
value: i,
153-
child: Text(widget.columns[i]),
154-
);
155-
}),
156-
onChanged: (idx) {
149+
items: [
150+
for (var i = 0; i < widget.columns.length; i++)
151+
QueryaDropdownItem(value: i, label: widget.columns[i]),
152+
],
153+
onSelected: (idx) {
157154
if (idx != null) {
158155
setState(() {
159156
_selectedColIndices = [idx];
@@ -177,18 +174,14 @@ class _DataGridGroupingsViewState
177174
// Aggregation Selector
178175
const Text('Agg:').small().semiBold(),
179176
const Gap(6),
180-
material.DropdownButton<GroupingAggType>(
177+
QueryaDropdown<GroupingAggType>(
178+
compact: true,
181179
value: _aggType,
182-
isDense: true,
183-
underline: const material.SizedBox.shrink(),
184-
style: TextStyle(fontSize: 12, color: cs.foreground),
185-
items: GroupingAggType.values.map((t) {
186-
return material.DropdownMenuItem<GroupingAggType>(
187-
value: t,
188-
child: Text(t.label),
189-
);
190-
}).toList(),
191-
onChanged: (val) {
180+
items: [
181+
for (final t in GroupingAggType.values)
182+
QueryaDropdownItem(value: t, label: t.label),
183+
],
184+
onSelected: (val) {
192185
if (val != null) {
193186
setState(() {
194187
_aggType = val;
@@ -199,21 +192,17 @@ class _DataGridGroupingsViewState
199192
),
200193
if (_aggType != GroupingAggType.count) ...[
201194
const Gap(4),
202-
material.DropdownButton<int>(
195+
QueryaDropdown<int>(
196+
compact: true,
203197
value: _aggTargetColIndex != null &&
204198
_aggTargetColIndex! < widget.columns.length
205-
? _aggTargetColIndex
199+
? _aggTargetColIndex!
206200
: 0,
207-
isDense: true,
208-
underline: const material.SizedBox.shrink(),
209-
style: TextStyle(fontSize: 12, color: cs.foreground),
210-
items: List.generate(widget.columns.length, (i) {
211-
return material.DropdownMenuItem<int>(
212-
value: i,
213-
child: Text(widget.columns[i]),
214-
);
215-
}),
216-
onChanged: (idx) {
201+
items: [
202+
for (var i = 0; i < widget.columns.length; i++)
203+
QueryaDropdownItem(value: i, label: widget.columns[i]),
204+
],
205+
onSelected: (idx) {
217206
if (idx != null) {
218207
setState(() {
219208
_aggTargetColIndex = idx;
@@ -237,18 +226,14 @@ class _DataGridGroupingsViewState
237226
// Sort Selector
238227
const Text('Sort:').small().semiBold(),
239228
const Gap(6),
240-
material.DropdownButton<GroupSortBy>(
229+
QueryaDropdown<GroupSortBy>(
230+
compact: true,
241231
value: _sortBy,
242-
isDense: true,
243-
underline: const material.SizedBox.shrink(),
244-
style: TextStyle(fontSize: 12, color: cs.foreground),
245-
items: GroupSortBy.values.map((s) {
246-
return material.DropdownMenuItem<GroupSortBy>(
247-
value: s,
248-
child: Text(s.label),
249-
);
250-
}).toList(),
251-
onChanged: (val) {
232+
items: [
233+
for (final t in GroupSortBy.values)
234+
QueryaDropdownItem(value: t, label: t.label),
235+
],
236+
onSelected: (val) {
252237
if (val != null) {
253238
setState(() {
254239
_sortBy = val;

‎lib/features/workspace/data_grid_value_panel.dart‎

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart' as material;
44
import 'package:flutter/services.dart';
55
import 'package:querya_desktop/core/editor/querya_code_editor.dart';
66
import 'package:querya_desktop/core/editor/querya_code_language.dart';
7+
import 'package:querya_desktop/shared/widgets/querya_dropdown.dart';
78
import 'package:shadcn_flutter/shadcn_flutter.dart';
89

910
import 'xml_html_formatter.dart';
@@ -315,39 +316,22 @@ class _DataGridValuePanelState extends material.State<DataGridValuePanel> {
315316
child: material.Row(
316317
children: [
317318
// Language Dropdown / Pill
318-
material.DropdownButton<ValuePanelLanguage>(
319+
QueryaDropdown<ValuePanelLanguage>(
320+
compact: true,
319321
value: _selectedLanguage,
320-
isDense: true,
321-
underline: const material.SizedBox(),
322-
icon: const material.Icon(material.Icons.arrow_drop_down, size: 16),
323-
style: TextStyle(
324-
fontSize: 11,
325-
color: cs.foreground,
326-
fontWeight: FontWeight.w600,
327-
),
328322
items: const [
329-
material.DropdownMenuItem(
330-
value: ValuePanelLanguage.auto,
331-
child: Text('Auto'),
332-
),
333-
material.DropdownMenuItem(
334-
value: ValuePanelLanguage.json,
335-
child: Text('JSON'),
336-
),
337-
material.DropdownMenuItem(
338-
value: ValuePanelLanguage.xml,
339-
child: Text('XML/HTML'),
340-
),
341-
material.DropdownMenuItem(
342-
value: ValuePanelLanguage.sql,
343-
child: Text('SQL'),
344-
),
345-
material.DropdownMenuItem(
346-
value: ValuePanelLanguage.text,
347-
child: Text('Plain Text'),
348-
),
323+
QueryaDropdownItem(
324+
value: ValuePanelLanguage.auto, label: 'Auto'),
325+
QueryaDropdownItem(
326+
value: ValuePanelLanguage.json, label: 'JSON'),
327+
QueryaDropdownItem(
328+
value: ValuePanelLanguage.xml, label: 'XML/HTML'),
329+
QueryaDropdownItem(
330+
value: ValuePanelLanguage.sql, label: 'SQL'),
331+
QueryaDropdownItem(
332+
value: ValuePanelLanguage.text, label: 'Plain Text'),
349333
],
350-
onChanged: (val) {
334+
onSelected: (val) {
351335
if (val != null) {
352336
setState(() => _selectedLanguage = val);
353337
_validateContent();

‎lib/shared/widgets/export_menu_button.dart‎

Lines changed: 22 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:flutter/material.dart' as material;
22
import 'package:querya_desktop/shared/services/data_export_service.dart';
3+
import 'package:querya_desktop/shared/widgets/querya_action_menu.dart';
34
import 'package:shadcn_flutter/shadcn_flutter.dart';
45

56
class ExportMenuButton extends StatelessWidget {
@@ -18,84 +19,37 @@ class ExportMenuButton extends StatelessWidget {
1819

1920
@override
2021
Widget build(BuildContext context) {
21-
return material.PopupMenuButton<DataExportFormat>(
22-
tooltip: label,
22+
return QueryaActionMenu<DataExportFormat>(
2323
onSelected: onSelected,
24-
itemBuilder: (context) => [
25-
material.PopupMenuItem(
24+
items: [
25+
QueryaActionMenuItem(
2626
value: DataExportFormat.csv,
27-
child: material.Row(
28-
children: [
29-
const material.Icon(
30-
material.Icons.table_chart_outlined,
31-
size: 16,
32-
),
33-
const material.SizedBox(width: 8),
34-
material.Text(isSave ? 'CSV (.csv)' : 'Copy as CSV'),
35-
],
36-
),
27+
icon: material.Icons.table_chart_outlined,
28+
label: isSave ? 'CSV (.csv)' : 'Copy as CSV',
3729
),
38-
material.PopupMenuItem(
30+
QueryaActionMenuItem(
3931
value: DataExportFormat.json,
40-
child: material.Row(
41-
children: [
42-
const material.Icon(
43-
material.Icons.data_object_rounded,
44-
size: 16,
45-
),
46-
const material.SizedBox(width: 8),
47-
material.Text(isSave ? 'JSON (.json)' : 'Copy as JSON'),
48-
],
49-
),
32+
icon: material.Icons.data_object_rounded,
33+
label: isSave ? 'JSON (.json)' : 'Copy as JSON',
5034
),
51-
material.PopupMenuItem(
35+
QueryaActionMenuItem(
5236
value: DataExportFormat.markdown,
53-
child: material.Row(
54-
children: [
55-
const material.Icon(material.Icons.code_rounded, size: 16),
56-
const material.SizedBox(width: 8),
57-
material.Text(
58-
isSave ? 'Markdown Table (.md)' : 'Copy as Markdown Table',
59-
),
60-
],
61-
),
37+
icon: material.Icons.code_rounded,
38+
label: isSave ? 'Markdown Table (.md)' : 'Copy as Markdown Table',
6239
),
63-
material.PopupMenuItem(
40+
QueryaActionMenuItem(
6441
value: DataExportFormat.sqlDump,
65-
child: material.Row(
66-
children: [
67-
const material.Icon(material.Icons.storage_rounded, size: 16),
68-
const material.SizedBox(width: 8),
69-
material.Text(
70-
isSave ? 'SQL INSERT Dump (.sql)' : 'Copy as SQL Dump',
71-
),
72-
],
73-
),
42+
icon: material.Icons.storage_rounded,
43+
label: isSave ? 'SQL INSERT Dump (.sql)' : 'Copy as SQL Dump',
7444
),
7545
],
76-
child: material.Container(
77-
padding: const material.EdgeInsets.symmetric(
78-
horizontal: 10,
79-
vertical: 6,
80-
),
81-
decoration: material.BoxDecoration(
82-
border: material.Border.all(
83-
color: Theme.of(context).colorScheme.border,
84-
),
85-
borderRadius: material.BorderRadius.circular(6),
86-
),
87-
child: material.Row(
88-
mainAxisSize: material.MainAxisSize.min,
89-
children: [
90-
material.Icon(
91-
icon,
92-
size: 14,
93-
color: Theme.of(context).colorScheme.foreground,
94-
),
95-
const material.SizedBox(width: 6),
96-
Text(label).small(),
97-
],
98-
),
46+
child: material.Row(
47+
mainAxisSize: material.MainAxisSize.min,
48+
children: [
49+
material.Icon(icon, size: 14),
50+
const material.SizedBox(width: 6),
51+
Text(label),
52+
],
9953
),
10054
);
10155
}

0 commit comments

Comments
 (0)