Resizable 可调整面板

允许用户通过拖拽调整容器大小的组件,支持 2D 调整、比例锁定以及面板分割。

组件特性

  • ↔️ 8 个 resize 方向:支持上、下、左、右及其四角组合。
  • 📐 比例锁定:支持在调整大小时锁定宽高比。
  • 📊 网格吸附:支持基于网格的精确对齐吸附。
  • ⌨️ 键盘调整:支持基于键盘的调整大小操作。
  • 📋 面板拆分:支持拖拽拆分面板,带调整手柄。
  • 📏 尺寸约束:支持 minWidthmaxWidthminHeightmaxHeight 约束。
  • 🎨 主题定制:基于 BrConfigProvider 支持全局主题配置和 TailwindCSS 局部覆盖。

基础用法

支持在水平或垂直方向上调整大小,也可以在多个方向上同时调整。

<script setup lang="ts">
import { ref } from 'vue'
import { BrResizable, BrResizableHandle } from '@breezeui/vue'

const width = ref(300)
const height = ref(200)
</script>

<template>
  <BrResizable
    v-model:width="width"
    v-model:height="height"
    :min-width="150"
    :min-height="100"
    :max-width="500"
    :max-height="400"
    class="border border-border rounded-lg bg-muted/20 flex items-center justify-center relative shadow-sm"
  >
    <div class="text-center">
      <div class="font-medium text-foreground">Basic Resizable</div>
      <div class="text-sm text-muted-foreground">{{ Math.round(width) }}px x {{ Math.round(height) }}px</div>
    </div>
    <template #handle="{ onDragStart }">
      <BrResizableHandle direction="right" class="bg-primary/50 hover:bg-primary w-2" @drag-start="onDragStart($event, 'right')" />
      <BrResizableHandle direction="bottom" class="bg-primary/50 hover:bg-primary h-2" @drag-start="onDragStart($event, 'bottom')" />
      <BrResizableHandle direction="bottom-right" class="bg-primary/50 hover:bg-primary w-4 h-4 rounded-tl-lg" @drag-start="onDragStart($event, 'bottom-right')" />
    </template>
  </BrResizable>
</template>

锁定比例

通过设置 lock-aspect-ratio 属性可以锁定调整时的宽高比。

<script setup lang="ts">
import { ref } from 'vue'
import { BrResizable } from '@breezeui/vue'

const width = ref(300)
const height = ref(168.75) // 16:9 ratio
</script>

<template>
  <BrResizable
    v-model:width="width"
    v-model:height="height"
    :lock-aspect-ratio="16/9"
    :min-width="200"
    :max-width="600"
    class="border border-border rounded-lg overflow-hidden relative shadow-sm"
  >
    <img 
      src="https://images.unsplash.com/photo-1682687220063-4742bd7fd538?q=80&w=2070&auto=format&fit=crop" 
      class="w-full h-full object-cover pointer-events-none"
      alt="demo"
    />
    <div class="absolute inset-0 flex items-center justify-center bg-black/30 text-white font-medium">
      16:9 ({{ Math.round(width) }} x {{ Math.round(height) }})
    </div>
  </BrResizable>
</template>

面板分割

结合 BrResizableSplitBrResizableSplitPane 可以创建多面板分割布局。

<script setup lang="ts">
import {
  BrResizableSplit,
  BrResizableSplitPane,
  BrResizableHandle
} from '@breezeui/vue'
</script>

<template>
  <BrResizableSplit direction="horizontal" class="h-[300px] w-full rounded-lg border shadow-sm">
    <BrResizableSplitPane :default-size="25" :min-size="15">
      <div class="flex h-full flex-col bg-muted/10">
        <div class="p-4 font-medium border-b">Sidebar</div>
        <div class="p-4 text-sm text-muted-foreground flex-1">Keyboard navigation supported</div>
      </div>
    </BrResizableSplitPane>
    
    <BrResizableHandle split with-handle />
    
    <BrResizableSplitPane :default-size="75">
      <BrResizableSplit direction="vertical">
        <BrResizableSplitPane :default-size="60">
          <div class="flex h-full items-center justify-center bg-background">
            <span class="font-medium text-foreground">Main Content</span>
          </div>
        </BrResizableSplitPane>
        
        <BrResizableHandle split with-handle class="hover:bg-primary/50 transition-colors" />
        
        <BrResizableSplitPane :default-size="40">
          <div class="flex h-full items-center justify-center bg-muted/20">
            <span class="font-medium text-muted-foreground">Terminal / Console</span>
          </div>
        </BrResizableSplitPane>
      </BrResizableSplit>
    </BrResizableSplitPane>
  </BrResizableSplit>
