# Popover

气泡框组件，承载聚合类信息。包含 Popover（信息气泡）和 PopoverConfirm（确认气泡）两个子组件。

## 适用场景

- 名词解释、更多信息的悬浮提示
- 用户可对浮层内容进行操作的场景（区别于 Tooltip）
- 轻量级的二次确认操作（替代模态弹窗）
- 错误提示、输入提示、新功能提示等场景

## Props

### Popover Props

| 名称 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| className | `string` | `""` | 否 | 自定义 class |
| withArrow | `boolean` | `true` | 否 | 是否有箭头 |
| headline | `ReactNode` | - | 否 | 标题 |
| content | `ReactNode` | - | 否 | 内容 |
| actions | `ReactNode` | - | 否 | 操作区 |
| actionsAlign | `"right" \| "center" \| "left"` | - | 否 | 操作区对齐方式 |
| withCloseX | `boolean` | - | 否 | 是否有关闭小叉 |
| onClickCloseX | `(e: MouseEvent<Element, MouseEvent>) => void` | - | 否 | 点击关闭小叉回调 |
| size | `"md" \| "sm"` | - | 否 | 尺寸，影响 headline/content 字体大小及按钮尺寸 |
| type | `"click" \| "hover" \| "focus"` | `"click"` | 否 | 触发方式 |
| visible | `boolean` | - | 否 | 是否显示控制值 |
| target | `(() => HTMLElement \| null \| void) \| HTMLElement \| null` | - | 否 | 指定目标元素（不接受 ReactNode） |
| defaultVisible | `boolean` | - | 否 | 是否显示默认值 |
| onChange | `(visible: boolean, e?: Event \| SyntheticEvent<Element, Event>) => void` | - | 否 | visible 变化回调 |
| beforeChange | `(visible: boolean) => boolean \| void \| Promise<unknown>` | - | 否 | visible 变化前回调，返回 false 或 reject 可阻止变化 |
| mouseEnterDelay | `number` | - | 否 | mouseEnter 延迟触发（毫秒） |
| mouseLeaveDelay | `number` | - | 否 | mouseLeave 延迟触发（毫秒） |
| focusDelay | `number` | - | 否 | focus 延迟触发（毫秒） |
| blurDelay | `number` | - | 否 | blur 延迟触发（毫秒） |
| shouldHideParent | `boolean` | - | 否 | 是否隐藏时隐藏 parent |
| shouldHideOnMousedownDocument | `boolean \| ((e: Event) => boolean)` | - | 否 | 点击外部时是否隐藏，设为 false 可禁用此行为 |
| placement | `Placement` | `"top"` | 否 | 浮层相对于 target 的位置 |
| arrowOffset | `number \| "auto"` | - | 否 | 箭头偏移量，仅在 placement 为 *-end/*-start 时有效 |
| portalContainer | `HTMLElement \| (() => HTMLElement)` | - | 否 | 指定 portal 挂载容器，默认 document.body |

### PopoverConfirm Props

| 名称 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| className | `string` | `""` | 否 | 自定义 class |
| type | `"warning" \| "info" \| "success" \| "error"` | `"warning"` | 否 | 类型 |
| icon | `SvgFC` | - | 否 | 自定义 icon |
| content | `ReactNode` | - | 否 | 内容 |
| headline | `ReactNode` | - | 否 | 标题 |
| actions | `ReactNode` | - | 否 | 操作区 |
| actionsAlign | `"right" \| "center" \| "left"` | - | 否 | 操作区对齐方式 |
| withCloseX | `boolean` | - | 否 | 是否有关闭小叉 |
| onClickCloseX | `(e: MouseEvent<Element, MouseEvent>) => void` | - | 否 | 点击关闭小叉回调 |
| size | `"md" \| "sm"` | - | 否 | 尺寸 |
| onConfirm | `(e: MouseEvent<Element, MouseEvent>) => void` | - | 否 | 点击确认回调 |
| onCancel | `(e: MouseEvent<Element, MouseEvent>) => void` | - | 否 | 点击取消回调 |
| confirmLabel | `string` | - | 否 | 自定义确认按钮文本 |
| cancelLabel | `string` | - | 否 | 自定义取消按钮文本 |
| placement | `Placement` | - | 否 | 浮层相对于 target 的位置 |
| withArrow | `boolean` | - | 否 | 是否有箭头 |
| arrowOffset | `number \| "auto"` | - | 否 | 箭头偏移量 |
| portalContainer | `HTMLElement \| (() => HTMLElement)` | - | 否 | 指定 portal 挂载容器，默认 document.body |
| visible | `boolean` | - | 否 | 是否显示控制值 |
| defaultVisible | `boolean` | - | 否 | 是否显示默认值 |
| beforeChange | `(visible: boolean) => boolean \| void \| Promise<unknown>` | - | 否 | visible 变化前回调 |
| mouseEnterDelay | `number` | - | 否 | mouseEnter 延迟触发（毫秒） |
| mouseLeaveDelay | `number` | - | 否 | mouseLeave 延迟触发（毫秒） |
| focusDelay | `number` | - | 否 | focus 延迟触发（毫秒） |
| blurDelay | `number` | - | 否 | blur 延迟触发（毫秒） |
| shouldHideParent | `boolean` | - | 否 | 是否隐藏时隐藏 parent |
| shouldHideOnMousedownDocument | `boolean \| ((e: Event) => boolean)` | - | 否 | 点击外部时是否隐藏 |
| target | `(() => HTMLElement \| null \| void) \| HTMLElement \| null` | - | 否 | 指定目标元素 |

## 典型用法

### 基础 Popover

```tsx
import { Popover, Button, Input } from '@befe/brick'

// click 触发，带标题和关闭按钮
<Popover headline="有个标题" content="一小段说明文字" withCloseX>
    <Button type="important">Basic</Button>
</Popover>

// hover 触发，带操作项
<Popover
    type="hover"
    placement="bottom-start"
    content="这里是一小段信息"
    actions={[<Button key="op" type="plain" color="brand">操作项</Button>]}
    actionsAlign="left"
>
    <span>hover me</span>
</Popover>

// focus 触发（输入框场景）
<Popover content="这里是一小段信息" type="focus">
    <Input placeholder="focus to show" />
</Popover>
```

### 确认气泡（PopoverConfirm）

```tsx
import { PopoverConfirm, Button } from '@befe/brick'

<PopoverConfirm
    content="请再次确认要进行操作"
    onConfirm={() => console.log('confirm')}
    onCancel={() => console.log('cancel')}
>
    <Button>简单确认</Button>
</PopoverConfirm>

// 带类型
<PopoverConfirm type="error" content="确认删除？">
    <Button type="intensive" color="danger">危险操作</Button>
</PopoverConfirm>
```

### 异步确认后提交

> 二次确认后提交时，loading 效果应控制在 Button 的 `props.loading` 上，而非控制 PopoverConfirm 的 `props.visible`。

```tsx
const [isSubmitting, setIsSubmitting] = useState(false)

const submit = async () => {
    setIsSubmitting(true)
    await doSomething()
    setIsSubmitting(false)
}

<PopoverConfirm onConfirm={submit} content="是否提交？">
    <Button type="intensive" loading={isSubmitting}>提交</Button>
</PopoverConfirm>
```

### beforeChange 拦截

```tsx
<Popover
    content="一小段说明文字"
    beforeChange={(visible) => {
        if (visible && !confirm('假设我们做了一些校验')) return false
        return true
    }}
>
    <Button>before change</Button>
</Popover>
```

## 注意事项

- 非控制型 PopoverConfirm 在 `onConfirm` 时会立即关闭，不支持根据异步操作结果决定是否关闭
- 二次确认场景中，loading 效果应通过 Button 的 `props.loading` 实现，不应通过控制 `props.visible` 实现
- `beforeChange` 适用于打开前的同步/异步校验，返回 `false` 或 `Promise.reject()` 均可阻止弹出
- `props.message` 已废弃，请使用 `props.content`
- `target` 只接受 HTMLElement 或返回 HTMLElement 的函数，不接受 ReactNode
