开发预览 更新于 2026-05-10

Popover 弹出层

Tooltip 的可交互变体 —— 支持任意富内容、表单、按钮,自动避让屏幕边缘。

基础用法

与 Tooltip 不同,Popover 内部支持任意可交互内容(输入、按钮、列表)。点击外部或按 Esc 关闭,鼠标移入悬停 Popover 不会关闭。

背景 视口
src/App.vue
<script setup lang="ts">
import { CfButton, CfPopover, CfInput } from '@chufix-design/vue';
</script>
<template>
  <CfPopover placement="bottom">
    <CfButton variant="secondary">点击展开</CfButton>
    <template #content>
      <div style="display: flex; flex-direction: column; gap: 8px; min-width: 200px;">
        <strong>快速操作</strong>
        <CfInput placeholder="搜索…" size="sm" />
        <CfButton size="sm">应用</CfButton>
      </div>
    </template>
  </CfPopover>
</template>
<script setup>
import { CfButton, CfPopover, CfInput } from '@chufix-design/vue';
</script>
<template>
  <CfPopover placement="bottom">
    <CfButton variant="secondary">点击展开</CfButton>
    <template #content>
      <div style="display: flex; flex-direction: column; gap: 8px; min-width: 200px;">
        <strong>快速操作</strong>
        <CfInput placeholder="搜索…" size="sm" />
        <CfButton size="sm">应用</CfButton>
      </div>
    </template>
  </CfPopover>
</template>
import { CfButton, CfInput, CfPopover } from '@chufix-design/react';

export default function Demo() {
  return (
    <>
      <CfPopover placement="bottom">
          <CfButton variant="secondary">点击展开</CfButton>
            <div style={{ display: "flex", flexDirection: "column", gap: 8, minWidth: 200 }}>
              <strong>快速操作</strong>
              <CfInput placeholder="搜索…" size="sm" />
              <CfButton size="sm">应用</CfButton>
            </div>
    </>
  );
}
import { CfButton, CfInput, CfPopover } from '@chufix-design/react';

export default function Demo() {
  return (
    <>
      <CfPopover placement="bottom">
          <CfButton variant="secondary">点击展开</CfButton>
            <div style={{ display: "flex", flexDirection: "column", gap: 8, minWidth: 200 }}>
              <strong>快速操作</strong>
              <CfInput placeholder="搜索…" size="sm" />
              <CfButton size="sm">应用</CfButton>
            </div>
    </>
  );
}

触发方式

trigger 决定如何打开 popover:

  • click(默认)—— 点击触发器打开,再点击或点外部关闭
  • hover —— 悬停打开,移开延时关闭
  • manual —— 完全由父组件通过 open 控制(受控模式)
背景 视口
src/App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { CfButton, CfPopover } from '@chufix-design/vue';

const manualOpen = ref(false);
</script>
<template>
  <div style="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;">
    <CfPopover trigger="click" placement="bottom">
      <CfButton variant="secondary">click</CfButton>
      <template #content>点击触发,再次点击或点外部关闭</template>
    </CfPopover>
    <CfPopover trigger="hover" placement="bottom">
      <CfButton variant="secondary">hover</CfButton>
      <template #content>悬停触发,鼠标移开延时关闭</template>
    </CfPopover>
    <CfPopover trigger="manual" :open="manualOpen" @update:open="(v) => manualOpen = v">
      <CfButton variant="secondary" @click="manualOpen = !manualOpen">
        manual ({{ manualOpen ? '已开' : '已关' }})
      </CfButton>
      <template #content>受控模式 — 完全由父组件 open 决定</template>
    </CfPopover>
  </div>
</template>
<script setup>
import { ref } from 'vue';
import { CfButton, CfPopover } from '@chufix-design/vue';

const manualOpen = ref(false);
</script>
<template>
  <div style="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;">
    <CfPopover trigger="click" placement="bottom">
      <CfButton variant="secondary">click</CfButton>
      <template #content>点击触发,再次点击或点外部关闭</template>
    </CfPopover>
    <CfPopover trigger="hover" placement="bottom">
      <CfButton variant="secondary">hover</CfButton>
      <template #content>悬停触发,鼠标移开延时关闭</template>
    </CfPopover>
    <CfPopover trigger="manual" :open="manualOpen" @update:open="(v) => manualOpen = v">
      <CfButton variant="secondary" @click="manualOpen = !manualOpen">
        manual ({{ manualOpen ? '已开' : '已关' }})
      </CfButton>
      <template #content>受控模式 — 完全由父组件 open 决定</template>
    </CfPopover>
  </div>
