feat(frontend): show stacked integration icons on sidebar hover
Add a compact stacked icon preview when hovering the Publish & Integrations menu item so users can see available channels before opening the page. Centralize integration tab metadata in config for easier future additions.
This commit is contained in:
@@ -94,6 +94,15 @@
|
||||
class="menu-pending-badge"
|
||||
:title="t('organization.settings.pendingJoinRequestsBadge')">{{
|
||||
orgStore.totalPendingJoinRequestCount }}</span>
|
||||
<span v-if="item.path === 'integrations'" class="integration-preview"
|
||||
aria-hidden="true">
|
||||
<span v-for="(preview, idx) in integrationPreviewItems" :key="preview.key"
|
||||
class="integration-preview__item" :style="{ zIndex: idx + 1 }">
|
||||
<t-icon v-if="preview.icon.type === 'icon'" :name="preview.icon.name"
|
||||
size="13px" />
|
||||
<span v-else class="integration-preview__emoji">{{ preview.icon.value }}</span>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -248,8 +257,10 @@ import UserMenu from '@/components/UserMenu.vue';
|
||||
import TenantSelector from '@/components/TenantSelector.vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { getSystemInfo } from '@/api/system';
|
||||
import { INTEGRATION_PREVIEW_ITEMS } from '@/config/integrations';
|
||||
|
||||
const chatResources = useChatResourcesStore();
|
||||
const integrationPreviewItems = INTEGRATION_PREVIEW_ITEMS;
|
||||
// Platform logos reused from IMChannelsOverviewPanel — keeps the session list
|
||||
// visually consistent with the channels admin view.
|
||||
import wecomLogo from '@/assets/img/im/wecom.svg';
|
||||
@@ -1887,6 +1898,48 @@ const onDragHandleMouseDown = (e: MouseEvent) => {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.integration-preview {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
|
||||
.menu_item:hover & {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
&__item {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
background: var(--td-bg-color-container);
|
||||
border: 2px solid var(--td-bg-color-sidebar);
|
||||
box-sizing: border-box;
|
||||
color: var(--td-text-color-primary);
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
:deep(.t-icon) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&__emoji {
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.menu_box {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -2,3 +2,22 @@ export const CHROME_EXTENSION_URL =
|
||||
'https://chromewebstore.google.com/detail/jpemjbopikggjlmikmclgbmkhhopjdgd?utm_source=item-share-cb'
|
||||
|
||||
export const CLAWHUB_SKILL_URL = 'https://clawhub.ai/lyingbug/weknora'
|
||||
|
||||
export type IntegrationTab = 'im' | 'embed' | 'chrome' | 'claw'
|
||||
|
||||
export const INTEGRATION_TABS: IntegrationTab[] = ['im', 'embed', 'chrome', 'claw']
|
||||
|
||||
export type IntegrationPreviewIcon =
|
||||
| { type: 'icon'; name: string }
|
||||
| { type: 'emoji'; value: string }
|
||||
|
||||
/** Sidebar hover preview + Integrations modal nav — add new entries here. */
|
||||
export const INTEGRATION_PREVIEW_ITEMS: Array<{
|
||||
key: IntegrationTab
|
||||
icon: IntegrationPreviewIcon
|
||||
}> = [
|
||||
{ key: 'im', icon: { type: 'icon', name: 'chat-message' } },
|
||||
{ key: 'embed', icon: { type: 'icon', name: 'code' } },
|
||||
{ key: 'chrome', icon: { type: 'icon', name: 'extension' } },
|
||||
{ key: 'claw', icon: { type: 'emoji', value: '🦞' } },
|
||||
]
|
||||
|
||||
@@ -76,10 +76,7 @@ import IMChannelPanel from '@/components/IMChannelPanel.vue';
|
||||
import AgentEmbedChannelPanel from '@/components/AgentEmbedChannelPanel.vue';
|
||||
import ChromeExtensionLanding from '@/views/integrations/ChromeExtensionLanding.vue';
|
||||
import ClawSkillLanding from '@/views/integrations/ClawSkillLanding.vue';
|
||||
|
||||
type IntegrationTab = 'im' | 'embed' | 'chrome' | 'claw';
|
||||
|
||||
const INTEGRATION_TABS: IntegrationTab[] = ['im', 'embed', 'chrome', 'claw'];
|
||||
import { INTEGRATION_PREVIEW_ITEMS, INTEGRATION_TABS, type IntegrationTab } from '@/config/integrations';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
@@ -94,12 +91,14 @@ const isLandingSection = computed(
|
||||
() => currentSection.value === 'chrome' || currentSection.value === 'claw',
|
||||
);
|
||||
|
||||
const navItems = computed(() => [
|
||||
{ key: 'im' as const, icon: 'chat-message', label: t('integrations.tabs.im') },
|
||||
{ key: 'embed' as const, icon: 'code', label: t('integrations.tabs.embed') },
|
||||
{ key: 'chrome' as const, icon: 'extension', label: t('integrations.tabs.chrome') },
|
||||
{ key: 'claw' as const, icon: '', emoji: '🦞', label: t('integrations.tabs.claw') },
|
||||
]);
|
||||
const navItems = computed(() =>
|
||||
INTEGRATION_PREVIEW_ITEMS.map((item) => ({
|
||||
key: item.key,
|
||||
icon: item.icon.type === 'icon' ? item.icon.name : '',
|
||||
emoji: item.icon.type === 'emoji' ? item.icon.value : undefined,
|
||||
label: t(`integrations.tabs.${item.key}`),
|
||||
})),
|
||||
);
|
||||
|
||||
function applyRouteQuery() {
|
||||
const tab = route.query.tab as string;
|
||||
|
||||
Reference in New Issue
Block a user