diff --git a/docs/theme.md b/docs/theme.md index f542f248..660386f9 100644 --- a/docs/theme.md +++ b/docs/theme.md @@ -55,7 +55,7 @@ flowchart TB ## Built-in presets - **Querya Dark** — default (`QueryaThemePreset.queryaDark`) -- **Querya Light** — light UI (`QueryaThemePreset.queryaLight`) +- **Querya Light** — built-in light UI (`QueryaThemePreset.queryaLight`); slate canvas, cyan accent, WCAG AA body text - **Imported** — after a VS Code file is imported (`QueryaThemePreset.imported`) Access tokens in widgets: diff --git a/lib/core/theme/querya_workbench_theme.dart b/lib/core/theme/querya_workbench_theme.dart index ddfd783d..99261857 100644 --- a/lib/core/theme/querya_workbench_theme.dart +++ b/lib/core/theme/querya_workbench_theme.dart @@ -51,7 +51,7 @@ class QueryaWorkbenchTheme { gitUntracked: Color(0xFF2EB88A), ); - /// Placeholder light preset (#51 will refine). + /// Built-in light preset (slate-like canvas, cyan brand accent). static const QueryaWorkbenchTheme lightDefault = QueryaWorkbenchTheme( canvas: Color(0xFFFAFAFA), surface: Color(0xFFFFFFFF), diff --git a/lib/features/main_screen/main_screen.dart b/lib/features/main_screen/main_screen.dart index 628f7446..a7461e5e 100644 --- a/lib/features/main_screen/main_screen.dart +++ b/lib/features/main_screen/main_screen.dart @@ -16,8 +16,8 @@ import 'package:flutter/material.dart' as material Widget, RepaintBoundary; import 'package:querya_desktop/core/storage/local_db.dart'; -import 'package:querya_desktop/core/theme/app_theme.dart'; import 'package:querya_desktop/core/theme/querya_theme_scope.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:querya_desktop/features/connections/connection_creation_flow.dart'; import 'package:querya_desktop/features/connections/connections_panel.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; @@ -127,37 +127,34 @@ class _MainScreenState extends State { @override material.Widget build(material.BuildContext context) { - final theme = AppTheme.dark.colorScheme; + final scheme = Theme.of(context).colorScheme; return material.Scaffold( - backgroundColor: theme.background, - body: Theme( - data: AppTheme.dark, - child: WindowBorder( - color: theme.border.withValues(alpha: 0.35), - width: 1, - child: Column( - children: [ - _CustomTitleBar( - theme: theme, - onNewDatabaseConnection: _onNewDatabaseConnectionFromMenu, - ), - Divider(height: 1, color: theme.border.withValues(alpha: 0.22)), - Expanded( - child: _MainContentSplit( - connectionsPanelKey: _connectionsPanelKey, - workspace: _workspace, - onConnectionSelected: _onConnectionSelected, - onPostgresObjectSelected: _onPostgresObjectSelected, - onMysqlObjectSelected: _onMysqlObjectSelected, - onRedisDatabaseSelected: _onRedisDatabaseSelected, - onMongoDBDatabaseSelected: _onMongoDBDatabaseSelected, - onPostgresOpenSqlWorkspace: _onPostgresOpenSqlWorkspace, - onMysqlOpenSqlWorkspace: _onMysqlOpenSqlWorkspace, - onRequestNewConnection: _openNewConnectionFromHero, - ), + backgroundColor: scheme.background, + body: WindowBorder( + color: scheme.border.withValues(alpha: 0.35), + width: 1, + child: Column( + children: [ + _CustomTitleBar( + theme: scheme, + onNewDatabaseConnection: _onNewDatabaseConnectionFromMenu, + ), + Divider(height: 1, color: scheme.border.withValues(alpha: 0.22)), + Expanded( + child: _MainContentSplit( + connectionsPanelKey: _connectionsPanelKey, + workspace: _workspace, + onConnectionSelected: _onConnectionSelected, + onPostgresObjectSelected: _onPostgresObjectSelected, + onMysqlObjectSelected: _onMysqlObjectSelected, + onRedisDatabaseSelected: _onRedisDatabaseSelected, + onMongoDBDatabaseSelected: _onMongoDBDatabaseSelected, + onPostgresOpenSqlWorkspace: _onPostgresOpenSqlWorkspace, + onMysqlOpenSqlWorkspace: _onMysqlOpenSqlWorkspace, + onRequestNewConnection: _openNewConnectionFromHero, ), - ], - ), + ), + ], ), ), ); diff --git a/test/core/theme/querya_light_theme_test.dart b/test/core/theme/querya_light_theme_test.dart new file mode 100644 index 00000000..0a6d2a4b --- /dev/null +++ b/test/core/theme/querya_light_theme_test.dart @@ -0,0 +1,64 @@ +import 'dart:math' as math; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/core/theme/querya_color_scheme.dart'; +import 'package:querya_desktop/core/theme/querya_theme.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; + +void main() { + group('QueryaTheme.lightDefault', () { + test('colorScheme differs from dark preset', () { + final light = QueryaTheme.lightDefault.colorScheme; + final dark = QueryaTheme.darkDefault.colorScheme; + expect(light.background, isNot(dark.background)); + expect(light.foreground, isNot(dark.foreground)); + expect(light.brightness, Brightness.light); + }); + + test('QueryaColorScheme.light matches lightDefault', () { + expect( + QueryaColorScheme.light.background, + QueryaTheme.lightDefault.colorScheme.background, + ); + }); + + test('primary text on canvas meets WCAG AA contrast (4.5:1)', () { + final w = QueryaTheme.lightDefault.workbench; + final ratio = contrastRatio(w.canvas, const Color(0xFF0F172A)); + expect(ratio, greaterThanOrEqualTo(4.5)); + }); + + test('editor foreground on editor background meets WCAG AA', () { + final e = QueryaTheme.lightDefault.editor; + final ratio = contrastRatio(e.background, e.foreground); + expect(ratio, greaterThanOrEqualTo(4.5)); + }); + + test('toShadcnThemeData uses light brightness', () { + final td = QueryaTheme.lightDefault.toShadcnThemeData(); + expect(td.brightness, Brightness.light); + expect(td.colorScheme.primary, QueryaTheme.lightDefault.workbench.accent); + }); + }); +} + +/// Relative luminance contrast per WCAG 2.1. +double contrastRatio(Color a, Color b) { + final l1 = _relativeLuminance(a); + final l2 = _relativeLuminance(b); + final lighter = math.max(l1, l2); + final darker = math.min(l1, l2); + return (lighter + 0.05) / (darker + 0.05); +} + +double _relativeLuminance(Color c) { + double channel(double v) { + final normalized = v <= 0.03928 ? v / 12.92 : math.pow((v + 0.055) / 1.055, 2.4).toDouble(); + return normalized; + } + + final r = channel(c.r); + final g = channel(c.g); + final b = channel(c.b); + return 0.2126 * r + 0.7152 * g + 0.0722 * b; +}