Sheet 侧边抽屉

基于 BrConfigProvider 实现全局主题配置,支持多方向弹出、模态锁定、键盘无障碍的企业级侧边抽屉组件,用于表单编辑、详情查看、菜单展开等场景。

组件特性

  • 🧭 多方向弹出:支持上、下、左、右四个方向滑出,灵活适应各种页面布局。
  • ♿️ 无障碍访问:强制聚焦不可穿透,支持 ESC 关闭、Enter 确认、Tab 键切换焦点。
  • 📱 响应式设计:小屏全屏自适应,大屏固定宽度,提供优秀的跨设备体验。
  • 🔌 灵活集成:与 BrButtonBrFormBrTable 等无缝集成。
  • 🔄 状态管理:支持 v-model 双向绑定控制抽屉的显示与隐藏。

基础用法

最基础的抽屉,默认从右侧弹出。

<script setup lang="ts">
import { BrButton, BrSheet, BrSheetTrigger, BrSheetContent, BrSheetHeader, BrSheetTitle, BrSheetDescription, BrSheetFooter, BrSheetClose } from '@breezeui/vue'
</script>

<template>
  <BrSheet>
    <BrSheetTrigger as-child>
      <BrButton variant="outline">Open Sheet</BrButton>
    </BrSheetTrigger>
    <BrSheetContent>
      <BrSheetHeader>
        <BrSheetTitle>Edit Profile</BrSheetTitle>
        <BrSheetDescription>
          Make changes to your profile here. Click save when you're done.
        </BrSheetDescription>
      </BrSheetHeader>
      <div class="grid gap-4 py-4">
        <div class="h-32 rounded-md border border-dashed flex items-center justify-center text-muted-foreground text-sm">
          Form Content
        </div>
      </div>
      <BrSheetFooter>
        <BrSheetClose as-child>
          <BrButton type="submit">Save changes</BrButton>
        </BrSheetClose>
      </BrSheetFooter>
    </BrSheetContent>
  </BrSheet>
</template>

侧边方向 (Directions)

通过 side 属性设置抽屉从上、下、左、右四个方向滑出。

<script setup lang="ts">
import { BrButton, BrSheet, BrSheetTrigger, BrSheetContent, BrSheetHeader, BrSheetTitle } from '@breezeui/vue'

const sides = ['top', 'right', 'bottom', 'left'] as const
</script>

<template>
  <div class="flex flex-wrap gap-4">
    <BrSheet v-for="side in sides" :key="side">
      <BrSheetTrigger as-child>
        <BrButton variant="outline" class="capitalize">{{ side }}</BrButton>
      </BrSheetTrigger>
      <BrSheetContent :side="side">
        <BrSheetHeader>
          <BrSheetTitle>Sheet - {{ side }}</BrSheetTitle>
        </BrSheetHeader>
        <div class="py-4 text-sm text-muted-foreground">
          Sheet content sliding from the {{ side }}.
        </div>
      </BrSheetContent>
    </BrSheet>
  </div>
</template>

自定义尺寸

支持设置抽屉的宽度或高度,适配不同内容的展示需求。

<script setup lang="ts">
import { BrButton, BrSheet, BrSheetTrigger, BrSheetContent, BrSheetHeader, BrSheetTitle } from '@breezeui/vue'
</script>

<template>
  <div class="flex flex-wrap gap-4">
    <BrSheet>
      <BrSheetTrigger as-child>
        <BrButton variant="outline">Custom Width (700px)</BrButton>
      </BrSheetTrigger>
      <BrSheetContent class="sm:max-w-[700px]">
        <BrSheetHeader>
          <BrSheetTitle>Wide Sheet</BrSheetTitle>
        </BrSheetHeader>
        <div class="py-4 text-sm text-muted-foreground leading-relaxed">
          Customize the maximum width of the sheet on large screens using the Tailwind class <code>sm:max-w-[700px]</code>. This is useful when you need to display wider forms or complex data.
        </div>
      </BrSheetContent>
    </BrSheet>

    <BrSheet>
      <BrSheetTrigger as-child>
        <BrButton variant="outline">Full Screen Sheet</BrButton>
      </BrSheetTrigger>
      <BrSheetContent class="w-screen sm:max-w-none">
        <BrSheetHeader>
          <BrSheetTitle>Full Screen Display</BrSheetTitle>
        </BrSheetHeader>
        <div class="py-4 text-sm text-muted-foreground leading-relaxed">
          Override the default max-width limit with <code>w-screen sm:max-w-none</code> to make the sheet occupy the entire screen.
        </div>
      </BrSheetContent>
    </BrSheet>
  </div>
</template>

滚动区域

当内容较多时,可以在抽屉内部使用 BrScrollArea 组件实现局部滚动。

<script setup lang="ts">
import { BrButton, BrSheet, BrSheetTrigger, BrSheetContent, BrSheetHeader, BrSheetTitle, BrScrollArea } from '@breezeui/vue'
</script>