</template>

与表格组件集成

Resizable 组件可以轻松与表格组件集成,实现自适应宽度和高度的数据网格。

<script setup lang="ts">
import { ref } from 'vue'
import { BrResizable, BrCard, BrTable } from '@breezeui/vue'

const tableWidth = ref(450)
const tableHeight = ref(300)

const tableData = [
  { id: 1, name: 'John Doe', role: 'Developer' },
  { id: 2, name: 'Jane Smith', role: 'Designer' },
  { id: 3, name: 'Mike Johnson', role: 'Manager' },
]

const columns = [
  { key: 'id', title: 'ID' },
  { key: 'name', title: 'Name' },
  { key: 'role', title: 'Role' },
]
</script>

<template>
  <BrResizable
    v-model:width="tableWidth"
    v-model:height="tableHeight"
    :min-width="300"
    :min-height="200"
    class="flex flex-col"
  >
    <BrCard class="w-full h-full flex flex-col overflow-hidden">
      <div class="p-4 border-b font-medium bg-muted/30">Resizable Table Card</div>
      <div class="flex-1 overflow-auto p-4">
        <BrTable :data="tableData" :columns="columns" />
      </div>
    </BrCard>
  </BrResizable>
</template>

与卡片组件集成

将卡片放入可调整大小的容器中,并利用卡片内部的滚动区域来展示更多内容。

<script setup lang="ts">
import { ref } from 'vue'
import { BrResizable, BrCard, BrCardHeader, BrCardTitle, BrCardDescription, BrCardContent } from '@breezeui/vue'

const cardWidth = ref(400)
const cardHeight = ref(250)
</script>

<template>
  <BrResizable
    v-model:width="cardWidth"
    v-model:height="cardHeight"
    :min-width="250"
    :min-height="150"
    class="flex flex-col"
  >
    <BrCard class="w-full h-full flex flex-col overflow-hidden shadow-md">
      <BrCardHeader>
        <BrCardTitle>Resizable Card</BrCardTitle>
        <BrCardDescription>Drag the handle to resize this card component.</BrCardDescription>
      </BrCardHeader>
      <BrCardContent class="flex-1 overflow-auto text-muted-foreground">
        <p class="mb-4">
          This is a card component nested inside a resizable container.
          Notice how the card header stays fixed while the content area scrolls if it overflows.
        </p>
        <p>
          You can integrate the Resizable component with almost any other UI element to create dynamic layouts.
        </p>
      </BrCardContent>
    </BrCard>
  </BrResizable>
</template>

API

Resizable Props

属性名说明类型默认值
as渲染的 HTML 标签string'div'
width / v-model:width容器宽度number | string-
height / v-model:height容器高度number | string-
minWidth最小宽度number0
maxWidth最大宽度numberInfinity
minHeight最小高度number0
maxHeight最大高度numberInfinity
directions允许调整的方向('top' | 'right' | 'bottom' | 'left' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right')[]['right', 'bottom', 'bottom-right']
lockAspectRatio锁定宽高比,可传布尔值(锁定初始比例)或具体数字比例boolean | numberfalse
grid对齐网格大小,可传单数字或 [x, y] 数组number | [number, number]1
step键盘调整时的步长number10
disabled是否禁用booleanfalse

Resizable Emits

事件名说明回调参数
update:width宽度改变时触发(width: number) => void
update:height高度改变时触发(height: number) => void
resizeStart开始调整时触发(event: { width: number; height: number; direction: string }) => void
resize调整过程中触发(event: { width: number; height: number; direction: string }) => void
resizeEnd结束调整时触发(event: { width: number; height: number; direction: string }) => void

ResizableHandle Props

属性名说明类型默认值
direction手柄控制的方向(非 Split 模式使用)string-
split是否作为 Split 模式的手柄booleanfalse
withHandleSplit 模式下是否显示内置图标booleanfalse

ResizableSplit Props

基于 radix-vueSplitterGroup,透传其所有属性。

属性名说明类型默认值
direction分割方向'horizontal' | 'vertical'-

ResizableSplitPane Props

基于 radix-vueSplitterPanel,透传其所有属性。

属性名说明类型默认值
defaultSize默认尺寸百分比number-
minSize最小尺寸百分比number0
maxSize最大尺寸百分比number100