You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
513 lines
14 KiB
513 lines
14 KiB
3 years ago
|
import * as React from 'react';
|
||
|
import classNames from 'classnames';
|
||
|
import useMergedState from 'rc-util/lib/hooks/useMergedState';
|
||
|
import toArray from 'rc-util/lib/Children/toArray';
|
||
|
import copy from 'copy-to-clipboard';
|
||
|
import omit from 'rc-util/lib/omit';
|
||
|
import { composeRef } from 'rc-util/lib/ref';
|
||
|
import EditOutlined from '@ant-design/icons/EditOutlined';
|
||
|
import CheckOutlined from '@ant-design/icons/CheckOutlined';
|
||
|
import CopyOutlined from '@ant-design/icons/CopyOutlined';
|
||
|
import ResizeObserver from 'rc-resize-observer';
|
||
|
import { AutoSizeType } from 'rc-textarea/lib/ResizableTextArea';
|
||
|
import { ConfigContext } from '../../config-provider';
|
||
|
import { useLocaleReceiver } from '../../locale-provider/LocaleReceiver';
|
||
|
import TransButton from '../../_util/transButton';
|
||
|
import { isStyleSupport } from '../../_util/styleChecker';
|
||
|
import Tooltip from '../../tooltip';
|
||
|
import Typography, { TypographyProps } from '../Typography';
|
||
|
import Editable from '../Editable';
|
||
|
import useMergedConfig from '../hooks/useMergedConfig';
|
||
|
import useUpdatedEffect from '../hooks/useUpdatedEffect';
|
||
|
import Ellipsis from './Ellipsis';
|
||
|
import EllipsisTooltip from './EllipsisTooltip';
|
||
|
|
||
|
export type BaseType = 'secondary' | 'success' | 'warning' | 'danger';
|
||
|
|
||
|
interface CopyConfig {
|
||
|
text?: string;
|
||
|
onCopy?: () => void;
|
||
|
icon?: React.ReactNode;
|
||
|
tooltips?: boolean | React.ReactNode;
|
||
|
}
|
||
|
|
||
|
interface EditConfig {
|
||
|
editing?: boolean;
|
||
|
icon?: React.ReactNode;
|
||
|
tooltip?: boolean | React.ReactNode;
|
||
|
onStart?: () => void;
|
||
|
onChange?: (value: string) => void;
|
||
|
onCancel?: () => void;
|
||
|
onEnd?: () => void;
|
||
|
maxLength?: number;
|
||
|
autoSize?: boolean | AutoSizeType;
|
||
|
triggerType?: ('icon' | 'text')[];
|
||
|
enterIcon?: React.ReactNode;
|
||
|
}
|
||
|
|
||
|
export interface EllipsisConfig {
|
||
|
rows?: number;
|
||
|
expandable?: boolean;
|
||
|
suffix?: string;
|
||
|
symbol?: React.ReactNode;
|
||
|
onExpand?: React.MouseEventHandler<HTMLElement>;
|
||
|
onEllipsis?: (ellipsis: boolean) => void;
|
||
|
tooltip?: React.ReactNode;
|
||
|
}
|
||
|
|
||
|
export interface BlockProps extends TypographyProps {
|
||
|
title?: string;
|
||
|
editable?: boolean | EditConfig;
|
||
|
copyable?: boolean | CopyConfig;
|
||
|
type?: BaseType;
|
||
|
disabled?: boolean;
|
||
|
ellipsis?: boolean | EllipsisConfig;
|
||
|
// decorations
|
||
|
code?: boolean;
|
||
|
mark?: boolean;
|
||
|
underline?: boolean;
|
||
|
delete?: boolean;
|
||
|
strong?: boolean;
|
||
|
keyboard?: boolean;
|
||
|
italic?: boolean;
|
||
|
}
|
||
|
|
||
|
function wrapperDecorations(
|
||
|
{ mark, code, underline, delete: del, strong, keyboard, italic }: BlockProps,
|
||
|
content: React.ReactNode,
|
||
|
) {
|
||
|
let currentContent = content;
|
||
|
|
||
|
function wrap(needed: boolean | undefined, tag: string) {
|
||
|
if (!needed) return;
|
||
|
|
||
|
currentContent = React.createElement(tag, {}, currentContent);
|
||
|
}
|
||
|
|
||
|
wrap(strong, 'strong');
|
||
|
wrap(underline, 'u');
|
||
|
wrap(del, 'del');
|
||
|
wrap(code, 'code');
|
||
|
wrap(mark, 'mark');
|
||
|
wrap(keyboard, 'kbd');
|
||
|
wrap(italic, 'i');
|
||
|
|
||
|
return currentContent;
|
||
|
}
|
||
|
|
||
|
function getNode(dom: React.ReactNode, defaultNode: React.ReactNode, needDom?: boolean) {
|
||
|
if (dom === true || dom === undefined) {
|
||
|
return defaultNode;
|
||
|
}
|
||
|
return dom || (needDom && defaultNode);
|
||
|
}
|
||
|
|
||
|
function toList<T>(val: T | T[]): T[] {
|
||
|
return Array.isArray(val) ? val : [val];
|
||
|
}
|
||
|
|
||
|
interface InternalBlockProps extends BlockProps {
|
||
|
component: string;
|
||
|
}
|
||
|
|
||
|
const ELLIPSIS_STR = '...';
|
||
|
|
||
|
const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
|
||
|
const {
|
||
|
prefixCls: customizePrefixCls,
|
||
|
className,
|
||
|
style,
|
||
|
type,
|
||
|
disabled,
|
||
|
children,
|
||
|
ellipsis,
|
||
|
editable,
|
||
|
copyable,
|
||
|
component,
|
||
|
title,
|
||
|
...restProps
|
||
|
} = props;
|
||
|
const { getPrefixCls, direction } = React.useContext(ConfigContext);
|
||
|
const textLocale = useLocaleReceiver('Text')[0]!; // Force TS get this
|
||
|
|
||
|
const typographyRef = React.useRef<HTMLElement>(null);
|
||
|
const editIconRef = React.useRef<HTMLDivElement>(null);
|
||
|
|
||
|
// ============================ MISC ============================
|
||
|
const prefixCls = getPrefixCls('typography', customizePrefixCls);
|
||
|
|
||
|
const textProps = omit(restProps, [
|
||
|
'mark',
|
||
|
'code',
|
||
|
'delete',
|
||
|
'underline',
|
||
|
'strong',
|
||
|
'keyboard',
|
||
|
'italic',
|
||
|
]) as any;
|
||
|
|
||
|
// ========================== Editable ==========================
|
||
|
const [enableEdit, editConfig] = useMergedConfig<EditConfig>(editable);
|
||
|
const [editing, setEditing] = useMergedState(false, {
|
||
|
value: editConfig.editing,
|
||
|
});
|
||
|
const { triggerType = ['icon'] } = editConfig;
|
||
|
|
||
|
const triggerEdit = (edit: boolean) => {
|
||
|
if (edit) {
|
||
|
editConfig.onStart?.();
|
||
|
}
|
||
|
|
||
|
setEditing(edit);
|
||
|
};
|
||
|
|
||
|
// Focus edit icon when back
|
||
|
useUpdatedEffect(() => {
|
||
|
if (!editing) {
|
||
|
editIconRef.current?.focus();
|
||
|