From e5cc1e132430df3b1a4d4fbd64f5cfa1bc34b754 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 11 Oct 2026 08:32:48 +0300 Subject: [PATCH] refactor(ui): QueryaActionButton is a QueryaButton.secondary wrapper, off shadcn The action button keeps its API (label, icon, loading, isDestructive, tooltip, compact) but renders a QueryaButton.secondary; size is now a QueryaControlSize instead of shadcn's ButtonSize, so the three MCP preference buttons pass sm. The Execute (F5) finders in four tests look for QueryaButton. The file leaves the shadcn allow-list. Part of #1337 and #1365. --- .../settings/preferences_mcp_section.dart | 7 +- lib/shared/widgets/querya_action_button.dart | 66 +++++++------------ .../extension_sql_workspace_test.dart | 3 +- .../generic_sql_workspace_shortcuts_test.dart | 6 +- ...ql_workspace_execute_dirty_guard_test.dart | 3 +- .../shared_does_not_import_shadcn_test.dart | 1 - .../widgets/querya_action_button_test.dart | 2 +- 7 files changed, 35 insertions(+), 53 deletions(-) diff --git a/lib/features/settings/preferences_mcp_section.dart b/lib/features/settings/preferences_mcp_section.dart index c4300c39..82fc953b 100644 --- a/lib/features/settings/preferences_mcp_section.dart +++ b/lib/features/settings/preferences_mcp_section.dart @@ -10,6 +10,7 @@ import 'package:querya_desktop/core/security/connection_environment.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; import 'package:querya_desktop/features/settings/preferences_controls.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; +import 'package:querya_desktop/core/ui/querya_control_tokens.dart'; /// Preferences → MCP Server (#1136): enable the in-app MCP server, choose the /// connections AI clients may read, copy client configs, see recent calls. @@ -256,14 +257,14 @@ class PreferencesMcpSectionState key: material.ValueKey('mcp_copy_${kind.name}'), label: 'Copy ${kind.label} config', icon: material.Icons.copy_rounded, - size: ButtonSize.small, + size: QueryaControlSize.sm, onPressed: () => unawaited(_copy(kind)), ), QueryaActionButton( key: const material.ValueKey('mcp_regenerate'), label: 'Regenerate token', icon: material.Icons.key_rounded, - size: ButtonSize.small, + size: QueryaControlSize.sm, tooltip: 'Disconnects connected clients; they reconnect on ' 'their next start.', onPressed: _enabled && !_busy ? () => unawaited(_regenerate()) : null, @@ -285,7 +286,7 @@ class PreferencesMcpSectionState QueryaActionButton( key: const material.ValueKey('mcp_clear_log'), label: 'Clear', - size: ButtonSize.small, + size: QueryaControlSize.sm, onPressed: _activity.isEmpty ? null : () => unawaited(_clearLog()), ), ], diff --git a/lib/shared/widgets/querya_action_button.dart b/lib/shared/widgets/querya_action_button.dart index 4dff999e..5b11fd26 100644 --- a/lib/shared/widgets/querya_action_button.dart +++ b/lib/shared/widgets/querya_action_button.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart' as material; -import 'package:querya_desktop/core/theme/querya_theme_scope.dart'; +import 'package:querya_desktop/core/ui/querya_control_tokens.dart'; import 'package:querya_desktop/core/ui/querya_tooltip.dart'; -import 'package:querya_desktop/shared/widgets/querya_spinner.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:querya_desktop/shared/widgets/querya_button.dart'; -/// Labelled outline button for toolbars: optional leading icon, a [loading] -/// state with a spinner, and a destructive tone. +/// Labelled toolbar button: optional leading icon, a [loading] state and a +/// destructive tone. A thin wrapper over [QueryaButton.secondary]; new code +/// uses [QueryaButton] directly (epic #1331). /// -/// While [loading] the button is disabled and shows a [QueryaSpinner] in place -/// of [icon]. Icon and spinner follow the workbench accent, or the destructive -/// color when [isDestructive] is set. +/// While [loading] the button is disabled and shows a spinner in place of +/// [icon]. With [isDestructive] the label and icon use the workbench +/// destructive colour. class QueryaActionButton extends material.StatelessWidget { const QueryaActionButton({ super.key, @@ -19,7 +19,7 @@ class QueryaActionButton extends material.StatelessWidget { this.loading = false, this.isDestructive = false, this.tooltip, - this.size = ButtonSize.normal, + this.size, this.compact = false, }); @@ -32,12 +32,14 @@ class QueryaActionButton extends material.StatelessWidget { /// Shows a spinner instead of [icon] and disables the button. final bool loading; - /// Uses the destructive color for the icon and the label. + /// Uses the destructive colour for the icon and the label. final bool isDestructive; /// Shown on hover after [kQueryaTooltipWait]. final String? tooltip; - final ButtonSize size; + + /// Control size; without one the nearest `QueryaControlScope`, then `md`. + final QueryaControlSize? size; /// Hides the label and keeps the icon (narrow toolbars). The label becomes /// the tooltip when none is set. Without an [icon], [compact] has no effect. @@ -45,39 +47,17 @@ class QueryaActionButton extends material.StatelessWidget { @override material.Widget build(material.BuildContext context) { - final workbench = context.workbench; - final tone = isDestructive ? workbench.destructive : workbench.accent; - final enabled = onPressed != null && !loading; - - material.Widget? leading; - if (loading) { - leading = QueryaSpinner(size: QueryaSpinnerSize.sm, color: tone); - } else if (icon != null) { - leading = material.Icon(icon, size: 18, color: tone); - } - - final iconOnly = compact && leading != null; - material.Widget button = OutlineButton( + return QueryaButton.secondary( + label: label, + icon: icon, + onPressed: onPressed, + loading: loading, + tooltip: tooltip, size: size, - onPressed: enabled ? onPressed : null, - leading: leading, - child: iconOnly - ? const material.SizedBox.shrink() - : isDestructive - ? Text(label, style: material.TextStyle(color: tone)) - : Text(label), + compact: compact, + tone: isDestructive + ? QueryaButtonTone.destructive + : QueryaButtonTone.normal, ); - - final hint = (tooltip != null && tooltip!.isNotEmpty) - ? tooltip! - : (iconOnly ? label : null); - if (hint != null) { - button = material.Tooltip( - message: hint, - waitDuration: kQueryaTooltipWait, - child: button, - ); - } - return button; } } diff --git a/test/features/extensions/extension_sql_workspace_test.dart b/test/features/extensions/extension_sql_workspace_test.dart index 0e7fc166..a19a394c 100644 --- a/test/features/extensions/extension_sql_workspace_test.dart +++ b/test/features/extensions/extension_sql_workspace_test.dart @@ -10,6 +10,7 @@ import 'package:querya_desktop/core/storage/app_settings.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; import 'package:querya_desktop/features/extensions/extension_sql_workspace.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:querya_desktop/shared/widgets/querya_button.dart'; import '../../support/querya_theme_test_shell.dart'; @@ -110,7 +111,7 @@ void main() { await tester.pumpAndSettle(); // Find Execute button in SQL editor chrome - final executeBtn = find.widgetWithText(OutlineButton, 'Execute (F5)'); + final executeBtn = find.widgetWithText(QueryaButton, 'Execute (F5)'); expect(executeBtn, findsOneWidget); await tester.tap(executeBtn); diff --git a/test/features/workspace/generic_sql_workspace_shortcuts_test.dart b/test/features/workspace/generic_sql_workspace_shortcuts_test.dart index a8c95819..fcce2173 100644 --- a/test/features/workspace/generic_sql_workspace_shortcuts_test.dart +++ b/test/features/workspace/generic_sql_workspace_shortcuts_test.dart @@ -15,7 +15,7 @@ import 'package:querya_desktop/features/settings/preferences_shortcuts_section.d import 'package:querya_desktop/features/workspace/generic_sql_workspace.dart'; import 'package:querya_desktop/features/workspace/results_tab.dart' show formatResultElapsed; import 'package:querya_desktop/features/workspace/sql_execution_delegate.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart' show OutlineButton; +import 'package:querya_desktop/shared/widgets/querya_button.dart'; import '../../support/fake_sql_execution_delegate.dart'; import '../../support/local_db_test_support.dart'; @@ -98,7 +98,7 @@ void main() { initialSql: 'SELECT n FROM t', ); - await tester.tap(find.widgetWithText(OutlineButton, 'Execute (F5)')); + await tester.tap(find.widgetWithText(QueryaButton, 'Execute (F5)')); await settle(tester); expect(delegate.executed, ['SELECT n FROM t']); @@ -390,7 +390,7 @@ void main() { await pumpWorkspace(tester, delegate, initialSql: 'SELECT n FROM t'); expect(find.byKey(const material.ValueKey('result_elapsed')), findsNothing); - await tester.tap(find.widgetWithText(OutlineButton, 'Execute (F5)')); + await tester.tap(find.widgetWithText(QueryaButton, 'Execute (F5)')); await settle(tester); expect(find.byKey(const material.ValueKey('result_elapsed')), findsOneWidget); diff --git a/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart b/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart index eb6d47ae..ea911785 100644 --- a/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart +++ b/test/features/workspace/sql_workspace_execute_dirty_guard_test.dart @@ -8,6 +8,7 @@ import 'package:querya_desktop/core/storage/local_db.dart'; import 'package:querya_desktop/features/sqlite/sqlite_sql_workspace.dart'; import 'package:querya_desktop/features/workspace/workspace.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:querya_desktop/shared/widgets/querya_button.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import '../../support/querya_theme_test_shell.dart'; @@ -111,7 +112,7 @@ void main() { expect(buffer.isDirty, isTrue); // 1. Press Execute while buffer is dirty - final executeBtn = find.widgetWithText(OutlineButton, 'Execute (F5)'); + final executeBtn = find.widgetWithText(QueryaButton, 'Execute (F5)'); expect(executeBtn, findsOneWidget); // 3. Press Execute again while buffer is dirty diff --git a/test/guards/shared_does_not_import_shadcn_test.dart b/test/guards/shared_does_not_import_shadcn_test.dart index 59fc7d5c..3ce73b32 100644 --- a/test/guards/shared_does_not_import_shadcn_test.dart +++ b/test/guards/shared_does_not_import_shadcn_test.dart @@ -13,7 +13,6 @@ void main() { const allowed = { 'lib/shared/widgets/app_toast.dart', 'lib/shared/widgets/export_menu_button.dart', - 'lib/shared/widgets/querya_action_button.dart', 'lib/shared/widgets/querya_action_menu.dart', 'lib/shared/widgets/widgets.dart', }; diff --git a/test/shared/widgets/querya_action_button_test.dart b/test/shared/widgets/querya_action_button_test.dart index c720373f..c2762915 100644 --- a/test/shared/widgets/querya_action_button_test.dart +++ b/test/shared/widgets/querya_action_button_test.dart @@ -27,7 +27,7 @@ void main() { child: const QueryaActionButton(label: 'Begin'), )); - final button = tester.widget(find.byType(OutlineButton)); + final button = tester.widget(find.byType(QueryaButton)); expect(button.onPressed, isNull); });