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
10 changes: 10 additions & 0 deletions lib/core/theme/querya_theme_scope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ extension QueryaThemeContext on BuildContext {

QueryaWorkbenchTheme get workbench => queryaTheme.workbench;

/// The workbench tokens of the nearest [QueryaThemeScope], or the defaults
/// for the ambient brightness when there is none (a widget under a bare app,
/// as in tests). Kit controls use this so they never assert on a missing
/// scope.
QueryaWorkbenchTheme get workbenchOrDefault =>
QueryaThemeScope.maybeOf(this)?.workbench ??
(colors.brightness == Brightness.light
? QueryaWorkbenchTheme.lightDefault
: QueryaWorkbenchTheme.darkDefault);

QueryaEditorTheme get editorTheme => queryaTheme.editor;

QueryaSemanticPalette get semanticPalette =>
Expand Down
4 changes: 2 additions & 2 deletions lib/shared/widgets/querya_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -294,8 +294,8 @@ class _QueryaButtonState extends material.State<QueryaButton> {
@override
material.Widget build(material.BuildContext context) {
final w = widget;
final wb = context.workbench;
final scheme = context.queryaTheme.colorScheme;
final wb = context.workbenchOrDefault;
final scheme = context.colors;
final size = w.size ?? QueryaControlSize.of(context);
final enabled = _enabled;
final hasLabel = w.label != null;
Expand Down
80 changes: 43 additions & 37 deletions lib/shared/widgets/querya_modal_dialog.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import 'package:flutter/material.dart' as material;
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:querya_desktop/core/theme/querya_theme_scope.dart';
import 'package:querya_desktop/core/ui/querya_control_tokens.dart';

import 'app_dialog.dart';
import 'querya_button.dart';
import 'querya_dialog_card.dart';

/// Standard modal dialog shell with title, optional description, icon, content, and actions.
Expand Down Expand Up @@ -53,7 +54,7 @@ class QueryaModalDialog extends material.StatelessWidget {

@override
material.Widget build(material.BuildContext context) {
final cs = Theme.of(context).colorScheme;
final cs = context.colors;
final workbench = QueryaThemeScope.maybeOf(context)?.workbench;

material.Widget? leadingIcon = icon;
Expand Down Expand Up @@ -93,7 +94,7 @@ class QueryaModalDialog extends material.StatelessWidget {
children: [
if (leadingIcon != null) ...[
leadingIcon,
const Gap(14),
const material.SizedBox(width: 14),
],
material.Expanded(
child: material.Column(
Expand All @@ -109,7 +110,7 @@ class QueryaModalDialog extends material.StatelessWidget {
child: title,
),
if (description != null) ...[
const Gap(4),
const material.SizedBox(height: 4),
material.DefaultTextStyle(
style: material.TextStyle(
fontSize: 13,
Expand All @@ -124,39 +125,38 @@ class QueryaModalDialog extends material.StatelessWidget {
if (showCloseButton || onClose != null)
material.Padding(
padding: const material.EdgeInsets.only(left: 8),
child: material.IconButton(
icon: const material.Icon(material.Icons.close_rounded, size: 18),
splashRadius: 16,
padding: material.EdgeInsets.zero,
constraints: const material.BoxConstraints(
minWidth: 28,
minHeight: 28,
),
color: cs.mutedForeground,
hoverColor: cs.muted.withValues(alpha: 0.3),
onPressed: onClose ?? () => Navigator.of(context).pop(),
child: QueryaButton.icon(
icon: material.Icons.close_rounded,
tooltip: 'Close',
size: QueryaControlSize.sm,
onPressed:
onClose ?? () => material.Navigator.of(context).pop(),
),
),
],
),

// Content
if (content != null) ...[
const Gap(16),
const material.SizedBox(height: 16),
content!,
],

// Actions
if (actions != null && actions!.isNotEmpty) ...[
const Gap(20),
const material.SizedBox(height: 20),
material.Align(
alignment: material.Alignment.centerRight,
child: material.Wrap(
alignment: material.WrapAlignment.end,
crossAxisAlignment: material.WrapCrossAlignment.center,
spacing: 8,
runSpacing: 8,
children: actions!,
// Buttons of the footer without an explicit size are `lg`.
child: QueryaControlScope(
size: QueryaControlSize.lg,
child: material.Wrap(
alignment: material.WrapAlignment.end,
crossAxisAlignment: material.WrapCrossAlignment.center,
spacing: 8,
runSpacing: 8,
children: actions!,
),
),
),
],
Expand Down Expand Up @@ -211,15 +211,15 @@ class QueryaConfirmDialog extends material.StatelessWidget {
cancelLabel: cancelLabel,
isDestructive: isDestructive,
icon: icon,
onConfirm: () => Navigator.of(ctx).pop(true),
onCancel: () => Navigator.of(ctx).pop(false),
onConfirm: () => material.Navigator.of(ctx).pop(true),
onCancel: () => material.Navigator.of(ctx).pop(false),
),
);
}

@override
material.Widget build(material.BuildContext context) {
final cs = Theme.of(context).colorScheme;
final cs = context.colors;
final workbench = QueryaThemeScope.maybeOf(context)?.workbench;

final effectiveIconData = icon ??
Expand All @@ -232,24 +232,30 @@ class QueryaConfirmDialog extends material.StatelessWidget {
: (workbench?.accent ?? cs.primary);

return QueryaModalDialog(
title: Text(title),
description: Text(message),
title: material.Text(title),
description: material.Text(message),
icon: material.Icon(effectiveIconData),
iconColor: iconColor,
actions: [
OutlineButton(
onPressed: onCancel ?? () => Navigator.of(context).pop(false),
child: Text(cancelLabel),
QueryaButton.ghost(
label: cancelLabel,
size: QueryaControlSize.lg,
onPressed:
onCancel ?? () => material.Navigator.of(context).pop(false),
),
if (isDestructive)
DestructiveButton(
onPressed: onConfirm ?? () => Navigator.of(context).pop(true),
child: Text(confirmLabel),
QueryaButton.destructive(
label: confirmLabel,
size: QueryaControlSize.lg,
onPressed:
onConfirm ?? () => material.Navigator.of(context).pop(true),
)
else
PrimaryButton(
onPressed: onConfirm ?? () => Navigator.of(context).pop(true),
child: Text(confirmLabel),
QueryaButton.primary(
label: confirmLabel,
size: QueryaControlSize.lg,
onPressed:
onConfirm ?? () => material.Navigator.of(context).pop(true),
),
],
);
Expand Down
1 change: 0 additions & 1 deletion test/guards/shared_does_not_import_shadcn_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ void main() {
'lib/shared/widgets/export_menu_button.dart',
'lib/shared/widgets/querya_action_button.dart',
'lib/shared/widgets/querya_action_menu.dart',
'lib/shared/widgets/querya_modal_dialog.dart',
'lib/shared/widgets/widgets.dart',
};

Expand Down
8 changes: 7 additions & 1 deletion test/shared/widgets/querya_modal_dialog_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,13 @@ void main() {
),
);

expect(find.byType(DestructiveButton), findsOneWidget);
expect(
find.byWidgetPredicate(
(w) =>
w is QueryaButton && w.variant == QueryaButtonVariant.destructive,
),
findsOneWidget,
);
expect(find.text('Drop'), findsOneWidget);
});
});
Expand Down
8 changes: 7 additions & 1 deletion test/shared/widgets/querya_ui_kit_behavior_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,13 @@ void main() {
(tester) async {
await open(tester, (_) {}, isDestructive: true);

expect(find.byType(DestructiveButton), findsOneWidget);
expect(
find.byWidgetPredicate(
(w) =>
w is QueryaButton && w.variant == QueryaButtonVariant.destructive,
),
findsOneWidget,
);
});
});
}
Loading