<template>
  <BrSheet>
    <BrSheetTrigger as-child>
      <BrButton variant="outline">With Long Content</BrButton>
    </BrSheetTrigger>
    <BrSheetContent class="flex flex-col">
      <BrSheetHeader>
        <BrSheetTitle>Terms of Service</BrSheetTitle>
      </BrSheetHeader>
      
      <BrScrollArea class="flex-1 -mx-6 px-6 mt-4">
        <div class="space-y-4 pb-4">
          <p v-for="i in 10" :key="i" class="text-sm text-muted-foreground leading-relaxed">
            This is a placeholder text to demonstrate scrolling of long content. In a real application, detailed terms of service, privacy policies, or complex forms with many fields might be placed here. Using the BrScrollArea component ensures the content scrolls elegantly within the limited space, while keeping the header title fixed and visible. This is paragraph {{ i }}.
          </p>
        </div>
      </BrScrollArea>
    </BrSheetContent>
  </BrSheet>
</template>

异步与交互控制 (Async & Controlled)

结合受控模式,可以在提交表单或执行异步操作时锁定抽屉。

<script setup lang="ts">
import { ref } from 'vue'
import { BrButton, BrSheet, BrSheetTrigger, BrSheetContent, BrSheetHeader, BrSheetTitle, BrSheetDescription, BrSheetFooter } from '@breezeui/vue'

const open = ref(false)
const loading = ref(false)

const handleSave = async () => {
  loading.value = true
  // 模拟异步请求
  await new Promise(resolve => setTimeout(resolve, 1500))
  loading.value = false
  open.value = false
}
</script>

<template>
  <BrSheet v-model:open="open">
    <BrSheetTrigger as-child>
      <BrButton variant="outline">Async Submit</BrButton>
    </BrSheetTrigger>
    <BrSheetContent 
      @interact-outside="(e) => loading && e.preventDefault()" 
      @escape-key-down="(e) => loading && e.preventDefault()"
    >
      <BrSheetHeader>
        <BrSheetTitle>Async Operation</BrSheetTitle>
        <BrSheetDescription>
          Clicking save will wait 1.5 seconds before closing the sheet. During this time, closing by clicking outside or pressing ESC is disabled.
        </BrSheetDescription>
      </BrSheetHeader>
      
      <div class="py-8 flex justify-center">
        <div v-if="loading" class="text-sm text-muted-foreground animate-pulse">
          Processing, please wait...
        </div>
        <div v-else class="text-sm text-muted-foreground">
          Ready, click save below to start.
        </div>
      </div>

      <BrSheetFooter>
        <BrButton :loading="loading" @click="handleSave">Save</BrButton>
      </BrSheetFooter>
    </BrSheetContent>
  </BrSheet>
</template>

API 参考

BrSheet

属性类型默认值说明
openbooleanfalse控制抽屉的显示与隐藏,支持 v-model:open 双向绑定
defaultOpenbooleanfalse初始显示状态(非受控模式)
modalbooleantrue是否为模态抽屉(显示遮罩层,禁止背后交互)

BrSheetContent

属性类型默认值说明
side'top' | 'right' | 'bottom' | 'left''right'抽屉弹出的方向
showClosebooleantrue是否显示右上角的关闭按钮
classstring-自定义类名,用于覆盖默认样式

事件 (BrSheetContent)

事件名类型说明
escapeKeyDown[event: KeyboardEvent]按下 ESC 键时触发,可调用 event.preventDefault() 阻止默认关闭行为
pointerDownOutside[event: PointerDownOutsideEvent]点击外部区域时触发,可调用 event.preventDefault() 阻止默认关闭行为
interactOutside[event: Event]与外部区域交互时触发(综合焦点和指针)

SheetManager

导出 getZIndex(), nextZIndex(), resetZIndex() 供开发者处理复杂的层级关系。

主题定制

BrSheet 组件的样式完全依赖于 BrConfigProvider 提供的全局设计 Token,你可以在项目的入口处或任意层级覆写 CSS 变量实现定制:

:root {
  /* 覆盖抽屉背景色 */
  --background: 0 0% 100%;
  /* 覆盖圆角大小 */
  --radius: 0.5rem;
  /* 覆盖阴影样式 */
  --shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1);
  /* 动画过渡时间 */
  --transition-duration: 300ms;
}

.dark {
  --background: 222.2 84% 4.9%;
}

运行说明与注意事项

  1. 模态规则:默认 modal="true",背景内容无法滚动和交互,焦点被锁定在抽屉内部。
  2. z-index 管理:多实例下,遮罩和内容的 z-index 会通过 SheetManager 自动递增(基础为 50)。
  3. ESC快捷键:默认支持 ESC 键关闭,可在 BrSheetContent 上监听 @escapeKeyDown 阻止默认行为。
  4. 外部点击关闭:点击遮罩层默认关闭抽屉,可通过监听 @interactOutside="(e) => e.preventDefault()" 禁用。
  5. 异步关闭:结合 v-model:open 控制,可禁用按钮和外部点击等手段保证异步请求的安全执行。