mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-19 07:17:00 +08:00
refactor(mobile): impl all-docs masonry with css grid, close AF-1598 (#8731)
- responsive layout support 
This commit is contained in:
@@ -10,21 +10,36 @@ import {
|
|||||||
import type { DocMeta } from '@blocksuite/affine/store';
|
import type { DocMeta } from '@blocksuite/affine/store';
|
||||||
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
|
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { forwardRef, type ReactNode } from 'react';
|
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) => {
|
||||||
|
const [MIN, MAX] = [2, 8];
|
||||||
|
|
||||||
|
const code = id.charCodeAt(0);
|
||||||
|
return Math.floor((code % (MAX - MIN)) + MIN);
|
||||||
|
};
|
||||||
|
|
||||||
export interface DocCardProps extends Omit<WorkbenchLinkProps, 'to'> {
|
export interface DocCardProps extends Omit<WorkbenchLinkProps, 'to'> {
|
||||||
meta: {
|
meta: {
|
||||||
id: DocMeta['id'];
|
id: DocMeta['id'];
|
||||||
title?: ReactNode;
|
title?: ReactNode;
|
||||||
} & { [key: string]: any };
|
} & { [key: string]: any };
|
||||||
showTags?: boolean;
|
showTags?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When enabled, preview's height will be calculated based on `meta.id`
|
||||||
|
*/
|
||||||
|
autoHeightById?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DocCard = forwardRef<HTMLAnchorElement, DocCardProps>(
|
export const DocCard = forwardRef<HTMLAnchorElement, DocCardProps>(
|
||||||
function DocCard({ showTags = true, meta, className, ...attrs }, ref) {
|
function DocCard(
|
||||||
|
{ showTags = true, meta, className, autoHeightById, ...attrs },
|
||||||
|
ref
|
||||||
|
) {
|
||||||
const favAdapter = useService(CompatibleFavoriteItemsAdapter);
|
const favAdapter = useService(CompatibleFavoriteItemsAdapter);
|
||||||
const workspace = useService(WorkspaceService).workspace;
|
const workspace = useService(WorkspaceService).workspace;
|
||||||
|
|
||||||
@@ -38,6 +53,12 @@ export const DocCard = forwardRef<HTMLAnchorElement, DocCardProps>(
|
|||||||
[favAdapter, meta.id]
|
[favAdapter, meta.id]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const contentStyle = useMemo(() => {
|
||||||
|
if (!autoHeightById) return { flex: 1 };
|
||||||
|
const rows = calcRowsById(meta.id);
|
||||||
|
return { height: `${rows * 18}px` };
|
||||||
|
}, [autoHeightById, meta.id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkbenchLink
|
<WorkbenchLink
|
||||||
to={`/${meta.id}`}
|
to={`/${meta.id}`}
|
||||||
@@ -57,7 +78,7 @@ export const DocCard = forwardRef<HTMLAnchorElement, DocCardProps>(
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</header>
|
</header>
|
||||||
<main className={styles.content}>
|
<main className={styles.content} style={contentStyle}>
|
||||||
<PagePreview
|
<PagePreview
|
||||||
docCollection={workspace.docCollection}
|
docCollection={workspace.docCollection}
|
||||||
pageId={meta.id}
|
pageId={meta.id}
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ export const untitled = style({
|
|||||||
export const content = style([
|
export const content = style([
|
||||||
footnoteRegular,
|
footnoteRegular,
|
||||||
{
|
{
|
||||||
flex: 1,
|
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -1,28 +1,13 @@
|
|||||||
import { style } from '@vanilla-extract/css';
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
export const invisibleWrapper = style({
|
export const paddingX = 16;
|
||||||
position: 'absolute',
|
export const columnGap = 17;
|
||||||
padding: 'inherit',
|
|
||||||
width: '100%',
|
export const masonry = style({
|
||||||
height: 0,
|
padding: `16px ${paddingX}px`,
|
||||||
overflow: 'hidden',
|
columnGap: columnGap,
|
||||||
visibility: 'hidden',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
});
|
});
|
||||||
export const invisibleList = style({
|
export const masonryItem = style({
|
||||||
width: `calc(50% - 17px / 2)`,
|
breakInside: 'avoid',
|
||||||
});
|
marginBottom: 10,
|
||||||
export const stacks = style({
|
|
||||||
position: 'relative',
|
|
||||||
width: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
gap: 17,
|
|
||||||
padding: 16,
|
|
||||||
});
|
|
||||||
export const stack = style({
|
|
||||||
width: 0,
|
|
||||||
flex: 1,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: 10,
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
|
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 { useMemo } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
import { DocCard } from '../../../components';
|
import { DocCard } from '../../../components';
|
||||||
import * as styles from './masonry.css';
|
import * as styles from './masonry.css';
|
||||||
|
|
||||||
// TODO(@CatsJuice): Large amount docs performance
|
const calcColumnCount = () => {
|
||||||
|
const maxCardWidth = 220;
|
||||||
|
const windowWidth = window.innerWidth;
|
||||||
|
const newColumnCount = Math.floor(
|
||||||
|
(windowWidth - styles.paddingX * 2 - styles.columnGap) / maxCardWidth
|
||||||
|
);
|
||||||
|
return Math.max(newColumnCount, 2);
|
||||||
|
};
|
||||||
|
|
||||||
export const MasonryDocs = ({
|
export const MasonryDocs = ({
|
||||||
items,
|
items,
|
||||||
showTags,
|
showTags,
|
||||||
@@ -12,25 +21,23 @@ export const MasonryDocs = ({
|
|||||||
items: DocMeta[];
|
items: DocMeta[];
|
||||||
showTags?: boolean;
|
showTags?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
// card preview is loaded lazily, it's meaningless to calculate height
|
const [columnCount, setColumnCount] = useState(calcColumnCount);
|
||||||
const stacks = useMemo(() => {
|
|
||||||
return items.reduce(
|
const updateColumnCount = useCallback(() => {
|
||||||
(acc, item, i) => {
|
setColumnCount(calcColumnCount());
|
||||||
acc[i % 2].push(item);
|
}, []);
|
||||||
return acc;
|
useGlobalEvent('resize', updateColumnCount);
|
||||||
},
|
|
||||||
[[], []] as [DocMeta[], DocMeta[]]
|
|
||||||
);
|
|
||||||
}, [items]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.stacks}>
|
<div className={styles.masonry} style={{ columnCount }}>
|
||||||
{stacks.map((stack, i) => (
|
{items.map(item => (
|
||||||
<ul key={i} className={styles.stack}>
|
<DocCard
|
||||||
{stack.map(item => (
|
key={item.id}
|
||||||
<DocCard showTags={showTags} key={item.id} meta={item} />
|
className={styles.masonryItem}
|
||||||
))}
|
showTags={showTags}
|
||||||
</ul>
|
meta={item}
|
||||||
|
autoHeightById
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user