Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion lib/features/mongodb/mongo_create_index_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,6 @@ class _MongoCreateIndexDialogState
),
tooltip: 'Remove Field',
size: QueryaControlSize.sm,
color: cs.destructive,
onPressed: () => _removeField(i),
),
],
Expand Down
2 changes: 0 additions & 2 deletions lib/features/workspace/data_grid_filter_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,6 @@ class _DataGridFilterBarState extends material.State<DataGridFilterBar> {
: material.Icons.close,
size: 14,
),
color: cs.mutedForeground,
tooltip: 'Clear filter',
onPressed: () {
_debounceTimer?.cancel();
Expand All @@ -446,7 +445,6 @@ class _DataGridFilterBarState extends material.State<DataGridFilterBar> {
QueryaIconButton(
size: QueryaControlSize.sm,
icon: const material.Icon(material.Icons.close, size: 14),
color: cs.mutedForeground,
tooltip: 'Close filter (Esc)',
onPressed: () {
_debounceTimer?.cancel();
Expand Down
2 changes: 0 additions & 2 deletions lib/features/workspace/data_grid_groupings_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,6 @@ class _DataGridGroupingsViewState
: material.Icons.arrow_downward_rounded,
size: 14,
),
color: cs.mutedForeground,
onPressed: () => setState(() {
_sortAscending = !_sortAscending;
_recomputeGroups();
Expand All @@ -263,7 +262,6 @@ class _DataGridGroupingsViewState
child: QueryaIconButton(
size: QueryaControlSize.sm,
icon: const material.Icon(material.Icons.copy_rounded, size: 14),
color: cs.mutedForeground,
onPressed: _exportPivot,
),
),
Expand Down
4 changes: 0 additions & 4 deletions lib/features/workspace/data_grid_value_panel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,6 @@ class _DataGridValuePanelState extends material.State<DataGridValuePanel> {
QueryaIconButton(
size: QueryaControlSize.sm,
icon: const material.Icon(material.Icons.close, size: 14),
color: cs.mutedForeground,
onPressed: widget.onClose,
),
],
Expand Down Expand Up @@ -386,14 +385,11 @@ class _DataGridValuePanelState extends material.State<DataGridValuePanel> {
size: 14,
),
isActive: _wordWrap,
color: cs.mutedForeground,
activeColor: cs.primary,
onPressed: () => setState(() => _wordWrap = !_wordWrap),
),
QueryaIconButton(
size: QueryaControlSize.sm,
icon: const material.Icon(material.Icons.copy_rounded, size: 14),
color: cs.mutedForeground,
onPressed: () {
Clipboard.setData(ClipboardData(text: _controller.text));
},
Expand Down
12 changes: 3 additions & 9 deletions lib/features/workspace/results_tab.dart
Original file line number Diff line number Diff line change
Expand Up @@ -565,9 +565,7 @@ class _ResultsTabState extends material.State<ResultsTab> {
size: 15,
),
tooltip: 'Toggle Quick Filter',
color: _showFilterBar || _filterText.isNotEmpty
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.mutedForeground,
isActive: _showFilterBar || _filterText.isNotEmpty,
onPressed: () {
setState(() => _showFilterBar = !_showFilterBar);
},
Expand All @@ -591,9 +589,7 @@ class _ResultsTabState extends material.State<ResultsTab> {
tooltip: masked
? 'Sensitive data is masked — click to show'
: 'Mask sensitive data',
color: masked
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.mutedForeground,
isActive: masked,
onPressed: PiiMaskingController.instance.toggle,
);
},
Expand All @@ -607,9 +603,7 @@ class _ResultsTabState extends material.State<ResultsTab> {
size: 15,
),
tooltip: 'Inspect Cell Panel',
color: _showValuePanel
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.mutedForeground,
isActive: _showValuePanel,
onPressed: () {
setState(() => _showValuePanel = !_showValuePanel);
},
Expand Down
8 changes: 6 additions & 2 deletions lib/shared/widgets/querya_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,12 @@ class _QueryaButtonState extends material.State<QueryaButton> {
? wb.destructive
: scheme.foreground;
if (w.isActive) {
foreground = wb.accent;
background = wb.accent.withValues(alpha: _pressed ? 0.22 : 0.14);
// A toggled button is tinted with its tone: the accent, or the
// destructive colour for a destructive one.
final activeColor =
w.tone == QueryaButtonTone.destructive ? wb.destructive : wb.accent;
foreground = activeColor;
background = activeColor.withValues(alpha: _pressed ? 0.22 : 0.14);
} else {
foreground = toneColor;
final tint = _pressed && enabled
Expand Down
158 changes: 30 additions & 128 deletions lib/shared/widgets/querya_icon_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@ 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';

/// Standard icon button sized by [QueryaControlSize], with hover / active
/// states and a built-in tooltip.
class QueryaIconButton extends material.StatefulWidget {
import 'package:querya_desktop/shared/widgets/querya_button.dart';

/// Icon-only button of the Querya kit, a thin wrapper over [QueryaButton]
/// (#1440): the same states as every other button (hover, pressed,
/// keyboard-only focus ring, disabled, `selected` semantics), square at its
/// [QueryaControlSize].
///
/// [icon] is a plain `Icon`: its glyph is used, the size and colour come from
/// the control size and the button's state. [isActive] tints it with the
/// accent, [isDestructive] with the destructive colour.
class QueryaIconButton extends material.StatelessWidget {
const QueryaIconButton({
super.key,
required this.icon,
Expand All @@ -14,15 +21,13 @@ class QueryaIconButton extends material.StatefulWidget {
this.size,
this.isActive = false,
this.isDestructive = false,
this.color,
this.activeColor,
this.borderRadius,
});

/// The icon widget or Icon to render.
final material.Widget icon;
/// The `Icon` to render.
final material.Icon icon;

/// Optional tooltip message shown on hover using [kQueryaTooltipWait].
/// Shown on hover after [kQueryaTooltipWait]; also the spoken label.
final String? tooltip;

/// Callback when pressed. If null, the button is disabled.
Expand All @@ -35,132 +40,29 @@ class QueryaIconButton extends material.StatefulWidget {
/// Whether the button is currently in an active/toggled state.
final bool isActive;

/// Whether clicking triggers a destructive action (tinted red on hover).
/// Whether clicking triggers a destructive action.
final bool isDestructive;

/// Custom icon color in normal state.
final material.Color? color;

/// Custom icon color in active state.
final material.Color? activeColor;

/// Custom border radius. Defaults to 6px.
/// Custom border radius; defaults to the control size's radius.
final material.BorderRadius? borderRadius;

@override
material.State<QueryaIconButton> createState() => _QueryaIconButtonState();
}

class _QueryaIconButtonState extends material.State<QueryaIconButton> {
/// Whether the button has keyboard focus (#1370): drawn as a 2px ring.
bool _focused = false;

@override
material.Widget build(material.BuildContext context) {
final icon = widget.icon;
final tooltip = widget.tooltip;
final onPressed = widget.onPressed;
final controlSize = widget.size ??
QueryaControlSize.of(context);
final boxSize = controlSize.scaledHeight(context);
final iconSize = controlSize.scaledIconSize(context);
final isActive = widget.isActive;
final isDestructive = widget.isDestructive;
final color = widget.color;
final activeColor = widget.activeColor;
final cs = context.colors;
final workbench = QueryaThemeScope.maybeOf(context)?.workbench;
final radius = widget.borderRadius ??
material.BorderRadius.circular(controlSize.scaledRadius(context));

final isEnabled = onPressed != null;

final destructiveColor = workbench?.destructive ?? cs.destructive;
final accentColor = workbench?.accent ?? cs.primary;
final mutedColor = workbench?.mutedForeground ?? cs.mutedForeground;

// Normal icon color
final normalColor = color ??
(isDestructive
? destructiveColor
: (isEnabled ? cs.foreground : cs.mutedForeground.withValues(alpha: 0.5)));

// Effective icon color
final effectiveIconColor = isActive
? (activeColor ?? (isDestructive ? destructiveColor : accentColor))
: normalColor;

// Effective background color for active state
material.Color? effectiveBgColor;
if (isActive) {
effectiveBgColor = isDestructive
? destructiveColor.withValues(alpha: 0.16)
: accentColor.withValues(alpha: 0.14);
}

final hoverColor = isDestructive
? destructiveColor.withValues(alpha: 0.12)
: cs.muted.withValues(alpha: 0.35);

material.Widget button = material.Material(
color: effectiveBgColor ?? material.Colors.transparent,
shape: material.RoundedRectangleBorder(
borderRadius: radius,
side: _focused
? material.BorderSide(color: cs.ring, width: 2)
: isActive
? material.BorderSide(
color: (isDestructive ? destructiveColor : accentColor)
.withValues(alpha: 0.3),
width: 1,
)
: material.BorderSide.none,
),
clipBehavior: material.Clip.antiAlias,
child: material.InkWell(
onTap: onPressed,
onFocusChange: (focused) {
if (_focused != focused) setState(() => _focused = focused);
},
borderRadius: radius,
hoverColor: hoverColor,
focusColor: material.Colors.transparent,
splashColor: (isDestructive ? destructiveColor : accentColor)
.withValues(alpha: 0.2),
child: material.SizedBox(
width: boxSize,
height: boxSize,
child: material.Center(
child: material.IconTheme(
data: material.IconThemeData(
size: iconSize,
color: isEnabled
? effectiveIconColor
: mutedColor.withValues(alpha: 0.4),
),
child: icon,
),
),
),
),
);

button = material.Semantics(
button: true,
enabled: onPressed != null,
selected: isActive,
child: button,
final glyph = icon.icon;
assert(glyph != null, 'QueryaIconButton needs an Icon with a glyph');
return QueryaButton(
variant: QueryaButtonVariant.ghost,
icon: glyph,
tooltip: tooltip,
semanticLabel: tooltip,
onPressed: onPressed,
isActive: isActive,
size: size,
tone: isDestructive
? QueryaButtonTone.destructive
: QueryaButtonTone.normal,
borderRadius: borderRadius,
);

if (tooltip != null && tooltip.isNotEmpty) {
button = material.Tooltip(
message: tooltip,
waitDuration: kQueryaTooltipWait,
child: button,
);
}

return button;
}
}

Expand Down
Loading
Loading