131 lines
3.2 KiB
TypeScript
131 lines
3.2 KiB
TypeScript
import breakpoints from '@/config/breakpoints';
|
|
import InfiniteScroller from '@/pages/_components/infinite-scroller';
|
|
import { useScrollerContext } from '@/pages/_components/infinite-scroller/use-scroller-context';
|
|
import { Col, FloatButton, Row, Spin } from 'antd';
|
|
import _ from 'lodash';
|
|
import ResizeObserver from 'rc-resize-observer';
|
|
import React, { useCallback } from 'react';
|
|
import styled from 'styled-components';
|
|
|
|
const SpinWrapper = styled.div`
|
|
width: 100%;
|
|
position: absolute;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
top: 0;
|
|
left: 0;
|
|
max-height: 400px;
|
|
right: 0;
|
|
.skelton-wrapper {
|
|
width: 100%;
|
|
}
|
|
`;
|
|
|
|
interface CatalogListProps {
|
|
defaultSpan?: number;
|
|
resizable?: boolean;
|
|
dataList: any[];
|
|
loading: boolean;
|
|
activeId: Global.WithFalse<number>;
|
|
isFirst: boolean;
|
|
renderItem: (data: any) => React.ReactNode;
|
|
Skeleton: React.ComponentType<{ span: number }>;
|
|
}
|
|
|
|
const InnerSkeleton = (Skeleton: React.ComponentType<{ span: number }>) => {
|
|
return (props: any) => {
|
|
return <Skeleton {...props} />;
|
|
};
|
|
};
|
|
|
|
const ListSkeleton: React.FC<{
|
|
span: number;
|
|
loading: boolean;
|
|
isFirst: boolean;
|
|
Skeleton: React.ComponentType<{ span: number }>;
|
|
}> = ({ span, loading, isFirst, Skeleton }) => {
|
|
const CatalogSkeleton = InnerSkeleton(Skeleton);
|
|
return (
|
|
<div>
|
|
{loading && (
|
|
<SpinWrapper>
|
|
<Spin
|
|
spinning={loading}
|
|
style={{
|
|
width: '100%'
|
|
}}
|
|
wrapperClassName="skelton-wrapper"
|
|
>
|
|
{isFirst && <CatalogSkeleton span={span}></CatalogSkeleton>}
|
|
</Spin>
|
|
</SpinWrapper>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const CardList: React.FC<CatalogListProps> = (props) => {
|
|
const {
|
|
dataList,
|
|
loading,
|
|
isFirst,
|
|
defaultSpan = 8,
|
|
resizable = true,
|
|
Skeleton,
|
|
renderItem
|
|
} = props;
|
|
const [span, setSpan] = React.useState(defaultSpan);
|
|
const {
|
|
total,
|
|
current,
|
|
loading: contextLoading,
|
|
refresh
|
|
} = useScrollerContext();
|
|
|
|
const getSpanByWidth = (width: number) => {
|
|
if (width < breakpoints.md) return 24;
|
|
if (width < breakpoints.lg) return 12;
|
|
return 8;
|
|
};
|
|
|
|
const handleResize = useCallback(
|
|
_.throttle((size: { width: number; height: number }) => {
|
|
setSpan(getSpanByWidth(size.width));
|
|
}, 100),
|
|
[]
|
|
);
|
|
|
|
return (
|
|
<div className="relative" style={{ width: '100%' }}>
|
|
<ResizeObserver onResize={handleResize} disabled={!resizable}>
|
|
<InfiniteScroller
|
|
total={total}
|
|
current={current}
|
|
loading={contextLoading}
|
|
refresh={refresh}
|
|
>
|
|
<Row gutter={[16, 16]}>
|
|
{dataList.map((item: any, index) => {
|
|
return (
|
|
<Col span={span} key={item.id}>
|
|
{renderItem?.(item)}
|
|
</Col>
|
|
);
|
|
})}
|
|
</Row>
|
|
<ListSkeleton
|
|
span={span}
|
|
loading={loading}
|
|
isFirst={isFirst}
|
|
Skeleton={Skeleton}
|
|
/>
|
|
</InfiniteScroller>
|
|
</ResizeObserver>
|
|
<FloatButton.BackTop visibilityHeight={1000} />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default CardList;
|