diff --git a/script/generate-usage/config.js b/script/generate-usage/config.js index be5aaef595..3e3db105f1 100644 --- a/script/generate-usage/config.js +++ b/script/generate-usage/config.js @@ -1062,24 +1062,6 @@ module.exports = { }, [changedProps]); `, }, - Jumper: { - importStr: ` - import configProps from './props.json';\n - import { Jumper } from 'tdesign-react';\n`, - configStr: ` - const [configList, setConfigList] = useState(configProps); - `, - panelStr: ` - const panelList = [{ label: 'jumper', value: 'jumper' }]; - `, - usageStr: ` - useEffect(() => { - setRenderComp(( - - )); - }, [changedProps]); - `, - }, Collapse: { importStr: ` import configProps from './props.json';\n @@ -1113,4 +1095,30 @@ module.exports = { }, [changedProps]); `, }, + Descriptions: { + importStr: ` + import configProps from './props.json';\n + import { Descriptions } from 'tdesign-react';\n`, + configStr: ` + const [configList, setConfigList] = useState(configProps); + `, + panelStr: ` + const panelList = [{ label: 'descriptions', value: 'descriptions' }]; + `, + usageStr: ` + const { DescriptionsItem } = Descriptions; + useEffect(() => { + setRenderComp(( + + TDesign + 139****0609 + China Tencent Headquarters + + Shenzhen Penguin Island D1 4A Mail Center + + + )); + }, [changedProps]); + `, + }, }; diff --git a/site/site.config.mjs b/site/site.config.mjs index d3fbf60cda..48fe712750 100644 --- a/site/site.config.mjs +++ b/site/site.config.mjs @@ -457,6 +457,14 @@ export const docs = [ component: () => import('tdesign-react/comment/comment.md'), componentEn: () => import('tdesign-react/comment/comment.en-US.md'), }, + { + title: 'Descriptions 描述', + titleEn: 'Descriptions', + name: 'descriptions', + path: '/react/components/descriptions', + component: () => import('tdesign-react/descriptions/descriptions.md'), + componentEn: () => import('tdesign-react/descriptions/descriptions.en-US.md'), + }, { title: 'Image 图片', titleEn: 'Image', diff --git a/site/test-coverage.js b/site/test-coverage.js index 261c8618d1..1814fedc95 100644 --- a/site/test-coverage.js +++ b/site/test-coverage.js @@ -119,6 +119,12 @@ module.exports = { "functions": "60%", "lines": "66.07%" }, + "descriptions": { + "statements": "98.82%", + "branches": "100%", + "functions": "95.45%", + "lines": "100%" + }, "dialog": { "statements": "85%", "branches": "71.42%", @@ -403,7 +409,7 @@ module.exports = { }, "treeSelect": { "statements": "95.45%", - "branches": "85.95%", + "branches": "86.17%", "functions": "97.61%", "lines": "97.2%" }, diff --git a/src/collapse/_usage/index.jsx b/src/collapse/_usage/index.jsx index 1dc027cbdf..4c3be4423d 100644 --- a/src/collapse/_usage/index.jsx +++ b/src/collapse/_usage/index.jsx @@ -10,9 +10,9 @@ import BaseUsage, { } from "@site/src/components/BaseUsage"; import jsxToString from "react-element-to-jsx-string"; -import { Collapse } from "tdesign-react"; import configProps from "./props.json"; +import { Collapse } from "tdesign-react"; export default function Usage() { const [configList, setConfigList] = useState(configProps); diff --git a/src/common.ts b/src/common.ts index 4a18766dfe..e5df47f926 100644 --- a/src/common.ts +++ b/src/common.ts @@ -64,6 +64,11 @@ export type HorizontalAlignEnum = 'left' | 'center' | 'right'; export type VerticalAlignEnum = 'top' | 'middle' | 'bottom'; +export enum LayoutEnum { + VERTICAL = 'vertical', + HORIZONTAL = 'horizontal', +} + export type ClassName = { [className: string]: any } | ClassName[] | string; export type CSSSelector = string; diff --git a/src/descriptions/Descriptions.tsx b/src/descriptions/Descriptions.tsx new file mode 100644 index 0000000000..05b328a6c5 --- /dev/null +++ b/src/descriptions/Descriptions.tsx @@ -0,0 +1,146 @@ +import React from 'react'; +import classNames from 'classnames'; +import isArray from 'lodash/isArray'; +import assign from 'lodash/assign'; +import { TdDescriptionItemProps, TdDescriptionsProps } from './type'; +import { descriptionItemDefaultProps, descriptionsDefaultProps } from './defaultProps'; +import useDefaultProps from '../hooks/useDefaultProps'; +import useConfig from '../hooks/useConfig'; +import useCommonClassName from '../hooks/useCommonClassName'; +import { LayoutEnum } from '../common'; +import { DescriptionsContext } from './DescriptionsContext'; +import DescriptionsItem from './DescriptionsItem'; +import Row from './Row'; + +/** + * 实现思路 + * 1. 基于 table tbody tr td 来实现布局 + * 2. 通过 span 计算总共有几行以及每一行的 item 个数,特别注意最后一行,要填充满 + * 3. 整体布局:左右布局(column 和 span 生效)/上下布局(column 和 span 失效,一行一个 item) + * 4. item 布局:左右布局/上下布局 + */ + +/** + * TDescriptions:承载 header(title) 和 body(table, tbody) + * TDescriptionsRow:承载每一行(tr) + * TDescriptionsItem:获取 item 数据(span, label, content) + */ + +export type DescriptionsProps = TdDescriptionsProps & { + children?: React.ReactNode; +}; + +const Descriptions = (DescriptionsProps: DescriptionsProps) => { + const props = useDefaultProps(DescriptionsProps, descriptionsDefaultProps); + + const { title, bordered, column, layout, items: rowItems, children } = props; + + const { classPrefix } = useConfig(); + + const COMPONENT_NAME = `${classPrefix}-descriptions`; + + const { SIZE } = useCommonClassName(); + + // 计算渲染的行内容 + const getRows = () => { + // 1. 两种方式:a. props 传 items b. slots t-descriptions-item; a 优先级更高 + + let items: TdDescriptionItemProps[] = []; + + if (isArray(rowItems)) { + /** + * 2.1 a 方式获取 items + * ! 这里要支持 label: string /
/ () =>
+ * ! 暂时没有这样一个全局的方法,所以先在组件内部写一个临时方法,无论之后是有了更好的处理方式要删除掉,还是其它组件也需要时再放到公共方法里面,都是可行的 + */ + items = rowItems.map((item) => { + const { span } = assign({}, descriptionItemDefaultProps, item); + return { + label: item.label, + content: item.content, + span, + }; + }); + } else { + // 2.2 b 方式 获取 TDescriptionsItem + const childrenList = React.Children.toArray(children).filter( + (child: JSX.Element) => child.type.displayName === DescriptionsItem.displayName, + ); + + if (childrenList.length !== 0) { + items = (childrenList as React.ReactElement[]).map(({ props: child }) => { + const { span } = assign({}, descriptionItemDefaultProps, child); + + return { + label: child.label, + content: child.content ?? child.children, + span, + }; + }); + } + } + + // 2. 判断布局,如果整体布局为 LayoutEnum.VERTICAL,那么直接返回即可。 + if (layout === LayoutEnum.VERTICAL) { + return [items]; + } + // 3. 布局为 LayoutEnum.HORIZONTAL 时,需要计算每一行的 item 个数 + let temp: TdDescriptionItemProps[] = []; + let reset = column; + // 4. 记录结果 + const res: TdDescriptionItemProps[][] = []; + items.forEach((item, index) => { + const { span } = item; + if (reset >= span) { + // 当前行还剩余空间 + temp.push(item); + reset -= span; + } else { + // 当前行放不下了,放下一行 + res.push(temp); + temp = [item]; + reset = column - span; + } + + if (index === items.length - 1) { + // 最后一个 + Reflect.set(item, 'span', span + reset); + res.push(temp); + } + }); + + return res; + }; + + // Header + const renderHeader = () => (title ?
{title}
: ''); + + // Body + const renderBody = () => { + const tableClass = [`${COMPONENT_NAME}__body`, SIZE[props.size], { [`${COMPONENT_NAME}__body--border`]: bordered }]; + return ( + + + {getRows().map((row, i) => ( + + ))} + +
+ ); + }; + + return ( + +
+ {renderHeader()} + {renderBody()} +
+
+ ); +}; + +Descriptions.displayName = 'Descriptions'; + +Descriptions.DescriptionsItem = DescriptionsItem; + +export default Descriptions; diff --git a/src/descriptions/DescriptionsContext.tsx b/src/descriptions/DescriptionsContext.tsx new file mode 100644 index 0000000000..60d1ed73eb --- /dev/null +++ b/src/descriptions/DescriptionsContext.tsx @@ -0,0 +1,6 @@ +import { createContext } from 'react'; +import { TdDescriptionsProps } from './type'; + +export type DescriptionsContextProps = TdDescriptionsProps; + +export const DescriptionsContext = createContext(null); diff --git a/src/descriptions/DescriptionsItem.tsx b/src/descriptions/DescriptionsItem.tsx new file mode 100644 index 0000000000..211dd7731e --- /dev/null +++ b/src/descriptions/DescriptionsItem.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { TdDescriptionItemProps } from './type'; + +export type DescriptionsItem = TdDescriptionItemProps & { children?: React.ReactNode }; + +const DescriptionsItem: React.FC = () => null; + +DescriptionsItem.displayName = 'DescriptionsItem'; + +export default DescriptionsItem; diff --git a/src/descriptions/Row.tsx b/src/descriptions/Row.tsx new file mode 100644 index 0000000000..258118d338 --- /dev/null +++ b/src/descriptions/Row.tsx @@ -0,0 +1,108 @@ +import React, { useContext } from 'react'; +import { TdDescriptionItemProps } from './type'; +import { LayoutEnum } from '../common'; +import useConfig from '../hooks/useConfig'; +import { DescriptionsContext } from './DescriptionsContext'; + +export type RowProps = { row: TdDescriptionItemProps[] }; + +const Row: React.FC = (props) => { + const { row } = props; + + const { classPrefix } = useConfig(); + const descriptionsContext = useContext(DescriptionsContext); + + const COMPONENT_NAME = `${classPrefix}-descriptions`; + + // label + const label = (node: TdDescriptionItemProps, layout: LayoutEnum = LayoutEnum.HORIZONTAL, rowKey?: string) => { + const { span } = node; + const labelSpan = layout === LayoutEnum.HORIZONTAL ? 1 : span; + return ( + + {node.label} + {descriptionsContext.colon && ':'} + + ); + }; + + // content + const content = (node: TdDescriptionItemProps, layout: LayoutEnum = LayoutEnum.HORIZONTAL, rowKey?: string) => { + const { span } = node; + const contentSpan = span > 1 && layout === LayoutEnum.HORIZONTAL ? span * 2 - 1 : span; + return ( + + {node.content} + + ); + }; + + // 总共有四种布局 + // Layout horizontal vertical + // itemLayout horizontal vertical + + const hh = () => ( + + {row.map((node, i) => ( + + {label(node)} + {content(node)} + + ))} + + ); + + const hv = () => ( + <> + {row.map((node, i) => label(node, LayoutEnum.VERTICAL, `top_${i}`))} + {row.map((node, i) => content(node, LayoutEnum.VERTICAL, `bottom_${i}`))} + + ); + + const vh = () => ( + <> + {row.map((node, i) => ( + + {label(node)} + {content(node)} + + ))} + + ); + + const vv = () => ( + <> + {row.map((node, i) => ( + + {label(node)} + {content(node)} + + ))} + + ); + + if (descriptionsContext.layout === LayoutEnum.HORIZONTAL) { + if (descriptionsContext.itemLayout === LayoutEnum.HORIZONTAL) { + return hh(); + } + return hv(); + } + if (descriptionsContext.itemLayout === LayoutEnum.HORIZONTAL) { + return vh(); + } + return vv(); +}; + +Row.displayName = 'DescriptionsRow'; + +export default Row; diff --git a/src/descriptions/__tests__/descriptions.test.tsx b/src/descriptions/__tests__/descriptions.test.tsx new file mode 100644 index 0000000000..6a712868d6 --- /dev/null +++ b/src/descriptions/__tests__/descriptions.test.tsx @@ -0,0 +1,245 @@ +import React from 'react'; +import { render } from '@test/utils'; +import Descriptions from '../index'; +import { SizeEnum } from '../../common'; + +const { DescriptionsItem } = Descriptions; + +const items = [ + { + label: 'Name', + content: 'TDesign', + }, + { + label: 'Telephone Number', + content: '139****0609', + }, + { + label: 'Area', + content: 'China Tencent Headquarters', + }, + { + label: 'Address', + content: 'Shenzhen Penguin Island D1 4A Mail Center', + }, +]; + +describe('Descriptions 组件测试', () => { + // base + test('base', () => { + const { container } = render(); + + expect(container.querySelector('.t-descriptions')).toBeInTheDocument(); + }); + + // bordered + test('bordered', () => { + const { container } = render(); + + expect(container.querySelector('.t-descriptions__body--border')).toBeTruthy(); + }); + + // colon + test('colon', () => { + const { container } = render(); + + expect(container.querySelector('.t-descriptions__label')).toHaveTextContent(':'); + }); + + // column + test('column=2', () => { + const { container } = render(); + + const tbody = container.querySelector('tbody'); + + // 检查 tbody 下面是否只有 2 个 tr 元素 + expect(tbody.querySelectorAll('tr')).toHaveLength(2); + + const secondTr = tbody.querySelectorAll('tr')[1]; + + // 检查第 2 个 tr 元素中是否只有 4 个 td 元素 + expect(secondTr.querySelectorAll('td')).toHaveLength(4); + + // 检查第 2 个 tr 元素中的第 4 个 td 元素是否具有 colspan 属性,并检查其值是否为 1 + expect(secondTr.querySelectorAll('td')[3].getAttribute('colspan')).toBe('1'); + }); + + test('column:4', () => { + const { container } = render(); + + const tbody = container.querySelector('tbody'); + + // 检查 tbody 下面是否只有 1 个 tr 元素 + expect(tbody.querySelectorAll('tr')).toHaveLength(1); + + const firstTr = tbody.querySelectorAll('tr')[0]; + + // 检查第 1 个 tr 元素中是否只有 8 个 td 元素 + expect(firstTr.querySelectorAll('td')).toHaveLength(8); + + // 检查第 2 个 tr 元素中的第 4 个 td 元素是否具有 colspan 属性,并检查其值是否为 1 + expect(firstTr.querySelectorAll('td')[7].getAttribute('colspan')).toBe('1'); + }); + + // custom-style + test('custom-style', () => { + const customLabelClassName: React.CSSProperties = { + width: '100px', + textAlign: 'left', + }; + const customContentClassName: React.CSSProperties = { + textAlign: 'center', + }; + + const { container } = render( + , + ); + + expect(container.querySelector('.t-descriptions__label')).toHaveStyle('width: 100px; text-align: left;'); + expect(container.querySelector('.t-descriptions__content')).toHaveStyle('text-align: center;'); + }); + + // direction + test('layout=horizontal columns=3', () => { + const { container } = render(); + const tbody = container.querySelector('tbody'); + + // 检查 tbody 下面是否只有 2 个 tr 元素 + expect(tbody.querySelectorAll('tr')).toHaveLength(2); + + // 检查第 1 个 tr 元素中是否只有 6 个 td 元素 + const firstTr = tbody.querySelectorAll('tr')[0]; + expect(firstTr.querySelectorAll('td')).toHaveLength(6); + + // 检查第 2 个 tr 元素中是否只有 2 个 td 元素 + const secondTr = tbody.querySelectorAll('tr')[1]; + expect(secondTr.querySelectorAll('td')).toHaveLength(2); + + // 检查第 2 个 tr 元素中的第 2 个 td 元素是否具有 colspan 属性,并检查其值是否为 5 + const secondTrTd = secondTr.querySelectorAll('td')[1]; + expect(secondTrTd.getAttribute('colspan')).toBe('5'); + }); + + // vertical + test('layout=vertical columns=3', () => { + const { container } = render( + , + ); + const tbody = container.querySelector('tbody'); + + // 检查 tbody 下面是否只有 4 个 tr 元素 + expect(tbody.querySelectorAll('tr')).toHaveLength(4); + + // 检查第 1 个 tr 元素中是否只有 2 个 td 元素 + const firstTr = tbody.querySelectorAll('tr')[0]; + expect(firstTr.querySelectorAll('td')).toHaveLength(2); + + // 检查第 3 个 tr 元素中是否只有 2 个 td 元素 + const thirdTr = tbody.querySelectorAll('tr')[2]; + expect(thirdTr.querySelectorAll('td')).toHaveLength(2); + + // 检查第 3 个 tr 元素中的第 1 个 td 元素是否具有 colspan 属性,并检查其值是否为 1 + const thirdTrTd = thirdTr.querySelectorAll('td')[0]; + expect(thirdTrTd.getAttribute('colspan')).toBe('1'); + }); + + // itemLayout=vertical + it('itemLayout=vertical', () => { + const { container } = render( + , + ); + + const tbody = container.querySelector('tbody'); + // 检查 tbody 下面是否只有 4 个 tr 元素 + expect(tbody.querySelectorAll('tr')).toHaveLength(4); + + // 检查第 1 个 tr 元素中是否只有 3 个 td 元素 + const firstTr = tbody.querySelectorAll('tr')[0]; + expect(firstTr.querySelectorAll('td')).toHaveLength(3); + + // 检查第 3 个 tr 元素中是否只有 1 个 td 元素 + const thirdTr = tbody.querySelectorAll('tr')[2]; + expect(thirdTr.querySelectorAll('td')).toHaveLength(1); + + // 检查第 3 个 tr 元素中的第 1 个 td 元素是否具有 colspan 属性,并检查其值是否为 3 + const thirdTrTd = thirdTr.querySelectorAll('td')[0]; + expect(thirdTrTd.getAttribute('colspan')).toBe('3'); + }); + + // layout=vertical itemLayout=vertical + it('layout=vertical itemLayout=vertical', () => { + const { container } = render( + , + ); + + const tbody = container.querySelector('tbody'); + // 检查 tbody 下面是否只有 8 个 tr 元素 + expect(tbody.querySelectorAll('tr')).toHaveLength(8); + + // 检查第 1 个 tr 元素中是否只有 1 个 td 元素 + const firstTr = tbody.querySelectorAll('tr')[0]; + expect(firstTr.querySelectorAll('td')).toHaveLength(1); + + // 检查第 7 个 tr 元素中是否只有 1 个 td 元素 + const thirdTr = tbody.querySelectorAll('tr')[2]; + expect(thirdTr.querySelectorAll('td')).toHaveLength(1); + + // 检查第 7 个 tr 元素中的第 1 个 td 元素是否具有 colspan 属性,并检查其值是否为 1 + const thirdTrTd = thirdTr.querySelectorAll('td')[0]; + expect(thirdTrTd.getAttribute('colspan')).toBe('1'); + }); + + // size + test(':size', () => { + const sizeList: SizeEnum[] = ['small', 'medium', 'large']; + sizeList.forEach((size) => { + const { container } = render(); + const body = container.querySelector('.t-descriptions__body'); + expect(body).toHaveClass(`t-size-${size.slice(0, 1)}`); + }); + }); + + // jsx + test('jsx and DescriptionsItem span', () => { + const { container } = render( + + TDesign + 139****0609 + China Tencent Headquarters + + Shenzhen Penguin Island D1 4A Mail Center + + , + ); + + expect(container.querySelector('.t-descriptions')).toBeInTheDocument(); + }); + + // props jsx + test('props jsx', () => { + const itemsProps = [ + { label: 'Name', content: 'TDesign' }, + { label: , content: '139****0609' }, + { label: 'Area', content:

China Tencent Headquarters

}, + { label: Address, content: Shenzhen Penguin Island D1 4A Mail Center }, + ]; + const { container } = render(); + + expect(container.querySelector('.t-descriptions')).toBeInTheDocument(); + }); +}); diff --git a/src/descriptions/_example/base.jsx b/src/descriptions/_example/base.jsx new file mode 100644 index 0000000000..b14ffda714 --- /dev/null +++ b/src/descriptions/_example/base.jsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Descriptions, Space } from 'tdesign-react'; + +const { DescriptionsItem } = Descriptions; + +export default function BasicDescriptions() { + const items = [ + { + label: 'Name', + content: 'TDesign', + }, + { + label: 'Telephone Number', + content: '139****0609', + }, + { + label: 'Area', + content: 'China Tencent Headquarters', + }, + { + label: 'Address', + content: 'Shenzhen Penguin Island D1 4A Mail Center', + }, + ]; + return ( + +

推荐:数据写法

+ +

JSX写法

+ + TDesign + 139****0609 + China Tencent Headquarters + + Shenzhen Penguin Island D1 4A Mail Center + + +
+ ); +} diff --git a/src/descriptions/_example/bordered.jsx b/src/descriptions/_example/bordered.jsx new file mode 100644 index 0000000000..a03bb67932 --- /dev/null +++ b/src/descriptions/_example/bordered.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { Descriptions } from 'tdesign-react'; + +export default function Bordered() { + const items = [ + { + label: 'Name', + content: 'TDesign', + }, + { + label: 'Telephone Number', + content: '139****0609', + }, + { + label: 'Area', + content: 'China Tencent Headquarters', + }, + { + label: 'Address', + content: 'Shenzhen Penguin Island D1 4A Mail Center', + }, + ]; + return ; +} diff --git a/src/descriptions/_example/colon.jsx b/src/descriptions/_example/colon.jsx new file mode 100644 index 0000000000..919cf52c5e --- /dev/null +++ b/src/descriptions/_example/colon.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { Descriptions, Space, Switch } from 'tdesign-react'; + +export default function Colon() { + const [checked, setChecked] = React.useState(false); + + const items = [ + { + label: 'Name', + content: 'TDesign', + }, + { + label: 'Telephone Number', + content: '139****0609', + }, + { + label: 'Area', + content: 'China Tencent Headquarters', + }, + { + label: 'Address', + content: 'Shenzhen Penguin Island D1 4A Mail Center', + }, + ]; + return ( + + + + 显示引号 + + + + ); +} diff --git a/src/descriptions/_example/column.jsx b/src/descriptions/_example/column.jsx new file mode 100644 index 0000000000..7dc342e760 --- /dev/null +++ b/src/descriptions/_example/column.jsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Descriptions, Space, Radio } from 'tdesign-react'; + +export default function Column() { + const [column, setColumn] = React.useState(2); + + const columnOptions = [2, 3, 4]; + + const items = [ + { + label: 'Name', + content: 'TDesign', + }, + { + label: 'Telephone Number', + content: '139****0609', + }, + { + label: 'Area', + content: 'China Tencent Headquarters', + }, + { + label: 'Address', + content: 'Shenzhen Penguin Island D1 4A Mail Center', + }, + ]; + return ( + + + + + ); +} diff --git a/src/descriptions/_example/custom-style.jsx b/src/descriptions/_example/custom-style.jsx new file mode 100644 index 0000000000..4f44bae17a --- /dev/null +++ b/src/descriptions/_example/custom-style.jsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Descriptions } from 'tdesign-react'; + +export default function CustomStyle() { + const customLabelClassName = { + width: '100px', + textAlign: 'left', + }; + const customContentClassName = { + textAlign: 'center', + }; + + const items = [ + { + label: 'Name', + content: 'TDesign', + }, + { + label: 'Telephone Number', + content: '139****0609', + }, + { + label: 'Area', + content: 'China Tencent Headquarters', + }, + { + label: 'Address', + content: 'Shenzhen Penguin Island D1 4A Mail Center', + }, + ]; + return ( + + ); +} diff --git a/src/descriptions/_example/items.jsx b/src/descriptions/_example/items.jsx new file mode 100644 index 0000000000..d0828bd2d8 --- /dev/null +++ b/src/descriptions/_example/items.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Descriptions } from 'tdesign-react'; + +export default function Items() { + const items = [ + { label: 'Name', content: 'TDesign' }, + { label: , content: '139****0609' }, + { label: 'Area', content:

China Tencent Headquarters

}, + { label: Address, content: Shenzhen Penguin Island D1 4A Mail Center }, + ]; + return ; +} diff --git a/src/descriptions/_example/layout.jsx b/src/descriptions/_example/layout.jsx new file mode 100644 index 0000000000..a94d02eb02 --- /dev/null +++ b/src/descriptions/_example/layout.jsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { Descriptions, Space } from 'tdesign-react'; + +export default function Layout() { + const items = [ + { + label: 'Name', + content: 'TDesign', + }, + { + label: 'Telephone Number', + content: '139****0609', + }, + { + label: 'Area', + content: 'China Tencent Headquarters', + }, + { + label: 'Address', + content: 'Shenzhen Penguin Island D1 4A Mail Center', + }, + ]; + + return ( + + +

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

+ +
+ + +

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

+ +
+ + +

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

+ +
+ + +

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

+ +
+
+ ); +} diff --git a/src/descriptions/_example/size.jsx b/src/descriptions/_example/size.jsx new file mode 100644 index 0000000000..0c3b4b4247 --- /dev/null +++ b/src/descriptions/_example/size.jsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Descriptions, Space, Radio } from 'tdesign-react'; + +export default function Size() { + const [size, setSize] = React.useState('medium'); + + const sizeOptions = ['large', 'medium', 'small']; + const items = [ + { + label: 'Name', + content: 'TDesign', + }, + { + label: 'Telephone Number', + content: '139****0609', + }, + { + label: 'Area', + content: 'China Tencent Headquarters', + }, + { + label: 'Address', + content: 'Shenzhen Penguin Island D1 4A Mail Center', + }, + ]; + return ( + + + + + ); +} diff --git a/src/descriptions/_usage/index.jsx b/src/descriptions/_usage/index.jsx new file mode 100644 index 0000000000..b3801e2fc4 --- /dev/null +++ b/src/descriptions/_usage/index.jsx @@ -0,0 +1,62 @@ +/** + * 该脚本为自动生成,如有需要请在 /script/generate-usage.js 中调整 + */ + +// @ts-nocheck +import React, { useState, useEffect, useMemo } from "react"; +import BaseUsage, { + useConfigChange, + usePanelChange, +} from "@site/src/components/BaseUsage"; +import jsxToString from "react-element-to-jsx-string"; + +import configProps from "./props.json"; + +import { Descriptions } from "tdesign-react"; + +export default function Usage() { + const [configList, setConfigList] = useState(configProps); + + const { changedProps, onConfigChange } = useConfigChange(configList); + + const panelList = [{ label: "descriptions", value: "descriptions" }]; + + const { panel, onPanelChange } = usePanelChange(panelList); + + const [renderComp, setRenderComp] = useState(); + + const { DescriptionsItem } = Descriptions; + useEffect(() => { + setRenderComp( + + TDesign + + 139****0609 + + + China Tencent Headquarters + + + Shenzhen Penguin Island D1 4A Mail Center + + + ); + }, [changedProps]); + + const jsxStr = useMemo(() => { + if (!renderComp) return ""; + return jsxToString(renderComp); + }, [renderComp]); + + return ( + + {renderComp} + + ); +} diff --git a/src/descriptions/_usage/props.json b/src/descriptions/_usage/props.json new file mode 100644 index 0000000000..927d59d9d6 --- /dev/null +++ b/src/descriptions/_usage/props.json @@ -0,0 +1,63 @@ +[ + { + "name": "bordered", + "type": "Boolean", + "defaultValue": false, + "options": [] + }, + { + "name": "colon", + "type": "Boolean", + "defaultValue": false, + "options": [] + }, + { + "name": "size", + "type": "enum", + "defaultValue": "medium", + "options": [ + { + "label": "large", + "value": "large" + }, + { + "label": "medium", + "value": "medium" + }, + { + "label": "small", + "value": "small" + } + ] + }, + { + "name": "layout", + "type": "enum", + "defaultValue": "horizontal", + "options": [ + { + "label": "horizontal", + "value": "horizontal" + }, + { + "label": "vertical", + "value": "vertical" + } + ] + }, + { + "name": "itemLayout", + "type": "enum", + "defaultValue": "horizontal", + "options": [ + { + "label": "horizontal", + "value": "horizontal" + }, + { + "label": "vertical", + "value": "vertical" + } + ] + } + ] \ No newline at end of file diff --git a/src/descriptions/defaultProps.ts b/src/descriptions/defaultProps.ts new file mode 100644 index 0000000000..9a293eb1e4 --- /dev/null +++ b/src/descriptions/defaultProps.ts @@ -0,0 +1,15 @@ +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TdDescriptionsProps, TdDescriptionItemProps } from './type'; + +export const descriptionsDefaultProps: TdDescriptionsProps = { + bordered: false, + column: 2, + itemLayout: 'horizontal', + layout: 'horizontal', + size: 'medium', +}; + +export const descriptionItemDefaultProps: TdDescriptionItemProps = { span: 1 }; diff --git a/src/descriptions/descriptions.en-US.md b/src/descriptions/descriptions.en-US.md new file mode 100644 index 0000000000..a52229952e --- /dev/null +++ b/src/descriptions/descriptions.en-US.md @@ -0,0 +1,29 @@ +:: BASE_DOC :: + +## API +### Descriptions Props + +name | type | default | description | required +-- | -- | -- | -- | -- +className | String | - | 类名 | N +style | Object | - | 样式,Typescript:`React.CSSProperties` | N +bordered | Boolean | false | set description list with grey border | N +colon | Boolean | - | set label with ":" on the right | N +column | Number | 2 | count of DescriptionItem in one row | N +contentStyle | Object | - | style of description cotent。Typescript:`Styles`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N +itemLayout | String | horizontal | layout direction of description item。options: horizontal/vertical | N +items | Array | - | list of descriptions items。Typescript:`Array` | N +labelStyle | Object | - | style of description item。Typescript:`Styles`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N +layout | String | horizontal | layout direction。options: horizontal/vertical | N +size | String | medium | a descriptions has three size。options: small/medium/large。Typescript:`SizeEnum`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N +title | TNode | - | title of descriptions。Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N + +### DescriptionItem Props + +name | type | default | description | required +-- | -- | -- | -- | -- +className | String | - | 类名 | N +style | Object | - | 样式,Typescript:`React.CSSProperties` | N +content | TNode | - | content of description item。Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N +label | TNode | - | label of description item。Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N +span | Number | 1 | width count | N diff --git a/src/descriptions/descriptions.md b/src/descriptions/descriptions.md new file mode 100644 index 0000000000..923b538be4 --- /dev/null +++ b/src/descriptions/descriptions.md @@ -0,0 +1,29 @@ +:: BASE_DOC :: + +## API +### Descriptions Props + +名称 | 类型 | 默认值 | 说明 | 必传 +-- | -- | -- | -- | -- +className | String | - | 类名 | N +style | Object | - | 样式,TS 类型:`React.CSSProperties` | N +bordered | Boolean | false | 是否带边框 | N +colon | Boolean | - | 字段名右侧是否携带冒号“:” | N +column | Number | 2 | 一行 `DescriptionItem` 的数量 | N +contentStyle | Object | - | 自定义描述项内容的样式。TS 类型:`Styles`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N +itemLayout | String | horizontal | 描述项的排列方向。可选项:horizontal/vertical | N +items | Array | - | 描述项的列表。TS 类型:`Array` | N +labelStyle | Object | - | 自定义描述项标签的样式。TS 类型:`Styles`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N +layout | String | horizontal | 排列方向。可选项:horizontal/vertical | N +size | String | medium | 组件尺寸。可选项:small/medium/large。TS 类型:`SizeEnum`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N +title | TNode | - | 描述列表的标题。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N + +### DescriptionItem Props + +名称 | 类型 | 默认值 | 说明 | 必传 +-- | -- | -- | -- | -- +className | String | - | 类名 | N +style | Object | - | 样式,TS 类型:`React.CSSProperties` | N +content | TNode | - | 描述项内容。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N +label | TNode | - | 描述项标签。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N +span | Number | 1 | 占用的宽度数量 | N diff --git a/src/descriptions/index.ts b/src/descriptions/index.ts new file mode 100644 index 0000000000..aef62c1a24 --- /dev/null +++ b/src/descriptions/index.ts @@ -0,0 +1,9 @@ +import _Descriptions from './Descriptions'; + +import './style/index.js'; + +export type { DescriptionsProps } from './Descriptions'; + +export const Descriptions = _Descriptions; + +export default Descriptions; diff --git a/src/descriptions/style/css.js b/src/descriptions/style/css.js new file mode 100644 index 0000000000..6a9a4b1328 --- /dev/null +++ b/src/descriptions/style/css.js @@ -0,0 +1 @@ +import './index.css'; diff --git a/src/descriptions/style/index.js b/src/descriptions/style/index.js new file mode 100644 index 0000000000..f590f64d42 --- /dev/null +++ b/src/descriptions/style/index.js @@ -0,0 +1 @@ +import '../../_common/style/web/components/descriptions/_index.less'; diff --git a/src/descriptions/type.ts b/src/descriptions/type.ts new file mode 100644 index 0000000000..df2888b121 --- /dev/null +++ b/src/descriptions/type.ts @@ -0,0 +1,71 @@ +/* eslint-disable */ + +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TNode, SizeEnum, Styles } from '../common'; + +export interface TdDescriptionsProps { + /** + * 是否带边框 + * @default false + */ + bordered?: boolean; + /** + * 字段名右侧是否携带冒号“:” + */ + colon?: boolean; + /** + * 一行 `DescriptionItem` 的数量 + * @default 2 + */ + column?: number; + /** + * 自定义描述项内容的样式 + */ + contentStyle?: Styles; + /** + * 描述项的排列方向 + * @default horizontal + */ + itemLayout?: 'horizontal' | 'vertical'; + /** + * 描述项的列表 + */ + items?: Array; + /** + * 自定义描述项标签的样式 + */ + labelStyle?: Styles; + /** + * 排列方向 + * @default horizontal + */ + layout?: 'horizontal' | 'vertical'; + /** + * 组件尺寸 + * @default medium + */ + size?: SizeEnum; + /** + * 描述列表的标题 + */ + title?: TNode; +} + +export interface TdDescriptionItemProps { + /** + * 描述项内容 + */ + content?: TNode; + /** + * 描述项标签 + */ + label?: TNode; + /** + * 占用的宽度数量 + * @default 1 + */ + span?: number; +} diff --git a/src/index.ts b/src/index.ts index f654956689..2b8142ca96 100644 --- a/src/index.ts +++ b/src/index.ts @@ -65,3 +65,4 @@ export * from './link'; export * from './guide'; export * from './back-top'; export * from './statistic'; +export * from './descriptions'; diff --git a/src/table/_usage/index.jsx b/src/table/_usage/index.jsx index 88aaaf367b..66d44f804f 100644 --- a/src/table/_usage/index.jsx +++ b/src/table/_usage/index.jsx @@ -3,58 +3,45 @@ */ // @ts-nocheck -import React, { useState, useEffect, useMemo } from 'react'; -import BaseUsage, { useConfigChange, usePanelChange } from '@site/src/components/BaseUsage'; -import jsxToString from 'react-element-to-jsx-string'; -import { ErrorCircleFilledIcon, CheckCircleFilledIcon, CloseCircleFilledIcon } from 'tdesign-icons-react'; +import React, { useState, useEffect, useMemo } from "react"; +import BaseUsage, { + useConfigChange, + usePanelChange, +} from "@site/src/components/BaseUsage"; +import jsxToString from "react-element-to-jsx-string"; -import { Table, Tag } from 'tdesign-react'; -import baseTableConfigProps from './base-table-props.json'; +import baseTableConfigProps from "./base-table-props.json"; + +import { Table } from "tdesign-react"; export default function Usage() { const [configList, setConfigList] = useState(baseTableConfigProps); const { changedProps, onConfigChange } = useConfigChange(configList); - const panelList = [{ label: 'Table', value: 'baseTable', config: baseTableConfigProps }]; + const panelList = [ + { label: "Table", value: "baseTable", config: baseTableConfigProps }, + ]; const data = Array(30) .fill(0) .map((_, i) => ({ index: i, - applicant: ['贾明', '张三', '王芳'][i % 3], - status: i % 3, - channel: ['电子签署', '纸质签署', '纸质签署'][i % 3], - detail: { - email: ['w.cezkdudy@lhll.au', 'r.nmgw@peurezgn.sl', 'p.cumx@rampblpa.ru'][i % 3], - }, + platform: "公有", + description: "数据源", })); - const statusNameListMap = { - 0: { label: '审批通过', theme: 'success', icon: }, - 1: { label: '审批失败', theme: 'danger', icon: }, - 2: { label: '审批过期', theme: 'warning', icon: }, - }; const columns = [ - { colKey: 'applicant', title: '申请人', width: '120' }, - { - colKey: 'status', - title: '审批状态', - width: '120', - cell: ({ row }) => ( - - {statusNameListMap[row.status].icon} - {statusNameListMap[row.status].label} - - ), - }, - { colKey: 'channel', title: '签署方式' }, - { colKey: 'detail.email', title: '电子邮件' }, + { colKey: "index", title: "index" }, + { colKey: "platform", title: "平台" }, + { colKey: "description", title: "说明" }, ]; const defaultProps = { data, columns, + maxHeight: 140, + pagination: { total: 30, defaultPageSize: 10 }, }; const panelMap = { @@ -70,7 +57,7 @@ export default function Usage() { }, [changedProps, panel]); const jsxStr = useMemo(() => { - if (!renderComp) return ''; + if (!renderComp) return ""; return jsxToString(renderComp); }, [renderComp]); diff --git a/src/tree-select/_usage/index.jsx b/src/tree-select/_usage/index.jsx index 6250eae3a8..b6380304d7 100644 --- a/src/tree-select/_usage/index.jsx +++ b/src/tree-select/_usage/index.jsx @@ -3,19 +3,23 @@ */ // @ts-nocheck -import React, { useState, useEffect, useMemo } from 'react'; -import BaseUsage, { useConfigChange, usePanelChange } from '@site/src/components/BaseUsage'; -import jsxToString from 'react-element-to-jsx-string'; +import React, { useState, useEffect, useMemo } from "react"; +import BaseUsage, { + useConfigChange, + usePanelChange, +} from "@site/src/components/BaseUsage"; +import jsxToString from "react-element-to-jsx-string"; -import { TreeSelect } from 'tdesign-react'; -import configProps from './props.json'; +import configProps from "./props.json"; + +import { TreeSelect } from "tdesign-react"; export default function Usage() { const [configList, setConfigList] = useState(configProps); const { changedProps, onConfigChange } = useConfigChange(configList); - const panelList = [{ label: 'tree-select', value: 'tree-select' }]; + const panelList = [{ label: "tree", value: "tree" }]; const { panel, onPanelChange } = usePanelChange(panelList); @@ -24,30 +28,30 @@ export default function Usage() { const defaultProps = { data: [ { - label: '广东省', - value: 'guangdong', + label: "广东省", + value: "guangdong", children: [ { - label: '广州市', - value: 'guangzhou', + label: "广州市", + value: "guangzhou", }, { - label: '深圳市', - value: 'shenzhen', + label: "深圳市", + value: "shenzhen", }, ], }, { - label: '江苏省', - value: 'jiangsu', + label: "江苏省", + value: "jiangsu", children: [ { - label: '南京市', - value: 'nanjing', + label: "南京市", + value: "nanjing", }, { - label: '苏州市', - value: 'suzhou', + label: "苏州市", + value: "suzhou", }, ], }, @@ -58,7 +62,7 @@ export default function Usage() { }, [changedProps]); const jsxStr = useMemo(() => { - if (!renderComp) return ''; + if (!renderComp) return ""; return jsxToString(renderComp); }, [renderComp]); diff --git a/test/snap/__snapshots__/csr.test.jsx.snap b/test/snap/__snapshots__/csr.test.jsx.snap index ef068f65ff..1bb07390d5 100644 --- a/test/snap/__snapshots__/csr.test.jsx.snap +++ b/test/snap/__snapshots__/csr.test.jsx.snap @@ -84542,6 +84542,2852 @@ exports[`csr snapshot test > csr test src/date-picker/_example/year.jsx 1`] = ` } `; +exports[`csr snapshot test > csr test src/descriptions/_example/base.jsx 1`] = ` +{ + "asFragment": [Function], + "baseElement": +