#抽屉
抽屉从页面边缘呈现补充任务或详情,在保留原页面上下文的同时提供更大的临时工作区。
安装Terminal
npm install @a3s-lab/ui<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('demo-drawer').showModal()">打开抽屉</button>
<dialog id="demo-drawer" class="drawer" aria-labelledby="demo-drawer-title" aria-describedby="demo-drawer-description">
<article>
<header class="mx-auto w-full max-w-sm">
<h2 id="demo-drawer-title">调整每日目标</h2>
<p id="demo-drawer-description">设置每天希望完成的活动目标。</p>
</header>
<section class="px-4">
<div class="mx-auto w-full max-w-sm">
<div class="flex items-center justify-center gap-6">
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="减少">...</button>
<div class="text-center">
<div class="text-7xl font-bold">350</div>
<div class="text-muted-foreground text-xs">千卡/天</div>
</div>
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="增加">...</button>
</div>
</div>
</section>
<footer class="mx-auto w-full max-w-sm">
<button type="button" class="btn" onclick="this.closest('dialog').close()">保存</button>
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">取消</button>
</footer>
</article>
</dialog>import "@a3s-lab/ui/a3s.css";
import "@a3s-lab/ui/all";#用法
引入 CSS
导入 Tailwind 和一个完整的 A3S UI 样式包。
@import "tailwindcss";
@import "@a3s-lab/ui/a3s.css";或者只导入基本CSS、抽屉组件CSS和一个样式包。
@import "tailwindcss";
@import "@a3s-lab/ui/base.css";
@import "@a3s-lab/ui/components/drawer.css";
@import "@a3s-lab/ui/styles/a3s.css";使用CDN或批量程序导入?请参见安装页。
引入 JavaScript
复制或提供完整的 A3S UI JavaScript 包。
<script src="/assets/js/all.min.js" defer></script>或者复制或提供A3S UI运行时和抽屉脚本。
<script src="/assets/js/runtime.min.js" defer></script>
<script src="/assets/js/drawer.min.js" defer></script>使用CDN或批量程序导入?请参见安装页。
添加抽屉 HTML
A3S UI 使用原生 <dialog> 实现抽屉,让浏览器直接负责模态语义、焦点约束和背景内容的不可交互状态。抽屉脚本补充了安全的遮罩点击关闭,并等待退出动效结束后再调用原生关闭方法。该组件不模拟拖动手势、吸附点或拖动手柄。
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('demo-drawer').showModal()">打开抽屉</button>
<dialog id="demo-drawer" class="drawer" aria-labelledby="demo-drawer-title" aria-describedby="demo-drawer-description">
<article>
<header class="mx-auto w-full max-w-sm">
<h2 id="demo-drawer-title">调整每日目标</h2>
<p id="demo-drawer-description">设置每天希望完成的活动目标。</p>
</header>
<section class="px-4">
<div class="mx-auto w-full max-w-sm">
<div class="flex items-center justify-center gap-6">
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="减少">...</button>
<div class="text-center">
<div class="text-7xl font-bold">350</div>
<div class="text-muted-foreground text-xs">千卡/天</div>
</div>
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="增加">...</button>
</div>
</div>
</section>
<footer class="mx-auto w-full max-w-sm">
<button type="button" class="btn" onclick="this.closest('dialog').close()">保存</button>
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">取消</button>
</footer>
</article>
</dialog>#HTML 结构
<button type="button" onclick="drawer.showModal()">可选- 触发按钮。A3S UI 直接使用原生
HTMLDialogElement.showModal()方法。 <dialog class="drawer" data-side="bottom|top|left|right">原生模态抽屉。省略
data-side时从底部打开;存在标题和说明时添加aria-labelledby与aria-describedby。JavaScript 会添加data-drawer-initialized标记并管理带动效的关闭过程。<article>抽屉内容表面。
<header>可选- 包含标题与可选说明的抽屉标题区。
<section>可选- 可滚动的主体内容区。
<footer>可选- 位于主体之后的操作区。
#JavaScript API
React 的 useDrawer 与 Vue 的同名 composable 暴露原生 showModal() 与增强后的 close(returnValue)。close() 会等待不超过 220ms 的退出过渡,并在减少动效环境中立即完成。
#示例
#侧面
使用data-side设置视口一侧。可用值为 top、right、bottom 和 left。
<div class="flex flex-wrap gap-2">
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('drawer-side-top').showModal()">顶部</button>
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('drawer-side-right').showModal()">右侧</button>
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('drawer-side-bottom').showModal()">底部</button>
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('drawer-side-left').showModal()">左侧</button>
</div>
<dialog id="drawer-side-top" class="drawer" data-side="top" aria-labelledby="drawer-side-top-title" aria-describedby="drawer-side-top-description">
<article class="max-h-[50vh]">
<header>
<h2 id="drawer-side-top-title">调整每日目标</h2>
<p id="drawer-side-top-description">设置每天希望完成的活动目标。</p>
</header>
<section class="px-4">
<dl class="grid gap-4 py-1">
<div><dt class="font-medium">每日完成任务</dt><dd class="text-sm text-muted-foreground">8 项</dd></div>
<div><dt class="font-medium">专注时长</dt><dd class="text-sm text-muted-foreground">90 分钟</dd></div>
<div><dt class="font-medium">提醒时间</dt><dd class="text-sm text-muted-foreground">工作日 17:30</dd></div>
</dl>
</section>
<footer>
<button type="button" class="btn" onclick="this.closest('dialog').close()">保存</button>
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">取消</button>
</footer>
</article>
</dialog>
<dialog id="drawer-side-right" class="drawer" data-side="right" aria-labelledby="drawer-side-right-title" aria-describedby="drawer-side-right-description">
<article>
<header>
<h2 id="drawer-side-right-title">调整每日目标</h2>
<p id="drawer-side-right-description">设置每天希望完成的活动目标。</p>
</header>
<section class="px-4">
<dl class="grid gap-4 py-1">
<div><dt class="font-medium">本周进度</dt><dd class="text-sm text-muted-foreground">已完成 31 / 40 项任务</dd></div>
<div><dt class="font-medium">连续达标</dt><dd class="text-sm text-muted-foreground">4 个工作日</dd></div>
<div><dt class="font-medium">调整建议</dt><dd class="text-sm text-muted-foreground">根据最近两周节奏,将目标保持在 8 项。</dd></div>
</dl>
</section>
<footer>
<button type="button" class="btn" onclick="this.closest('dialog').close()">保存</button>
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">取消</button>
</footer>
</article>
</dialog>
<dialog id="drawer-side-bottom" class="drawer" data-side="bottom" aria-labelledby="drawer-side-bottom-title" aria-describedby="drawer-side-bottom-description">
<article class="max-h-[50vh]">
<header>
<h2 id="drawer-side-bottom-title">调整每日目标</h2>
<p id="drawer-side-bottom-description">设置每天希望完成的活动目标。</p>
</header>
<section class="px-4">
<dl class="grid gap-4 py-1 sm:grid-cols-3">
<div><dt class="font-medium">任务目标</dt><dd class="text-sm text-muted-foreground">每天 8 项</dd></div>
<div><dt class="font-medium">专注目标</dt><dd class="text-sm text-muted-foreground">每天 90 分钟</dd></div>
<div><dt class="font-medium">休息日</dt><dd class="text-sm text-muted-foreground">周六、周日</dd></div>
</dl>
</section>
<footer>
<button type="button" class="btn" onclick="this.closest('dialog').close()">保存</button>
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">取消</button>
</footer>
</article>
</dialog>
<dialog id="drawer-side-left" class="drawer" data-side="left" aria-labelledby="drawer-side-left-title" aria-describedby="drawer-side-left-description">
<article>
<header>
<h2 id="drawer-side-left-title">调整每日目标</h2>
<p id="drawer-side-left-description">设置每天希望完成的活动目标。</p>
</header>
<section class="px-4">
<dl class="grid gap-4 py-1">
<div><dt class="font-medium">计算方式</dt><dd class="text-sm text-muted-foreground">仅统计已完成且通过验收的任务。</dd></div>
<div><dt class="font-medium">跨日规则</dt><dd class="text-sm text-muted-foreground">未完成任务不会自动计入第二天。</dd></div>
<div><dt class="font-medium">生效时间</dt><dd class="text-sm text-muted-foreground">保存后立即更新今天的进度。</dd></div>
</dl>
</section>
<footer>
<button type="button" class="btn" onclick="this.closest('dialog').close()">保存</button>
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">取消</button>
</footer>
</article>
</dialog>#可滚动内容
当抽屉主体滚动时,保持操作可见。
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('drawer-scrollable').showModal()">查看长内容</button>
<dialog id="drawer-scrollable" class="drawer" data-side="right" aria-labelledby="drawer-scrollable-title" aria-describedby="drawer-scrollable-description">
<article>
<header>
<h2 id="drawer-scrollable-title">调整每日目标</h2>
<p id="drawer-scrollable-description">设置每天希望完成的活动目标。</p>
</header>
<section class="px-4">
<div class="grid gap-6 py-1">
<div><h3 class="mb-1 font-medium">任务目标</h3><p class="text-sm leading-normal text-muted-foreground">每天完成 8 项经过验收的任务。草稿、已取消任务和失败的自动化不会计入。</p></div>
<div><h3 class="mb-1 font-medium">专注目标</h3><p class="text-sm leading-normal text-muted-foreground">累计 90 分钟专注时间。少于 5 分钟的片段会被忽略,避免误触影响统计。</p></div>
<div><h3 class="mb-1 font-medium">工作日</h3><p class="text-sm leading-normal text-muted-foreground">目标仅在周一至周五生效,法定节假日与手动标记的休息日不会中断连续记录。</p></div>
<div><h3 class="mb-1 font-medium">提醒</h3><p class="text-sm leading-normal text-muted-foreground">工作日 17:30 提醒一次;达到目标或当天已设为休息日时不再发送。</p></div>
<div><h3 class="mb-1 font-medium">统计范围</h3><p class="text-sm leading-normal text-muted-foreground">个人任务和当前团队项目都会纳入统计,归档项目中的历史任务不会被重新计算。</p></div>
<div><h3 class="mb-1 font-medium">生效方式</h3><p class="text-sm leading-normal text-muted-foreground">保存后立即更新今日进度,过往日期保持原始记录,方便比较目标调整前后的节奏。</p></div>
</div>
</section>
<footer>
<button type="button" class="btn" onclick="this.closest('dialog').close()">保存</button>
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">取消</button>
</footer>
</article>
</dialog>#RTL
抽屉侧面是物理视口侧面。内容间距和文本对齐遵循文档方向。
<div dir="rtl">
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('drawer-rtl').showModal()">فتح الدرج</button>
<dialog id="drawer-rtl" class="drawer" aria-labelledby="drawer-rtl-title" aria-describedby="drawer-rtl-description">
<article>
<header class="mx-auto w-full max-w-sm">
<h2 id="drawer-rtl-title">نقل الهدف</h2>
<p id="drawer-rtl-description">حدد هدف نشاطك اليومي.</p>
</header>
<section class="px-4">
<div class="mx-auto w-full max-w-sm">
<div class="flex items-center justify-center gap-6">
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="تقليل"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14" /></svg></button>
<div class="text-center">
<div class="text-7xl font-bold">٣٥٠</div>
<div class="text-muted-foreground text-xs uppercase">سعرات حرارية/يوم</div>
</div>
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="زيادة"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14" /><path d="M12 5v14" /></svg></button>
</div>
</div>
</section>
<footer class="mx-auto w-full max-w-sm">
<button type="button" class="btn" onclick="this.closest('dialog').close()">إرسال</button>
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">إلغاء</button>
</footer>
</article>
</dialog>
</div>