Skip to content

Commit

Permalink
feat: export dialog panel (#291)
Browse files Browse the repository at this point in the history
* feat: export dialog panel

* fix: props pass
  • Loading branch information
zombieJ authored Jun 9, 2022
1 parent 3c2a9d2 commit 79649e1
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 102 deletions.
3 changes: 3 additions & 0 deletions docs/demo/pure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## pure-debug

<code src="../examples/pure.tsx">
11 changes: 11 additions & 0 deletions docs/examples/pure.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* eslint no-console:0 */
import * as React from 'react';
import 'rc-select/assets/index.less';
import { Panel } from 'rc-dialog';
import '../../assets/index.less';

export default () => (
<Panel prefixCls="rc-dialog" title="Title" closable>
Hello World!
</Panel>
);
137 changes: 137 additions & 0 deletions src/Dialog/Content/Panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import React, { useRef } from 'react';
import classNames from 'classnames';
import type { IDialogChildProps } from '..';
import MemoChildren from './MemoChildren';

const sentinelStyle = { width: 0, height: 0, overflow: 'hidden', outline: 'none' };

export interface PanelProps extends Omit<IDialogChildProps, 'getOpenCount'> {
prefixCls: string;
ariaId?: string;
onMouseDown?: React.MouseEventHandler;
onMouseUp?: React.MouseEventHandler;
holderRef?: React.Ref<HTMLDivElement>;
}

export type ContentRef = {
focus: () => void;
changeActive: (next: boolean) => void;
};

const Panel = React.forwardRef<ContentRef, PanelProps>((props, ref) => {
const {
prefixCls,
className,
style,
title,
ariaId,
footer,
closable,
closeIcon,
onClose,
children,
bodyStyle,
bodyProps,
modalRender,
onMouseDown,
onMouseUp,
holderRef,
visible,
forceRender,
width,
height,
} = props;

// ================================= Refs =================================
const sentinelStartRef = useRef<HTMLDivElement>();
const sentinelEndRef = useRef<HTMLDivElement>();

React.useImperativeHandle(ref, () => ({
focus: () => {
sentinelStartRef.current?.focus();
},
changeActive: (next) => {
const { activeElement } = document;
if (next && activeElement === sentinelEndRef.current) {
sentinelStartRef.current.focus();
} else if (!next && activeElement === sentinelStartRef.current) {
sentinelEndRef.current.focus();
}
},
}));

// ================================ Style =================================
const contentStyle: React.CSSProperties = {};

if (width !== undefined) {
contentStyle.width = width;
}
if (height !== undefined) {
contentStyle.height = height;
}
// ================================ Render ================================
let footerNode: React.ReactNode;
if (footer) {
footerNode = <div className={`${prefixCls}-footer`}>{footer}</div>;
}

let headerNode: React.ReactNode;
if (title) {
headerNode = (
<div className={`${prefixCls}-header`}>
<div className={`${prefixCls}-title`} id={ariaId}>
{title}
</div>
</div>
);
}

let closer: React.ReactNode;
if (closable) {
closer = (
<button type="button" onClick={onClose} aria-label="Close" className={`${prefixCls}-close`}>
{closeIcon || <span className={`${prefixCls}-close-x`} />}
</button>
);
}

const content = (
<div className={`${prefixCls}-content`}>
{closer}
{headerNode}
<div className={`${prefixCls}-body`} style={bodyStyle} {...bodyProps}>
{children}
</div>
{footerNode}
</div>
);

return (
<div
key="dialog-element"
role="dialog"
aria-labelledby={title ? ariaId : null}
aria-modal="true"
ref={holderRef}
style={{
...style,
...contentStyle,
}}
className={classNames(prefixCls, className)}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
>
<div tabIndex={0} ref={sentinelStartRef} style={sentinelStyle} aria-hidden="true" />
<MemoChildren shouldUpdate={visible || forceRender}>
{modalRender ? modalRender(content) : content}
</MemoChildren>
<div tabIndex={0} ref={sentinelEndRef} style={sentinelStyle} aria-hidden="true" />
</div>
);
});

if (process.env.NODE_ENV !== 'production') {
Panel.displayName = 'Panel';
}

export default Panel;
112 changes: 13 additions & 99 deletions src/Dialog/Content/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,80 +2,37 @@ import * as React from 'react';
import { useRef } from 'react';
import classNames from 'classnames';
import CSSMotion from 'rc-motion';
import type { IDialogChildProps } from '..';
import { offset } from '../../util';
import MemoChildren from './MemoChildren';

const sentinelStyle = { width: 0, height: 0, overflow: 'hidden', outline: 'none' };
import type { PanelProps, ContentRef } from './Panel';
import Panel from './Panel';

export type ContentProps = {
motionName: string;
ariaId: string;
onVisibleChanged: (visible: boolean) => void;
onMouseDown: React.MouseEventHandler;
onMouseUp: React.MouseEventHandler;
} & IDialogChildProps;

export type ContentRef = {
focus: () => void;
changeActive: (next: boolean) => void;
};
} & PanelProps;

