Components
Feedback
向用户传达状态、进度与结果的一组组件:内联 Alert、命令式 Toast、遮罩层 Modal / Drawer、Empty state、Spinner、页头 Banner 与轻量 Popover。交互组件按 data-ody-* 属性或 window.Odyssey API 驱动,亮/暗两色均走语义 token。
Alert
内联状态条 .ody-alert:左侧色条 + 图标 + .ody-alert__body(含可选 .ody-alert__title)。语义色修饰符 --ok / --warn / --down / --info;不加修饰符即中性提示。
<div class="ody-alert ody-alert--ok">
<svg class="ody-alert__ico" viewBox="0 0 24 24" ...></svg>
<div class="ody-alert__body">
<div class="ody-alert__title">保存成功</div>
你的更改已同步到所有设备。
</div>
</div>
<!-- 语义色:--ok / --warn / --down / --info,省略即中性 -->
<div class="ody-alert ody-alert--warn">…</div>
<div class="ody-alert ody-alert--down">…</div>
<div class="ody-alert ody-alert--info">…</div>可关闭 Alert
加入 .ody-alert__close 按钮即可提供关闭入口。库脚本的 data-ody-dismiss 仅接管 Modal / Drawer,Alert 的移除由宿主自行处理 —— 给关闭按钮一个 handler 切换 .is-dismissed(或 hidden)即可,CSS 已内置隐藏规则。
<div class="ody-alert ody-alert--info">
<svg class="ody-alert__ico" ...></svg>
<div class="ody-alert__body">
<div class="ody-alert__title">同步进行中</div>
点击右侧 × 可关闭这条提示。
</div>
<button class="ody-alert__close" type="button" aria-label="关闭提示"
onclick="this.closest('.ody-alert').classList.add('is-dismissed')">
<svg viewBox="0 0 24 24" ...><path d="M6 6l12 12M18 6L6 18"/></svg>
</button>
</div>Toast
短暂的浮层通知,通过命令式 API 触发:Odyssey.toast({title, message, tone, timeout})。脚本会自动创建固定于右下角的 .ody-toast-region 并堆叠 Toast,超时后自动消失,也可点 × 手动关闭。tone 取 ok / warn / down / info;timeout 单位毫秒(默认 4200,传 0 则常驻)。
<button class="ody-btn ody-btn--secondary"
onclick="Odyssey.toast({ title:'保存成功', message:'更改已同步。', tone:'ok' })">
成功 Toast
</button>
<script>
// 语义色:tone = 'ok' | 'warn' | 'down' | 'info'
Odyssey.toast({ title:'有新消息', message:'你有 3 条未读通知。', tone:'info' });
// timeout 单位毫秒;传 0 则常驻,需手动关闭
Odyssey.toast({ title:'常驻通知', message:'不会自动消失。', tone:'info', timeout:0 });
</script>Modal
居中对话框。触发器加 data-ody-open="#id" 打开对应的 .ody-modal 容器;关闭方式包括容器内的 data-ody-dismiss 按钮、点击 .ody-modal__backdrop 遮罩、或按 Esc。打开时锁定 body 滚动并做焦点捕获。尺寸修饰符 --sm / --lg。
<button class="ody-btn ody-btn--primary" data-ody-open="#demoModal">打开对话框</button>
<div class="ody-modal" id="demoModal" hidden>
<div class="ody-modal__backdrop"></div>
<div class="ody-modal__panel">
<div class="ody-modal__head">
<h2 class="ody-modal__title">删除工作区?</h2>
<button class="ody-iconbtn ody-iconbtn--sm" data-ody-dismiss aria-label="关闭">
<svg viewBox="0 0 24 24" ...><path d="M6 6l12 12M18 6L6 18"/></svg>
</button>
</div>
<div class="ody-modal__body">
<p>此操作不可撤销,工作区内的全部数据将被永久删除。</p>
</div>
<div class="ody-modal__foot">
<button class="ody-btn ody-btn--subtle" data-ody-dismiss>取消</button>
<button class="ody-btn ody-btn--danger" data-ody-dismiss>删除</button>
</div>
</div>
</div>
<!-- 尺寸:.ody-modal--sm / .ody-modal--lg -->Drawer
侧边抽屉,与 Modal 共用开合引擎:data-ody-open="#id" 打开 .ody-drawer,遮罩点击、data-ody-dismiss 或 Esc 关闭。默认从右侧滑入,加 --left 改为左侧。
<button class="ody-btn ody-btn--primary" data-ody-open="#demoDrawer">右侧抽屉</button>
<div class="ody-drawer" id="demoDrawer" hidden>
<div class="ody-drawer__backdrop"></div>
<div class="ody-drawer__panel">
<div class="ody-drawer__head">
<h2 class="ody-drawer__title">筛选</h2>
<button class="ody-iconbtn ody-iconbtn--sm" data-ody-dismiss aria-label="关闭">
<svg viewBox="0 0 24 24" ...><path d="M6 6l12 12M18 6L6 18"/></svg>
</button>
</div>
<div class="ody-drawer__body">抽屉内容……</div>
<div class="ody-drawer__foot">
<button class="ody-btn ody-btn--subtle" data-ody-dismiss>重置</button>
<button class="ody-btn ody-btn--primary" data-ody-dismiss>应用</button>
</div>
</div>
</div>
<!-- 从左侧滑入:.ody-drawer--left -->Empty state
空数据占位 .ody-empty:__ico 图标 + __title 标题 + __text 说明,通常附带一个引导操作。适用于列表为空、搜索无结果等场景。
还没有任何项目
创建你的第一个项目,开始协作与追踪进度。
<div class="ody-empty">
<div class="ody-empty__ico">
<svg viewBox="0 0 24 24" ...></svg>
</div>
<h3 class="ody-empty__title">还没有任何项目</h3>
<p class="ody-empty__text">创建你的第一个项目,开始协作与追踪进度。</p>
<button class="ody-btn ody-btn--primary">新建项目</button>
</div>Spinner
加载指示器 .ody-spinner,尺寸修饰符 --sm / --lg。按钮的加载态直接用 .is-loading —— 它会隐藏文字并内嵌自旋圈,同时禁用点击。
<span class="ody-spinner ody-spinner--sm" role="status" aria-label="加载中"></span>
<span class="ody-spinner" role="status" aria-label="加载中"></span>
<span class="ody-spinner ody-spinner--lg" role="status" aria-label="加载中"></span>
<!-- 按钮加载态 -->
<button class="ody-btn ody-btn--primary is-loading">保存中</button>Banner
横跨容器宽度的通栏提示 .ody-banner,默认强调色,修饰符 --warn / --down。用 .ody-banner__spacer 把操作推到右侧;.ody-banner[hidden] 内置隐藏,可作可关闭横幅。
<div class="ody-banner">
<svg width="18" height="18" ...></svg>
<span>新版本 v1.0.0 已发布 —— 查看更新内容。</span>
<span class="ody-banner__spacer"></span>
<a class="ody-btn ody-btn--sm ody-btn--secondary" href="#">查看</a>
<button class="ody-iconbtn ody-iconbtn--sm" aria-label="关闭横幅"
onclick="this.closest('.ody-banner').hidden=true">
<svg viewBox="0 0 24 24" ...><path d="M6 6l12 12M18 6L6 18"/></svg>
</button>
</div>
<!-- 语义色:.ody-banner--warn / .ody-banner--down -->Popover
点击触发的轻量浮层。触发器加 data-ody-popover="#id",目标 .ody-popover(可含 __title)显隐由脚本切换 .is-open;点击外部或按 Esc 关闭。把触发器与浮层放进同一个相对定位容器,即可让浮层贴着触发器展开。
构建 #482
在 main 分支上耗时 1m 12s 通过全部 128 项检查。
图标按钮同样可作触发器,浮层内容随意排版。
<span style="position:relative;display:inline-flex">
<button class="ody-btn ody-btn--secondary" data-ody-popover="#demoPop">显示详情</button>
<div class="ody-popover" id="demoPop" style="top:calc(100% + 8px);left:0" hidden>
<p class="ody-popover__title">构建 #482</p>
<p>在 main 分支上耗时 1m 12s 通过全部 128 项检查。</p>
</div>
</span>