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

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();