const Content = React.forwardRef<ContentRef, ContentProps>((props, ref) => {
const {
closable,
prefixCls,
width,
height,
footer,
title,
closeIcon,
style,
className,
visible,
forceRender,
bodyStyle,
bodyProps,
children,
destroyOnClose,
modalRender,
motionName,
ariaId,
onClose,
onVisibleChanged,
onMouseDown,
onMouseUp,
mousePosition,
} = props;

const sentinelStartRef = useRef<HTMLDivElement>();
const sentinelEndRef = useRef<HTMLDivElement>();
const dialogRef = useRef<HTMLDivElement>();

// ============================== Ref ===============================
React.useImperativeHandle(ref, () => ({
focus: () => {
sentinelStartRef.current?.focus();
},
changeActive: (next) => {
const { activeElement } = document;
if (next && activeElement === sentinelEndRef.current) {
sentinelStartRef.current.focus();
} else if (!next && activeElement === sentinelStartRef.current) {
sentinelEndRef.current.focus();
}
},
}));

// ============================= Style ==============================
const [transformOrigin, setTransformOrigin] = React.useState<string>();
const contentStyle: React.CSSProperties = {};
if (width !== undefined) {
contentStyle.width = width;
}
if (height !== undefined) {
contentStyle.height = height;
}

if (transformOrigin) {
contentStyle.transformOrigin = transformOrigin;
}
Expand All @@ -91,42 +48,6 @@ const Content = React.forwardRef<ContentRef, ContentProps>((props, ref) => {
}

// ============================= Render =============================
let footerNode: React.ReactNode;
if (footer) {
footerNode = <div className={`${prefixCls}-footer`}>{footer}</div>;
}

let headerNode: React.ReactNode;
if (title) {
headerNode = (
<div className={`${prefixCls}-header`}>
<div className={`${prefixCls}-title`} id={ariaId}>
{title}
</div>
</div>
);
}

let closer: React.ReactNode;
if (closable) {
closer = (
<button type="button" onClick={onClose} aria-label="Close" className={`${prefixCls}-close`}>
{closeIcon || <span className={`${prefixCls}-close-x`} />}
</button>
);
}

const content = (
<div className={`${prefixCls}-content`}>
{closer}
{headerNode}
<div className={`${prefixCls}-body`} style={bodyStyle} {...bodyProps}>
{children}
</div>
{footerNode}
</div>
);

return (
<CSSMotion
visible={visible}
Expand All @@ -139,23 +60,16 @@ const Content = React.forwardRef<ContentRef, ContentProps>((props, ref) => {
ref={dialogRef}
>
{({ className: motionClassName, style: motionStyle }, motionRef) => (
<div
key="dialog-element"
role="dialog"
aria-labelledby={title ? ariaId : null}
aria-modal="true"
ref={motionRef}
<Panel
{...props}
ref={ref}
title={title}
ariaId={ariaId}
prefixCls={prefixCls}
holderRef={motionRef}
style={{ ...motionStyle, ...style, ...contentStyle }}
className={classNames(prefixCls, className, motionClassName)}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
>
<div tabIndex={0} ref={sentinelStartRef} style={sentinelStyle} aria-hidden="true" />
<MemoChildren shouldUpdate={visible || forceRender}>
{modalRender ? modalRender(content) : content}
</MemoChildren>
<div tabIndex={0} ref={sentinelEndRef} style={sentinelStyle} aria-hidden="true" />
</div>
className={classNames(className, motionClassName)}
/>
)}
</CSSMotion>
);
Expand Down
2 changes: 1 addition & 1 deletion src/Dialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import type ScollLocker from 'rc-util/lib/Dom/scrollLocker';
import type { IDialogPropTypes } from '../IDialogPropTypes';
import Mask from './Mask';
import { getMotionName } from '../util';
import type { ContentRef } from './Content';
import Content from './Content';
import type { ContentRef } from './Content/Panel';

export type IDialogChildProps = {
// zombieJ: This should be handle on top instead of each Dialog.
Expand Down
6 changes: 4 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import DialogWrap from './DialogWrap';
import { IDialogPropTypes as DialogProps } from './IDialogPropTypes';
import Panel from './Dialog/Content/Panel';
import type { IDialogPropTypes as DialogProps } from './IDialogPropTypes';

export { DialogProps };
export type { DialogProps };
export { Panel };

export default DialogWrap;

1 comment on commit 79649e1

@vercel
Copy link

@vercel vercel bot commented on 79649e1 Jun 9, 2022

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.