packages/antd/src/array-collapse/index.tsx (225 lines of code) (raw):

import React, { Fragment, useState, useEffect } from 'react' import { Badge, Card, Collapse, CollapsePanelProps, CollapseProps, Empty, } from 'antd' import { ArrayField } from '@formily/core' import { RecursionField, useField, useFieldSchema, observer, ISchema, } from '@formily/react' import { toArr } from '@formily/shared' import cls from 'classnames' import ArrayBase, { ArrayBaseMixins, IArrayBaseProps } from '../array-base' import { usePrefixCls } from '../__builtins__' export interface IArrayCollapseProps extends CollapseProps { defaultOpenPanelCount?: number } type ComposedArrayCollapse = React.FC< React.PropsWithChildren<IArrayCollapseProps & IArrayBaseProps> > & ArrayBaseMixins & { CollapsePanel?: React.FC<React.PropsWithChildren<CollapsePanelProps>> } const isAdditionComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Addition') > -1 } const isIndexComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Index') > -1 } const isRemoveComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Remove') > -1 } const isMoveUpComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('MoveUp') > -1 } const isMoveDownComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('MoveDown') > -1 } const isOperationComponent = (schema: ISchema) => { return ( isAdditionComponent(schema) || isRemoveComponent(schema) || isMoveDownComponent(schema) || isMoveUpComponent(schema) ) } const range = (count: number) => Array.from({ length: count }).map((_, i) => i) const takeDefaultActiveKeys = ( dataSourceLength: number, defaultOpenPanelCount: number ) => { if (dataSourceLength < defaultOpenPanelCount) return range(dataSourceLength) return range(defaultOpenPanelCount) } const insertActiveKeys = (activeKeys: number[], index: number) => { if (activeKeys.length <= index) return activeKeys.concat(index) return activeKeys.reduce((buf, key) => { if (key < index) return buf.concat(key) if (key === index) return buf.concat([key, key + 1]) return buf.concat(key + 1) }, []) } export const ArrayCollapse: ComposedArrayCollapse = observer( ({ defaultOpenPanelCount = 5, ...props }) => { const field = useField<ArrayField>() const dataSource = Array.isArray(field.value) ? field.value : [] const [activeKeys, setActiveKeys] = useState<number[]>( takeDefaultActiveKeys(dataSource.length, defaultOpenPanelCount) ) const schema = useFieldSchema() const prefixCls = usePrefixCls('formily-array-collapse', props) useEffect(() => { if (!field.modified && dataSource.length) { setActiveKeys( takeDefaultActiveKeys(dataSource.length, defaultOpenPanelCount) ) } }, [dataSource.length, field]) if (!schema) throw new Error('can not found schema object') const { onAdd, onCopy, onRemove, onMoveDown, onMoveUp } = props const renderAddition = () => { return schema.reduceProperties((addition, schema, key) => { if (isAdditionComponent(schema)) { return <RecursionField schema={schema} name={key} /> } return addition }, null) } const renderEmpty = () => { if (dataSource.length) return return ( <Card className={cls(`${prefixCls}-item`, props.className)}> <Empty /> </Card> ) } const renderItems = () => { return ( <Collapse {...props} activeKey={activeKeys} onChange={(keys: string[]) => setActiveKeys(toArr(keys).map(Number))} className={cls(`${prefixCls}-item`, props.className)} > {dataSource.map((item, index) => { const items = Array.isArray(schema.items) ? schema.items[index] || schema.items[0] : schema.items const panelProps = field .query(`${field.address}.${index}`) .get('componentProps') const props: CollapsePanelProps = items['x-component-props'] const header = () => { const header = panelProps?.header || props.header || field.title const path = field.address.concat(index) const errors = field.form.queryFeedbacks({ type: 'error', address: `${path}.**`, }) return ( <ArrayBase.Item index={index} record={() => field.value?.[index]} > <RecursionField schema={items} name={index} filterProperties={(schema) => { if (!isIndexComponent(schema)) return false return true }} onlyRenderProperties /> {errors.length ? ( <Badge size="small" className="errors-badge" count={errors.length} > {header} </Badge> ) : ( header )} </ArrayBase.Item> ) } const extra = ( <ArrayBase.Item index={index} record={item}> <RecursionField schema={items} name={index} filterProperties={(schema) => { if (!isOperationComponent(schema)) return false return true }} onlyRenderProperties /> {panelProps?.extra} </ArrayBase.Item> ) const content = ( <RecursionField schema={items} name={index} filterProperties={(schema) => { if (isIndexComponent(schema)) return false if (isOperationComponent(schema)) return false return true }} /> ) return ( <Collapse.Panel {...props} {...panelProps} forceRender key={index} header={header()} extra={extra} > <ArrayBase.Item index={index} key={index} record={item}> {content} </ArrayBase.Item> </Collapse.Panel> ) })} </Collapse> ) } return ( <ArrayBase onAdd={(index) => { onAdd?.(index) setActiveKeys(insertActiveKeys(activeKeys, index)) }} onCopy={onCopy} onRemove={onRemove} onMoveUp={onMoveUp} onMoveDown={onMoveDown} > {renderEmpty()} {renderItems()} {renderAddition()} </ArrayBase> ) } ) const CollapsePanel: React.FC<React.PropsWithChildren<CollapsePanelProps>> = ({ children, }) => { return <Fragment>{children}</Fragment> } CollapsePanel.displayName = 'CollapsePanel' ArrayCollapse.displayName = 'ArrayCollapse' ArrayCollapse.CollapsePanel = CollapsePanel ArrayBase.mixin(ArrayCollapse) export default ArrayCollapse