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 { DocCardTags } from './tag';
const calcRowsById = (id: string) => {
const [MIN, MAX] = [2, 8];
export const calcRowsById = (id: string, min = 2, max = 8) => {
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'> {

View File

@@ -3,11 +3,16 @@ import { style } from '@vanilla-extract/css';
export const paddingX = 16;
export const columnGap = 17;
export const masonry = style({
export const columns = style({
padding: `16px ${paddingX}px`,
columnGap: columnGap,
display: 'flex',
gap: columnGap,
});
export const masonryItem = style({
breakInside: 'avoid',
marginBottom: 10,
export const column = style({
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 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';
const calcColumnCount = () => {
@@ -14,6 +14,20 @@ const calcColumnCount = () => {
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 = ({
items,
showTags,
@@ -28,16 +42,24 @@ export const MasonryDocs = ({
}, []);
useGlobalEvent('resize', updateColumnCount);
const columns = useMemo(
() => calcColumns(items, columnCount),
[items, columnCount]
);
return (
<div className={styles.masonry} style={{ columnCount }}>
{items.map(item => (
<DocCard
key={item.id}
className={styles.masonryItem}
showTags={showTags}
meta={item}
autoHeightById
/>
<div className={styles.columns}>
{columns.map((col, index) => (
<div key={`${columnCount}-${index}`} className={styles.column}>
{col.map(item => (
<DocCard
key={item.id}
showTags={showTags}
meta={item}
autoHeightById
/>
))}
</div>
))}
</div>
);