localeCode: zh-CN order: 59 category: 展示类 title: Popover 气泡卡片 icon: doc-popover
Popover 气泡卡片是由用户自主打开的临时性浮层卡片,能够承载一些额外内容和交互行为而不影响原页面。
和 Tooltip 的区别是,它可以承载更复杂的内容,而不仅仅是提示文本。
import { Popover } from '@douyinfe/semi-ui';
将浮层的触发器 Trigger 作为children,使用 Popover 包裹(如下的例子中触发器为 Tag 元素)。浮层内容通过content传入
import React from 'react';
import { Popover, Tag } from '@douyinfe/semi-ui';
function Demo() {
return (
<Popover
content={
<article style={{ padding: 12 }}>
Hi ByteDancer, this is a popover.
<br /> We have 2 lines.
</article>
}
>
<Tag>悬停此处</Tag>
</Popover>
);
}
支持通过position设置浮层弹出方向,共支持十二个方向。
import React from 'react';
import { Popover, Tag } from '@douyinfe/semi-ui';
function Demo() {
const tops = [
['topLeft', 'TL'],
['top', 'Top'],
['topRight', 'TR'],
];
const lefts = [
['leftTop', 'LT'],
['left', 'Left'],
['leftBottom', 'LB'],
];
const rights = [
['rightTop', 'RT'],
['right', 'Right'],
['rightBottom', 'RB'],
];
const bottoms = [
['bottomLeft', 'BL'],
['bottom', 'Bottom'],
['bottomRight', 'BR'],
];
return (
<div style={{ paddingLeft: 40 }} className="tag-margin-right">
<div style={{ marginLeft: 40, whiteSpace: 'nowrap' }}>
{tops.map((pos, index) => (
<Popover
content={
<article style={{ padding: 12 }}>
Hi ByteDancer, this is a popover.
<br /> We have 2 lines.
</article>
}
position={Array.isArray(pos) ? pos[0] : pos}
key={index}
>
<Tag>{Array.isArray(pos) ? pos[1] : pos}</Tag>
</Popover>
))}
</div>
<div style={{ width: 40, float: 'left' }}>
{lefts.map((pos, index) => (
<Popover
content={
<article style={{ padding: 12 }}>
Hi ByteDancer, this is a popover.
<br /> We have 2 lines.
</article>
}
position={Array.isArray(pos) ? pos[0] : pos}
key={index}
>
<Tag>{Array.isArray(pos) ? pos[1] : pos}</Tag>
</Popover>
))}
</div>
<div style={{ width: 40, marginLeft: 180 }}>
{rights.map((pos, index) => (
<Popover
content={
<article style={{ padding: 12 }}>
Hi ByteDancer, this is a popover.
<br /> We have 2 lines.
</article>
}
position={Array.isArray(pos) ? pos[0] : pos}
key={index}
>
<Tag>{Array.isArray(pos) ? pos[1] : pos}</Tag>
</Popover>
))}
</div>
<div style={{ marginLeft: 40, clear: 'both', whiteSpace: 'nowrap' }}>
{bottoms.map((pos, index) => (
<Popover
content={
<article style={{ padding: 12 }}>
Hi ByteDancer, this is a popover.
<br /> We have 2 lines.
</article>
}
position={Array.isArray(pos) ? pos[0] : pos}
key={index}
>
<Tag>{Array.isArray(pos) ? pos[1] : pos}</Tag>
</Popover>
))}
</div>
</div>
);
}
设置trigger='custom',此场景下,Popover 的显示与否完全受到参数 visible 的控制。
import React from 'react';
import { Popover, Button } from '@douyinfe/semi-ui';
class App extends React.Component {
constructor(props = {}) {
super(props);
this.state = {
visible: false,
};
this.content = (
<article style={{ padding: 12 }}>
Hi ByteDancer, this is a popover.
<br /> We have 2 lines.
</article>
);
this.toggleShow = this.toggleShow.bind(this);
}
toggleShow() {
this.setState({
visible: !this.state.visible,
});
}
render() {
const content = this.content;
const { visible } = this.state;
return (
<div>
<div>
<Popover visible={visible} content={content} trigger="custom">
<Button onClick={this.toggleShow}>点我</Button>
</Popover>
</div>
</div>
);
}
}
版本:>= 0.19.0
通过设置showArrow, Popover 同样也支持展示一个小三角。
这种模式下浮层会拥有一个默认的样式,你可以通过传递 style 参数来覆盖掉。
import React from 'react';
import { Popover, Tag } from '@douyinfe/semi-ui';
function Demo() {
const tops = [
['topLeft', 'TL'],
['top', 'Top'],
['topRight', 'TR'],
];
const lefts = [
['leftTop', 'LT'],
['left', 'Left'],
['leftBottom', 'LB'],
];
const rights = [
['rightTop', 'RT'],
['right', 'Right'],
['rightBottom', 'RB'],
];
const bottoms = [
['bottomLeft', 'BL'],
['bottom', 'Bottom'],
['bottomRight', 'BR'],
];
return (
<div style={{ paddingLeft: 40 }} className="tag-margin-right">
<div style={{ marginLeft: 40, whiteSpace: 'nowrap' }}>
{tops.map((pos, index) => (
<Popover
showArrow
content={
<article>
Hi ByteDancer, this is a popover.
<br /> We have 2 lines.
</article>
}
position={Array.isArray(pos) ? pos[0] : pos}
key={index}
>
<Tag>{Array.isArray(pos) ? pos[1] : pos}</Tag>
</Popover>
))}
</div>
<div style={{ width: 40, float: 'left' }}>
{lefts.map((pos, index) => (
<Popover
showArrow
content={
<article>
Hi ByteDancer, this is a popover.
<br /> We have 2 lines.
</article>
}
position={Array.isArray(pos) ? pos[0] : pos}
key={index}
>
<Tag>{Array.isArray(pos) ? pos[1] : pos}</Tag>
</Popover>
))}
</div>
<div style={{ width: 40, marginLeft: 180 }}>
{rights.map((pos, index) => (
<Popover
showArrow
content={
<article>
Hi ByteDancer, this is a popover.
<br /> We have 2 lines.
</article>
}
position={Array.isArray(pos) ? pos[0] : pos}
key={index}
>
<Tag>{Array.isArray(pos) ? pos[1] : pos}</Tag>
</Popover>
))}
</div>
<div style={{ marginLeft: 40, clear: 'both', whiteSpace: 'nowrap' }}>
{bottoms.map((pos, index) => (
<Popover
showArrow
content={
<article>
Hi ByteDancer, this is a popover.
<br /> We have 2 lines.
</article>
}
position={Array.isArray(pos) ? pos[0] : pos}
key={index}
>
<Tag>{Array.isArray(pos) ? pos[1] : pos}</Tag>
</Popover>
))}
</div>
</div>
);
}
版本:>= 0.34.0
在显示小三角的条件(showArrow=true)下,可以传入 arrowPointAtCenter=true 使得小三角始终指向元素中心位置。
import React from 'react';
import { Popover, Tag } from '@douyinfe/semi-ui';
function Demo() {
const tops = [
['topLeft', 'TL'],
['top', 'Top'],
['topRight', 'TR'],
];
const lefts = [
['leftTop', 'LT'],
['left', 'Left'],
['leftBottom', 'LB'],
];
const rights = [
['rightTop', 'RT'],
['right', 'Right'],
['rightBottom', 'RB'],
];
const bottoms = [
['bottomLeft', 'BL'],
['bottom', 'Bottom'],
['bottomRight', 'BR'],
];
return (
<div style={{ paddingLeft: 40 }} className="tag-margin-right">
<div style={{ marginLeft: 40, whiteSpace: 'nowrap' }}>
{tops.map((pos, index) => (
<Popover
showArrow
arrowPointAtCenter
content={
<article>
Hi ByteDancer, this is a popover.
<br /> We have 2 lines.
</article>
}
position={Array.isArray(pos) ? pos[0] : pos}
key={index}
>
<Tag>{Array.isArray(pos) ? pos[1] : pos}</Tag>
</Popover>
))}
</div>
<div style={{ width: 40, float: 'left' }}>
{lefts.map((pos, index) => (
<Popover
showArrow
arrowPointAtCenter
content={
<article>
Hi ByteDancer, this is a popover.
<br /> We have 2 lines.
</article>
}
position={Array.isArray(pos) ? pos[0] : pos}
key={index}
>
<Tag>{Array.isArray(pos) ? pos[1] : pos}</Tag>
</Popover>
))}
</div>
<div style={{ width: 40, marginLeft: 180 }}>
{rights.map((pos, index) => (
<Popover
showArrow
arrowPointAtCenter
content={
<article>
Hi ByteDancer, this is a popover.
<br /> We have 2 lines.
</article>
}
position={Array.isArray(pos) ? pos[0] : pos}
key={index}
>
<Tag>{Array.isArray(pos) ? pos[1] : pos}</Tag>
</Popover>
))}
</div>
<div style={{ marginLeft: 40, clear: 'both', whiteSpace: 'nowrap' }}>
{bottoms.map((pos, index) => (
<Popover
showArrow
arrowPointAtCenter
content={
<article>
Hi ByteDancer, this is a popover.
<br /> We have 2 lines.
</article>
}
position={Array.isArray(pos) ? pos[0] : pos}
key={index}
>
<Tag>{Array.isArray(pos) ? pos[1] : pos}</Tag>
</Popover>
))}
</div>
</div>
);
}
如果你需要定制浮层的背景色或边框颜色,请务必单独声明 style 中的 backgroundColor 和 borderColor 属性,这样能够使得“小三角”也能应用相同的背景色和边框颜色。
import React from 'react';
import { Popover, Tag } from '@douyinfe/semi-ui';
function Demo() {
return (
<Popover
content={
<article style={{ padding: 4 }}>
Hi ByteDancer, this is a popover.
<br /> We have 2 lines.
</article>
}
trigger="custom"
position='right'
visible
showArrow
style={{
backgroundColor: 'rgba(var(--semi-blue-4),1)',
borderColor: 'rgba(var(--semi-blue-4),1)',
color: 'var(--semi-color-white)',
borderWidth: 1,
borderStyle: 'solid',
}}
>
<Tag>点击此处</Tag>
</Popover>
);
}
Popover content 支持传入函数,它的入参是一个对象,将 initialFocusRef 绑定在可聚焦 DOM 或组件上,打开面板时会自动聚焦在该位置。
import React from 'react';
import { Button, Input, Popover, Space } from '@douyinfe/semi-ui';
() => {
const renderContent = ({ initialFocusRef }) => {
return (
<div style={{ padding: 12 }}>
<Space>
<Button>first focusable element</Button>
<Input ref={initialFocusRef} placeholder="focus here" />
</Space>
</div>
);
};
return (
<Popover content={renderContent} trigger="click">
<Button>click me</Button>
</Popover>
);
};
请参考搭配使用
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
| autoAdjustOverflow | 是否自动调整弹出层展开方向,用于边缘遮挡时自动调整展开方向 | boolean | true | |
| arrowPointAtCenter | “小三角”是否指向元素中心,需要同时传入"showArrow=true" | boolean | true | 0.34.0 |
| closeOnEsc | 在 trigger 或 弹出层按 Esc 键是否关闭面板,受控时不生效 | boolean | true | 2.8.0 |
| content | 显示的内容(函数类型,2.8.0 版本支持) | ReactNode | ({ initialFocusRef }) => ReactNode | ||
| clickToHide | 点击弹出层及内部任一元素时是否自动关闭弹层 | boolean | false | 0.24.0 |
| disableFocusListener | trigger为hover时,不响应键盘聚焦弹出浮层事件,详见issue#977 |
boolean | true | 2.17.0 |
| getPopupContainer | 指定父级 DOM,弹层将会渲染至该 DOM 中,自定义需要设置 position: relative |
function():HTMLElement | () => document.body | |
| guardFocus | 当焦点处于弹出层内时,切换 Tab 是否让焦点在弹出层内循环 | boolean | true | 2.8.0 |
| mouseEnterDelay | 鼠标移入后,延迟显示的时间,单位毫秒(仅当 trigger 为 hover/focus 时生效) | number | 50 | |
| mouseLeaveDelay | 鼠标移出后,延迟消失的时间,单位毫秒(仅当 trigger 为 hover/focus 时生效) | number | 50 | |
| rePosKey | 可以更新该项值手动触发弹出层的重新定位 | string|number | ||
| returnFocusOnClose | 按下 Esc 键后,焦点是否回到 trigger 上,设置 trigger 为 hover, focus, click 时生效 | boolean | true | 2.8.0 |
| position | 方向,可选值:top,topLeft,topRight,left,leftTop,leftBottom,right,rightTop,rightBottom,bottom,bottomLeft,bottomRight |
string | "bottom" | |
| spacing | 弹出层与 children 元素的距离,单位 px | number | 4(showArrow=false 时) 10(showArrow=true 时) | |
| showArrow | 是否显示“小三角” | boolean | ||
| stopPropagation | 是否阻止弹出层上的点击事件冒泡 | boolean | false | 0.34.0 |
| trigger | 触发方式,可选值:hover, focus, click, custom |
string | 'hover' | |
| visible | 是否显示,配合trigger='custom'可实现完全受控 | boolean | ||
| zIndex | 弹出层 z-index 值 | number | 1030 | |
| onClickOutSide | 当弹出层处于展示状态,点击非Children、非浮层内部区域时的回调(仅trigger为custom、click时有效) | function(e:event) | 2.1.0 | |
| onEscKeyDown | 在 trigger 或 弹出层按 Esc 键时调用 | function(e:event) | 2.8.0 | |
| onVisibleChange | 弹出层展示/隐藏时触发的回调 | function(isVisble:boolean) |
dialog roletooltip roleid 属性true,不可见时为 falsedialog为什么 Popover 浮层卡片的位置和浮层的触发器的相对位置不符合预期?
Popover 底层依赖了 Tooltip,Tooltip 为了计算定位,需要获取到 children 的真实 DOM 元素,因此 Popover 类型目前支持如下两种类型的 children:
若以带有前缀的 Semi Input 作为 children,即使设置了 Input 和 Popover content等宽,浮层卡片的位置仍是相对于不包含前缀部分的 input 框进行定位,此时只要在 Input 外层再套一个 div 就能解决问题。
为什么 Popover 浮层卡片在靠近屏幕边界宽度不够时,丢失宽度意外换行?
在 chromium 104 后 对于屏幕边界文本宽度不够时的换行渲染策略发生变化,详细原因可查看 issue #1022,semi侧已经在v2.17.0版本修复了这个问题。