</template>
import { useState } from 'react';
import { CfButton, CfPopover } from '@chufix-design/react';

export default function Demo() {
  const [manualOpen, setManualOpen] = useState(false);
  return (
    <>
      <div style={{ display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap" }}>
          <CfPopover trigger="click" placement="bottom">
            <CfButton variant="secondary">click</CfButton>
            点击触发,再次点击或点外部关闭
    </>
  );
}
import { useState } from 'react';
import { CfButton, CfPopover } from '@chufix-design/react';

export default function Demo() {
  const [manualOpen, setManualOpen] = useState(false);
  return (
    <>
      <div style={{ display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap" }}>
          <CfPopover trigger="click" placement="bottom">
            <CfButton variant="secondary">click</CfButton>
            点击触发,再次点击或点外部关闭
    </>
  );
}

4 种 placement

placement —— top / bottom(默认)/ left / right,空间不够时自动翻转到对侧。

背景 视口
src/App.vue
<script setup lang="ts">
import { CfButton, CfPopover } from '@chufix-design/vue';
</script>
<template>
  <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; max-width: 320px;">
    <CfPopover placement="top">
      <CfButton variant="secondary">top</CfButton>
      <template #content>位于触发器上方</template>
    </CfPopover>
    <CfPopover placement="bottom">
      <CfButton variant="secondary">bottom</CfButton>
      <template #content>位于触发器下方</template>
    </CfPopover>
    <CfPopover placement="left">
      <CfButton variant="secondary">left</CfButton>
      <template #content>位于触发器左侧</template>
    </CfPopover>
    <CfPopover placement="right">
      <CfButton variant="secondary">right</CfButton>
      <template #content>位于触发器右侧</template>
    </CfPopover>
  </div>
</template>
<script setup>
import { CfButton, CfPopover } from '@chufix-design/vue';
</script>
<template>
  <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; max-width: 320px;">
    <CfPopover placement="top">
      <CfButton variant="secondary">top</CfButton>
      <template #content>位于触发器上方</template>
    </CfPopover>
    <CfPopover placement="bottom">
      <CfButton variant="secondary">bottom</CfButton>
      <template #content>位于触发器下方</template>
    </CfPopover>
    <CfPopover placement="left">
      <CfButton variant="secondary">left</CfButton>
      <template #content>位于触发器左侧</template>
    </CfPopover>
    <CfPopover placement="right">
      <CfButton variant="secondary">right</CfButton>
      <template #content>位于触发器右侧</template>
    </CfPopover>
  </div>
</template>
import { CfButton, CfPopover } from '@chufix-design/react';

export default function Demo() {
  return (
    <>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(2, 1fr)", gap: 12, maxWidth: 320 }}>
          <CfPopover placement="top">
            <CfButton variant="secondary">top</CfButton>
            位于触发器上方
    </>
  );
}
import { CfButton, CfPopover } from '@chufix-design/react';

export default function Demo() {
  return (
    <>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(2, 1fr)", gap: 12, maxWidth: 320 }}>
          <CfPopover placement="top">
            <CfButton variant="secondary">top</CfButton>
            位于触发器上方
    </>
  );
}

固定宽度

width 给 popover 一个固定宽度(数字或 CSS 字符串);省略则按内容自适应。需要多列内容或精准排版时用。

背景 视口
src/App.vue
<script setup lang="ts">
import { CfButton, CfPopover } from '@chufix-design/vue';
</script>
<template>
  <div style="display: flex; gap: 12px;">
    <CfPopover placement="bottom">
      <CfButton variant="secondary">自适应宽度</CfButton>
      <template #content>仅按内容自然撑开</template>
    </CfPopover>
    <CfPopover placement="bottom" :width="320">
      <CfButton variant="secondary">固定宽度 320</CfButton>
      <template #content>
        <p style="margin: 0;">这条 popover 强制 320px 宽,便于复杂布局。</p>
      </template>
    </CfPopover>
  </div>
</template>
<script setup>
import { CfButton, CfPopover } from '@chufix-design/vue';
</script>
<template>
  <div style="display: flex; gap: 12px;">
    <CfPopover placement="bottom">
      <CfButton variant="secondary">自适应宽度</CfButton>
      <template #content>仅按内容自然撑开</template>
    </CfPopover>
    <CfPopover placement="bottom" :width="320">
      <CfButton variant="secondary">固定宽度 320</CfButton>
      <template #content>
        <p style="margin: 0;">这条 popover 强制 320px 宽,便于复杂布局。</p>
      </template>
    </CfPopover>
  </div>
</template>
import { CfButton, CfPopover } from '@chufix-design/react';

export default function Demo() {
  return (
    <>
      <div style={{ display: "flex", gap: 12 }}>
          <CfPopover placement="bottom">
            <CfButton variant="secondary">自适应宽度</CfButton>
            仅按内容自然撑开
    </>
  );
}
import { CfButton, CfPopover } from '@chufix-design/react';

export default function Demo() {
  return (
    <>
      <div style={{ display: "flex", gap: 12 }}>
          <CfPopover placement="bottom">
            <CfButton variant="secondary">自适应宽度</CfButton>
            仅按内容自然撑开
    </>
  );
}

控制关闭路径

默认 popover 在三种情形会关闭:点击外部、按 Esc、再次点击触发器。把 closeOnOutside / closeOnEsc 单独置 false 可锁死对应路径 —— 常用于内嵌表单 popover,避免误触关闭丢失输入。

背景 视口
src/App.vue
<script setup lang="ts">
import { CfButton, CfPopover } from '@chufix-design/vue';
</script>
<template>
  <div class="demo-stack">
    <div class="demo-row">
      <CfPopover placement="bottom" :close-on-outside="false">
        <CfButton variant="secondary">closeOnOutside = false</CfButton>
        <template #content>
          <strong>外部点击不会关闭</strong>
          <p class="adm-p">仅 Esc 或再次点击触发器才能关闭。</p>
        </template>
      </CfPopover>
      <span class="adm-hint">点页面任意位置都不会关</span>
    </div>
    <div class="demo-row">
      <CfPopover placement="bottom" :close-on-esc="false">
        <CfButton variant="secondary">closeOnEsc = false</CfButton>
        <template #content>
          <strong>Esc 不会关闭</strong>
          <p class="adm-p">仅外部点击或再次点击触发器才能关闭。</p>
        </template>
      </CfPopover>
      <span class="adm-hint">键盘 Esc 无效</span>
    </div>
    <div class="demo-row">
      <CfPopover placement="bottom" :close-on-outside="false" :close-on-esc="false">
        <CfButton variant="secondary">两者都 false</CfButton>
        <template #content>
          <strong>必须显式关闭</strong>
          <p class="adm-p">只能再次点击触发器才能关闭。常用于表单 popover。</p>
        </template>
      </CfPopover>
      <span class="adm-hint">外部点击 + Esc 均无效</span>
    </div>
  </div>
</template>
<style scoped>
.demo-stack { display: flex; flex-direction: column; gap: 12px; }
.adm-p { margin: 4px 0 0; color: var(--fg-2); }
.adm-hint { color: var(--fg-3); font-size: var(--t-12); }
</style>
<script setup>
import { CfButton, CfPopover } from '@chufix-design/vue';
</script>
<template>
  <div class="demo-stack">
    <div class="demo-row">
      <CfPopover placement="bottom" :close-on-outside="false">
        <CfButton variant="secondary">closeOnOutside = false</CfButton>
        <template #content>
          <strong>外部点击不会关闭</strong>
          <p class="adm-p">仅 Esc 或再次点击触发器才能关闭。</p>
        </template>
      </CfPopover>
      <span class="adm-hint">点页面任意位置都不会关</span>
    </div>
    <div class="demo-row">
      <CfPopover placement="bottom" :close-on-esc="false">
        <CfButton variant="secondary">closeOnEsc = false</CfButton>
        <template #content>
          <strong>Esc 不会关闭</strong>
          <p class="adm-p">仅外部点击或再次点击触发器才能关闭。</p>
        </template>
      </CfPopover>
      <span class="adm-hint">键盘 Esc 无效</span>
    </div>
    <div class="demo-row">
      <CfPopover placement="bottom" :close-on-outside="false" :close-on-esc="false">
        <CfButton variant="secondary">两者都 false</CfButton>
        <template #content>
          <strong>必须显式关闭</strong>
          <p class="adm-p">只能再次点击触发器才能关闭。常用于表单 popover。</p>
        </template>
      </CfPopover>
      <span class="adm-hint">外部点击 + Esc 均无效</span>
    </div>
  </div>
</template>
<style scoped>
.demo-stack { display: flex; flex-direction: column; gap: 12px; }
.adm-p { margin: 4px 0 0; color: var(--fg-2); }
.adm-hint { color: var(--fg-3); font-size: var(--t-12); }
</style>
import { CfButton, CfPopover } from '@chufix-design/react';

export default function Demo() {
  return (
    <>
      <div className="demo-stack">
          <div className="demo-row">
            <CfPopover placement="bottom" closeOnOutside={false}>
              <CfButton variant="secondary">closeOnOutside = false</CfButton>
                <strong>外部点击不会关闭</strong>
                <p className="adm-p">仅 Esc 或再次点击触发器才能关闭。</p>
    </>
  );
}
import { CfButton, CfPopover } from '@chufix-design/react';

export default function Demo() {
  return (
    <>
      <div className="demo-stack">
          <div className="demo-row">
            <CfPopover placement="bottom" closeOnOutside={false}>
              <CfButton variant="secondary">closeOnOutside = false</CfButton>
                <strong>外部点击不会关闭</strong>
                <p className="adm-p">仅 Esc 或再次点击触发器才能关闭。</p>
    </>
  );
}

禁用 popover

disabled 让触发器失去任何打开 popover 的能力 —— 触发器本身依然渲染、可点击,但不会弹出浮层。常用于权限或条件互斥的场景。

背景 视口
src/App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { CfButton, CfPopover } from '@chufix-design/vue';

const disabled = ref<boolean>(false);
</script>
<template>
  <div class="demo-row">
    <CfPopover placement="bottom" :disabled="disabled">
      <CfButton variant="secondary">点击展开</CfButton>
      <template #content>
        <strong>已启用</strong>
        <p class="adm-p">Popover 内容…</p>
      </template>
    </CfPopover>
    <CfButton size="sm" variant="ghost" @click="disabled = !disabled">
      {{ disabled ? '启用' : '禁用' }} popover
    </CfButton>
    <span class="adm-hint">当前 disabled = {{ disabled }}</span>
  </div>
</template>
<style scoped>
.adm-p { margin: 4px 0 0; color: var(--fg-2); }
.adm-hint { color: var(--fg-3); font-size: var(--t-12); }
</style>
<script setup>
import { ref } from 'vue';
import { CfButton, CfPopover } from '@chufix-design/vue';

const disabled = ref<boolean>(false);
</script>
<template>
  <div class="demo-row">
    <CfPopover placement="bottom" :disabled="disabled">
      <CfButton variant="secondary">点击展开</CfButton>
      <template #content>
        <strong>已启用</strong>
        <p class="adm-p">Popover 内容…</p>
      </template>
    </CfPopover>
    <CfButton size="sm" variant="ghost" @click="disabled = !disabled">
      {{ disabled ? '启用' : '禁用' }} popover
    </CfButton>
    <span class="adm-hint">当前 disabled = {{ disabled }}</span>
  </div>
</template>
<style scoped>
.adm-p { margin: 4px 0 0; color: var(--fg-2); }
.adm-hint { color: var(--fg-3); font-size: var(--t-12); }
</style>
import { useState } from 'react';
import { CfButton, CfPopover } from '@chufix-design/react';

export default function Demo() {
  const [disabled, setDisabled] = useState<boolean>(false);
  return (
    <>
      <div className="demo-row">
          <CfPopover placement="bottom" disabled={disabled}>
            <CfButton variant="secondary">点击展开</CfButton>
              <strong>已启用</strong>
              <p className="adm-p">Popover 内容…</p>
    </>
  );
}
import { useState } from 'react';
import { CfButton, CfPopover } from '@chufix-design/react';

export default function Demo() {
  const [disabled, setDisabled] = useState<boolean>(false);
  return (
    <>
      <div className="demo-row">
          <CfPopover placement="bottom" disabled={disabled}>
            <CfButton variant="secondary">点击展开</CfButton>
              <strong>已启用</strong>
              <p className="adm-p">Popover 内容…</p>
    </>
  );
}

API · Props

属性类型默认值说明
openboolean受控时使用,配合 update:open / onOpenChange
placement'top' | 'bottom' | 'left' | 'right''bottom'首选位置;空间不够时自动翻转到对侧
trigger'click' | 'hover' | 'manual''click'触发方式
offsetnumber8与触发元素的间距(px)
closeOnOutsidebooleantrue点击外部时关闭(manual 时忽略)
closeOnEscbooleantrueEsc 关闭
widthnumber | string固定宽度,省略则自适应内容
disabledbooleanfalse禁用触发

API · Slots / 子节点

名称说明
default (children)触发元素
contentPopover 面板内容(React 用 content prop)

反馈与讨论

Popover 弹出层 的讨论

0
0 / 600
正在加载评论...