feat(ui): frosted-glass tooltip and collapsed-rail alignment

This commit is contained in:
matevip 2026-05-19 09:56:15 +08:00
parent 2c1e673fba
commit 99718d276e
4 changed files with 185 additions and 87 deletions

View File

@ -58,9 +58,14 @@
<div class="conversation-list"> <div class="conversation-list">
<template v-for="group in groupedConversations" :key="group.label"> <template v-for="group in groupedConversations" :key="group.label">
<div v-if="!collapsed || isMobile" class="conv-group-title">{{ group.label }}</div> <div v-if="!collapsed || isMobile" class="conv-group-title">{{ group.label }}</div>
<div <McTooltip
v-for="conv in group.items" v-for="conv in group.items"
:key="conv.conversationId" :key="conv.conversationId"
:content="conv.title"
placement="right"
:disabled="!collapsed || isMobile || !conv.title"
>
<div
class="conv-item" class="conv-item"
:class="{ :class="{
active: !selectMode && currentConversationId === conv.conversationId, active: !selectMode && currentConversationId === conv.conversationId,
@ -133,6 +138,7 @@
</button> </button>
</div> </div>
</div> </div>
</McTooltip>
</template> </template>
<div v-if="conversations.length === 0" class="empty-convs"> <div v-if="conversations.length === 0" class="empty-convs">
@ -181,6 +187,7 @@ import { useI18n } from 'vue-i18n'
import { Plus } from '@element-plus/icons-vue' import { Plus } from '@element-plus/icons-vue'
import AgentPickerDialog from '@/components/common/AgentPickerDialog.vue' import AgentPickerDialog from '@/components/common/AgentPickerDialog.vue'
import DropdownMenu, { type DropdownMenuItem } from '@/components/common/DropdownMenu.vue' import DropdownMenu, { type DropdownMenuItem } from '@/components/common/DropdownMenu.vue'
import McTooltip from '@/components/common/McTooltip.vue'
import { conversationApi } from '@/api/index' import { conversationApi } from '@/api/index'
import { channelIconUrl } from '@/utils/channelSource' import { channelIconUrl } from '@/utils/channelSource'
import { mcToast } from '@/composables/useMcToast' import { mcToast } from '@/composables/useMcToast'

View File

@ -0,0 +1,74 @@
<script setup lang="ts">
/**
* MateClaw-styled tooltip.
*
* A thin wrapper over Element Plus `el-tooltip` that pins the app's
* visual identity onto every tooltip: a frosted-glass surface, rounded
* geometry, no arrow, and automatic light/dark theming. Use it as a
* drop-in replacement for `<el-tooltip>` every prop and the default /
* `#content` slots pass straight through, while `effect`, the frosted
* `popper-class` and the arrow visibility stay owned by the wrapper so
* tooltips look identical across the app.
*/
import { computed } from 'vue'
defineOptions({ inheritAttrs: false })
const props = defineProps<{
/** Extra class(es) merged onto the frosted popper element. */
popperClass?: string
}>()
// `mc-tooltip` drives the frosted styling below; callers may layer on more.
const mergedPopperClass = computed(
() => ['mc-tooltip', props.popperClass].filter(Boolean).join(' '),
)
</script>
<template>
<el-tooltip
:show-after="80"
:offset="8"
v-bind="$attrs"
effect="mateclaw"
:show-arrow="false"
:popper-class="mergedPopperClass"
>
<slot />
<template v-if="$slots.content" #content>
<slot name="content" />
</template>
</el-tooltip>
</template>
<!-- Not scoped: el-tooltip teleports its popper to <body>, so the popper
is unreachable by scoped styles. The `mc-tooltip` popper class keeps
these rules namespaced. The `mateclaw` effect carries no Element Plus
theme of its own, so the wrapper fully owns the surface. -->
<style>
.el-popper.mc-tooltip {
background: rgba(255, 250, 245, 0.82);
-webkit-backdrop-filter: blur(20px) saturate(180%);
backdrop-filter: blur(20px) saturate(180%);
border: 1px solid var(--mc-border-light);
border-radius: 10px;
padding: 7px 11px;
max-width: 260px;
color: var(--mc-text-primary);
font-size: 12px;
font-weight: 500;
line-height: 1.5;
letter-spacing: -0.005em;
box-shadow:
0 1px 0 rgba(255, 255, 255, 0.6) inset,
0 8px 24px rgba(25, 14, 8, 0.16);
}
html.dark .el-popper.mc-tooltip {
background: rgba(32, 26, 22, 0.86);
border-color: rgba(255, 255, 255, 0.08);
box-shadow:
0 1px 0 rgba(255, 255, 255, 0.06) inset,
0 8px 24px rgba(0, 0, 0, 0.45);
}
</style>

View File

@ -1978,6 +1978,8 @@ function handleCodeCopy(e: MouseEvent) {
} }
.agent-badge-icon { .agent-badge-icon {
display: flex;
align-items: center;
font-size: 14px; font-size: 14px;
} }

