From 1cdbf4fa3decd0d082f08a0e33b50aacf90e206c Mon Sep 17 00:00:00 2001 From: Freedom <48708512+xddcode@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:24:10 +0800 Subject: [PATCH] fix(desktop): unify UI colors onto theme tokens --- .../src/components/common/JsonTree.vue | 26 +- .../components/config/DriverStoreDialog.vue | 4 +- .../connection/ConnectionDialog.vue | 26 +- .../components/diff/SchemaDiffDdlPanel.vue | 4 +- .../components/diff/SchemaDiffDeployStep.vue | 4 +- .../src/components/diff/SchemaDiffDialog.vue | 4 +- .../src/components/editor/AiAssistant.vue | 20 +- .../components/editor/EditorSearchPanel.vue | 84 ++---- .../editor/EditorSettingsDialog.vue | 106 ++----- .../src/components/editor/QueryEditor.vue | 2 +- .../editor/SqlFormatterSettingsPanel.vue | 20 +- apps/desktop/src/components/grid/DataGrid.vue | 258 ++++++------------ .../src/components/layout/EditorToolbar.vue | 6 + .../src/components/mq/MonitoringPanel.vue | 42 +-- .../src/components/mq/SubscriptionsPanel.vue | 4 +- .../src/components/mq/shared/mqPanel.css | 2 +- .../src/components/objects/ObjectBrowser.vue | 2 +- .../settings/McpConnectionScopePicker.vue | 4 +- .../McpConnectionScopePicker.spec.ts | 2 +- .../src/components/sidebar/TreeItem.vue | 22 +- .../structure/TableStructureEditor.vue | 4 +- .../ui/searchable-select/SearchableSelect.vue | 37 +-- .../components/ui/select/SelectTrigger.vue | 35 +-- .../src/components/ui/switch/Switch.vue | 28 +- .../dataGrid/dataGridPaintTheme.spec.ts | 115 +++++++- .../dataGrid/dataGridRuntime.spec.ts | 5 +- .../src/lib/dataGrid/dataGridPaintTheme.ts | 79 +++--- apps/desktop/src/styles/globals.css | 176 ++++++++++-- .../app-tests/dataGridFontSettings.test.ts | 7 +- .../dataGridHeaderBackground.test.ts | 2 +- .../dataGridSelectionContrast.test.ts | 10 + 31 files changed, 558 insertions(+), 582 deletions(-) create mode 100644 packages/app-tests/dataGridSelectionContrast.test.ts diff --git a/apps/desktop/src/components/common/JsonTree.vue b/apps/desktop/src/components/common/JsonTree.vue index 0fefff14f..a17618d97 100644 --- a/apps/desktop/src/components/common/JsonTree.vue +++ b/apps/desktop/src/components/common/JsonTree.vue @@ -209,13 +209,13 @@ defineExpose({ expandAll, collapseAll, resetExpansion, refresh }); diff --git a/apps/desktop/src/components/diff/SchemaDiffDeployStep.vue b/apps/desktop/src/components/diff/SchemaDiffDeployStep.vue index 26d3841a9..50405bb14 100644 --- a/apps/desktop/src/components/diff/SchemaDiffDeployStep.vue +++ b/apps/desktop/src/components/diff/SchemaDiffDeployStep.vue @@ -335,10 +335,10 @@ function getObjectIconColor(kind: DiffObjectKind): string { diff --git a/apps/desktop/src/components/diff/SchemaDiffDialog.vue b/apps/desktop/src/components/diff/SchemaDiffDialog.vue index 0afd7ea98..79d872504 100644 --- a/apps/desktop/src/components/diff/SchemaDiffDialog.vue +++ b/apps/desktop/src/components/diff/SchemaDiffDialog.vue @@ -887,10 +887,10 @@ const targetConnectionInfo = computed(() => { diff --git a/apps/desktop/src/components/editor/AiAssistant.vue b/apps/desktop/src/components/editor/AiAssistant.vue index 17c199852..279c4c890 100644 --- a/apps/desktop/src/components/editor/AiAssistant.vue +++ b/apps/desktop/src/components/editor/AiAssistant.vue @@ -2424,24 +2424,24 @@ async function openExternalUrl(url: string) { font-weight: 600; } .ai-markdown :deep(a) { - color: hsl(var(--primary)); + color: var(--primary); text-decoration: underline; } .ai-markdown :deep(blockquote) { - border-left: 2px solid hsl(var(--muted-foreground) / 0.3); + border-left: 2px solid color-mix(in srgb, var(--muted-foreground) 30%, transparent); padding-left: 0.75em; margin: 0.3em 0; - color: hsl(var(--muted-foreground)); + color: var(--muted-foreground); } .ai-markdown :deep(code) { border-radius: 0.25rem; - background: hsl(var(--muted)); + background: var(--muted); padding: 0.125rem 0.375rem; font-size: 11px; font-family: ui-monospace, monospace; } .ai-markdown :deep(pre) { - background: hsl(var(--muted)); + background: var(--muted); border-radius: 0.375rem; padding: 0.5em 0.75em; margin: 0.3em 0; @@ -2464,7 +2464,7 @@ async function openExternalUrl(url: string) { max-width: 100%; margin: 0.3em 0; border-radius: 0.375rem; - border: 1px solid hsl(var(--border)); + border: 1px solid var(--border); } .ai-markdown :deep(.ai-markdown-table-wrap table) { border: none; @@ -2472,14 +2472,14 @@ async function openExternalUrl(url: string) { } .ai-markdown :deep(th), .ai-markdown :deep(td) { - border: 1px solid hsl(var(--border)); + border: 1px solid var(--border); padding: 0.25em 0.5em; text-align: left; white-space: nowrap; } .ai-markdown :deep(th) { font-weight: 600; - background: hsl(var(--muted)); + background: var(--muted); position: sticky; top: 0; z-index: 1; @@ -2496,11 +2496,11 @@ async function openExternalUrl(url: string) { height: 4px; width: 100%; cursor: ns-resize; - background-color: hsl(var(--border)); + background-color: var(--border); transition: background-color 0.15s ease; } .resize-handle:hover { - background-color: hsl(var(--foreground) / 0.2); + background-color: color-mix(in srgb, var(--foreground) 20%, transparent); } diff --git a/apps/desktop/src/components/editor/EditorSearchPanel.vue b/apps/desktop/src/components/editor/EditorSearchPanel.vue index f855d5d46..928ec2b50 100644 --- a/apps/desktop/src/components/editor/EditorSearchPanel.vue +++ b/apps/desktop/src/components/editor/EditorSearchPanel.vue @@ -491,13 +491,13 @@ defineExpose({ } .editor-search-panel--editor { - background: #f6f7f9; - border-color: #d8dce3; + background: var(--popover); + border-color: var(--border); border-radius: var(--dbx-radius-fixed-6); box-shadow: - 0 8px 22px rgb(15 23 42 / 0.14), - 0 1px 0 rgb(255 255 255 / 0.78) inset; - color: #20242a; + 0 8px 22px color-mix(in srgb, var(--foreground) 14%, transparent), + 0 1px 0 color-mix(in srgb, var(--background) 78%, transparent) inset; + color: var(--foreground); gap: 3px; max-width: min(calc(100vw - 2rem), 500px); padding: 4px 6px; @@ -535,96 +535,46 @@ defineExpose({ } .editor-search-panel--editor :deep(.border-input) { - background: #ffffff; - border-color: #c7ccd5; + background: var(--background); + border-color: var(--input); border-radius: var(--dbx-radius-fixed-5); - box-shadow: 0 1px 0 rgb(15 23 42 / 0.03) inset; + box-shadow: 0 1px 0 color-mix(in srgb, var(--foreground) 3%, transparent) inset; } .editor-search-panel--editor :deep(.focus-within\:border-ring:focus-within) { - border-color: #3b82f6; - box-shadow: 0 0 0 1px #3b82f6; + border-color: var(--ring); + box-shadow: 0 0 0 1px var(--ring); } .editor-search-panel--editor :deep(input) { - color: #20242a; + color: var(--foreground); font-size: 12px; } .editor-search-panel--editor :deep(input::placeholder) { - color: #7a828e; + color: var(--muted-foreground); } .editor-search-panel--editor :deep(.text-muted-foreground) { - color: #6e7681; + color: var(--muted-foreground); } .editor-search-panel--editor :deep(.text-destructive) { - color: #c2410c; + color: var(--destructive); } .editor-search-panel--editor :deep(.hover\:bg-accent:hover), .editor-search-panel--editor :deep(.bg-accent) { - background: #e6e9ef; + background: var(--accent); } .editor-search-panel--editor :deep(.hover\:text-foreground:hover), .editor-search-panel--editor :deep(.text-foreground) { - color: #1f2329; + color: var(--foreground); } .editor-search-panel--editor :deep(button.border-border) { - border-color: #d0d5dd; -} - -:global(.dark) .editor-search-panel--editor { - background: #20252d; - border-color: #2c333d; - box-shadow: - 0 8px 24px rgb(0 0 0 / 0.28), - 0 1px 0 rgb(255 255 255 / 0.04) inset; - color: #d4d7dc; -} - -:global(.dark) .editor-search-panel--editor :deep(.border-input) { - background: #191d25; - border-color: #2f3742; - box-shadow: none; -} - -:global(.dark) .editor-search-panel--editor :deep(.focus-within\:border-ring:focus-within) { - border-color: #4d8dff; - box-shadow: 0 0 0 1px #4d8dff; -} - -:global(.dark) .editor-search-panel--editor :deep(input) { - color: #d7dae0; -} - -:global(.dark) .editor-search-panel--editor :deep(input::placeholder) { - color: #858c97; -} - -:global(.dark) .editor-search-panel--editor :deep(.text-muted-foreground) { - color: #9aa2ad; -} - -:global(.dark) .editor-search-panel--editor :deep(.text-destructive) { - color: #f59e7a; -} - -:global(.dark) .editor-search-panel--editor :deep(.hover\:bg-accent:hover), -:global(.dark) .editor-search-panel--editor :deep(.bg-accent) { - background: #303844; -} - -:global(.dark) .editor-search-panel--editor :deep(.hover\:text-foreground:hover), -:global(.dark) .editor-search-panel--editor :deep(.text-foreground) { - color: #f2f4f8; -} - -:global(.dark) .editor-search-panel--editor :deep(button.border-border) { - border-color: #38414d; + border-color: var(--border); } @media (max-width: 720px) { diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index d262873ff..1e45b3975 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -392,10 +392,10 @@ const systemFontsLoading = ref(false); const systemFontsLoaded = ref(false); const uiScaleOptions = [0.75, 0.9, 0.95, 1, 1.05, 1.1, 1.15, 1.2, 1.25, 1.5, 1.75]; const fontSearchTriggerClass = - "h-8 w-full max-w-none justify-between gap-1.5 rounded-[6px] border border-input bg-transparent py-2 pl-2.5 pr-2 text-sm font-normal shadow-none hover:bg-transparent focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-expanded:bg-transparent dark:bg-input/30 dark:hover:bg-input/50"; -const appearanceFontSearchTriggerClass = `${fontSearchTriggerClass} gap-0 pl-2 pr-1.5`; + "h-8 w-full max-w-none justify-between gap-1.5 rounded-md border border-input bg-transparent py-2 pl-2.5 pr-2 text-sm font-normal shadow-none hover:bg-muted/40 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-expanded:bg-transparent dark:bg-input/30 dark:hover:bg-input/50"; +const appearanceFontSearchTriggerClass = fontSearchTriggerClass; const fontSearchTriggerIconClass = "size-4 text-muted-foreground"; -const appearanceFontSearchTriggerIconClass = "size-2.5 text-muted-foreground"; +const appearanceFontSearchTriggerIconClass = "size-4 text-muted-foreground"; const disconnectTabHandlingModeDescriptionKey = computed(() => { switch (editDisconnectTabHandlingMode.value) { case "close-tabs": @@ -3630,8 +3630,8 @@ onUnmounted(cleanupPreviewEditor); allow-custom :display-name="displayFontFamily" :normalize-custom="normalizeCustomFontFamilyInput" - trigger-variant="outline" - trigger-class="h-9 w-full max-w-none justify-between" + :trigger-class="appearanceFontSearchTriggerClass" + :trigger-icon-class="appearanceFontSearchTriggerIconClass" content-class="w-[var(--reka-popover-trigger-width)] min-w-[260px]" @update:model-value="onTableFontFamilyChange" @update:open="(open: boolean) => open && loadSystemFontOptions()" @@ -3657,16 +3657,7 @@ onUnmounted(cleanupPreviewEditor);
- @@ -3683,7 +3674,7 @@ onUnmounted(cleanupPreviewEditor); variant="outline" size="sm" class="settings-choice-button h-8 px-3" - :class="cornerStyle === option.value ? 'settings-choice-button--selected border-primary/40 bg-primary/10 text-primary ring-1 ring-primary/30' : 'text-foreground'" + :class="cornerStyle === option.value ? 'dbx-choice-selected' : 'text-foreground'" :style="{ borderRadius: option.previewRadius }" @click="setCornerStyle(option.value)" > @@ -3698,7 +3689,7 @@ onUnmounted(cleanupPreviewEditor);
- - - - - - - - + + +
@@ -5320,7 +5311,7 @@ onUnmounted(cleanupPreviewEditor); :tabindex="mcpExecutionMode === 'read_only' ? 0 : -1" variant="outline" class="settings-choice-card h-auto justify-center border p-3" - :class="mcpExecutionMode === 'read_only' ? 'settings-choice-card--selected border-blue-300 ring-2 ring-blue-300/50' : ''" + :class="mcpExecutionMode === 'read_only' ? 'dbx-choice-selected' : ''" @click="onMcpExecutionModeChange('read_only')" @keydown="onMcpExecutionModeKeydown($event, 'read_only')" > @@ -5335,7 +5326,7 @@ onUnmounted(cleanupPreviewEditor); :tabindex="mcpExecutionMode === 'safe_write' ? 0 : -1" variant="outline" class="settings-choice-card h-auto justify-center border p-3" - :class="mcpExecutionMode === 'safe_write' ? 'settings-choice-card--selected border-blue-300 ring-2 ring-blue-300/50' : ''" + :class="mcpExecutionMode === 'safe_write' ? 'dbx-choice-selected' : ''" @click="onMcpExecutionModeChange('safe_write')" @keydown="onMcpExecutionModeKeydown($event, 'safe_write')" > @@ -5351,7 +5342,7 @@ onUnmounted(cleanupPreviewEditor); :tabindex="mcpExecutionMode === 'high_risk_write' ? 0 : -1" variant="outline" class="settings-choice-card h-auto justify-center border p-3" - :class="mcpExecutionMode === 'high_risk_write' ? 'settings-choice-card--selected border-blue-300 ring-2 ring-blue-300/50' : ''" + :class="mcpExecutionMode === 'high_risk_write' ? 'dbx-choice-selected' : ''" @click="onMcpExecutionModeChange('high_risk_write')" @keydown="onMcpExecutionModeKeydown($event, 'high_risk_write')" > @@ -5856,34 +5847,6 @@ onUnmounted(cleanupPreviewEditor); } } -.settings-category-button--active { - background-color: rgb(23, 23, 23) !important; - color: rgb(255, 255, 255) !important; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12); -} - -.settings-choice-button { - color: rgb(23, 23, 23); -} - -.settings-choice-button--selected { - border-color: rgb(96, 165, 250) !important; - background-color: rgba(59, 130, 246, 0.1) !important; - color: rgb(29, 78, 216) !important; - box-shadow: 0 0 0 1px rgba(96, 165, 250, 0.45) !important; -} - -.settings-choice-button--selected svg { - color: currentColor !important; -} - -.settings-choice-card--selected { - border-color: rgb(96, 165, 250) !important; - background-color: rgba(59, 130, 246, 0.04) !important; - color: rgb(23, 23, 23) !important; - box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.45) !important; -} - .settings-appearance-top-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); @@ -5928,27 +5891,4 @@ onUnmounted(cleanupPreviewEditor); grid-template-columns: 1fr; } } - -.dark .settings-category-button--active { - background-color: rgb(245, 245, 245) !important; - color: rgb(23, 23, 23) !important; -} - -.dark .settings-choice-button { - color: rgb(245, 245, 245); -} - -.dark .settings-choice-button--selected { - border-color: rgb(147, 197, 253) !important; - background-color: rgba(96, 165, 250, 0.18) !important; - color: rgb(191, 219, 254) !important; - box-shadow: 0 0 0 1px rgba(147, 197, 253, 0.5) !important; -} - -.dark .settings-choice-card--selected { - border-color: rgb(147, 197, 253) !important; - background-color: rgba(96, 165, 250, 0.12) !important; - color: rgb(245, 245, 245) !important; - box-shadow: 0 0 0 2px rgba(147, 197, 253, 0.45) !important; -} diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index 98f8a4407..840df2ee9 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -3307,7 +3307,7 @@ onMounted(async () => { textUnderlineOffset: "3px", }, ".cm-sql-semantic-warning": { - textDecoration: "underline wavy hsl(var(--warning, 38 92% 50%))", + textDecoration: "underline wavy var(--warning)", textUnderlineOffset: "3px", }, }); diff --git a/apps/desktop/src/components/editor/SqlFormatterSettingsPanel.vue b/apps/desktop/src/components/editor/SqlFormatterSettingsPanel.vue index a6363bfc3..e5c955670 100644 --- a/apps/desktop/src/components/editor/SqlFormatterSettingsPanel.vue +++ b/apps/desktop/src/components/editor/SqlFormatterSettingsPanel.vue @@ -394,24 +394,24 @@ async function initJsonEditor() { minHeight: "260px", maxHeight: "360px", fontSize: "12px", - border: "1px solid hsl(var(--border))", + border: "1px solid var(--border)", borderRadius: "var(--radius-md)", - backgroundColor: "hsl(var(--background))", - color: "hsl(var(--foreground))", + backgroundColor: "var(--background)", + color: "var(--foreground)", }, ".cm-scroller": { fontFamily: "var(--font-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace", }, ".cm-gutters": { - backgroundColor: "hsl(var(--muted) / 0.35)", - color: "hsl(var(--muted-foreground))", - borderRight: "1px solid hsl(var(--border))", + backgroundColor: "color-mix(in srgb, var(--muted) 35%, transparent)", + color: "var(--muted-foreground)", + borderRight: "1px solid var(--border)", }, ".cm-activeLine, .cm-activeLineGutter": { - backgroundColor: "hsl(var(--muted) / 0.45)", + backgroundColor: "color-mix(in srgb, var(--muted) 45%, transparent)", }, ".cm-focused": { - outline: "2px solid hsl(var(--ring) / 0.35)", + outline: "2px solid color-mix(in srgb, var(--ring) 35%, transparent)", outlineOffset: "1px", }, }), @@ -564,10 +564,10 @@ onBeforeUnmount(() => {
- -
diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index e12cf79a3..f212f52c4 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -71,7 +71,6 @@ import { tableInfoTabForDrawerToggle } from "@/lib/table/tableInfoTabPreference" import * as api from "@/lib/backend/api"; import { formatElapsedSeconds } from "@/lib/common/elapsedTime"; import { dataGridCellDisplayText, dataGridCellEditorText } from "@/lib/dataGrid/dataGridCellCoercion"; -import { dataGridActiveRowBackground } from "@/lib/dataGrid/dataGridPaintTheme"; import { createColumnDrafts } from "@/lib/table/tableStructureEditorState"; import type { BuildSingleColumnAlterSqlOptions } from "@/lib/table/tableStructureEditorSql"; import { buildTableSelectSql, quoteTableDataIdentifier } from "@/lib/table/tableSelectSql"; @@ -139,7 +138,7 @@ import { canGoNextDataGridPage, hasCompleteLocalDataGridResult, resolveDataGridP import { dataGridCountQueryOptions } from "@/lib/dataGrid/dataGridQueryOptions"; import { dataGridBottomScrollTop, dataGridScrollPosition, isDataGridAtScrollBottom, isDataGridNearScrollBottom, shouldCheckInfiniteScrollAfterScroll, type DataGridScrollPosition } from "@/lib/dataGrid/dataGridInfiniteScroll"; import { CANVAS_DATA_GRID_ROW_HEIGHT, canvasDataGridActionReservedWidth, dataGridSearchMatchKey, drawCanvasDataGrid } from "@/lib/dataGrid/canvasDataGridRenderer"; -import { DATA_GRID_DARK_STRIPED_ROW_BG, DATA_GRID_LIGHT_STRIPED_ROW_BG } from "@/lib/dataGrid/dataGridPaintTheme"; +import { DATA_GRID_DARK_ROW_NUMBER_BG, DATA_GRID_DARK_STRIPED_ROW_BG, DATA_GRID_LIGHT_STRIPED_ROW_BG, dataGridActiveRowBackground } from "@/lib/dataGrid/dataGridPaintTheme"; import { createRowLowerTextCache } from "@/lib/dataGrid/dataGridRowLowerText"; import { dataGridPreviewLabelKey, dataGridSaveActionMode, dataGridSaveToolbarState } from "@/lib/dataGrid/dataGridSaveUi"; import type { QueryEditabilityReason } from "@/lib/sql/sqlAnalysis"; @@ -4374,16 +4373,16 @@ function isNull(value: unknown): boolean { function rowNumberStatusClass(item: RowItem): string { if (item.status === "draft") { - return "border-muted-foreground/20 bg-muted/20 font-semibold text-muted-foreground"; + return "data-grid-row-number--status-draft font-semibold"; } if (item.status === "new") { - return "border-emerald-500/40 bg-emerald-500/15 font-semibold text-emerald-700 dark:text-emerald-300"; + return "data-grid-row-number--status-new font-semibold"; } if (item.status === "edited") { - return "border-amber-500/40 bg-amber-500/15 font-semibold text-amber-700 dark:text-amber-300"; + return "data-grid-row-number--status-edited font-semibold"; } if (item.status === "deleted") { - return "border-destructive/40 bg-destructive/15 font-semibold text-destructive line-through"; + return "data-grid-row-number--status-deleted font-semibold line-through"; } return "text-muted-foreground"; } @@ -4394,53 +4393,27 @@ function rowCellsUseSelectionVisual(rowId: number): boolean { function dataGridRowStyle(item: RowItem): CSSProperties { const dark = isDark.value || (typeof document !== "undefined" && document.documentElement.classList.contains("dark")); - const activeRowBg = dataGridActiveRowBackground(dark); - const rowBg = item.isDeleted - ? dark - ? "rgb(55, 31, 32)" - : "rgb(255, 244, 244)" + const deletedBg = "var(--color-error-bg, color-mix(in srgb, var(--destructive) 12%, transparent))"; + const newBg = "var(--success-bg, color-mix(in srgb, var(--success) 12%, transparent))"; + const editedBg = "var(--warning-bg, color-mix(in srgb, var(--warning) 14%, transparent))"; + const activeBg = `var(--data-grid-cell-active-bg, ${dataGridActiveRowBackground(dark)})`; + const resolvedRowBg = item.isDeleted + ? deletedBg : isRowActive(item.displayIndex) - ? activeRowBg - : item.isNew - ? dark - ? "rgb(51, 51, 55)" - : "rgb(243, 243, 243)" - : item.isDraft - ? dark - ? "rgb(51, 51, 55)" - : "rgb(243, 243, 243)" - : item.displayIndex % 2 === 1 - ? `var(--data-grid-row-muted-bg, ${dark ? DATA_GRID_DARK_STRIPED_ROW_BG : DATA_GRID_LIGHT_STRIPED_ROW_BG})` - : dark - ? "rgb(19, 20, 22)" - : "rgb(255, 255, 255)"; - const rowNumberBg = - item.status === "new" - ? dark - ? "rgb(33, 45, 40)" - : "rgb(219, 244, 233)" - : item.status === "edited" - ? dark - ? "rgb(48, 41, 28)" - : "rgb(253, 241, 219)" - : item.status === "deleted" - ? dark - ? "rgb(55, 31, 32)" - : "rgb(255, 244, 244)" - : isRowActive(item.displayIndex) && !item.isDeleted - ? activeRowBg - : dark - ? "rgb(35, 37, 42)" - : "rgb(255, 255, 255)"; + ? activeBg + : item.isNew || item.isDraft + ? "color-mix(in srgb, var(--muted) 80%, var(--background))" + : item.displayIndex % 2 === 1 + ? `var(--data-grid-row-muted-bg, ${dark ? DATA_GRID_DARK_STRIPED_ROW_BG : DATA_GRID_LIGHT_STRIPED_ROW_BG})` + : "var(--background)"; + const rowNumberBg = item.status === "new" ? newBg : item.status === "edited" ? editedBg : item.status === "deleted" ? deletedBg : isRowActive(item.displayIndex) && !item.isDeleted ? activeBg : dark ? DATA_GRID_DARK_ROW_NUMBER_BG : "var(--background)"; return { - "--data-grid-cell-bg": rowBg, + "--data-grid-cell-bg": resolvedRowBg, "--data-grid-row-number-bg": rowNumberBg, - "--data-grid-cell-selected-bg": dark ? "rgb(20, 40, 60)" : "rgb(239, 246, 255)", - "--data-grid-cell-selected-single-bg": dark ? "rgb(30, 64, 96)" : "rgb(191, 219, 254)", - "--data-grid-cell-selected-dirty-bg": dark ? "rgb(76, 66, 38)" : "rgb(235, 224, 184)", - "--data-grid-cell-selected-border": dark ? "rgb(96, 165, 250)" : "rgb(59, 130, 246)", - "--data-grid-row-number-active-bg": activeRowBg, - "--data-grid-row-number-selected-bg": dark ? "rgb(30, 64, 96)" : "rgb(191, 219, 254)", + // Keep selection fill/border from [data-grid-root] tokens (primary/ring mix). + // Do not flatten them to --accent/--border — that erases outline contrast. + "--data-grid-cell-selected-dirty-bg": editedBg, + "--data-grid-row-number-active-bg": activeBg, } as CSSProperties; } @@ -7989,8 +7962,8 @@ const gridContextMenuItems = computed(() => {
(() => { 'row-cell-selected': transposeRecordUsesSelectionVisual(cell.recordIndex) && !transposeCellIsSelected(cell.recordIndex, cell.valueIndex) && !displayItems[cell.recordIndex]?.isDirtyCol[cell.valueIndex], 'row-cell-selected-dirty': transposeRecordUsesSelectionVisual(cell.recordIndex) && !transposeCellIsSelected(cell.recordIndex, cell.valueIndex) && displayItems[cell.recordIndex]?.isDirtyCol[cell.valueIndex], 'bg-primary/15': transposeRecordUsesActiveHighlight(cell.recordIndex) && !transposeRecordUsesSelectionVisual(cell.recordIndex) && !displayItems[cell.recordIndex]?.isDirtyCol[cell.valueIndex] && !transposeCellIsSelected(cell.recordIndex, cell.valueIndex), - 'bg-yellow-500/10 cell-dirty': displayItems[cell.recordIndex]?.isDirtyCol[cell.valueIndex], - 'bg-yellow-200/60 dark:bg-yellow-500/20': cellIsSearchMatch(cell.recordIndex, cell.valueIndex), - 'ring-2 ring-inset ring-yellow-500 bg-yellow-300/60 dark:bg-yellow-500/40': cellIsCurrentMatch(cell.recordIndex, cell.valueIndex), + 'cell-dirty': displayItems[cell.recordIndex]?.isDirtyCol[cell.valueIndex], + 'cell-search-match': cellIsSearchMatch(cell.recordIndex, cell.valueIndex), + 'cell-current-search-match': cellIsCurrentMatch(cell.recordIndex, cell.valueIndex), 'cursor-text': !isScrolling && canEditCellItem(displayItems[cell.recordIndex], cell.valueIndex), - 'hover:bg-gray-200 dark:hover:bg-gray-800': + 'hover:bg-accent': !isScrolling && canEditCellItem(displayItems[cell.recordIndex], cell.valueIndex) && !transposeRecordUsesSelectionVisual(cell.recordIndex) && !transposeRecordUsesActiveHighlight(cell.recordIndex) && !transposeCellIsSelected(cell.recordIndex, cell.valueIndex), }" :style="{ width: `${getTransposeRecordWidth(cell.recordIndex)}px` }" @@ -8661,17 +8634,15 @@ const gridContextMenuItems = computed(() => { 'data-grid-cell--frozen-separator': frozenColumnCount > 0 && col.visibleColIdx === frozenColumnCount - 1, 'text-right': columnAligns[col.visibleColIdx] === 'right', 'text-muted-foreground italic': isNull(item.data[col.actualColIdx]), - 'bg-yellow-500/10 cell-dirty': item.isDirtyCol[col.actualColIdx], + 'cell-dirty': item.isDirtyCol[col.actualColIdx], 'cell-selected': cellIsSelected(item.displayIndex, col.visibleColIdx) && !item.isDirtyCol[col.actualColIdx], 'cell-selected-dirty': cellIsSelected(item.displayIndex, col.visibleColIdx) && item.isDirtyCol[col.actualColIdx], 'row-cell-selected': rowCellsUseSelectionVisual(item.id) && !cellIsSelected(item.displayIndex, col.visibleColIdx) && !item.isDirtyCol[col.actualColIdx], 'row-cell-selected-dirty': rowCellsUseSelectionVisual(item.id) && !cellIsSelected(item.displayIndex, col.visibleColIdx) && item.isDirtyCol[col.actualColIdx], 'cell-search-match': cellIsSearchMatch(item.displayIndex, col.actualColIdx), 'cell-current-search-match': cellIsCurrentMatch(item.displayIndex, col.actualColIdx), - 'bg-yellow-200/60 dark:bg-yellow-500/20': cellIsSearchMatch(item.displayIndex, col.actualColIdx), - 'ring-2 ring-inset ring-yellow-500 bg-yellow-300/60 dark:bg-yellow-500/40': cellIsCurrentMatch(item.displayIndex, col.actualColIdx), 'tabular-nums': typeof item.data[col.actualColIdx] === 'number', - 'cursor-text hover:bg-gray-200 dark:hover:bg-gray-800': !isScrolling && canEditCellItem(item, col.actualColIdx), + 'cursor-text hover:bg-accent': !isScrolling && canEditCellItem(item, col.actualColIdx), 'line-through': item.isDeleted, 'overflow-visible z-20 border-r-transparent': editingCell?.rowId === item.id && editingCell?.col === col.actualColIdx, 'overflow-hidden': !(editingCell?.rowId === item.id && editingCell?.col === col.actualColIdx), @@ -8928,8 +8899,8 @@ const gridContextMenuItems = computed(() => {
{{ index.name }}
- PK - UNIQUE + PK + UNIQUE {{ index.index_type }}
@@ -9323,74 +9294,32 @@ const gridContextMenuItems = computed(() => { @reference "../../styles/globals.css"; [data-grid-root] { - --data-grid-row-muted-bg: rgb(240, 240, 240); - --data-grid-row-new-bg: rgb(243, 243, 243); - --data-grid-row-deleted-bg: rgb(255, 244, 244); - --data-grid-cell-active-bg: rgb(244, 248, 255); - --data-grid-cell-dirty-bg: rgb(255, 248, 230); - --data-grid-cell-selected-bg: rgb(239, 246, 255); - --data-grid-cell-selected-single-bg: rgb(191, 219, 254); - --data-grid-cell-selected-dirty-bg: rgb(235, 224, 184); - --data-grid-cell-selected-border: rgb(59, 130, 246); - --data-grid-cell-hover-bg: rgb(245, 245, 245); - --data-grid-cell-search-bg: rgb(253, 245, 184); - --data-grid-cell-current-search-bg: rgba(253, 224, 71, 0.52); - --data-grid-cell-current-search-border: rgba(234, 179, 8, 0.82); - --data-grid-row-number-default-bg: rgb(255, 255, 255); - --data-grid-row-number-new-bg: rgb(219, 244, 233); - --data-grid-row-number-edited-bg: rgb(253, 241, 219); - --data-grid-row-number-deleted-bg: rgb(255, 244, 244); - --data-grid-row-number-active-bg: rgb(244, 248, 255); - --data-grid-row-number-selected-bg: rgb(191, 219, 254); - --data-grid-scrollbar-thumb: color-mix(in oklch, var(--foreground) 30%, transparent); - --data-grid-scrollbar-thumb-hover: color-mix(in oklch, var(--foreground) 48%, transparent); + /* Status / selection chrome — derived from theme tokens so palettes stay in sync. */ + --data-grid-row-muted-bg: color-mix(in srgb, var(--muted) 92%, var(--foreground)); + --data-grid-row-new-bg: color-mix(in srgb, var(--muted) 80%, var(--background)); + --data-grid-row-deleted-bg: var(--color-error-bg, color-mix(in srgb, var(--destructive) 12%, transparent)); + --data-grid-cell-active-bg: color-mix(in srgb, var(--info) 10%, var(--background)); + --data-grid-cell-dirty-bg: var(--warning-bg, color-mix(in srgb, var(--warning) 14%, transparent)); + --data-grid-cell-selected-bg: color-mix(in srgb, var(--primary) 12%, var(--accent)); + --data-grid-cell-selected-single-bg: color-mix(in srgb, var(--primary) 22%, var(--accent)); + --data-grid-cell-selected-dirty-bg: color-mix(in srgb, var(--warning) 22%, var(--accent)); + /* Prefer ring/primary so the outline stays visible on the selected fill. */ + --data-grid-cell-selected-border: color-mix(in srgb, var(--ring) 55%, var(--primary)); + --data-grid-cell-selected-single-border: var(--primary); + --data-grid-cell-hover-bg: var(--accent); + --data-grid-cell-search-bg: var(--warning-bg, color-mix(in srgb, var(--warning) 18%, transparent)); + --data-grid-cell-current-search-bg: color-mix(in srgb, var(--warning) 35%, transparent); + --data-grid-cell-current-search-border: var(--warning); + --data-grid-row-number-default-bg: var(--background); + --data-grid-row-number-new-bg: var(--success-bg, color-mix(in srgb, var(--success) 12%, transparent)); + --data-grid-row-number-edited-bg: var(--warning-bg, color-mix(in srgb, var(--warning) 14%, transparent)); + --data-grid-row-number-deleted-bg: var(--color-error-bg, color-mix(in srgb, var(--destructive) 12%, transparent)); + --data-grid-row-number-active-bg: var(--data-grid-cell-active-bg); + --data-grid-row-number-selected-bg: var(--data-grid-cell-selected-single-bg); + --data-grid-scrollbar-thumb: color-mix(in srgb, var(--foreground) 30%, transparent); + --data-grid-scrollbar-thumb-hover: color-mix(in srgb, var(--foreground) 48%, transparent); --data-grid-scrollbar-track: transparent; - background-color: rgb(255, 255, 255); -} - -[data-grid-root].data-grid--dark, -:global(.dark) [data-grid-root] { - --data-grid-row-muted-bg: rgb(40, 40, 43); - --data-grid-row-new-bg: rgb(51, 51, 55); - --data-grid-row-deleted-bg: rgb(55, 31, 32); - --data-grid-cell-active-bg: rgb(25, 34, 46); - --data-grid-cell-dirty-bg: rgb(94, 75, 26); - --data-grid-cell-selected-bg: rgb(20, 40, 60); - --data-grid-cell-selected-single-bg: rgb(30, 64, 96); - --data-grid-cell-selected-dirty-bg: rgb(76, 66, 38); - --data-grid-cell-selected-border: rgb(96, 165, 250); - --data-grid-cell-hover-bg: rgb(46, 47, 51); - --data-grid-cell-search-bg: rgb(72, 57, 8); - --data-grid-cell-current-search-bg: rgb(116, 87, 0); - --data-grid-cell-current-search-border: rgb(239, 177, 0); - --data-grid-row-number-default-bg: rgb(35, 37, 42); - --data-grid-row-number-new-bg: rgb(33, 45, 40); - --data-grid-row-number-edited-bg: rgb(48, 41, 28); - --data-grid-row-number-deleted-bg: rgb(55, 31, 32); - --data-grid-row-number-active-bg: rgb(25, 34, 46); - --data-grid-row-number-selected-bg: rgb(30, 64, 96); - --data-grid-scrollbar-thumb: rgb(82, 82, 91); - --data-grid-scrollbar-thumb-hover: rgb(113, 113, 122); - --data-grid-scrollbar-track: rgb(24, 24, 27); - background-color: rgb(19, 20, 22); -} - -@supports (background: color-mix(in oklab, white 50%, transparent)) { - [data-grid-root] { - --data-grid-row-muted-bg: color-mix(in oklab, var(--muted) 99%, var(--foreground)); - --data-grid-row-new-bg: color-mix(in oklab, var(--primary) 5%, transparent); - --data-grid-row-deleted-bg: color-mix(in oklab, var(--destructive) 5%, transparent); - --data-grid-cell-dirty-bg: color-mix(in oklab, rgb(240 177 0) 10%, transparent); - --data-grid-cell-selected-bg: color-mix(in oklab, rgb(59 130 246) 12%, var(--background)); - --data-grid-cell-selected-single-bg: color-mix(in oklab, rgb(59 130 246) 30%, var(--background)); - --data-grid-cell-selected-dirty-bg: color-mix(in oklab, rgb(234 181 50) 30%, color-mix(in oklab, rgb(59 130 246) 18%, var(--background))); - --data-grid-cell-selected-border: color-mix(in oklab, rgb(59 130 246) 75%, transparent); - --data-grid-cell-hover-bg: color-mix(in oklab, var(--accent) 50%, transparent); - --data-grid-row-number-new-bg: color-mix(in oklab, rgb(16 185 129) 15%, var(--background)); - --data-grid-row-number-edited-bg: color-mix(in oklab, rgb(245 158 11) 15%, var(--background)); - --data-grid-row-number-deleted-bg: color-mix(in oklab, var(--destructive) 15%, var(--background)); - --data-grid-row-number-selected-bg: color-mix(in oklab, rgb(59 130 246) 30%, var(--background)); - } + background-color: var(--background); } .data-grid-header-shell { @@ -9400,36 +9329,28 @@ const gridContextMenuItems = computed(() => { } .data-grid-header-cell { - background-color: rgb(239, 239, 239); + background-color: color-mix(in srgb, var(--muted) 88%, var(--background)); } [data-grid-root].data-grid--dark .data-grid-header-cell, :global(.dark) [data-grid-root] .data-grid-header-cell { - background-color: rgb(32, 32, 34) !important; + background-color: color-mix(in srgb, var(--muted) 70%, var(--background)); } [data-grid-root].data-grid--dark .data-grid-header-row, :global(.dark) [data-grid-root] .data-grid-header-row { - color: rgb(215, 215, 219); + color: var(--foreground); } [data-grid-root].data-grid--dark .data-grid-header-cell:hover, :global(.dark) [data-grid-root] .data-grid-header-cell:hover { - background-color: rgb(46, 47, 51) !important; + background-color: var(--accent); } .data-grid-header-cell--selected { background-color: var(--data-grid-cell-selected-single-bg) !important; } -:global(.dark) [data-grid-root] { - --data-grid-cell-selected-bg: rgb(20, 40, 60); - --data-grid-cell-selected-single-bg: rgb(30, 64, 96); - --data-grid-cell-selected-dirty-bg: rgb(76, 66, 38); - --data-grid-cell-selected-border: rgb(96, 165, 250); - --data-grid-row-number-selected-bg: rgb(30, 64, 96); -} - [data-grid-root].data-grid--dark .data-grid-header-cell--selected, [data-grid-root].data-grid--dark .transpose-record-header-selected, [data-grid-root].data-grid--dark .transpose-record-header-active, @@ -9437,7 +9358,7 @@ const gridContextMenuItems = computed(() => { :global(.dark) [data-grid-root] .transpose-record-header-selected, :global(.dark) [data-grid-root] .transpose-record-header-active { background-color: var(--data-grid-cell-selected-single-bg) !important; - color: rgb(244, 244, 245) !important; + color: var(--foreground) !important; } .data-grid-row { @@ -9450,12 +9371,12 @@ const gridContextMenuItems = computed(() => { /* 冻结列:不透明背景遮挡滚动的非冻结列;状态 class 的 !important 会覆盖此项 */ .data-grid-cell--frozen { - background-color: var(--data-grid-cell-bg, rgb(255, 255, 255)) !important; + background-color: var(--data-grid-cell-bg, var(--background)) !important; } /* 冻结列分隔线:与 Canvas 模式和列头一致(2px 深色右边框) */ .data-grid-cell--frozen-separator { - border-right: 2px solid rgb(100, 116, 139) !important; + border-right: 2px solid color-mix(in srgb, var(--border) 80%, var(--foreground)) !important; } .data-grid-row-number { @@ -9908,16 +9829,28 @@ const gridContextMenuItems = computed(() => { background-color: var(--data-grid-cell-selected-dirty-bg) !important; } -.data-grid-row-number.bg-emerald-500\/15 { +.data-grid-row-number--status-draft { + border: 1px solid color-mix(in srgb, var(--muted-foreground) 20%, transparent); + background-color: color-mix(in srgb, var(--muted) 20%, transparent); + color: var(--muted-foreground); +} + +.data-grid-row-number--status-new { + border: 1px solid color-mix(in srgb, var(--success) 40%, transparent); background-color: var(--data-grid-row-number-new-bg); + color: var(--success); } -.data-grid-row-number.bg-amber-500\/15 { +.data-grid-row-number--status-edited { + border: 1px solid color-mix(in srgb, var(--warning) 40%, transparent); background-color: var(--data-grid-row-number-edited-bg); + color: var(--warning); } -.data-grid-row-number.bg-destructive\/15 { +.data-grid-row-number--status-deleted { + border: 1px solid color-mix(in srgb, var(--destructive) 40%, transparent); background-color: var(--data-grid-row-number-deleted-bg); + color: var(--destructive); } .active-row > .data-grid-row-number { @@ -9931,35 +9864,10 @@ const gridContextMenuItems = computed(() => { } .cell-selected { - color: hsl(var(--foreground)); + color: var(--foreground); background-color: var(--data-grid-cell-selected-single-bg) !important; -} - -.cell-selected { - @apply outline outline-primary -outline-offset-1; -} - -:global(.dark) [data-grid-root] .cell-selected, -:global(.dark) [data-grid-root] .row-cell-selected { - color: rgb(244, 244, 245) !important; -} - -:global(.dark) [data-grid-root] .cell-selected { - background-color: var(--data-grid-cell-selected-single-bg) !important; -} - -:global(.dark) [data-grid-root] .row-cell-selected { - background-color: var(--data-grid-cell-selected-bg) !important; -} - -:global(.dark) [data-grid-root] .cell-selected-dirty, -:global(.dark) [data-grid-root] .row-cell-selected-dirty { - background-color: var(--data-grid-cell-selected-dirty-bg) !important; -} - -:global(.dark) [data-grid-root] .cell-selected, -:global(.dark) [data-grid-root] .cell-selected-dirty { - outline-color: var(--data-grid-cell-selected-border) !important; + outline: 1px solid var(--data-grid-cell-selected-border); + outline-offset: -1px; } .ddl-code :deep(.ddl-kw) { diff --git a/apps/desktop/src/components/layout/EditorToolbar.vue b/apps/desktop/src/components/layout/EditorToolbar.vue index e0979369b..ead153066 100644 --- a/apps/desktop/src/components/layout/EditorToolbar.vue +++ b/apps/desktop/src/components/layout/EditorToolbar.vue @@ -335,7 +335,9 @@ function databaseOptionIsProduction(database: string): boolean { :search-placeholder="t('editor.searchConnection')" :empty-text="t('grid.noSearchResults')" :loading-text="t('common.loading')" + trigger-variant="ghost" trigger-class="font-medium text-foreground" + trigger-icon-class="h-3 w-3" :display-name="connectionDisplayName" list-class="w-96 max-w-[calc(100vw-2rem)]" item-class="min-h-9 h-auto py-1" @@ -376,7 +378,9 @@ function databaseOptionIsProduction(database: string): boolean { :loading-text="t('common.loading')" :loading="loadingDatabaseOptions[activeConnection?.id || '']" :display-name="databaseDisplayName" + trigger-variant="ghost" trigger-class="gap-1.5" + trigger-icon-class="h-3 w-3" @update:model-value="(database) => emit('changeDatabase', database)" @update:open=" (open: boolean) => { @@ -419,7 +423,9 @@ function databaseOptionIsProduction(database: string): boolean { :loading-text="t('common.loading')" :loading="!!activeConnection && isLoadingSchemas(activeConnection.id, schemaDatabaseKey)" :clear-selected-option="supportsClearableQuerySchema(activeConnection?.db_type)" + trigger-variant="ghost" trigger-class="gap-1.5" + trigger-icon-class="h-3 w-3" @update:model-value="(schema) => emit('changeSchema', schema || undefined)" @update:open=" (open: boolean) => { diff --git a/apps/desktop/src/components/mq/MonitoringPanel.vue b/apps/desktop/src/components/mq/MonitoringPanel.vue index c94b9579e..8ee995292 100644 --- a/apps/desktop/src/components/mq/MonitoringPanel.vue +++ b/apps/desktop/src/components/mq/MonitoringPanel.vue @@ -984,15 +984,15 @@ onUnmounted(() => { --monitor-muted: var(--color-text-secondary, var(--color-muted-foreground, #64748b)); --monitor-faint: var(--color-text-tertiary, color-mix(in srgb, var(--monitor-muted) 72%, transparent)); --monitor-hover: color-mix(in srgb, var(--color-hover, var(--color-muted, #f4f4f5)) 72%, var(--monitor-accent) 7%); - --monitor-accent: #0f766e; - --monitor-accent-soft: rgb(15 118 110 / 0.1); - --monitor-accent-border: rgb(15 118 110 / 0.22); - --monitor-success: #15803d; - --monitor-success-soft: rgb(21 128 61 / 0.1); - --monitor-warning: #b45309; - --monitor-warning-soft: rgb(180 83 9 / 0.12); - --monitor-danger: #b91c1c; - --monitor-danger-soft: rgb(185 28 28 / 0.1); + --monitor-accent: var(--info); + --monitor-accent-soft: var(--info-bg); + --monitor-accent-border: color-mix(in srgb, var(--info) 22%, transparent); + --monitor-success: var(--success); + --monitor-success-soft: var(--success-bg); + --monitor-warning: var(--warning); + --monitor-warning-soft: var(--warning-bg); + --monitor-danger: var(--destructive); + --monitor-danger-soft: color-mix(in srgb, var(--destructive) 10%, transparent); --monitor-shadow: 0 18px 38px -28px rgb(15 23 42 / 0.42); height: 100%; display: flex; @@ -1003,20 +1003,20 @@ onUnmounted(() => { } :global(.dark) .monitoring-panel { - --monitor-panel-bg: color-mix(in srgb, var(--color-background, #131416) 90%, #0f766e 4%); + --monitor-panel-bg: color-mix(in srgb, var(--color-background, #131416) 90%, var(--info) 4%); --monitor-surface: color-mix(in srgb, var(--color-card, #1b1b1e) 94%, #ffffff 2%); - --monitor-surface-raised: color-mix(in srgb, var(--monitor-surface) 92%, #0f766e 8%); + --monitor-surface-raised: color-mix(in srgb, var(--monitor-surface) 92%, var(--info) 8%); --monitor-border: color-mix(in srgb, var(--color-border, rgb(110 110 114 / 0.28)) 82%, transparent); - --monitor-border-strong: color-mix(in srgb, var(--monitor-border) 76%, #2dd4bf 24%); - --monitor-accent: #2dd4bf; - --monitor-accent-soft: rgb(45 212 191 / 0.12); - --monitor-accent-border: rgb(45 212 191 / 0.24); - --monitor-success: #4ade80; - --monitor-success-soft: rgb(74 222 128 / 0.12); - --monitor-warning: #f59e0b; - --monitor-warning-soft: rgb(245 158 11 / 0.14); - --monitor-danger: #f87171; - --monitor-danger-soft: rgb(248 113 113 / 0.12); + --monitor-border-strong: color-mix(in srgb, var(--monitor-border) 76%, var(--info) 24%); + --monitor-accent: var(--info); + --monitor-accent-soft: var(--info-bg); + --monitor-accent-border: color-mix(in srgb, var(--info) 24%, transparent); + --monitor-success: var(--success); + --monitor-success-soft: var(--success-bg); + --monitor-warning: var(--warning); + --monitor-warning-soft: var(--warning-bg); + --monitor-danger: var(--destructive); + --monitor-danger-soft: color-mix(in srgb, var(--destructive) 12%, transparent); --monitor-shadow: 0 18px 42px -28px rgb(0 0 0 / 0.72); } diff --git a/apps/desktop/src/components/mq/SubscriptionsPanel.vue b/apps/desktop/src/components/mq/SubscriptionsPanel.vue index b22fc4541..5716b6405 100644 --- a/apps/desktop/src/components/mq/SubscriptionsPanel.vue +++ b/apps/desktop/src/components/mq/SubscriptionsPanel.vue @@ -761,8 +761,8 @@ watch( } .badge-info { - background: color-mix(in srgb, #3b82f6 18%, transparent); - color: #2563eb; + background: var(--color-info-alpha); + color: var(--color-info); } .badge-muted { diff --git a/apps/desktop/src/components/mq/shared/mqPanel.css b/apps/desktop/src/components/mq/shared/mqPanel.css index ec2b4a671..266080384 100644 --- a/apps/desktop/src/components/mq/shared/mqPanel.css +++ b/apps/desktop/src/components/mq/shared/mqPanel.css @@ -67,7 +67,7 @@ .mq-btn-primary { background: var(--color-primary); border-color: var(--color-primary); - color: #fff; + color: var(--color-primary-foreground); } .mq-btn-secondary:hover:not(:disabled), diff --git a/apps/desktop/src/components/objects/ObjectBrowser.vue b/apps/desktop/src/components/objects/ObjectBrowser.vue index e00480c0c..af40add37 100644 --- a/apps/desktop/src/components/objects/ObjectBrowser.vue +++ b/apps/desktop/src/components/objects/ObjectBrowser.vue @@ -2700,7 +2700,7 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] { 'bg-primary/10': sidePanelRow?.id === item.id && !selectedTableIds.has(item.id), 'bg-primary/5': selectedTableIds.has(item.id), }" - :style="{ gridTemplateColumns, boxShadow: sidePanelRow?.id === item.id && !selectedTableIds.has(item.id) ? 'inset 3px 0 0 hsl(var(--primary))' : undefined }" + :style="{ gridTemplateColumns, boxShadow: sidePanelRow?.id === item.id && !selectedTableIds.has(item.id) ? 'inset 3px 0 0 var(--primary)' : undefined }" @click="onRowClick(item, $event)" @contextmenu="onContextMenu" > diff --git a/apps/desktop/src/components/settings/McpConnectionScopePicker.vue b/apps/desktop/src/components/settings/McpConnectionScopePicker.vue index c158fd702..da94d7e23 100644 --- a/apps/desktop/src/components/settings/McpConnectionScopePicker.vue +++ b/apps/desktop/src/components/settings/McpConnectionScopePicker.vue @@ -159,7 +159,7 @@ watch([policyKey, () => groups.value.allowed.length], () => { :tabindex="scopeMode === 'all' ? 0 : -1" variant="outline" class="settings-choice-card h-auto justify-center border p-3" - :class="[scopeMode === 'all' ? 'settings-choice-card--selected border-blue-300 ring-2 ring-blue-300/50' : '', disabled ? 'cursor-not-allowed opacity-50' : '']" + :class="[scopeMode === 'all' ? 'dbx-choice-selected' : '', disabled ? 'cursor-not-allowed opacity-50' : '']" @click="setScopeMode('all')" @keydown="onScopeModeKeydown($event, 'all')" > @@ -177,7 +177,7 @@ watch([policyKey, () => groups.value.allowed.length], () => { :tabindex="scopeMode === 'selected' ? 0 : -1" variant="outline" class="settings-choice-card h-auto justify-center border p-3" - :class="[scopeMode === 'selected' ? 'settings-choice-card--selected border-blue-300 ring-2 ring-blue-300/50' : '', disabled ? 'cursor-not-allowed opacity-50' : '']" + :class="[scopeMode === 'selected' ? 'dbx-choice-selected' : '', disabled ? 'cursor-not-allowed opacity-50' : '']" @click="setScopeMode('selected')" @keydown="onScopeModeKeydown($event, 'selected')" > diff --git a/apps/desktop/src/components/settings/__tests__/McpConnectionScopePicker.spec.ts b/apps/desktop/src/components/settings/__tests__/McpConnectionScopePicker.spec.ts index 798c5c05b..6bfd7be40 100644 --- a/apps/desktop/src/components/settings/__tests__/McpConnectionScopePicker.spec.ts +++ b/apps/desktop/src/components/settings/__tests__/McpConnectionScopePicker.spec.ts @@ -70,7 +70,7 @@ describe("McpConnectionScopePicker", () => { return node.type === "button" && node.props["class"]?.includes("settings-choice-card"); }); - expect(modeButtons.find((node) => node.props["data-scope-mode"] === "all")?.props["class"]?.includes("settings-choice-card--selected")).toBe(true); + expect(modeButtons.find((node) => node.props["data-scope-mode"] === "all")?.props["class"]?.includes("dbx-choice-selected")).toBe(true); expect(modeButtons.find((node) => node.props["data-scope-mode"] === "all")?.props.role).toBe("radio"); expect(modeButtons.find((node) => node.props["data-scope-mode"] === "all")?.props["aria-checked"]).toBe(true); expect(modeButtons.find((node) => node.props["data-scope-mode"] === "selected")?.props.tabindex).toBe(-1); diff --git a/apps/desktop/src/components/sidebar/TreeItem.vue b/apps/desktop/src/components/sidebar/TreeItem.vue index 4912cf381..4b0e68285 100644 --- a/apps/desktop/src/components/sidebar/TreeItem.vue +++ b/apps/desktop/src/components/sidebar/TreeItem.vue @@ -714,8 +714,8 @@ const tableSearchStyle = computed(() => { return { paddingLeft: paddingLeft.value, "--tree-table-search-row-bg": rowBackgroundColor, - "--tree-table-search-input-bg": color ? hexToRgba(color, isActiveConnectionScope.value ? 0.05 : 0.03) : "hsl(var(--background) / 0.56)", - "--tree-table-search-border": color ? hexToRgba(color, isActiveConnectionScope.value ? 0.12 : 0.08) : "hsl(var(--border) / 0.36)", + "--tree-table-search-input-bg": color ? hexToRgba(color, isActiveConnectionScope.value ? 0.05 : 0.03) : "color-mix(in srgb, var(--background) 56%, transparent)", + "--tree-table-search-border": color ? hexToRgba(color, isActiveConnectionScope.value ? 0.12 : 0.08) : "color-mix(in srgb, var(--border) 36%, transparent)", }; }); @@ -1339,33 +1339,29 @@ function onKeydown(event: KeyboardEvent) { /* Multi-selection treats every selected row as equal; keep focus neutral. */ .tree-item-active--selection-set:focus { background-color: var(--tree-connection-active-bg, rgb(235 235 235)) !important; - box-shadow: inset 0 0 0 1px hsl(var(--foreground) / 0.14); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--foreground) 14%, transparent); } :root.dark .tree-item-active--selection-set:focus { background-color: var(--tree-connection-active-bg, rgb(36 36 36)) !important; - box-shadow: inset 0 0 0 1px hsl(var(--foreground) / 0.18); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--foreground) 18%, transparent); } -/* Locate highlight: instant amber, then fade on removal */ +/* Locate highlight: instant warning tint, then fade on removal */ .tree-item-highlight { - background-color: rgb(253 225 167) !important; - background-color: oklch(0.92 0.08 85) !important; + background-color: var(--warning-bg) !important; transition: background-color 0.28s ease-out; } :root.dark .tree-item-highlight { - background-color: rgb(110 67 0) !important; - background-color: oklch(0.42 0.12 80) !important; + background-color: var(--warning-bg) !important; transition: background-color 0.28s ease-out; } .tree-item-connection-tint.tree-item-highlight::before { - background-color: rgb(253 225 167) !important; - background-color: oklch(0.92 0.08 85) !important; + background-color: var(--warning-bg) !important; } :root.dark .tree-item-connection-tint.tree-item-highlight::before { - background-color: rgb(110 67 0) !important; - background-color: oklch(0.42 0.12 80) !important; + background-color: var(--warning-bg) !important; } diff --git a/apps/desktop/src/components/structure/TableStructureEditor.vue b/apps/desktop/src/components/structure/TableStructureEditor.vue index 81422e4c1..c8073550f 100644 --- a/apps/desktop/src/components/structure/TableStructureEditor.vue +++ b/apps/desktop/src/components/structure/TableStructureEditor.vue @@ -3302,7 +3302,7 @@ watch([activeTab, ddlLoading], ([tab, loading]) => {
-
+
{{ t("structureEditor.sqliteRebuildNotice") }}
@@ -3335,7 +3335,7 @@ watch([activeTab, ddlLoading], ([tab, loading]) => { diff --git a/apps/desktop/src/components/ui/switch/Switch.vue b/apps/desktop/src/components/ui/switch/Switch.vue index a89e549dd..a480674dd 100644 --- a/apps/desktop/src/components/ui/switch/Switch.vue +++ b/apps/desktop/src/components/ui/switch/Switch.vue @@ -51,8 +51,8 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits); position: relative; flex-shrink: 0; overflow: hidden; - border: 1px solid rgba(120, 120, 128, 0.22) !important; - background-color: rgb(229, 229, 229) !important; + border: 1px solid color-mix(in srgb, var(--border) 80%, transparent) !important; + background-color: var(--input) !important; vertical-align: middle; } @@ -69,8 +69,8 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits); .dbx-switch-thumb { display: block !important; border-radius: 9999px; - background-color: rgb(255, 255, 255) !important; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.22); + background-color: var(--background) !important; + box-shadow: 0 1px 2px color-mix(in srgb, var(--foreground) 18%, transparent); transform: translateX(0) !important; } @@ -87,8 +87,8 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits); .dbx-switch[data-state="checked"], .dbx-switch[data-checked], .dbx-switch[aria-checked="true"] { - border-color: rgb(23, 23, 23) !important; - background-color: rgb(23, 23, 23) !important; + border-color: var(--primary) !important; + background-color: var(--primary) !important; } .dbx-switch[data-size="default"][data-state="checked"] .dbx-switch-thumb, @@ -103,21 +103,27 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits); transform: translateX(10px) !important; } +.dbx-switch[data-state="checked"] .dbx-switch-thumb, +.dbx-switch[data-checked] .dbx-switch-thumb, +.dbx-switch[aria-checked="true"] .dbx-switch-thumb { + background-color: var(--primary-foreground) !important; +} + .dark .dbx-switch { - border-color: rgba(255, 255, 255, 0.16) !important; - background-color: rgba(255, 255, 255, 0.18) !important; + border-color: color-mix(in srgb, var(--border) 90%, transparent) !important; + background-color: color-mix(in srgb, var(--input) 80%, transparent) !important; } .dark .dbx-switch[data-state="checked"], .dark .dbx-switch[data-checked], .dark .dbx-switch[aria-checked="true"] { - border-color: rgb(245, 245, 245) !important; - background-color: rgb(245, 245, 245) !important; + border-color: var(--primary) !important; + background-color: var(--primary) !important; } .dark .dbx-switch[data-state="checked"] .dbx-switch-thumb, .dark .dbx-switch[data-checked] .dbx-switch-thumb, .dark .dbx-switch[aria-checked="true"] .dbx-switch-thumb { - background-color: rgb(23, 23, 23) !important; + background-color: var(--primary-foreground) !important; } diff --git a/apps/desktop/src/lib/__tests__/dataGrid/dataGridPaintTheme.spec.ts b/apps/desktop/src/lib/__tests__/dataGrid/dataGridPaintTheme.spec.ts index b77f5a1a7..c59d49f3b 100644 --- a/apps/desktop/src/lib/__tests__/dataGrid/dataGridPaintTheme.spec.ts +++ b/apps/desktop/src/lib/__tests__/dataGrid/dataGridPaintTheme.spec.ts @@ -1,8 +1,34 @@ import { describe, expect, it } from "vitest"; +import { readFileSync } from "node:fs"; import { DATA_GRID_DARK_ACTIVE_ROW_BG, DATA_GRID_LIGHT_ACTIVE_ROW_BG, dataGridActiveRowBackground, resolveDataGridPaintTheme } from "@/lib/dataGrid/dataGridPaintTheme"; +function parseRgb(value: string): { r: number; g: number; b: number } | null { + const match = value.match(/rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)/i); + if (!match) return null; + return { r: Number(match[1]), g: Number(match[2]), b: Number(match[3]) }; +} + +function relativeLuminance(rgb: { r: number; g: number; b: number }): number { + const channel = (value: number) => { + const s = value / 255; + return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4; + }; + return 0.2126 * channel(rgb.r) + 0.7152 * channel(rgb.g) + 0.0722 * channel(rgb.b); +} + +function contrastRatio(a: string, b: string): number { + const left = parseRgb(a); + const right = parseRgb(b); + if (!left || !right) return 0; + const l1 = relativeLuminance(left); + const l2 = relativeLuminance(right); + const lighter = Math.max(l1, l2); + const darker = Math.min(l1, l2); + return (lighter + 0.05) / (darker + 0.05); +} + describe("data grid paint theme", () => { - it("uses a subtle blue active-row surface in both color schemes", () => { + it("uses a subtle active-row surface fallback in both color schemes", () => { expect(dataGridActiveRowBackground(false)).toBe(DATA_GRID_LIGHT_ACTIVE_ROW_BG); expect(dataGridActiveRowBackground(true)).toBe(DATA_GRID_DARK_ACTIVE_ROW_BG); @@ -12,4 +38,91 @@ describe("data grid paint theme", () => { expect(resolveDataGridPaintTheme({ getVar: emptyCssVariable, isDark: true }).cellActive).toBe(DATA_GRID_DARK_ACTIVE_ROW_BG); expect(resolveDataGridPaintTheme({ getVar: emptyCssVariable, isDark: true }).rowNumberActive).toBe(DATA_GRID_DARK_ACTIVE_ROW_BG); }); + + it("reads semantic success/warning tokens when present", () => { + const vars: Record = { + "--background": "rgb(255, 255, 255)", + "--foreground": "rgb(10, 10, 10)", + "--muted-foreground": "rgb(115, 115, 115)", + "--primary": "rgb(23, 23, 23)", + "--destructive": "rgb(231, 0, 11)", + "--accent": "rgb(245, 245, 245)", + "--border": "rgb(229, 229, 229)", + "--muted": "rgb(245, 245, 245)", + "--success": "rgb(22, 163, 74)", + "--warning": "rgb(217, 119, 6)", + "--success-bg": "rgb(220, 252, 231)", + "--warning-bg": "rgb(254, 243, 199)", + "--color-error-bg": "rgb(254, 226, 226)", + }; + + const theme = resolveDataGridPaintTheme({ + getVar: (name) => vars[name] ?? "", + isDark: false, + }); + + expect(theme.rowNumberTextNew).toBe("rgb(22, 163, 74)"); + expect(theme.rowNumberTextEdited).toBe("rgb(217, 119, 6)"); + expect(theme.rowNumberNew).toBe("rgb(220, 252, 231)"); + expect(theme.cellDirty).toBe("rgb(254, 243, 199)"); + expect(theme.rowDeleted).toBe("rgb(254, 226, 226)"); + }); + + it("derives selection border from ring/primary instead of the low-contrast border token", () => { + const vars: Record = { + "--background": "rgb(255, 255, 255)", + "--foreground": "rgb(10, 10, 10)", + "--muted-foreground": "rgb(115, 115, 115)", + "--primary": "rgb(23, 23, 23)", + "--destructive": "rgb(231, 0, 11)", + "--accent": "rgb(226, 226, 226)", + "--border": "rgb(229, 229, 229)", + "--ring": "rgb(23, 23, 23)", + "--muted": "rgb(245, 245, 245)", + }; + + const theme = resolveDataGridPaintTheme({ + getVar: (name) => vars[name] ?? "", + isDark: false, + }); + + expect(theme.cellSelectedBorder).toBe("rgb(23, 23, 23)"); + expect(theme.cellSelectedBorder).not.toBe(vars["--border"]); + expect(contrastRatio(theme.cellSelectedBorder, theme.cellSelected)).toBeGreaterThanOrEqual(3); + }); + + it("honors an explicit --data-grid-cell-selected-border token when provided", () => { + const vars: Record = { + "--background": "rgb(255, 255, 255)", + "--foreground": "rgb(10, 10, 10)", + "--muted-foreground": "rgb(115, 115, 115)", + "--primary": "rgb(23, 23, 23)", + "--destructive": "rgb(231, 0, 11)", + "--accent": "rgb(226, 226, 226)", + "--border": "rgb(229, 229, 229)", + "--ring": "rgb(161, 161, 161)", + "--muted": "rgb(245, 245, 245)", + "--data-grid-cell-selected-border": "rgb(37, 99, 235)", + }; + + const theme = resolveDataGridPaintTheme({ + getVar: (name) => vars[name] ?? "", + isDark: false, + }); + + expect(theme.cellSelectedBorder).toBe("rgb(37, 99, 235)"); + }); +}); + +describe("dbx-control-chrome cascade contract", () => { + it("keeps chrome defaults layered and low-specificity so invalid/focus utilities can win", () => { + const css = readFileSync(new URL("../../../styles/globals.css", import.meta.url), "utf8"); + const chromeBlockStart = css.indexOf("Shared control chrome defaults"); + expect(chromeBlockStart).toBeGreaterThanOrEqual(0); + const chromeSlice = css.slice(chromeBlockStart - 40, chromeBlockStart + 1600); + expect(chromeSlice).toMatch(/@layer components/); + expect(chromeSlice).toMatch(/:where\(\.dbx-control-chrome\)/); + expect(chromeSlice).toMatch(/\[aria-invalid="true"\]/); + expect(chromeSlice).not.toMatch(/box-shadow:\s*none/); + }); }); diff --git a/apps/desktop/src/lib/__tests__/dataGrid/dataGridRuntime.spec.ts b/apps/desktop/src/lib/__tests__/dataGrid/dataGridRuntime.spec.ts index 639d1d39c..f0880839f 100644 --- a/apps/desktop/src/lib/__tests__/dataGrid/dataGridRuntime.spec.ts +++ b/apps/desktop/src/lib/__tests__/dataGrid/dataGridRuntime.spec.ts @@ -30,13 +30,14 @@ describe("dataGridRuntime", () => { }); it("keeps runtime modules independent from the DataGrid component", () => { - const sourceRoots = [join(process.cwd(), "apps/desktop/src/lib/dataGrid"), join(process.cwd(), "apps/desktop/src/composables")]; + const repoRoot = process.cwd().replace(/[\\/]apps[\\/]desktop$/, ""); + const sourceRoots = [join(repoRoot, "apps/desktop/src/lib/dataGrid"), join(repoRoot, "apps/desktop/src/composables")]; const sourceFiles: string[] = []; const visit = (directory: string) => { for (const entry of readdirSync(directory, { withFileTypes: true })) { const path = join(directory, entry.name); if (entry.isDirectory()) visit(path); - else if (entry.name.endsWith(".ts") && (!directory.endsWith("/composables") || entry.name.startsWith("useDataGrid"))) sourceFiles.push(path); + else if (entry.name.endsWith(".ts") && (!directory.endsWith("composables") || entry.name.startsWith("useDataGrid"))) sourceFiles.push(path); } }; sourceRoots.forEach(visit); diff --git a/apps/desktop/src/lib/dataGrid/dataGridPaintTheme.ts b/apps/desktop/src/lib/dataGrid/dataGridPaintTheme.ts index 73180367d..4c5e91b80 100644 --- a/apps/desktop/src/lib/dataGrid/dataGridPaintTheme.ts +++ b/apps/desktop/src/lib/dataGrid/dataGridPaintTheme.ts @@ -246,26 +246,33 @@ export function resolveDataGridPaintTheme(options: { getVar: (name: string) => s const primary = cssVarColor(getVar, "--primary", isDark ? "rgb(208, 208, 214)" : "rgb(23, 23, 23)"); const destructive = cssVarColor(getVar, "--destructive", isDark ? "rgb(243, 98, 95)" : "rgb(231, 0, 11)"); const accent = cssVarColor(getVar, "--accent", isDark ? "rgb(46, 47, 51)" : "rgb(245, 245, 245)"); - const activeSurface = dataGridActiveRowBackground(isDark); + const success = cssVarColor(getVar, "--success", isDark ? "rgb(74, 222, 128)" : "rgb(22, 163, 74)"); + const warning = cssVarColor(getVar, "--warning", isDark ? "rgb(251, 191, 36)" : "rgb(217, 119, 6)"); + const successBg = paintToken(getVar, "--success-bg", isDark ? DATA_GRID_DARK_ROW_NUMBER_NEW_BG : DATA_GRID_LIGHT_ROW_NUMBER_NEW_BG); + const warningBg = paintToken(getVar, "--warning-bg", isDark ? DATA_GRID_DARK_ROW_NUMBER_EDITED_BG : DATA_GRID_LIGHT_ROW_NUMBER_EDITED_BG); + const destructiveBg = paintToken(getVar, "--color-error-bg", isDark ? DATA_GRID_DARK_ROW_NUMBER_DELETED_BG : DATA_GRID_LIGHT_ROW_NUMBER_DELETED_BG); + const activeSurface = paintToken(getVar, "--data-grid-cell-active-bg", dataGridActiveRowBackground(isDark)); const rowMuted = isDark ? DATA_GRID_DARK_STRIPED_ROW_BG : DATA_GRID_LIGHT_STRIPED_ROW_BG; - const rowNew = isDark ? "rgb(51, 51, 55)" : "rgb(243, 243, 243)"; - const rowDeleted = isDark ? "rgb(55, 31, 32)" : "rgb(255, 244, 244)"; + const rowNew = paintToken(getVar, "--muted", isDark ? "rgb(51, 51, 55)" : "rgb(243, 243, 243)"); + const rowDeleted = destructiveBg; const cellActive = activeSurface; - const cellDirty = isDark ? "rgb(94, 75, 26)" : "rgb(255, 248, 230)"; - const cellSelected = isDark ? "rgb(20, 40, 60)" : "rgb(239, 246, 255)"; - const cellSelectedDirty = isDark ? "rgb(76, 66, 38)" : "rgb(235, 224, 184)"; - const cellSelectedBorder = isDark ? "rgb(96, 165, 250)" : "rgb(59, 130, 246)"; - const cellSelectedSingle = isDark ? "rgb(30, 64, 96)" : "rgb(191, 219, 254)"; + const cellDirty = warningBg; + const cellSelected = paintToken(getVar, "--accent", isDark ? "rgb(20, 40, 60)" : "rgb(239, 246, 255)"); + const cellSelectedDirty = warningBg; + // Selection outline must stay readable on the selected fill (WCAG non-text ~3:1). + // Prefer ring/primary over the generic border token, which is often near the fill. + const cellSelectedBorder = paintToken(getVar, "--ring", isDark ? "rgb(96, 165, 250)" : primary); + const cellSelectedSingle = paintToken(getVar, "--muted", isDark ? "rgb(30, 64, 96)" : "rgb(191, 219, 254)"); const cellHover = accent; - const cellSearch = isDark ? DATA_GRID_DARK_SEARCH_COLORS.match : "rgb(253, 245, 184)"; - const cellCurrentSearch = isDark ? DATA_GRID_DARK_SEARCH_COLORS.current : "rgba(253, 224, 71, 0.52)"; - const cellCurrentSearchBorder = isDark ? DATA_GRID_DARK_SEARCH_COLORS.currentBorder : "rgba(234, 179, 8, 0.82)"; + const cellSearch = paintToken(getVar, "--warning-bg", isDark ? DATA_GRID_DARK_SEARCH_COLORS.match : "rgb(253, 245, 184)"); + const cellCurrentSearch = paintToken(getVar, "--warning-bg", isDark ? DATA_GRID_DARK_SEARCH_COLORS.current : "rgba(253, 224, 71, 0.52)"); + const cellCurrentSearchBorder = paintToken(getVar, "--warning", isDark ? DATA_GRID_DARK_SEARCH_COLORS.currentBorder : "rgba(234, 179, 8, 0.82)"); const rowNumberDefault = isDark ? DATA_GRID_DARK_ROW_NUMBER_BG : paintToken(getVar, "--data-grid-row-number-default-bg", "rgb(255, 255, 255)"); - const rowNumberNew = isDark ? DATA_GRID_DARK_ROW_NUMBER_NEW_BG : DATA_GRID_LIGHT_ROW_NUMBER_NEW_BG; - const rowNumberEdited = isDark ? DATA_GRID_DARK_ROW_NUMBER_EDITED_BG : DATA_GRID_LIGHT_ROW_NUMBER_EDITED_BG; - const rowNumberDeleted = isDark ? DATA_GRID_DARK_ROW_NUMBER_DELETED_BG : DATA_GRID_LIGHT_ROW_NUMBER_DELETED_BG; + const rowNumberNew = successBg; + const rowNumberEdited = warningBg; + const rowNumberDeleted = destructiveBg; const rowNumberActive = activeSurface; - const rowNumberSelected = isDark ? "rgb(30, 64, 96)" : "rgb(191, 219, 254)"; + const rowNumberSelected = cellSelectedSingle; return { background, @@ -273,29 +280,29 @@ export function resolveDataGridPaintTheme(options: { getVar: (name: string) => s foreground, mutedForeground, primary, - rowMuted: isDark ? rowMuted : paintToken(getVar, "--data-grid-row-muted-bg", rowMuted), - rowNew: isDark ? rowNew : paintToken(getVar, "--data-grid-row-new-bg", rowNew), - rowDeleted: isDark ? rowDeleted : paintToken(getVar, "--data-grid-row-deleted-bg", rowDeleted), - cellActive: isDark ? cellActive : paintToken(getVar, "--data-grid-cell-active-bg", cellActive), - cellDirty: isDark ? cellDirty : paintToken(getVar, "--data-grid-cell-dirty-bg", cellDirty), - cellSelected: isDark ? cellSelected : paintToken(getVar, "--data-grid-cell-selected-bg", cellSelected), - cellSelectedDirty: isDark ? cellSelectedDirty : paintToken(getVar, "--data-grid-cell-selected-dirty-bg", cellSelectedDirty), - cellSelectedBorder: isDark ? cellSelectedBorder : paintToken(getVar, "--data-grid-cell-selected-border", cellSelectedBorder), - cellSelectedSingle: isDark ? cellSelectedSingle : paintToken(getVar, "--data-grid-cell-selected-single-bg", cellSelectedSingle), - cellSelectedSingleBorder: isDark ? primary : paintToken(getVar, "--data-grid-cell-selected-single-border", primary), - cellHover: isDark ? cellHover : paintToken(getVar, "--data-grid-cell-hover-bg", cellHover), - cellSearch: isDark ? cellSearch : paintToken(getVar, "--data-grid-cell-search-bg", cellSearch), - cellCurrentSearch: isDark ? cellCurrentSearch : paintToken(getVar, "--data-grid-cell-current-search-bg", cellCurrentSearch), - cellCurrentSearchBorder: isDark ? cellCurrentSearchBorder : paintToken(getVar, "--data-grid-cell-current-search-border", cellCurrentSearchBorder), + rowMuted: paintToken(getVar, "--data-grid-row-muted-bg", rowMuted), + rowNew: paintToken(getVar, "--data-grid-row-new-bg", rowNew), + rowDeleted: paintToken(getVar, "--data-grid-row-deleted-bg", rowDeleted), + cellActive: paintToken(getVar, "--data-grid-cell-active-bg", cellActive), + cellDirty: paintToken(getVar, "--data-grid-cell-dirty-bg", cellDirty), + cellSelected: paintToken(getVar, "--data-grid-cell-selected-bg", cellSelected), + cellSelectedDirty: paintToken(getVar, "--data-grid-cell-selected-dirty-bg", cellSelectedDirty), + cellSelectedBorder: paintToken(getVar, "--data-grid-cell-selected-border", cellSelectedBorder), + cellSelectedSingle: paintToken(getVar, "--data-grid-cell-selected-single-bg", cellSelectedSingle), + cellSelectedSingleBorder: paintToken(getVar, "--data-grid-cell-selected-single-border", primary), + cellHover: paintToken(getVar, "--data-grid-cell-hover-bg", cellHover), + cellSearch: paintToken(getVar, "--data-grid-cell-search-bg", cellSearch), + cellCurrentSearch: paintToken(getVar, "--data-grid-cell-current-search-bg", cellCurrentSearch), + cellCurrentSearchBorder: paintToken(getVar, "--data-grid-cell-current-search-border", cellCurrentSearchBorder), rowNumberDefault, - rowNumberNew: isDark ? rowNumberNew : paintToken(getVar, "--data-grid-row-number-new-bg", rowNumberNew), - rowNumberEdited: isDark ? rowNumberEdited : paintToken(getVar, "--data-grid-row-number-edited-bg", rowNumberEdited), - rowNumberDeleted: isDark ? rowNumberDeleted : paintToken(getVar, "--data-grid-row-number-deleted-bg", rowNumberDeleted), - rowNumberActive: isDark ? rowNumberActive : paintToken(getVar, "--data-grid-row-number-active-bg", rowNumberActive), - rowNumberSelected: isDark ? rowNumberSelected : paintToken(getVar, "--data-grid-row-number-selected-bg", rowNumberSelected), + rowNumberNew: paintToken(getVar, "--data-grid-row-number-new-bg", rowNumberNew), + rowNumberEdited: paintToken(getVar, "--data-grid-row-number-edited-bg", rowNumberEdited), + rowNumberDeleted: paintToken(getVar, "--data-grid-row-number-deleted-bg", rowNumberDeleted), + rowNumberActive: paintToken(getVar, "--data-grid-row-number-active-bg", rowNumberActive), + rowNumberSelected: paintToken(getVar, "--data-grid-row-number-selected-bg", rowNumberSelected), rowNumberTextClean: mutedForeground, - rowNumberTextNew: isDark ? "rgb(94, 233, 181)" : "rgb(0, 122, 85)", - rowNumberTextEdited: isDark ? "rgb(255, 210, 48)" : "rgb(187, 77, 0)", + rowNumberTextNew: success, + rowNumberTextEdited: warning, rowNumberTextDeleted: destructive, }; } diff --git a/apps/desktop/src/styles/globals.css b/apps/desktop/src/styles/globals.css index 70af1c132..c4bb950c5 100644 --- a/apps/desktop/src/styles/globals.css +++ b/apps/desktop/src/styles/globals.css @@ -68,8 +68,19 @@ /* Aliases for legacy MQ panel styles (used across components/mq/*). */ --color-error: var(--destructive); --color-error-bg: color-mix(in srgb, var(--destructive) 10%, transparent); + --color-error-alpha: var(--color-error-bg); + --color-success: var(--success); + --color-success-bg: var(--success-bg); + --color-success-alpha: var(--success-bg); + --color-warning: var(--warning); + --color-warning-bg: var(--warning-bg); + --color-warning-alpha: var(--warning-bg); + --color-info: var(--info); + --color-info-bg: var(--info-bg); + --color-info-alpha: var(--info-bg); --color-hover: var(--accent); --color-background-secondary: var(--muted); + --color-text: var(--foreground); --color-text-secondary: var(--muted-foreground); --color-text-tertiary: color-mix(in srgb, var(--muted-foreground) 70%, transparent); --color-border-light: color-mix(in srgb, var(--border) 60%, transparent); @@ -136,6 +147,21 @@ --sidebar-accent-foreground: rgb(23 23 23); --sidebar-border: rgb(229 229 229); --sidebar-ring: rgb(161 161 161); + --success: rgb(22 163 74); + --success-foreground: rgb(240 253 244); + --success-bg: color-mix(in srgb, var(--success) 12%, transparent); + --warning: rgb(217 119 6); + --warning-foreground: rgb(255 251 235); + --warning-bg: color-mix(in srgb, var(--warning) 14%, transparent); + --info: rgb(37 99 235); + --info-foreground: rgb(239 246 255); + --info-bg: color-mix(in srgb, var(--info) 12%, transparent); + --dbx-chrome: rgb(245 245 245); + --dbx-chrome-muted: rgb(240 240 240); + --dbx-content: rgb(255 255 255); + --dbx-editor-toolbar: rgb(250 250 250); + --dbx-gutter: rgb(243 243 243); + --dbx-sidebar-header: rgb(250 250 250); --dbx-window-top-border: rgb(0 0 0 / 0.35); --dbx-viewport-height: 100vh; } @@ -178,6 +204,66 @@ border-radius: var(--dbx-radius-fixed-6); } +/* + * Shared control chrome defaults. + * Keep these in @layer components + :where() so Tailwind utilities and + * aria-invalid / disabled / caller bg-* hover:* focus rings can override. + * Do not set box-shadow here — it would erase focus/invalid rings. + */ +@layer components { + :where(.dbx-control-chrome):not(.border-0):not(.border-transparent):not([aria-invalid="true"]):not(:disabled) { + border-color: var(--input); + background-color: var(--background); + } + + :where(.dbx-control-chrome):not(.border-0):not(.border-transparent):not([aria-invalid="true"]):not(:disabled):hover { + background-color: color-mix(in srgb, var(--muted) 55%, var(--background)); + } + + :where(.dbx-control-chrome):not(.border-0):not(.border-transparent):not([aria-invalid="true"]):not(:disabled):focus-visible, + :where(.dbx-control-chrome):not(.border-0):not(.border-transparent):not([aria-invalid="true"]):not(:disabled)[data-state="open"], + :where(.dbx-control-chrome):not(.border-0):not(.border-transparent):not([aria-invalid="true"]):not(:disabled)[aria-expanded="true"] { + border-color: var(--ring); + } + + :where(.dark) :where(.dbx-control-chrome):not(.border-0):not(.border-transparent):not([aria-invalid="true"]):not(:disabled) { + border-color: var(--input); + background-color: color-mix(in srgb, var(--input) 30%, transparent); + } + + :where(.dark) :where(.dbx-control-chrome):not(.border-0):not(.border-transparent):not([aria-invalid="true"]):not(:disabled):hover { + background-color: color-mix(in srgb, var(--input) 50%, transparent); + } + + :where(.dark) :where(.dbx-control-chrome):not(.border-0):not(.border-transparent):not([aria-invalid="true"]):not(:disabled):focus-visible, + :where(.dark) :where(.dbx-control-chrome):not(.border-0):not(.border-transparent):not([aria-invalid="true"]):not(:disabled)[data-state="open"], + :where(.dark) :where(.dbx-control-chrome):not(.border-0):not(.border-transparent):not([aria-invalid="true"]):not(:disabled)[aria-expanded="true"] { + border-color: var(--ring); + } +} + +.dbx-choice-selected { + border-color: color-mix(in srgb, var(--primary) 40%, transparent) !important; + background-color: color-mix(in srgb, var(--primary) 10%, transparent) !important; + color: var(--primary) !important; + box-shadow: 0 0 0 1px color-mix(in srgb, var(--primary) 30%, transparent) !important; +} + +.dbx-choice-selected svg { + color: currentColor !important; +} + +.dbx-tile-selected { + border-color: var(--primary) !important; + background-color: color-mix(in srgb, var(--primary) 10%, transparent) !important; + color: var(--foreground) !important; + box-shadow: 0 0 0 1px color-mix(in srgb, var(--primary) 28%, transparent); +} + +.dbx-tile-selected:hover { + background-color: color-mix(in srgb, var(--primary) 14%, transparent) !important; +} + @supports (height: 100dvh) { :root { --dbx-viewport-height: min(100vh, 100dvh); @@ -211,12 +297,27 @@ --chart-5: rgb(38 38 38); --sidebar: rgb(25 25 28); --sidebar-foreground: rgb(208 208 213); - --sidebar-primary: rgb(20 71 230); - --sidebar-primary-foreground: rgb(234 235 239); + --sidebar-primary: rgb(208 208 214); + --sidebar-primary-foreground: rgb(19 20 22); --sidebar-accent: rgb(44 44 48); --sidebar-accent-foreground: rgb(221 221 226); --sidebar-border: rgb(110 110 114 / 0.28); --sidebar-ring: rgb(133 134 139); + --success: rgb(74 222 128); + --success-foreground: rgb(20 30 24); + --success-bg: color-mix(in srgb, var(--success) 16%, transparent); + --warning: rgb(251 191 36); + --warning-foreground: rgb(40 32 12); + --warning-bg: color-mix(in srgb, var(--warning) 16%, transparent); + --info: rgb(96 165 250); + --info-foreground: rgb(18 28 46); + --info-bg: color-mix(in srgb, var(--info) 16%, transparent); + --dbx-chrome: rgb(27 27 30); + --dbx-chrome-muted: rgb(32 32 36); + --dbx-content: rgb(19 20 22); + --dbx-editor-toolbar: rgb(25 25 28); + --dbx-gutter: rgb(23 23 25); + --dbx-sidebar-header: rgb(25 25 28); --dbx-window-top-border: rgb(255 255 255 / 0.18); } @@ -1215,6 +1316,21 @@ html.theme-claude.dark { --sidebar-accent-foreground: oklch(0.205 0 0); --sidebar-border: oklch(0.922 0 0); --sidebar-ring: oklch(0.708 0 0); + --success: oklch(0.62 0.17 150); + --success-foreground: oklch(0.97 0.02 150); + --success-bg: color-mix(in oklch, var(--success) 12%, transparent); + --warning: oklch(0.7 0.15 70); + --warning-foreground: oklch(0.98 0.02 85); + --warning-bg: color-mix(in oklch, var(--warning) 14%, transparent); + --info: oklch(0.55 0.18 250); + --info-foreground: oklch(0.97 0.02 250); + --info-bg: color-mix(in oklch, var(--info) 12%, transparent); + --dbx-chrome: oklch(0.97 0 0); + --dbx-chrome-muted: oklch(0.95 0 0); + --dbx-content: oklch(1 0 0); + --dbx-editor-toolbar: oklch(0.985 0 0); + --dbx-gutter: oklch(0.965 0 0); + --dbx-sidebar-header: oklch(0.985 0 0); } .dark { @@ -1243,12 +1359,27 @@ html.theme-claude.dark { --chart-5: oklch(0.269 0 0); --sidebar: oklch(0.215 0.005 285); --sidebar-foreground: oklch(0.86 0.006 285); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.94 0.006 285); + --sidebar-primary: oklch(0.86 0.008 285); + --sidebar-primary-foreground: oklch(0.19 0.004 285); --sidebar-accent: oklch(0.295 0.006 285); --sidebar-accent-foreground: oklch(0.9 0.006 285); --sidebar-border: oklch(0.54 0.006 285 / 28%); --sidebar-ring: oklch(0.62 0.008 285); + --success: oklch(0.78 0.16 150); + --success-foreground: oklch(0.22 0.03 150); + --success-bg: color-mix(in oklch, var(--success) 16%, transparent); + --warning: oklch(0.82 0.15 85); + --warning-foreground: oklch(0.28 0.05 85); + --warning-bg: color-mix(in oklch, var(--warning) 16%, transparent); + --info: oklch(0.75 0.12 250); + --info-foreground: oklch(0.25 0.04 250); + --info-bg: color-mix(in oklch, var(--info) 16%, transparent); + --dbx-chrome: oklch(0.225 0.005 285); + --dbx-chrome-muted: oklch(0.25 0.006 285); + --dbx-content: oklch(0.19 0.004 285); + --dbx-editor-toolbar: oklch(0.215 0.005 285); + --dbx-gutter: oklch(0.205 0.004 285); + --dbx-sidebar-header: oklch(0.215 0.005 285); } html.theme-soft { @@ -1899,46 +2030,40 @@ body.dbx-table-reference-dragging * { cursor: row-resize; } .splitpanes__splitter:hover { - background: rgb(39 132 213) !important; - background: oklch(0.6 0.15 250) !important; + background: var(--ring) !important; } /* Panel resize handles */ .app-panel-gutter { - background: rgb(243 243 243); - background: oklch(0.965 0 0); -} -.dark .app-panel-gutter { - background: rgb(23 23 25); - background: oklch(0.205 0.004 285); -} -:is(.theme-soft, .theme-graphite, .theme-cobalt, .theme-sage, .theme-amber, .theme-blush, .theme-vscode, .theme-idea, .theme-xcode, .theme-jetbrains, .theme-cursor, .theme-claude) .app-panel-gutter { background: var(--dbx-gutter); } -:is(.theme-soft, .theme-graphite, .theme-cobalt, .theme-sage, .theme-amber, .theme-blush, .theme-vscode, .theme-idea, .theme-xcode, .theme-jetbrains, .theme-cursor, .theme-claude) .app-toolbar, -:is(.theme-soft, .theme-graphite, .theme-cobalt, .theme-sage, .theme-amber, .theme-blush, .theme-vscode, .theme-idea, .theme-xcode, .theme-jetbrains, .theme-cursor, .theme-claude) .app-tab-bar { +.dark .app-panel-gutter { + background: var(--dbx-gutter); +} +.app-toolbar, +.app-tab-bar { border-color: var(--border); background: var(--dbx-chrome); } -:is(.theme-soft, .theme-graphite, .theme-cobalt, .theme-sage, .theme-amber, .theme-blush, .theme-vscode, .theme-idea, .theme-xcode, .theme-jetbrains, .theme-cursor, .theme-claude) .app-sidebar-panel { +.app-sidebar-panel { color: var(--sidebar-foreground); background: var(--sidebar); } -:is(.theme-soft, .theme-graphite, .theme-cobalt, .theme-sage, .theme-amber, .theme-blush, .theme-vscode, .theme-idea, .theme-xcode, .theme-jetbrains, .theme-cursor, .theme-claude) .app-layout-classic > .app-sidebar-panel { +.app-layout-classic > .app-sidebar-panel { border-right: 1px solid var(--sidebar-border); } -:is(.theme-soft, .theme-graphite, .theme-cobalt, .theme-sage, .theme-amber, .theme-blush, .theme-vscode, .theme-idea, .theme-xcode, .theme-jetbrains, .theme-cursor, .theme-claude) .app-sidebar-toolbar, -:is(.theme-soft, .theme-graphite, .theme-cobalt, .theme-sage, .theme-amber, .theme-blush, .theme-vscode, .theme-idea, .theme-xcode, .theme-jetbrains, .theme-cursor, .theme-claude) .connection-tree-search, -:is(.theme-soft, .theme-graphite, .theme-cobalt, .theme-sage, .theme-amber, .theme-blush, .theme-vscode, .theme-idea, .theme-xcode, .theme-jetbrains, .theme-cursor, .theme-claude) .sticky-database-header { +.app-sidebar-toolbar, +.connection-tree-search, +.sticky-database-header { border-color: var(--sidebar-border); background: var(--dbx-sidebar-header); } -:is(.theme-soft, .theme-graphite, .theme-cobalt, .theme-sage, .theme-amber, .theme-blush, .theme-vscode, .theme-idea, .theme-xcode, .theme-jetbrains, .theme-cursor, .theme-claude) .app-editor-toolbar { +.app-editor-toolbar { border-color: var(--border); background: var(--dbx-editor-toolbar); } -:is(.theme-soft, .theme-graphite, .theme-cobalt, .theme-sage, .theme-amber, .theme-blush, .theme-vscode, .theme-idea, .theme-xcode, .theme-jetbrains, .theme-cursor, .theme-claude) .app-layout-classic > .min-w-0, -:is(.theme-soft, .theme-graphite, .theme-cobalt, .theme-sage, .theme-amber, .theme-blush, .theme-vscode, .theme-idea, .theme-xcode, .theme-jetbrains, .theme-cursor, .theme-claude) .app-layout-classic > .flex-1 { +.app-layout-classic > .min-w-0, +.app-layout-classic > .flex-1 { background: var(--dbx-content); } html.theme-vscode .app-tab-scroll.wrap-mode .app-tab-pill[data-active-tab="false"] { @@ -1957,8 +2082,7 @@ html.theme-vscode .app-tab-scroll.wrap-mode .app-tab-pill[data-active-tab="false } .app-layout-classic .panel-resize-handle:hover, .app-layout-classic .panel-resize-handle:active { - background: rgb(39 132 213); - background: oklch(0.6 0.15 250); + background: var(--ring); } .panel-resize-handle { diff --git a/packages/app-tests/dataGridFontSettings.test.ts b/packages/app-tests/dataGridFontSettings.test.ts index cd0bf3ba9..8dc9ab041 100644 --- a/packages/app-tests/dataGridFontSettings.test.ts +++ b/packages/app-tests/dataGridFontSettings.test.ts @@ -36,15 +36,16 @@ test("keeps appearance section spacing and help icons aligned without stacked ma assert.match(settingsDialogSource, /
\s*