coze-studio/frontend/packages/community/component/src/infinite-list/index.tsx

152 lines
4.2 KiB
TypeScript

/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
forwardRef,
useImperativeHandle,
type RefObject,
useEffect,
} from 'react';
import cls from 'classnames';
import { ResponsiveList } from '@coze-arch/responsive-kit';
import { List } from '@coze-arch/bot-semi';
import { type InfiniteListProps, type InfiniteListRef } from './type';
import useScroll from './hooks/use-scroll';
import Footer from './components/footer';
import Empty from './components/empty';
import s from './index.module.less';
/* Plugin header */
function Index<T extends object>(props: InfiniteListProps<T>, ref) {
const {
isSearching,
className,
emptyContent,
grid,
renderItem,
itemClassName,
renderFooter,
scrollConf,
emptyConf,
onChangeState,
canShowData = true,
isNeedBtnLoadMore = false,
isResponsive,
retryFunc,
responsiveConf,
containerClassName,
} = props;
const {
dataList,
isLoading,
loadMore,
noMore,
isLoadingError,
mutate,
reload,
insertData,
removeData,
getDataList,
} = useScroll<T>({ ...scrollConf, isNeedBtnLoadMore });
useImperativeHandle(
ref,
() => ({ mutate, reload, insertData, removeData, getDataList }),
[mutate, reload, insertData, removeData, getDataList],
);
useEffect(() => {
onChangeState?.(isLoading, dataList);
}, [dataList, isLoading]);
// Adapt the mobile end of the list according to the whitelist
return (
<div className={cls(s['height-whole-100'], containerClassName)}>
{!dataList?.length || !canShowData ? (
/** How to display an empty page when the data is empty */
<Empty
isError={canShowData ? isLoadingError : false}
isSearching={isSearching}
isLoading={canShowData ? isLoading : true}
loadRetry={retryFunc || loadMore}
{...emptyConf}
/>
) : isResponsive ? (
<ResponsiveList<T>
className={className}
emptyContent={isLoading ? <></> : emptyContent}
dataSource={dataList}
renderItem={(item, number) => renderItem?.(item, number)}
gridCols={responsiveConf?.gridCols}
gridGapXs={{
basic: 4,
}}
footer={
<div className="text-sm px-6 py-3">
<Footer
isError={isLoadingError}
noMore={noMore}
isLoading={isLoading}
loadRetry={retryFunc || loadMore}
renderFooter={renderFooter}
isNeedBtnLoadMore={isNeedBtnLoadMore}
/>
</div>
}
/>
) : (
<List
{...{ className, emptyContent, grid }}
emptyContent={isLoading ? <></> : emptyContent}
dataSource={dataList}
split={false}
renderItem={(item, number) => (
<List.Item
className={
typeof itemClassName === 'string'
? itemClassName
: itemClassName?.(item) // Support dynamic row className
}
>
{renderItem?.(item, number)}
</List.Item>
)}
footer={
<Footer
isError={isLoadingError}
noMore={noMore}
isLoading={isLoading}
loadRetry={retryFunc || loadMore}
renderFooter={renderFooter}
isNeedBtnLoadMore={isNeedBtnLoadMore}
dataNum={dataList?.length}
/>
}
/>
)}
</div>
);
}
export const InfiniteList = forwardRef(Index) as <T>(
props: InfiniteListProps<T> & { ref?: RefObject<InfiniteListRef> },
) => JSX.Element;