agent-Specialization/docs/sidebar_overlay_demo.html

582 lines
19 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Sidebar Reveal Demo</title>
<style>
:root {
color-scheme: light;
--bg: #ffffff;
--sidebar-bg: #fbfbfb;
--text: #111111;
--muted: #8f8f8f;
--border: rgba(0, 0, 0, 0.09);
--hover: rgba(0, 0, 0, 0.055);
--hover-strong: rgba(0, 0, 0, 0.095);
--active: rgba(0, 0, 0, 0.078);
--menu-bg: #ffffff;
--menu-shadow: 0 12px 28px rgba(0, 0, 0, 0.14);
--collapsed-width: 52px;
--expanded-width: 306px;
--icon-cell: 40px;
--row-height: 40px;
--row-radius: 12px;
--anim: 320ms cubic-bezier(0.22, 1, 0.36, 1);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
:root[data-theme="dark"] {
color-scheme: dark;
--bg: #1a1a1a;
--sidebar-bg: #0f0f0f;
--text: #f4f4f4;
--muted: #8f8f8f;
--border: rgba(255, 255, 255, 0.105);
--hover: rgba(255, 255, 255, 0.078);
--hover-strong: rgba(255, 255, 255, 0.13);
--active: rgba(255, 255, 255, 0.105);
--menu-bg: #202020;
--menu-shadow: 0 18px 36px rgba(0, 0, 0, 0.42);
}
* { box-sizing: border-box; }
body {
margin: 0;
height: 100vh;
background: var(--bg);
color: var(--text);
overflow: hidden;
}
button,
input {
font: inherit;
}
.app {
position: relative;
height: 100vh;
background: var(--bg);
}
.main {
min-width: 0;
height: 100vh;
margin-left: var(--collapsed-width);
display: flex;
align-items: flex-start;
justify-content: center;
padding: 28px 32px;
}
.main-title {
margin: 0;
font-size: 34px;
line-height: 1.1;
font-weight: 620;
letter-spacing: -0.04em;
}
.main-title span {
color: var(--muted);
font-weight: 430;
}
.sidebar-frame {
position: fixed;
left: 0;
top: 0;
z-index: 50;
width: var(--collapsed-width);
height: 100vh;
overflow: hidden;
background: var(--sidebar-bg);
border-right: 1px solid var(--border);
transition: width var(--anim), background 160ms ease, border-color 160ms ease;
will-change: width;
}
.sidebar-frame.open {
width: var(--expanded-width);
}
.sidebar-content {
width: var(--expanded-width);
height: 100%;
background: var(--sidebar-bg);
display: flex;
flex-direction: column;
padding: 18px 8px 14px 6px;
transition: background 160ms ease;
}
.sidebar-top {
display: flex;
flex-direction: column;
gap: 2px;
flex: 0 0 auto;
}
.sidebar-bottom {
margin-top: 8px;
flex: 0 0 auto;
}
.side-row,
.search-shell,
.conversation-row {
width: 100%;
min-height: var(--row-height);
border: 0;
border-radius: var(--row-radius);
background: transparent;
color: var(--text);
display: grid;
grid-template-columns: var(--icon-cell) minmax(0, 1fr);
align-items: center;
padding: 0;
text-align: left;
}
.side-row,
.conversation-row {
cursor: pointer;
transition: background 140ms ease, color 140ms ease;
}
.side-row:hover,
.conversation-row:hover {
background: transparent;
}
.side-icon {
width: var(--icon-cell);
height: var(--row-height);
border-radius: var(--row-radius);
display: grid;
place-items: center;
color: currentColor;
flex: 0 0 auto;
transition: background 140ms ease;
}
.sidebar-frame:not(.open) .side-row:hover .side-icon,
.sidebar-frame:not(.open) .theme-toggle:hover .side-icon,
.sidebar-frame:not(.open) .conversation-row:hover .side-icon {
background: var(--hover);
}
.sidebar-frame.open .side-row:hover,
.sidebar-frame.open .side-row.active,
.sidebar-frame.open .conversation-row:hover,
.sidebar-frame.open .conversation-row.menu-open {
background: var(--hover);
}
.side-icon svg {
display: block;
stroke: currentColor;
flex: 0 0 auto;
}
/* 保留视觉校准:按钮容器统一,图标真实尺寸不机械统一 */
.side-icon--chat svg { width: 29px; height: 29px; }
.side-icon--pencil svg { width: 22px; height: 22px; }
.side-icon--workspace svg { width: 23px; height: 23px; }
.side-icon--monitor svg { width: 23px; height: 23px; }
.side-icon--theme svg { width: 22px; height: 22px; }
.side-icon--user svg { width: 22px; height: 22px; }
.side-label,
.search-area,
.conversation-list,
.conversation-title,
.conversation-more {
opacity: 0;
transition: opacity 220ms ease;
transition-delay: 0ms;
}
.sidebar-frame.open .side-label,
.sidebar-frame.open .search-area,
.sidebar-frame.open .conversation-list,
.sidebar-frame.open .conversation-title,
.sidebar-frame.open .conversation-more {
opacity: 1;
transition-delay: 80ms;
}
.side-label {
min-width: 0;
padding-left: 8px;
padding-right: 12px;
font-size: 15px;
line-height: 1;
letter-spacing: -0.015em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.search-area {
flex: 0 0 auto;
margin-top: 10px;
}
.search-shell {
height: var(--row-height);
overflow: hidden;
transition: background 140ms ease;
}
.search-shell:focus-within,
.search-shell:hover {
background: var(--hover);
}
.search-input {
min-width: 0;
width: calc(var(--expanded-width) - var(--icon-cell) - 22px);
height: var(--row-height);
border: 0;
outline: 0;
padding: 0 12px 0 8px;
background: transparent;
color: var(--text);
font-size: 14px;
letter-spacing: -0.01em;
}
.search-input::placeholder {
color: var(--muted);
}
.conversation-list {
min-height: 0;
flex: 1 1 auto;
overflow-y: auto;
padding-top: 12px;
padding-right: 0;
scrollbar-width: thin;
}
.conversation-row {
position: relative;
grid-template-columns: minmax(0, 1fr) 40px;
min-height: var(--row-height);
margin: 0;
}
.conversation-title {
min-width: 0;
padding-left: 12px;
padding-right: 4px;
color: var(--text);
font-size: 14px;
line-height: 1.25;
letter-spacing: -0.01em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.conversation-more {
width: 30px;
height: 30px;
border: 0;
border-radius: 9px;
background: transparent;
color: var(--muted);
display: grid;
place-items: center;
cursor: pointer;
justify-self: start;
visibility: hidden;
transition: background 140ms ease, color 140ms ease, opacity 160ms ease;
}
.conversation-row:hover .conversation-more,
.conversation-row.menu-open .conversation-more {
visibility: visible;
}
.conversation-more:hover,
.conversation-row.menu-open .conversation-more {
background: transparent;
color: var(--text);
}
.conversation-more svg {
width: 22px;
height: 22px;
stroke: currentColor;
}
.context-menu {
position: fixed;
z-index: 80;
width: max-content;
min-width: 92px;
padding: 6px;
border: 1px solid var(--border);
border-radius: 14px;
background: var(--menu-bg);
box-shadow: var(--menu-shadow);
opacity: 0;
transform: translateY(-4px) scale(0.98);
pointer-events: none;
transition:
opacity 120ms ease,
transform 150ms cubic-bezier(0.22, 1, 0.36, 1),
background 160ms ease,
border-color 160ms ease;
}
.context-menu.open {
opacity: 1;
transform: translateY(0) scale(1);
pointer-events: auto;
}
.menu-button {
width: max-content;
min-width: 80px;
height: 36px;
border: 0;
border-radius: 10px;
background: transparent;
color: var(--text);
display: grid;
grid-template-columns: 24px auto;
align-items: center;
padding: 0 10px 0 8px;
text-align: left;
cursor: pointer;
font-size: 14px;
letter-spacing: -0.01em;
transition: background 140ms ease;
}
.menu-button:hover {
background: var(--hover);
}
.menu-button.danger {
color: #d6483b;
}
:root[data-theme="dark"] .menu-button.danger {
color: #ff7b72;
}
.menu-button svg {
width: 18px;
height: 18px;
stroke: currentColor;
}
.demo-hint {
position: fixed;
left: calc(var(--collapsed-width) + 20px);
bottom: 18px;
color: var(--muted);
font-size: 13px;
pointer-events: none;
}
</style>
</head>
<body>
<div class="app">
<aside class="sidebar-frame" id="sidebar" aria-label="对话侧边栏">
<div class="sidebar-content">
<div class="sidebar-top">
<button class="side-row" id="toggleSidebar" type="button" aria-label="展开对话记录" aria-expanded="false">
<span class="side-icon side-icon--chat" aria-hidden="true">
<svg viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 6.5c0-1.38 1.12-2.5 2.5-2.5h13c1.38 0 2.5 1.12 2.5 2.5v8.5c0 1.38-1.12 2.5-2.5 2.5h-5.6l-3.4 3.2.6-3.2H7.5c-1.38 0-2.5-1.12-2.5-2.5V6.5z" stroke-width="1.45" stroke-linejoin="round" />
<path d="M9 9.5h10" stroke-width="1.45" stroke-linecap="round" />
<path d="M9 13h6" stroke-width="1.45" stroke-linecap="round" />
</svg>
</span>
<span class="side-label">对话记录</span>
</button>
<button class="side-row" type="button" aria-label="新建对话">
<span class="side-icon side-icon--pencil" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
<path d="m15 5 4 4" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</span>
<span class="side-label">新建对话</span>
</button>
<button class="side-row" type="button" aria-label="工作区">
<span class="side-icon side-icon--workspace" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 3 4.5 7.15v9.7L12 21l7.5-4.15v-9.7L12 3Z" stroke-width="1.55" stroke-linejoin="round" />
<path d="M4.8 7.35 12 11.5l7.2-4.15M12 11.5V21" stroke-width="1.55" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</span>
<span class="side-label">工作区</span>
</button>
<button class="side-row" type="button" aria-label="虚拟显示器">
<span class="side-icon side-icon--monitor" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="3.5" y="5" width="17" height="12" rx="2" stroke-width="1.55" />
<path d="M9 21h6M12 17v4" stroke-width="1.55" stroke-linecap="round" />
</svg>
</span>
<span class="side-label">虚拟显示器</span>
</button>
</div>
<div class="search-area" aria-label="搜索对话">
<label class="search-shell">
<span class="side-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="width:22px;height:22px;stroke:currentColor">
<circle cx="11" cy="11" r="7" stroke-width="1.55" />
<path d="m20 20-3.4-3.4" stroke-width="1.55" stroke-linecap="round" />
</svg>
</span>
<input class="search-input" type="search" placeholder="搜索对话" />
</label>
</div>
<div class="conversation-list" aria-label="对话记录">
<div class="conversation-row" data-title="随意的 py 代码块">
<span class="conversation-title">随意的 py 代码块</span>
<button class="conversation-more" type="button" aria-label="更多操作">
<svg viewBox="0 0 24 24" fill="none"><path d="M6 12h.01M12 12h.01M18 12h.01" stroke-width="3" stroke-linecap="round" /></svg>
</button>
</div>
<div class="conversation-row" data-title="前端现代化设计讨论">
<span class="conversation-title">前端现代化设计讨论</span>
<button class="conversation-more" type="button" aria-label="更多操作">
<svg viewBox="0 0 24 24" fill="none"><path d="M6 12h.01M12 12h.01M18 12h.01" stroke-width="3" stroke-linecap="round" /></svg>
</button>
</div>
<div class="conversation-row" data-title="审批面板覆盖窗口方案">
<span class="conversation-title">审批面板覆盖窗口方案</span>
<button class="conversation-more" type="button" aria-label="更多操作">
<svg viewBox="0 0 24 24" fill="none"><path d="M6 12h.01M12 12h.01M18 12h.01" stroke-width="3" stroke-linecap="round" /></svg>
</button>
</div>
<div class="conversation-row" data-title="CLI 显示层细节修复">
<span class="conversation-title">CLI 显示层细节修复</span>
<button class="conversation-more" type="button" aria-label="更多操作">
<svg viewBox="0 0 24 24" fill="none"><path d="M6 12h.01M12 12h.01M18 12h.01" stroke-width="3" stroke-linecap="round" /></svg>
</button>
</div>
<div class="conversation-row" data-title="个人空间设置页重设计">
<span class="conversation-title">个人空间设置页重设计</span>
<button class="conversation-more" type="button" aria-label="更多操作">
<svg viewBox="0 0 24 24" fill="none"><path d="M6 12h.01M12 12h.01M18 12h.01" stroke-width="3" stroke-linecap="round" /></svg>
</button>
</div>
</div>
<div class="sidebar-bottom">
<button class="side-row theme-toggle" id="themeToggle" type="button" aria-label="切换深色模式">
<span class="side-icon side-icon--theme" aria-hidden="true">
<svg class="theme-icon theme-icon-light" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="4" stroke-width="1.55" />
<path d="M12 2.8v2.3M12 18.9v2.3M4.2 4.2l1.6 1.6M18.2 18.2l1.6 1.6M2.8 12h2.3M18.9 12h2.3M4.2 19.8l1.6-1.6M18.2 5.8l1.6-1.6" stroke-width="1.55" stroke-linecap="round" />
</svg>
</span>
<span class="side-label">浅色模式</span>
</button>
<button class="side-row" type="button" aria-label="个人空间">
<span class="side-icon side-icon--user" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="8" r="4" stroke-width="1.6" />
<path d="M4.5 21a7.5 7.5 0 0 1 15 0" stroke-width="1.6" stroke-linecap="round" />
</svg>
</span>
<span class="side-label">个人空间</span>
</button>
</div>
</div>
</aside>
<main class="main">
<h1 class="main-title">AI Agent <span>Auto</span></h1>
</main>
<div class="context-menu" id="contextMenu" role="menu" aria-hidden="true">
<button class="menu-button" type="button" role="menuitem">
<svg viewBox="0 0 24 24" fill="none"><rect x="8" y="8" width="11" height="11" rx="2" stroke-width="1.55"/><path d="M5 15V7a2 2 0 0 1 2-2h8" stroke-width="1.55" stroke-linecap="round"/></svg>
<span>复制</span>
</button>
<button class="menu-button danger" type="button" role="menuitem">
<svg viewBox="0 0 24 24" fill="none"><path d="M4 7h16M10 11v6M14 11v6M6 7l1 14h10l1-14M9 7V4h6v3" stroke-width="1.55" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span>删除</span>
</button>
</div>
</div>
<div class="demo-hint">点击第一个按钮折叠/露出;对话行 hover 后显示 ...;点击 ... 打开二级菜单。</div>
<script>
const sidebar = document.getElementById('sidebar');
const toggle = document.getElementById('toggleSidebar');
const themeToggle = document.getElementById('themeToggle');
const contextMenu = document.getElementById('contextMenu');
let menuOwner = null;
toggle.addEventListener('click', () => {
const isOpen = sidebar.classList.toggle('open');
toggle.setAttribute('aria-expanded', String(isOpen));
toggle.setAttribute('aria-label', isOpen ? '收起对话记录' : '展开对话记录');
if (!isOpen) closeMenu();
});
themeToggle.addEventListener('click', () => {
const root = document.documentElement;
const next = root.dataset.theme === 'dark' ? 'light' : 'dark';
root.dataset.theme = next;
themeToggle.querySelector('.side-label').textContent = next === 'dark' ? '深色模式' : '浅色模式';
themeToggle.setAttribute('aria-label', next === 'dark' ? '切换浅色模式' : '切换深色模式');
});
function closeMenu() {
contextMenu.classList.remove('open');
contextMenu.setAttribute('aria-hidden', 'true');
if (menuOwner) menuOwner.classList.remove('menu-open');
menuOwner = null;
}
document.querySelectorAll('.conversation-more').forEach((button) => {
button.addEventListener('click', (event) => {
event.stopPropagation();
const row = button.closest('.conversation-row');
if (menuOwner === row && contextMenu.classList.contains('open')) {
closeMenu();
return;
}
closeMenu();
menuOwner = row;
row.classList.add('menu-open');
const rect = button.getBoundingClientRect();
contextMenu.style.left = '0px';
contextMenu.style.top = `${rect.bottom + 6}px`;
contextMenu.classList.add('open');
contextMenu.setAttribute('aria-hidden', 'false');
const menuWidth = contextMenu.offsetWidth;
contextMenu.style.left = `${Math.min(rect.right - menuWidth, window.innerWidth - menuWidth - 12)}px`;
});
});
document.addEventListener('click', (event) => {
if (!contextMenu.contains(event.target)) closeMenu();
});
window.addEventListener('resize', closeMenu);
</script>
</body>
</html>