您好,如果喜欢我的文章,可以关注我的公众号「量子前端」,将不定期关注推送前端好文~
折叠面板Collapse、CollapseItem两个组件实现,Collapse用于接收一些折叠面板全局配置,如手风琴、懒加载等;CollapseItem用于对单个折叠栏进行配置,如禁用、排列、图标,因此核心部分使用useContext将父组件的props的部分子组件所需要的值传递给所有子组件。
组件库文档如下:
组件提供的API能力如下:
组件源码如下: Collapse.tsx:
import React, { FC, memo, useState, createContext } from 'react';
import { CollapseProps } from './interfase';
import './style/index.module.less';
export const ctx = createContext<any>({} as any); //顶层通信装置
const Collapse: FC<CollapseProps> = (props) => {
const {
children,
defaultActive,
accordion,
noBorder,
headerAlign = 'left',
lazyLoad = false,
toggleCallback,
} = props;
const [activeKeyList, setActiveKeyList] = useState<Array<number | string>>(defaultActive || []); //父组件管理选中列表
const providerList = {
//父组件状态管理store
activeKeyList,
setActiveKeyList,
accordion,
headerAlign,
lazyLoad,
toggleCallback,
};
return (
<ctx.Provider value={providerList}>
<div
className="collapse-box"
style={noBorder ? {} : { border: '1px solid rgba(229, 230, 235, 1)' }}
>
{children}
</div>
</ctx.Provider>
);
};
export default memo(Collapse);
CollapseItem.tsx:
import React, { FC, memo, useMemo, useEffect, useContext } from 'react';
import { CollapseItemProps } from './interfase';
import { CaretDownOutlined, CaretRightOutlined, CaretLeftOutlined } from '@ant-design/icons';
import './style/item.module.less';
import useStateCallback from '../_util/hooks/useStateCallback';
import { ctx } from './index';
const CollapseItem: FC<CollapseItemProps> = (props) => {
const { children, header, disabled = false, listKey, extra } = props;
const [contentDomHeight, setContentDomHeight] = useStateCallback(0);
const [hasOpen, setHasOpen] = useStateCallback(false);
const { activeKeyList, setActiveKeyList, accordion, headerAlign, lazyLoad, toggleCallback } =
useContext(ctx); //父组件共享状态
useEffect(() => {
//根据默认值展开或收起
if (activeKeyList.indexOf(Number(listKey)) == -1) {
setContentDomHeight(0);
} else {
setContentDomHeight(
(document.querySelector('.collapse-item-content') as HTMLElement).scrollHeight + 30,
);
}
}, [activeKeyList]);
const toggleContent = () => {
if (disabled) return; //禁用
let newHeight = contentDomHeight;
if (newHeight == 0) {
//展开
if (lazyLoad && !hasOpen) {
//首次展开懒加载
setHasOpen(true, (state: boolean) => {
newHeight =
(document.querySelector('.collapse-item-content') as HTMLElement).scrollHeight + 30;
if (accordion) {
//手风琴,全部清除再加入
setActiveKeyList([Number(listKey)]);
toggleCallback && toggleCallback([Number(listKey)]);
} else {
setActiveKeyList((oldAList: Array<string | number>) => {
toggleCallback && toggleCallback([...[...oldAList, Number(listKey)].sort()]);
return [...[...oldAList, Number(listKey)].sort()];
});
}
setContentDomHeight(newHeight);
});
} else {
newHeight =
(document.querySelector('.collapse-item-content') as HTMLElement).scrollHeight + 30;
if (accordion) {
//手风琴,全部清除再加入
setActiveKeyList([Number(listKey)]);
toggleCallback && toggleCallback([Number(listKey)]);
} else {
setActiveKeyList((oldAList: Array<string | number>) => {
toggleCallback && toggleCallback([...[...oldAList, Number(listKey)].sort()]);
return [...[...oldAList, Number(listKey)].sort()];
});
}
setContentDomHeight(newHeight);
}
} else {
//收起
newHeight = 0;
setActiveKeyList((oldAList: Array<string | number>) => {
oldAList.splice(
oldAList.findIndex(
(item: number | string, i: number | string) => Number(i) + 1 == listKey,
),
1,
);
return [...oldAList.sort()];
});
setContentDomHeight(newHeight);
}
};
const headerHeight = useMemo(() => {
//展开高度
return {
maxHeight: `${contentDomHeight}px`,
};
}, [contentDomHeight]);
const renderHeader = useMemo(() => {
if (headerAlign == 'left') {
return (
<div
className="collapse-item-header"
onClick={toggleContent}
style={disabled ? { color: '#c9cdd4', cursor: 'not-allowed' } : {}}
>
<div className="left">
<div className="header-icon">
{headerHeight.maxHeight == '0px' ? <CaretRightOutlined /> : <CaretDownOutlined />}
</div>
<div className="header-text">{header}</div>
</div>
{extra && <div className="right">{extra}</div>}
</div>
);
} else if (headerAlign == 'right') {
return (
<div
className="collapse-item-header"
onClick={toggleContent}
style={disabled ? { color: '#c9cdd4', cursor: 'not-allowed' } : {}}
>
<div className="left">
<div className="header-text">{header}</div>
</div>
<div className="right">
{extra}
<div className="header-icon">
{headerHeight.maxHeight == '0px' ? <CaretLeftOutlined /> : <CaretDownOutlined />}
</div>
</div>
</div>
);
} else if (headerAlign == 'hide') {
return (
<div
className="collapse-item-header"
onClick={toggleContent}
style={disabled ? { color: '#c9cdd4', cursor: 'not-allowed' } : {}}
>
<div className="left">
<div className="header-text">{header}</div>
</div>
<div className="right">{extra}</div>
</div>
);
}
}, [headerAlign, headerHeight, disabled]);
return (
<div className="collapse-item">
{renderHeader}
<div className="collapse-item-content" style={headerHeight}>
{lazyLoad ? hasOpen && children : children}
</div>
</div>
);
};
export default memo(CollapseItem);
- Concis组件库线上链接:react-view-ui.com:92
- github:github.com/fengxinhhh/…
- npm:www.npmjs.com/package/con…
开源不易,欢迎学习和体验,喜欢请多多支持,有问题请留言。