feat: add new page button (#2417)

This commit is contained in:
Whitewater
2023-05-18 13:07:05 -07:00
committed by GitHub
parent 11370bc07e
commit 530dd5ff7f
16 changed files with 378 additions and 116 deletions

View File

@@ -9,6 +9,7 @@ export const blockCard = style({
borderRadius: '4px',
userSelect: 'none',
cursor: 'pointer',
textAlign: 'start',
selectors: {
'&:hover': {
boxShadow: 'var(--affine-shadow-1)',

View File

@@ -18,8 +18,10 @@ import {
FavoriteIcon,
} from '@blocksuite/icons';
import { useMediaQuery, useTheme } from '@mui/material';
import type { CSSProperties } from 'react';
import { forwardRef } from 'react';
import { NewPageButton } from './new-page-buttton';
import {
StyledTableContainer,
StyledTableRow,
@@ -68,11 +70,8 @@ const FavoriteTag = forwardRef<
export type PageListProps = {
isPublicWorkspace?: boolean;
list: ListData[];
/**
* @deprecated
*/
listType: 'all' | 'favorite' | 'shared' | 'public';
onClickPage: (pageId: string, newTab?: boolean) => void;
onCreateNewPage: () => void;
onCreateNewEdgeless: () => void;
};
const TitleCell = ({
@@ -100,6 +99,80 @@ const TitleCell = ({
);
};
const AllPagesHead = ({
sorter,
createNewPage,
createNewEdgeless,
}: {
sorter: ReturnType<typeof useSorter<ListData>>;
createNewPage: () => void;
createNewEdgeless: () => void;
}) => {
const t = useAFFiNEI18N();
const titleList = [
{
key: 'title',
content: t['Title'](),
proportion: 0.5,
},
{
key: 'createDate',
content: t['Created'](),
proportion: 0.2,
},
{
key: 'updatedDate',
content: t['Updated'](),
proportion: 0.2,
},
{
key: 'unsortable_action',
content: (
<NewPageButton
createNewPage={createNewPage}
createNewEdgeless={createNewEdgeless}
/>
),
sortable: false,
styles: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
} satisfies CSSProperties,
},
];
return (
<TableHead>
<TableRow>
{titleList.map(
({ key, content, proportion, sortable = true, styles }) => (
<TableCell
key={key}
proportion={proportion}
active={sorter.key === key}
onClick={
sortable
? () => sorter.shiftOrder(key as keyof ListData)
: undefined
}
style={styles}
>
{content}
{sorter.key === key &&
(sorter.order === 'asc' ? (
<ArrowUpBigIcon width={24} height={24} />
) : (
<ArrowDownBigIcon width={24} height={24} />
))}
</TableCell>
)
)}
</TableRow>
</TableHead>
);
};
export type ListData = {
pageId: string;
icon: JSX.Element;
@@ -119,7 +192,8 @@ export type ListData = {
export const PageList: React.FC<PageListProps> = ({
isPublicWorkspace = false,
list,
listType,
onCreateNewPage,
onCreateNewEdgeless,
}) => {
const t = useAFFiNEI18N();
const sorter = useSorter<ListData>({
@@ -128,70 +202,12 @@ export const PageList: React.FC<PageListProps> = ({
order: 'desc',
});
const isShared = listType === 'shared';
const theme = useTheme();
const isSmallDevices = useMediaQuery(theme.breakpoints.down('sm'));
if (isSmallDevices) {
return <PageListMobileView list={sorter.data} />;
}
const ListHead = () => {
const t = useAFFiNEI18N();
const titleList = [
{
key: 'title',
text: t['Title'](),
proportion: 0.5,
},
{
key: 'createDate',
text: t['Created'](),
proportion: 0.2,
},
{
key: 'updatedDate',
text: isShared
? // TODO deprecated
'Shared'
: t['Updated'](),
proportion: 0.2,
},
{ key: 'unsortable_action', sortable: false },
];
return (
<TableHead>
<TableRow>
{titleList.map(({ key, text, proportion, sortable = true }) => (
<TableCell
key={key}
proportion={proportion}
active={sorter.key === key}
onClick={
sortable
? () => sorter.shiftOrder(key as keyof ListData)
: undefined
}
>
<div
style={{ display: 'flex', alignItems: 'center', width: '100%' }}
>
{text}
{sorter.key === key &&
(sorter.order === 'asc' ? (
<ArrowUpBigIcon width={24} height={24} />
) : (
<ArrowDownBigIcon width={24} height={24} />
))}
</div>
</TableCell>
))}
</TableRow>
</TableHead>
);
};
const ListItems = sorter.data.map(
(
{
@@ -237,7 +253,13 @@ export const PageList: React.FC<PageListProps> = ({
</TableCell>
{!isPublicWorkspace && (
<TableCell
style={{ padding: 0, display: 'flex', alignItems: 'center' }}
style={{
padding: 0,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
gap: '10px',
}}
data-testid={`more-actions-${pageId}`}
>
<FavoriteTag
@@ -264,7 +286,11 @@ export const PageList: React.FC<PageListProps> = ({
return (
<StyledTableContainer>
<Table>
<ListHead />
<AllPagesHead
sorter={sorter}
createNewPage={onCreateNewPage}
createNewEdgeless={onCreateNewEdgeless}
/>
<TableBody>{ListItems}</TableBody>
</Table>
</StyledTableContainer>

View File

@@ -0,0 +1,85 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
import { useState } from 'react';
import { DropdownButton } from '../../ui/button/dropdown';
import { Menu } from '../../ui/menu/menu';
import { BlockCard } from '../card/block-card';
type NewPageButtonProps = {
createNewPage: () => void;
createNewEdgeless: () => void;
};
export const CreateNewPagePopup = ({
createNewPage,
createNewEdgeless,
}: NewPageButtonProps) => {
const t = useAFFiNEI18N();
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: '8px',
padding: '8px',
}}
>
<BlockCard
title={t['New Page']()}
desc={t['com.affine.write_with_a_blank_page']()}
right={<PageIcon width={20} height={20} />}
onClick={createNewPage}
/>
<BlockCard
title={t['com.affine.new_edgeless']()}
desc={t['com.affine.draw_with_a_blank_whiteboard']()}
right={<EdgelessIcon width={20} height={20} />}
onClick={createNewEdgeless}
/>
{/* TODO Import */}
</div>
);
};
export const NewPageButton = ({
createNewPage,
createNewEdgeless,
}: NewPageButtonProps) => {
const t = useAFFiNEI18N();
const [open, setOpen] = useState(false);
return (
<Menu
visible={open}
placement="bottom-end"
trigger={['click']}
disablePortal={true}
onClickAway={() => {
setOpen(false);
}}
menuStyles={{ padding: '0px' }}
content={
<CreateNewPagePopup
createNewPage={() => {
createNewPage();
setOpen(false);
}}
createNewEdgeless={() => {
createNewEdgeless();
setOpen(false);
}}
/>
}
>
<DropdownButton
onClick={() => {
createNewPage();
setOpen(false);
}}
onClickDropDown={() => setOpen(!open)}
>
{t['New Page']()}
</DropdownButton>
</Menu>
);
};

View File

@@ -1,6 +1,6 @@
import { useState } from 'react';
type Sorter<T> = {
type SorterConfig<T> = {
data: T[];
key: keyof T;
order: 'asc' | 'desc' | 'none';
@@ -32,8 +32,8 @@ const defaultSortingFn = <T extends Record<keyof any, unknown>>(
export const useSorter = <T extends Record<keyof any, unknown>>({
data,
...defaultSorter
}: Sorter<T> & { order: 'asc' | 'desc' }) => {
const [sorter, setSorter] = useState<Omit<Sorter<T>, 'data'>>({
}: SorterConfig<T> & { order: 'asc' | 'desc' }) => {
const [sorter, setSorter] = useState<Omit<SorterConfig<T>, 'data'>>({
...defaultSorter,
// We should not show sorting icon at first time
order: 'none',
@@ -74,7 +74,7 @@ export const useSorter = <T extends Record<keyof any, unknown>>({
/**
* @deprecated In most cases, we no necessary use `setSorter` directly.
*/
updateSorter: (newVal: Partial<Sorter<T>>) =>
updateSorter: (newVal: Partial<SorterConfig<T>>) =>
setSorter({ ...sorter, ...newVal }),
shiftOrder,
resetSorter: () => setSorter(defaultSorter),