mirror of
https://github.com/zhigang1992/react.git
synced 2026-02-02 17:18:52 +08:00
@@ -46,3 +46,4 @@ export { default as Table } from './table'
|
||||
export { default as Toggle } from './toggle'
|
||||
export { default as Snippet } from './snippet'
|
||||
export { default as Tooltip } from './tooltip'
|
||||
export { default as Popover } from './popover'
|
||||
|
||||
7
components/popover/index.ts
Normal file
7
components/popover/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import Popover from './popover'
|
||||
import PopoverItem from './popover-item'
|
||||
|
||||
Popover.Item = PopoverItem
|
||||
Popover.Option = PopoverItem
|
||||
|
||||
export default Popover
|
||||
76
components/popover/popover-item.tsx
Normal file
76
components/popover/popover-item.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React from 'react'
|
||||
import useTheme from '../styles/use-theme'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
|
||||
interface Props {
|
||||
line?: boolean
|
||||
title?: boolean
|
||||
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
line: false,
|
||||
title: false,
|
||||
}
|
||||
|
||||
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
|
||||
export type PopoverItemProps = Props & typeof defaultProps & NativeAttrs
|
||||
|
||||
const PopoverItem: React.FC<React.PropsWithChildren<PopoverItemProps>> = React.memo(({
|
||||
children, line, title, className, ...props
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<>
|
||||
<div className={`item ${line ? 'line' : ''} ${title ? 'title' : ''} ${className}`} {...props}>
|
||||
{children}
|
||||
<style jsx>{`
|
||||
.item {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: .5rem ${theme.layout.gap};
|
||||
color: ${theme.palette.accents_5};
|
||||
font-size: .875rem;
|
||||
line-height: 1.25rem;
|
||||
text-align: left;
|
||||
transition: color 0.1s ease 0s, background-color 0.1s ease 0s;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
color: ${theme.palette.foreground};
|
||||
}
|
||||
|
||||
.item > :global(*) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.item.line {
|
||||
line-height: 0;
|
||||
height: 0;
|
||||
padding: 0;
|
||||
border-top: 1px solid ${theme.palette.border};
|
||||
margin: .5rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.item.title {
|
||||
padding: 1.15rem;
|
||||
font-weight: 500;
|
||||
font-size: .83rem;
|
||||
color: ${theme.palette.foreground};
|
||||
}
|
||||
|
||||
.item.title:first-of-type {
|
||||
padding-top: .6rem;
|
||||
padding-bottom: .6rem;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
{title && <PopoverItem line title={false} />}
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
export default withDefaults(PopoverItem, defaultProps)
|
||||
61
components/popover/popover.tsx
Normal file
61
components/popover/popover.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import useTheme from '../styles/use-theme'
|
||||
import Tooltip, { TooltipProps } from '../tooltip/tooltip'
|
||||
import PopoverItem from '../popover/popover-item'
|
||||
import { Placement, TriggerTypes } from '../utils/prop-types'
|
||||
import { getReactNode } from '../utils/collections'
|
||||
|
||||
interface Props {
|
||||
content?: React.ReactNode | (() => React.ReactNode)
|
||||
trigger?: TriggerTypes
|
||||
placement?: Placement
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
trigger: 'click' as TriggerTypes,
|
||||
placement: 'bottom',
|
||||
}
|
||||
|
||||
type ExcludeTooltipProps = {
|
||||
type: any
|
||||
text: any
|
||||
trigger: any
|
||||
placement: any,
|
||||
}
|
||||
|
||||
export type PopoverProps = Props & Omit<TooltipProps, keyof ExcludeTooltipProps>
|
||||
|
||||
const Popover: React.FC<React.PropsWithChildren<PopoverProps>> = ({
|
||||
content, children, trigger, placement, portalClassName, ...props
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const textNode = useMemo(() => getReactNode(content), [content])
|
||||
|
||||
return (
|
||||
<Tooltip text={textNode} trigger={trigger} placement={placement}
|
||||
portalClassName={`popover ${portalClassName}`}
|
||||
{...props}>
|
||||
{children}
|
||||
<style jsx>{`
|
||||
:global(.tooltip-content.popover > .inner) {
|
||||
padding: ${theme.layout.gapHalf} 0;
|
||||
text-align: center;
|
||||
}
|
||||
`}</style>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
type PopoverComponent<P = {}> = React.FC<P> & {
|
||||
Item: typeof PopoverItem
|
||||
Option: typeof PopoverItem
|
||||
}
|
||||
|
||||
type ComponentProps = Partial<typeof defaultProps>
|
||||
& Omit<Props, keyof typeof defaultProps>
|
||||
& Omit<TooltipProps, keyof ExcludeTooltipProps>
|
||||
|
||||
(Popover as PopoverComponent<ComponentProps>).defaultProps = defaultProps
|
||||
|
||||
export default Popover as PopoverComponent<ComponentProps>
|
||||
@@ -73,7 +73,6 @@ const TooltipContent: React.FC<React.PropsWithChildren<Props>> = React.memo(({
|
||||
|
||||
const preventHandler = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
event.nativeEvent.stopImmediatePropagation()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useRef, useState } from 'react'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
import TooltipContent from './tooltip-content'
|
||||
import useClickAway from '../utils/use-click-away'
|
||||
@@ -8,6 +8,7 @@ interface Props {
|
||||
text: string | React.ReactNode
|
||||
type?: SnippetTypes
|
||||
placement?: Placement
|
||||
visible?: boolean
|
||||
initialVisible?: boolean
|
||||
hideArrow?: boolean
|
||||
trigger?: TriggerTypes
|
||||
@@ -39,7 +40,7 @@ export type TooltipProps = Props & typeof defaultProps & NativeAttrs
|
||||
const Tooltip: React.FC<React.PropsWithChildren<TooltipProps>> = ({
|
||||
children, initialVisible, text, offset, placement, portalClassName,
|
||||
enterDelay, leaveDelay, trigger, type, className, onVisibleChange,
|
||||
hideArrow, ...props
|
||||
hideArrow, visible: customVisible, ...props
|
||||
}) => {
|
||||
const timer = useRef<number>()
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
@@ -74,8 +75,14 @@ const Tooltip: React.FC<React.PropsWithChildren<TooltipProps>> = ({
|
||||
|
||||
const mouseEventHandler = (next: boolean) => trigger === 'hover' && changeVisible(next)
|
||||
const clickEventHandler = () => trigger === 'click' && changeVisible(!visible)
|
||||
|
||||
useClickAway(ref, () => trigger === 'click' && changeVisible(false))
|
||||
|
||||
useEffect(() => {
|
||||
if (customVisible === undefined) return
|
||||
changeVisible(customVisible)
|
||||
}, [customVisible])
|
||||
|
||||
return (
|
||||
<div ref={ref} className={`tooltip ${className}`}
|
||||
onClick={clickEventHandler}
|
||||
|
||||
@@ -101,3 +101,12 @@ export const setChildrenIndex = (
|
||||
return item
|
||||
})
|
||||
}
|
||||
|
||||
export const getReactNode = (
|
||||
node?: React.ReactNode | (() => React.ReactNode),
|
||||
): React.ReactNode => {
|
||||
if (!node) return null
|
||||
|
||||
if (typeof node !== 'function') return node
|
||||
return (node as () => React.ReactNode)()
|
||||
}
|
||||
|
||||
134
pages/en-us/components/popover.mdx
Normal file
134
pages/en-us/components/popover.mdx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { Layout, Playground, Attributes } from 'lib/components'
|
||||
import { Popover, Spacer, Link } from 'components'
|
||||
import { useState } from 'react'
|
||||
|
||||
export const meta = {
|
||||
title: 'Popover',
|
||||
group: 'Data Display',
|
||||
}
|
||||
|
||||
## Popover
|
||||
|
||||
The floating box popped by clicking or hovering.
|
||||
|
||||
<Playground
|
||||
scope={{ Popover, Spacer, Link }}
|
||||
code={`
|
||||
() => {
|
||||
const content = () => (
|
||||
<div style={{ padding: '0 10px' }}>
|
||||
<Link pure href="#">A hyperlink</Link>
|
||||
<Spacer y={.5} />
|
||||
<Link pure color href="#">External link</Link>
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<Popover content={content}>
|
||||
Menu
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Preset Item"
|
||||
desc="Use preset `Item` component build layout."
|
||||
scope={{ Popover, Spacer, Link }}
|
||||
code={`
|
||||
() => {
|
||||
const content = () => (
|
||||
<>
|
||||
<Popover.Item title>
|
||||
<span>User Settings</span>
|
||||
</Popover.Item>
|
||||
<Popover.Item>
|
||||
<Link pure href="#">A hyperlink</Link>
|
||||
</Popover.Item>
|
||||
<Popover.Item>
|
||||
<Link pure color href="#">A hyperlink for edit profile</Link>
|
||||
</Popover.Item>
|
||||
<Popover.Item line />
|
||||
<Popover.Item>
|
||||
<span>Command-Line</span>
|
||||
</Popover.Item>
|
||||
</>
|
||||
)
|
||||
return (
|
||||
<Popover content={content}>
|
||||
Menu
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Close Manual"
|
||||
desc="You can control when to close the pop-up box."
|
||||
scope={{ Popover, Spacer, Link, useState }}
|
||||
code={`
|
||||
() => {
|
||||
const [visible, setVisible] = useState(false)
|
||||
const changeHandler = (next) => {
|
||||
setVisible(next)
|
||||
}
|
||||
const content = () => (
|
||||
<div style={{ padding: '0 10px' }}>
|
||||
<span onClick={() => setVisible(false)}>Click to close</span>
|
||||
<Spacer y={.5} />
|
||||
<span>Nothing</span>
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<Popover content={content} visible={visible}
|
||||
onVisibleChange={changeHandler}>
|
||||
Menu
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
`} />
|
||||
|
||||
<Attributes edit="/pages/en-us/components/popover.mdx">
|
||||
<Attributes.Title>Popover.Props</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **content** | content of pop-up | `ReactNode` `() => ReactNode` | - | - |
|
||||
| **visible** | visible or not | `boolean` | - | `false` |
|
||||
| **initialVisible** | visible on initial | `boolean` | - | `false` |
|
||||
| **hideArrow** | hide arrow icon | `boolean` | - | `false` |
|
||||
| **placement** | position of the popover relative to the target | [Placement](#placement) | - | `bottom` |
|
||||
| **trigger** | tooltip trigger mode | `'click' / 'hover'` | - | `click` |
|
||||
| **enterDelay**(ms) | delay before popover is shown | `number` | - | `100` |
|
||||
| **leaveDelay**(ms) | delay before popover is hidden | `number` | - | `0` |
|
||||
| **offset**(px) | distance between pop-up and target | `number` | - | `12` |
|
||||
| **portalClassName** | className of pop-up box | `string` | - | - |
|
||||
| **onVisibleChange** | call when visibility of the popover is changed | `(visible: boolean) => void` | - | - |
|
||||
| ... | native props | `HTMLAttributes` | `'id', 'name', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title alias="Popover.Option">Popover.Item</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **line** | show a line | `boolean` | - | `false` |
|
||||
| **title** | show text with title style | `boolean` | - | `false` |
|
||||
|
||||
<Attributes.Title>Placement</Attributes.Title>
|
||||
|
||||
```ts
|
||||
type Placement = 'top'
|
||||
| 'topStart',
|
||||
| 'topEnd',
|
||||
| 'left',
|
||||
| 'leftStart',
|
||||
| 'leftEnd',
|
||||
| 'bottom',
|
||||
| 'bottomStart',
|
||||
| 'bottomEnd',
|
||||
| 'right',
|
||||
| 'rightStart',
|
||||
| 'rightEnd',
|
||||
```
|
||||
|
||||
</Attributes>
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>
|
||||
@@ -178,6 +178,7 @@ Displays additional information on hover.
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **text** | text of pop-up | `string` `React.ReactNode` | - | - |
|
||||
| **visible** | visible or not | `boolean` | - | `false` |
|
||||
| **initialVisible** | visible on initial | `boolean` | - | `false` |
|
||||
| **hideArrow** | hide arrow icon | `boolean` | - | `false` |
|
||||
| **type** | preset style type | [TooltipTypes](#tooltiptypes) | - | `default` |
|
||||
|
||||
135
pages/zh-cn/components/popover.mdx
Normal file
135
pages/zh-cn/components/popover.mdx
Normal file
@@ -0,0 +1,135 @@
|
||||
import { Layout, Playground, Attributes } from 'lib/components'
|
||||
import { Popover, Spacer, Link } from 'components'
|
||||
import { useState } from 'react'
|
||||
|
||||
export const meta = {
|
||||
title: '气泡卡片 Popover',
|
||||
group: '数据展示',
|
||||
}
|
||||
|
||||
## Popover / 气泡卡片
|
||||
|
||||
通过点击或鼠标移入触发的气泡风格弹出层。
|
||||
|
||||
<Playground
|
||||
desc="基础示例。"
|
||||
scope={{ Popover, Spacer, Link }}
|
||||
code={`
|
||||
() => {
|
||||
const content = () => (
|
||||
<div style={{ padding: '0 10px' }}>
|
||||
<Link pure href="#">一个超链接</Link>
|
||||
<Spacer y={.5} />
|
||||
<Link pure color href="#">外部链接</Link>
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<Popover content={content}>
|
||||
菜单
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="预置子选项"
|
||||
desc="使用预置的 `Item` 组件完成弹出内容的布局。"
|
||||
scope={{ Popover, Spacer, Link }}
|
||||
code={`
|
||||
() => {
|
||||
const content = () => (
|
||||
<>
|
||||
<Popover.Item title>
|
||||
<span>用户设置</span>
|
||||
</Popover.Item>
|
||||
<Popover.Item>
|
||||
<Link pure href="#">一个超链接</Link>
|
||||
</Popover.Item>
|
||||
<Popover.Item>
|
||||
<Link pure color href="#">前往修改用户配置</Link>
|
||||
</Popover.Item>
|
||||
<Popover.Item line />
|
||||
<Popover.Item>
|
||||
<span>命令行工具</span>
|
||||
</Popover.Item>
|
||||
</>
|
||||
)
|
||||
return (
|
||||
<Popover content={content}>
|
||||
菜单
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="手动关闭"
|
||||
desc="你可以控制何时手动地关闭弹出卡片。"
|
||||
scope={{ Popover, Spacer, Link, useState }}
|
||||
code={`
|
||||
() => {
|
||||
const [visible, setVisible] = useState(false)
|
||||
const changeHandler = (next) => {
|
||||
setVisible(next)
|
||||
}
|
||||
const content = () => (
|
||||
<div style={{ padding: '0 10px' }}>
|
||||
<span onClick={() => setVisible(false)}>点击关闭</span>
|
||||
<Spacer y={.5} />
|
||||
<span>不关闭</span>
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<Popover content={content} visible={visible}
|
||||
onVisibleChange={changeHandler}>
|
||||
菜单
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
`} />
|
||||
|
||||
<Attributes edit="/pages/zh-cn/components/popover.mdx">
|
||||
<Attributes.Title>Popover.Props</Attributes.Title>
|
||||
|
||||
| 属性 | 描述 | 类型 | 推荐值 | 默认
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **content** | 气泡卡片内容 | `ReactNode` `() => ReactNode` | - | - |
|
||||
| **visible** | 手动控制气泡的显示与隐藏 | `boolean` | - | `false` |
|
||||
| **initialVisible** | 初始是否可见 | `boolean` | - | `false` |
|
||||
| **hideArrow** | 隐藏箭头 | `boolean` | - | `false` |
|
||||
| **placement** | 气泡卡片与目标的对齐方式 | [Placement](#placement) | - | `bottom` |
|
||||
| **trigger** | 触发气泡卡片的方式 | `'click' / 'hover'` | - | `click` |
|
||||
| **enterDelay**(ms) | 在提示显示前的延迟 | `number` | - | `100` |
|
||||
| **leaveDelay**(ms) | 关闭提示前的延迟 | `number` | - | `0` |
|
||||
| **offset**(px) | 提示框与目标之间的偏移 | `number` | - | `12` |
|
||||
| **portalClassName** | 气泡卡片的类名 | `string` | - | - |
|
||||
| **onVisibleChange** | 当气泡卡片状态改变时触发 | `(visible: boolean) => void` | - | - |
|
||||
| ... | 原生属性 | `HTMLAttributes` | `'id', 'name', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title alias="Popover.Option">Popover.Item</Attributes.Title>
|
||||
|
||||
| 属性 | 描述 | 类型 | 推荐值 | 默认
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **line** | 显示线条 | `boolean` | - | `false` |
|
||||
| **title** | 用标题的样式展示文字 | `boolean` | - | `false` |
|
||||
|
||||
<Attributes.Title>Placement</Attributes.Title>
|
||||
|
||||
```ts
|
||||
type Placement = 'top'
|
||||
| 'topStart',
|
||||
| 'topEnd',
|
||||
| 'left',
|
||||
| 'leftStart',
|
||||
| 'leftEnd',
|
||||
| 'bottom',
|
||||
| 'bottomStart',
|
||||
| 'bottomEnd',
|
||||
| 'right',
|
||||
| 'rightStart',
|
||||
| 'rightEnd',
|
||||
```
|
||||
|
||||
</Attributes>
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>
|
||||
@@ -178,6 +178,7 @@ export const meta = {
|
||||
| 属性 | 描述 | 类型 | 推荐值 | 默认
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **text** | 弹出框文字 | `string` `React.ReactNode` | - | - |
|
||||
| **visible** | 手动控制提示框的显示与隐藏 | `boolean` | - | `false` |
|
||||
| **initialVisible** | 初始是否可见 | `boolean` | - | `false` |
|
||||
| **hideArrow** | 隐藏箭头 | `boolean` | - | `false` |
|
||||
| **type** | 不同的文字提示类型 | [TooltipTypes](#tooltiptypes) | - | `default` |
|
||||
|
||||
Reference in New Issue
Block a user