fix(desktop): unify UI colors onto theme tokens

This commit is contained in:
Freedom authored and GitHub committed 2026-07-28 11:24:10 +08:00
1 parent af479c7770
commit 1cdbf4fa3d
31 files changed
+558 -582

No files matched your search

+13 -13
View File
@@ -209,13 +209,13 @@ defineExpose({ expandAll, collapseAll, resetExpansion, refresh });
<style> <style>
.json-tree { .json-tree {
--json-tree-key: #1d4ed8; --json-tree-key: var(--info);
--json-tree-string: #15803d; --json-tree-string: var(--success);
--json-tree-number: #b45309; --json-tree-number: var(--warning);
--json-tree-boolean: #7c3aed; --json-tree-boolean: color-mix(in srgb, var(--primary) 70%, var(--info));
--json-tree-null: #64748b; --json-tree-null: var(--muted-foreground);
color: hsl(var(--foreground)); color: var(--foreground);
white-space: pre-wrap; white-space: pre-wrap;
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
@@ -245,7 +245,7 @@ defineExpose({ expandAll, collapseAll, resetExpansion, refresh });
} }
.json-tree-row:hover { .json-tree-row:hover {
background: hsl(var(--muted) / 0.5); background: color-mix(in srgb, var(--muted) 50%, transparent);
} }
.json-tree-toggle { .json-tree-toggle {
@@ -257,16 +257,16 @@ defineExpose({ expandAll, collapseAll, resetExpansion, refresh });
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 3px; border-radius: 3px;
color: hsl(var(--muted-foreground)); color: var(--muted-foreground);
} }
.json-tree-toggle:hover { .json-tree-toggle:hover {
background: hsl(var(--accent)); background: var(--accent);
color: hsl(var(--foreground)); color: var(--foreground);
} }
.json-tree-toggle:focus-visible { .json-tree-toggle:focus-visible {
outline: 2px solid hsl(var(--ring)); outline: 2px solid var(--ring);
outline-offset: 1px; outline-offset: 1px;
} }
@@ -299,11 +299,11 @@ defineExpose({ expandAll, collapseAll, resetExpansion, refresh });
.json-tree-index, .json-tree-index,
.json-tree-punctuation, .json-tree-punctuation,
.json-tree-summary { .json-tree-summary {
color: hsl(var(--muted-foreground)); color: var(--muted-foreground);
} }
.json-tree-bracket { .json-tree-bracket {
color: hsl(var(--foreground)); color: var(--foreground);
font-weight: 650; font-weight: 650;
} }
@@ -1811,8 +1811,8 @@ watch(driverStoreTab, (tab) => {
<style> <style>
.driver-store-focus-highlight { .driver-store-focus-highlight {
background-color: hsl(var(--primary) / 0.08); background-color: color-mix(in srgb, var(--primary) 8%, transparent);
box-shadow: inset 0 0 0 1.5px hsl(var(--primary) / 0.45); box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--primary) 45%, transparent);
border-radius: var(--dbx-radius-fixed-6); border-radius: var(--dbx-radius-fixed-6);
} }
@@ -4708,7 +4708,7 @@ function openExternalUrl(url: string) {
:key="opt.value" :key="opt.value"
type="button" type="button"
class="connection-db-picker-option group flex min-h-24 flex-col items-center justify-center gap-2 rounded-[4px] border bg-background/70 p-3 text-center transition hover:border-primary/40 hover:bg-muted/40 hover:shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" class="connection-db-picker-option group flex min-h-24 flex-col items-center justify-center gap-2 rounded-[4px] border bg-background/70 p-3 text-center transition hover:border-primary/40 hover:bg-muted/40 hover:shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
:class="selectedType === opt.value ? 'connection-db-picker-option--selected shadow-sm' : 'border-border'" :class="selectedType === opt.value ? 'dbx-tile-selected shadow-sm' : 'border-border'"
:aria-pressed="selectedType === opt.value" :aria-pressed="selectedType === opt.value"
@click="onDbTypeChange(opt.value)" @click="onDbTypeChange(opt.value)"
@dblclick="goToConnectionStep(opt.value)" @dblclick="goToConnectionStep(opt.value)"
@@ -4726,7 +4726,7 @@ function openExternalUrl(url: string) {
:key="opt.value" :key="opt.value"
type="button" type="button"
class="connection-db-picker-option flex items-center gap-3 rounded-[4px] border bg-background px-3 py-2 text-left transition hover:border-primary/40 hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" class="connection-db-picker-option flex items-center gap-3 rounded-[4px] border bg-background px-3 py-2 text-left transition hover:border-primary/40 hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
:class="selectedType === opt.value ? 'connection-db-picker-option--selected' : 'border-border'" :class="selectedType === opt.value ? 'dbx-tile-selected' : 'border-border'"
:aria-pressed="selectedType === opt.value" :aria-pressed="selectedType === opt.value"
@click="onDbTypeChange(opt.value)" @click="onDbTypeChange(opt.value)"
@dblclick="goToConnectionStep(opt.value)" @dblclick="goToConnectionStep(opt.value)"
@@ -7133,32 +7133,10 @@ function openExternalUrl(url: string) {
color: var(--foreground); color: var(--foreground);
} }
.connection-db-picker-option--selected {
border-color: rgb(23, 23, 23);
background-color: rgba(23, 23, 23, 0.08);
box-shadow: 0 0 0 1px rgba(23, 23, 23, 0.24);
color: rgb(10, 10, 10);
}
.connection-db-picker-option--selected:hover {
background-color: rgba(23, 23, 23, 0.12);
}
.connection-config-step :is([data-slot="input"], [data-slot="select-trigger"], [data-slot="tabs-list"], [data-slot="tabs-trigger"], textarea) { .connection-config-step :is([data-slot="input"], [data-slot="select-trigger"], [data-slot="tabs-list"], [data-slot="tabs-trigger"], textarea) {
border-radius: var(--dbx-radius-fixed-4, 4px); border-radius: var(--dbx-radius-fixed-4, 4px);
} }
.dark .connection-db-picker-option--selected {
border-color: rgb(208, 208, 214);
background-color: rgba(255, 255, 255, 0.08);
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.22);
color: rgb(221, 221, 226);
}
.dark .connection-db-picker-option--selected:hover {
background-color: rgba(255, 255, 255, 0.12);
}
.connection-dialog-content[data-wide="true"] .grid.grid-cols-4 { .connection-dialog-content[data-wide="true"] .grid.grid-cols-4 {
grid-template-columns: minmax(5.5rem, 0.7fr) repeat(3, minmax(0, 1fr)); grid-template-columns: minmax(5.5rem, 0.7fr) repeat(3, minmax(0, 1fr));
} }
@@ -379,12 +379,12 @@ function copyDeploySqlAll() {
<style scoped> <style scoped>
:deep(.splitpanes--vertical > .splitpanes__splitter) { :deep(.splitpanes--vertical > .splitpanes__splitter) {
background-color: hsl(var(--border)); background-color: var(--border);
width: 4px; width: 4px;
cursor: col-resize; cursor: col-resize;
position: relative; position: relative;
} }
:deep(.splitpanes--vertical > .splitpanes__splitter:hover) { :deep(.splitpanes--vertical > .splitpanes__splitter:hover) {
background-color: hsl(var(--primary)); background-color: var(--primary);
} }
</style> </style>
@@ -335,10 +335,10 @@ function getObjectIconColor(kind: DiffObjectKind): string {
<style scoped> <style scoped>
:deep(.splitpanes--vertical > .splitpanes__splitter) { :deep(.splitpanes--vertical > .splitpanes__splitter) {
width: 4px; width: 4px;
background: hsl(var(--border)); background: var(--border);
cursor: col-resize; cursor: col-resize;
} }
:deep(.splitpanes--vertical > .splitpanes__splitter:hover) { :deep(.splitpanes--vertical > .splitpanes__splitter:hover) {
background: hsl(var(--primary)); background: var(--primary);
} }
</style> </style>
@@ -887,10 +887,10 @@ const targetConnectionInfo = computed(() => {
<style scoped> <style scoped>
:deep(.splitpanes--horizontal > .splitpanes__splitter) { :deep(.splitpanes--horizontal > .splitpanes__splitter) {
height: 8px; height: 8px;
background: hsl(var(--border)); background: var(--border);
cursor: row-resize; cursor: row-resize;
} }
:deep(.splitpanes--horizontal > .splitpanes__splitter:hover) { :deep(.splitpanes--horizontal > .splitpanes__splitter:hover) {
background: hsl(var(--primary)); background: var(--primary);
} }
</style> </style>
@@ -2424,24 +2424,24 @@ async function openExternalUrl(url: string) {
font-weight: 600; font-weight: 600;
} }
.ai-markdown :deep(a) { .ai-markdown :deep(a) {
color: hsl(var(--primary)); color: var(--primary);
text-decoration: underline; text-decoration: underline;
} }
.ai-markdown :deep(blockquote) { .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; padding-left: 0.75em;
margin: 0.3em 0; margin: 0.3em 0;
color: hsl(var(--muted-foreground)); color: var(--muted-foreground);
} }
.ai-markdown :deep(code) { .ai-markdown :deep(code) {
border-radius: 0.25rem; border-radius: 0.25rem;
background: hsl(var(--muted)); background: var(--muted);
padding: 0.125rem 0.375rem; padding: 0.125rem 0.375rem;
font-size: 11px; font-size: 11px;
font-family: ui-monospace, monospace; font-family: ui-monospace, monospace;
} }
.ai-markdown :deep(pre) { .ai-markdown :deep(pre) {
background: hsl(var(--muted)); background: var(--muted);
border-radius: 0.375rem; border-radius: 0.375rem;
padding: 0.5em 0.75em; padding: 0.5em 0.75em;
margin: 0.3em 0; margin: 0.3em 0;
@@ -2464,7 +2464,7 @@ async function openExternalUrl(url: string) {
max-width: 100%; max-width: 100%;
margin: 0.3em 0; margin: 0.3em 0;
border-radius: 0.375rem; border-radius: 0.375rem;
border: 1px solid hsl(var(--border)); border: 1px solid var(--border);
} }
.ai-markdown :deep(.ai-markdown-table-wrap table) { .ai-markdown :deep(.ai-markdown-table-wrap table) {
border: none; border: none;
@@ -2472,14 +2472,14 @@ async function openExternalUrl(url: string) {
} }
.ai-markdown :deep(th), .ai-markdown :deep(th),
.ai-markdown :deep(td) { .ai-markdown :deep(td) {
border: 1px solid hsl(var(--border)); border: 1px solid var(--border);
padding: 0.25em 0.5em; padding: 0.25em 0.5em;
text-align: left; text-align: left;
white-space: nowrap; white-space: nowrap;
} }
.ai-markdown :deep(th) { .ai-markdown :deep(th) {
font-weight: 600; font-weight: 600;
background: hsl(var(--muted)); background: var(--muted);
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 1; z-index: 1;
@@ -2496,11 +2496,11 @@ async function openExternalUrl(url: string) {
height: 4px; height: 4px;
width: 100%; width: 100%;
cursor: ns-resize; cursor: ns-resize;
background-color: hsl(var(--border)); background-color: var(--border);
transition: background-color 0.15s ease; transition: background-color 0.15s ease;
} }
.resize-handle:hover { .resize-handle:hover {
background-color: hsl(var(--foreground) / 0.2); background-color: color-mix(in srgb, var(--foreground) 20%, transparent);
} }
</style> </style>
@@ -491,13 +491,13 @@ defineExpose({
} }
.editor-search-panel--editor { .editor-search-panel--editor {
background: #f6f7f9; background: var(--popover);
border-color: #d8dce3; border-color: var(--border);
border-radius: var(--dbx-radius-fixed-6); border-radius: var(--dbx-radius-fixed-6);
box-shadow: box-shadow:
0 8px 22px rgb(15 23 42 / 0.14), 0 8px 22px color-mix(in srgb, var(--foreground) 14%, transparent),
0 1px 0 rgb(255 255 255 / 0.78) inset; 0 1px 0 color-mix(in srgb, var(--background) 78%, transparent) inset;
color: #20242a; color: var(--foreground);
gap: 3px; gap: 3px;
max-width: min(calc(100vw - 2rem), 500px); max-width: min(calc(100vw - 2rem), 500px);
padding: 4px 6px; padding: 4px 6px;
@@ -535,96 +535,46 @@ defineExpose({
} }
.editor-search-panel--editor :deep(.border-input) { .editor-search-panel--editor :deep(.border-input) {
background: #ffffff; background: var(--background);
border-color: #c7ccd5; border-color: var(--input);
border-radius: var(--dbx-radius-fixed-5); 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) { .editor-search-panel--editor :deep(.focus-within\:border-ring:focus-within) {
border-color: #3b82f6; border-color: var(--ring);
box-shadow: 0 0 0 1px #3b82f6; box-shadow: 0 0 0 1px var(--ring);
} }
.editor-search-panel--editor :deep(input) { .editor-search-panel--editor :deep(input) {
color: #20242a; color: var(--foreground);
font-size: 12px; font-size: 12px;
} }
.editor-search-panel--editor :deep(input::placeholder) { .editor-search-panel--editor :deep(input::placeholder) {
color: #7a828e; color: var(--muted-foreground);
} }
.editor-search-panel--editor :deep(.text-muted-foreground) { .editor-search-panel--editor :deep(.text-muted-foreground) {
color: #6e7681; color: var(--muted-foreground);
} }
.editor-search-panel--editor :deep(.text-destructive) { .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(.hover\:bg-accent:hover),
.editor-search-panel--editor :deep(.bg-accent) { .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(.hover\:text-foreground:hover),
.editor-search-panel--editor :deep(.text-foreground) { .editor-search-panel--editor :deep(.text-foreground) {
color: #1f2329; color: var(--foreground);
} }
.editor-search-panel--editor :deep(button.border-border) { .editor-search-panel--editor :deep(button.border-border) {
border-color: #d0d5dd; border-color: var(--border);
}
: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;
} }
@media (max-width: 720px) { @media (max-width: 720px) {
@@ -392,10 +392,10 @@ const systemFontsLoading = ref(false);
const systemFontsLoaded = 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 uiScaleOptions = [0.75, 0.9, 0.95, 1, 1.05, 1.1, 1.15, 1.2, 1.25, 1.5, 1.75];
const fontSearchTriggerClass = 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"; "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} gap-0 pl-2 pr-1.5`; const appearanceFontSearchTriggerClass = fontSearchTriggerClass;
const fontSearchTriggerIconClass = "size-4 text-muted-foreground"; 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(() => { const disconnectTabHandlingModeDescriptionKey = computed(() => {
switch (editDisconnectTabHandlingMode.value) { switch (editDisconnectTabHandlingMode.value) {
case "close-tabs": case "close-tabs":
@@ -3630,8 +3630,8 @@ onUnmounted(cleanupPreviewEditor);
allow-custom allow-custom
:display-name="displayFontFamily" :display-name="displayFontFamily"
:normalize-custom="normalizeCustomFontFamilyInput" :normalize-custom="normalizeCustomFontFamilyInput"
trigger-variant="outline" :trigger-class="appearanceFontSearchTriggerClass"
trigger-class="h-9 w-full max-w-none justify-between" :trigger-icon-class="appearanceFontSearchTriggerIconClass"
content-class="w-[var(--reka-popover-trigger-width)] min-w-[260px]" content-class="w-[var(--reka-popover-trigger-width)] min-w-[260px]"
@update:model-value="onTableFontFamilyChange" @update:model-value="onTableFontFamilyChange"
@update:open="(open: boolean) => open && loadSystemFontOptions()" @update:open="(open: boolean) => open && loadSystemFontOptions()"
@@ -3657,16 +3657,7 @@ onUnmounted(cleanupPreviewEditor);
<div class="settings-appearance-group min-w-0"> <div class="settings-appearance-group min-w-0">
<Label>{{ t("settings.theme") }}</Label> <Label>{{ t("settings.theme") }}</Label>
<div class="settings-appearance-button-row flex flex-wrap gap-2"> <div class="settings-appearance-button-row flex flex-wrap gap-2">
<Button <Button v-for="option in appThemeModeOptions" :key="option.value" type="button" variant="outline" size="sm" class="settings-choice-button h-8 gap-1.5 px-3" :class="themeMode === option.value ? 'dbx-choice-selected' : 'text-foreground'" @click="setThemeMode(option.value)">
v-for="option in appThemeModeOptions"
:key="option.value"
type="button"
variant="outline"
size="sm"
class="settings-choice-button h-8 gap-1.5 px-3"
:class="themeMode === option.value ? 'settings-choice-button--selected border-primary/40 bg-primary/10 text-primary ring-1 ring-primary/30' : 'text-foreground'"
@click="setThemeMode(option.value)"
>
<component :is="option.icon" class="h-3.5 w-3.5" /> <component :is="option.icon" class="h-3.5 w-3.5" />
{{ option.label }} {{ option.label }}
</Button> </Button>
@@ -3683,7 +3674,7 @@ onUnmounted(cleanupPreviewEditor);
variant="outline" variant="outline"
size="sm" size="sm"
class="settings-choice-button h-8 px-3" 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 }" :style="{ borderRadius: option.previewRadius }"
@click="setCornerStyle(option.value)" @click="setCornerStyle(option.value)"
> >
@@ -3698,7 +3689,7 @@ onUnmounted(cleanupPreviewEditor);
<div class="settings-appearance-group"> <div class="settings-appearance-group">
<Label>{{ t("settings.appLayout") }}</Label> <Label>{{ t("settings.appLayout") }}</Label>
<div class="settings-appearance-choice-grid"> <div class="settings-appearance-choice-grid">
<Button type="button" variant="outline" class="settings-choice-card h-auto justify-start border p-3" :class="editAppLayout === 'separated' ? 'settings-choice-card--selected border-blue-300 ring-2 ring-blue-300/50' : ''" @click="setAppLayout('separated')"> <Button type="button" variant="outline" class="settings-choice-card h-auto justify-start border p-3" :class="editAppLayout === 'separated' ? 'dbx-choice-selected' : ''" @click="setAppLayout('separated')">
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger as-child> <TooltipTrigger as-child>
@@ -3713,7 +3704,7 @@ onUnmounted(cleanupPreviewEditor);
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
</Button> </Button>
<Button type="button" variant="outline" class="settings-choice-card h-auto justify-start border p-3" :class="editAppLayout === 'classic' ? 'settings-choice-card--selected border-blue-300 ring-2 ring-blue-300/50' : ''" @click="setAppLayout('classic')"> <Button type="button" variant="outline" class="settings-choice-card h-auto justify-start border p-3" :class="editAppLayout === 'classic' ? 'dbx-choice-selected' : ''" @click="setAppLayout('classic')">
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger as-child> <TooltipTrigger as-child>
@@ -3736,13 +3727,13 @@ onUnmounted(cleanupPreviewEditor);
<div class="settings-appearance-group"> <div class="settings-appearance-group">
<Label>{{ t("settings.tabLayout") }}</Label> <Label>{{ t("settings.tabLayout") }}</Label>
<div class="settings-appearance-choice-grid"> <div class="settings-appearance-choice-grid">
<Button type="button" variant="outline" class="settings-choice-card h-auto justify-start border p-3" :class="editTabLayout === 'scroll' ? 'settings-choice-card--selected border-blue-300 ring-2 ring-blue-300/50' : ''" @click="setTabLayout('scroll')"> <Button type="button" variant="outline" class="settings-choice-card h-auto justify-start border p-3" :class="editTabLayout === 'scroll' ? 'dbx-choice-selected' : ''" @click="setTabLayout('scroll')">
<div class="w-full min-w-0 text-left"> <div class="w-full min-w-0 text-left">
<div class="text-sm font-medium">{{ t("settings.tabLayoutScroll") }}</div> <div class="text-sm font-medium">{{ t("settings.tabLayoutScroll") }}</div>
<div class="text-xs text-muted-foreground">{{ t("settings.tabLayoutScrollDescription") }}</div> <div class="text-xs text-muted-foreground">{{ t("settings.tabLayoutScrollDescription") }}</div>
</div> </div>
</Button> </Button>
<Button type="button" variant="outline" class="settings-choice-card h-auto justify-start border p-3" :class="editTabLayout === 'wrap' ? 'settings-choice-card--selected border-blue-300 ring-2 ring-blue-300/50' : ''" @click="setTabLayout('wrap')"> <Button type="button" variant="outline" class="settings-choice-card h-auto justify-start border p-3" :class="editTabLayout === 'wrap' ? 'dbx-choice-selected' : ''" @click="setTabLayout('wrap')">
<div class="w-full min-w-0 text-left"> <div class="w-full min-w-0 text-left">
<div class="text-sm font-medium">{{ t("settings.tabLayoutWrap") }}</div> <div class="text-sm font-medium">{{ t("settings.tabLayoutWrap") }}</div>
<div class="text-xs text-muted-foreground">{{ t("settings.tabLayoutWrapDescription") }}</div> <div class="text-xs text-muted-foreground">{{ t("settings.tabLayoutWrapDescription") }}</div>
@@ -3755,7 +3746,7 @@ onUnmounted(cleanupPreviewEditor);
<div class="settings-appearance-group"> <div class="settings-appearance-group">
<Label>{{ t("settings.iconTheme") }}</Label> <Label>{{ t("settings.iconTheme") }}</Label>
<div class="settings-appearance-choice-grid"> <div class="settings-appearance-choice-grid">
<Button type="button" variant="outline" class="settings-choice-card h-auto justify-start border p-3" :class="editIconTheme === 'default' ? 'settings-choice-card--selected border-blue-300 ring-2 ring-blue-300/50' : ''" @click="setIconTheme('default')"> <Button type="button" variant="outline" class="settings-choice-card h-auto justify-start border p-3" :class="editIconTheme === 'default' ? 'dbx-choice-selected' : ''" @click="setIconTheme('default')">
<div class="flex items-center gap-3 text-left w-full min-w-0"> <div class="flex items-center gap-3 text-left w-full min-w-0">
<img src="/icon-preview-default.png" alt="DBX" class="h-12 w-12 shrink-0" /> <img src="/icon-preview-default.png" alt="DBX" class="h-12 w-12 shrink-0" />
<TooltipProvider> <TooltipProvider>
@@ -3773,7 +3764,7 @@ onUnmounted(cleanupPreviewEditor);
</TooltipProvider> </TooltipProvider>
</div> </div>
</Button> </Button>
<Button type="button" variant="outline" class="settings-choice-card h-auto justify-start border p-3" :class="editIconTheme === 'black' ? 'settings-choice-card--selected border-blue-300 ring-2 ring-blue-300/50' : ''" @click="setIconTheme('black')"> <Button type="button" variant="outline" class="settings-choice-card h-auto justify-start border p-3" :class="editIconTheme === 'black' ? 'dbx-choice-selected' : ''" @click="setIconTheme('black')">
<div class="flex items-center gap-3 text-left w-full min-w-0"> <div class="flex items-center gap-3 text-left w-full min-w-0">
<img src="/icon-preview-black.png" alt="DBX" class="h-12 w-12 shrink-0" /> <img src="/icon-preview-black.png" alt="DBX" class="h-12 w-12 shrink-0" />
<TooltipProvider> <TooltipProvider>
@@ -3991,7 +3982,7 @@ onUnmounted(cleanupPreviewEditor);
<div class="space-y-2"> <div class="space-y-2">
<Label>{{ t("settings.sidebarActivation") }}</Label> <Label>{{ t("settings.sidebarActivation") }}</Label>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
<Button type="button" variant="outline" class="h-auto justify-start border p-3" :class="editSidebarActivation === 'single' ? 'border-blue-300 ring-2 ring-blue-300/50' : ''" @click="setSidebarActivation('single')"> <Button type="button" variant="outline" class="h-auto justify-start border p-3" :class="editSidebarActivation === 'single' ? 'dbx-choice-selected' : ''" @click="setSidebarActivation('single')">
<div class="text-left"> <div class="text-left">
<div class="text-sm font-medium">{{ t("settings.sidebarActivationSingle") }}</div> <div class="text-sm font-medium">{{ t("settings.sidebarActivationSingle") }}</div>
<div class="text-xs text-muted-foreground"> <div class="text-xs text-muted-foreground">
@@ -3999,7 +3990,7 @@ onUnmounted(cleanupPreviewEditor);
</div> </div>
</div> </div>
</Button> </Button>
<Button type="button" variant="outline" class="h-auto justify-start border p-3" :class="editSidebarActivation === 'double' ? 'border-blue-300 ring-2 ring-blue-300/50' : ''" @click="setSidebarActivation('double')"> <Button type="button" variant="outline" class="h-auto justify-start border p-3" :class="editSidebarActivation === 'double' ? 'dbx-choice-selected' : ''" @click="setSidebarActivation('double')">
<div class="text-left"> <div class="text-left">
<div class="text-sm font-medium">{{ t("settings.sidebarActivationDouble") }}</div> <div class="text-sm font-medium">{{ t("settings.sidebarActivationDouble") }}</div>
<div class="text-xs text-muted-foreground"> <div class="text-xs text-muted-foreground">
@@ -4021,7 +4012,7 @@ onUnmounted(cleanupPreviewEditor);
<div class="space-y-2"> <div class="space-y-2">
<Label>{{ t("settings.sidebarObjectDisplay") }}</Label> <Label>{{ t("settings.sidebarObjectDisplay") }}</Label>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
<Button type="button" variant="outline" class="h-auto justify-start border p-3" :class="editSidebarObjectDisplay === 'grouped' ? 'border-blue-300 ring-2 ring-blue-300/50' : ''" @click="setSidebarObjectDisplay('grouped')"> <Button type="button" variant="outline" class="h-auto justify-start border p-3" :class="editSidebarObjectDisplay === 'grouped' ? 'dbx-choice-selected' : ''" @click="setSidebarObjectDisplay('grouped')">
<div class="text-left"> <div class="text-left">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="text-sm font-medium">{{ t("settings.sidebarObjectDisplayGrouped") }}</div> <div class="text-sm font-medium">{{ t("settings.sidebarObjectDisplayGrouped") }}</div>
@@ -4038,7 +4029,7 @@ onUnmounted(cleanupPreviewEditor);
</div> </div>
</div> </div>
</Button> </Button>
<Button type="button" variant="outline" class="h-auto justify-start border p-3" :class="editSidebarObjectDisplay === 'simple' ? 'border-blue-300 ring-2 ring-blue-300/50' : ''" @click="setSidebarObjectDisplay('simple')"> <Button type="button" variant="outline" class="h-auto justify-start border p-3" :class="editSidebarObjectDisplay === 'simple' ? 'dbx-choice-selected' : ''" @click="setSidebarObjectDisplay('simple')">
<div class="text-left"> <div class="text-left">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="text-sm font-medium">{{ t("settings.sidebarObjectDisplaySimple") }}</div> <div class="text-sm font-medium">{{ t("settings.sidebarObjectDisplaySimple") }}</div>
@@ -5171,9 +5162,9 @@ onUnmounted(cleanupPreviewEditor);
<div v-if="aiSupportsApiStyle" class="grid grid-cols-3 items-center gap-3"> <div v-if="aiSupportsApiStyle" class="grid grid-cols-3 items-center gap-3">
<Label class="text-right text-xs">API</Label> <Label class="text-right text-xs">API</Label>
<div class="col-span-2 flex gap-2"> <div class="col-span-2 flex gap-2">
<Button size="sm" variant="outline" class="h-8 flex-1 text-xs" :class="{ 'border-blue-300 border-2 ring-2 ring-blue-300/50': aiEditApiStyle === 'completions' }" @click="aiSelectApiStyle('completions')">/chat/completions</Button> <Button size="sm" variant="outline" class="h-8 flex-1 text-xs" :class="{ 'dbx-choice-selected': aiEditApiStyle === 'completions' }" @click="aiSelectApiStyle('completions')">/chat/completions</Button>
<Button size="sm" variant="outline" class="h-8 flex-1 text-xs" :class="{ 'border-blue-300 border-2 ring-2 ring-blue-300/50': aiEditApiStyle === 'responses' }" @click="aiSelectApiStyle('responses')">/responses</Button> <Button size="sm" variant="outline" class="h-8 flex-1 text-xs" :class="{ 'dbx-choice-selected': aiEditApiStyle === 'responses' }" @click="aiSelectApiStyle('responses')">/responses</Button>
<Button v-if="aiSupportsAnthropicApiStyle" size="sm" variant="outline" class="h-8 flex-1 text-xs" :class="{ 'border-blue-300 border-2 ring-2 ring-blue-300/50': aiEditApiStyle === 'anthropic-messages' }" @click="aiSelectApiStyle('anthropic-messages')">/messages</Button> <Button v-if="aiSupportsAnthropicApiStyle" size="sm" variant="outline" class="h-8 flex-1 text-xs" :class="{ 'dbx-choice-selected': aiEditApiStyle === 'anthropic-messages' }" @click="aiSelectApiStyle('anthropic-messages')">/messages</Button>
</div> </div>
</div> </div>
@@ -5320,7 +5311,7 @@ onUnmounted(cleanupPreviewEditor);
:tabindex="mcpExecutionMode === 'read_only' ? 0 : -1" :tabindex="mcpExecutionMode === 'read_only' ? 0 : -1"
variant="outline" variant="outline"
class="settings-choice-card h-auto justify-center border p-3" 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')" @click="onMcpExecutionModeChange('read_only')"
@keydown="onMcpExecutionModeKeydown($event, 'read_only')" @keydown="onMcpExecutionModeKeydown($event, 'read_only')"
> >
@@ -5335,7 +5326,7 @@ onUnmounted(cleanupPreviewEditor);
:tabindex="mcpExecutionMode === 'safe_write' ? 0 : -1" :tabindex="mcpExecutionMode === 'safe_write' ? 0 : -1"
variant="outline" variant="outline"
class="settings-choice-card h-auto justify-center border p-3" 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')" @click="onMcpExecutionModeChange('safe_write')"
@keydown="onMcpExecutionModeKeydown($event, 'safe_write')" @keydown="onMcpExecutionModeKeydown($event, 'safe_write')"
> >
@@ -5351,7 +5342,7 @@ onUnmounted(cleanupPreviewEditor);
:tabindex="mcpExecutionMode === 'high_risk_write' ? 0 : -1" :tabindex="mcpExecutionMode === 'high_risk_write' ? 0 : -1"
variant="outline" variant="outline"
class="settings-choice-card h-auto justify-center border p-3" 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')" @click="onMcpExecutionModeChange('high_risk_write')"
@keydown="onMcpExecutionModeKeydown($event, '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 { .settings-appearance-top-grid {
display: grid; display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -5928,27 +5891,4 @@ onUnmounted(cleanupPreviewEditor);
grid-template-columns: 1fr; 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;
}
</style> </style>
@@ -3307,7 +3307,7 @@ onMounted(async () => {
textUnderlineOffset: "3px", textUnderlineOffset: "3px",
}, },
".cm-sql-semantic-warning": { ".cm-sql-semantic-warning": {
textDecoration: "underline wavy hsl(var(--warning, 38 92% 50%))", textDecoration: "underline wavy var(--warning)",
textUnderlineOffset: "3px", textUnderlineOffset: "3px",
}, },
}); });
@@ -394,24 +394,24 @@ async function initJsonEditor() {
minHeight: "260px", minHeight: "260px",
maxHeight: "360px", maxHeight: "360px",
fontSize: "12px", fontSize: "12px",
border: "1px solid hsl(var(--border))", border: "1px solid var(--border)",
borderRadius: "var(--radius-md)", borderRadius: "var(--radius-md)",
backgroundColor: "hsl(var(--background))", backgroundColor: "var(--background)",
color: "hsl(var(--foreground))", color: "var(--foreground)",
}, },
".cm-scroller": { ".cm-scroller": {
fontFamily: "var(--font-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace", fontFamily: "var(--font-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
}, },
".cm-gutters": { ".cm-gutters": {
backgroundColor: "hsl(var(--muted) / 0.35)", backgroundColor: "color-mix(in srgb, var(--muted) 35%, transparent)",
color: "hsl(var(--muted-foreground))", color: "var(--muted-foreground)",
borderRight: "1px solid hsl(var(--border))", borderRight: "1px solid var(--border)",
}, },
".cm-activeLine, .cm-activeLineGutter": { ".cm-activeLine, .cm-activeLineGutter": {
backgroundColor: "hsl(var(--muted) / 0.45)", backgroundColor: "color-mix(in srgb, var(--muted) 45%, transparent)",
}, },
".cm-focused": { ".cm-focused": {
outline: "2px solid hsl(var(--ring) / 0.35)", outline: "2px solid color-mix(in srgb, var(--ring) 35%, transparent)",
outlineOffset: "1px", outlineOffset: "1px",
}, },
}), }),
@@ -564,10 +564,10 @@ onBeforeUnmount(() => {
<div class="space-y-2"> <div class="space-y-2">
<Label>{{ t("settings.sqlFormatterIndent") }}</Label> <Label>{{ t("settings.sqlFormatterIndent") }}</Label>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
<Button type="button" variant="outline" class="justify-center" :class="!settings.useTabs ? 'border-blue-300 ring-2 ring-blue-300/50' : ''" @click="updateOption('useTabs', false)"> <Button type="button" variant="outline" class="justify-center" :class="!settings.useTabs ? 'dbx-choice-selected' : ''" @click="updateOption('useTabs', false)">
{{ t("settings.sqlFormatterIndentSpaces") }} {{ t("settings.sqlFormatterIndentSpaces") }}
</Button> </Button>
<Button type="button" variant="outline" class="justify-center" :class="settings.useTabs ? 'border-blue-300 ring-2 ring-blue-300/50' : ''" @click="updateOption('useTabs', true)"> <Button type="button" variant="outline" class="justify-center" :class="settings.useTabs ? 'dbx-choice-selected' : ''" @click="updateOption('useTabs', true)">
{{ t("settings.sqlFormatterIndentTabs") }} {{ t("settings.sqlFormatterIndentTabs") }}
</Button> </Button>
</div> </div>
+83 -175
View File
@@ -71,7 +71,6 @@ import { tableInfoTabForDrawerToggle } from "@/lib/table/tableInfoTabPreference"
import * as api from "@/lib/backend/api"; import * as api from "@/lib/backend/api";
import { formatElapsedSeconds } from "@/lib/common/elapsedTime"; import { formatElapsedSeconds } from "@/lib/common/elapsedTime";
import { dataGridCellDisplayText, dataGridCellEditorText } from "@/lib/dataGrid/dataGridCellCoercion"; import { dataGridCellDisplayText, dataGridCellEditorText } from "@/lib/dataGrid/dataGridCellCoercion";
import { dataGridActiveRowBackground } from "@/lib/dataGrid/dataGridPaintTheme";
import { createColumnDrafts } from "@/lib/table/tableStructureEditorState"; import { createColumnDrafts } from "@/lib/table/tableStructureEditorState";
import type { BuildSingleColumnAlterSqlOptions } from "@/lib/table/tableStructureEditorSql"; import type { BuildSingleColumnAlterSqlOptions } from "@/lib/table/tableStructureEditorSql";
import { buildTableSelectSql, quoteTableDataIdentifier } from "@/lib/table/tableSelectSql"; import { buildTableSelectSql, quoteTableDataIdentifier } from "@/lib/table/tableSelectSql";
@@ -139,7 +138,7 @@ import { canGoNextDataGridPage, hasCompleteLocalDataGridResult, resolveDataGridP
import { dataGridCountQueryOptions } from "@/lib/dataGrid/dataGridQueryOptions"; import { dataGridCountQueryOptions } from "@/lib/dataGrid/dataGridQueryOptions";
import { dataGridBottomScrollTop, dataGridScrollPosition, isDataGridAtScrollBottom, isDataGridNearScrollBottom, shouldCheckInfiniteScrollAfterScroll, type DataGridScrollPosition } from "@/lib/dataGrid/dataGridInfiniteScroll"; 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 { 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 { createRowLowerTextCache } from "@/lib/dataGrid/dataGridRowLowerText";
import { dataGridPreviewLabelKey, dataGridSaveActionMode, dataGridSaveToolbarState } from "@/lib/dataGrid/dataGridSaveUi"; import { dataGridPreviewLabelKey, dataGridSaveActionMode, dataGridSaveToolbarState } from "@/lib/dataGrid/dataGridSaveUi";
import type { QueryEditabilityReason } from "@/lib/sql/sqlAnalysis"; import type { QueryEditabilityReason } from "@/lib/sql/sqlAnalysis";
@@ -4374,16 +4373,16 @@ function isNull(value: unknown): boolean {
function rowNumberStatusClass(item: RowItem): string { function rowNumberStatusClass(item: RowItem): string {
if (item.status === "draft") { 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") { 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") { 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") { 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"; return "text-muted-foreground";
} }
@@ -4394,53 +4393,27 @@ function rowCellsUseSelectionVisual(rowId: number): boolean {
function dataGridRowStyle(item: RowItem): CSSProperties { function dataGridRowStyle(item: RowItem): CSSProperties {
const dark = isDark.value || (typeof document !== "undefined" && document.documentElement.classList.contains("dark")); const dark = isDark.value || (typeof document !== "undefined" && document.documentElement.classList.contains("dark"));
const activeRowBg = dataGridActiveRowBackground(dark); const deletedBg = "var(--color-error-bg, color-mix(in srgb, var(--destructive) 12%, transparent))";
const rowBg = item.isDeleted const newBg = "var(--success-bg, color-mix(in srgb, var(--success) 12%, transparent))";
? dark const editedBg = "var(--warning-bg, color-mix(in srgb, var(--warning) 14%, transparent))";
? "rgb(55, 31, 32)" const activeBg = `var(--data-grid-cell-active-bg, ${dataGridActiveRowBackground(dark)})`;
: "rgb(255, 244, 244)" const resolvedRowBg = item.isDeleted
? deletedBg
: isRowActive(item.displayIndex) : isRowActive(item.displayIndex)
? activeRowBg ? activeBg
: item.isNew : item.isNew || item.isDraft
? dark ? "color-mix(in srgb, var(--muted) 80%, var(--background))"
? "rgb(51, 51, 55)" : item.displayIndex % 2 === 1
: "rgb(243, 243, 243)" ? `var(--data-grid-row-muted-bg, ${dark ? DATA_GRID_DARK_STRIPED_ROW_BG : DATA_GRID_LIGHT_STRIPED_ROW_BG})`
: item.isDraft : "var(--background)";
? dark 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)";
? "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)";
return { return {
"--data-grid-cell-bg": rowBg, "--data-grid-cell-bg": resolvedRowBg,
"--data-grid-row-number-bg": rowNumberBg, "--data-grid-row-number-bg": rowNumberBg,
"--data-grid-cell-selected-bg": dark ? "rgb(20, 40, 60)" : "rgb(239, 246, 255)", // Keep selection fill/border from [data-grid-root] tokens (primary/ring mix).
"--data-grid-cell-selected-single-bg": dark ? "rgb(30, 64, 96)" : "rgb(191, 219, 254)", // Do not flatten them to --accent/--border — that erases outline contrast.
"--data-grid-cell-selected-dirty-bg": dark ? "rgb(76, 66, 38)" : "rgb(235, 224, 184)", "--data-grid-cell-selected-dirty-bg": editedBg,
"--data-grid-cell-selected-border": dark ? "rgb(96, 165, 250)" : "rgb(59, 130, 246)", "--data-grid-row-number-active-bg": activeBg,
"--data-grid-row-number-active-bg": activeRowBg,
"--data-grid-row-number-selected-bg": dark ? "rgb(30, 64, 96)" : "rgb(191, 219, 254)",
} as CSSProperties; } as CSSProperties;
} }
@@ -7989,8 +7962,8 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
<div <div
class="sticky left-0 z-10 flex shrink-0 items-center border-r border-border bg-background px-3 py-0 font-medium truncate" class="sticky left-0 z-10 flex shrink-0 items-center border-r border-border bg-background px-3 py-0 font-medium truncate"
:class="{ :class="{
'bg-yellow-200/60 dark:bg-yellow-500/20': transposeHeaderIsSearchMatch(visibleColumnIndexes[index]), 'cell-search-match': transposeHeaderIsSearchMatch(visibleColumnIndexes[index]),
'ring-2 ring-inset ring-yellow-500 bg-yellow-300/60 dark:bg-yellow-500/40': transposeHeaderIsCurrentMatch(visibleColumnIndexes[index]), 'cell-current-search-match': transposeHeaderIsCurrentMatch(visibleColumnIndexes[index]),
}" }"
:style="{ width: `${transposePinnedWidth}px` }" :style="{ width: `${transposePinnedWidth}px` }"
:title="item.column" :title="item.column"
@@ -8009,11 +7982,11 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
'row-cell-selected': transposeRecordUsesSelectionVisual(cell.recordIndex) && !transposeCellIsSelected(cell.recordIndex, cell.valueIndex) && !displayItems[cell.recordIndex]?.isDirtyCol[cell.valueIndex], '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], '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-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], 'cell-dirty': displayItems[cell.recordIndex]?.isDirtyCol[cell.valueIndex],
'bg-yellow-200/60 dark:bg-yellow-500/20': cellIsSearchMatch(cell.recordIndex, cell.valueIndex), 'cell-search-match': 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-current-search-match': cellIsCurrentMatch(cell.recordIndex, cell.valueIndex),
'cursor-text': !isScrolling && canEditCellItem(displayItems[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), !isScrolling && canEditCellItem(displayItems[cell.recordIndex], cell.valueIndex) && !transposeRecordUsesSelectionVisual(cell.recordIndex) && !transposeRecordUsesActiveHighlight(cell.recordIndex) && !transposeCellIsSelected(cell.recordIndex, cell.valueIndex),
}" }"
:style="{ width: `${getTransposeRecordWidth(cell.recordIndex)}px` }" :style="{ width: `${getTransposeRecordWidth(cell.recordIndex)}px` }"
@@ -8661,17 +8634,15 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
'data-grid-cell--frozen-separator': frozenColumnCount > 0 && col.visibleColIdx === frozenColumnCount - 1, 'data-grid-cell--frozen-separator': frozenColumnCount > 0 && col.visibleColIdx === frozenColumnCount - 1,
'text-right': columnAligns[col.visibleColIdx] === 'right', 'text-right': columnAligns[col.visibleColIdx] === 'right',
'text-muted-foreground italic': isNull(item.data[col.actualColIdx]), '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': cellIsSelected(item.displayIndex, col.visibleColIdx) && !item.isDirtyCol[col.actualColIdx],
'cell-selected-dirty': 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': 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], '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-search-match': cellIsSearchMatch(item.displayIndex, col.actualColIdx),
'cell-current-search-match': cellIsCurrentMatch(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', '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, 'line-through': item.isDeleted,
'overflow-visible z-20 border-r-transparent': editingCell?.rowId === item.id && editingCell?.col === col.actualColIdx, '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), 'overflow-hidden': !(editingCell?.rowId === item.id && editingCell?.col === col.actualColIdx),
@@ -8928,8 +8899,8 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<div class="font-medium truncate">{{ index.name }}</div> <div class="font-medium truncate">{{ index.name }}</div>
<div class="mt-1 flex flex-wrap gap-1"> <div class="mt-1 flex flex-wrap gap-1">
<span v-if="index.is_primary" class="rounded bg-amber-500/10 px-1.5 py-0.5 text-amber-600">PK</span> <span v-if="index.is_primary" class="rounded bg-warning-bg px-1.5 py-0.5 text-warning">PK</span>
<span v-if="index.is_unique" class="rounded bg-emerald-500/10 px-1.5 py-0.5 text-emerald-600">UNIQUE</span> <span v-if="index.is_unique" class="rounded bg-success-bg px-1.5 py-0.5 text-success">UNIQUE</span>
<span v-if="index.index_type" class="rounded bg-muted px-1.5 py-0.5 text-muted-foreground">{{ index.index_type }}</span> <span v-if="index.index_type" class="rounded bg-muted px-1.5 py-0.5 text-muted-foreground">{{ index.index_type }}</span>
</div> </div>
<div class="mt-2 font-mono text-[11px] text-muted-foreground break-all"> <div class="mt-2 font-mono text-[11px] text-muted-foreground break-all">
@@ -9323,74 +9294,32 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
@reference "../../styles/globals.css"; @reference "../../styles/globals.css";
[data-grid-root] { [data-grid-root] {
--data-grid-row-muted-bg: rgb(240, 240, 240); /* Status / selection chrome — derived from theme tokens so palettes stay in sync. */
--data-grid-row-new-bg: rgb(243, 243, 243); --data-grid-row-muted-bg: color-mix(in srgb, var(--muted) 92%, var(--foreground));
--data-grid-row-deleted-bg: rgb(255, 244, 244); --data-grid-row-new-bg: color-mix(in srgb, var(--muted) 80%, var(--background));
--data-grid-cell-active-bg: rgb(244, 248, 255); --data-grid-row-deleted-bg: var(--color-error-bg, color-mix(in srgb, var(--destructive) 12%, transparent));
--data-grid-cell-dirty-bg: rgb(255, 248, 230); --data-grid-cell-active-bg: color-mix(in srgb, var(--info) 10%, var(--background));
--data-grid-cell-selected-bg: rgb(239, 246, 255); --data-grid-cell-dirty-bg: var(--warning-bg, color-mix(in srgb, var(--warning) 14%, transparent));
--data-grid-cell-selected-single-bg: rgb(191, 219, 254); --data-grid-cell-selected-bg: color-mix(in srgb, var(--primary) 12%, var(--accent));
--data-grid-cell-selected-dirty-bg: rgb(235, 224, 184); --data-grid-cell-selected-single-bg: color-mix(in srgb, var(--primary) 22%, var(--accent));
--data-grid-cell-selected-border: rgb(59, 130, 246); --data-grid-cell-selected-dirty-bg: color-mix(in srgb, var(--warning) 22%, var(--accent));
--data-grid-cell-hover-bg: rgb(245, 245, 245); /* Prefer ring/primary so the outline stays visible on the selected fill. */
--data-grid-cell-search-bg: rgb(253, 245, 184); --data-grid-cell-selected-border: color-mix(in srgb, var(--ring) 55%, var(--primary));
--data-grid-cell-current-search-bg: rgba(253, 224, 71, 0.52); --data-grid-cell-selected-single-border: var(--primary);
--data-grid-cell-current-search-border: rgba(234, 179, 8, 0.82); --data-grid-cell-hover-bg: var(--accent);
--data-grid-row-number-default-bg: rgb(255, 255, 255); --data-grid-cell-search-bg: var(--warning-bg, color-mix(in srgb, var(--warning) 18%, transparent));
--data-grid-row-number-new-bg: rgb(219, 244, 233); --data-grid-cell-current-search-bg: color-mix(in srgb, var(--warning) 35%, transparent);
--data-grid-row-number-edited-bg: rgb(253, 241, 219); --data-grid-cell-current-search-border: var(--warning);
--data-grid-row-number-deleted-bg: rgb(255, 244, 244); --data-grid-row-number-default-bg: var(--background);
--data-grid-row-number-active-bg: rgb(244, 248, 255); --data-grid-row-number-new-bg: var(--success-bg, color-mix(in srgb, var(--success) 12%, transparent));
--data-grid-row-number-selected-bg: rgb(191, 219, 254); --data-grid-row-number-edited-bg: var(--warning-bg, color-mix(in srgb, var(--warning) 14%, transparent));
--data-grid-scrollbar-thumb: color-mix(in oklch, var(--foreground) 30%, transparent); --data-grid-row-number-deleted-bg: var(--color-error-bg, color-mix(in srgb, var(--destructive) 12%, transparent));
--data-grid-scrollbar-thumb-hover: color-mix(in oklch, var(--foreground) 48%, 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; --data-grid-scrollbar-track: transparent;
background-color: rgb(255, 255, 255); background-color: var(--background);
}
[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));
}
} }
.data-grid-header-shell { .data-grid-header-shell {
@@ -9400,36 +9329,28 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
} }
.data-grid-header-cell { .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, [data-grid-root].data-grid--dark .data-grid-header-cell,
:global(.dark) [data-grid-root] .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, [data-grid-root].data-grid--dark .data-grid-header-row,
:global(.dark) [data-grid-root] .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, [data-grid-root].data-grid--dark .data-grid-header-cell:hover,
:global(.dark) [data-grid-root] .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 { .data-grid-header-cell--selected {
background-color: var(--data-grid-cell-selected-single-bg) !important; 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 .data-grid-header-cell--selected,
[data-grid-root].data-grid--dark .transpose-record-header-selected, [data-grid-root].data-grid--dark .transpose-record-header-selected,
[data-grid-root].data-grid--dark .transpose-record-header-active, [data-grid-root].data-grid--dark .transpose-record-header-active,
@@ -9437,7 +9358,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
:global(.dark) [data-grid-root] .transpose-record-header-selected, :global(.dark) [data-grid-root] .transpose-record-header-selected,
:global(.dark) [data-grid-root] .transpose-record-header-active { :global(.dark) [data-grid-root] .transpose-record-header-active {
background-color: var(--data-grid-cell-selected-single-bg) !important; background-color: var(--data-grid-cell-selected-single-bg) !important;
color: rgb(244, 244, 245) !important; color: var(--foreground) !important;
} }
.data-grid-row { .data-grid-row {
@@ -9450,12 +9371,12 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
/* 冻结列:不透明背景遮挡滚动的非冻结列;状态 class 的 !important 会覆盖此项 */ /* 冻结列:不透明背景遮挡滚动的非冻结列;状态 class 的 !important 会覆盖此项 */
.data-grid-cell--frozen { .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 深色右边框) */ /* 冻结列分隔线:与 Canvas 模式和列头一致(2px 深色右边框) */
.data-grid-cell--frozen-separator { .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 { .data-grid-row-number {
@@ -9908,16 +9829,28 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
background-color: var(--data-grid-cell-selected-dirty-bg) !important; 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); 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); 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); background-color: var(--data-grid-row-number-deleted-bg);
color: var(--destructive);
} }
.active-row > .data-grid-row-number { .active-row > .data-grid-row-number {
@@ -9931,35 +9864,10 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
} }
.cell-selected { .cell-selected {
color: hsl(var(--foreground)); color: var(--foreground);
background-color: var(--data-grid-cell-selected-single-bg) !important; background-color: var(--data-grid-cell-selected-single-bg) !important;
} outline: 1px solid var(--data-grid-cell-selected-border);
outline-offset: -1px;
.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;
} }
.ddl-code :deep(.ddl-kw) { .ddl-code :deep(.ddl-kw) {
@@ -335,7 +335,9 @@ function databaseOptionIsProduction(database: string): boolean {
:search-placeholder="t('editor.searchConnection')" :search-placeholder="t('editor.searchConnection')"
:empty-text="t('grid.noSearchResults')" :empty-text="t('grid.noSearchResults')"
:loading-text="t('common.loading')" :loading-text="t('common.loading')"
trigger-variant="ghost"
trigger-class="font-medium text-foreground" trigger-class="font-medium text-foreground"
trigger-icon-class="h-3 w-3"
:display-name="connectionDisplayName" :display-name="connectionDisplayName"
list-class="w-96 max-w-[calc(100vw-2rem)]" list-class="w-96 max-w-[calc(100vw-2rem)]"
item-class="min-h-9 h-auto py-1" item-class="min-h-9 h-auto py-1"
@@ -376,7 +378,9 @@ function databaseOptionIsProduction(database: string): boolean {
:loading-text="t('common.loading')" :loading-text="t('common.loading')"
:loading="loadingDatabaseOptions[activeConnection?.id || '']" :loading="loadingDatabaseOptions[activeConnection?.id || '']"
:display-name="databaseDisplayName" :display-name="databaseDisplayName"
trigger-variant="ghost"
trigger-class="gap-1.5" trigger-class="gap-1.5"
trigger-icon-class="h-3 w-3"
@update:model-value="(database) => emit('changeDatabase', database)" @update:model-value="(database) => emit('changeDatabase', database)"
@update:open=" @update:open="
(open: boolean) => { (open: boolean) => {
@@ -419,7 +423,9 @@ function databaseOptionIsProduction(database: string): boolean {
:loading-text="t('common.loading')" :loading-text="t('common.loading')"
:loading="!!activeConnection && isLoadingSchemas(activeConnection.id, schemaDatabaseKey)" :loading="!!activeConnection && isLoadingSchemas(activeConnection.id, schemaDatabaseKey)"
:clear-selected-option="supportsClearableQuerySchema(activeConnection?.db_type)" :clear-selected-option="supportsClearableQuerySchema(activeConnection?.db_type)"
trigger-variant="ghost"
trigger-class="gap-1.5" trigger-class="gap-1.5"
trigger-icon-class="h-3 w-3"
@update:model-value="(schema) => emit('changeSchema', schema || undefined)" @update:model-value="(schema) => emit('changeSchema', schema || undefined)"
@update:open=" @update:open="
(open: boolean) => { (open: boolean) => {
@@ -984,15 +984,15 @@ onUnmounted(() => {
--monitor-muted: var(--color-text-secondary, var(--color-muted-foreground, #64748b)); --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-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-hover: color-mix(in srgb, var(--color-hover, var(--color-muted, #f4f4f5)) 72%, var(--monitor-accent) 7%);
--monitor-accent: #0f766e; --monitor-accent: var(--info);
--monitor-accent-soft: rgb(15 118 110 / 0.1); --monitor-accent-soft: var(--info-bg);
--monitor-accent-border: rgb(15 118 110 / 0.22); --monitor-accent-border: color-mix(in srgb, var(--info) 22%, transparent);
--monitor-success: #15803d; --monitor-success: var(--success);
--monitor-success-soft: rgb(21 128 61 / 0.1); --monitor-success-soft: var(--success-bg);
--monitor-warning: #b45309; --monitor-warning: var(--warning);
--monitor-warning-soft: rgb(180 83 9 / 0.12); --monitor-warning-soft: var(--warning-bg);
--monitor-danger: #b91c1c; --monitor-danger: var(--destructive);
--monitor-danger-soft: rgb(185 28 28 / 0.1); --monitor-danger-soft: color-mix(in srgb, var(--destructive) 10%, transparent);
--monitor-shadow: 0 18px 38px -28px rgb(15 23 42 / 0.42); --monitor-shadow: 0 18px 38px -28px rgb(15 23 42 / 0.42);
height: 100%; height: 100%;
display: flex; display: flex;
@@ -1003,20 +1003,20 @@ onUnmounted(() => {
} }
:global(.dark) .monitoring-panel { :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: 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: 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-border-strong: color-mix(in srgb, var(--monitor-border) 76%, var(--info) 24%);
--monitor-accent: #2dd4bf; --monitor-accent: var(--info);
--monitor-accent-soft: rgb(45 212 191 / 0.12); --monitor-accent-soft: var(--info-bg);
--monitor-accent-border: rgb(45 212 191 / 0.24); --monitor-accent-border: color-mix(in srgb, var(--info) 24%, transparent);
--monitor-success: #4ade80; --monitor-success: var(--success);
--monitor-success-soft: rgb(74 222 128 / 0.12); --monitor-success-soft: var(--success-bg);
--monitor-warning: #f59e0b; --monitor-warning: var(--warning);
--monitor-warning-soft: rgb(245 158 11 / 0.14); --monitor-warning-soft: var(--warning-bg);
--monitor-danger: #f87171; --monitor-danger: var(--destructive);
--monitor-danger-soft: rgb(248 113 113 / 0.12); --monitor-danger-soft: color-mix(in srgb, var(--destructive) 12%, transparent);
--monitor-shadow: 0 18px 42px -28px rgb(0 0 0 / 0.72); --monitor-shadow: 0 18px 42px -28px rgb(0 0 0 / 0.72);
} }
@@ -761,8 +761,8 @@ watch(
} }
.badge-info { .badge-info {
background: color-mix(in srgb, #3b82f6 18%, transparent); background: var(--color-info-alpha);
color: #2563eb; color: var(--color-info);
} }
.badge-muted { .badge-muted {
@@ -67,7 +67,7 @@
.mq-btn-primary { .mq-btn-primary {
background: var(--color-primary); background: var(--color-primary);
border-color: var(--color-primary); border-color: var(--color-primary);
color: #fff; color: var(--color-primary-foreground);
} }
.mq-btn-secondary:hover:not(:disabled), .mq-btn-secondary:hover:not(:disabled),
@@ -2700,7 +2700,7 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
'bg-primary/10': sidePanelRow?.id === item.id && !selectedTableIds.has(item.id), 'bg-primary/10': sidePanelRow?.id === item.id && !selectedTableIds.has(item.id),
'bg-primary/5': 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)" @click="onRowClick(item, $event)"
@contextmenu="onContextMenu" @contextmenu="onContextMenu"
> >
@@ -159,7 +159,7 @@ watch([policyKey, () => groups.value.allowed.length], () => {
:tabindex="scopeMode === 'all' ? 0 : -1" :tabindex="scopeMode === 'all' ? 0 : -1"
variant="outline" variant="outline"
class="settings-choice-card h-auto justify-center border p-3" 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')" @click="setScopeMode('all')"
@keydown="onScopeModeKeydown($event, 'all')" @keydown="onScopeModeKeydown($event, 'all')"
> >
@@ -177,7 +177,7 @@ watch([policyKey, () => groups.value.allowed.length], () => {
:tabindex="scopeMode === 'selected' ? 0 : -1" :tabindex="scopeMode === 'selected' ? 0 : -1"
variant="outline" variant="outline"
class="settings-choice-card h-auto justify-center border p-3" 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')" @click="setScopeMode('selected')"
@keydown="onScopeModeKeydown($event, 'selected')" @keydown="onScopeModeKeydown($event, 'selected')"
> >
@@ -70,7 +70,7 @@ describe("McpConnectionScopePicker", () => {
return node.type === "button" && node.props["class"]?.includes("settings-choice-card"); 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.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"] === "all")?.props["aria-checked"]).toBe(true);
expect(modeButtons.find((node) => node.props["data-scope-mode"] === "selected")?.props.tabindex).toBe(-1); expect(modeButtons.find((node) => node.props["data-scope-mode"] === "selected")?.props.tabindex).toBe(-1);
@@ -714,8 +714,8 @@ const tableSearchStyle = computed(() => {
return { return {
paddingLeft: paddingLeft.value, paddingLeft: paddingLeft.value,
"--tree-table-search-row-bg": rowBackgroundColor, "--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-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) : "hsl(var(--border) / 0.36)", "--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. */ /* Multi-selection treats every selected row as equal; keep focus neutral. */
.tree-item-active--selection-set:focus { .tree-item-active--selection-set:focus {
background-color: var(--tree-connection-active-bg, rgb(235 235 235)) !important; 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 { :root.dark .tree-item-active--selection-set:focus {
background-color: var(--tree-connection-active-bg, rgb(36 36 36)) !important; 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 { .tree-item-highlight {
background-color: rgb(253 225 167) !important; background-color: var(--warning-bg) !important;
background-color: oklch(0.92 0.08 85) !important;
transition: background-color 0.28s ease-out; transition: background-color 0.28s ease-out;
} }
:root.dark .tree-item-highlight { :root.dark .tree-item-highlight {
background-color: rgb(110 67 0) !important; background-color: var(--warning-bg) !important;
background-color: oklch(0.42 0.12 80) !important;
transition: background-color 0.28s ease-out; transition: background-color 0.28s ease-out;
} }
.tree-item-connection-tint.tree-item-highlight::before { .tree-item-connection-tint.tree-item-highlight::before {
background-color: rgb(253 225 167) !important; background-color: var(--warning-bg) !important;
background-color: oklch(0.92 0.08 85) !important;
} }
:root.dark .tree-item-connection-tint.tree-item-highlight::before { :root.dark .tree-item-connection-tint.tree-item-highlight::before {
background-color: rgb(110 67 0) !important; background-color: var(--warning-bg) !important;
background-color: oklch(0.42 0.12 80) !important;
} }
</style> </style>
@@ -3302,7 +3302,7 @@ watch([activeTab, ddlLoading], ([tab, loading]) => {
</div> </div>
</div> </div>
<div v-if="!sqlPreviewCollapsed" class="min-h-0 flex-1 overflow-auto p-2.5"> <div v-if="!sqlPreviewCollapsed" class="min-h-0 flex-1 overflow-auto p-2.5">
<div v-if="hasSqliteTypeChange" class="mb-2 flex gap-1.5 rounded-md border border-blue-300/40 bg-blue-500/10 px-[var(--structure-cell-px)] py-[var(--structure-cell-py)] text-[length:var(--structure-font-size)] text-blue-700 dark:text-blue-300"> <div v-if="hasSqliteTypeChange" class="mb-2 flex gap-1.5 rounded-md border border-primary/40 bg-primary/10 px-[var(--structure-cell-px)] py-[var(--structure-cell-py)] text-[length:var(--structure-font-size)] text-primary">
<Info :class="[structureIconClass, 'mt-0.5 shrink-0']" /> <Info :class="[structureIconClass, 'mt-0.5 shrink-0']" />
<span>{{ t("structureEditor.sqliteRebuildNotice") }}</span> <span>{{ t("structureEditor.sqliteRebuildNotice") }}</span>
</div> </div>
@@ -3335,7 +3335,7 @@ watch([activeTab, ddlLoading], ([tab, loading]) => {
</template> </template>
<style scoped> <style scoped>
/* --primary is rgb/oklch; use color-mix like DataGrid, not hsl(var(--primary)). */ /* --primary is rgb/oklch; use color-mix like DataGrid, not channel-based hsl wrappers. */
.structure-column-search-match > td:first-child { .structure-column-search-match > td:first-child {
box-shadow: inset 3px 0 0 color-mix(in oklab, var(--primary) 55%, transparent); box-shadow: inset 3px 0 0 color-mix(in oklab, var(--primary) 55%, transparent);
} }
@@ -42,8 +42,8 @@ const props = withDefaults(
clearable: false, clearable: false,
clearSelectedOption: false, clearSelectedOption: false,
loadingText: "Loading...", loadingText: "Loading...",
triggerVariant: "ghost", triggerVariant: "outline",
triggerIconClass: "h-3 w-3", triggerIconClass: "size-4 text-muted-foreground",
displayName: (option: string) => option, displayName: (option: string) => option,
optionTooltip: () => undefined, optionTooltip: () => undefined,
normalizeCustom: (value: string) => value, normalizeCustom: (value: string) => value,
@@ -78,7 +78,7 @@ const selectedLabel = computed(() => {
const triggerBaseClass = computed(() => const triggerBaseClass = computed(() =>
props.triggerVariant === "outline" props.triggerVariant === "outline"
? "dbx-searchable-select-trigger h-6 w-auto max-w-56 min-w-0 justify-between gap-1 px-2 text-xs font-normal shadow-none" ? "dbx-searchable-select-trigger dbx-control-chrome h-8 w-full min-w-0 justify-between gap-1.5 border border-input bg-transparent px-2.5 text-sm font-normal shadow-none hover:bg-muted/40 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 dark:bg-input/30 dark:hover:bg-input/50"
: "h-6 w-auto max-w-56 min-w-0 justify-between gap-1 border-0 bg-transparent px-1 text-xs font-normal shadow-none hover:bg-muted/50 focus-visible:ring-0", : "h-6 w-auto max-w-56 min-w-0 justify-between gap-1 border-0 bg-transparent px-1 text-xs font-normal shadow-none hover:bg-muted/50 focus-visible:ring-0",
); );
@@ -335,37 +335,6 @@ function handleKeydown(event: KeyboardEvent) {
</template> </template>
<style> <style>
.dbx-searchable-select-trigger {
border: 1px solid rgb(229, 229, 229) !important;
background-color: rgb(255, 255, 255) !important;
box-shadow: none !important;
}
.dbx-searchable-select-trigger:hover {
background-color: rgb(250, 250, 250) !important;
}
.dbx-searchable-select-trigger[aria-expanded="true"],
.dbx-searchable-select-trigger:focus-visible {
border-color: rgb(96, 165, 250) !important;
box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.22) !important;
}
.dark .dbx-searchable-select-trigger {
border-color: rgba(255, 255, 255, 0.14) !important;
background-color: rgba(255, 255, 255, 0.08) !important;
}
.dark .dbx-searchable-select-trigger:hover {
background-color: rgba(255, 255, 255, 0.12) !important;
}
.dark .dbx-searchable-select-trigger[aria-expanded="true"],
.dark .dbx-searchable-select-trigger:focus-visible {
border-color: rgb(147, 197, 253) !important;
box-shadow: 0 0 0 2px rgba(147, 197, 253, 0.24) !important;
}
.dbx-searchable-select-list { .dbx-searchable-select-list {
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: color-mix(in oklch, var(--foreground) 30%, transparent) transparent; scrollbar-color: color-mix(in oklch, var(--foreground) 30%, transparent) transparent;
@@ -20,7 +20,7 @@ const forwardedProps = useForwardProps(delegatedProps);
v-bind="forwardedProps" v-bind="forwardedProps"
:class=" :class="
cn( cn(
'dbx-select-trigger border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-md border bg-transparent py-2 pr-2 pl-2.5 text-sm transition-colors select-none focus-visible:ring-3 aria-invalid:ring-3 data-[size=default]:h-8 data-[size=sm]:h-7 *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*=size-])]:size-4 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0', 'dbx-select-trigger dbx-control-chrome border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-md border bg-transparent py-2 pr-2 pl-2.5 text-sm transition-colors select-none focus-visible:ring-3 aria-invalid:ring-3 data-[size=default]:h-8 data-[size=sm]:h-7 *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*=size-])]:size-4 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0',
props.class, props.class,
) )
" "
@@ -31,36 +31,3 @@ const forwardedProps = useForwardProps(delegatedProps);
</SelectIcon> </SelectIcon>
</SelectTrigger> </SelectTrigger>
</template> </template>
<style>
.dbx-select-trigger:not(.border-0):not(.border-transparent) {
border: 1px solid rgb(229, 229, 229) !important;
background-color: rgb(255, 255, 255) !important;
box-shadow: none !important;
}
.dbx-select-trigger:not(.border-0):not(.border-transparent):hover {
background-color: rgb(250, 250, 250) !important;
}
.dbx-select-trigger:not(.border-0):not(.border-transparent)[data-state="open"],
.dbx-select-trigger:not(.border-0):not(.border-transparent):focus-visible {
border-color: rgb(96, 165, 250) !important;
box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.22) !important;
}
.dark .dbx-select-trigger:not(.border-0):not(.border-transparent) {
border-color: rgba(255, 255, 255, 0.14) !important;
background-color: rgba(255, 255, 255, 0.08) !important;
}
.dark .dbx-select-trigger:not(.border-0):not(.border-transparent):hover {
background-color: rgba(255, 255, 255, 0.12) !important;
}
.dark .dbx-select-trigger:not(.border-0):not(.border-transparent)[data-state="open"],
.dark .dbx-select-trigger:not(.border-0):not(.border-transparent):focus-visible {
border-color: rgb(147, 197, 253) !important;
box-shadow: 0 0 0 2px rgba(147, 197, 253, 0.24) !important;
}
</style>
@@ -51,8 +51,8 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
position: relative; position: relative;
flex-shrink: 0; flex-shrink: 0;
overflow: hidden; overflow: hidden;
border: 1px solid rgba(120, 120, 128, 0.22) !important; border: 1px solid color-mix(in srgb, var(--border) 80%, transparent) !important;
background-color: rgb(229, 229, 229) !important; background-color: var(--input) !important;
vertical-align: middle; vertical-align: middle;
} }
@@ -69,8 +69,8 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
.dbx-switch-thumb { .dbx-switch-thumb {
display: block !important; display: block !important;
border-radius: 9999px; border-radius: 9999px;
background-color: rgb(255, 255, 255) !important; background-color: var(--background) !important;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.22); box-shadow: 0 1px 2px color-mix(in srgb, var(--foreground) 18%, transparent);
transform: translateX(0) !important; transform: translateX(0) !important;
} }
@@ -87,8 +87,8 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
.dbx-switch[data-state="checked"], .dbx-switch[data-state="checked"],
.dbx-switch[data-checked], .dbx-switch[data-checked],
.dbx-switch[aria-checked="true"] { .dbx-switch[aria-checked="true"] {
border-color: rgb(23, 23, 23) !important; border-color: var(--primary) !important;
background-color: rgb(23, 23, 23) !important; background-color: var(--primary) !important;
} }
.dbx-switch[data-size="default"][data-state="checked"] .dbx-switch-thumb, .dbx-switch[data-size="default"][data-state="checked"] .dbx-switch-thumb,
@@ -103,21 +103,27 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
transform: translateX(10px) !important; 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 { .dark .dbx-switch {
border-color: rgba(255, 255, 255, 0.16) !important; border-color: color-mix(in srgb, var(--border) 90%, transparent) !important;
background-color: rgba(255, 255, 255, 0.18) !important; background-color: color-mix(in srgb, var(--input) 80%, transparent) !important;
} }
.dark .dbx-switch[data-state="checked"], .dark .dbx-switch[data-state="checked"],
.dark .dbx-switch[data-checked], .dark .dbx-switch[data-checked],
.dark .dbx-switch[aria-checked="true"] { .dark .dbx-switch[aria-checked="true"] {
border-color: rgb(245, 245, 245) !important; border-color: var(--primary) !important;
background-color: rgb(245, 245, 245) !important; background-color: var(--primary) !important;
} }
.dark .dbx-switch[data-state="checked"] .dbx-switch-thumb, .dark .dbx-switch[data-state="checked"] .dbx-switch-thumb,
.dark .dbx-switch[data-checked] .dbx-switch-thumb, .dark .dbx-switch[data-checked] .dbx-switch-thumb,
.dark .dbx-switch[aria-checked="true"] .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;
} }
</style> </style>
@@ -1,8 +1,34 @@
import { describe, expect, it } from "vitest"; 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"; 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", () => { 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(false)).toBe(DATA_GRID_LIGHT_ACTIVE_ROW_BG);
expect(dataGridActiveRowBackground(true)).toBe(DATA_GRID_DARK_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 }).cellActive).toBe(DATA_GRID_DARK_ACTIVE_ROW_BG);
expect(resolveDataGridPaintTheme({ getVar: emptyCssVariable, isDark: true }).rowNumberActive).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<string, string> = {
"--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<string, string> = {
"--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<string, string> = {
"--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/);
});
}); });
@@ -30,13 +30,14 @@ describe("dataGridRuntime", () => {
}); });
it("keeps runtime modules independent from the DataGrid component", () => { 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 sourceFiles: string[] = [];
const visit = (directory: string) => { const visit = (directory: string) => {
for (const entry of readdirSync(directory, { withFileTypes: true })) { for (const entry of readdirSync(directory, { withFileTypes: true })) {
const path = join(directory, entry.name); const path = join(directory, entry.name);
if (entry.isDirectory()) visit(path); 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); sourceRoots.forEach(visit);
@@ -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 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 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 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 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 rowNew = paintToken(getVar, "--muted", isDark ? "rgb(51, 51, 55)" : "rgb(243, 243, 243)");
const rowDeleted = isDark ? "rgb(55, 31, 32)" : "rgb(255, 244, 244)"; const rowDeleted = destructiveBg;
const cellActive = activeSurface; const cellActive = activeSurface;
const cellDirty = isDark ? "rgb(94, 75, 26)" : "rgb(255, 248, 230)"; const cellDirty = warningBg;
const cellSelected = isDark ? "rgb(20, 40, 60)" : "rgb(239, 246, 255)"; const cellSelected = paintToken(getVar, "--accent", isDark ? "rgb(20, 40, 60)" : "rgb(239, 246, 255)");
const cellSelectedDirty = isDark ? "rgb(76, 66, 38)" : "rgb(235, 224, 184)"; const cellSelectedDirty = warningBg;
const cellSelectedBorder = isDark ? "rgb(96, 165, 250)" : "rgb(59, 130, 246)"; // Selection outline must stay readable on the selected fill (WCAG non-text ~3:1).
const cellSelectedSingle = isDark ? "rgb(30, 64, 96)" : "rgb(191, 219, 254)"; // 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 cellHover = accent;
const cellSearch = isDark ? DATA_GRID_DARK_SEARCH_COLORS.match : "rgb(253, 245, 184)"; const cellSearch = paintToken(getVar, "--warning-bg", 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 cellCurrentSearch = paintToken(getVar, "--warning-bg", 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 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 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 rowNumberNew = successBg;
const rowNumberEdited = isDark ? DATA_GRID_DARK_ROW_NUMBER_EDITED_BG : DATA_GRID_LIGHT_ROW_NUMBER_EDITED_BG; const rowNumberEdited = warningBg;
const rowNumberDeleted = isDark ? DATA_GRID_DARK_ROW_NUMBER_DELETED_BG : DATA_GRID_LIGHT_ROW_NUMBER_DELETED_BG; const rowNumberDeleted = destructiveBg;
const rowNumberActive = activeSurface; const rowNumberActive = activeSurface;
const rowNumberSelected = isDark ? "rgb(30, 64, 96)" : "rgb(191, 219, 254)"; const rowNumberSelected = cellSelectedSingle;
return { return {
background, background,
@@ -273,29 +280,29 @@ export function resolveDataGridPaintTheme(options: { getVar: (name: string) => s
foreground, foreground,
mutedForeground, mutedForeground,
primary, primary,
rowMuted: isDark ? rowMuted : paintToken(getVar, "--data-grid-row-muted-bg", rowMuted), rowMuted: paintToken(getVar, "--data-grid-row-muted-bg", rowMuted),
rowNew: isDark ? rowNew : paintToken(getVar, "--data-grid-row-new-bg", rowNew), rowNew: paintToken(getVar, "--data-grid-row-new-bg", rowNew),
rowDeleted: isDark ? rowDeleted : paintToken(getVar, "--data-grid-row-deleted-bg", rowDeleted), rowDeleted: paintToken(getVar, "--data-grid-row-deleted-bg", rowDeleted),
cellActive: isDark ? cellActive : paintToken(getVar, "--data-grid-cell-active-bg", cellActive), cellActive: paintToken(getVar, "--data-grid-cell-active-bg", cellActive),
cellDirty: isDark ? cellDirty : paintToken(getVar, "--data-grid-cell-dirty-bg", cellDirty), cellDirty: paintToken(getVar, "--data-grid-cell-dirty-bg", cellDirty),
cellSelected: isDark ? cellSelected : paintToken(getVar, "--data-grid-cell-selected-bg", cellSelected), cellSelected: paintToken(getVar, "--data-grid-cell-selected-bg", cellSelected),
cellSelectedDirty: isDark ? cellSelectedDirty : paintToken(getVar, "--data-grid-cell-selected-dirty-bg", cellSelectedDirty), cellSelectedDirty: paintToken(getVar, "--data-grid-cell-selected-dirty-bg", cellSelectedDirty),
cellSelectedBorder: isDark ? cellSelectedBorder : paintToken(getVar, "--data-grid-cell-selected-border", cellSelectedBorder), cellSelectedBorder: paintToken(getVar, "--data-grid-cell-selected-border", cellSelectedBorder),
cellSelectedSingle: isDark ? cellSelectedSingle : paintToken(getVar, "--data-grid-cell-selected-single-bg", cellSelectedSingle), cellSelectedSingle: paintToken(getVar, "--data-grid-cell-selected-single-bg", cellSelectedSingle),
cellSelectedSingleBorder: isDark ? primary : paintToken(getVar, "--data-grid-cell-selected-single-border", primary), cellSelectedSingleBorder: paintToken(getVar, "--data-grid-cell-selected-single-border", primary),
cellHover: isDark ? cellHover : paintToken(getVar, "--data-grid-cell-hover-bg", cellHover), cellHover: paintToken(getVar, "--data-grid-cell-hover-bg", cellHover),
cellSearch: isDark ? cellSearch : paintToken(getVar, "--data-grid-cell-search-bg", cellSearch), cellSearch: paintToken(getVar, "--data-grid-cell-search-bg", cellSearch),
cellCurrentSearch: isDark ? cellCurrentSearch : paintToken(getVar, "--data-grid-cell-current-search-bg", cellCurrentSearch), cellCurrentSearch: paintToken(getVar, "--data-grid-cell-current-search-bg", cellCurrentSearch),
cellCurrentSearchBorder: isDark ? cellCurrentSearchBorder : paintToken(getVar, "--data-grid-cell-current-search-border", cellCurrentSearchBorder), cellCurrentSearchBorder: paintToken(getVar, "--data-grid-cell-current-search-border", cellCurrentSearchBorder),
rowNumberDefault, rowNumberDefault,
rowNumberNew: isDark ? rowNumberNew : paintToken(getVar, "--data-grid-row-number-new-bg", rowNumberNew), rowNumberNew: paintToken(getVar, "--data-grid-row-number-new-bg", rowNumberNew),
rowNumberEdited: isDark ? rowNumberEdited : paintToken(getVar, "--data-grid-row-number-edited-bg", rowNumberEdited), rowNumberEdited: paintToken(getVar, "--data-grid-row-number-edited-bg", rowNumberEdited),
rowNumberDeleted: isDark ? rowNumberDeleted : paintToken(getVar, "--data-grid-row-number-deleted-bg", rowNumberDeleted), rowNumberDeleted: paintToken(getVar, "--data-grid-row-number-deleted-bg", rowNumberDeleted),
rowNumberActive: isDark ? rowNumberActive : paintToken(getVar, "--data-grid-row-number-active-bg", rowNumberActive), rowNumberActive: paintToken(getVar, "--data-grid-row-number-active-bg", rowNumberActive),
rowNumberSelected: isDark ? rowNumberSelected : paintToken(getVar, "--data-grid-row-number-selected-bg", rowNumberSelected), rowNumberSelected: paintToken(getVar, "--data-grid-row-number-selected-bg", rowNumberSelected),
rowNumberTextClean: mutedForeground, rowNumberTextClean: mutedForeground,
rowNumberTextNew: isDark ? "rgb(94, 233, 181)" : "rgb(0, 122, 85)", rowNumberTextNew: success,
rowNumberTextEdited: isDark ? "rgb(255, 210, 48)" : "rgb(187, 77, 0)", rowNumberTextEdited: warning,
rowNumberTextDeleted: destructive, rowNumberTextDeleted: destructive,
}; };
} }
+150 -26
View File
@@ -68,8 +68,19 @@
/* Aliases for legacy MQ panel styles (used across components/mq/*). */ /* Aliases for legacy MQ panel styles (used across components/mq/*). */
--color-error: var(--destructive); --color-error: var(--destructive);
--color-error-bg: color-mix(in srgb, var(--destructive) 10%, transparent); --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-hover: var(--accent);
--color-background-secondary: var(--muted); --color-background-secondary: var(--muted);
--color-text: var(--foreground);
--color-text-secondary: var(--muted-foreground); --color-text-secondary: var(--muted-foreground);
--color-text-tertiary: color-mix(in srgb, var(--muted-foreground) 70%, transparent); --color-text-tertiary: color-mix(in srgb, var(--muted-foreground) 70%, transparent);
--color-border-light: color-mix(in srgb, var(--border) 60%, transparent); --color-border-light: color-mix(in srgb, var(--border) 60%, transparent);
@@ -136,6 +147,21 @@
--sidebar-accent-foreground: rgb(23 23 23); --sidebar-accent-foreground: rgb(23 23 23);
--sidebar-border: rgb(229 229 229); --sidebar-border: rgb(229 229 229);
--sidebar-ring: rgb(161 161 161); --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-window-top-border: rgb(0 0 0 / 0.35);
--dbx-viewport-height: 100vh; --dbx-viewport-height: 100vh;
} }
@@ -178,6 +204,66 @@
border-radius: var(--dbx-radius-fixed-6); 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) { @supports (height: 100dvh) {
:root { :root {
--dbx-viewport-height: min(100vh, 100dvh); --dbx-viewport-height: min(100vh, 100dvh);
@@ -211,12 +297,27 @@
--chart-5: rgb(38 38 38); --chart-5: rgb(38 38 38);
--sidebar: rgb(25 25 28); --sidebar: rgb(25 25 28);
--sidebar-foreground: rgb(208 208 213); --sidebar-foreground: rgb(208 208 213);
--sidebar-primary: rgb(20 71 230); --sidebar-primary: rgb(208 208 214);
--sidebar-primary-foreground: rgb(234 235 239); --sidebar-primary-foreground: rgb(19 20 22);
--sidebar-accent: rgb(44 44 48); --sidebar-accent: rgb(44 44 48);
--sidebar-accent-foreground: rgb(221 221 226); --sidebar-accent-foreground: rgb(221 221 226);
--sidebar-border: rgb(110 110 114 / 0.28); --sidebar-border: rgb(110 110 114 / 0.28);
--sidebar-ring: rgb(133 134 139); --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); --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-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0); --sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 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 { .dark {
@@ -1243,12 +1359,27 @@ html.theme-claude.dark {
--chart-5: oklch(0.269 0 0); --chart-5: oklch(0.269 0 0);
--sidebar: oklch(0.215 0.005 285); --sidebar: oklch(0.215 0.005 285);
--sidebar-foreground: oklch(0.86 0.006 285); --sidebar-foreground: oklch(0.86 0.006 285);
--sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary: oklch(0.86 0.008 285);
--sidebar-primary-foreground: oklch(0.94 0.006 285); --sidebar-primary-foreground: oklch(0.19 0.004 285);
--sidebar-accent: oklch(0.295 0.006 285); --sidebar-accent: oklch(0.295 0.006 285);
--sidebar-accent-foreground: oklch(0.9 0.006 285); --sidebar-accent-foreground: oklch(0.9 0.006 285);
--sidebar-border: oklch(0.54 0.006 285 / 28%); --sidebar-border: oklch(0.54 0.006 285 / 28%);
--sidebar-ring: oklch(0.62 0.008 285); --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 { html.theme-soft {
@@ -1899,46 +2030,40 @@ body.dbx-table-reference-dragging * {
cursor: row-resize; cursor: row-resize;
} }
.splitpanes__splitter:hover { .splitpanes__splitter:hover {
background: rgb(39 132 213) !important; background: var(--ring) !important;
background: oklch(0.6 0.15 250) !important;
} }
/* Panel resize handles */ /* Panel resize handles */
.app-panel-gutter { .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); 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, .dark .app-panel-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-tab-bar { background: var(--dbx-gutter);
}
.app-toolbar,
.app-tab-bar {
border-color: var(--border); border-color: var(--border);
background: var(--dbx-chrome); 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); color: var(--sidebar-foreground);
background: var(--sidebar); 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); 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, .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, .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 { .sticky-database-header {
border-color: var(--sidebar-border); border-color: var(--sidebar-border);
background: var(--dbx-sidebar-header); 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); border-color: var(--border);
background: var(--dbx-editor-toolbar); 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, .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 > .flex-1 {
background: var(--dbx-content); background: var(--dbx-content);
} }
html.theme-vscode .app-tab-scroll.wrap-mode .app-tab-pill[data-active-tab="false"] { 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:hover,
.app-layout-classic .panel-resize-handle:active { .app-layout-classic .panel-resize-handle:active {
background: rgb(39 132 213); background: var(--ring);
background: oklch(0.6 0.15 250);
} }
.panel-resize-handle { .panel-resize-handle {
+4 -3
View File
@@ -36,15 +36,16 @@ test("keeps appearance section spacing and help icons aligned without stacked ma
assert.match(settingsDialogSource, /<div class="flex min-w-0 items-center gap-1">\s*<Label class="min-w-0 whitespace-normal leading-tight">\{\{ t\("settings\.dataGridFontFamily"\) \}\}<\/Label>/); assert.match(settingsDialogSource, /<div class="flex min-w-0 items-center gap-1">\s*<Label class="min-w-0 whitespace-normal leading-tight">\{\{ t\("settings\.dataGridFontFamily"\) \}\}<\/Label>/);
}); });
test("matches the result grid font selector styling with date and time selectors", () => { test("matches the result grid font selector styling with appearance form controls", () => {
const selectorStart = settingsDialogSource.indexOf(`:model-value="editTableFontFamily"`); const selectorStart = settingsDialogSource.indexOf(`:model-value="editTableFontFamily"`);
const selectorEnd = settingsDialogSource.indexOf(`</SearchableSelect>`, selectorStart); const selectorEnd = settingsDialogSource.indexOf(`</SearchableSelect>`, selectorStart);
const selectorSource = settingsDialogSource.slice(selectorStart, selectorEnd); const selectorSource = settingsDialogSource.slice(selectorStart, selectorEnd);
assert.ok(selectorStart >= 0); assert.ok(selectorStart >= 0);
assert.ok(selectorEnd > selectorStart); assert.ok(selectorEnd > selectorStart);
assert.match(selectorSource, /trigger-variant="outline"/); // SearchableSelect defaults to outline; appearance fonts share the h-8 control chrome class.
assert.match(selectorSource, /trigger-class="h-9 w-full max-w-none justify-between"/); assert.match(selectorSource, /:trigger-class="appearanceFontSearchTriggerClass"/);
assert.match(settingsDialogSource, /const fontSearchTriggerClass =\s*"h-8 w-full max-w-none justify-between/);
}); });
test("uses a table font label in view options without changing the settings label", () => { test("uses a table font label in view options without changing the settings label", () => {
+1 -1
View File
@@ -9,7 +9,7 @@ test("unused header width keeps the result background", () => {
const style = descriptor.styles.map((block) => block.content).join("\n"); const style = descriptor.styles.map((block) => block.content).join("\n");
assert.match(style, /\.data-grid-header-shell\s*\{[^}]*background-color:\s*var\(--background\)/s); assert.match(style, /\.data-grid-header-shell\s*\{[^}]*background-color:\s*var\(--background\)/s);
assert.match(style, /\.data-grid-header-cell\s*\{[^}]*background-color:\s*rgb\(239, 239, 239\)/s); assert.match(style, /\.data-grid-header-cell\s*\{[^}]*background-color:\s*color-mix\(in srgb, var\(--muted\) 88%, var\(--background\)\)/s);
assert.doesNotMatch(style, /\.data-grid-header-shell\s*,\s*\.data-grid-header-cell/); assert.doesNotMatch(style, /\.data-grid-header-shell\s*,\s*\.data-grid-header-cell/);
assert.doesNotMatch(style, /data-grid--dark\s+\.data-grid-header-shell/); assert.doesNotMatch(style, /data-grid--dark\s+\.data-grid-header-shell/);
}); });
+10
View File
@@ -0,0 +1,10 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { test } from "vitest";
test("data grid selection outline uses ring/primary tokens instead of flat border", () => {
const source = readFileSync("apps/desktop/src/components/grid/DataGrid.vue", "utf8");
assert.match(source, /--data-grid-cell-selected-border:\s*color-mix\(in srgb, var\(--ring\) 55%, var\(--primary\)\)/);
assert.doesNotMatch(source, /"--data-grid-cell-selected-border":\s*"var\(--border\)"/);
assert.doesNotMatch(source, /"--data-grid-cell-selected-bg":\s*"var\(--accent\)"/);
});