fix(ui): unify sidebar tooltips

This commit is contained in:
t8y2
2026-07-03 12:41:25 +08:00
parent 3d3b0a688f
commit c702149931
3 changed files with 79 additions and 84 deletions
@@ -4,8 +4,8 @@ import { useI18n } from "vue-i18n";
import { translateBackendError } from "@/i18n/backend-errors";
import { Upload, Download, FolderPlus, RefreshCw, ChevronsLeft, ChevronsUp } from "@lucide/vue";
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
import LightDropdown from "@/components/ui/LightDropdown.vue";
import LightTooltip from "@/components/ui/LightTooltip.vue";
import ConnectionTree from "@/components/sidebar/ConnectionTree.vue";
import { useConnectionStore } from "@/stores/connectionStore";
import { useToast } from "@/composables/useToast";
@@ -62,68 +62,50 @@ defineExpose({ focusSearch });
<div class="flex items-center gap-px px-3 text-xs font-medium text-muted-foreground border-b bg-muted/20" :class="classicLayout ? 'h-9' : 'h-10'">
<span class="flex self-stretch items-center truncate" data-tauri-drag-region>{{ t("sidebar.connections") }}</span>
<span class="flex-1 self-stretch" data-tauri-drag-region />
<Tooltip>
<TooltipTrigger as-child>
<span class="inline-flex">
<LightDropdown
model-value=""
:items="importSourceItems"
:aria-label="t('sidebar.import')"
:trigger-icon="Download"
trigger-class="inline-flex h-6 w-5 items-center justify-center rounded-md outline-none hover:bg-muted hover:text-foreground focus-visible:ring-0"
trigger-icon-class="h-4 w-4"
content-class="w-44"
:show-trigger-label="false"
:show-chevron="false"
:highlight-selected="false"
check-position="none"
align="end"
@update:model-value="(source) => emit('import', source as 'dbx' | 'navicat' | 'dbeaver' | 'datagrip')"
/>
</span>
</TooltipTrigger>
<TooltipContent>{{ t("sidebar.import") }}</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="h-5 w-5" @click="emit('export')">
<Upload class="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>{{ t("sidebar.export") }}</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="h-5 w-5" @click="collapseAllTreeNodes">
<ChevronsUp class="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>{{ t("sidebar.collapseAll") }}</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="h-5 w-5" @click="createNewGroup">
<FolderPlus class="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>{{ t("connectionGroup.createGroup") }}</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="h-5 w-5" @click="refreshTree">
<RefreshCw class="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>{{ t("contextMenu.refreshChildren") }}</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger as-child>
<Button variant="ghost" size="icon" class="h-6 w-6" @click="emit('collapse')">
<ChevronsLeft class="h-3.5 w-3.5" />
</Button>
</TooltipTrigger>
<TooltipContent>{{ t("sidebar.collapse") }}</TooltipContent>
</Tooltip>
<LightTooltip :text="t('sidebar.import')" side="bottom" :delay="0" :close-delay="0" nowrap>
<span class="inline-flex">
<LightDropdown
model-value=""
:items="importSourceItems"
:aria-label="t('sidebar.import')"
:trigger-icon="Download"
trigger-class="inline-flex h-6 w-5 items-center justify-center rounded-md outline-none hover:bg-muted hover:text-foreground focus-visible:ring-0"
trigger-icon-class="h-4 w-4"
content-class="w-44"
:show-trigger-label="false"
:show-chevron="false"
:highlight-selected="false"
check-position="none"
align="end"
@update:model-value="(source) => emit('import', source as 'dbx' | 'navicat' | 'dbeaver' | 'datagrip')"
/>
</span>
</LightTooltip>
<LightTooltip :text="t('sidebar.export')" side="bottom" :delay="0" :close-delay="0" nowrap>
<Button variant="ghost" size="icon" class="h-5 w-5" @click="emit('export')">
<Upload class="h-3 w-3" />
</Button>
</LightTooltip>
<LightTooltip :text="t('sidebar.collapseAll')" side="bottom" :delay="0" :close-delay="0" nowrap>
<Button variant="ghost" size="icon" class="h-5 w-5" @click="collapseAllTreeNodes">
<ChevronsUp class="h-3 w-3" />
</Button>
</LightTooltip>
<LightTooltip :text="t('connectionGroup.createGroup')" side="bottom" :delay="0" :close-delay="0" nowrap>
<Button variant="ghost" size="icon" class="h-5 w-5" @click="createNewGroup">
<FolderPlus class="h-3 w-3" />
</Button>
</LightTooltip>
<LightTooltip :text="t('contextMenu.refreshChildren')" side="bottom" :delay="0" :close-delay="0" nowrap>
<Button variant="ghost" size="icon" class="h-5 w-5" @click="refreshTree">
<RefreshCw class="h-3 w-3" />
</Button>
</LightTooltip>
<LightTooltip :text="t('sidebar.collapse')" side="bottom" :delay="0" :close-delay="0" nowrap>
<Button variant="ghost" size="icon" class="h-6 w-6" @click="emit('collapse')">
<ChevronsLeft class="h-3.5 w-3.5" />
</Button>
</LightTooltip>
</div>
<div class="flex-1 min-h-0">
<ConnectionTree ref="connectionTreeRef" />
@@ -1,10 +1,11 @@
<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, reactive, ref } from "vue";
import { useI18n } from "vue-i18n";
import { Download, FileInput, FilePlus, FileText, FolderCog, FolderClosed, FolderOpen, FolderPlus, Library, LocateFixed, Pencil, Search, Trash2, Upload, X, ArrowDownWideNarrow } from "@lucide/vue";
import { ArrowDownWideNarrow, Download, FileInput, FilePlus, FileText, FolderCog, FolderClosed, FolderOpen, FolderPlus, Library, LocateFixed, Pencil, Search, Trash2, Upload, X } from "@lucide/vue";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import CustomContextMenu, { type ContextMenuItem as CtxMenuItem } from "@/components/ui/CustomContextMenu.vue";
import LightTooltip from "@/components/ui/LightTooltip.vue";
import { useToast } from "@/composables/useToast";
import { isTauriRuntime } from "@/lib/tauriRuntime";
import * as api from "@/lib/api";
@@ -738,8 +739,8 @@ const contextMenuItems = computed<CtxMenuItem[]>(() => {
return [
{ label: t("savedSql.newFolder"), action: openNewFolderInput, icon: FolderPlus },
{ label: t("savedSql.newQuery"), action: () => openNewQueryInFolder(), icon: FilePlus },
{ label: t("sqlLibrary.importDirectory"), action: () => importDirectoryIntoLibrary(), icon: Upload },
{ label: t("sqlLibrary.exportLibrary"), action: () => exportFolderContents(), icon: Download },
{ label: t("sqlLibrary.importDirectory"), action: () => importDirectoryIntoLibrary(), icon: Download },
{ label: t("sqlLibrary.exportLibrary"), action: () => exportFolderContents(), icon: Upload },
{ label: "", separator: true },
{ label: t("sqlLibrary.openStorageDirectory"), action: openSqlStorageDirectory, icon: LocateFixed },
{ label: t("sqlLibrary.chooseSyncDirectory"), action: chooseSyncDirectory, icon: FolderCog },
@@ -769,8 +770,8 @@ const contextMenuItems = computed<CtxMenuItem[]>(() => {
return [
{ label: t("savedSql.newFolder"), action: () => openNewFolderInput(target.id), icon: FolderPlus },
{ label: t("savedSql.newQuery"), action: () => openNewQueryInFolder(target), icon: FilePlus },
{ label: t("sqlLibrary.importIntoFolder"), action: () => importDirectoryIntoLibrary(target), icon: Upload },
{ label: t("sqlLibrary.exportFolder"), action: () => exportFolderContents(target), icon: Download },
{ label: t("sqlLibrary.importIntoFolder"), action: () => importDirectoryIntoLibrary(target), icon: Download },
{ label: t("sqlLibrary.exportFolder"), action: () => exportFolderContents(target), icon: Upload },
{ label: "", separator: true },
{ label: t("savedSql.renameFolder"), action: () => startRenameFolder(target), icon: Pencil },
{ label: "", separator: true },
@@ -1019,21 +1020,31 @@ function showDropInside(targetId: string) {
<span class="text-[13px] font-medium">{{ t("sqlLibrary.title") }}</span>
<span v-if="hasSelection" class="text-[12px] text-muted-foreground ml-1">({{ selectedCount }})</span>
<span class="flex-1" />
<Button variant="ghost" size="icon" class="h-5 w-5" :title="sortMode === 'folder' ? t('sqlLibrary.sortByDate') : t('sqlLibrary.sortByFolder')" @click="sortMode = sortMode === 'folder' ? 'date' : 'folder'">
<ArrowDownWideNarrow :class="['h-3 w-3', sortMode === 'date' ? 'text-primary' : '']" />
</Button>
<Button variant="ghost" size="icon" class="h-5 w-5" :title="t('savedSql.newFolder')" @click="openNewFolderInput">
<FolderPlus class="h-3 w-3" />
</Button>
<Button variant="ghost" size="icon" class="h-5 w-5" :title="t('sqlLibrary.importDirectory')" @click="importDirectoryIntoLibrary()">
<Upload class="h-3 w-3" />
</Button>
<Button variant="ghost" size="icon" class="h-5 w-5" :title="t('sqlLibrary.exportLibrary')" @click="exportFolderContents()">
<Download class="h-3 w-3" />
</Button>
<Button variant="ghost" size="icon" class="h-5 w-5" @click="emit('close')">
<X class="h-3 w-3" />
</Button>
<LightTooltip :text="sortMode === 'folder' ? t('sqlLibrary.sortByDate') : t('sqlLibrary.sortByFolder')" side="bottom" :delay="0" :close-delay="0" nowrap>
<Button variant="ghost" size="icon" class="h-5 w-5" @click="sortMode = sortMode === 'folder' ? 'date' : 'folder'">
<ArrowDownWideNarrow :class="['h-3 w-3', sortMode === 'date' ? 'text-primary' : '']" />
</Button>
</LightTooltip>
<LightTooltip :text="t('savedSql.newFolder')" side="bottom" :delay="0" :close-delay="0" nowrap>
<Button variant="ghost" size="icon" class="h-5 w-5" @click="openNewFolderInput">
<FolderPlus class="h-3 w-3" />
</Button>
</LightTooltip>
<LightTooltip :text="t('sqlLibrary.importDirectory')" side="bottom" :delay="0" :close-delay="0" nowrap>
<Button variant="ghost" size="icon" class="h-5 w-5" @click="importDirectoryIntoLibrary()">
<Download class="h-3 w-3" />
</Button>
</LightTooltip>
<LightTooltip :text="t('sqlLibrary.exportLibrary')" side="bottom" :delay="0" :close-delay="0" nowrap>
<Button variant="ghost" size="icon" class="h-5 w-5" @click="exportFolderContents()">
<Upload class="h-3 w-3" />
</Button>
</LightTooltip>
<LightTooltip :text="t('common.close')" side="bottom" :delay="0" :close-delay="0" nowrap>
<Button variant="ghost" size="icon" class="h-5 w-5" @click="emit('close')">
<X class="h-3 w-3" />
</Button>
</LightTooltip>
</div>
<div class="border-b shrink-0 px-2 py-1">
@@ -10,6 +10,7 @@ const props = withDefaults(
delay?: number;
closeDelay?: number;
openOnFocus?: boolean;
nowrap?: boolean;
}>(),
{
disabled: false,
@@ -18,6 +19,7 @@ const props = withDefaults(
delay: 300,
closeDelay: 100,
openOnFocus: true,
nowrap: false,
},
);
@@ -240,7 +242,7 @@ watch(
v-if="show"
ref="tooltipRef"
class="fixed z-50 rounded-md bg-foreground text-xs text-background"
:class="[slots.content ? '' : 'inline-flex w-fit max-w-xs items-center gap-1.5 px-3 py-1.5', tooltipTransformClass]"
:class="[slots.content ? '' : ['inline-flex w-fit max-w-xs items-center gap-1.5 px-3 py-1.5', nowrap ? 'whitespace-nowrap' : ''], tooltipTransformClass]"
:style="{ left: `${x}px`, top: `${y}px` }"
role="tooltip"
@mouseenter="clearCloseTimer"