-
Notifications
You must be signed in to change notification settings - Fork 328
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Descriptions): support Descriptions component (#2706)
* feat(support descriptions component): support Descriptions component * test(descriptions): update snap * docs(descriptions): update descriptions example n * test(descriptions): update test snap n * fix(descriptions): descriptions-item replace item * docs: rename Descriptions direction to layout * test: update snap test * docs(descriptions): update usage * docs(descriptions): update usage --------- Co-authored-by: Heising <[email protected]>
- Loading branch information
1 parent
d4a642e
commit d164c94
Showing
32 changed files
with
4,010 additions
and
72 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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>(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 / <div></div> / () => <div></div> | ||
* ! 暂时没有这样一个全局的方法,所以先在组件内部写一个临时方法,无论之后是有了更好的处理方式要删除掉,还是其它组件也需要时再放到公共方法里面,都是可行的 | ||
*/ | ||
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 ? <div className={`${COMPONENT_NAME}__header`}>{title}</div> : ''); | ||
|
||
// Body | ||
const renderBody = () => { | ||
const tableClass = [`${COMPONENT_NAME}__body`, SIZE[props.size], { [`${COMPONENT_NAME}__body--border`]: bordered }]; | ||
return ( | ||
<table className={classNames(tableClass)}> | ||
<tbody> | ||
{getRows().map((row, i) => ( | ||
<Row row={row} key={i} /> | ||
))} | ||
</tbody> | ||
</table> | ||
); | ||
}; | ||
|
||
return ( | ||
<DescriptionsContext.Provider value={props}> | ||
<div className={COMPONENT_NAME}> | ||
{renderHeader()} | ||
{renderBody()} | ||
</div> | ||
</DescriptionsContext.Provider> | ||
); | ||
}; | ||
|
||
Descriptions.displayName = 'Descriptions'; | ||
|
||
Descriptions.DescriptionsItem = DescriptionsItem; | ||
|
||
export default Descriptions; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { createContext } from 'react'; | ||
import { TdDescriptionsProps } from './type'; | ||
|
||
export type DescriptionsContextProps = TdDescriptionsProps; | ||
|
||
export const DescriptionsContext = createContext<DescriptionsContextProps>(null); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import React from 'react'; | ||
import { TdDescriptionItemProps } from './type'; | ||
|
||
export type DescriptionsItem = TdDescriptionItemProps & { children?: React.ReactNode }; | ||
|
||
const DescriptionsItem: React.FC<DescriptionsItem> = () => null; | ||
|
||
DescriptionsItem.displayName = 'DescriptionsItem'; | ||
|
||
export default DescriptionsItem; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<RowProps> = (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 ( | ||
<td | ||
key={rowKey} | ||
colSpan={labelSpan} | ||
className={`${COMPONENT_NAME}__label`} | ||
style={descriptionsContext.labelStyle} | ||
> | ||
{node.label} | ||
{descriptionsContext.colon && ':'} | ||
</td> | ||
); | ||
}; | ||
|
||
// 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 ( | ||
<td | ||
key={rowKey} | ||
colSpan={contentSpan} | ||
className={`${COMPONENT_NAME}__content`} | ||
style={descriptionsContext.contentStyle} | ||
> | ||
{node.content} | ||
</td> | ||
); | ||
}; | ||
|
||
// 总共有四种布局 | ||
// Layout horizontal vertical | ||
// itemLayout horizontal vertical | ||
|
||
const hh = () => ( | ||
<tr> | ||
{row.map((node, i) => ( | ||
<React.Fragment key={i}> | ||
{label(node)} | ||
{content(node)} | ||
</React.Fragment> | ||
))} | ||
</tr> | ||
); | ||
|
||
const hv = () => ( | ||
<> | ||
<tr>{row.map((node, i) => label(node, LayoutEnum.VERTICAL, `top_${i}`))}</tr> | ||
<tr>{row.map((node, i) => content(node, LayoutEnum.VERTICAL, `bottom_${i}`))}</tr> | ||
</> | ||
); | ||
|
||
const vh = () => ( | ||
<> | ||
{row.map((node, i) => ( | ||
<tr key={i}> | ||
{label(node)} | ||
{content(node)} | ||
</tr> | ||
))} | ||
</> | ||
); | ||
|
||
const vv = () => ( | ||
<> | ||
{row.map((node, i) => ( | ||
<React.Fragment key={i}> | ||
<tr>{label(node)}</tr> | ||
<tr>{content(node)}</tr> | ||
</React.Fragment> | ||
))} | ||
</> | ||
); | ||
|
||
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; |
Oops, something went wrong.