Skip to content

Commit

Permalink
feat(popover): popover 不再对触发元素包裹 div;popover 默认挂载到 body 上 (#1527)
Browse files Browse the repository at this point in the history
Co-authored-by: maxin <maxin@growingio.com>
  • Loading branch information
nnmax and maxin authored Nov 25, 2021
1 parent 520f170 commit e186fb0
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 67 deletions.
2 changes: 1 addition & 1 deletion src/date-picker/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface DatePickerProps
/**
* 自定义的触发器
*/
trigger?: React.ReactNode;
trigger?: React.ReactElement;
/**
* 日期展示格式
* @see https://date-fns.org/v2.25.0/docs/fp/format
Expand Down
2 changes: 1 addition & 1 deletion src/date-range-picker/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface DateRangePickerProps
/**
* 自定义的触发器
*/
trigger?: React.ReactNode;
trigger?: React.ReactElement;
/**
* 日期展示格式
* @see https://date-fns.org/v2.25.0/docs/fp/format
Expand Down
2 changes: 1 addition & 1 deletion src/list/inner/CascaderItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const CascaderItem = WithRef<HTMLLIElement, CascaderItemProps & Omit<DOMAttribut
return <></>;
};

const PopoverRender = (element: React.ReactNode): React.ReactElement => {
const PopoverRender = (element: React.ReactElement): React.ReactElement => {
if (!isEmpty(childrens) || React.isValidElement(children)) {
return (
<div className={prefixClsItem}>
Expand Down
2 changes: 1 addition & 1 deletion src/list/interfance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export interface OptionProps {
disabledTooltip?: string;
prefix?: string | React.ReactNode;
suffix?: string | React.ReactNode;
wrapper?: (element: React.ReactNode) => React.ReactElement;
wrapper?: (element: React.ReactElement) => React.ReactElement;

[key: string]: unknown;
}
Expand Down
131 changes: 81 additions & 50 deletions src/popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const Popover = (props: PopoverProps) => {
strategy = 'absolute',
triggerClassName,
triggerStyle,
getContainer,
getContainer = () => document.body,
distoryOnHide = true,
} = props;

Expand Down Expand Up @@ -55,7 +55,7 @@ const Popover = (props: PopoverProps) => {
const triggerChildEvent = useCallback(
(name: string, e: any) => {
if (supportRef(children)) {
const fireEvent = (children as React.ReactElement)?.props?.[name];
const fireEvent = children?.props?.[name];
fireEvent?.(e);
}
},
Expand Down Expand Up @@ -120,20 +120,19 @@ const Popover = (props: PopoverProps) => {

const isFocusToShow = useMemo(() => trigger.indexOf('focus') !== -1, [trigger]);

const onMouseEnter = useMemo(
() =>
debounce((e: Event) => {
triggerChildEvent('onMouseEnter', e);
isHoverToShow && updateVisible(true);
}, 100),
[triggerChildEvent, isHoverToShow, updateVisible]
const onMouseEnter = useCallback(
(e: Event) => {
triggerChildEvent('onMouseEnter', e);
isHoverToShow && updateVisible(true);
},
[isHoverToShow, triggerChildEvent, updateVisible]
);
const onMouseLeave = useMemo(
() =>
debounce((e: Event) => {
triggerChildEvent('onMouseLeave', e);
isHoverToShow && updateVisible(false);
}, 100),

const onMouseLeave = useCallback(
(e: Event) => {
triggerChildEvent('onMouseLeave', e);
isHoverToShow && updateVisible(false);
},
[triggerChildEvent, isHoverToShow, updateVisible]
);

Expand Down Expand Up @@ -176,21 +175,6 @@ const Popover = (props: PopoverProps) => {
[trigger, updateVisible]
);

const divRoles = useMemo(() => {
const roles: any = {};
if (isClickToShow) {
roles.onClick = onClick;
}
if (isFocusToShow) {
roles.onFocus = onFocus;
roles.onBlur = onBlur;
}
if (isHoverToShow) {
roles.onMouseEnter = onMouseEnter;
roles.onMouseLeave = onMouseLeave;
}
return roles;
}, [isClickToShow, isFocusToShow, isHoverToShow, onClick, onFocus, onBlur, onMouseEnter, onMouseLeave]);
const contentRender = content && (
<div
{...attributes.popper}
Expand All @@ -207,29 +191,75 @@ const Popover = (props: PopoverProps) => {
</div>
);

// =============== refs =====================
let triggerNode = (
<div
className={classNames(`${prefixCls}__popcorn`, triggerClassName)}
style={triggerStyle}
ref={(instance) => setReferenceELement(instance)}
{...divRoles}
>
{children}
</div>
);
const triggerNode = useMemo(() => {
const child = React.Children.only(children);

const { props: childProps } = child;

if (supportRef(children)) {
const cloneProps = {
...divRoles,
className: classNames((children as React.ReactElement)?.props?.className),
style: { ...(children as React.ReactElement)?.props?.style, ...overlayInnerStyle },
const handleEvents: {
onClick?: typeof onClick;
onFocus?: typeof onFocus;
onBlur?: typeof onBlur;
onMouseEnter?: typeof onMouseEnter;
onMouseLeave?: typeof onMouseLeave;
} = {};

if (isClickToShow) {
handleEvents.onClick = (event) => {
onClick(event);
childProps.onClick?.(event);
};
}
if (isFocusToShow) {
handleEvents.onFocus = (event) => {
onFocus(event);
childProps.onFocus?.(event);
};
handleEvents.onBlur = (event) => {
onBlur(event);
childProps.onBlur?.(event);
};
}
if (isHoverToShow) {
handleEvents.onMouseEnter = (event) => {
onMouseEnter(event);
childProps.onMouseEnter?.(event);
};
handleEvents.onMouseLeave = (event) => {
onMouseLeave(event);
childProps.onMouseLeave?.(event);
};
}

const cloneProps: Partial<any> & React.Attributes = {
...handleEvents,
className: classNames(childProps.className, triggerClassName, `${prefixCls}__popcorn`),
style: { ...childProps.style, ...triggerStyle },
};

const child = React.Children.only(children);
cloneProps.ref = composeRef(setReferenceELement, (child as any).ref);
triggerNode = React.cloneElement(child as React.ReactElement, cloneProps);
}
if (supportRef(child)) {
cloneProps.ref = composeRef(setReferenceELement, (child as any).ref);
return React.cloneElement(child, cloneProps);
}

const referenceNode = document.getElementsByClassName(`${prefixCls}__popcorn`)?.[0] as HTMLElement;
setReferenceELement(referenceNode);

return React.cloneElement(child, cloneProps);
}, [
children,
isClickToShow,
isFocusToShow,
isHoverToShow,
onBlur,
onClick,
onFocus,
onMouseEnter,
onMouseLeave,
prefixCls,
triggerClassName,
triggerStyle,
]);

const renderContent = (
<>
Expand All @@ -238,6 +268,7 @@ const Popover = (props: PopoverProps) => {
: contentRender}
</>
);

return (
<>
{triggerNode}
Expand Down
13 changes: 5 additions & 8 deletions src/popover/demos/Popover.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ Portal.args = {

const SupportRefTemplate: Story<PopoverProps> = (args) => (
<Popover {...args} strategy="fixed">
Only String
<span>Only String</span>
</Popover>
);

Expand All @@ -261,15 +261,12 @@ const NotSupportRefTemplate: Story<PopoverProps> = (args) => {
const onClick = () => {
console.log('Click trigger button!');
};
const NotSupportRefNode = (props: any) => <Button {...props}>Button Trigger</Button>;
return (
<>
<span>margin: 30px</span>
<br />
<div style={{ border: '1px solid #3c3c3c', borderRadius: '4px', display: 'inline-block' }}>
<div style={{ border: '1px solid #3c3c3c', borderRadius: '4px', padding: 300 }}>
<Popover {...args} strategy="fixed">
<Button style={{ margin: 30 }} onClick={onClick}>
Button Trigger
</Button>
<NotSupportRefNode onClick={onClick} />
</Popover>
</div>
</>
Expand All @@ -284,7 +281,7 @@ NotSupportRef.args = {

const DisabledTemplate: Story<PopoverProps> = (args) => (
<Popover {...args} strategy="fixed" disabled>
Disabled
<span>Disabled</span>
</Popover>
);

Expand Down
25 changes: 25 additions & 0 deletions src/popover/demos/PopoverPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,31 @@ export default function ListPage() {
<li>样式:无变化。</li>
<li>api变化:内容由content包裹,去除了footer,组件用到了react-popper</li>
</ul>
<p>注意点⚠️</p>
<ul>
<li>被包裹的元素必须是一个原生 HTML 标签或者 React 组件</li>
<li>被包裹的组件尽量能传递 ref(及使用 React.forwardRef()),因为底层使用 ref 定位</li>
<li>如果不支持传递 ref,则通过 className 定位元素</li>
<li>
被包裹的元素必须能接收剩余的 props,如下:
<br />
<br />
<code>{`const Trigger = (props) => <Button {...props}>Trigger</Button>`}</code>
<br />
<br />
<code>ReactDOM.render((</code>
<br />
<code style={{ paddingLeft: '2em' }}>{`<Popover>`}</code>
<br />
<code style={{ paddingLeft: '4em' }}>{`<Trigger />`}</code>
<br />
<code style={{ paddingLeft: '2em' }}>{`</Popover>`}</code>
<br />
<code>), document.body)</code>
<br />
<br />
</li>
</ul>
<Heading>{formatMessage({ defaultMessage: '代码演示' })}</Heading>
<Subheading>{formatMessage({ defaultMessage: 'controlled' })}</Subheading>
<Canvas>
Expand Down
3 changes: 2 additions & 1 deletion src/popover/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export interface PopoverProps {
/**
被包裹的元素
*/
children: React.ReactNode;
children: React.ReactElement;

placement?: Placement;
trigger?: TriggerAction | TriggerAction[];
Expand Down Expand Up @@ -93,6 +93,7 @@ export interface PopoverProps {
strategy?: 'fixed' | 'absolute';
/**
* 浮动显示的层
* @default () => document.body
*/
getContainer?: (node: HTMLElement) => HTMLElement;
/**
Expand Down
3 changes: 0 additions & 3 deletions src/popover/style/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
@popover-background-color: #ffffff00;

.@{popover-prefix-cls} {
&__popcorn {
display: inline-block;
}
&__content {
z-index: @z-index-popover;
visibility: hidden;
Expand Down
2 changes: 1 addition & 1 deletion src/tooltip/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ export interface TooltipProps extends Omit<PopoverProps, 'content'> {
/**
被包裹的元素
*/
children: React.ReactNode;
children: React.ReactElement;
}

1 comment on commit e186fb0

@vercel
Copy link

@vercel vercel bot commented on e186fb0 Nov 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.