feat(mobile): impl masonry docs with flex and predict card height (#8849)

previous `columns` implementation has some limitation:
- the card order is not as expected
- there may be strange shadow on top
This commit is contained in:
CatsJuice
2024-11-18 08:30:07 +00:00
parent bf093710b7
commit bd7c422c46
3 changed files with 45 additions and 20 deletions

View File

@@ -17,11 +17,9 @@ import { forwardRef, type ReactNode, useMemo } from 'react';
import * as styles from './styles.css'; import * as styles from './styles.css';
import { DocCardTags } from './tag'; import { DocCardTags } from './tag';
const calcRowsById = (id: string) => { export const calcRowsById = (id: string, min = 2, max = 8) => {
const [MIN, MAX] = [2, 8];
const code = id.charCodeAt(0); const code = id.charCodeAt(0);
return Math.floor((code % (MAX - MIN)) + MIN); return Math.floor((code % (max - min)) + min);
}; };
export interface DocCardProps extends Omit<WorkbenchLinkProps, 'to'> { export interface DocCardProps extends Omit<WorkbenchLinkProps, 'to'> {

View File

@@ -3,11 +3,16 @@ import { style } from '@vanilla-extract/css';
export const paddingX = 16; export const paddingX = 16;
export const columnGap = 17; export const columnGap = 17;
export const masonry = style({ export const columns = style({
padding: `16px ${paddingX}px`, padding: `16px ${paddingX}px`,
columnGap: columnGap, display: 'flex',
gap: columnGap,
}); });
export const masonryItem = style({
breakInside: 'avoid', export const column = style({
marginBottom: 10, display: 'flex',
flexDirection: 'column',
gap: 10,
width: 0,
flex: 1,
}); });

View File

@@ -1,8 +1,8 @@
import { useGlobalEvent } from '@affine/core/mobile/hooks/use-global-events'; import { useGlobalEvent } from '@affine/core/mobile/hooks/use-global-events';
import type { DocMeta } from '@blocksuite/affine/store'; import type { DocMeta } from '@blocksuite/affine/store';
import { useCallback, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { DocCard } from '../../../components'; import { calcRowsById, DocCard } from '../../../components';
import * as styles from './masonry.css'; import * as styles from './masonry.css';
const calcColumnCount = () => { const calcColumnCount = () => {
@@ -14,6 +14,20 @@ const calcColumnCount = () => {
return Math.max(newColumnCount, 2); return Math.max(newColumnCount, 2);
}; };
const calcColumns = (items: DocMeta[], length: number) => {
const columns = Array.from({ length }, () => [] as DocMeta[]);
const heights = Array.from({ length }, () => 0);
items.forEach(item => {
const itemHeight = calcRowsById(item.id);
const minHeightIndex = heights.indexOf(Math.min(...heights));
heights[minHeightIndex] += itemHeight;
columns[minHeightIndex].push(item);
});
return columns;
};
export const MasonryDocs = ({ export const MasonryDocs = ({
items, items,
showTags, showTags,
@@ -28,16 +42,24 @@ export const MasonryDocs = ({
}, []); }, []);
useGlobalEvent('resize', updateColumnCount); useGlobalEvent('resize', updateColumnCount);
const columns = useMemo(
() => calcColumns(items, columnCount),
[items, columnCount]
);
return ( return (
<div className={styles.masonry} style={{ columnCount }}> <div className={styles.columns}>
{items.map(item => ( {columns.map((col, index) => (
<DocCard <div key={`${columnCount}-${index}`} className={styles.column}>
key={item.id} {col.map(item => (
className={styles.masonryItem} <DocCard
showTags={showTags} key={item.id}
meta={item} showTags={showTags}
autoHeightById meta={item}
/> autoHeightById
/>
))}
</div>
))} ))}
</div> </div>
); );