View File

@ -41,13 +41,18 @@
<template v-for="group in navGroups" :key="group.key"> <template v-for="group in navGroups" :key="group.key">
<div class="nav-group"> <div class="nav-group">
<div v-if="!effectiveCollapsed" class="nav-group-title">{{ group.label }}</div> <div v-if="!effectiveCollapsed" class="nav-group-title">{{ group.label }}</div>
<router-link <McTooltip
v-for="item in group.items" v-for="item in group.items"
:key="item.path" :key="item.path"
:content="item.tooltip || item.label"
placement="right"
:disabled="!effectiveCollapsed"
>
<router-link
:to="item.path" :to="item.path"
class="nav-item" class="nav-item"
:class="{ active: isNavItemActive(item) }" :class="{ active: isNavItemActive(item) }"
:title="effectiveCollapsed ? (item.tooltip || item.label) : (item.tooltip || '')" :title="effectiveCollapsed ? '' : (item.tooltip || '')"
@click="onNavClick" @click="onNavClick"
> >
<span class="nav-icon" v-html="item.icon"></span> <span class="nav-icon" v-html="item.icon"></span>
@ -67,6 +72,7 @@
:title="t('notifications.pendingApprovals', { n: pendingApprovals })" :title="t('notifications.pendingApprovals', { n: pendingApprovals })"
/> />
</router-link> </router-link>
</McTooltip>
</div> </div>
</template> </template>
</nav> </nav>
@ -196,6 +202,7 @@ import OnboardingWizard from '@/views/Onboarding/OnboardingWizard.vue'
import DoctorDrawer from '@/views/Doctor/DoctorDrawer.vue' import DoctorDrawer from '@/views/Doctor/DoctorDrawer.vue'
import WorkspaceSwitcher from '@/components/workspace/WorkspaceSwitcher.vue' import WorkspaceSwitcher from '@/components/workspace/WorkspaceSwitcher.vue'
import NavBadge from '@/components/common/NavBadge.vue' import NavBadge from '@/components/common/NavBadge.vue'
import McTooltip from '@/components/common/McTooltip.vue'
import { useNotificationCenter } from '@/composables/useNotificationCenter' import { useNotificationCenter } from '@/composables/useNotificationCenter'
import { useWorkspaceStore } from '@/stores/useWorkspaceStore' import { useWorkspaceStore } from '@/stores/useWorkspaceStore'
import { applyLocale, currentLocale, type AppLocale } from '@/i18n' import { applyLocale, currentLocale, type AppLocale } from '@/i18n'
@ -746,6 +753,14 @@ watch(() => workspaceStore.currentWorkspaceId, () => {
/* Active indicator bar removed — active state uses bg color + font weight only */ /* Active indicator bar removed — active state uses bg color + font weight only */
/* Collapsed rail: the label is hidden, so center the lone icon on the
sidebar's vertical axis instead of leaving it left-aligned by the
nav-item's horizontal padding. */
.sidebar.collapsed .nav-item {
justify-content: center;
gap: 0;
}
.nav-icon { display: flex; align-items: center; flex-shrink: 0; } .nav-icon { display: flex; align-items: center; flex-shrink: 0; }
.nav-label { overflow: hidden; text-overflow: ellipsis; } .nav-label { overflow: hidden; text-overflow: ellipsis; }