582 lines
19 KiB
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>
|