diff --git a/.github/workflows/issue-assignees.temp.yml b/.github/workflows/issue-assignees.temp.yml index 83d5d6b87e..29b803a06a 100644 --- a/.github/workflows/issue-assignees.temp.yml +++ b/.github/workflows/issue-assignees.temp.yml @@ -15,38 +15,3 @@ jobs: issuesOpened: | 👋 @{{ author }},感谢给 TDesign 提出了 issue。 请根据 issue 模版确保背景信息的完善,我们将调查并尽快回复你。 - - # https://docs.github.com/cn/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#issues - - uses: 94dreamer/issue-assignees@main - id: assignees - with: - project_name: ${{github.event.repository.name}} - issue_title: ${{github.event.issue.title}} - - - run: echo ${{ steps.assignees.outputs.contributors }} - - name: Add assigness - if: steps.assignees.outputs.contributors != '' - uses: actions-cool/issues-helper@v3 - with: - actions: 'add-assignees' - token: ${{ secrets.GITHUB_TOKEN }} - issue-number: ${{ github.event.issue.number }} - assignees: ${{ steps.assignees.outputs.contributors }} - - - run: | - contributors=${{ steps.assignees.outputs.contributors }} - contributorstring=${contributors//,/ @} - echo "::set-output name=string::@$contributorstring" - id: contributors - - - name: 通知贡献者 - if: steps.assignees.outputs.contributors != '' - uses: actions-cool/maintain-one-comment@v2.0.0 - with: - token: ${{ secrets.GITHUB_TOKEN }} - body: | - ♥️ 有劳 ${{ steps.contributors.outputs.string }} 尽快确认问题。 - 确认有效后将下一步计划和可能需要的时间回复给 @${{ github.event.issue.user.login }} 。 - - number: ${{ github.event.issue.number }} - body-include: "" diff --git a/.vscode/settings.json b/.vscode/settings.json index 4ca63e8b91..beeb08a71e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,4 +25,4 @@ "source.fixAll.eslint": "explicit" }, "cSpell.words": ["activable", "actived", "borderless", "Cascader", "Popconfirm", "Swiper", "tdesign"] -} \ No newline at end of file +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 49703778a0..e44bdf7aec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,49 @@ toc: false spline: explain --- +## 🌈 1.5.5 `2024-03-28` +### 🐞 Bug Fixes +- `ImageViewer`: 修复 `imageReferrerpolicy` 没有对顶部缩略图生效的问题 @uyarn ([#2815](https://github.com/Tencent/tdesign-react/pull/2815)) + +## 🌈 1.5.4 `2024-03-28` +### 🚀 Features +- `ImageViewer`: 新增`imageReferrerpolicy` API,支持配合 Image 组件的需要配置 Referrerpolicy 的场景 @uyarn ([#2813](https://github.com/Tencent/tdesign-react/pull/2813)) +### 🐞 Bug Fixes +- `Select`: 修复 `onRemove` 事件没有正常触发的问题 @Ali-ovo ([#2802](https://github.com/Tencent/tdesign-react/pull/2802)) +- `Skeleton`: 修复`children`为必须的类型问题 @uyarn ([#2805](https://github.com/Tencent/tdesign-react/pull/2805)) +- `Tabs`: 提供 `action` 区域默认样式 @HaixingOoO ([#2808](https://github.com/Tencent/tdesign-react/pull/2808)) +- `Locale`: 修复`image`和`imageViewer` 英语语言包异常的问题 @uyarn @HaixingOoO ([#2808](https://github.com/Tencent/tdesign-react/pull/2808)) +- `Image`: `referrerpolicy` 参数被错误传递到外层 `div` 上,实际传递目标为原生 `image` 标签 @NWYLZW ([#2811](https://github.com/Tencent/tdesign-react/pull/2811)) + +## 🌈 1.5.3 `2024-03-14` +### 🚀 Features +- `Breadcrumb`: `BreadcrumbItem` 支持 `onClick` 事件 @HaixingOoO ([#2795](https://github.com/Tencent/tdesign-react/pull/2795)) +- `tag`: `Tag`组件新增`color`API,支持自定义颜色 @maoyiluo @uyarn ([#2799](https://github.com/Tencent/tdesign-react/pull/2799)) +### 🐞 Bug Fixes +- `FormList`: 修复多个`FormList` 卡死的问题 @HaixingOoO ([#2788](https://github.com/Tencent/tdesign-react/pull/2788)) +- `DatePicker`: 修复 `format` 与 `valueType` 不一致的场景下计算错误的问题 @uyarn ([#2798](https://github.com/Tencent/tdesign-react/pull/2798)) +### 🚧 Others +- `Portal`: 添加Portal测试用例 @HaixingOoO ([#2791](https://github.com/Tencent/tdesign-react/pull/2791)) +- `List`: 完善 List 测试用例 @HaixingOoO ([#2792](https://github.com/Tencent/tdesign-react/pull/2792)) +- `Alert`: 完善 Alert 测试,优化代码 @HaixingOoO ([#2793](https://github.com/Tencent/tdesign-react/pull/2793)) + +## 🌈 1.5.2 `2024-02-29` +### 🚀 Features +- `Cascader`: 新增`valueDisplay`和`label` API的支持 @HaixingOoO ([#2736](https://github.com/Tencent/tdesign-react/pull/2736)) +- `Descriptions`: `Descriptions` 组件支持嵌套 @HaixingOoO ([#2777](https://github.com/Tencent/tdesign-react/pull/2777)) +- `Tabs`: 调整激活 `Tab`下划线与 `TabHeader`边框的层级关系 @uyarn ([#2780](https://github.com/Tencent/tdesign-react/pull/2780)) +### 🐞 Bug Fixes +- `Grid`: 尺寸计算错误,宽度兼容异常 @NWYLZW ([#2738](https://github.com/Tencent/tdesign-react/pull/2738)) +- `Cascader`: 修复`clearable`点击清除按钮触发三次`onChange`的问题 @HaixingOoO ([#2736](https://github.com/Tencent/tdesign-react/pull/2736)) +- `Dialog`: 修复`useDialogPosition`渲染多次绑定事件 @HaixingOoO ([#2749](https://github.com/Tencent/tdesign-react/pull/2749)) +- `Guide`: 修复`Guide`自定义内容功能失效 @zhangpaopao0609 ([#2752](https://github.com/Tencent/tdesign-react/pull/2752)) +- `Tree`: 修复设置 `keys.children` 后展开图标没有正常变化的问题 @uyarn ([#2746](https://github.com/Tencent/tdesign-react/pull/2746)) +- `Tree`: 修复`Tree` 自定义label `setData` 没有渲染的问题 @HaixingOoO ([#2776](https://github.com/Tencent/tdesign-react/pull/2776)) +- `Tree`: 修复设置 `Tree` 宽度,`TreeItem` 的 `checkbox` 会被压缩,`label` 省略号失效的问题 @HaixingOoO @uyarn ([#2780](https://github.com/Tencent/tdesign-react/pull/2780)) +- `Select`: @uyarn + - 修复通过滚动加载选项选中后滚动行为异常的问题 ([#2779](https://github.com/Tencent/tdesign-react/pull/2779)) + - 修复使用`size` API时,虚拟滚动的功能异常问题 ([#2756](https://github.com/Tencent/tdesign-react/pull/2756)) + ## 🌈 1.5.1 `2024-01-25` ### 🚀 Features - `Popup`: 支持`Plugin`方式使用。 @HaixingOoO ([#2717](https://github.com/Tencent/tdesign-react/pull/2717)) diff --git a/README-zh_CN.md b/README-zh_CN.md index 0b58259129..0f6038a3dd 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -95,7 +95,7 @@ TDesign 欢迎任何愿意参与贡献的参与者。如果需要本地运行代 有任何问题,建议通过 [Github issues](https://github.com/Tencent/tdesign-react/issues) 反馈或扫码加入用户微信群。 - + # 开源协议 diff --git a/README.md b/README.md index 925480a03a..1329570293 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ Contributing is welcome. Read [guidelines for contributing](https://github.com/T Create your [Github issues](https://github.com/Tencent/tdesign-react/issues) or scan the QR code below to join our user groups - + # License diff --git a/package.json b/package.json index 3b38305d4d..545420680c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "tdesign-react", "purename": "tdesign", - "version": "1.5.1", + "version": "1.5.5", "description": "TDesign Component for React", "title": "tdesign-react", "main": "lib/index.js", @@ -184,7 +184,7 @@ "start-server-and-test": "^1.14.0", "tdesign-icons-view": "^0.2.0", "tdesign-publish-cli": "^0.0.10", - "tdesign-site-components": "^0.13.1", + "tdesign-site-components": "^0.14.6", "tdesign-theme-generator": "^1.0.0", "ts-morph": "^13.0.02", "ts-node": "^10.4.0", @@ -194,7 +194,7 @@ "vite-plugin-pwa": "^0.12.8", "vite-plugin-tdoc": "^2.0.1", "vitest": "^0.24.1", - "workbox-precaching": "^6.3.0" + "workbox-precaching": "^7.0.0" }, "dependencies": { "@babel/runtime": "~7.17.2", diff --git a/script/init-component/config.js b/script/init-component/config.js index 5df9ccf478..963d927fb4 100644 --- a/script/init-component/config.js +++ b/script/init-component/config.js @@ -12,14 +12,6 @@ function getToBeCreatedFiles(component, pascalCaseName) { file: `${pascalCaseName}.tsx`, template: 'component.tsx.tpl', }, - { - file: `api.md`, - template: 'api.md.tpl', - }, - { - file: `README.md`, - template: 'readme.md.tpl', - }, ], }, [`src/${component}/_example`]: { diff --git a/script/init-component/tpl/api.md.tpl b/script/init-component/tpl/api.md.tpl deleted file mode 100644 index 78f6e3bd6c..0000000000 --- a/script/init-component/tpl/api.md.tpl +++ /dev/null @@ -1,3 +0,0 @@ -### <%= PascalCaseComponent %> Props -名称 | 类型 | 默认值 | 说明 | 必传 --- | -- | -- | -- | -- diff --git a/script/init-component/tpl/component.tsx.tpl b/script/init-component/tpl/component.tsx.tpl index 14854d7413..855244893e 100644 --- a/script/init-component/tpl/component.tsx.tpl +++ b/script/init-component/tpl/component.tsx.tpl @@ -1,5 +1,5 @@ import React from 'react'; -import { Td<%= PascalCaseComponent %>Props } from '../_type/components/<%= component %>'; +import type { Td<%= PascalCaseComponent %>Props } from './type'; export type <%= PascalCaseComponent %>Props = Td<%= PascalCaseComponent %>Props; diff --git a/script/init-component/tpl/readme.md.tpl b/script/init-component/tpl/readme.md.tpl deleted file mode 100644 index e5ac36e603..0000000000 --- a/script/init-component/tpl/readme.md.tpl +++ /dev/null @@ -1,3 +0,0 @@ -:: BASE_DOC :: - -:: BASE_PROPS :: diff --git a/site/test-coverage.js b/site/test-coverage.js index 1814fedc95..5a8d2a6482 100644 --- a/site/test-coverage.js +++ b/site/test-coverage.js @@ -12,8 +12,8 @@ module.exports = { "lines": "85.93%" }, "alert": { - "statements": "96.96%", - "branches": "72.72%", + "statements": "100%", + "branches": "100%", "functions": "100%", "lines": "100%" }, @@ -48,10 +48,10 @@ module.exports = { "lines": "100%" }, "breadcrumb": { - "statements": "85.1%", - "branches": "54.83%", - "functions": "83.33%", - "lines": "88.88%" + "statements": "84.31%", + "branches": "53.12%", + "functions": "85.71%", + "lines": "89.58%" }, "button": { "statements": "100%", @@ -72,10 +72,10 @@ module.exports = { "lines": "100%" }, "cascader": { - "statements": "92.66%", - "branches": "71.42%", - "functions": "92.3%", - "lines": "94.11%" + "statements": "93.12%", + "branches": "75.8%", + "functions": "90.62%", + "lines": "94.21%" }, "checkbox": { "statements": "90.27%", @@ -114,10 +114,10 @@ module.exports = { "lines": "68.75%" }, "datePicker": { - "statements": "62.38%", - "branches": "42.79%", + "statements": "62.47%", + "branches": "43.3%", "functions": "60%", - "lines": "66.07%" + "lines": "66.16%" }, "descriptions": { "statements": "98.82%", @@ -150,10 +150,10 @@ module.exports = { "lines": "92.4%" }, "form": { - "statements": "83.54%", + "statements": "83.5%", "branches": "70.73%", "functions": "81.51%", - "lines": "87.2%" + "lines": "87.17%" }, "grid": { "statements": "84.21%", @@ -216,10 +216,10 @@ module.exports = { "lines": "100%" }, "list": { - "statements": "78.78%", - "branches": "53.84%", - "functions": "66.66%", - "lines": "78.78%" + "statements": "100%", + "branches": "100%", + "functions": "100%", + "lines": "100%" }, "loading": { "statements": "86.07%", @@ -264,10 +264,10 @@ module.exports = { "lines": "76.92%" }, "popup": { - "statements": "95.45%", - "branches": "92.85%", - "functions": "86.36%", - "lines": "94.82%" + "statements": "47.01%", + "branches": "41.26%", + "functions": "45.23%", + "lines": "45%" }, "progress": { "statements": "89.23%", @@ -318,10 +318,10 @@ module.exports = { "lines": "90.47%" }, "space": { - "statements": "87.5%", + "statements": "87.75%", "branches": "84.37%", "functions": "100%", - "lines": "86.95%" + "lines": "87.75%" }, "statistic": { "statements": "84.44%", @@ -330,40 +330,40 @@ module.exports = { "lines": "85.71%" }, "steps": { - "statements": "87.65%", - "branches": "66.66%", + "statements": "87.8%", + "branches": "66.07%", "functions": "100%", - "lines": "100%" + "lines": "87.8%" }, "swiper": { - "statements": "72.13%", - "branches": "42.6%", + "statements": "71.93%", + "branches": "43.28%", "functions": "85.71%", - "lines": "71.5%" + "lines": "71.35%" }, "switch": { - "statements": "96.29%", + "statements": "96.55%", "branches": "92%", "functions": "100%", - "lines": "100%" + "lines": "96.55%" }, "table": { - "statements": "48.13%", + "statements": "48.36%", "branches": "33.74%", "functions": "45.91%", - "lines": "49.31%" + "lines": "49.56%" }, "tabs": { - "statements": "91.11%", + "statements": "90.96%", "branches": "78.64%", "functions": "86.36%", - "lines": "91.32%" + "lines": "91.17%" }, "tag": { - "statements": "65%", - "branches": "59.09%", - "functions": "46.66%", - "lines": "64.86%" + "statements": "56.25%", + "branches": "48.21%", + "functions": "47.05%", + "lines": "55.55%" }, "tagInput": { "statements": "85.11%", @@ -396,22 +396,22 @@ module.exports = { "lines": "90.56%" }, "transfer": { - "statements": "86.06%", - "branches": "66.66%", + "statements": "86.27%", + "branches": "67.61%", "functions": "84.28%", - "lines": "87.77%" + "lines": "87.97%" }, "tree": { - "statements": "85.94%", - "branches": "70.09%", - "functions": "84.61%", - "lines": "88.08%" + "statements": "86.22%", + "branches": "70.64%", + "functions": "84.9%", + "lines": "88.33%" }, "treeSelect": { - "statements": "95.45%", + "statements": "95.48%", "branches": "86.17%", "functions": "97.61%", - "lines": "97.2%" + "lines": "97.22%" }, "upload": { "statements": "96.66%", @@ -420,10 +420,10 @@ module.exports = { "lines": "100%" }, "watermark": { - "statements": "95.65%", - "branches": "82.92%", + "statements": "95.77%", + "branches": "79.41%", "functions": "100%", - "lines": "98.46%" + "lines": "98.5%" }, "utils": { "statements": "75.43%", diff --git a/src/alert/Alert.tsx b/src/alert/Alert.tsx index 0d5a45de90..df6e317211 100644 --- a/src/alert/Alert.tsx +++ b/src/alert/Alert.tsx @@ -67,7 +67,7 @@ const Alert = forwardRef((props, ref) => { const renderIconNode = () => { if (React.isValidElement(icon)) return icon; - return React.createElement(iconMap[theme] || iconMap.info); + return React.createElement(iconMap[theme]); }; const renderMessage = () => { @@ -84,11 +84,9 @@ const Alert = forwardRef((props, ref) => { } return true; })} - {+maxLine > 0 ? ( -
- {!collapsed ? t(local.expandText) : t(local.collapseText)} -
- ) : null} +
+ {!collapsed ? t(local.expandText) : t(local.collapseText)} +
); } diff --git a/src/alert/__tests__/alert.test.tsx b/src/alert/__tests__/alert.test.tsx index b5b3a69d11..baa9a65bf0 100644 --- a/src/alert/__tests__/alert.test.tsx +++ b/src/alert/__tests__/alert.test.tsx @@ -10,26 +10,93 @@ describe('Alert 组件测试', () => { const onClose = vi.fn(); const onClosed = vi.fn(); - const { queryByTestId, container } = render( + const { queryByTestId, container, queryByText } = render( {text}} onClose={onClose} onClosed={onClosed} + operation={test content} />, ); + expect(container.querySelector('.t-alert--closing')).not.toBeInTheDocument(); + expect(container.querySelector('#operation-test')).not.toBeNull(); + expect(container.querySelector('#operation-test')).toBeInTheDocument(); + expect(queryByText('title content')).not.toBeNull(); + expect(queryByText('title content')).toBeInTheDocument(); + expect(container.querySelector('.t-alert--error')).not.toBeNull(); + expect(container.querySelector('.t-alert--error')).toBeInTheDocument(); act(() => { fireEvent.click(queryByTestId(testId)); }); - expect(onClose).toHaveBeenCalledTimes(1); expect(container.querySelector('.t-alert--closing')).toBeInTheDocument(); + expect(onClose).toHaveBeenCalledTimes(1); await mockTimeout(() => expect(onClosed).toHaveBeenCalledTimes(1), 300); }); + test('custom close icon render', () => { + const { queryByTestId } = render( + {text}} />, + ); + + expect(queryByTestId(testId)).not.toBeNull(); + expect(queryByTestId(testId)).toBeInTheDocument(); + }); + + test('default close icon render', () => { + const { container } = render(); + + expect(container.querySelector('.t-icon-close')).not.toBeNull(); + expect(container.querySelector('.t-icon-close')).toBeInTheDocument(); + }); + + test('custom icon render', () => { + const { queryByText } = render(custom icon} title="title content" />); + + expect(queryByText('custom icon')).not.toBeNull(); + expect(queryByText('custom icon')).toBeInTheDocument(); + }); + + test('theme icon render', () => { + const { container } = render(); + + expect(container.querySelector('.t-icon-check-circle-filled')).not.toBeNull(); + expect(container.querySelector('.t-icon-check-circle-filled')).toBeInTheDocument(); + }); + + test('default theme icon render', () => { + const { container } = render(); + + expect(container.querySelector('.t-icon-info-circle-filled')).not.toBeNull(); + expect(container.querySelector('.t-icon-info-circle-filled')).toBeInTheDocument(); + }); + + test('maxLine', () => { + const { container } = render(); + + expect(container.querySelector('.t-alert__collapse')).toBeNull(); + expect(container.querySelector('.t-alert__collapse')).not.toBeInTheDocument(); + }); + + test('message not collapsed', () => { + const massage = [ +
{text}
, +
{text}
, +
+ {text} +
, + ]; + const { container } = render(); + + expect(container.querySelector('.t-alert__collapse')).toBeNull(); + expect(container.querySelector('.t-alert__collapse')).not.toBeInTheDocument(); + }); + test('Alert 展开收起操作', async () => { const massage = [
{text}
, @@ -39,6 +106,9 @@ describe('Alert 组件测试', () => { , ]; const { queryByText, queryByTestId } = render(); + + expect(queryByText('展开更多')).not.toBeNull(); + expect(queryByText('展开更多')).toBeInTheDocument(); const element = await waitFor(() => queryByTestId(testId)); expect(element).toBeNull(); @@ -47,6 +117,8 @@ describe('Alert 组件测试', () => { fireEvent.click(btn); }); + expect(queryByText('收起')).not.toBeNull(); + expect(queryByText('收起')).toBeInTheDocument(); const element1 = await waitFor(() => queryByTestId(testId)); expect(element1).not.toBeNull(); diff --git a/src/breadcrumb/BreadcrumbItem.tsx b/src/breadcrumb/BreadcrumbItem.tsx index 4eef279d4b..a9b7c9fa88 100644 --- a/src/breadcrumb/BreadcrumbItem.tsx +++ b/src/breadcrumb/BreadcrumbItem.tsx @@ -32,6 +32,7 @@ const BreadcrumbItem = forwardRef((props, r replace, className, content, + onClick, ...restProps } = useDefaultProps(props, breadcrumbItemDefaultProps); @@ -89,8 +90,13 @@ const BreadcrumbItem = forwardRef((props, r ); + const handleClick = (e: React.MouseEvent) => { + if (disabled) return; + onClick?.(e); + }; + return ( -
+
{isCutOff ? {itemContent} : itemContent} {separatorContent}
diff --git a/src/breadcrumb/breadcrumb.en-US.md b/src/breadcrumb/breadcrumb.en-US.md index a7c881b5d8..364ded77fd 100644 --- a/src/breadcrumb/breadcrumb.en-US.md +++ b/src/breadcrumb/breadcrumb.en-US.md @@ -1,22 +1,24 @@ :: BASE_DOC :: ## API + ### Breadcrumb Props name | type | default | description | required -- | -- | -- | -- | -- -className | String | - | 类名 | N -style | Object | - | 样式,Typescript:`React.CSSProperties` | N +className | String | - | className of component | N +style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N maxItemWidth | String | undefined | \- | N options | Array | - | Typescript:`Array` | N separator | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N + ### BreadcrumbItem Props name | type | default | description | required -- | -- | -- | -- | -- -className | String | - | 类名 | N -style | Object | - | 样式,Typescript:`React.CSSProperties` | N +className | String | - | className of component | N +style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N children | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N content | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N disabled | Boolean | - | \- | N @@ -25,5 +27,6 @@ icon | TElement | - | prefix icon in breadcrumb item。Typescript:`TNode`。[s maxWidth | String | undefined | \- | N replace | Boolean | false | \- | N router | Object | - | Typescript:`any` | N -target | String | _self | options:_blank/_self/_parent/_top | N -to | String / Object | - | Typescript:`Route` `interface Route { path?: string; name?: string; hash?: string; query?: RouteData; params?: RouteData }` `type RouteData = { [key: string]: string \| string[] }`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/breadcrumb/type.ts) | N +target | String | _self | options: _blank/_self/_parent/_top | N +to | String / Object | - | Typescript:`string \| Route` `interface Route { path?: string; name?: string; hash?: string; query?: RouteData; params?: RouteData }` `type RouteData = { [key: string]: string \| string[] }`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/breadcrumb/type.ts) | N +onClick | Function | | Typescript:`(e: MouseEvent) => void`
trigger on click | N diff --git a/src/breadcrumb/breadcrumb.md b/src/breadcrumb/breadcrumb.md index ce63ef9bc2..a87ff10054 100644 --- a/src/breadcrumb/breadcrumb.md +++ b/src/breadcrumb/breadcrumb.md @@ -1,9 +1,10 @@ :: BASE_DOC :: ## API + ### Breadcrumb Props -名称 | 类型 | 默认值 | 说明 | 必传 +名称 | 类型 | 默认值 | 描述 | 必传 -- | -- | -- | -- | -- className | String | - | 类名 | N style | Object | - | 样式,TS 类型:`React.CSSProperties` | N @@ -11,9 +12,10 @@ maxItemWidth | String | undefined | 单项最大宽度,超出后会以省略 options | Array | - | 面包屑项,功能同 BreadcrumbItem。TS 类型:`Array` | N separator | TNode | - | 自定义分隔符。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N + ### BreadcrumbItem Props -名称 | 类型 | 默认值 | 说明 | 必传 +名称 | 类型 | 默认值 | 描述 | 必传 -- | -- | -- | -- | -- className | String | - | 类名 | N style | Object | - | 样式,TS 类型:`React.CSSProperties` | N @@ -26,4 +28,5 @@ maxWidth | String | undefined | 最大宽度,超出后会以省略号形式呈 replace | Boolean | false | 路由跳转是否采用覆盖的方式(覆盖后将没有浏览器历史记录) | N router | Object | - | 路由对象。如果项目存在 Router,则默认使用 Router。TS 类型:`any` | N target | String | _self | 链接或路由跳转方式。可选项:_blank/_self/_parent/_top | N -to | String / Object | - | 路由跳转目标,当且仅当 Router 存在时,该 API 有效。TS 类型:`Route` `interface Route { path?: string; name?: string; hash?: string; query?: RouteData; params?: RouteData }` `type RouteData = { [key: string]: string \| string[] }`。[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/breadcrumb/type.ts) | N +to | String / Object | - | 路由跳转目标,当且仅当 Router 存在时,该 API 有效。TS 类型:`string \| Route` `interface Route { path?: string; name?: string; hash?: string; query?: RouteData; params?: RouteData }` `type RouteData = { [key: string]: string \| string[] }`。[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/breadcrumb/type.ts) | N +onClick | Function | | TS 类型:`(e: MouseEvent) => void`
点击时触发 | N diff --git a/src/breadcrumb/type.ts b/src/breadcrumb/type.ts index 93697472d1..ba4d309592 100644 --- a/src/breadcrumb/type.ts +++ b/src/breadcrumb/type.ts @@ -5,6 +5,7 @@ * */ import { TNode, TElement } from '../common'; +import { MouseEvent } from 'react'; export interface TdBreadcrumbProps { /** @@ -64,7 +65,11 @@ export interface TdBreadcrumbItemProps { /** * 路由跳转目标,当且仅当 Router 存在时,该 API 有效 */ - to?: Route | string; + to?: string | Route; + /** + * 点击时触发 + */ + onClick?: (e: MouseEvent) => void; } export interface Route { diff --git a/src/common/__tests__/portal.test.tsx b/src/common/__tests__/portal.test.tsx new file mode 100644 index 0000000000..8419b5a4b2 --- /dev/null +++ b/src/common/__tests__/portal.test.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { renderToString } from 'react-dom/server'; +import { render } from '@test/utils'; +import Portal from '../Portal'; + +describe('Portal', () => { + test('Portal render', () => { + const { unmount } = render( + +
Hello World
+
, + ); + + expect(document.querySelector('#portal')).not.toBeNull(); + expect(document.querySelector('#portal')).toBeInTheDocument(); + unmount(); + expect(document.querySelector('#portal')).toBeNull(); + }); + + test('Portal ssr render', () => { + const renderOnServer = () => + renderToString( + +
Hello World
+
, + ); + + // 目前test会出错,待后续添加lazy可support ssr + expect(renderOnServer).toThrow(); + }); +}); diff --git a/src/date-picker/DatePicker.tsx b/src/date-picker/DatePicker.tsx index c334f6e823..536f15f659 100644 --- a/src/date-picker/DatePicker.tsx +++ b/src/date-picker/DatePicker.tsx @@ -8,7 +8,7 @@ import SelectInput from '../select-input'; import SinglePanel from './panel/SinglePanel'; import useSingle from './hooks/useSingle'; import { parseToDayjs, getDefaultFormat, formatTime, formatDate } from '../_common/js/date-picker/format'; -import { subtractMonth, addMonth, extractTimeObj } from '../_common/js/date-picker/utils'; +import { subtractMonth, addMonth, extractTimeObj, covertToDate } from '../_common/js/date-picker/utils'; import { datePickerDefaultProps } from './defaultProps'; import useDefaultProps from '../hooks/useDefaultProps'; @@ -64,8 +64,9 @@ const DatePicker = forwardRef((originalProps, r useEffect(() => { // 面板展开重置数据 - setCacheValue(formatDate(value, { format })); - setInputValue(formatDate(value, { format })); + const dateValue = value ? covertToDate(value as string, valueType) : value; + setCacheValue(formatDate(dateValue, { format })); + setInputValue(formatDate(dateValue, { format })); if (popupVisible) { setYear(parseToDayjs(value, format).year()); diff --git a/src/date-picker/panel/RangePanel.tsx b/src/date-picker/panel/RangePanel.tsx index 6d3c0d4951..72b33001cb 100644 --- a/src/date-picker/panel/RangePanel.tsx +++ b/src/date-picker/panel/RangePanel.tsx @@ -9,6 +9,7 @@ import type { TdTimePickerProps } from '../../time-picker'; import { getDefaultFormat, parseToDayjs } from '../../_common/js/date-picker/format'; import useTableData from '../hooks/useTableData'; import useDisableDate from '../hooks/useDisableDate'; +import useDefaultProps from '../../hooks/useDefaultProps'; export interface RangePanelProps extends TdDateRangePickerProps, StyledProps { hoverValue?: string[]; @@ -32,9 +33,15 @@ export interface RangePanelProps extends TdDateRangePickerProps, StyledProps { onTimePickerChange?: TdTimePickerProps['onChange']; } -const RangePanel = forwardRef((props, ref) => { +const RangePanel = forwardRef((originalProps, ref) => { const { classPrefix, datePicker: globalDatePickerConfig } = useConfig(); const panelName = `${classPrefix}-date-range-picker__panel`; + const props = useDefaultProps(originalProps, { + mode: 'date', + panelPreselection: true, + enableTimePicker: false, + presetsPlacement: 'bottom', + }); const { value = [], hoverValue = [], @@ -200,11 +207,5 @@ const RangePanel = forwardRef((props, ref) => { }); RangePanel.displayName = 'RangePanel'; -RangePanel.defaultProps = { - mode: 'date', - panelPreselection: true, - enableTimePicker: false, - presetsPlacement: 'bottom', -}; export default RangePanel; diff --git a/src/date-picker/panel/SinglePanel.tsx b/src/date-picker/panel/SinglePanel.tsx index 14cf5695d1..bee9d57fce 100644 --- a/src/date-picker/panel/SinglePanel.tsx +++ b/src/date-picker/panel/SinglePanel.tsx @@ -9,6 +9,7 @@ import type { TdTimePickerProps } from '../../time-picker'; import { getDefaultFormat, parseToDayjs } from '../../_common/js/date-picker/format'; import useTableData from '../hooks/useTableData'; import useDisableDate from '../hooks/useDisableDate'; +import useDefaultProps from '../../hooks/useDefaultProps'; export interface SinglePanelProps extends TdDatePickerProps, StyledProps { year?: number; @@ -27,9 +28,14 @@ export interface SinglePanelProps extends TdDatePickerProps, StyledProps { onTimePickerChange?: TdTimePickerProps['onChange']; } -const SinglePanel = forwardRef((props, ref) => { +const SinglePanel = forwardRef((originalProps, ref) => { const { classPrefix, datePicker: globalDatePickerConfig } = useConfig(); const panelName = `${classPrefix}-date-picker__panel`; + const props = useDefaultProps(originalProps, { + mode: 'date', + enableTimePicker: false, + presetsPlacement: 'bottom', + }); const { value, mode, @@ -109,10 +115,4 @@ const SinglePanel = forwardRef((props, ref) => SinglePanel.displayName = 'SinglePanel'; -SinglePanel.defaultProps = { - mode: 'date', - enableTimePicker: false, - presetsPlacement: 'bottom', -}; - export default SinglePanel; diff --git a/src/descriptions/Descriptions.tsx b/src/descriptions/Descriptions.tsx index 05b328a6c5..3e94396922 100644 --- a/src/descriptions/Descriptions.tsx +++ b/src/descriptions/Descriptions.tsx @@ -7,7 +7,7 @@ import { descriptionItemDefaultProps, descriptionsDefaultProps } from './default import useDefaultProps from '../hooks/useDefaultProps'; import useConfig from '../hooks/useConfig'; import useCommonClassName from '../hooks/useCommonClassName'; -import { LayoutEnum } from '../common'; +import { LayoutEnum, StyledProps } from '../common'; import { DescriptionsContext } from './DescriptionsContext'; import DescriptionsItem from './DescriptionsItem'; import Row from './Row'; @@ -26,14 +26,15 @@ import Row from './Row'; * TDescriptionsItem:获取 item 数据(span, label, content) */ -export type DescriptionsProps = TdDescriptionsProps & { - children?: React.ReactNode; -}; +export type DescriptionsProps = TdDescriptionsProps & + StyledProps & { + children?: React.ReactNode; + }; const Descriptions = (DescriptionsProps: DescriptionsProps) => { const props = useDefaultProps(DescriptionsProps, descriptionsDefaultProps); - const { title, bordered, column, layout, items: rowItems, children } = props; + const { className, style, title, bordered, column, layout, items: rowItems, children } = props; const { classPrefix } = useConfig(); @@ -131,7 +132,7 @@ const Descriptions = (DescriptionsProps: DescriptionsProps) => { return ( -
+
{renderHeader()} {renderBody()}
diff --git a/src/descriptions/__tests__/descriptions.test.tsx b/src/descriptions/__tests__/descriptions.test.tsx index 6a712868d6..0b408f98a2 100644 --- a/src/descriptions/__tests__/descriptions.test.tsx +++ b/src/descriptions/__tests__/descriptions.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render } from '@test/utils'; +import { render, screen } from '@test/utils'; import Descriptions from '../index'; import { SizeEnum } from '../../common'; @@ -242,4 +242,40 @@ describe('Descriptions 组件测试', () => { expect(container.querySelector('.t-descriptions')).toBeInTheDocument(); }); + + // nest + test('nest', () => { + const itemsContent = [ + { + label: 'City', + content: 'Shenzhen', + }, + { + label: 'Detail', + content: 'Penguin Island D1 4A Mail Center', + }, + ]; + + const items = [ + { + label: 'Name', + content: 'TDesign', + }, + { + label: 'Telephone Number', + content: '139****0609', + }, + { + label: 'Area', + content: 'China Tencent Headquarters', + }, + { + label: 'Address', + content: , + }, + ]; + render(); + expect(screen.getByText('Shenzhen')).not.toBeNull(); + expect(screen.getByText('Shenzhen')).toBeInTheDocument(); + }); }); diff --git a/src/descriptions/_example/base.jsx b/src/descriptions/_example/base.jsx index b14ffda714..3d537fa461 100644 --- a/src/descriptions/_example/base.jsx +++ b/src/descriptions/_example/base.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { Descriptions, Space } from 'tdesign-react'; -const { DescriptionsItem } = Descriptions; +// const { DescriptionsItem } = Descriptions; export default function BasicDescriptions() { const items = [ @@ -24,9 +24,9 @@ export default function BasicDescriptions() { ]; return ( -

推荐:数据写法

+ {/*

推荐:数据写法

*/} -

JSX写法

+ {/*

JSX写法

TDesign 139****0609 @@ -34,7 +34,7 @@ export default function BasicDescriptions() { Shenzhen Penguin Island D1 4A Mail Center - +
*/}
); } diff --git a/src/descriptions/_example/layout.jsx b/src/descriptions/_example/layout.jsx index a94d02eb02..9cf7d43bc0 100644 --- a/src/descriptions/_example/layout.jsx +++ b/src/descriptions/_example/layout.jsx @@ -1,7 +1,10 @@ import React from 'react'; -import { Descriptions, Space } from 'tdesign-react'; +import { Descriptions, Space, Row, Col, Radio } from 'tdesign-react'; export default function Layout() { + const [layout, setLayout] = React.useState('horizontal'); + const [itemLayout, setItemLayout] = React.useState('horizontal'); + const items = [ { label: 'Name', @@ -21,27 +24,33 @@ export default function Layout() { }, ]; + const layoutOptions = ['horizontal', 'vertical']; + const itemLayoutOptions = ['horizontal', 'vertical']; + return ( - -

整体左右布局,item 左右布局

- -
- - -

整体左右布局,item 上下布局

- -
- - -

整体上下布局,item 左右布局

- -
- - -

整体上下布局,item 上下布局

- -
+ + + layout: + + + + + + + + itemLayout: + + + + + +
); } diff --git a/src/descriptions/_example/nest.jsx b/src/descriptions/_example/nest.jsx new file mode 100644 index 0000000000..b0a7df912b --- /dev/null +++ b/src/descriptions/_example/nest.jsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { Descriptions, Space } from 'tdesign-react'; + +// const { DescriptionsItem } = Descriptions; + +export default function Nest() { + const itemsContent = [ + { + label: 'City', + content: 'Shenzhen', + }, + { + label: 'Detail', + content: 'Penguin Island D1 4A Mail Center', + }, + ]; + + const items = [ + { + label: 'Name', + content: 'TDesign', + }, + { + label: 'Telephone Number', + content: '139****0609', + }, + { + label: 'Area', + content: 'China Tencent Headquarters', + }, + { + label: 'Address', + content: , + }, + ]; + return ( + + + {/* + TDesign + 139****0609 + China Tencent Headquarters + + + Shenzhen + Penguin Island D1 4A Mail Center + + + */} + + ); +} diff --git a/src/form/FormList.tsx b/src/form/FormList.tsx index 1d6c1df2f8..25f6453bd0 100644 --- a/src/form/FormList.tsx +++ b/src/form/FormList.tsx @@ -145,7 +145,8 @@ const FormList: React.FC = (props) => { Promise.resolve().then(() => { if (!fieldsTaskQueueRef.current.length) return; - const currentQueue = fieldsTaskQueueRef.current[0]; + // fix multiple formlist stuck + const currentQueue = fieldsTaskQueueRef.current.pop(); const { fieldData, callback, originData } = currentQueue; [...formListMapRef.current.values()].forEach((formItemRef) => { @@ -155,7 +156,6 @@ const FormList: React.FC = (props) => { const data = get(fieldData, itemName); callback(formItemRef, data); }); - fieldsTaskQueueRef.current.pop(); // formList 嵌套 formList if (!formMapRef || !formMapRef.current) return; diff --git a/src/guide/Guide.tsx b/src/guide/Guide.tsx index 1e0ef69f67..c16b6750ed 100644 --- a/src/guide/Guide.tsx +++ b/src/guide/Guide.tsx @@ -338,7 +338,7 @@ const Guide: React.FC = (originalProps) => { current: innerCurrent, total: stepsTotal, }; - renderBody = isFunction(content) ? content(contentProps) : content; + renderBody = isFunction(content) ? content(contentProps) : React.cloneElement(content, contentProps); } else { renderBody = renderPopupContent(); } @@ -354,7 +354,7 @@ const Guide: React.FC = (originalProps) => { zIndex={zIndex} placement={currentStepInfo.placement as StepPopupPlacement} {...currentStepInfo.popupProps} - overlayClassName={currentStepInfo.stepOverlayClass} + overlayClassName={[`${prefixCls}__popup`, currentStepInfo.stepOverlayClass]} overlayInnerClassName={innerClassName.concat(currentStepInfo.popupProps?.overlayInnerClassName)} >
diff --git a/src/guide/__tests__/__snapshots__/vitest-guide.test.jsx.snap b/src/guide/__tests__/__snapshots__/vitest-guide.test.jsx.snap index 63e07e8802..d24171f6d7 100644 --- a/src/guide/__tests__/__snapshots__/vitest-guide.test.jsx.snap +++ b/src/guide/__tests__/__snapshots__/vitest-guide.test.jsx.snap @@ -113,7 +113,7 @@ exports[`Guide Component > GuideStep.body works fine 1`] = ` class="t-portal-wrapper appear appear-active" >
GuideStep.children works fine 1`] = ` class="t-portal-wrapper appear appear-active" >
GuideStep.children works fine 1`] = ` > TNode @@ -421,7 +423,7 @@ exports[`Guide Component > GuideStep.content works fine 1`] = ` class="t-portal-wrapper appear appear-active" >
GuideStep.content works fine 1`] = ` > TNode @@ -561,7 +565,7 @@ exports[`Guide Component > GuideStep.highlightContent works fine 1`] = ` class="t-portal-wrapper appear appear-active" >
GuideStep.placement is equal to bottom-left 1`] = ` class="t-portal-wrapper appear-done enter-done" >
GuideStep.stepOverlayClass is equal to t-test-guide-s class="t-portal-wrapper appear-done enter-done" >
GuideStep.title works fine 1`] = ` class="t-portal-wrapper appear-done enter-done" >
(originalProps: T, defaultProps: Recor // eslint-disable-next-line const props = Object.assign({}, originalProps); Object.keys(defaultProps).forEach((key) => { - // https://github.com/facebook/react/blob/main/packages/react/src/ReactElement.js#L328-L330 + // https://github.com/facebook/react/blob/main/packages/react/src/jsx/ReactJSXElement.js#L719-L722 if (props[key] === undefined) { props[key] = defaultProps[key]; } diff --git a/src/image-viewer/ImageViewer.tsx b/src/image-viewer/ImageViewer.tsx index e27a9d5086..cbe489cf5b 100644 --- a/src/image-viewer/ImageViewer.tsx +++ b/src/image-viewer/ImageViewer.tsx @@ -65,6 +65,7 @@ const ImageViewer: React.FC = (originalProps) => { closeOnEscKeydown={props.closeOnEscKeydown} onClose={close} onOpen={open} + imageReferrerpolicy={props.imageReferrerpolicy} />, document.body, )} diff --git a/src/image-viewer/ImageViewerMini.tsx b/src/image-viewer/ImageViewerMini.tsx index d1365702b0..8ddf0c10fc 100644 --- a/src/image-viewer/ImageViewerMini.tsx +++ b/src/image-viewer/ImageViewerMini.tsx @@ -5,6 +5,8 @@ import { ImageInfo, ImageScale, ImageViewerScale } from './type'; import { ImageModalItem, ImageViewerUtils } from './ImageViewerModal'; import useConfig from '../hooks/useConfig'; +import type { TdImageViewerProps } from './type'; + export interface ImageModalMiniProps { visible: boolean; title?: TNode; @@ -32,6 +34,7 @@ export interface ImageModalMiniProps { rotate: string; originsize: string; }; + imageReferrerpolicy?: TdImageViewerProps['imageReferrerpolicy']; } export const ImageModalMiniContent: React.FC = (props) => { @@ -46,6 +49,7 @@ export const ImageModalMiniContent: React.FC = (props) => { src={props.currentImage.mainImage} preSrc={props.currentImage.thumbnail} errorText={props.errorText} + imageReferrerpolicy={props.imageReferrerpolicy} />
); diff --git a/src/image-viewer/ImageViewerModal.tsx b/src/image-viewer/ImageViewerModal.tsx index de4d051694..15c8dbabdc 100644 --- a/src/image-viewer/ImageViewerModal.tsx +++ b/src/image-viewer/ImageViewerModal.tsx @@ -25,6 +25,9 @@ import useGlobalIcon from '../hooks/useGlobalIcon'; import useIconMap from './hooks/useIconMap'; import Image from '../image'; +import type { TdImageViewerProps } from './type'; +import { ImageViewerProps } from './ImageViewer'; + const ImageError = ({ errorText }: { errorText: string }) => { const { classPrefix } = useConfig(); const { ImageErrorIcon } = useGlobalIcon({ ImageErrorIcon: TdImageErrorIcon }); @@ -47,10 +50,19 @@ interface ImageModalItemProps { src: string | File; preSrc?: string | File; errorText: string; + imageReferrerpolicy?: TdImageViewerProps['imageReferrerpolicy']; } // 单个弹窗实例 -export const ImageModalItem: React.FC = ({ rotateZ, scale, src, preSrc, mirror, errorText }) => { +export const ImageModalItem: React.FC = ({ + rotateZ, + scale, + src, + preSrc, + mirror, + errorText, + imageReferrerpolicy, +}) => { const { classPrefix } = useConfig(); const [position, onMouseDown] = usePosition({ initPosition: [0, 0] }); @@ -84,7 +96,7 @@ export const ImageModalItem: React.FC = ({ rotateZ, scale, }} src={preSrcImagePreviewUrl} style={preImgStyle} - data-testid="img-drag" + referrerPolicy={imageReferrerpolicy} alt="image" draggable="false" /> @@ -100,7 +112,6 @@ export const ImageModalItem: React.FC = ({ rotateZ, scale, onLoad={() => setLoaded(true)} onError={() => setError(true)} style={imgStyle} - data-testid="img-drag" alt="image" draggable="false" /> @@ -228,16 +239,33 @@ type ImageViewerHeaderProps = { onImgClick: (index: number, ctx: { trigger: 'current' }) => void; images: ImageInfo[]; currentIndex: number; + imageReferrerpolicy?: TdImageViewerProps['imageReferrerpolicy']; }; -function OneImagePreview({ image, classPrefix }: { image: ImageInfo; classPrefix: string }) { +function OneImagePreview({ + image, + classPrefix, + imageReferrerpolicy, +}: { + image: ImageInfo; + classPrefix: string; + imageReferrerpolicy?: TdImageViewerProps['imageReferrerpolicy']; +}) { const { previewUrl } = useImagePreviewUrl(image.thumbnail || image.mainImage); - return ; + return ( + + ); } const ImageViewerHeader = (props: ImageViewerHeaderProps) => { const { classPrefix } = useConfig(); - const { images, currentIndex, onImgClick } = props; + const { images, currentIndex, onImgClick, imageReferrerpolicy } = props; const [isExpand, setIsExpand] = useState(true); @@ -265,7 +293,7 @@ const ImageViewerHeader = (props: ImageViewerHeaderProps) => { })} onClick={() => onImgClick(index, { trigger: 'current' })} > - +
))}
@@ -292,6 +320,7 @@ interface ImageModalProps { closeBtn: boolean | TNode; closeOnEscKeydown?: boolean; onIndexChange?: (index: number, context: { trigger: 'prev' | 'next' }) => void; + imageReferrerpolicy?: ImageViewerProps['imageReferrerpolicy']; } // 弹窗基础组件 @@ -311,6 +340,7 @@ export const ImageModal: React.FC = (props) => { visible, title, closeOnEscKeydown, + imageReferrerpolicy, ...resProps } = props; const { classPrefix } = useConfig(); @@ -410,6 +440,7 @@ export const ImageModal: React.FC = (props) => { onRotate={onRotate} errorText={errorText} tipText={tipText} + imageReferrerpolicy={imageReferrerpolicy} /> ); } @@ -442,7 +473,12 @@ export const ImageModal: React.FC = (props) => { )} {images.length > 1 && ( <> - +
{title} {`${index + 1}/${images.length}`} @@ -481,6 +517,7 @@ export const ImageModal: React.FC = (props) => { preSrc={currentImage.thumbnail} src={currentImage.mainImage} errorText={errorText} + imageReferrerpolicy={imageReferrerpolicy} />
); diff --git a/src/image-viewer/__tests__/image-viewer.test.tsx b/src/image-viewer/__tests__/image-viewer.test.tsx index 8955981a66..8a37301d93 100644 --- a/src/image-viewer/__tests__/image-viewer.test.tsx +++ b/src/image-viewer/__tests__/image-viewer.test.tsx @@ -226,4 +226,23 @@ describe('ImageViewerModal', () => { transform: 'rotateZ(0deg) scale(2)', }); }); + + test('imageReferrerpolicy', async () => { + const referrerPolicy = 'strict-origin-when-cross-origin'; + + const BasicImageViewer = () => { + const trigger = ({ onOpen }) => {triggerText}; + return ; + }; + const { getByText } = render(); + + // 模拟鼠标点击 + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + await mockDelay(); + + expect(document.querySelector('.t-image-viewer__modal-image')?.getAttribute('referrerpolicy')).toBe(referrerPolicy); + }); }); diff --git a/src/image-viewer/_example/base.jsx b/src/image-viewer/_example/base.jsx index 5dfbe3d4ff..f27c9f10de 100644 --- a/src/image-viewer/_example/base.jsx +++ b/src/image-viewer/_example/base.jsx @@ -1,11 +1,11 @@ import React from 'react'; -import {ImageViewer, Image, Space} from 'tdesign-react'; -import {BrowseIcon} from 'tdesign-icons-react' +import { ImageViewer, Image, Space } from 'tdesign-react'; +import { BrowseIcon } from 'tdesign-icons-react'; const img = 'https://tdesign.gtimg.com/demo/demo-image-1.png'; export default function BasicImageViewer() { - const trigger = ({open}) => { + const trigger = ({ open }) => { const mask = (
- 预览 + + 预览 +
); @@ -34,15 +36,15 @@ export default function BasicImageViewer() { height: 160, border: '4px solid var(--td-bg-color-secondarycontainer)', borderRadius: 'var(--td-radius-medium)', - backgroundColor: '#fff' + backgroundColor: '#fff', }} /> - ) - } + ); + }; return ( - + {/* TODO: fix visible=true can not show image previewer */} {/* */} diff --git a/src/image-viewer/image-viewer.en-US.md b/src/image-viewer/image-viewer.en-US.md index 818d441d63..99d75374fe 100644 --- a/src/image-viewer/image-viewer.en-US.md +++ b/src/image-viewer/image-viewer.en-US.md @@ -1,16 +1,18 @@ :: BASE_DOC :: ## API + ### ImageViewer Props name | type | default | description | required -- | -- | -- | -- | -- -className | String | - | 类名 | N -style | Object | - | 样式,Typescript:`React.CSSProperties` | N +className | String | - | className of component | N +style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N closeBtn | TNode | true | Typescript:`boolean \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N closeOnEscKeydown | Boolean | true | trigger image viewer close event on `ESC` keydown | N closeOnOverlay | Boolean | - | \- | N draggable | Boolean | undefined | \- | N +imageReferrerpolicy | String | - | attribute of ``, [MDN Definition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy)。options: no-referrer/no-referrer-when-downgrade/origin/origin-when-cross-origin/same-origin/strict-origin/strict-origin-when-cross-origin/unsafe-url | N imageScale | Object | - | Typescript:`ImageScale` `interface ImageScale { max: number; min: number; step: number; defaultScale?: number; }`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/image-viewer/type.ts) | N images | Array | [] | Typescript:`Array` `interface ImageInfo { mainImage: string \| File; thumbnail?: string \| File; download?: boolean }`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/image-viewer/type.ts) | N index | Number | 0 | \- | N diff --git a/src/image-viewer/image-viewer.md b/src/image-viewer/image-viewer.md index e6eca93f3d..a52a757a03 100644 --- a/src/image-viewer/image-viewer.md +++ b/src/image-viewer/image-viewer.md @@ -1,9 +1,10 @@ :: BASE_DOC :: ## API + ### ImageViewer Props -名称 | 类型 | 默认值 | 说明 | 必传 +名称 | 类型 | 默认值 | 描述 | 必传 -- | -- | -- | -- | -- className | String | - | 类名 | N style | Object | - | 样式,TS 类型:`React.CSSProperties` | N @@ -11,6 +12,7 @@ closeBtn | TNode | true | 是否展示关闭按钮,值为 `true` 显示默认 closeOnEscKeydown | Boolean | true | 按下 ESC 时是否触发图片预览器关闭事件 | N closeOnOverlay | Boolean | - | 是否在点击遮罩层时,触发预览关闭 | N draggable | Boolean | undefined | 是否允许拖拽调整位置。`mode=modal` 时,默认不允许拖拽;`mode=modeless` 时,默认允许拖拽 | N +imageReferrerpolicy | String | - | 图片预览中的 `` 标签的原生属性,[MDN 定义](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy)。可选项:no-referrer/no-referrer-when-downgrade/origin/origin-when-cross-origin/same-origin/strict-origin/strict-origin-when-cross-origin/unsafe-url | N imageScale | Object | - | 图片缩放相关配置。`imageScale.max` 缩放的最大比例;`imageScale.min` 缩放的最小比例;`imageScale.step` 缩放的步长速度; `imageScale.defaultScale` 默认的缩放比例。TS 类型:`ImageScale` `interface ImageScale { max: number; min: number; step: number; defaultScale?: number; }`。[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/image-viewer/type.ts) | N images | Array | [] | 图片数组。`mainImage` 表示主图,必传;`thumbnail` 表示缩略图,如果不存在,则使用主图显示;`download` 是否允许下载图片,默认允许下载。示例: `['img_url_1', 'img_url_2']`,`[{ thumbnail: 'small_image_url', mainImage: 'big_image_url', download: false }]`。TS 类型:`Array` `interface ImageInfo { mainImage: string \| File; thumbnail?: string \| File; download?: boolean }`。[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/image-viewer/type.ts) | N index | Number | 0 | 当前预览图片所在的下标 | N diff --git a/src/image-viewer/type.ts b/src/image-viewer/type.ts index b1fa3d2f40..127e3d7b49 100644 --- a/src/image-viewer/type.ts +++ b/src/image-viewer/type.ts @@ -26,6 +26,18 @@ export interface TdImageViewerProps { * 是否允许拖拽调整位置。`mode=modal` 时,默认不允许拖拽;`mode=modeless` 时,默认允许拖拽 */ draggable?: boolean; + /** + * 图片预览中的 `` 标签的原生属性,[MDN 定义](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy) + */ + imageReferrerpolicy?: + | 'no-referrer' + | 'no-referrer-when-downgrade' + | 'origin' + | 'origin-when-cross-origin' + | 'same-origin' + | 'strict-origin' + | 'strict-origin-when-cross-origin' + | 'unsafe-url'; /** * 图片缩放相关配置。`imageScale.max` 缩放的最大比例;`imageScale.min` 缩放的最小比例;`imageScale.step` 缩放的步长速度; `imageScale.defaultScale` 默认的缩放比例 */ @@ -102,12 +114,30 @@ export interface ImageScale { defaultScale?: number; } +export interface ImageScale { + max: number; + min: number; + step: number; + defaultScale?: number; +} + export interface ImageInfo { mainImage: string | File; thumbnail?: string | File; download?: boolean; } +export interface ImageInfo { + mainImage: string | File; + thumbnail?: string | File; + download?: boolean; +} + +export interface ImageViewerScale { + minWidth: number; + minHeight: number; +} + export interface ImageViewerScale { minWidth: number; minHeight: number; diff --git a/src/image/Image.tsx b/src/image/Image.tsx index f561ac7db8..35aea54f88 100644 --- a/src/image/Image.tsx +++ b/src/image/Image.tsx @@ -35,7 +35,6 @@ const InternalImage: React.ForwardRefRenderFunction className, src, style, - alt, fit, position, shape, @@ -50,8 +49,14 @@ const InternalImage: React.ForwardRefRenderFunction fallback, onLoad, onError, - ...rest + ...waitPassRest } = props; + const { + // penetrate pass to image tag element props + alt, + referrerpolicy, + ...rest + } = waitPassRest; const { classPrefix } = useConfig(); const imageRef = useRef(null); @@ -127,7 +132,7 @@ const InternalImage: React.ForwardRefRenderFunction // 这里添加setTimeout是因为CSR image渲染时,onError有时快有时慢,会导致执行顺序不同导致的bug setTimeout(() => { if (!isValid && !isFirstError.current) { - // SSR模式下获取不到imaage的合成事件,暂时传递image实例 + // SSR模式下获取不到 image 的合成事件,暂时传递 image 实例 handleError(imgRef.current); } }, 0); @@ -141,7 +146,7 @@ const InternalImage: React.ForwardRefRenderFunction if (imgRef.current) { const { complete, naturalWidth, naturalHeight } = imgRef.current; if (complete && naturalWidth !== 0 && naturalHeight !== 0) { - // SSR模式下获取不到imaage的合成事件,暂时传递image实例 + // SSR模式下获取不到 image 的合成事件,暂时传递 image 实例 handleLoad(imgRef.current); } } @@ -197,6 +202,7 @@ const InternalImage: React.ForwardRefRenderFunction `${classPrefix}-image--position-${position}`, )} alt={alt} + referrerPolicy={referrerpolicy} /> ); }; diff --git a/src/list/__tests__/list.test.tsx b/src/list/__tests__/list.test.tsx index 77d7aff258..0c4eb9c488 100644 --- a/src/list/__tests__/list.test.tsx +++ b/src/list/__tests__/list.test.tsx @@ -2,7 +2,7 @@ import { render, fireEvent, vi } from '@test/utils'; import React from 'react'; import List from '../List'; -const { ListItem } = List; +const { ListItem, ListItemMeta } = List; describe('List 组件测试', () => { const data = [ @@ -51,5 +51,79 @@ describe('List 组件测试', () => { fireEvent.scroll(container.firstChild); expect(fn).toHaveBeenCalled(); }); + + test('header and footer props', () => { + const header = 'header content'; + const footer = 'footer content'; + const { queryByText } = render(); + + expect(queryByText(header)).not.toBeNull(); + expect(queryByText(header)).toBeInTheDocument(); + expect(queryByText(footer)).not.toBeNull(); + expect(queryByText(footer)).toBeInTheDocument(); + }); + + test('asyncLoading props', () => { + const { container } = render(); + + expect(container.querySelector('.t-loading')).not.toBeNull(); + expect(container.querySelector('.t-loading')).toBeInTheDocument(); + }); + }); + + describe('ListItem Component Test', () => { + test('content and children render', () => { + const contextText = 'content render'; + const { container } = render({contextText}
} />); + + const { container: childContainer } = render( + +
{contextText}
+
, + ); + + expect(container.querySelector('#content_id')).not.toBeNull(); + expect(container.querySelector('#content_id')).toBeInTheDocument(); + expect(childContainer.querySelector('#child_test')).not.toBeNull(); + expect(childContainer.querySelector('#child_test')).toBeInTheDocument(); + }); + + test('action', () => { + const { queryByText } = render(操作1} />); + + expect(queryByText('操作1')).not.toBeNull(); + expect(queryByText('操作1')).toBeInTheDocument(); + }); + }); + + describe('ListItemMeta Component Test', () => { + const imgSrc = 'https://tdesign.gtimg.com/list-icon.png'; + const description = 'Test Description'; + test('image string', () => { + const { container } = render(); + + expect(container.querySelector('img')).not.toBeNull(); + expect(container.querySelector('img')).toBeInTheDocument(); + }); + test('image TNode', () => { + const Img = () => test img; + const { container } = render(} />); + + expect(container.querySelector('#img_test')).not.toBeNull(); + expect(container.querySelector('#img_test')).toBeInTheDocument(); + }); + test('description string', () => { + const { queryByText } = render(); + + expect(queryByText(description)).not.toBeNull(); + expect(queryByText(description)).toBeInTheDocument(); + }); + test('description TNode', () => { + const Description = () =>
{description}
; + const { container } = render(} />); + + expect(container.querySelector('#description_test')).not.toBeNull(); + expect(container.querySelector('#description_test')).toBeInTheDocument(); + }); }); }); diff --git a/src/select/_example/multiple.jsx b/src/select/_example/multiple.jsx index 8f223b9ca0..02c5e7f322 100644 --- a/src/select/_example/multiple.jsx +++ b/src/select/_example/multiple.jsx @@ -35,8 +35,24 @@ const MultipleSelect = () => { return ( - + { + console.log('onRemove', options); + }} + > {options2.map((item) => ( ))} diff --git a/src/select/base/PopupContent.tsx b/src/select/base/PopupContent.tsx index fe391a0259..2bd4a10714 100644 --- a/src/select/base/PopupContent.tsx +++ b/src/select/base/PopupContent.tsx @@ -82,6 +82,7 @@ const PopupContent = React.forwardRef((props, popupContentRef, scroll: propsScroll, options: propsOptions, + size, }); const { classPrefix } = useConfig(); diff --git a/src/select/base/Select.tsx b/src/select/base/Select.tsx index c4a2a88da2..2dad56f6bf 100644 --- a/src/select/base/Select.tsx +++ b/src/select/base/Select.tsx @@ -2,15 +2,18 @@ import React, { useEffect, useMemo, KeyboardEvent, + WheelEvent, useRef, useCallback, Children, cloneElement, isValidElement, + useState, } from 'react'; import classNames from 'classnames'; import isFunction from 'lodash/isFunction'; import get from 'lodash/get'; +import debounce from 'lodash/debounce'; import useControlled from '../../hooks/useControlled'; import { useLocaleReceiver } from '../../locale/LocalReceiver'; import useConfig from '../../hooks/useConfig'; @@ -99,7 +102,8 @@ const Select = forwardRefWithStatics( const [value, onChange] = useControlled(props, 'value', props.onChange); const selectInputRef = useRef(null); const { classPrefix } = useConfig(); - const { overlayClassName, ...restPopupProps } = popupProps || {}; + const { overlayClassName, onScroll, onScrollToBottom, ...restPopupProps } = popupProps || {}; + const [isScrolling, toggleIsScrolling] = useState(false); const name = `${classPrefix}-select`; // t-select @@ -123,8 +127,9 @@ const Select = forwardRefWithStatics( const handleShowPopup = (visible: boolean, ctx: PopupVisibleChangeContext) => { if (disabled) return; - setShowPopup(visible, ctx); + visible && toggleIsScrolling(false); !visible && onInputChange('', { trigger: 'blur' }); + setShowPopup(visible, ctx); }; // 可以根据触发来源,自由定制标签变化时的筛选器行为 @@ -345,6 +350,12 @@ const Select = forwardRefWithStatics( const selectedOptions = getSelectedOptions(values, multiple, valueType, keys, tmpPropOptions); onChange(values, { e, selectedOptions, trigger: 'uncheck' }); tagProps?.onClose?.({ e }); + + onRemove?.({ + value: value[key], + data: { label: v, value: value[key] }, + e, + }); }} > {v} @@ -375,7 +386,7 @@ const Select = forwardRefWithStatics( // 将第一个选中的 option 置于列表可见范围的最后一位 const updateScrollTop = (content: HTMLDivElement) => { - if (!content) { + if (!content || isScrolling) { return; } const firstSelectedNode: HTMLDivElement = content.querySelector(`.${classPrefix}-is-selected`); @@ -399,6 +410,20 @@ const Select = forwardRefWithStatics( const handleEnter = (_, context: { inputValue: string; e: KeyboardEvent }) => { onEnter?.({ ...context, value }); }; + + const handleScroll = ({ e }: { e: WheelEvent }) => { + toggleIsScrolling(true); + + onScroll?.({ e }); + if (onScrollToBottom) { + const debounceOnScrollBottom = debounce((e) => onScrollToBottom({ e }), 100); + + const { scrollTop, clientHeight, scrollHeight } = e.target as HTMLDivElement; + if (clientHeight + Math.floor(scrollTop) === scrollHeight) { + debounceOnScrollBottom(e); + } + } + }; return (
; options: TdSelectProps['options']; + size: SizeEnum; } -const usePanelVirtualScroll = ({ popupContentRef, scroll, options }: PanelVirtualScroll) => { +const usePanelVirtualScroll = ({ popupContentRef, scroll, options, size }: PanelVirtualScroll) => { const scrollThreshold = scroll?.threshold || 100; const scrollType = scroll?.type; @@ -18,16 +19,22 @@ const usePanelVirtualScroll = ({ popupContentRef, scroll, options }: PanelVirtua [scrollType, scrollThreshold, options], ); - const scrollParams = useMemo( - () => ({ + const scrollParams = useMemo(() => { + const heightMap = { + small: 20, + medium: 28, + large: 36, + }; + const rowHeight = heightMap[size] || 28; + return { type: 'virtual', isFixedRowHeight: scroll?.isFixedRowHeight || false, - rowHeight: scroll?.rowHeight || 28, // 默认每行高度28 + rowHeight: scroll?.rowHeight || rowHeight, bufferSize: scroll?.bufferSize || 20, threshold: scrollThreshold, - }), - [scroll, scrollThreshold], - ); + }; + }, [scroll, scrollThreshold, size]); + const { visibleData = null, handleScroll: handleVirtualScroll = null, diff --git a/src/select/type.ts b/src/select/type.ts index 1f32030582..777027a271 100644 --- a/src/select/type.ts +++ b/src/select/type.ts @@ -317,7 +317,7 @@ export type SelectValueChangeTrigger = 'clear' | 'tag-remove' | 'backspace' | 'c export interface SelectRemoveContext { value: string | number; data: T; - e: MouseEvent | KeyboardEvent; + e: MouseEvent | KeyboardEvent; } export type SelectOption = TdOptionProps | SelectOptionGroup | PlainObject; diff --git a/src/skeleton/Skeleton.tsx b/src/skeleton/Skeleton.tsx index 4a06849eb6..815204c0e0 100644 --- a/src/skeleton/Skeleton.tsx +++ b/src/skeleton/Skeleton.tsx @@ -10,7 +10,7 @@ import parseTNode from '../_util/parseTNode'; import { skeletonDefaultProps } from './defaultProps'; import useDefaultProps from '../hooks/useDefaultProps'; -export type SkeletonProps = TdSkeletonProps & StyledProps & { children: React.ReactNode }; +export type SkeletonProps = TdSkeletonProps & StyledProps & { children?: React.ReactNode }; const ThemeMap: Record = { text: [1], diff --git a/src/space/Space.tsx b/src/space/Space.tsx index fccf227e26..cc918a41df 100644 --- a/src/space/Space.tsx +++ b/src/space/Space.tsx @@ -1,4 +1,4 @@ -import React, { CSSProperties, ReactNode, forwardRef, useMemo } from 'react'; +import React, { CSSProperties, ReactNode, useMemo } from 'react'; import classNames from 'classnames'; import { isFragment } from 'react-is'; import useConfig from '../hooks/useConfig'; @@ -6,6 +6,7 @@ import { TdSpaceProps } from './type'; import { StyledProps } from '../common'; import { spaceDefaultProps } from './defaultProps'; import { getFlexGapPolyFill } from '../_common/js/utils/helper'; +import useDefaultProps from '../hooks/useDefaultProps'; // export for test export const SizeMap = { small: '8px', medium: '16px', large: '24px' }; @@ -39,19 +40,24 @@ const toArray = (children: React.ReactNode): React.ReactElement[] => { const EMPTY_NODE: ReactNode[] = ['', false, null, undefined]; -const Space = forwardRef((props: SpaceProps, ref: React.Ref) => { - const { className, style, align, direction, size, breakLine, separator } = props; +const Space = React.forwardRef((originalProps, ref) => { + const props = useDefaultProps(originalProps, spaceDefaultProps); + const { className, style, align, direction, size, breakLine, separator, forceFlexGapPolyfill } = props; const { classPrefix } = useConfig(); - const needPolyfill = Boolean(props.forceFlexGapPolyfill || defaultNeedPolyfill); + const needPolyfill = Boolean(forceFlexGapPolyfill || defaultNeedPolyfill); - const renderStyle = useMemo(() => { + const renderStyle = useMemo(() => { let renderGap = ''; if (Array.isArray(size)) { renderGap = size .map((s) => { - if (typeof s === 'number') return `${s}px`; - if (typeof s === 'string') return SizeMap[s] || s; + if (typeof s === 'number') { + return `${s}px`; + } + if (typeof s === 'string') { + return SizeMap[s] || s; + } return s; }) .join(' '); @@ -70,9 +76,9 @@ const Space = forwardRef((props: SpaceProps, ref: React.Ref) => tStyle.gap = renderGap; } return tStyle; - }, [style, size, needPolyfill]) as React.CSSProperties; + }, [style, size, needPolyfill]); - function renderChildren() { + const childrenNode = React.useMemo(() => { const children = toArray(props.children); const childCount = React.Children.count(children); return React.Children.map(children, (child, index) => { @@ -85,7 +91,7 @@ const Space = forwardRef((props: SpaceProps, ref: React.Ref) => ); }); - } + }, [props.children, classPrefix, separator]); return (
) => [`${classPrefix}-space--polyfill`]: needPolyfill, })} > - {renderChildren()} + {childrenNode}
); }); Space.displayName = 'Space'; -Space.defaultProps = spaceDefaultProps; export default Space; diff --git a/src/steps/StepItem.tsx b/src/steps/StepItem.tsx index cad45d4d29..826ce99082 100644 --- a/src/steps/StepItem.tsx +++ b/src/steps/StepItem.tsx @@ -7,45 +7,48 @@ import { TdStepItemProps } from './type'; import { StyledProps } from '../common'; import StepsContext from './StepsContext'; import { stepItemDefaultProps } from './defaultProps'; +import useDefaultProps from '../hooks/useDefaultProps'; export interface StepItemProps extends TdStepItemProps, StyledProps { index?: number; children?: React.ReactNode; } -const StepItem = (props: StepItemProps) => { +const StepItem: React.FC = (originalProps) => { + const props = useDefaultProps(originalProps, stepItemDefaultProps); const { index, icon, title, content, value, children, style, status } = props; const { current, theme, onChange, readonly } = useContext(StepsContext); const { classPrefix, steps: globalStepsConfig } = useConfig(); - const { CloseIcon, CheckIcon } = useGlobalIcon({ - CloseIcon: TdCloseIcon, - CheckIcon: TdCheckIcon, - }); + const { CloseIcon, CheckIcon } = useGlobalIcon({ CloseIcon: TdCloseIcon, CheckIcon: TdCheckIcon }); const canClick = status !== 'process' && !readonly; // 步骤条每一步展示的图标 - function renderIcon() { - if (!icon) return null; - + const iconNode = React.useMemo(() => { + if (!icon) { + return null; + } const iconCls = `${classPrefix}-steps-item__icon--number`; - - if (icon && icon !== true) return {icon}; - - if (theme !== 'default') return null; - - if (status === 'error') + if (icon && icon !== true) { + return {icon}; + } + if (theme !== 'default') { + return null; + } + if (status === 'error') { return {(globalStepsConfig.errorIcon || ) as React.ReactNode}; - - if (status === 'finish') + } + if (status === 'finish') { return {(globalStepsConfig.checkIcon || ) as React.ReactNode}; - + } return {Number(index) + 1}; - } + }, [icon, classPrefix, theme, status, globalStepsConfig, index, CloseIcon, CheckIcon]); function onStepClick(e: React.MouseEvent) { - if (!canClick) return; + if (!canClick) { + return; + } const currentValue = value ?? index; onChange(currentValue, current, { e }); } @@ -73,7 +76,7 @@ const StepItem = (props: StepItemProps) => { [`${classPrefix}-steps-item-${status}`]: status, })} > - {renderIcon()} + {iconNode}
{title}
@@ -86,6 +89,5 @@ const StepItem = (props: StepItemProps) => { }; StepItem.displayName = 'StepItem'; -StepItem.defaultProps = stepItemDefaultProps; export default StepItem; diff --git a/src/steps/Steps.tsx b/src/steps/Steps.tsx index a7f00aef9b..75c7e56d9d 100644 --- a/src/steps/Steps.tsx +++ b/src/steps/Steps.tsx @@ -8,13 +8,15 @@ import { StyledProps } from '../common'; import StepItem from './StepItem'; import StepsContext from './StepsContext'; import { stepsDefaultProps } from './defaultProps'; +import useDefaultProps from '../hooks/useDefaultProps'; export interface StepsProps extends TdStepsProps, StyledProps { children?: React.ReactNode; } const Steps = forwardRefWithStatics( - (props: StepsProps, ref) => { + (originalProps: StepsProps, ref) => { + const props = useDefaultProps(originalProps, stepsDefaultProps); const { style, readonly, layout, theme, sequence, separator, children, options } = props; const { classPrefix } = useConfig(); @@ -23,15 +25,20 @@ const Steps = forwardRefWithStatics( // 整理 StepItem value 映射 const indexMap = useMemo(() => { const map = {}; - if (options) { options.forEach((item, index) => { - if (item.value !== undefined) map[item.value] = index; + if (item.value !== undefined) { + map[item.value] = index; + } }); } else { React.Children.forEach(children, (child, index) => { - if (!React.isValidElement(child)) return; - if (child.props.value !== undefined) map[child.props.value] = index; + if (!React.isValidElement(child)) { + return; + } + if (child.props.value !== undefined) { + map[child.props.value] = index; + } }); } return map; @@ -39,13 +46,20 @@ const Steps = forwardRefWithStatics( const handleStatus = useCallback( (item: TdStepItemProps, index: number) => { - if (current === 'FINISH') return 'finish'; - if (item.status && item.status !== 'default') return item.status; - + if (current === 'FINISH') { + return 'finish'; + } + if (item.status && item.status !== 'default') { + return item.status; + } // value 不存在时,使用 index 进行区分每一个步骤 if (item.value === undefined) { - if (sequence === 'positive' && index < current) return 'finish'; - if (sequence === 'reverse' && index > current) return 'finish'; + if (sequence === 'positive' && typeof current === 'number' && index < current) { + return 'finish'; + } + if (sequence === 'reverse' && typeof current === 'number' && index > current) { + return 'finish'; + } } // value 存在,找匹配位置 @@ -55,21 +69,26 @@ const Steps = forwardRefWithStatics( console.warn('TDesign Steps Warn: The current `value` is not exist.'); return 'default'; } - if (sequence === 'positive' && index < matchIndex) return 'finish'; - if (sequence === 'reverse' && index > matchIndex) return 'finish'; + if (sequence === 'positive' && index < matchIndex) { + return 'finish'; + } + if (sequence === 'reverse' && index > matchIndex) { + return 'finish'; + } } const key = item.value ?? index; - if (key === current) return 'process'; + if (key === current) { + return 'process'; + } return 'default'; }, [current, sequence, indexMap], ); - const stepItemList = useMemo(() => { + const stepItemList = useMemo(() => { if (options) { const optionsDisplayList = sequence === 'reverse' ? options.reverse() : options; - - return options.map((item, index: number) => { + return options.map((item, index) => { const stepIndex = sequence === 'reverse' ? optionsDisplayList.length - index - 1 : index; return ; }); @@ -111,6 +130,5 @@ const Steps = forwardRefWithStatics( ); Steps.displayName = 'Steps'; -Steps.defaultProps = stepsDefaultProps; export default Steps; diff --git a/src/sticky-tool/StickyItem.tsx b/src/sticky-tool/StickyItem.tsx index 316a5ed39a..18c6c77257 100644 --- a/src/sticky-tool/StickyItem.tsx +++ b/src/sticky-tool/StickyItem.tsx @@ -1,8 +1,8 @@ -import React, { forwardRef, useCallback, useMemo, MouseEvent } from 'react'; +import React, { useCallback, useMemo, MouseEvent } from 'react'; import classNames from 'classnames'; import useConfig from '../hooks/useConfig'; import type { TdStickyItemProps, TdStickyToolProps } from './type'; -import Popup, { PopupProps } from '../popup'; +import Popup, { PopupPlacement, PopupProps } from '../popup'; import type { StyledProps, Styles } from '../common'; export interface StickyItemProps extends TdStickyItemProps, StyledProps { @@ -16,7 +16,7 @@ export interface StickyItemProps extends TdStickyItemProps, StyledProps { children?: React.ReactNode; } -const StickyItem = forwardRef((props: StickyItemProps, ref: React.Ref) => { +const StickyItem = React.forwardRef((props, ref) => { const { icon, label, @@ -34,8 +34,8 @@ const StickyItem = forwardRef((props: StickyItemProps, ref: React.Ref (placement.indexOf('right') !== -1 ? 'left' : 'right'), [placement]); - const styles = useMemo(() => { + const popupPlacement = useMemo(() => (placement.includes('right') ? 'left' : 'right'), [placement]); + const styles = useMemo(() => { const styles: Styles = { ...style }; if (baseWidth) { const selfWidth = type === 'normal' ? '56px' : '40px'; diff --git a/src/sticky-tool/StickyTool.tsx b/src/sticky-tool/StickyTool.tsx index dfff54a459..f65d3d361a 100644 --- a/src/sticky-tool/StickyTool.tsx +++ b/src/sticky-tool/StickyTool.tsx @@ -6,6 +6,7 @@ import type { TdStickyToolProps, TdStickyItemProps } from './type'; import type { StyledProps, Styles } from '../common'; import StickyItem from './StickyItem'; import { stickyToolDefaultProps } from './defaultProps'; +import useDefaultProps from '../hooks/useDefaultProps'; export interface StickyToolProps extends TdStickyToolProps, StyledProps { children?: React.ReactNode; @@ -14,10 +15,10 @@ export interface StickyToolProps extends TdStickyToolProps, StyledProps { const StickyTool = forwardRefWithStatics( (props: StickyToolProps, ref: React.Ref) => { const { style, className, children, width, type, shape, placement, offset, popupProps, list, onClick, onHover } = - props; + useDefaultProps(props, stickyToolDefaultProps); const { classPrefix } = useConfig(); - const styles = useMemo(() => { + const styles = useMemo(() => { const position: Array = offset ? [80, 24] : ['80px', '24px']; offset?.forEach((item, index) => { position[index] = isNaN(Number(item)) @@ -33,7 +34,9 @@ const StickyTool = forwardRefWithStatics( styles.transform = 'translate(0, -50%)'; } }); - if (width) styles.width = typeof width === 'number' ? `${width}px` : width; + if (width) { + styles.width = typeof width === 'number' ? `${width}px` : width; + } return styles; }, [offset, placement, width, style]); @@ -50,9 +53,9 @@ const StickyTool = forwardRefWithStatics( [onHover], ); - const stickyItemList = useMemo(() => { + const stickyItemList = useMemo(() => { if (list?.length) { - return list.map((item, index: number) => { + return list.map((item, index: number) => { const itemProps = { ...item, type, @@ -98,6 +101,5 @@ const StickyTool = forwardRefWithStatics( ); StickyTool.displayName = 'StickyTool'; -StickyTool.defaultProps = stickyToolDefaultProps; export default StickyTool; diff --git a/src/swiper/Swiper.tsx b/src/swiper/Swiper.tsx index 4ee355e162..2d910e0b21 100644 --- a/src/swiper/Swiper.tsx +++ b/src/swiper/Swiper.tsx @@ -38,9 +38,8 @@ const defaultNavigation: SwiperNavigation = { type: 'bars', }; -const Swiper = (swiperProps: SwiperProps) => { +const Swiper: React.FC & Record<'SwiperItem', typeof SwiperItem> = (swiperProps) => { const props = useDefaultProps(swiperProps, swiperDefaultProps); - const { animation, // 轮播切换动画效果类型 autoplay, // 是否自动播放 @@ -76,25 +75,26 @@ const Swiper = (swiperProps: SwiperProps) => { const [currentIndex, setCurrentIndex] = useState(defaultCurrent); const [needAnimation, setNeedAnimation] = useState(false); const [arrowShow, setArrowShow] = useState(navigationConfig.showSlideBtn === 'always'); - const swiperTimer = useRef(null); // 计时器指针 - const swiperAnimationTimer = useRef(null); // 计时器指针 - const isHovering = useRef(false); - const swiperWrap = useRef(null); + const swiperTimer = useRef>(null); // 计时器指针 + const swiperAnimationTimer = useRef>(null); // 计时器指针 + const isHovering = useRef(false); + const swiperWrap = useRef(null); - const getWrapAttribute = (attr: string) => swiperWrap.current?.parentNode?.[attr]; + const getWrapAttribute = React.useCallback((attr: string) => swiperWrap.current?.parentNode?.[attr], []); // 进行子组件筛选,创建子节点列表 - const childrenList = useMemo( + const childrenList = useMemo( () => React.Children.toArray(children).filter( (child: JSX.Element) => child.type.displayName === SwiperItem.displayName, ), [children], ); + const childrenLength = childrenList.length; // 创建渲染用的节点列表 - const swiperItemList = childrenList.map((child: JSX.Element, index: number) => + const swiperItemList = childrenList.map((child: JSX.Element, index) => React.cloneElement(child, { key: index, index, @@ -206,7 +206,7 @@ const Swiper = (swiperProps: SwiperProps) => { }, [setTimer, clearTimer, stopOnHover, loop, currentIndex, endIndex]); // 鼠标移入移出事件 - const onMouseEnter = () => { + const onMouseEnter: React.MouseEventHandler = () => { isHovering.current = true; if (stopOnHover) { clearTimer(); @@ -215,7 +215,7 @@ const Swiper = (swiperProps: SwiperProps) => { setArrowShow(true); } }; - const onMouseLeave = () => { + const onMouseLeave: React.MouseEventHandler = () => { isHovering.current = false; if (!swiperTimer.current && autoplay) { setTimer(); @@ -255,7 +255,7 @@ const Swiper = (swiperProps: SwiperProps) => { } }; - const createArrow = (type: CreateArrow) => { + const renderArrow = (type: CreateArrow): React.ReactNode => { if (!arrowShow) { return ''; } @@ -286,11 +286,11 @@ const Swiper = (swiperProps: SwiperProps) => { ); }; - const createNavigation = () => { + const renderNavigation = (): React.ReactNode => { if (navigationConfig.type === 'fraction') { return (
- {createArrow(CreateArrow.Fraction)} + {renderArrow(CreateArrow.Fraction)}
); } @@ -322,7 +322,7 @@ const Swiper = (swiperProps: SwiperProps) => { }; // 构造 css 对象 - const getWrapperStyle = () => { + const getWrapperStyle = (): React.CSSProperties => { const loopIndex = loop ? 1 : 0; const offsetHeight = height ? `${height}px` : `${getWrapAttribute('offsetHeight')}px`; if (type === 'card' || animation === 'fade') { @@ -376,8 +376,8 @@ const Swiper = (swiperProps: SwiperProps) => { {swiperItemList}
- {createNavigation()} - {createArrow(CreateArrow.Default)} + {renderNavigation()} + {renderArrow(CreateArrow.Default)}
); diff --git a/src/swiper/SwiperItem.tsx b/src/swiper/SwiperItem.tsx index fda99c6ea0..6a2af5969b 100644 --- a/src/swiper/SwiperItem.tsx +++ b/src/swiper/SwiperItem.tsx @@ -42,7 +42,7 @@ const calculateTranslate = (index: number, currentIndex: number, parentWidth: nu return ((2 + itemWidth * (CARD_SCALE - 1)) * parentWidth) / 2; }; -const getZindex = (isActivity, inStage) => { +const getZindex = (isActivity: boolean, inStage: boolean) => { if (isActivity) { return 2; } @@ -52,7 +52,7 @@ const getZindex = (isActivity, inStage) => { return 0; }; -const SwiperItem = (props: SwiperItemProps) => { +const SwiperItem: React.FC = (props) => { const { children, currentIndex, @@ -68,7 +68,7 @@ const SwiperItem = (props: SwiperItemProps) => { const [, setUpdate] = useState({}); const isFirstFirstRender = useIsFirstRender(); - const getSwiperItemStyle = () => { + const getSwiperItemStyle = (): React.CSSProperties => { if (animation === 'fade') { return { opacity: currentIndex === index ? 1 : 0, diff --git a/src/swiper/__tests__/swiper.test.tsx b/src/swiper/__tests__/swiper.test.tsx index 1be5398bb6..40e77b8d2b 100644 --- a/src/swiper/__tests__/swiper.test.tsx +++ b/src/swiper/__tests__/swiper.test.tsx @@ -72,7 +72,7 @@ describe('Swiper 组件测试', () => { expect(document.querySelector('.t-swiper__container')).not.toBeNull(); fireEvent.click(document.querySelector('.t-swiper__arrow-right')); - expect(document.querySelector('.t-swiper__container').getAttribute('style')).toBe( + expect(document.querySelector('.t-swiper__container')?.getAttribute('style')).toBe( 'transform: translate3d(-200%, 0px, 0px); transition: transform 0.3s ease;', ); fireEvent.click(document.querySelector('.t-swiper__arrow-left')); diff --git a/src/switch/Switch.tsx b/src/switch/Switch.tsx index a63d13395a..6dda377770 100644 --- a/src/switch/Switch.tsx +++ b/src/switch/Switch.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import classNames from 'classnames'; import Loading from '../loading'; import useConfig from '../hooks/useConfig'; @@ -8,14 +8,16 @@ import { SwitchValue, TdSwitchProps } from './type'; import { switchDefaultProps } from './defaultProps'; import log from '../_common/js/log'; import parseTNode from '../_util/parseTNode'; +import useDefaultProps from '../hooks/useDefaultProps'; export type SwitchChangeEventHandler = (value: boolean, event: React.MouseEvent) => void; export type SwitchClickEventHandler = SwitchChangeEventHandler; export interface SwitchProps extends TdSwitchProps, StyledProps {} -const Switch = forwardRef((props, ref) => { +const Switch = React.forwardRef((originalProps, ref) => { const { classPrefix } = useConfig(); + const props = useDefaultProps>(originalProps, switchDefaultProps); const { className, value, defaultValue, disabled, loading, size, label, customValue, onChange, ...restProps } = props; const [activeValue = true, inactiveValue = false] = customValue || []; @@ -23,24 +25,23 @@ const Switch = forwardRef((props, ref) => { const initChecked = defaultValue === activeValue || value === activeValue; const [innerChecked, setInnerChecked] = useState(initChecked); - function renderContent(checked: boolean) { + const contentNode = React.useMemo(() => { if (Array.isArray(label)) { const [activeContent = '', inactiveContent = ''] = label; - const content = checked ? activeContent : inactiveContent; + const content = innerChecked ? activeContent : inactiveContent; return parseTNode(content, { value }); } - return parseTNode(label, { value }); - } - - function onInternalClick(e: React.MouseEvent) { - if (disabled) return; + }, [label, innerChecked, value]); + const onInternalClick: React.MouseEventHandler = (e) => { + if (disabled) { + return; + } !isControlled && setInnerChecked(!innerChecked); - const changedValue = !innerChecked ? activeValue : inactiveValue; onChange?.(changedValue, { e }); - } + }; useEffect(() => { if (Array.isArray(customValue) && !customValue.includes(value)) { @@ -71,17 +72,14 @@ const Switch = forwardRef((props, ref) => { ref={ref} onClick={onInternalClick} > - {loading && } -
{renderContent(innerChecked)}
+ {loading && } +
{contentNode}
); }); Switch.displayName = 'Switch'; -Switch.defaultProps = switchDefaultProps; export default Switch as ( - props: SwitchProps & { - ref?: React.Ref; - }, + props: SwitchProps & React.RefAttributes, ) => React.ReactElement; diff --git a/src/table/BaseTable.tsx b/src/table/BaseTable.tsx index e3fa8fe547..59ce4e0de2 100644 --- a/src/table/BaseTable.tsx +++ b/src/table/BaseTable.tsx @@ -1,10 +1,19 @@ -import React, { useRef, useMemo, useImperativeHandle, forwardRef, useEffect, useState, WheelEvent } from 'react'; +import React, { + useRef, + useMemo, + useImperativeHandle, + forwardRef, + useEffect, + useState, + WheelEvent, + RefAttributes, +} from 'react'; import pick from 'lodash/pick'; import classNames from 'classnames'; import TBody, { extendTableProps, TableBodyProps } from './TBody'; -import { Affix } from '../affix'; +import { Affix, AffixRef } from '../affix'; import { ROW_LISTENERS } from './TR'; -import THead from './THead'; +import THead, { TheadProps } from './THead'; import TFoot from './TFoot'; import useTableHeader from './hooks/useTableHeader'; import useColumnResize from './hooks/useColumnResize'; @@ -23,6 +32,7 @@ import { TableRowData } from './type'; import useVirtualScroll from '../hooks/useVirtualScroll'; import { getIEVersion } from '../_common/js/utils/helper'; import log from '../_common/js/log'; +import useDefaultProps from '../hooks/useDefaultProps'; export const BASE_TABLE_EVENTS = ['page-change', 'cell-click', 'scroll', 'scrollX', 'scrollY']; export const BASE_TABLE_ALL_EVENTS = ROW_LISTENERS.map((t) => `row-${t}`).concat(BASE_TABLE_EVENTS); @@ -31,7 +41,8 @@ export interface TableListeners { [key: string]: Function; } -const BaseTable = forwardRef((props, ref) => { +const BaseTable = forwardRef((originalProps, ref) => { + const props = useDefaultProps>(originalProps, baseTableDefaultProps); const { showHeader = true, tableLayout, @@ -62,10 +73,10 @@ const BaseTable = forwardRef((props, ref) => { ); const { showElement } = useElementLazyRender(tableRef, lazyLoad); - const paginationAffixRef = useRef(); - const horizontalScrollAffixRef = useRef(); - const headerTopAffixRef = useRef(); - const footerBottomAffixRef = useRef(); + const paginationAffixRef = useRef(); + const horizontalScrollAffixRef = useRef(); + const headerTopAffixRef = useRef(); + const footerBottomAffixRef = useRef(); // 1. 表头吸顶;2. 表尾吸底;3. 底部滚动条吸底;4. 分页器吸底 const { @@ -284,7 +295,7 @@ const BaseTable = forwardRef((props, ref) => { })} ); - const headProps = { + const headProps: TheadProps = { isFixedHeader, rowAndColFixedPosition, isMultipleHeader, @@ -727,10 +738,6 @@ const BaseTable = forwardRef((props, ref) => { BaseTable.displayName = 'BaseTable'; -BaseTable.defaultProps = baseTableDefaultProps; - export default BaseTable as ( - props: BaseTableProps & { - ref?: React.Ref; - }, + props: BaseTableProps & RefAttributes, ) => React.ReactElement; diff --git a/src/table/Cell.tsx b/src/table/Cell.tsx index eec7f5e1c1..34fce64796 100644 --- a/src/table/Cell.tsx +++ b/src/table/Cell.tsx @@ -1,4 +1,4 @@ -import React, { MouseEvent, MutableRefObject } from 'react'; +import React, { MouseEvent, MutableRefObject, ReactNode } from 'react'; import classNames from 'classnames'; import isFunction from 'lodash/isFunction'; import get from 'lodash/get'; @@ -12,7 +12,7 @@ import { TooltipProps } from '../tooltip'; import { PaginationProps } from '../pagination'; export interface RenderEllipsisCellParams { - cellNode: any; + cellNode: ReactNode; tableElm?: HTMLDivElement; columnLength: number; classPrefix?: string; diff --git a/src/table/EditableCell.tsx b/src/table/EditableCell.tsx index 062dda0d28..db3d18b3eb 100644 --- a/src/table/EditableCell.tsx +++ b/src/table/EditableCell.tsx @@ -128,7 +128,7 @@ const EditableCell = (props: EditableCellProps) => { }, [col]); const validateEdit = (trigger: 'self' | 'parent', newVal: any) => - new Promise((resolve) => { + new Promise((resolve) => { const params: PrimaryTableRowValidateContext = { result: [ { diff --git a/src/table/EnhancedTable.tsx b/src/table/EnhancedTable.tsx index 135ecfedf4..0c2316ea8b 100644 --- a/src/table/EnhancedTable.tsx +++ b/src/table/EnhancedTable.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, useImperativeHandle, useRef } from 'react'; +import React, { RefAttributes, forwardRef, useImperativeHandle, useRef } from 'react'; import get from 'lodash/get'; import PrimaryTable from './PrimaryTable'; import { PrimaryTableCol, TableRowData, DragSortContext, TdPrimaryTableProps } from './type'; @@ -106,7 +106,5 @@ const EnhancedTable = forwardRef((props, EnhancedTable.displayName = 'EnhancedTable'; export default EnhancedTable as ( - props: EnhancedTableProps & { - ref?: React.Ref; - }, + props: EnhancedTableProps & RefAttributes, ) => React.ReactElement; diff --git a/src/table/PrimaryTable.tsx b/src/table/PrimaryTable.tsx index e619a6b4f3..a62b8f0348 100644 --- a/src/table/PrimaryTable.tsx +++ b/src/table/PrimaryTable.tsx @@ -1,4 +1,4 @@ -import React, { useRef, forwardRef, useImperativeHandle } from 'react'; +import React, { useRef, forwardRef, useImperativeHandle, ReactNode, RefAttributes } from 'react'; import get from 'lodash/get'; import classNames from 'classnames'; import BaseTable from './BaseTable'; @@ -20,12 +20,14 @@ import { StyledProps } from '../common'; import { useEditableRow } from './hooks/useEditableRow'; import { primaryTableDefaultProps } from './defaultProps'; import { CheckboxGroupValue } from '../checkbox'; +import useDefaultProps from '../hooks/useDefaultProps'; export { BASE_TABLE_ALL_EVENTS } from './BaseTable'; export interface TPrimaryTableProps extends PrimaryTableProps, StyledProps {} -const PrimaryTable = forwardRef((props, ref) => { +const PrimaryTable = forwardRef((originalProps, ref) => { + const props = useDefaultProps(originalProps, primaryTableDefaultProps); const { columns, columnController, editableRowKeys, style, className } = props; const primaryTableRef = useRef(null); const innerPagination = useRef(props.pagination); @@ -207,7 +209,12 @@ const PrimaryTable = forwardRef((props, ref } }; - function formatNode(api: string, renderInnerNode: Function, condition: boolean, extra?: { reverse?: boolean }) { + function formatNode( + api: string, + renderInnerNode: () => ReactNode, + condition: boolean, + extra?: { reverse?: boolean }, + ) { if (!condition) return props[api]; const innerNode = renderInnerNode(); const propsNode = props[api]; @@ -271,10 +278,6 @@ const PrimaryTable = forwardRef((props, ref PrimaryTable.displayName = 'PrimaryTable'; -PrimaryTable.defaultProps = primaryTableDefaultProps; - export default PrimaryTable as ( - props: PrimaryTableProps & { - ref?: React.Ref; - }, + props: PrimaryTableProps & RefAttributes, ) => React.ReactElement; diff --git a/src/table/TBody.tsx b/src/table/TBody.tsx index e744ed245d..01b00220a5 100644 --- a/src/table/TBody.tsx +++ b/src/table/TBody.tsx @@ -1,4 +1,4 @@ -import React, { MutableRefObject, useMemo } from 'react'; +import React, { CSSProperties, MutableRefObject, ReactNode, useMemo } from 'react'; import camelCase from 'lodash/camelCase'; import get from 'lodash/get'; import pick from 'lodash/pick'; @@ -123,7 +123,7 @@ export default function TBody(props: TableBodyProps) { const getTRNodeList = () => { if (isSkipSnapsMapNotFinish) return null; - const trNodeList = []; + const trNodeList: ReactNode[] = []; const properties = [ 'classPrefix', 'ellipsisOverlayClassName', @@ -182,13 +182,13 @@ export default function TBody(props: TableBodyProps) { // 垫上隐藏的 tr 元素高度 const translate = `translateY(${virtualConfig.translateY}px)`; - const posStyle = virtualConfig.isVirtualScroll - ? { + const posStyle: CSSProperties = virtualConfig.isVirtualScroll + ? ({ transform: translate, msTransform: translate, MozTransform: translate, WebkitTransform: translate, - } + } as CSSProperties) : undefined; const list = ( diff --git a/src/table/TFoot.tsx b/src/table/TFoot.tsx index b8ef9dcf5c..5356397ea0 100644 --- a/src/table/TFoot.tsx +++ b/src/table/TFoot.tsx @@ -27,7 +27,7 @@ export interface TFootProps { export default function TFoot(props: TFootProps) { const { footData, columns, rowKey, footerSummary } = props; - const tfooterRef = useRef(); + const tfooterRef = useRef(); const classnames = useClassName(); const { skipSpansMap } = useRowspanAndColspan(footData, columns, rowKey, props.rowspanAndColspanInFooter); diff --git a/src/table/THead.tsx b/src/table/THead.tsx index e501f20035..dae5c9bd2b 100644 --- a/src/table/THead.tsx +++ b/src/table/THead.tsx @@ -36,8 +36,15 @@ export interface TheadProps { columnResizeParams?: { resizeLineRef: MutableRefObject; resizeLineStyle: CSSProperties; - onColumnMouseover: (e: MouseEvent, col: BaseTableCol) => void; - onColumnMousedown: (e: MouseEvent, col: BaseTableCol, index: number) => void; + onColumnMouseover: ( + e: React.MouseEvent, + col: BaseTableCol, + ) => void; + onColumnMousedown: ( + e: React.MouseEvent, + col: BaseTableCol, + index: number, + ) => void; }; } @@ -72,8 +79,8 @@ export default function THead(props: TheadProps) { return map; }, [props.thList]); - const getTableNode = (thead: HTMLElement) => { - let parent = thead; + const getTableNode = (thead: HTMLTableSectionElement) => { + let parent: HTMLElement = thead; while (parent) { parent = parent.parentNode as HTMLElement; if (parent?.classList?.contains(`${props.classPrefix}-table`)) { @@ -85,7 +92,7 @@ export default function THead(props: TheadProps) { const renderThNodeList = (rowAndColFixedPosition: RowAndColFixedPosition, thWidthList: TheadProps['thWidthList']) => { // thBorderMap: rowspan 会影响 tr > th 是否为第一列表头,从而影响边框 - const thBorderMap = new Map(); + const thBorderMap = new Map, boolean>(); const thRowspanAndColspan = props.spansAndLeafNodes.rowspanAndColspanMap; return props.thList.map((row, rowIndex) => { const thRow = row.map((col: TableColumns[0], index: number) => { @@ -130,19 +137,19 @@ export default function THead(props: TheadProps) { const resizeColumnListener = props.resizable || !canDragSort ? { - onMouseDown: (e) => { + onMouseDown: (e: React.MouseEvent) => { if (props.resizable) { columnResizeParams?.onColumnMousedown?.(e, col, index); } if (!canDragSort) { const timer = setTimeout(() => { - const thList = theadRef.current.querySelectorAll('th'); + const thList = theadRef.current.querySelectorAll('th'); thList[index]?.removeAttribute('draggable'); clearTimeout(timer); }, 10); } }, - onMouseMove: (e) => { + onMouseMove: (e: React.MouseEvent) => { props.resizable && columnResizeParams?.onColumnMouseover?.(e, col); }, } diff --git a/src/table/hooks/useColumnResize.tsx b/src/table/hooks/useColumnResize.tsx index 0df3ddb807..fb7d14ebd5 100644 --- a/src/table/hooks/useColumnResize.tsx +++ b/src/table/hooks/useColumnResize.tsx @@ -5,7 +5,7 @@ * - 当表格内容没有超出时,即没有出现横向滚动条时,此时认为表格有足够的列宽呈现内容,修改宽度为相邻宽度调整 * - 当表格内容超出,出现横向滚动条时,会自动调整当前列宽和表格总列宽,不影响相邻列宽 */ -import { useState, useRef, MutableRefObject, CSSProperties, useEffect } from 'react'; +import React, { useState, useRef, MutableRefObject, CSSProperties, useEffect } from 'react'; import isNumber from 'lodash/isNumber'; import { BaseTableCol, TableRowData, TdBaseTableProps } from '../type'; import { on, off } from '../../_util/dom'; @@ -108,7 +108,10 @@ export default function useColumnResize(params: { // 表格列宽拖拽事件 // 只在表头显示拖拽图标 - const onColumnMouseover = (e: MouseEvent, col: BaseTableCol) => { + const onColumnMouseover = ( + e: React.MouseEvent, + col: BaseTableCol, + ) => { // calculate mouse cursor before drag start if (!resizeLineRef.current || resizeLineParams.isDragging || !e.target) return; const target = (e.target as HTMLElement).closest('th'); @@ -200,7 +203,11 @@ export default function useColumnResize(params: { }; // 调整表格列宽 - const onColumnMousedown = (e: MouseEvent, col: BaseTableCol, index: number) => { + const onColumnMousedown = ( + e: React.MouseEvent, + col: BaseTableCol, + index: number, + ) => { if (!resizeLineParams.draggingCol) return; const target = resizeLineParams.draggingCol; const targetBoundRect = target.getBoundingClientRect(); @@ -217,7 +224,7 @@ export default function useColumnResize(params: { // 初始化 resizeLine 标记线 if (resizeLineRef?.current) { - const styles = { ...resizeLineStyle }; + const styles: CSSProperties = { ...resizeLineStyle }; styles.display = 'block'; styles.height = `${tableBoundRect.bottom - targetBoundRect.top}px`; styles.left = `${resizeLinePos}px`; diff --git a/src/table/hooks/useFixed.ts b/src/table/hooks/useFixed.ts index 53e97ec683..0b152dcf15 100644 --- a/src/table/hooks/useFixed.ts +++ b/src/table/hooks/useFixed.ts @@ -442,7 +442,6 @@ export default function useFixed( finalColumns: BaseTableCol[] = [], preFinalColumns: BaseTableCol[] = [], ) => { - console.log(preFinalColumns); const finalColKeys = finalColumns.map((t) => t.colKey); const preColKeys = preFinalColumns.map((t) => t.colKey); if (finalColKeys.length < preColKeys.length) { diff --git a/src/table/utils.ts b/src/table/utils.ts index 76d9c8d967..32fb09024d 100644 --- a/src/table/utils.ts +++ b/src/table/utils.ts @@ -85,7 +85,7 @@ export function formatClassNames( for (let i = 0, len = classes.length; i < len; i++) { const cls = classes[i]; if (isFunction(cls)) { - arr.push(cls(params)); + arr.push(cls(params as CellData)); } else { arr.push(cls); } diff --git a/src/tabs/TabBar.tsx b/src/tabs/TabBar.tsx index 55045eaed4..e240d5f14a 100644 --- a/src/tabs/TabBar.tsx +++ b/src/tabs/TabBar.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, CSSProperties, useCallback, useRef } from 'react'; +import React, { useEffect, useState, CSSProperties, useRef } from 'react'; import classNames from 'classnames'; import useConfig from '../hooks/useConfig'; import useMutationObserver from '../_util/useMutationObserver'; @@ -14,13 +14,12 @@ const TabBar: React.FC = (props) => { const { classPrefix } = useConfig(); const [barStyle, setBarStyle] = useState({}); const tabsClassPrefix = `${classPrefix}-tabs`; - const currentActiveIdRef = useRef(activeId); useEffect(() => { currentActiveIdRef.current = activeId; }, [activeId]); - const computeStyle = useCallback(() => { + const computeStyle = React.useCallback(() => { const isHorizontal = ['bottom', 'top'].includes(tabPosition); const transformPosition = isHorizontal ? 'translateX' : 'translateY'; const itemProp = isHorizontal ? 'width' : 'height'; @@ -29,26 +28,20 @@ const TabBar: React.FC = (props) => { let offset = 0; if (containerRef.current) { - const itemsRef = containerRef.current.querySelectorAll?.(`.${tabsClassPrefix}__nav-item`); - if (itemsRef.length - 1 >= currentActiveIdRef.current) { + const itemsRef = containerRef.current?.querySelectorAll(`.${tabsClassPrefix}__nav-item`); + if (itemsRef.length - 1 >= (currentActiveIdRef.current as number)) { itemsRef.forEach((item, itemIndex) => { - if (itemIndex < currentActiveIdRef.current) { + if (itemIndex < (currentActiveIdRef.current as number)) { offset += Number(getComputedStyle(item)[itemProp].replace('px', '')); } }); const computedItem = itemsRef[currentActiveIdRef.current]; if (!computedItem) { - setBarStyle({ - transform: `${transformPosition}(${0}px)`, - [barBorderProp]: 0, - }); + setBarStyle({ transform: `${transformPosition}(${0}px)`, [barBorderProp]: 0 }); return; } const itemPropValue = getComputedStyle(computedItem)[itemProp]; - setBarStyle({ - transform: `${transformPosition}(${offset}px)`, - [barBorderProp]: itemPropValue || 0, - }); + setBarStyle({ transform: `${transformPosition}(${offset}px)`, [barBorderProp]: itemPropValue || 0 }); } } }, [currentActiveIdRef, containerRef, tabPosition, tabsClassPrefix]); @@ -60,8 +53,8 @@ const TabBar: React.FC = (props) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [tabPosition, activeId, containerRef.current]); - const handleMutationObserver = useCallback( - (mutations) => { + const handleMutationObserver = React.useCallback( + (mutations: MutationRecord[]) => { mutations.forEach((mutation) => { if (mutation.type === 'characterData') { computeStyle(); diff --git a/src/tabs/TabNav.tsx b/src/tabs/TabNav.tsx index 23cdf8b28d..2b52d1fd07 100644 --- a/src/tabs/TabNav.tsx +++ b/src/tabs/TabNav.tsx @@ -14,6 +14,7 @@ import TabBar from './TabBar'; import tabBase from '../_common/js/tabs/base'; import useGlobalIcon from '../hooks/useGlobalIcon'; import type { DragSortInnerProps } from '../_util/useDragSorter'; +import parseTNode from '../_util/parseTNode'; const { moveActiveTabIntoView, calcScrollLeft, scrollToLeft, scrollToRight, calculateCanToLeft, calculateCanToRight } = tabBase; @@ -39,6 +40,7 @@ const TabNav: React.FC = (props) => { onChange = noop, activeValue, children, + action, getDragProps, } = props; @@ -272,7 +274,17 @@ const TabNav: React.FC = (props) => {
) : null} - {props.action} + {action ? ( +
+ {parseTNode(action)} +
+ ) : null}
= (props) => { dragProps, } = props; - const { CloseIcon } = useGlobalIcon({ - CloseIcon: TdCloseIcon, - }); + const { CloseIcon } = useGlobalIcon({ CloseIcon: TdCloseIcon }); const isCard = theme === 'card'; diff --git a/src/tabs/TabPanel.tsx b/src/tabs/TabPanel.tsx index 091d2e75c3..cdcb8141de 100644 --- a/src/tabs/TabPanel.tsx +++ b/src/tabs/TabPanel.tsx @@ -4,6 +4,7 @@ import { TdTabPanelProps } from './type'; import { StyledProps } from '../common'; import { useTabClass } from './useTabClass'; import { tabPanelDefaultProps } from './defaultProps'; +import useDefaultProps from '../hooks/useDefaultProps'; export interface TabPanelProps extends TdTabPanelProps, StyledProps { children?: React.ReactNode; @@ -11,9 +12,7 @@ export interface TabPanelProps extends TdTabPanelProps, StyledProps { const TabPanel: React.FC = (props) => { const { tdTabPanelClassPrefix } = useTabClass(); - - const { className, style } = props; - + const { className, style } = useDefaultProps(props, tabPanelDefaultProps); return (
{props.children || props.panel} @@ -22,6 +21,5 @@ const TabPanel: React.FC = (props) => { }; TabPanel.displayName = 'TabPanel'; -TabPanel.defaultProps = tabPanelDefaultProps; export default TabPanel; diff --git a/src/tabs/Tabs.tsx b/src/tabs/Tabs.tsx index 507c834be0..c11095e9bf 100644 --- a/src/tabs/Tabs.tsx +++ b/src/tabs/Tabs.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect } from 'react'; import classNames from 'classnames'; import { TabValue, TdTabsProps } from './type'; import forwardRefWithStatics from '../_util/forwardRefWithStatics'; @@ -8,15 +8,27 @@ import TabPanel from './TabPanel'; import { StyledProps } from '../common'; import { tabsDefaultProps } from './defaultProps'; import useDragSorter from '../_util/useDragSorter'; +import useDefaultProps from '../hooks/useDefaultProps'; export interface TabsProps extends TdTabsProps, StyledProps { children?: React.ReactNode; } const Tabs = forwardRefWithStatics( - (props: TabsProps, ref) => { - const { children, list, placement, onRemove, value: tabValue, onChange, className, style } = props; - let { defaultValue } = props; + (originalProps: TabsProps, ref: React.Ref) => { + const props = useDefaultProps(originalProps, tabsDefaultProps); + const { + defaultValue, + children, + list, + placement, + value: tabValue, + dragSort, + className, + style, + onRemove, + onChange, + } = props; // 样式工具引入 const { tdTabsClassPrefix, tdTabsClassGenerator, tdClassGenerator } = useTabClass(); @@ -24,21 +36,21 @@ const Tabs = forwardRefWithStatics( const { getDragProps } = useDragSorter({ ...props, - sortOnDraggable: props.dragSort, + sortOnDraggable: dragSort, onDragOverCheck: { x: true, targetClassNameRegExp: new RegExp(targetClassNameRegExpStr), }, }); - const memoChildren = useMemo(() => { + const memoChildren = React.useMemo(() => { if (!list || list.length === 0) { return children; } - return list.map((panelProps) => ); + return list.map((panelProps) => ); }, [children, list]); - const itemList = React.Children.map(memoChildren, (child: any) => { + const itemList = React.Children.map(memoChildren, (child: React.ReactElement) => { if (child && child.type === TabPanel) { return child.props; } @@ -46,46 +58,66 @@ const Tabs = forwardRefWithStatics( }); // 当未设置默认值时,默认选中第一个。 - if (defaultValue === undefined && Array.isArray(itemList) && itemList.length !== 0) { - defaultValue = itemList[0].value; - } - - const [value, setValue] = useState(defaultValue); + const [value, setValue] = React.useState( + defaultValue === undefined && Array.isArray(itemList) && itemList.length !== 0 ? itemList[0].value : defaultValue, + ); useEffect(() => { - tabValue !== undefined && setValue(tabValue); + if (tabValue !== undefined) { + setValue(tabValue); + } }, [tabValue]); - const handleChange = (v) => { - if (tabValue === undefined) { - setValue(v); - } - onChange?.(v); - }; + const handleChange = React.useCallback( + (v: TabValue) => { + if (tabValue === undefined) { + setValue(v); + } + onChange?.(v); + }, + [tabValue, onChange], + ); - const handleClickTab = (v) => { - if (tabValue === undefined) { - setValue(v); - } - }; + const handleClickTab = React.useCallback( + (v: TabValue) => { + if (tabValue === undefined) { + setValue(v); + } + }, + [tabValue], + ); - const renderHeader = () => ( -
- -
+ const headerNode = React.useMemo( + () => ( +
+ +
+ ), + [ + props, + getDragProps, + value, + onRemove, + itemList, + handleClickTab, + handleChange, + placement, + tdTabsClassGenerator, + tdClassGenerator, + ], ); return (
- {placement !== 'bottom' ? renderHeader() : null} + {placement !== 'bottom' ? headerNode : null}
{React.Children.map(memoChildren, (child: any) => { if (child && child.type === TabPanel) { @@ -99,7 +131,7 @@ const Tabs = forwardRefWithStatics( return null; })}
- {placement === 'bottom' ? renderHeader() : null} + {placement === 'bottom' ? headerNode : null}
); }, @@ -107,6 +139,5 @@ const Tabs = forwardRefWithStatics( ); Tabs.displayName = 'Tabs'; -Tabs.defaultProps = tabsDefaultProps; export default Tabs; diff --git a/src/tabs/__tests__/tabs.test.tsx b/src/tabs/__tests__/tabs.test.tsx index 0804841533..59c7ba35a2 100644 --- a/src/tabs/__tests__/tabs.test.tsx +++ b/src/tabs/__tests__/tabs.test.tsx @@ -340,4 +340,26 @@ describe('Tabs 组件测试', () => { expect(onDragSort.mock.calls[0][0].target.value).toEqual('vue'); expect(container.querySelectorAll('.t-tabs__nav-item-text-wrapper').item(0).firstChild.nodeValue).toEqual('react'); }); + + test('Tabs action', () => { + const { container } = render( + , + ); + + expect(container.querySelector('.t-tabs__nav-action')).not.toBeNull(); + expect(container.querySelector('.t-tabs__nav-action')).toBeInTheDocument(); + }); }); diff --git a/src/tag-input/TagInput.tsx b/src/tag-input/TagInput.tsx index 900963af8b..24b1de9fa3 100644 --- a/src/tag-input/TagInput.tsx +++ b/src/tag-input/TagInput.tsx @@ -13,10 +13,12 @@ import useHover from './useHover'; import useControlled from '../hooks/useControlled'; import { StyledProps } from '../common'; import { tagInputDefaultProps } from './defaultProps'; +import useDefaultProps from '../hooks/useDefaultProps'; export interface TagInputProps extends TdTagInputProps, StyledProps {} -const TagInput = forwardRef((props: TagInputProps, ref: React.RefObject) => { +const TagInput = forwardRef((originalProps, ref) => { + const props = useDefaultProps(originalProps, tagInputDefaultProps); const { classPrefix: prefix } = useConfig(); const { CloseCircleFilledIcon } = useGlobalIcon({ CloseCircleFilledIcon: TdCloseCircleFilledIcon, @@ -183,6 +185,5 @@ const TagInput = forwardRef((props: TagInputProps, ref: React.RefObject) => { +const CheckTag = forwardRef((originalProps, ref) => { + const props = useDefaultProps(originalProps, checkTagDefaultProps); const { value, content, @@ -101,6 +103,5 @@ const CheckTag = forwardRef((props: CheckTagProps, ref: React.Ref { +const CheckTagGroup: React.FC = (originalProps) => { + const props = useDefaultProps(originalProps, checkTagGroupDefaultProps); const { options, onChange } = props; const { classPrefix } = useConfig(); const componentName = `${classPrefix}-check-tag-group`; @@ -54,6 +56,5 @@ const CheckTagGroup = (props: CheckTagGroupProps) => { }; CheckTagGroup.displayName = 'CheckTagGroup'; -CheckTagGroup.defaultProps = checkTagGroupDefaultProps; export default CheckTagGroup; diff --git a/src/tag/Tag.tsx b/src/tag/Tag.tsx index 987f501907..d7aff2f979 100644 --- a/src/tag/Tag.tsx +++ b/src/tag/Tag.tsx @@ -1,12 +1,15 @@ -import React, { FocusEvent, forwardRef } from 'react'; +import React, { ForwardRefRenderFunction, FocusEvent, forwardRef, useMemo } from 'react'; import classNames from 'classnames'; import { CloseIcon as TdCloseIcon } from 'tdesign-icons-react'; +import tinycolor from 'tinycolor2'; + import noop from '../_util/noop'; import useConfig from '../hooks/useConfig'; import useGlobalIcon from '../hooks/useGlobalIcon'; import { StyledProps } from '../common'; import { TdTagProps } from './type'; import { tagDefaultProps } from './defaultProps'; +import useDefaultProps from '../hooks/useDefaultProps'; /** * Tag 组件支持的属性。 @@ -21,7 +24,8 @@ export interface TagProps extends TdTagProps, StyledProps { onBlur?: (e: FocusEvent) => void; } -export function TagFunction(props: TagProps, ref: React.Ref) { +export const TagFunction: ForwardRefRenderFunction = (originalProps, ref) => { + const props = useDefaultProps(originalProps, tagDefaultProps); const { theme, size, @@ -37,6 +41,7 @@ export function TagFunction(props: TagProps, ref: React.Ref) { style, disabled, children, + color, ...otherTagProps } = props; @@ -83,6 +88,32 @@ export function TagFunction(props: TagProps, ref: React.Ref) { })(); const titleAttribute = title ? { title } : undefined; + const getTagStyle = useMemo(() => { + if (!color) return style; + const luminance = tinycolor(color).getLuminance(); + + const calculatedStyle = style || {}; + + calculatedStyle.color = luminance > 0.5 ? 'black' : 'white'; + if (variant === 'outline' || variant === 'light-outline') { + calculatedStyle.borderColor = color; + } + + if (variant !== 'outline') { + const getLightestShade = () => { + const { r, g, b } = tinycolor(color).toRgb(); + // alpha 0.1 is designed by @wen1kang + return `rgba(${r}, ${g}, ${b}, 0.1)`; + }; + + calculatedStyle.backgroundColor = variant === 'dark' ? color : getLightestShade(); + } + if (variant !== 'dark') { + calculatedStyle.color = color; + } + return calculatedStyle; + }, [color, variant, style]); + const tag = (
) { if (disabled) return; onClick({ e }); }} - style={maxWidth ? { maxWidth: typeof maxWidth === 'number' ? `${maxWidth}px` : maxWidth, ...style } : style} + style={ + maxWidth ? { maxWidth: typeof maxWidth === 'number' ? `${maxWidth}px` : maxWidth, ...getTagStyle } : getTagStyle + } {...otherTagProps} > <> @@ -105,11 +138,10 @@ export function TagFunction(props: TagProps, ref: React.Ref) { ); return tag; -} +}; export const Tag = forwardRef(TagFunction); Tag.displayName = 'Tag'; -Tag.defaultProps = tagDefaultProps; export default Tag; diff --git a/src/tag/_example/custom-color.jsx b/src/tag/_example/custom-color.jsx new file mode 100644 index 0000000000..4d32f506b6 --- /dev/null +++ b/src/tag/_example/custom-color.jsx @@ -0,0 +1,27 @@ +import React, { useState } from 'react'; +import { Space, Tag, ColorPicker } from 'tdesign-react'; + +export default function CustomColor() { + const [selfDefinedColor, changeSelfDefinedColor] = useState('#0052D9'); + return ( + + + changeSelfDefinedColor(v)} /> + + + + default + + + light + + + outline + + + light-outline + + + + ); +} diff --git a/src/tag/index.ts b/src/tag/index.ts index 8403c65abf..b0719edfa1 100644 --- a/src/tag/index.ts +++ b/src/tag/index.ts @@ -2,8 +2,6 @@ import { TagFunction } from './Tag'; import _CheckTag from './CheckTag'; import _CheckTagGroup from './CheckTagGroup'; import forwardRefWithStatics from '../_util/forwardRefWithStatics'; -import { tagDefaultProps } from './defaultProps'; - import './style/index.js'; export type { TagProps } from './Tag'; @@ -19,7 +17,6 @@ export const Tag = forwardRefWithStatics(TagFunction, { }); Tag.displayName = 'Tag'; -Tag.defaultProps = tagDefaultProps; export const CheckTag = _CheckTag; export const CheckTagGroup = _CheckTagGroup; diff --git a/src/tag/tag.en-US.md b/src/tag/tag.en-US.md index 378dc44c58..9540a520bc 100644 --- a/src/tag/tag.en-US.md +++ b/src/tag/tag.en-US.md @@ -1,14 +1,16 @@ :: BASE_DOC :: ## API + ### Tag Props name | type | default | description | required -- | -- | -- | -- | -- -className | String | - | 类名 | N -style | Object | - | 样式,Typescript:`React.CSSProperties` | N +className | String | - | className of component | N +style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N children | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N closable | Boolean | false | \- | N +color | String | - | self-defined tag color | N content | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N disabled | Boolean | false | \- | N icon | TElement | undefined | Typescript:`TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N @@ -20,12 +22,13 @@ variant | String | dark | options: dark/light/outline/light-outline | N onClick | Function | | Typescript:`(context: { e: MouseEvent }) => void`
| N onClose | Function | | Typescript:`(context: { e: MouseEvent }) => void`
| N + ### CheckTag Props name | type | default | description | required -- | -- | -- | -- | -- -className | String | - | 类名 | N -style | Object | - | 样式,Typescript:`React.CSSProperties` | N +className | String | - | className of component | N +style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N checked | Boolean | - | \- | N defaultChecked | Boolean | - | uncontrolled property | N checkedProps | Object | - | used to set checked tag props。Typescript:`TdTagProps` | N @@ -38,12 +41,13 @@ value | String / Number | - | tag unique key | N onChange | Function | | Typescript:`(checked: boolean, context: CheckTagChangeContext) => void`
[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/tag/type.ts)。
`interface CheckTagChangeContext { e: MouseEvent \| KeyboardEvent; value: string \| number }`
| N onClick | Function | | Typescript:`(context: { e: MouseEvent }) => void`
| N + ### CheckTagGroup Props name | type | default | description | required -- | -- | -- | -- | -- -className | String | - | 类名 | N -style | Object | - | 样式,Typescript:`React.CSSProperties` | N +className | String | - | className of component | N +style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N checkedProps | Object | - | used to set checked tag props。Typescript:`TdTagProps` | N multiple | Boolean | false | allow to select multiple tags | N options | Array | - | tag list。Typescript:`CheckTagGroupOption[]` `interface CheckTagGroupOption extends TdCheckTagProps { label: string \| TNode; value: string \| number }`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts)。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/tag/type.ts) | N diff --git a/src/tag/tag.md b/src/tag/tag.md index ea477a8df0..2c065b96d7 100644 --- a/src/tag/tag.md +++ b/src/tag/tag.md @@ -1,14 +1,16 @@ :: BASE_DOC :: ## API + ### Tag Props -名称 | 类型 | 默认值 | 说明 | 必传 +名称 | 类型 | 默认值 | 描述 | 必传 -- | -- | -- | -- | -- className | String | - | 类名 | N style | Object | - | 样式,TS 类型:`React.CSSProperties` | N children | TNode | - | 组件子元素,同 `content`。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N closable | Boolean | false | 标签是否可关闭 | N +color | String | - | 自定义标签颜色 | N content | TNode | - | 组件子元素。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N disabled | Boolean | false | 标签禁用态,失效标签不能触发事件。默认风格(theme=default)才有禁用态 | N icon | TElement | undefined | 标签中的图标,可自定义图标呈现。TS 类型:`TNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N @@ -20,9 +22,10 @@ variant | String | dark | 标签风格变体。可选项:dark/light/outline/li onClick | Function | | TS 类型:`(context: { e: MouseEvent }) => void`
点击时触发 | N onClose | Function | | TS 类型:`(context: { e: MouseEvent }) => void`
如果关闭按钮存在,点击关闭按钮时触发 | N + ### CheckTag Props -名称 | 类型 | 默认值 | 说明 | 必传 +名称 | 类型 | 默认值 | 描述 | 必传 -- | -- | -- | -- | -- className | String | - | 类名 | N style | Object | - | 样式,TS 类型:`React.CSSProperties` | N @@ -38,9 +41,10 @@ value | String / Number | - | 标签唯一标识,一般用于标签组场景 onChange | Function | | TS 类型:`(checked: boolean, context: CheckTagChangeContext) => void`
状态切换时触发。[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/tag/type.ts)。
`interface CheckTagChangeContext { e: MouseEvent \| KeyboardEvent; value: string \| number }`
| N onClick | Function | | TS 类型:`(context: { e: MouseEvent }) => void`
点击标签时触发 | N + ### CheckTagGroup Props -名称 | 类型 | 默认值 | 说明 | 必传 +名称 | 类型 | 默认值 | 描述 | 必传 -- | -- | -- | -- | -- className | String | - | 类名 | N style | Object | - | 样式,TS 类型:`React.CSSProperties` | N diff --git a/src/tag/type.ts b/src/tag/type.ts index 71729aee00..db009a7052 100644 --- a/src/tag/type.ts +++ b/src/tag/type.ts @@ -17,6 +17,11 @@ export interface TdTagProps { * @default false */ closable?: boolean; + /** + * 自定义标签颜色 + * @default '' + */ + color?: string; /** * 组件子元素 */ diff --git a/src/textarea/Textarea.tsx b/src/textarea/Textarea.tsx index 1f1403ea41..4fcef82859 100644 --- a/src/textarea/Textarea.tsx +++ b/src/textarea/Textarea.tsx @@ -8,6 +8,7 @@ import useControlled from '../hooks/useControlled'; import { getCharacterLength, getUnicodeLength, limitUnicodeMaxLength } from '../_common/js/utils/helper'; import calcTextareaHeight from '../_common/js/utils/calcTextareaHeight'; import { textareaDefaultProps } from './defaultProps'; +import useDefaultProps from '../hooks/useDefaultProps'; export interface TextareaProps extends Omit< @@ -21,7 +22,8 @@ export interface TextareaRefInterface extends React.RefObject { textareaElement: HTMLTextAreaElement; } -const Textarea = forwardRef((props: TextareaProps, ref: TextareaRefInterface) => { +const Textarea = forwardRef((originalProps: TextareaProps, ref: TextareaRefInterface) => { + const props = useDefaultProps(originalProps, textareaDefaultProps); const { disabled, maxlength, @@ -201,6 +203,5 @@ const Textarea = forwardRef((props: TextareaProps, ref: TextareaRefInterface) => }); Textarea.displayName = 'Textarea'; -Textarea.defaultProps = textareaDefaultProps; export default Textarea; diff --git a/src/time-picker/TimePicker.tsx b/src/time-picker/TimePicker.tsx index d6af49b0c8..db2ca6d9b0 100644 --- a/src/time-picker/TimePicker.tsx +++ b/src/time-picker/TimePicker.tsx @@ -21,6 +21,7 @@ import { timePickerDefaultProps } from './defaultProps'; import type { StyledProps } from '../common'; import type { TdTimePickerProps } from './type'; +import useDefaultProps from '../hooks/useDefaultProps'; // https://github.com/iamkun/dayjs/issues/1552 dayjs.extend(customParseFormat); @@ -28,7 +29,8 @@ dayjs.extend(customParseFormat); export interface TimePickerProps extends TdTimePickerProps, StyledProps {} const TimePicker = forwardRefWithStatics( - (props: TimePickerProps, ref: Ref) => { + (originalProps: TimePickerProps, ref: Ref) => { + const props = useDefaultProps(originalProps, timePickerDefaultProps); const TEXT_CONFIG = useTimePickerTextConfig(); const { allowInput, @@ -147,6 +149,5 @@ const TimePicker = forwardRefWithStatics( ); TimePicker.displayName = 'TimePicker'; -TimePicker.defaultProps = timePickerDefaultProps; export default TimePicker; diff --git a/src/time-picker/TimeRangePicker.tsx b/src/time-picker/TimeRangePicker.tsx index 28844b4594..3c49318a63 100644 --- a/src/time-picker/TimeRangePicker.tsx +++ b/src/time-picker/TimeRangePicker.tsx @@ -16,12 +16,14 @@ import { TIME_PICKER_EMPTY } from '../_common/js/time-picker/const'; import { TdTimeRangePickerProps, TimeRangeValue, TimeRangePickerPartial } from './type'; import { StyledProps } from '../common'; import { timeRangePickerDefaultProps } from './defaultProps'; +import useDefaultProps from '../hooks/useDefaultProps'; export interface TimeRangePickerProps extends TdTimeRangePickerProps, StyledProps {} const defaultArrVal = [undefined, undefined]; -const TimeRangePicker: FC = (props) => { +const TimeRangePicker: FC = (originalProps) => { + const props = useDefaultProps(originalProps, timeRangePickerDefaultProps); const TEXT_CONFIG = useTimePickerTextConfig(); const { @@ -172,6 +174,5 @@ const TimeRangePicker: FC = (props) => { }; TimeRangePicker.displayName = 'TimeRangePicker'; -TimeRangePicker.defaultProps = timeRangePickerDefaultProps; export default TimeRangePicker; diff --git a/src/timeline/useAlign.ts b/src/timeline/useAlign.ts index 446e89f362..5074858314 100644 --- a/src/timeline/useAlign.ts +++ b/src/timeline/useAlign.ts @@ -6,7 +6,7 @@ const DefaultAlign = { horizontal: ['top', 'bottom'], }; -export const useAlign = (align, layout = 'vertical') => +export const useAlign = (align: string, layout = 'vertical') => useMemo(() => { let renderAlign = layout === 'vertical' ? 'left' : 'top'; if (layout === 'vertical' && align) { diff --git a/src/tooltip/Tooltip.tsx b/src/tooltip/Tooltip.tsx index 6cfd516adf..176537519b 100644 --- a/src/tooltip/Tooltip.tsx +++ b/src/tooltip/Tooltip.tsx @@ -1,13 +1,15 @@ import React, { forwardRef, useState, useEffect, useRef, useImperativeHandle } from 'react'; import classNames from 'classnames'; -import Popup, { PopupVisibleChangeContext } from '../popup'; +import Popup, { PopupRef, PopupVisibleChangeContext } from '../popup'; import useConfig from '../hooks/useConfig'; import { TdTooltipProps } from './type'; import { tooltipDefaultProps } from './defaultProps'; +import useDefaultProps from '../hooks/useDefaultProps'; export type TooltipProps = TdTooltipProps; -const Tooltip = forwardRef((props: TdTooltipProps, ref) => { +const Tooltip = forwardRef, TdTooltipProps>((originalProps, ref) => { + const props = useDefaultProps(originalProps, tooltipDefaultProps); const { theme, showArrow, @@ -22,7 +24,7 @@ const Tooltip = forwardRef((props: TdTooltipProps, ref) => { const { classPrefix } = useConfig(); const [timeUp, setTimeUp] = useState(false); - const popupRef = useRef(null); + const popupRef = useRef(null); const timerRef = useRef(null); const toolTipClass = classNames( `${classPrefix}-tooltip`, @@ -51,7 +53,7 @@ const Tooltip = forwardRef((props: TdTooltipProps, ref) => { }, [duration, timeUp]); useImperativeHandle(ref, () => ({ - ...((popupRef.current || {}) as any), + ...(popupRef.current || {}), })); return ( @@ -70,6 +72,5 @@ const Tooltip = forwardRef((props: TdTooltipProps, ref) => { }); Tooltip.displayName = 'Tooltip'; -Tooltip.defaultProps = tooltipDefaultProps; export default Tooltip; diff --git a/src/tooltip/TooltipLite.tsx b/src/tooltip/TooltipLite.tsx index e25fe9277e..1f39adde79 100644 --- a/src/tooltip/TooltipLite.tsx +++ b/src/tooltip/TooltipLite.tsx @@ -10,6 +10,7 @@ import getPosition from '../_common/js/utils/getPosition'; import { TdTooltipLiteProps } from './type'; import { tooltipLiteDefaultProps } from './defaultProps'; import { getTransitionParams } from '../popup/utils/transition'; +import useDefaultProps from '../hooks/useDefaultProps'; export interface TooltipLiteProps extends TdTooltipLiteProps, StyledProps { children?: ReactNode; @@ -17,7 +18,8 @@ export interface TooltipLiteProps extends TdTooltipLiteProps, StyledProps { const DEFAULT_TRANSITION_TIMEOUT = 180; -const TooltipLite: React.FC = (props) => { +const TooltipLite: React.FC = (originalProps) => { + const props = useDefaultProps(originalProps, tooltipLiteDefaultProps); const { style, className, placement, showArrow, theme, children, triggerElement, content, showShadow } = props; const triggerRef = useRef(null); const contentRef = useRef(null); @@ -108,6 +110,5 @@ const TooltipLite: React.FC = (props) => { }; TooltipLite.displayName = 'Tooltiplite'; -TooltipLite.defaultProps = tooltipLiteDefaultProps; export default React.memo(TooltipLite); diff --git a/src/transfer/Transfer.tsx b/src/transfer/Transfer.tsx index 41f2e551ca..edab3f18ae 100644 --- a/src/transfer/Transfer.tsx +++ b/src/transfer/Transfer.tsx @@ -13,6 +13,7 @@ import { filterCheckedTreeNodes, getTargetNodes, getDefaultValue, getJSX, getLea import { TNode, StyledProps } from '../common'; import { useLocaleReceiver } from '../locale/LocalReceiver'; import { transferDefaultProps } from './defaultProps'; +import useDefaultProps from '../hooks/useDefaultProps'; export interface TransferProps extends TdTransferProps, StyledProps { content?: Array; @@ -28,7 +29,8 @@ interface CheckedInterface { target: Array; } -const Transfer: React.FunctionComponent = (props) => { +const Transfer: React.FunctionComponent = (originalProps) => { + const props = useDefaultProps(originalProps, transferDefaultProps); const { data, search, @@ -279,6 +281,5 @@ const Transfer: React.FunctionComponent = (props) => { }; Transfer.displayName = 'Transfer'; -Transfer.defaultProps = transferDefaultProps; export default Transfer; diff --git a/src/tree-select/TreeSelect.tsx b/src/tree-select/TreeSelect.tsx index 017d8556b1..6c806746bb 100644 --- a/src/tree-select/TreeSelect.tsx +++ b/src/tree-select/TreeSelect.tsx @@ -16,6 +16,9 @@ import { useTreeSelectPassThroughProps } from './useTreeSelectPassthoughProps'; import { useTreeSelectLocale } from './useTreeSelectLocale'; import { treeSelectDefaultProps } from './defaultProps'; import parseTNode from '../_util/parseTNode'; +import useDefaultProps from '../hooks/useDefaultProps'; +import { PopupRef } from '../popup'; +import { InputRef } from '../input'; export interface TreeSelectProps extends TdTreeSelectProps, @@ -29,7 +32,10 @@ export interface NodeOptions { const useMergeFn = (...fns: Array<(...args: T) => void>) => usePersistFn((...args: T) => fns.forEach((fn) => fn?.(...args))); -const TreeSelect = forwardRef((props: TreeSelectProps, ref) => { +type TreeSelectRefType = Partial & PopupRef & InputRef>; + +const TreeSelect = forwardRef((originalProps, ref) => { + const props = useDefaultProps>(originalProps, treeSelectDefaultProps); /* ---------------------------------config---------------------------------------- */ // 国际化文本初始化 @@ -73,7 +79,7 @@ const TreeSelect = forwardRef((props: TreeSelectProps, ref) => { const [filterInput, setFilterInput] = useControlled(props, 'inputValue', onInputChange); const treeRef = useRef>(); - const selectInputRef = useRef(); + const selectInputRef = useRef>(); const tKeys = useMemo( () => ({ @@ -351,6 +357,5 @@ const TreeSelect = forwardRef((props: TreeSelectProps, ref) => { }); TreeSelect.displayName = 'TreeSelect'; -TreeSelect.defaultProps = treeSelectDefaultProps; export default TreeSelect; diff --git a/src/tree/Tree.tsx b/src/tree/Tree.tsx index 7041193ff1..69fc0d9073 100644 --- a/src/tree/Tree.tsx +++ b/src/tree/Tree.tsx @@ -28,11 +28,21 @@ import useTreeVirtualScroll from './hooks/useTreeVirtualScroll'; import type { TreeNodeState, TreeNodeValue, TypeTreeNodeData, TypeTreeNodeModel } from '../_common/js/tree-v1/types'; import type { TreeInstanceFunctions, TdTreeProps } from './type'; +import useDefaultProps from '../hooks/useDefaultProps'; export type TreeProps = TdTreeProps & StyledProps; -const Tree = forwardRef((props: TreeProps, ref: React.Ref) => { +const Tree = forwardRef, TreeProps>((originalProps, ref) => { const { treeClassNames, transitionNames, transitionClassNames, transitionDuration, locale } = useTreeConfig(); + const props = useDefaultProps(originalProps, { + data: [], + expandLevel: 0, + icon: true, + line: false, + transition: true, + lazy: true, + valueMode: 'onlyLeaf', + }); // 可见节点集合 const [visibleNodes, setVisibleNodes] = useState([]); @@ -365,14 +375,4 @@ const Tree = forwardRef((props: TreeProps, ref: React.Ref Tree.displayName = 'Tree'; -Tree.defaultProps = { - data: [], - expandLevel: 0, - icon: true, - line: false, - transition: true, - lazy: true, - valueMode: 'onlyLeaf', -}; - export default Tree; diff --git a/src/tree/TreeItem.tsx b/src/tree/TreeItem.tsx index 476a5fc3f9..ded073df88 100644 --- a/src/tree/TreeItem.tsx +++ b/src/tree/TreeItem.tsx @@ -8,6 +8,7 @@ import React, { DragEvent, isValidElement, useEffect, + useState, } from 'react'; import classNames from 'classnames'; import isFunction from 'lodash/isFunction'; @@ -25,6 +26,7 @@ import useConfig from '../hooks/useConfig'; import type { TdTreeProps } from './type'; import type { TreeItemProps } from './interface'; +import type { TypeTreeNodeData } from '../_common/js/tree-v1/types'; /** * 树节点组件 @@ -210,11 +212,23 @@ const TreeItem = forwardRef( const [labelDom, setRefCurrent] = useDomRefCallback(); useRipple(labelDom); + // setData需要强制刷新组件来更新数据 + const [, updateRender] = useState({}); + const renderLabel = () => { const emptyView = locale('empty'); let labelText: string | ReactNode = ''; if (label instanceof Function) { - labelText = label(node.getModel()) || emptyView; + const { setData: nodeSetData, ...rest } = node.getModel(); + labelText = + label({ + ...rest, + // 拦截setData render tree-item + setData: (value: TypeTreeNodeData) => { + nodeSetData(value); + updateRender({}); + }, + }) || emptyView; } else { labelText = node.label || emptyView; } diff --git a/src/tree/__tests__/tree.test.tsx b/src/tree/__tests__/tree.test.tsx index 2dea5c0542..fc749f67c6 100644 --- a/src/tree/__tests__/tree.test.tsx +++ b/src/tree/__tests__/tree.test.tsx @@ -320,4 +320,71 @@ describe('Tree test', () => { await mockDelay(300); expect(container.querySelectorAll('.t-loading').length).toBe(1); }); + + test('custom label', async () => { + const data = [ + { + label: '第1一段', + value: 1, + children: [ + { + label: '第二段', + value: '1-1', + }, + ], + }, + { + label: '第二段', + value: 2, + }, + ]; + const { container } = await renderTreeWithProps({ + data, + label: ({ data, setData }) => + data.isEditing ? ( + { + console.log('blur', { ...data, isEditing: false }); + setData({ ...data, label: e.target.value, isEditing: false }); + }} + onKeyDown={(e) => { + console.log('keydown', e.key); + if (e.key === 'Enter') { + setData({ ...data, label: e.currentTarget.value, isEditing: false }); + console.log('Enter setData({ ...data, name: e.target.value, isEditing: false })'); + } + if (e.key === 'Escape') { + setData({ ...data, isEditing: false }); + console.log('ESC setData({ ...data, isEditing: false })'); + } + }} + /> + ) : ( + { + e.stopPropagation(); + setData({ ...data, isEditing: true }); + }} + > + {data.label} + + ), + }); + await mockDelay(300); + expect(container.querySelector('.tree-item-span')).not.toBeNull(); + fireEvent.dblClick(container.querySelector('.tree-item-span')); + await mockDelay(300); + expect(container.querySelector('.tree-item-input')).not.toBeNull(); + fireEvent.change(container.querySelector('.tree-item-input'), { target: { value: '123' } }); + expect(container.querySelector('.tree-item-input').value).toBe('123'); + container.querySelector('.tree-item-input').focus(); + container.querySelector('.tree-item-input').blur(); + await mockDelay(300); + expect(container.querySelector('.tree-item-input')).toBeNull(); + expect(container.querySelector('.tree-item-span')).not.toBeNull(); + expect(container.querySelector('.tree-item-span').textContent).toBe('123'); + }); }); diff --git a/src/upload/upload.tsx b/src/upload/upload.tsx index 1439fc4eeb..8f07c80285 100644 --- a/src/upload/upload.tsx +++ b/src/upload/upload.tsx @@ -13,9 +13,11 @@ import { UploadDragEvents } from './hooks/useDrag'; import CustomFile from './themes/CustomFile'; import { UploadFile } from './type'; import parseTNode from '../_util/parseTNode'; +import useDefaultProps from '../hooks/useDefaultProps'; // const Upload = forwardRef((props: UploadProps, ref) => { -function TdUpload(props: UploadProps, ref: ForwardedRef) { +function TdUpload(originalProps: UploadProps, ref: ForwardedRef) { + const props = useDefaultProps>(originalProps, uploadDefaultProps); const { theme } = props; const { locale, @@ -216,6 +218,5 @@ export type UploadOuterForwardRef = { const Upload = forwardRef(TdUpload) as UploadOuterForwardRef; Upload.displayName = 'Upload'; -Upload.defaultProps = uploadDefaultProps; export default Upload; diff --git a/src/watermark/Watermark.tsx b/src/watermark/Watermark.tsx index 54e50cfdd1..be36f04e4f 100644 --- a/src/watermark/Watermark.tsx +++ b/src/watermark/Watermark.tsx @@ -8,31 +8,35 @@ import injectStyle from '../_common/js/utils/injectStyle'; import useConfig from '../hooks/useConfig'; import useMutationObserver from '../_util/useMutationObserver'; import { TdWatermarkProps } from './type'; -import { watermarkDefaultProps as defaultProps } from './defaultProps'; +import { watermarkDefaultProps } from './defaultProps'; import { getStyleStr } from './utils'; +import useDefaultProps from '../hooks/useDefaultProps'; export interface WatermarkProps extends TdWatermarkProps, StyledProps {} -const Watermark: React.FC = ({ - alpha = defaultProps.alpha, - x = 200, - y = 210, - width = 120, - height = 60, - rotate: tempRotate = defaultProps.rotate, - zIndex = 10, - lineSpace = defaultProps.lineSpace, - isRepeat = defaultProps.isRepeat, - removable = defaultProps.removable, - movable = defaultProps.movable, - moveInterval = defaultProps.moveInterval, - offset = [], - content, - children, - watermarkContent, - className, - style = {}, -}) => { +const Watermark: React.FC = (originalProps) => { + const props = useDefaultProps(originalProps, watermarkDefaultProps); + const { + alpha, + x = 200, + y = 210, + width = 120, + height = 60, + rotate: tempRotate, + zIndex = 10, + lineSpace, + isRepeat, + removable, + movable, + moveInterval, + offset = [], + content, + children, + watermarkContent, + className, + style = {}, + } = props; + const { classPrefix } = useConfig(); let gapX = x; diff --git a/test/snap/__snapshots__/csr.test.jsx.snap b/test/snap/__snapshots__/csr.test.jsx.snap index c17242e172..6d21b14474 100644 --- a/test/snap/__snapshots__/csr.test.jsx.snap +++ b/test/snap/__snapshots__/csr.test.jsx.snap @@ -6109,7 +6109,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/base.jsx 1`] = `
- 图片加载中 + loading
@@ -6200,7 +6200,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/base.jsx 1`] = `
- 图片加载中 + loading
@@ -6329,7 +6329,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/group.jsx 1`] = `
- 图片加载中 + loading
@@ -6405,7 +6405,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/group.jsx 1`] = `
- 图片加载中 + loading
@@ -6489,7 +6489,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/group.jsx 1`] = `
- 图片加载中 + loading
@@ -6565,7 +6565,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/group.jsx 1`] = `
- 图片加载中 + loading
@@ -6706,7 +6706,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/group-cascading.jsx 1`
- 图片加载中 + loading
@@ -6782,7 +6782,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/group-cascading.jsx 1`
- 图片加载中 + loading
@@ -6866,7 +6866,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/group-cascading.jsx 1`
- 图片加载中 + loading
@@ -6942,7 +6942,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/group-cascading.jsx 1`
- 图片加载中 + loading
@@ -7083,7 +7083,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/group-max.jsx 1`] = `
- 图片加载中 + loading
@@ -7152,7 +7152,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/group-max.jsx 1`] = `
- 图片加载中 + loading
@@ -7232,7 +7232,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/group-max.jsx 1`] = `
- 图片加载中 + loading
@@ -7309,7 +7309,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/group-max.jsx 1`] = `
- 图片加载中 + loading
@@ -7378,7 +7378,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/group-max.jsx 1`] = `
- 图片加载中 + loading
@@ -7458,7 +7458,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/group-max.jsx 1`] = `
- 图片加载中 + loading
@@ -7857,7 +7857,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/size.jsx 1`] = `
- 图片加载中 + loading
@@ -7905,7 +7905,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/size.jsx 1`] = `
- 图片加载中 + loading
@@ -7953,7 +7953,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/size.jsx 1`] = `
- 图片加载中 + loading
@@ -8002,7 +8002,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/size.jsx 1`] = `
- 图片加载中 + loading
@@ -8198,7 +8198,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/size.jsx 1`] = `
- 图片加载中 + loading
@@ -8246,7 +8246,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/size.jsx 1`] = `
- 图片加载中 + loading
@@ -8294,7 +8294,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/size.jsx 1`] = `
- 图片加载中 + loading
@@ -8343,7 +8343,7 @@ exports[`csr snapshot test > csr test src/avatar/_example/size.jsx 1`] = `
- 图片加载中 + loading
@@ -41311,7 +41311,7 @@ exports[`csr snapshot test > csr test src/card/_example/footer-actions.jsx 1`] =
- 图片加载中 + loading
@@ -41668,7 +41668,7 @@ exports[`csr snapshot test > csr test src/card/_example/footer-actions.jsx 1`] =
- 图片加载中 + loading
@@ -42797,7 +42797,7 @@ exports[`csr snapshot test > csr test src/card/_example/header-footer-actions.js
- 图片加载中 + loading
@@ -43002,7 +43002,7 @@ exports[`csr snapshot test > csr test src/card/_example/header-footer-actions.js
- 图片加载中 + loading
@@ -69283,7 +69283,7 @@ exports[`csr snapshot test > csr test src/config-provider/_example/others.jsx 1`
- 图片加载中 + loading
@@ -69905,7 +69905,7 @@ exports[`csr snapshot test > csr test src/config-provider/_example/others.jsx 1`
- 图片加载中 + loading
@@ -85042,13 +85042,6 @@ exports[`csr snapshot test > csr test src/descriptions/_example/base.jsx 1`] = ` class="t-space t-space-vertical" style="gap: 16px;" > -
-

- 推荐:数据写法 -

-
@@ -85120,84 +85113,6 @@ exports[`csr snapshot test > csr test src/descriptions/_example/base.jsx 1`] = `
-
-

- JSX写法 -

-
-
-
-
- Shipping address -
- - - - - - - - - - - - - - - -
- Name - - TDesign - - Telephone Number - - 139****0609 -
- Area - - China Tencent Headquarters - - Address - - test -
-
-
, @@ -85206,13 +85121,6 @@ exports[`csr snapshot test > csr test src/descriptions/_example/base.jsx 1`] = ` class="t-space t-space-vertical" style="gap: 16px;" > -
-

- 推荐:数据写法 -

-
@@ -85284,84 +85192,6 @@ exports[`csr snapshot test > csr test src/descriptions/_example/base.jsx 1`] = `
-
-

- JSX写法 -

-
-
-
-
- Shipping address -
- - - - - - - - - - - - - - - -
- Name - - TDesign - - Telephone Number - - 139****0609 -
- Area - - China Tencent Headquarters - - Address - - test -
-
-
, "debug": [Function], @@ -86714,176 +86544,69 @@ exports[`csr snapshot test > csr test src/descriptions/_example/layout.jsx 1`] = class="t-space-item" >
-

- 整体左右布局,item 左右布局 -

+ + layout: +
-
- Shipping address -
- + + + horizontal + + + - - - - - - - - - - - - - -
- Name - - TDesign - - Telephone Number - - 139****0609 -
- Area - - China Tencent Headquarters - - Address - - Shenzhen Penguin Island D1 4A Mail Center -
-
-
-
- -
-
-
-

- 整体左右布局,item 上下布局 -

-
-
-
+ + + + vertical + +
- Shipping address -
- - - - - - - - - - - - - - - - - - - -
- Name - - Telephone Number -
- TDesign - - 139****0609 -
- Area - - Address -
- China Tencent Headquarters - - Shenzhen Penguin Island D1 4A Mail Center -
+ class="t-radio-group__bg-block" + style="width: 0px; height: 0px; left: 0px; top: 0px;" + />
@@ -86892,89 +86615,69 @@ exports[`csr snapshot test > csr test src/descriptions/_example/layout.jsx 1`] = class="t-space-item" >
-

- 整体上下布局,item 左右布局 -

+ + itemLayout: +
-
- Shipping address -
- + + + horizontal + + + - - - - - - - - - - - - - - - - - -
- Name - - TDesign -
- Telephone Number - - 139****0609 -
- Area - - China Tencent Headquarters -
- Address - - Shenzhen Penguin Island D1 4A Mail Center -
+ + + + vertical + + +
@@ -86983,99 +86686,71 @@ exports[`csr snapshot test > csr test src/descriptions/_example/layout.jsx 1`] = class="t-space-item" >
-

- 整体上下布局,item 上下布局 -

+ Shipping address
-
-
-
- Shipping address -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Name -
- TDesign -
- Telephone Number -
- 139****0609 -
- Area -
- China Tencent Headquarters -
- Address -
- Shenzhen Penguin Island D1 4A Mail Center -
-
-
+ + + + Name + + + TDesign + + + Telephone Number + + + 139****0609 + + + + + Area + + + China Tencent Headquarters + + + Address + + + Shenzhen Penguin Island D1 4A Mail Center + + + +
@@ -87090,85 +86765,69 @@ exports[`csr snapshot test > csr test src/descriptions/_example/layout.jsx 1`] = class="t-space-item" >
-

- 整体左右布局,item 左右布局 -

+ + layout: +
-
- Shipping address -
- + + + horizontal + + + - - - - - - - - - - - - - -
- Name - - TDesign - - Telephone Number - - 139****0609 -
- Area - - China Tencent Headquarters - - Address - - Shenzhen Penguin Island D1 4A Mail Center -
+ + + + vertical + + +
@@ -87177,89 +86836,69 @@ exports[`csr snapshot test > csr test src/descriptions/_example/layout.jsx 1`] = class="t-space-item" >
-

- 整体左右布局,item 上下布局 -

+ + itemLayout: +
-
- Shipping address -
- + + + horizontal + + + - - - - - - - - - - - - - - - - - -
- Name - - Telephone Number -
- TDesign - - 139****0609 -
- Area - - Address -
- China Tencent Headquarters - - Shenzhen Penguin Island D1 4A Mail Center -
+ + + + vertical + + +
@@ -87268,190 +86907,368 @@ exports[`csr snapshot test > csr test src/descriptions/_example/layout.jsx 1`] = class="t-space-item" >
-

- 整体上下布局,item 左右布局 -

+ Shipping address
+ + + + + + + + + + + + + + + +
+ Name + + TDesign + + Telephone Number + + 139****0609 +
+ Area + + China Tencent Headquarters + + Address + + Shenzhen Penguin Island D1 4A Mail Center +
+
+
+
+ , + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + +exports[`csr snapshot test > csr test src/descriptions/_example/nest.jsx 1`] = ` +{ + "asFragment": [Function], + "baseElement": +