Repository: ant-design/ant-design-mobile
Pull request #6134 (2023-04-28 09:23:13)

feat(Ellipsis): add icon
#6107

github-actions[bot]: PR preview has been successfully built and deployed to https://antd-mobile-preview-pr-6134.surge.sh
codecov[bot]: ## [Codecov](https://codecov.io/gh/ant-design/ant-design-mobile/pull/6134?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design) Report
Patch and project coverage have no change.
> Comparison is base [(`6803b34`)](https://codecov.io/gh/ant-design/ant-design-mobile/commit/6803b344e0a8ef51ee1d4143b4b243eb657bd98e?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design) 90.59% compared to head [(`274cb4f`)](https://codecov.io/gh/ant-design/ant-design-mobile/pull/6134?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design) 90.59%.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff            @@
##           feature    #6134   +/-   ##
========================================
  Coverage    90.59%   90.59%           
========================================
  Files          304      304           
  Lines         6589     6589           
  Branches      1639     1639           
========================================
  Hits          5969     5969           
  Misses         568      568           
  Partials        52       52           
```


| [Impacted Files](https://codecov.io/gh/ant-design/ant-design-mobile/pull/6134?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design) | Coverage Δ | |
|---|---|---|
| [src/components/ellipsis/ellipsis.tsx](https://codecov.io/gh/ant-design/ant-design-mobile/pull/6134?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design#diff-c3JjL2NvbXBvbmVudHMvZWxsaXBzaXMvZWxsaXBzaXMudHN4) | `99.13% <ø> (ø)` | |


</details>

[:umbrella: View full report in Codecov by Sentry](https://codecov.io/gh/ant-design/ant-design-mobile/pull/6134?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design).

miracles1919: <img width="352" alt="image" src="https://user-images.githubusercontent.com/22469543/236611689-ac0a19fc-ab6f-4001-a217-78bf39a4c56e.png">

用 runes 处理下，参考 content
miracles1919: 调整了下 `check` 的逻辑，收起/展开传入非 string 类型时，通过 ref 去获取相应 dom，再用 `innerHTML` 拼到 container 上
zombieJ: 感觉 OK 的。但是 CI 挂了~
1587315093: > 感觉 OK 的。但是 CI 挂了~

哈哈哈应该是我的问题 ，不熟练，1919大佬提的 CI跑通了，我删了点旧代码提交上去又失败了

Patch:
diff --git a/src/components/ellipsis/demos/demo1.tsx b/src/components/ellipsis/demos/demo1.tsx
index d170bd2d6..3a2889e1b 100644
--- a/src/components/ellipsis/demos/demo1.tsx
+++ b/src/components/ellipsis/demos/demo1.tsx
@@ -1,6 +1,7 @@
 import React from 'react'
 import { Ellipsis, Space } from 'antd-mobile'
 import { DemoBlock } from 'demos'
+import { DownOutline, UpOutline } from 'antd-mobile-icons'
 
 const content =
   '蚂蚁的企业级产品是一个庞大且复杂的体系。这类产品不仅量级巨大且功能复杂，而且变动和并发频繁，常常需要设计与开发能够快速的做出响应。同时这类产品中有存在很多类似的页面以及组件，可以通过抽象得到一些稳定且高复用性的内容。'
@@ -60,6 +61,24 @@ export default () => {
           collapseText='收起'
         />
       </DemoBlock>
+      <DemoBlock title='这是一个使用icon的例子'>
+        <Ellipsis
+          direction='end'
+          content={content}
+          expandText={
+            <>
+              展开
+              <DownOutline />
+            </>
+          }
+          collapseText={
+            <>
+              收起
+              <UpOutline />
+            </>
+          }
+        />
+      </DemoBlock>
     </>
   )
 }
diff --git a/src/components/ellipsis/ellipsis.tsx b/src/components/ellipsis/ellipsis.tsx
index 33bcdae4e..8a0650bd7 100644
--- a/src/components/ellipsis/ellipsis.tsx
+++ b/src/components/ellipsis/ellipsis.tsx
@@ -15,8 +15,8 @@ export type EllipsisProps = {
   content: string
   direction?: 'start' | 'end' | 'middle'
   rows?: number
-  expandText?: string
-  collapseText?: string
+  expandText?: React.ReactNode
+  collapseText?: React.ReactNode
   stopPropagationForActionButtons?: PropagationEvent[]
   onContentClick?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void
   defaultExpanded?: boolean
@@ -41,6 +41,8 @@ type EllipsisedValue = {
 export const Ellipsis: FC<EllipsisProps> = p => {
   const props = mergeProps(defaultProps, p)
   const rootRef = useRef<HTMLDivElement>(null)
+  const expandElRef = useRef<HTMLAnchorElement>(null)
+  const collapseElRef = useRef<HTMLAnchorElement>(null)
 
   const [ellipsised, setEllipsised] = useState<EllipsisedValue>({})
   const [expanded, setExpanded] = useState(props.defaultExpanded)
@@ -89,7 +91,16 @@ export const Ellipsis: FC<EllipsisProps> = p => {
     } else {
       setExceeded(true)
       const end = props.content.length
-      const actionText = expanded ? props.collapseText : props.expandText
+
+      const collapseEl =
+        typeof props.collapseText === 'string'
+          ? props.collapseText
+          : collapseElRef.current?.innerHTML
+      const expandEl =
+        typeof props.expandText === 'string'
+          ? props.expandText
+          : expandElRef.current?.innerHTML
+      const actionText = expanded ? collapseEl : expandEl
 
       function check(left: number, right: number): EllipsisedValue {
         if (right - left <= 1) {
@@ -105,10 +116,11 @@ export const Ellipsis: FC<EllipsisProps> = p => {
         }
         const middle = Math.round((left + right) / 2)
         if (props.direction === 'end') {
-          container.innerText = getSubString(0, middle) + '...' + actionText
+          container.innerHTML = getSubString(0, middle) + '...' + actionText
         } else {
-          container.innerText = actionText + '...' + getSubString(middle, end)
+          container.innerHTML = actionText + '...' + getSubString(middle, end)
         }
+
         if (container.offsetHeight <= maxHeight) {
           if (props.direction === 'end') {
             return check(middle, right)
@@ -139,7 +151,7 @@ export const Ellipsis: FC<EllipsisProps> = p => {
         }
         const leftPartMiddle = Math.floor((leftPart[0] + leftPart[1]) / 2)
         const rightPartMiddle = Math.ceil((rightPart[0] + rightPart[1]) / 2)
-        container.innerText =
+        container.innerHTML =
           getSubString(0, leftPartMiddle) +
           '...' +
           actionText +
@@ -184,6 +196,7 @@ export const Ellipsis: FC<EllipsisProps> = p => {
       ? withStopPropagation(
           props.stopPropagationForActionButtons,
           <a
+            ref={expandElRef}
             onClick={() => {
               setExpanded(true)
             }}
@@ -198,6 +211,7 @@ export const Ellipsis: FC<EllipsisProps> = p => {
       ? withStopPropagation(
           props.stopPropagationForActionButtons,
           <a
+            ref={collapseElRef}
             onClick={() => {
               setExpanded(false)
             }}
diff --git a/src/components/ellipsis/index.en.md b/src/components/ellipsis/index.en.md
index f06c6aea8..e954e63b9 100644
--- a/src/components/ellipsis/index.en.md
+++ b/src/components/ellipsis/index.en.md
@@ -18,10 +18,10 @@ When the display space is not enough, hide some content and replace it with "...
 
 | Name | Description | Type | Default |
 | --- | --- | --- | --- |
-| collapseText | Collapse operation text | `string` | `''` |
+| collapseText | Collapse operation text | `React.ReactNode` | `''` |
 | content | The text content | `string` | - |
 | direction | Position omitted | `'start' \| 'end' \| 'middle'` | `'end'` |
-| expandText | Expand operation text | `string` | `''` |
+| expandText | Expand operation text | `React.ReactNode` | `''` |
 | onContentClick | Trigger when clicked text content | `(e: React.MouseEvent) => void` | - |
 | rows | The number to display lines | `number` | `1` |
 | stopPropagationForActionButtons | Prevent the event bubbling caused by the expand operation and the collapse operation | `PropagationEvent[]` | `[]` |
diff --git a/src/components/ellipsis/index.zh.md b/src/components/ellipsis/index.zh.md
index 6366821c3..301d71f07 100644
--- a/src/components/ellipsis/index.zh.md
+++ b/src/components/ellipsis/index.zh.md
@@ -18,10 +18,10 @@
 
 | 属性 | 说明 | 类型 | 默认值 |
 | --- | --- | --- | --- |
-| collapseText | 收起操作的文案 | `string` | `''` |
+| collapseText | 收起操作的文案 | `React.ReactNode` | `''` |
 | content | 文本内容 | `string` | - |
 | direction | 省略位置 | `'start' \| 'end' \| 'middle'` | `'end'` |
-| expandText | 展开操作的文案 | `string` | `''` |
+| expandText | 展开操作的文案 | `React.ReactNode` | `''` |
 | onContentClick | 点击文本内容时触发 | `(e: React.MouseEvent) => void` | - |
 | rows | 展示几行 | `number` | `1` |
 | stopPropagationForActionButtons | 阻止展开操作，收起操作引发的事件冒泡 | `PropagationEvent[]` | `[]` |


Test patch:
diff --git a/src/components/ellipsis/tests/ellipsis.test.tsx b/src/components/ellipsis/tests/ellipsis.test.tsx
index 3d683e849..52ee5be57 100644
--- a/src/components/ellipsis/tests/ellipsis.test.tsx
+++ b/src/components/ellipsis/tests/ellipsis.test.tsx
@@ -18,12 +18,18 @@ describe('Ellipsis', () => {
       return style
     }
 
+    Object.defineProperty(HTMLElement.prototype, 'offsetParent', {
+      value: {},
+    })
+  })
+
+  beforeEach(() => {
     Object.defineProperty(HTMLElement.prototype, 'offsetHeight', {
       get() {
-        if (this.innerText.includes('...')) {
+        if (this.innerHTML.includes('...')) {
           const row = Math.ceil(
             // the width of '...' is equal to a Chinese char
-            (this.innerText.replace(/\.\.\./g, '中').length / content.length) *
+            (this.innerHTML.replace(/\.\.\./g, '中').length / content.length) *
               4
           )
           return lineHeight * row
@@ -31,10 +37,6 @@ describe('Ellipsis', () => {
         return lineHeight * 4
       },
     })
-
-    Object.defineProperty(HTMLElement.prototype, 'offsetParent', {
-      value: {},
-    })
   })
 
   afterAll(() => {
@@ -45,35 +47,35 @@ describe('Ellipsis', () => {
     await testA11y(<Ellipsis content={content} />)
   })
 
-  test('direction start', async () => {
+  test('direction start', () => {
     const { getByTestId } = render(
       <Ellipsis content={content} direction='start' data-testid='ellipsis' />
     )
     expect(getByTestId('ellipsis')).toMatchSnapshot()
   })
 
-  test('direction middle', async () => {
+  test('direction middle', () => {
     const { getByTestId } = render(
       <Ellipsis content={content} direction='middle' data-testid='ellipsis' />
     )
     expect(getByTestId('ellipsis')).toMatchSnapshot()
   })
 
-  test('direction end', async () => {
+  test('direction end', () => {
     const { getByTestId } = render(
       <Ellipsis content={content} direction='end' data-testid='ellipsis' />
     )
     expect(getByTestId('ellipsis')).toMatchSnapshot()
   })
 
-  test('multi line', async () => {
+  test('multi line', () => {
     const { getByTestId } = render(
       <Ellipsis content={content} rows={3} data-testid='ellipsis' />
     )
     expect(getByTestId('ellipsis')).toMatchSnapshot()
   })
 
-  test('expand and collapse', async () => {
+  test('expand and collapse', () => {
     const { getByTestId, getByText } = render(
       <Ellipsis
         content={content}
@@ -89,7 +91,7 @@ describe('Ellipsis', () => {
     expect(getByTestId('ellipsis')).toMatchSnapshot()
   })
 
-  test('content click', async () => {
+  test('content click', () => {
     const onClick = jest.fn()
     const { getByTestId } = render(
       <Ellipsis
@@ -103,7 +105,7 @@ describe('Ellipsis', () => {
     expect(onClick).toBeCalled()
   })
 
-  test('default expand should be work', async () => {
+  test('default expand should be work', () => {
     const { getByText } = render(
       <Ellipsis
         content={content}
@@ -120,7 +122,7 @@ describe('Ellipsis', () => {
     expect(ellipsis).toHaveTextContent('...')
   })
 
-  test('content not exceeded', async () => {
+  test('content not exceeded', () => {
     Object.defineProperty(HTMLElement.prototype, 'offsetHeight', {
       value: lineHeight,
     })
@@ -139,4 +141,18 @@ describe('Ellipsis', () => {
       render(<Ellipsis content={undefined} data-testid='ellipsis' />)
     }).not.toThrowError()
   })
+
+  test('expand and collapse support ReactNode', async () => {
+    const { getByText, findByText } = render(
+      <Ellipsis
+        content={content}
+        expandText={<span>expand</span>}
+        collapseText={<span>collapse</span>}
+      />
+    )
+
+    expect(await findByText('expand')).toBeVisible()
+    fireEvent.click(getByText('expand'))
+    expect(getByText('collapse')).toBeInTheDocument()
+  })
 })