diff --git a/components/_util/getAllowClear.tsx b/components/_util/getAllowClear.tsx index ce487e4832c5..3bde54877f3d 100644 --- a/components/_util/getAllowClear.tsx +++ b/components/_util/getAllowClear.tsx @@ -13,7 +13,7 @@ const getAllowClear = (allowClear: AllowClear): AllowClear => { clearIcon: , }; } - + return mergedAllowClear; }; diff --git a/components/locale/en_US.ts b/components/locale/en_US.ts index 1800823a7402..85780c2829c8 100644 --- a/components/locale/en_US.ts +++ b/components/locale/en_US.ts @@ -80,6 +80,7 @@ const localeValues: Locale = { copy: 'Copy', copied: 'Copied', expand: 'Expand', + collapse: 'Collapse', }, Form: { optional: '(optional)', diff --git a/components/locale/index.tsx b/components/locale/index.tsx index 2f7fe3f7275e..cf06dbba9a52 100644 --- a/components/locale/index.tsx +++ b/components/locale/index.tsx @@ -40,6 +40,7 @@ export interface Locale { copy?: any; copied?: any; expand?: any; + collapse?: any; }; Form?: { optional?: string; diff --git a/components/locale/zh_CN.ts b/components/locale/zh_CN.ts index 5df7f470a150..c95913f6eb13 100644 --- a/components/locale/zh_CN.ts +++ b/components/locale/zh_CN.ts @@ -80,6 +80,7 @@ const localeValues: Locale = { copy: '复制', copied: '复制成功', expand: '展开', + collapse: '收起', }, Form: { optional: '(可选)', diff --git a/components/typography/Base/Ellipsis.tsx b/components/typography/Base/Ellipsis.tsx index 51c0c68f256e..d7d60b63b990 100644 --- a/components/typography/Base/Ellipsis.tsx +++ b/components/typography/Base/Ellipsis.tsx @@ -105,12 +105,11 @@ export interface EllipsisProps { rows: number; children: ( cutChildren: React.ReactNode[], - /** Tell current `cutChildren` is in ellipsis */ - inEllipsis: boolean, /** Tell current `text` is exceed the `rows` which can be ellipsis */ canEllipsis: boolean, ) => React.ReactNode; onEllipsis: (isEllipsis: boolean) => void; + expanded: boolean; /** * Mark for misc update. Which will not affect ellipsis content length. * e.g. tooltip content update. @@ -131,13 +130,14 @@ const lineClipStyle: React.CSSProperties = { }; export default function EllipsisMeasure(props: EllipsisProps) { - const { enableMeasure, width, text, children, rows, miscDeps, onEllipsis } = props; + const { enableMeasure, width, text, children, rows, expanded, miscDeps, onEllipsis } = props; const nodeList = React.useMemo(() => toArray(text), [text]); const nodeLen = React.useMemo(() => getNodesLen(nodeList), [text]); // ========================= Full Content ========================= - const fullContent = React.useMemo(() => children(nodeList, false, false), [text]); + // Used for measure only, which means it's always render as no need ellipsis + const fullContent = React.useMemo(() => children(nodeList, false), [text]); // ========================= Cut Content ========================== const [ellipsisCutIndex, setEllipsisCutIndex] = React.useState<[number, number] | null>(null); @@ -150,6 +150,7 @@ export default function EllipsisMeasure(props: EllipsisProps) { const descRowsEllipsisRef = React.useRef(null); const symbolRowEllipsisRef = React.useRef(null); + const [canEllipsis, setCanEllipsis] = React.useState(false); const [needEllipsis, setNeedEllipsis] = React.useState(STATUS_MEASURE_NONE); const [ellipsisHeight, setEllipsisHeight] = React.useState(0); @@ -169,6 +170,7 @@ export default function EllipsisMeasure(props: EllipsisProps) { setNeedEllipsis(isOverflow ? STATUS_MEASURE_NEED_ELLIPSIS : STATUS_MEASURE_NO_NEED_ELLIPSIS); setEllipsisCutIndex(isOverflow ? [0, nodeLen] : null); + setCanEllipsis(isOverflow); // Get the basic height of ellipsis rows const baseRowsEllipsisHeight = needEllipsisRef.current?.getHeight() || 0; @@ -218,7 +220,7 @@ export default function EllipsisMeasure(props: EllipsisProps) { !ellipsisCutIndex || ellipsisCutIndex[0] !== ellipsisCutIndex[1] ) { - const content = children(nodeList, false, false); + const content = children(nodeList, false); // Limit the max line count to avoid scrollbar blink // https://github.com/ant-design/ant-design/issues/42958 @@ -241,8 +243,8 @@ export default function EllipsisMeasure(props: EllipsisProps) { return content; } - return children(sliceNodes(nodeList, ellipsisCutIndex[0]), true, true); - }, [needEllipsis, ellipsisCutIndex, nodeList, ...miscDeps]); + return children(expanded ? nodeList : sliceNodes(nodeList, ellipsisCutIndex[0]), canEllipsis); + }, [expanded, needEllipsis, ellipsisCutIndex, nodeList, ...miscDeps]); // ============================ Render ============================ const measureStyle: React.CSSProperties = { @@ -293,7 +295,7 @@ export default function EllipsisMeasure(props: EllipsisProps) { }} ref={symbolRowEllipsisRef} > - {children([], true, true)} + {children([], true)} )} @@ -309,7 +311,7 @@ export default function EllipsisMeasure(props: EllipsisProps) { }} ref={cutMidRef} > - {children(sliceNodes(nodeList, cutMidIndex), true, true)} + {children(sliceNodes(nodeList, cutMidIndex), true)} )} diff --git a/components/typography/Base/index.tsx b/components/typography/Base/index.tsx index 3c3a43f30618..0008e0c9877d 100644 --- a/components/typography/Base/index.tsx +++ b/components/typography/Base/index.tsx @@ -52,10 +52,12 @@ interface EditConfig { export interface EllipsisConfig { rows?: number; - expandable?: boolean; + expandable?: boolean | 'collapsible'; suffix?: string; - symbol?: React.ReactNode; - onExpand?: React.MouseEventHandler; + symbol?: React.ReactNode | ((expanded: boolean) => React.ReactNode); + defaultExpanded?: boolean; + expanded?: boolean; + onExpand?: (e: React.MouseEvent, info: { expanded: boolean }) => void; onEllipsis?: (ellipsis: boolean) => void; tooltip?: React.ReactNode | TooltipProps; } @@ -215,15 +217,19 @@ const Base = React.forwardRef((props, ref) => { const [isLineClampSupport, setIsLineClampSupport] = React.useState(false); const [isTextOverflowSupport, setIsTextOverflowSupport] = React.useState(false); - const [expanded, setExpanded] = React.useState(false); const [isJsEllipsis, setIsJsEllipsis] = React.useState(false); const [isNativeEllipsis, setIsNativeEllipsis] = React.useState(false); const [isNativeVisible, setIsNativeVisible] = React.useState(true); const [enableEllipsis, ellipsisConfig] = useMergedConfig(ellipsis, { expandable: false, + symbol: (isExpanded) => (isExpanded ? textLocale?.collapse : textLocale?.expand), + }); + const [expanded, setExpanded] = useMergedState(ellipsisConfig.defaultExpanded || false, { + value: ellipsisConfig.expanded, }); - const mergedEnableEllipsis = enableEllipsis && !expanded; + const mergedEnableEllipsis = + enableEllipsis && (!expanded || ellipsisConfig.expandable === 'collapsible'); // Shared prop to reduce bundle size const { rows = 1 } = ellipsisConfig; @@ -267,9 +273,9 @@ const Base = React.forwardRef((props, ref) => { const cssLineClamp = mergedEnableEllipsis && rows > 1 && cssEllipsis; // >>>>> Expand - const onExpandClick: React.MouseEventHandler = (e) => { - setExpanded(true); - ellipsisConfig.onExpand?.(e); + const onExpandClick: EllipsisConfig['onExpand'] = (e, info) => { + setExpanded(info.expanded); + ellipsisConfig.onExpand?.(e, info); }; const [ellipsisWidth, setEllipsisWidth] = React.useState(0); @@ -389,22 +395,16 @@ const Base = React.forwardRef((props, ref) => { const { expandable, symbol } = ellipsisConfig; if (!expandable) return null; - - let expandContent: React.ReactNode; - if (symbol) { - expandContent = symbol; - } else { - expandContent = textLocale?.expand; - } + if (expanded && expandable !== 'collapsible') return null; return ( onExpandClick(e, { expanded: !expanded })} + aria-label={expanded ? textLocale.collapse : textLocale?.expand} > - {expandContent} + {typeof symbol === 'function' ? symbol(expanded) : symbol} ); }; @@ -451,20 +451,21 @@ const Base = React.forwardRef((props, ref) => { ); }; - const renderOperations = (renderExpanded: boolean) => [ - renderExpanded && renderExpand(), + const renderOperations = (canEllipsis: boolean) => [ + // (renderExpanded || ellipsisConfig.collapsible) && renderExpand(), + canEllipsis && renderExpand(), renderEdit(), renderCopy(), ]; - const renderEllipsis = (needEllipsis: boolean) => [ - needEllipsis && ( + const renderEllipsis = (canEllipsis: boolean) => [ + canEllipsis && !expanded && ( {ELLIPSIS_STR} ), ellipsisConfig.suffix, - renderOperations(needEllipsis), + renderOperations(canEllipsis), ]; return ( @@ -506,11 +507,12 @@ const Base = React.forwardRef((props, ref) => { rows={rows} width={ellipsisWidth} onEllipsis={onJsEllipsis} + expanded={expanded} miscDeps={[copied, expanded]} > - {(node, needEllipsis) => { + {(node, canEllipsis) => { let renderNode: React.ReactNode = node; - if (node.length && needEllipsis && topAriaLabel) { + if (node.length && canEllipsis && !expanded && topAriaLabel) { renderNode = ( {renderNode} @@ -522,7 +524,7 @@ const Base = React.forwardRef((props, ref) => { props, <> {renderNode} - {renderEllipsis(needEllipsis)} + {renderEllipsis(canEllipsis)} , ); diff --git a/components/typography/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/typography/__tests__/__snapshots__/demo-extend.test.ts.snap index 238b76fec14c..e806d4fc7313 100644 --- a/components/typography/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/typography/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -1520,6 +1520,139 @@ Array [ exports[`renders components/typography/demo/ellipsis.tsx extend context correctly 2`] = `[]`; +exports[`renders components/typography/demo/ellipsis-controlled.tsx extend context correctly 1`] = ` +
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team. +
+ + + +
+
+
+
+ +
+
+
+
+`; + +exports[`renders components/typography/demo/ellipsis-controlled.tsx extend context correctly 2`] = `[]`; + exports[`renders components/typography/demo/ellipsis-debug.tsx extend context correctly 1`] = ` Array [ +
+
+
+
+
+
+
+
+ Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team.Ant Design, a design language for background applications, is refined by Ant UED Team. +
+ + + +
+
+
+`; + exports[`renders components/typography/demo/ellipsis-debug.tsx correctly 1`] = ` Array [