From 2882db76d1aa119c52a241b55090e5dad25ef96f Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 11 Oct 2026 17:10:09 +0300 Subject: [PATCH 1/4] refactor(ui): the Mongo and staging private buttons are kit buttons _ActionButton (databases, collections), _SmallActionButton, _TreeIconButton and the staging toolbar's _ToolbarButton are QueryaButton / QueryaIconButton with a tooltip; five hand-drawn button classes less. Refs #1445 --- .../mongodb/mongo_collections_view.dart | 61 ++-------- .../mongodb/mongo_databases_view.dart | 61 ++-------- .../mongodb/mongo_document_tree_view.dart | 108 +++++++---------- .../mongodb/mongo_documents_view.dart | 41 ++----- .../workspace/data_grid_staging_toolbar.dart | 113 ++---------------- 5 files changed, 77 insertions(+), 307 deletions(-) diff --git a/lib/features/mongodb/mongo_collections_view.dart b/lib/features/mongodb/mongo_collections_view.dart index 289a9f4c..19937831 100644 --- a/lib/features/mongodb/mongo_collections_view.dart +++ b/lib/features/mongodb/mongo_collections_view.dart @@ -411,11 +411,11 @@ class _CollectionRow extends StatelessWidget { const material.EdgeInsets.symmetric(horizontal: 20, vertical: 10), child: Row( children: [ - _ActionButton( + QueryaButton.secondary( label: 'View', icon: material.Icons.visibility_rounded, - color: context.semanticPalette.success, - onTap: onView, + size: QueryaControlSize.sm, + onPressed: onView, ), const Gap(16), Expanded( @@ -440,11 +440,12 @@ class _CollectionRow extends StatelessWidget { width: 100, child: Text(_formatSize(collection.size)).muted().small(), ), - _ActionButton( + QueryaButton.secondary( label: 'Del', icon: material.Icons.delete_rounded, - color: context.semanticPalette.destructive, - onTap: onDrop, + size: QueryaControlSize.sm, + tone: QueryaButtonTone.destructive, + onPressed: onDrop, ), ], ), @@ -466,54 +467,6 @@ class _CollectionRow extends StatelessWidget { } } -class _ActionButton extends StatelessWidget { - const _ActionButton({ - required this.label, - required this.icon, - required this.color, - required this.onTap, - }); - - final String label; - final material.IconData icon; - final Color color; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - return material.Material( - color: Colors.transparent, - child: material.InkWell( - onTap: onTap, - borderRadius: material.BorderRadius.circular(6), - child: material.Container( - padding: - const material.EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: material.BoxDecoration( - color: color.withValues(alpha: 0.14), - borderRadius: material.BorderRadius.circular(6), - ), - child: Row( - mainAxisSize: material.MainAxisSize.min, - children: [ - material.Icon(icon, size: 14, color: color), - const Gap(5), - Text( - label, - style: material.TextStyle( - color: color, - fontSize: 12, - fontWeight: material.FontWeight.w500, - ), - ), - ], - ), - ), - ), - ); - } -} - // ─── Data model ────────────────────────────────────────────────────────────── class _CollectionInfo { diff --git a/lib/features/mongodb/mongo_databases_view.dart b/lib/features/mongodb/mongo_databases_view.dart index a20e09a9..53da224b 100644 --- a/lib/features/mongodb/mongo_databases_view.dart +++ b/lib/features/mongodb/mongo_databases_view.dart @@ -390,11 +390,11 @@ class _DatabaseRow extends StatelessWidget { const material.EdgeInsets.symmetric(horizontal: 20, vertical: 10), child: Row( children: [ - _ActionButton( + QueryaButton.secondary( label: 'View', icon: material.Icons.visibility_rounded, - color: context.semanticPalette.success, - onTap: onView, + size: QueryaControlSize.sm, + onPressed: onView, ), const Gap(16), Expanded( @@ -413,11 +413,12 @@ class _DatabaseRow extends StatelessWidget { width: 120, child: Text(_formatSize(database.sizeOnDisk)).muted().small(), ), - _ActionButton( + QueryaButton.secondary( label: 'Del', icon: material.Icons.delete_rounded, - color: context.semanticPalette.destructive, - onTap: onDrop, + size: QueryaControlSize.sm, + tone: QueryaButtonTone.destructive, + onPressed: onDrop, ), ], ), @@ -439,54 +440,6 @@ class _DatabaseRow extends StatelessWidget { } } -class _ActionButton extends StatelessWidget { - const _ActionButton({ - required this.label, - required this.icon, - required this.color, - required this.onTap, - }); - - final String label; - final material.IconData icon; - final Color color; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - return material.Material( - color: Colors.transparent, - child: material.InkWell( - onTap: onTap, - borderRadius: material.BorderRadius.circular(6), - child: material.Container( - padding: - const material.EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: material.BoxDecoration( - color: color.withValues(alpha: 0.14), - borderRadius: material.BorderRadius.circular(6), - ), - child: Row( - mainAxisSize: material.MainAxisSize.min, - children: [ - material.Icon(icon, size: 14, color: color), - const Gap(5), - Text( - label, - style: material.TextStyle( - color: color, - fontSize: 12, - fontWeight: material.FontWeight.w500, - ), - ), - ], - ), - ), - ), - ); - } -} - // ─── Data models ───────────────────────────────────────────────────────────── class _DatabaseInfo { diff --git a/lib/features/mongodb/mongo_document_tree_view.dart b/lib/features/mongodb/mongo_document_tree_view.dart index f93f9eb5..71cbec60 100644 --- a/lib/features/mongodb/mongo_document_tree_view.dart +++ b/lib/features/mongodb/mongo_document_tree_view.dart @@ -10,6 +10,7 @@ import 'package:shadcn_flutter/shadcn_flutter.dart' hide TextExtension; import 'package:querya_desktop/core/ui/querya_control_tokens.dart'; import 'package:querya_desktop/shared/widgets/querya_button.dart'; import 'package:querya_desktop/shared/widgets/querya_divider.dart'; +import 'package:querya_desktop/shared/widgets/querya_icon_button.dart'; import 'package:querya_desktop/shared/widgets/querya_text.dart'; /// Appends a key or list index to a JavaScript / JSON Path expression. @@ -312,30 +313,33 @@ class _MongoDocumentTreeViewState extends State { Text('$fieldsCount fields').muted().small(), const Spacer(), // Actions - _TreeIconButton( - icon: material.Icons.copy_rounded, - tooltip: 'Copy Document JSON', - onTap: () => _copyText( + QueryaIconButton( + icon: material.Icon(material.Icons.copy_rounded), + tooltip: 'Copy Document JSON', + size: QueryaControlSize.sm, + onPressed: () => _copyText( mongoDocumentToEjson(doc), 'Document JSON', ), - ), + ), if (widget.onDocumentTap != null) ...[ const Gap(4), - _TreeIconButton( - icon: material.Icons.open_in_new_rounded, - tooltip: 'View / Edit Document', - onTap: () => widget.onDocumentTap!(doc), - ), + QueryaIconButton( + icon: material.Icon(material.Icons.open_in_new_rounded), + tooltip: 'View / Edit Document', + size: QueryaControlSize.sm, + onPressed: () => widget.onDocumentTap!(doc), + ), ], if (widget.onDeleteDocument != null) ...[ const Gap(4), - _TreeIconButton( - icon: material.Icons.delete_outline_rounded, - tooltip: 'Delete Document', - color: cs.destructive, - onTap: () => widget.onDeleteDocument!(doc), - ), + QueryaIconButton( + icon: material.Icon(material.Icons.delete_outline_rounded), + tooltip: 'Delete Document', + size: QueryaControlSize.sm, + isDestructive: true, + onPressed: () => widget.onDeleteDocument!(doc), + ), ], ], ), @@ -540,16 +544,18 @@ class _MongoDocumentTreeViewState extends State { ), ), const Spacer(), - _TreeIconButton( - icon: material.Icons.alt_route_rounded, + QueryaIconButton( + icon: material.Icon(material.Icons.alt_route_rounded), tooltip: 'Copy JSON Path ($jsonPath)', - onTap: () => _copyText(jsonPath, 'JSON Path: $jsonPath'), + size: QueryaControlSize.sm, + onPressed: () => _copyText(jsonPath, 'JSON Path: $jsonPath'), ), const Gap(4), - _TreeIconButton( - icon: material.Icons.copy_rounded, + QueryaIconButton( + icon: material.Icon(material.Icons.copy_rounded), tooltip: 'Copy JSON', - onTap: () { + size: QueryaControlSize.sm, + onPressed: () { try { final encoded = const JsonEncoder.withIndent(' ') .convert(rawSubtree); @@ -642,24 +648,27 @@ class _MongoDocumentTreeViewState extends State { ), ), const Gap(6), - _TreeIconButton( - icon: material.Icons.alt_route_rounded, + QueryaIconButton( + icon: material.Icon(material.Icons.alt_route_rounded), tooltip: 'Copy JSON Path ($jsonPath)', - onTap: () => _copyText(jsonPath, 'JSON Path: $jsonPath'), + size: QueryaControlSize.sm, + onPressed: () => _copyText(jsonPath, 'JSON Path: $jsonPath'), ), const Gap(4), - _TreeIconButton( - icon: material.Icons.copy_rounded, + QueryaIconButton( + icon: material.Icon(material.Icons.copy_rounded), tooltip: 'Copy Value', - onTap: () => _copyText(valueDisplay, 'Value'), + size: QueryaControlSize.sm, + onPressed: () => _copyText(valueDisplay, 'Value'), ), if (canEdit) ...[ const Gap(4), - _TreeIconButton( - icon: material.Icons.edit_note_rounded, - tooltip: 'Edit Field', - onTap: () => widget.onInspectField!(rootDoc, keyOrIndex), - ), + QueryaIconButton( + icon: material.Icon(material.Icons.edit_note_rounded), + tooltip: 'Edit Field', + size: QueryaControlSize.sm, + onPressed: () => widget.onInspectField!(rootDoc, keyOrIndex), + ), ], ], ), @@ -696,36 +705,3 @@ class _MongoDocumentTreeViewState extends State { } } -class _TreeIconButton extends StatelessWidget { - const _TreeIconButton({ - required this.icon, - required this.tooltip, - required this.onTap, - this.color, - }); - - final material.IconData icon; - final String tooltip; - final VoidCallback onTap; - final Color? color; - - @override - Widget build(BuildContext context) { - final cs = Theme.of(context).colorScheme; - return material.Tooltip( - message: tooltip, - child: material.InkWell( - onTap: onTap, - borderRadius: material.BorderRadius.circular(4), - child: material.Padding( - padding: const material.EdgeInsets.all(3), - child: material.Icon( - icon, - size: 14, - color: color ?? cs.mutedForeground, - ), - ), - ), - ); - } -} diff --git a/lib/features/mongodb/mongo_documents_view.dart b/lib/features/mongodb/mongo_documents_view.dart index 81153fea..5c0342c6 100644 --- a/lib/features/mongodb/mongo_documents_view.dart +++ b/lib/features/mongodb/mongo_documents_view.dart @@ -762,16 +762,19 @@ class _DocumentCardState extends State<_DocumentCard> { ), ), const Gap(8), - _SmallActionButton( - icon: material.Icons.edit_rounded, - color: context.semanticPalette.action, - onTap: widget.onView, + QueryaIconButton( + icon: material.Icon(material.Icons.edit_rounded), + tooltip: 'Open document', + size: QueryaControlSize.sm, + onPressed: widget.onView, ), const Gap(4), - _SmallActionButton( - icon: material.Icons.delete_rounded, - color: context.semanticPalette.destructive, - onTap: widget.onDelete, + QueryaIconButton( + icon: material.Icon(material.Icons.delete_rounded), + tooltip: 'Delete document', + size: QueryaControlSize.sm, + isDestructive: true, + onPressed: widget.onDelete, ), ], ), @@ -932,25 +935,3 @@ class MongoFieldList extends StatelessWidget { // ─── Small icon-only action button ────────────────────────────────────────── -class _SmallActionButton extends StatelessWidget { - const _SmallActionButton({ - required this.icon, - required this.color, - required this.onTap, - }); - - final material.IconData icon; - final Color color; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - return material.IconButton( - onPressed: onTap, - icon: material.Icon(icon, size: 15, color: color), - padding: const material.EdgeInsets.all(5), - constraints: const material.BoxConstraints(minWidth: 28, minHeight: 28), - splashRadius: 18, - ); - } -} diff --git a/lib/features/workspace/data_grid_staging_toolbar.dart b/lib/features/workspace/data_grid_staging_toolbar.dart index f9c0357d..687199be 100644 --- a/lib/features/workspace/data_grid_staging_toolbar.dart +++ b/lib/features/workspace/data_grid_staging_toolbar.dart @@ -61,25 +61,26 @@ class DataGridStagingToolbar extends StatelessWidget { child: material.Row( mainAxisSize: material.MainAxisSize.min, children: [ - _ToolbarButton( + QueryaButton.ghost( label: 'Add Row', icon: material.Icons.add_rounded, compact: compact, + size: QueryaControlSize.sm, + tone: QueryaButtonTone.normal, tooltip: 'Add new row (Ctrl+Insert / Cmd+N)', - onPressed: - isSaving ? null : () => stagingBuffer.addRow(), + onPressed: isSaving ? null : () => stagingBuffer.addRow(), ), const Gap(2), - _ToolbarButton( - label: - isSelectedDeleted ? 'Restore Row' : 'Delete Row', + QueryaButton.ghost( + label: isSelectedDeleted ? 'Restore Row' : 'Delete Row', icon: isSelectedDeleted ? material.Icons.restore_from_trash_rounded : material.Icons.remove_circle_outline_rounded, compact: compact, - color: isSelectedDeleted - ? cs.primary - : (hasSelectedRow ? cs.destructive : null), + size: QueryaControlSize.sm, + tone: isSelectedDeleted || !hasSelectedRow + ? QueryaButtonTone.normal + : QueryaButtonTone.destructive, tooltip: isSelectedDeleted ? 'Restore marked row' : hasSelectedRow @@ -199,97 +200,3 @@ class _PendingBadge extends StatelessWidget { ); } } - -class _ToolbarButton extends material.StatefulWidget { - const _ToolbarButton({ - required this.label, - required this.icon, - this.onPressed, - this.color, - this.tooltip, - this.compact = false, - }); - - /// Icon only; the label moves into the tooltip. - final bool compact; - final String label; - final material.IconData icon; - final material.VoidCallback? onPressed; - final material.Color? color; - final String? tooltip; - - @override - material.State<_ToolbarButton> createState() => _ToolbarButtonState(); -} - -class _ToolbarButtonState extends material.State<_ToolbarButton> { - bool _isHovered = false; - - @override - material.Widget build(material.BuildContext context) { - final cs = Theme.of(context).colorScheme; - final enabled = widget.onPressed != null; - final fg = widget.color ?? cs.foreground; - - material.Widget button = material.MouseRegion( - cursor: enabled - ? material.SystemMouseCursors.click - : material.SystemMouseCursors.basic, - onEnter: enabled ? (_) => setState(() => _isHovered = true) : null, - onExit: enabled ? (_) => setState(() => _isHovered = false) : null, - child: material.GestureDetector( - onTap: widget.onPressed, - behavior: material.HitTestBehavior.opaque, - child: material.Opacity( - opacity: enabled ? 1.0 : 0.4, - child: material.AnimatedContainer( - duration: context.motionDuration(QueryaMotion.fast), - curve: context.motionCurve(QueryaMotion.enter), - padding: const material.EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - decoration: material.BoxDecoration( - color: enabled && _isHovered - ? (widget.color != null - ? widget.color!.withValues(alpha: 0.12) - : cs.muted.withValues(alpha: 0.5)) - : material.Colors.transparent, - borderRadius: material.BorderRadius.circular(4), - ), - child: material.Row( - mainAxisSize: material.MainAxisSize.min, - children: [ - material.Icon(widget.icon, size: 14, color: fg), - if (!widget.compact) ...[ - const material.SizedBox(width: 4), - material.Text( - widget.label, - style: material.TextStyle( - fontSize: 12, - fontWeight: material.FontWeight.w500, - color: fg, - ), - ), - ], - ], - ), - ), - ), - ), - ); - - final tooltip = widget.compact - ? '${widget.label}${widget.tooltip != null ? ' — ${widget.tooltip}' : ''}' - : widget.tooltip; - if (tooltip != null) { - button = material.Tooltip( - message: tooltip, - waitDuration: const Duration(milliseconds: 400), - child: button, - ); - } - - return button; - } -} From 1ccdc40e107ae439d937576195c6accba7193917 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 11 Oct 2026 17:11:34 +0300 Subject: [PATCH 2/4] refactor(ui): raw Material buttons and literal tooltip delays leave lib, guards land 14 Material IconButtons (password eyes, dismiss, close, delete, sidebar toggle, search clear), the value panel's TextButton / ElevatedButton and literal tooltip delays use the kit. test/guards/button_standard_guards_test.dart forbids shadcn buttons, raw Material buttons, *Button classes outside the kit (two allowed) and literal tooltip delays. Refs #1445 #1339 --- docs/ui-kit.md | 20 +++-- .../forms/connection_auth_section.dart | 18 ++-- .../connections/sqlite_connection_form.dart | 18 ++-- .../main_screen/querya_window_title_bar.dart | 33 +++---- .../mongodb/mongodb_connection_form.dart | 18 ++-- lib/features/mysql/mysql_connection_form.dart | 18 ++-- .../onboarding/welcome_tour_dialog.dart | 15 ++-- .../postgresql_connection_form.dart | 18 ++-- lib/features/redis/redis_connection_form.dart | 35 +++----- lib/features/redis/redis_keys_view.dart | 40 +++------ lib/features/settings/preferences_dialog.dart | 18 ++-- .../workspace/data_grid_staging_toolbar.dart | 7 +- .../workspace/data_grid_value_panel.dart | 44 ++++------ .../workspace/table_view_staging.dart | 5 +- lib/shared/widgets/querya_search_field.dart | 14 +-- test/guards/button_standard_guards_test.dart | 85 +++++++++++++++++++ 16 files changed, 206 insertions(+), 200 deletions(-) create mode 100644 test/guards/button_standard_guards_test.dart diff --git a/docs/ui-kit.md b/docs/ui-kit.md index 4210fe07..8cd1603c 100644 --- a/docs/ui-kit.md +++ b/docs/ui-kit.md @@ -207,17 +207,19 @@ Canonical labels and roles from the review: | several related buttons side by side with gaps | `QueryaButtonGroup` | | shadcn / material `IconButton`, private `_TreeIconButton` | `QueryaIconButton` (with tooltip) | | `InkWell` / `GestureDetector` around an icon or text | `QueryaButton` or `QueryaIconButton` | -| private `_ActionButton`, `_SmallActionButton`, `_ToolbarButton` | `QueryaButton` | -| show-password eye pasted in forms | one password field widget | +| private `_ActionButton`, `_SmallActionButton`, `_ToolbarButton` (gone) | `QueryaButton` | +| show-password eye pasted in forms | `QueryaIconButton` (one password field widget stays open in #1445) | -### Guards (landing with #1339) +### Guards -| Test | Forbids | -|------|---------| -| `no_shadcn_buttons_test.dart` | `PrimaryButton`, `OutlineButton`, `GhostButton`, `DestructiveButton`, `SecondaryButton`, `LinkButton`, `ButtonSize`, `ButtonDensity` outside an allow-list | -| `no_raw_material_buttons_test.dart` | `IconButton`, `TextButton`, `ElevatedButton`, `OutlinedButton`, `FilledButton` | -| `no_ad_hoc_button_classes_test.dart` | a `*Button` class outside `lib/shared/widgets/`; a button-like `InkWell` / `GestureDetector` outside the kit | -| `button_literals_test.dart` | `waitDuration: Duration(...)` literals, `BorderRadius.circular(n)` on a kit control | +All four live in `test/guards/button_standard_guards_test.dart`: + +| Guard | Forbids | +|-------|---------| +| shadcn button types | `PrimaryButton`, `OutlineButton`, `GhostButton`, `DestructiveButton`, `SecondaryButton`, `LinkButton`, `ButtonSize`, `ButtonDensity` in `lib/` | +| raw Material buttons | `IconButton`, `TextButton`, `ElevatedButton`, `OutlinedButton`, `FilledButton` in `lib/` | +| button classes | a `*Button` class outside `lib/shared/widgets/` (allow-list: `ThemePickerButton`, `TableEditModeButton`, follow-up #1445) | +| tooltip delay | `waitDuration: Duration(...)` literals; use `kQueryaTooltipWait` | ## Design tokens diff --git a/lib/features/connections/forms/connection_auth_section.dart b/lib/features/connections/forms/connection_auth_section.dart index bdb4365f..1a21a726 100644 --- a/lib/features/connections/forms/connection_auth_section.dart +++ b/lib/features/connections/forms/connection_auth_section.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/core/ui/querya_control_tokens.dart'; import 'package:querya_desktop/features/connections/remove_saved_password_option.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; @@ -77,17 +78,14 @@ class _ConnectionAuthSectionState extends material.State top: 0, bottom: 0, child: material.Center( - child: material.IconButton( - icon: material.Icon( - _showPassword + child: QueryaIconButton( +icon: material.Icon(_showPassword ? material.Icons.visibility_off - : material.Icons.visibility, - size: 20, - ), - onPressed: () => setState(() => _showPassword = !_showPassword), - padding: material.EdgeInsets.zero, - constraints: const material.BoxConstraints(), - ), + : material.Icons.visibility), +tooltip: _showPassword ? 'Hide password' : 'Show password', +size: QueryaControlSize.sm, +onPressed: () => setState(() => _showPassword = !_showPassword), +), ), ), ], diff --git a/lib/features/connections/sqlite_connection_form.dart b/lib/features/connections/sqlite_connection_form.dart index 1d28211b..40c7778a 100644 --- a/lib/features/connections/sqlite_connection_form.dart +++ b/lib/features/connections/sqlite_connection_form.dart @@ -364,18 +364,12 @@ class _SqliteConnectionFormContentState ), ).small(), ), - material.IconButton( - icon: material.Icon( - material.Icons.close, - size: 18, - color: theme.mutedForeground, - ), - onPressed: _dismissResult, - style: material.IconButton.styleFrom( - minimumSize: const material.Size(28, 28), - padding: material.EdgeInsets.zero, - ), - ), + QueryaIconButton( +icon: material.Icon(material.Icons.close), +tooltip: 'Dismiss', +size: QueryaControlSize.sm, +onPressed: _dismissResult, +), ], ), ), diff --git a/lib/features/main_screen/querya_window_title_bar.dart b/lib/features/main_screen/querya_window_title_bar.dart index 5f53290f..0219599c 100644 --- a/lib/features/main_screen/querya_window_title_bar.dart +++ b/lib/features/main_screen/querya_window_title_bar.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart' as material; import 'package:querya_desktop/core/layout/ui_scale.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; import 'package:querya_desktop/core/theme/querya_theme_scope.dart'; +import 'package:querya_desktop/core/ui/querya_control_tokens.dart'; import 'package:querya_desktop/features/connections/driver_manager_dialog.dart'; import 'package:querya_desktop/core/actions/querya_command_host.dart'; import 'package:querya_desktop/features/command_palette/command_palette_dialog.dart'; @@ -17,6 +18,7 @@ import 'package:querya_desktop/features/updater/update_available_badge.dart'; import 'package:querya_desktop/features/updater/update_controller.dart'; import 'package:querya_desktop/features/updater/update_dialog.dart'; import 'package:querya_desktop/core/actions/sql_editor_actions.dart'; +import 'package:querya_desktop/shared/widgets/querya_icon_button.dart'; import 'package:querya_desktop/shared/widgets/querya_text.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart' hide TextExtension; @@ -126,28 +128,17 @@ class QueryaWindowTitleBar extends StatelessWidget { const Text('Querya').semiBold().small(), const Gap(12), if (onToggleSidebar != null) ...[ - material.Tooltip( - message: - 'Toggle Sidebar (${Platform.isMacOS ? "Cmd+B" : "Ctrl+B"})', - child: material.IconButton( - key: const Key('title_bar_toggle_sidebar_button'), - icon: material.Icon( - isSidebarVisible - ? material.Icons.view_sidebar_rounded - : material.Icons.view_sidebar_outlined, - size: 16, - color: isSidebarVisible - ? Theme.of(context).colorScheme.foreground - : wb.mutedForeground, - ), - splashRadius: 14, - padding: const material.EdgeInsets.all(4), - constraints: const material.BoxConstraints( - minWidth: 26, - minHeight: 26, - ), - onPressed: onToggleSidebar, + QueryaIconButton( + key: const Key('title_bar_toggle_sidebar_button'), + icon: material.Icon( + isSidebarVisible + ? material.Icons.view_sidebar_rounded + : material.Icons.view_sidebar_outlined, ), + tooltip: + 'Toggle Sidebar (${Platform.isMacOS ? "Cmd+B" : "Ctrl+B"})', + size: QueryaControlSize.sm, + onPressed: onToggleSidebar, ), const Gap(12), ] else diff --git a/lib/features/mongodb/mongodb_connection_form.dart b/lib/features/mongodb/mongodb_connection_form.dart index 3e7fc6cd..14f7a822 100644 --- a/lib/features/mongodb/mongodb_connection_form.dart +++ b/lib/features/mongodb/mongodb_connection_form.dart @@ -624,18 +624,12 @@ class _MongoConnectionFormContentState ), ).small(), ), - material.IconButton( - icon: material.Icon( - material.Icons.close, - size: 18, - color: theme.mutedForeground, - ), - onPressed: _dismissResult, - style: material.IconButton.styleFrom( - minimumSize: const material.Size(28, 28), - padding: material.EdgeInsets.zero, - ), - ), + QueryaIconButton( +icon: material.Icon(material.Icons.close), +tooltip: 'Dismiss', +size: QueryaControlSize.sm, +onPressed: _dismissResult, +), ], ), ), diff --git a/lib/features/mysql/mysql_connection_form.dart b/lib/features/mysql/mysql_connection_form.dart index 2b1b72f2..cb327991 100644 --- a/lib/features/mysql/mysql_connection_form.dart +++ b/lib/features/mysql/mysql_connection_form.dart @@ -529,18 +529,12 @@ class _MysqlConnectionFormContentState ), ).small(), ), - material.IconButton( - icon: material.Icon( - material.Icons.close, - size: 18, - color: theme.mutedForeground, - ), - onPressed: _dismissResult, - style: material.IconButton.styleFrom( - minimumSize: const material.Size(28, 28), - padding: material.EdgeInsets.zero, - ), - ), + QueryaIconButton( +icon: material.Icon(material.Icons.close), +tooltip: 'Dismiss', +size: QueryaControlSize.sm, +onPressed: _dismissResult, +), ], ), ), diff --git a/lib/features/onboarding/welcome_tour_dialog.dart b/lib/features/onboarding/welcome_tour_dialog.dart index cd7a8759..c3fa515b 100644 --- a/lib/features/onboarding/welcome_tour_dialog.dart +++ b/lib/features/onboarding/welcome_tour_dialog.dart @@ -271,15 +271,12 @@ class _WelcomeTourDialogState extends State { ), const material.SizedBox(width: 4), ], - material.IconButton( - icon: const material.Icon( - material.Icons.close_rounded, - size: 18, - ), - tooltip: 'Close', - splashRadius: 16, - onPressed: _finish, - ), + QueryaIconButton( +icon: material.Icon(material.Icons.close_rounded), +tooltip: 'Close', +size: QueryaControlSize.sm, +onPressed: _finish, +), ], ), const material.SizedBox(height: 18), diff --git a/lib/features/postgresql/postgresql_connection_form.dart b/lib/features/postgresql/postgresql_connection_form.dart index 261f4f0a..a76c8291 100644 --- a/lib/features/postgresql/postgresql_connection_form.dart +++ b/lib/features/postgresql/postgresql_connection_form.dart @@ -750,18 +750,12 @@ class _PostgresConnectionFormContentState ), ).small(), ), - material.IconButton( - icon: material.Icon( - material.Icons.close, - size: 18, - color: theme.mutedForeground, - ), - onPressed: _dismissResult, - style: material.IconButton.styleFrom( - minimumSize: const material.Size(28, 28), - padding: material.EdgeInsets.zero, - ), - ), + QueryaIconButton( +icon: material.Icon(material.Icons.close), +tooltip: 'Dismiss', +size: QueryaControlSize.sm, +onPressed: _dismissResult, +), ], ), ), diff --git a/lib/features/redis/redis_connection_form.dart b/lib/features/redis/redis_connection_form.dart index 497c7779..2512cb41 100644 --- a/lib/features/redis/redis_connection_form.dart +++ b/lib/features/redis/redis_connection_form.dart @@ -439,18 +439,15 @@ class _RedisConnectionFormContentState top: 0, bottom: 0, child: material.Center( - child: material.IconButton( - icon: material.Icon( - _showPassword + child: QueryaIconButton( +icon: material.Icon(_showPassword ? material.Icons.visibility_off - : material.Icons.visibility, - size: 20, - ), - onPressed: () => setState( + : material.Icons.visibility), +tooltip: _showPassword ? 'Hide password' : 'Show password', +size: QueryaControlSize.sm, +onPressed: () => setState( () => _showPassword = !_showPassword), - padding: material.EdgeInsets.zero, - constraints: const material.BoxConstraints(), - ), +), ), ), ], @@ -543,18 +540,12 @@ class _RedisConnectionFormContentState ), ).small(), ), - material.IconButton( - icon: material.Icon( - material.Icons.close, - size: 18, - color: theme.mutedForeground, - ), - onPressed: _dismissResult, - style: material.IconButton.styleFrom( - minimumSize: const material.Size(28, 28), - padding: material.EdgeInsets.zero, - ), - ), + QueryaIconButton( +icon: material.Icon(material.Icons.close), +tooltip: 'Dismiss', +size: QueryaControlSize.sm, +onPressed: _dismissResult, +), ], ), ), diff --git a/lib/features/redis/redis_keys_view.dart b/lib/features/redis/redis_keys_view.dart index d83d8058..af5b8a71 100644 --- a/lib/features/redis/redis_keys_view.dart +++ b/lib/features/redis/redis_keys_view.dart @@ -775,19 +775,13 @@ class _FolderTile extends material.StatelessWidget { ), const material.Spacer(), if (onDelete != null) - material.IconButton( - onPressed: onDelete, - icon: material.Icon( - material.Icons.delete_outline_rounded, - size: 16, - color: palette.destructive, - ), - padding: const material.EdgeInsets.all(4), - constraints: const material.BoxConstraints( - minWidth: 28, minHeight: 28), - splashRadius: 18, - tooltip: 'Delete folder (${folder.totalKeyCount} keys)', - ), + QueryaIconButton( +icon: material.Icon(material.Icons.delete_outline_rounded), +tooltip: 'Delete folder (${folder.totalKeyCount} keys)', +size: QueryaControlSize.sm, +isDestructive: true, +onPressed: onDelete, +), ], ), ), @@ -939,19 +933,13 @@ class _KeyTile extends material.StatelessWidget { ), const Gap(4), if (onDelete != null) - material.IconButton( - onPressed: onDelete, - icon: material.Icon( - material.Icons.delete_rounded, - size: 15, - color: palette.destructive, - ), - padding: const material.EdgeInsets.all(4), - constraints: const material.BoxConstraints( - minWidth: 28, minHeight: 28), - splashRadius: 18, - tooltip: 'Delete key', - ), + QueryaIconButton( +icon: material.Icon(material.Icons.delete_rounded), +tooltip: 'Delete key', +size: QueryaControlSize.sm, +isDestructive: true, +onPressed: onDelete, +), material.Icon(material.Icons.chevron_right_rounded, size: 18, color: shadcnCs.mutedForeground), ], diff --git a/lib/features/settings/preferences_dialog.dart b/lib/features/settings/preferences_dialog.dart index d04e6cc1..2f8c8584 100644 --- a/lib/features/settings/preferences_dialog.dart +++ b/lib/features/settings/preferences_dialog.dart @@ -346,18 +346,12 @@ class PreferencesDialogContentState ), ), const Gap(8), - material.IconButton( - icon: const material.Icon(material.Icons.close_rounded, - size: 18), - padding: material.EdgeInsets.zero, - constraints: const material.BoxConstraints( - minWidth: 28, - minHeight: 28, - ), - splashRadius: 16, - tooltip: 'Close', - onPressed: () => material.Navigator.of(context).pop(), - ), + QueryaIconButton( +icon: material.Icon(material.Icons.close_rounded), +tooltip: 'Close', +size: QueryaControlSize.sm, +onPressed: () => material.Navigator.of(context).pop(), +), ], ), ), diff --git a/lib/features/workspace/data_grid_staging_toolbar.dart b/lib/features/workspace/data_grid_staging_toolbar.dart index 687199be..9d514843 100644 --- a/lib/features/workspace/data_grid_staging_toolbar.dart +++ b/lib/features/workspace/data_grid_staging_toolbar.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/core/ui/querya_tooltip.dart'; import 'package:querya_desktop/features/workspace/data_grid_staging_buffer.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; import 'package:querya_desktop/core/ui/querya_control_tokens.dart'; @@ -102,7 +103,7 @@ class DataGridStagingToolbar extends StatelessWidget { const Gap(6), material.Tooltip( message: 'Discard all pending edits (Ctrl+Z / Cmd+Z)', - waitDuration: const Duration(milliseconds: 400), + waitDuration: kQueryaTooltipWait, child: compact ? QueryaButton.icon( icon: material.Icons.undo_rounded, @@ -137,7 +138,7 @@ class DataGridStagingToolbar extends StatelessWidget { message: isDirty ? 'Review and save pending changes (Ctrl+S / Cmd+S)' : 'No pending changes to save', - waitDuration: const Duration(milliseconds: 400), + waitDuration: kQueryaTooltipWait, child: QueryaButton.primary( label: isSaving ? 'Saving…' : (compact ? 'Save' : 'Save Changes'), icon: material.Icons.save_rounded, @@ -170,7 +171,7 @@ class _PendingBadge extends StatelessWidget { : '$count pending ${count == 1 ? 'change' : 'changes'}'; return material.Tooltip( message: '$count pending ${count == 1 ? 'change' : 'changes'} not saved yet', - waitDuration: const Duration(milliseconds: 400), + waitDuration: kQueryaTooltipWait, child: material.Container( padding: const material.EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: material.BoxDecoration( diff --git a/lib/features/workspace/data_grid_value_panel.dart b/lib/features/workspace/data_grid_value_panel.dart index bac07d32..f642b5c9 100644 --- a/lib/features/workspace/data_grid_value_panel.dart +++ b/lib/features/workspace/data_grid_value_panel.dart @@ -5,6 +5,7 @@ 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/core/ui/querya_control_tokens.dart'; +import 'package:querya_desktop/shared/widgets/querya_button.dart'; import 'package:querya_desktop/shared/widgets/querya_dropdown.dart'; import 'package:querya_desktop/shared/widgets/querya_icon_button.dart'; import 'package:querya_desktop/shared/widgets/querya_text.dart'; @@ -357,25 +358,17 @@ class _DataGridValuePanelState extends material.State { ), const Gap(6), if (activeLang == ValuePanelLanguage.json || activeLang == ValuePanelLanguage.xml) ...[ - material.TextButton( - onPressed: _formatCode, - style: material.TextButton.styleFrom( - padding: const material.EdgeInsets.symmetric(horizontal: 6, vertical: 2), - minimumSize: material.Size.zero, - tapTargetSize: material.MaterialTapTargetSize.shrinkWrap, - ), - child: const Text('Format').small(), - ), + QueryaButton.ghost( + label: 'Format', + size: QueryaControlSize.sm, + onPressed: _formatCode, + ), const Gap(4), - material.TextButton( - onPressed: _minifyCode, - style: material.TextButton.styleFrom( - padding: const material.EdgeInsets.symmetric(horizontal: 6, vertical: 2), - minimumSize: material.Size.zero, - tapTargetSize: material.MaterialTapTargetSize.shrinkWrap, - ), - child: const Text('Minify').small(), - ), + QueryaButton.ghost( + label: 'Minify', + size: QueryaControlSize.sm, + onPressed: _minifyCode, + ), ], const material.Spacer(), QueryaIconButton( @@ -490,16 +483,11 @@ class _DataGridValuePanelState extends material.State { ), ), ], - material.ElevatedButton( - onPressed: _validationError != null ? null : _handleUpdateValue, - style: material.ElevatedButton.styleFrom( - backgroundColor: cs.primary, - foregroundColor: cs.primaryForeground, - padding: const material.EdgeInsets.symmetric(vertical: 8), - minimumSize: material.Size.zero, - ), - child: const Text('Update Cell Value').small().bold(), - ), + QueryaButton.primary( + label: 'Update Cell Value', + size: QueryaControlSize.sm, + onPressed: _validationError != null ? null : _handleUpdateValue, + ), ], ), ), diff --git a/lib/features/workspace/table_view_staging.dart b/lib/features/workspace/table_view_staging.dart index a3b4068a..1b7abf1e 100644 --- a/lib/features/workspace/table_view_staging.dart +++ b/lib/features/workspace/table_view_staging.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart' as material; import 'package:flutter/services.dart' show Clipboard, ClipboardData; import 'package:querya_desktop/core/database/table_mutation_engine.dart'; import 'package:querya_desktop/core/database/table_schema_meta.dart'; +import 'package:querya_desktop/core/ui/querya_tooltip.dart'; import 'package:querya_desktop/features/workspace/data_grid_staging_buffer.dart'; import 'package:querya_desktop/features/workspace/dml_preview_dialog.dart'; import 'package:querya_desktop/features/workspace/save_error_description.dart'; @@ -223,7 +224,7 @@ class TableEditModeButton extends material.StatelessWidget { if (editMode) { return material.Tooltip( message: 'Stop editing (Ctrl+E)', - waitDuration: const Duration(milliseconds: 400), + waitDuration: kQueryaTooltipWait, child: QueryaButton.primary( label: 'Done', icon: material.Icons.check_rounded, @@ -237,7 +238,7 @@ class TableEditModeButton extends material.StatelessWidget { message: canEdit ? 'Edit rows (Ctrl+E)' : (disabledReason ?? 'This table cannot be edited'), - waitDuration: const Duration(milliseconds: 400), + waitDuration: kQueryaTooltipWait, child: QueryaButton.secondary( label: 'Edit', icon: material.Icons.edit_rounded, diff --git a/lib/shared/widgets/querya_search_field.dart b/lib/shared/widgets/querya_search_field.dart index 90ac78ee..62a4c957 100644 --- a/lib/shared/widgets/querya_search_field.dart +++ b/lib/shared/widgets/querya_search_field.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart' as material; import 'package:flutter/services.dart'; import 'package:querya_desktop/core/theme/querya_theme_scope.dart'; import 'package:querya_desktop/core/ui/querya_control_tokens.dart'; +import 'package:querya_desktop/shared/widgets/querya_button.dart'; /// Standard search and filter input field with debounce, clear button, and shortcut support. class QueryaSearchField extends material.StatefulWidget { @@ -210,17 +211,10 @@ class _QueryaSearchFieldState extends material.State { if (_hasText) material.Padding( padding: const material.EdgeInsets.only(right: 4), - child: material.IconButton( - icon: const material.Icon(material.Icons.close_rounded, size: 14), - splashRadius: 12, - padding: material.EdgeInsets.zero, - constraints: const material.BoxConstraints( - minWidth: 20, - minHeight: 20, - ), - color: cs.mutedForeground, - hoverColor: cs.muted.withValues(alpha: 0.3), + child: QueryaButton.icon( + icon: material.Icons.close_rounded, tooltip: 'Clear search (Esc)', + size: QueryaControlSize.sm, onPressed: _clear, ), ) diff --git a/test/guards/button_standard_guards_test.dart b/test/guards/button_standard_guards_test.dart new file mode 100644 index 00000000..ccd9c6a9 --- /dev/null +++ b/test/guards/button_standard_guards_test.dart @@ -0,0 +1,85 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; + +/// The button standard (`docs/ui-kit.md`, epic #1331): every button is a +/// `QueryaButton` / `QueryaIconButton` / menu or split button of the kit. +/// These guards keep the migration done. An allow-list entry names the +/// follow-up that removes it. +void main() { + List libFiles() => [ + for (final e in Directory('lib').listSync(recursive: true)) + if (e is File && e.path.endsWith('.dart')) e, + ]; + + /// The source without `//` comments, so a word in a comment is not a use. + String code(File f) => f + .readAsLinesSync() + .where((l) => !l.trimLeft().startsWith('//')) + .join('\n'); + + String norm(File f) => f.path.replaceAll(r'\', '/'); + + test('no shadcn button types in lib/', () { + final banned = RegExp( + r'\b(PrimaryButton|OutlineButton|GhostButton|DestructiveButton|' + r'SecondaryButton|LinkButton|TextButton|ButtonSize|ButtonDensity)\b', + ); + final offenders = []; + for (final f in libFiles()) { + // Material's own TextButton is covered by the next guard. + final source = code(f).replaceAll(RegExp(r'material\.TextButton'), ''); + if (banned.hasMatch(source)) offenders.add(norm(f)); + } + + expect(offenders, isEmpty, + reason: 'shadcn buttons in $offenders: use QueryaButton.'); + }); + + test('no raw Material buttons in lib/', () { + final banned = RegExp( + r'(?[]; + for (final f in libFiles()) { + if (banned.hasMatch(code(f))) offenders.add(norm(f)); + } + + expect(offenders, isEmpty, + reason: 'Material buttons in $offenders: use QueryaButton / ' + 'QueryaIconButton.'); + }); + + test('no button class outside the kit', () { + // Each entry is a labelled trigger that is still hand-built; the + // follow-up is #1445. + const allowed = { + 'lib/features/settings/theme_picker_button.dart', + 'lib/features/workspace/table_view_staging.dart', + }; + final decl = RegExp(r'^class _?\w*Button\b', multiLine: true); + final offenders = []; + for (final f in libFiles()) { + final path = norm(f); + if (path.startsWith('lib/shared/widgets/')) continue; + if (allowed.contains(path)) continue; + if (decl.hasMatch(code(f))) offenders.add(path); + } + + expect(offenders, isEmpty, + reason: 'Button classes in $offenders: compose QueryaButton instead.'); + }); + + test('tooltips take kQueryaTooltipWait, not a literal delay', () { + final banned = RegExp(r'waitDuration:\s*(const\s+)?Duration\('); + final offenders = []; + for (final f in libFiles()) { + if (norm(f) == 'lib/core/ui/querya_tooltip.dart') continue; + if (banned.hasMatch(code(f))) offenders.add(norm(f)); + } + + expect(offenders, isEmpty, + reason: 'Literal tooltip delays in $offenders: use kQueryaTooltipWait.'); + }); +} From 80186324ed55cb3ccd278e6b3487f07a60cc327d Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 11 Oct 2026 17:14:18 +0300 Subject: [PATCH 3/4] style: const kit icon buttons (prefer_const_constructors) --- .../connections/sqlite_connection_form.dart | 2 +- .../mongodb/mongo_document_tree_view.dart | 16 ++++++++-------- lib/features/mongodb/mongo_documents_view.dart | 4 ++-- .../mongodb/mongodb_connection_form.dart | 2 +- lib/features/mysql/mysql_connection_form.dart | 2 +- lib/features/onboarding/welcome_tour_dialog.dart | 2 +- .../postgresql/postgresql_connection_form.dart | 2 +- lib/features/redis/redis_connection_form.dart | 2 +- lib/features/redis/redis_keys_view.dart | 4 ++-- lib/features/settings/preferences_dialog.dart | 2 +- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/features/connections/sqlite_connection_form.dart b/lib/features/connections/sqlite_connection_form.dart index 40c7778a..34f86cdd 100644 --- a/lib/features/connections/sqlite_connection_form.dart +++ b/lib/features/connections/sqlite_connection_form.dart @@ -365,7 +365,7 @@ class _SqliteConnectionFormContentState ).small(), ), QueryaIconButton( -icon: material.Icon(material.Icons.close), +icon: const material.Icon(material.Icons.close), tooltip: 'Dismiss', size: QueryaControlSize.sm, onPressed: _dismissResult, diff --git a/lib/features/mongodb/mongo_document_tree_view.dart b/lib/features/mongodb/mongo_document_tree_view.dart index 71cbec60..985da922 100644 --- a/lib/features/mongodb/mongo_document_tree_view.dart +++ b/lib/features/mongodb/mongo_document_tree_view.dart @@ -314,7 +314,7 @@ class _MongoDocumentTreeViewState extends State { const Spacer(), // Actions QueryaIconButton( - icon: material.Icon(material.Icons.copy_rounded), + icon: const material.Icon(material.Icons.copy_rounded), tooltip: 'Copy Document JSON', size: QueryaControlSize.sm, onPressed: () => _copyText( @@ -325,7 +325,7 @@ class _MongoDocumentTreeViewState extends State { if (widget.onDocumentTap != null) ...[ const Gap(4), QueryaIconButton( - icon: material.Icon(material.Icons.open_in_new_rounded), + icon: const material.Icon(material.Icons.open_in_new_rounded), tooltip: 'View / Edit Document', size: QueryaControlSize.sm, onPressed: () => widget.onDocumentTap!(doc), @@ -334,7 +334,7 @@ class _MongoDocumentTreeViewState extends State { if (widget.onDeleteDocument != null) ...[ const Gap(4), QueryaIconButton( - icon: material.Icon(material.Icons.delete_outline_rounded), + icon: const material.Icon(material.Icons.delete_outline_rounded), tooltip: 'Delete Document', size: QueryaControlSize.sm, isDestructive: true, @@ -545,14 +545,14 @@ class _MongoDocumentTreeViewState extends State { ), const Spacer(), QueryaIconButton( - icon: material.Icon(material.Icons.alt_route_rounded), + icon: const material.Icon(material.Icons.alt_route_rounded), tooltip: 'Copy JSON Path ($jsonPath)', size: QueryaControlSize.sm, onPressed: () => _copyText(jsonPath, 'JSON Path: $jsonPath'), ), const Gap(4), QueryaIconButton( - icon: material.Icon(material.Icons.copy_rounded), + icon: const material.Icon(material.Icons.copy_rounded), tooltip: 'Copy JSON', size: QueryaControlSize.sm, onPressed: () { @@ -649,14 +649,14 @@ class _MongoDocumentTreeViewState extends State { ), const Gap(6), QueryaIconButton( - icon: material.Icon(material.Icons.alt_route_rounded), + icon: const material.Icon(material.Icons.alt_route_rounded), tooltip: 'Copy JSON Path ($jsonPath)', size: QueryaControlSize.sm, onPressed: () => _copyText(jsonPath, 'JSON Path: $jsonPath'), ), const Gap(4), QueryaIconButton( - icon: material.Icon(material.Icons.copy_rounded), + icon: const material.Icon(material.Icons.copy_rounded), tooltip: 'Copy Value', size: QueryaControlSize.sm, onPressed: () => _copyText(valueDisplay, 'Value'), @@ -664,7 +664,7 @@ class _MongoDocumentTreeViewState extends State { if (canEdit) ...[ const Gap(4), QueryaIconButton( - icon: material.Icon(material.Icons.edit_note_rounded), + icon: const material.Icon(material.Icons.edit_note_rounded), tooltip: 'Edit Field', size: QueryaControlSize.sm, onPressed: () => widget.onInspectField!(rootDoc, keyOrIndex), diff --git a/lib/features/mongodb/mongo_documents_view.dart b/lib/features/mongodb/mongo_documents_view.dart index 5c0342c6..6dfd8ac7 100644 --- a/lib/features/mongodb/mongo_documents_view.dart +++ b/lib/features/mongodb/mongo_documents_view.dart @@ -763,14 +763,14 @@ class _DocumentCardState extends State<_DocumentCard> { ), const Gap(8), QueryaIconButton( - icon: material.Icon(material.Icons.edit_rounded), + icon: const material.Icon(material.Icons.edit_rounded), tooltip: 'Open document', size: QueryaControlSize.sm, onPressed: widget.onView, ), const Gap(4), QueryaIconButton( - icon: material.Icon(material.Icons.delete_rounded), + icon: const material.Icon(material.Icons.delete_rounded), tooltip: 'Delete document', size: QueryaControlSize.sm, isDestructive: true, diff --git a/lib/features/mongodb/mongodb_connection_form.dart b/lib/features/mongodb/mongodb_connection_form.dart index 14f7a822..30c1ddd0 100644 --- a/lib/features/mongodb/mongodb_connection_form.dart +++ b/lib/features/mongodb/mongodb_connection_form.dart @@ -625,7 +625,7 @@ class _MongoConnectionFormContentState ).small(), ), QueryaIconButton( -icon: material.Icon(material.Icons.close), +icon: const material.Icon(material.Icons.close), tooltip: 'Dismiss', size: QueryaControlSize.sm, onPressed: _dismissResult, diff --git a/lib/features/mysql/mysql_connection_form.dart b/lib/features/mysql/mysql_connection_form.dart index cb327991..4238819f 100644 --- a/lib/features/mysql/mysql_connection_form.dart +++ b/lib/features/mysql/mysql_connection_form.dart @@ -530,7 +530,7 @@ class _MysqlConnectionFormContentState ).small(), ), QueryaIconButton( -icon: material.Icon(material.Icons.close), +icon: const material.Icon(material.Icons.close), tooltip: 'Dismiss', size: QueryaControlSize.sm, onPressed: _dismissResult, diff --git a/lib/features/onboarding/welcome_tour_dialog.dart b/lib/features/onboarding/welcome_tour_dialog.dart index c3fa515b..d425f5d2 100644 --- a/lib/features/onboarding/welcome_tour_dialog.dart +++ b/lib/features/onboarding/welcome_tour_dialog.dart @@ -272,7 +272,7 @@ class _WelcomeTourDialogState extends State { const material.SizedBox(width: 4), ], QueryaIconButton( -icon: material.Icon(material.Icons.close_rounded), +icon: const material.Icon(material.Icons.close_rounded), tooltip: 'Close', size: QueryaControlSize.sm, onPressed: _finish, diff --git a/lib/features/postgresql/postgresql_connection_form.dart b/lib/features/postgresql/postgresql_connection_form.dart index a76c8291..5b5e7bf3 100644 --- a/lib/features/postgresql/postgresql_connection_form.dart +++ b/lib/features/postgresql/postgresql_connection_form.dart @@ -751,7 +751,7 @@ class _PostgresConnectionFormContentState ).small(), ), QueryaIconButton( -icon: material.Icon(material.Icons.close), +icon: const material.Icon(material.Icons.close), tooltip: 'Dismiss', size: QueryaControlSize.sm, onPressed: _dismissResult, diff --git a/lib/features/redis/redis_connection_form.dart b/lib/features/redis/redis_connection_form.dart index 2512cb41..91033492 100644 --- a/lib/features/redis/redis_connection_form.dart +++ b/lib/features/redis/redis_connection_form.dart @@ -541,7 +541,7 @@ onPressed: () => setState( ).small(), ), QueryaIconButton( -icon: material.Icon(material.Icons.close), +icon: const material.Icon(material.Icons.close), tooltip: 'Dismiss', size: QueryaControlSize.sm, onPressed: _dismissResult, diff --git a/lib/features/redis/redis_keys_view.dart b/lib/features/redis/redis_keys_view.dart index af5b8a71..640472fa 100644 --- a/lib/features/redis/redis_keys_view.dart +++ b/lib/features/redis/redis_keys_view.dart @@ -776,7 +776,7 @@ class _FolderTile extends material.StatelessWidget { const material.Spacer(), if (onDelete != null) QueryaIconButton( -icon: material.Icon(material.Icons.delete_outline_rounded), +icon: const material.Icon(material.Icons.delete_outline_rounded), tooltip: 'Delete folder (${folder.totalKeyCount} keys)', size: QueryaControlSize.sm, isDestructive: true, @@ -934,7 +934,7 @@ class _KeyTile extends material.StatelessWidget { const Gap(4), if (onDelete != null) QueryaIconButton( -icon: material.Icon(material.Icons.delete_rounded), +icon: const material.Icon(material.Icons.delete_rounded), tooltip: 'Delete key', size: QueryaControlSize.sm, isDestructive: true, diff --git a/lib/features/settings/preferences_dialog.dart b/lib/features/settings/preferences_dialog.dart index 2f8c8584..4ce70256 100644 --- a/lib/features/settings/preferences_dialog.dart +++ b/lib/features/settings/preferences_dialog.dart @@ -347,7 +347,7 @@ class PreferencesDialogContentState ), const Gap(8), QueryaIconButton( -icon: material.Icon(material.Icons.close_rounded), +icon: const material.Icon(material.Icons.close_rounded), tooltip: 'Close', size: QueryaControlSize.sm, onPressed: () => material.Navigator.of(context).pop(), From a0c87dfa643e62a30901443969842006a55b7650 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 11 Oct 2026 17:20:29 +0300 Subject: [PATCH 4/4] test: the value panel's update button is a QueryaButton --- test/features/workspace/data_grid_value_panel_test.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/features/workspace/data_grid_value_panel_test.dart b/test/features/workspace/data_grid_value_panel_test.dart index f66621ad..c0a68d2a 100644 --- a/test/features/workspace/data_grid_value_panel_test.dart +++ b/test/features/workspace/data_grid_value_panel_test.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart' as material; import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/shared/widgets/querya_button.dart'; import 'package:querya_desktop/features/workspace/data_grid_value_panel.dart'; import 'package:querya_desktop/features/workspace/xml_html_formatter.dart'; @@ -169,8 +170,12 @@ void main() { // Error banner should be displayed immediately expect(find.textContaining('Invalid JSON'), findsOneWidget); - // Elevated button is now disabled because _validationError != null - final button = tester.widget(find.byType(material.ElevatedButton)); + // The update button is now disabled because _validationError != null + final button = tester.widget( + find.byWidgetPredicate( + (w) => w is QueryaButton && w.label == 'Update Cell Value', + ), + ); expect(button.onPressed, isNull); });