feat: replace menu with new design (#4012)

Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
Qi
2023-09-06 12:36:43 +08:00
committed by GitHub
parent ef3d3a34e2
commit d8c9f10bc1
51 changed files with 611 additions and 739 deletions

View File

@@ -1,138 +1,69 @@
import {
Menu,
MenuItem,
type MenuProps,
MenuTrigger,
styled,
} from '@affine/component';
import { LOCALES, useI18N } from '@affine/i18n';
import { assertExists } from '@blocksuite/global/utils';
import type { ButtonProps } from '@toeverything/components/button';
import { useCallback, useEffect, useState } from 'react';
export const StyledListItem = styled(MenuItem)(() => ({
height: '38px',
textTransform: 'capitalize',
}));
interface LanguageMenuContentProps {
currentLanguage: string;
currentLanguageIndex: number;
}
import { LOCALES } from '@affine/i18n';
import { useI18N } from '@affine/i18n';
import { Menu, MenuItem, MenuTrigger } from '@toeverything/components/menu';
import type { ReactElement } from 'react';
import { useCallback, useMemo, useRef } from 'react';
// Fixme: keyboard focus should be supported by Menu component
const LanguageMenuContent = ({
currentLanguage,
currentLanguageIndex,
}: LanguageMenuContentProps) => {
const i18n = useI18N();
const changeLanguage = useCallback(
(targetLanguage: string) => {
console.assert(
LOCALES.some(item => item.tag === targetLanguage),
'targetLanguage should be one of the LOCALES'
);
i18n.changeLanguage(targetLanguage).catch(err => {
console.error('Failed to change language', err);
});
},
[i18n]
);
const [focusedOptionIndex, setFocusedOptionIndex] = useState(
currentLanguageIndex ?? 0
);
const handleKeyDown = useCallback(
(event: KeyboardEvent) => {
switch (event.key) {
case 'ArrowUp':
event.preventDefault();
setFocusedOptionIndex(prevIndex =>
prevIndex > 0 ? prevIndex - 1 : 0
);
break;
case 'ArrowDown':
event.preventDefault();
setFocusedOptionIndex(prevIndex =>
prevIndex < LOCALES.length - 1 ? prevIndex + 1 : LOCALES.length
);
break;
case 'Enter':
if (focusedOptionIndex !== -1) {
const selectedOption = LOCALES[focusedOptionIndex];
changeLanguage(selectedOption.tag);
}
break;
default:
break;
}
},
[changeLanguage, focusedOptionIndex]
);
useEffect(() => {
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [handleKeyDown]);
onSelect,
}: {
currentLanguage?: string;
onSelect: (value: string) => void;
}) => {
return (
<>
{LOCALES.map((option, optionIndex) => {
{LOCALES.map(option => {
return (
<StyledListItem
<MenuItem
key={option.name}
active={option.tag === currentLanguage}
userFocused={optionIndex == focusedOptionIndex}
selected={currentLanguage === option.originalName}
title={option.name}
onClick={() => {
changeLanguage(option.tag);
}}
onSelect={() => onSelect(option.tag)}
>
{option.originalName}
</StyledListItem>
</MenuItem>
);
})}
</>
);
};
interface LanguageMenuProps extends Omit<MenuProps, 'children'> {
triggerProps?: ButtonProps;
}
export const LanguageMenu = ({
triggerProps,
...menuProps
}: LanguageMenuProps) => {
export const LanguageMenu = () => {
const i18n = useI18N();
const currentLanguageIndex = LOCALES.findIndex(
item => item.tag === i18n.language
const ref = useRef(null);
const currentLanguage = useMemo(
() => LOCALES.find(item => item.tag === i18n.language),
[i18n.language]
);
const onSelect = useCallback(
(event: string) => {
return i18n.changeLanguage(event);
},
[i18n]
);
const currentLanguage = LOCALES[currentLanguageIndex];
assertExists(currentLanguage, 'currentLanguage should exist');
return (
<Menu
content={
<LanguageMenuContent
currentLanguage={currentLanguage.tag}
currentLanguageIndex={currentLanguageIndex}
/>
items={
(
<LanguageMenuContent
currentLanguage={currentLanguage?.originalName}
onSelect={onSelect}
/>
) as ReactElement
}
placement="bottom-end"
trigger="click"
disablePortal={true}
{...menuProps}
portalOptions={{
container: ref.current,
}}
>
<MenuTrigger
ref={ref}
data-testid="language-menu-button"
style={{ textTransform: 'capitalize' }}
{...triggerProps}
style={{ textTransform: 'capitalize', fontWeight: 600 }}
block={true}
>
{currentLanguage.originalName}
{currentLanguage?.originalName || ''}
</MenuTrigger>
</Menu>
);

View File

@@ -0,0 +1,5 @@
import { style } from '@vanilla-extract/css';
export const hoveredLanguageItem = style({
background: 'var(--affine-hover-color)',
});

View File

@@ -1,4 +1,3 @@
import { Menu, MenuItem } from '@affine/component';
import {
InviteModal,
type InviteModalProps,
@@ -12,6 +11,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { MoreVerticalIcon } from '@blocksuite/icons';
import { Avatar } from '@toeverything/components/avatar';
import { Button, IconButton } from '@toeverything/components/button';
import { Menu, MenuItem } from '@toeverything/components/menu';
import { Tooltip } from '@toeverything/components/tooltip';
import clsx from 'clsx';
import { useSetAtom } from 'jotai/react';
@@ -190,14 +190,11 @@ const MemberItem = ({
: 'Pending'}
</div>
<Menu
content={
items={
<MenuItem data-member-id={member.id} onClick={handleRevoke}>
{operationButtonInfo.leaveOrRevokeText}
</MenuItem>
}
placement="bottom"
disablePortal={true}
trigger="click"
>
<IconButton
disabled={!operationButtonInfo.show}

View File

@@ -1,6 +1,6 @@
import { Menu, MenuItem, MenuTrigger } from '@affine/component';
import { Menu, MenuItem, MenuTrigger } from '@toeverything/components/menu';
import dayjs from 'dayjs';
import { useCallback } from 'react';
import { useCallback, useRef } from 'react';
import {
dateFormatOptions,
@@ -23,10 +23,8 @@ const DateFormatMenuContent = ({
return (
<MenuItem
key={option}
active={currentOption === option}
onClick={() => {
onSelect(option);
}}
selected={currentOption === option}
onSelect={() => onSelect(option)}
>
{dayjs(new Date()).format(option)}
</MenuItem>
@@ -37,6 +35,7 @@ const DateFormatMenuContent = ({
};
export const DateFormatSetting = () => {
const ref = useRef(null);
const [appearanceSettings, setAppSettings] = useAppSetting();
const handleSelect = useCallback(
(option: DateFormats) => {
@@ -47,17 +46,15 @@ export const DateFormatSetting = () => {
return (
<Menu
content={
items={
<DateFormatMenuContent
onSelect={handleSelect}
currentOption={appearanceSettings.dateFormat}
/>
}
placement="bottom-end"
trigger="click"
disablePortal={true}
portalOptions={{ container: ref.current }}
>
<MenuTrigger data-testid="date-format-menu-trigger">
<MenuTrigger ref={ref} data-testid="date-format-menu-trigger" block>
{dayjs(new Date()).format(appearanceSettings.dateFormat)}
</MenuTrigger>
</Menu>

View File

@@ -113,17 +113,7 @@ export const AppearanceSettings = () => {
desc={t['com.affine.settings.appearance.language-description']()}
>
<div className={settingWrapper}>
<LanguageMenu
triggerContainerStyle={{ width: '100%' }}
triggerProps={{
style: {
width: '100%',
justifyContent: 'space-between',
fontWeight: 600,
padding: '0 10px',
},
}}
/>
<LanguageMenu />
</div>
</SettingRow>
{environment.isDesktop ? (

View File

@@ -1,4 +1,4 @@
import { FlexWrapper, Menu, MenuItem } from '@affine/component';
import { FlexWrapper } from '@affine/component';
import { Export, MoveToTrash } from '@affine/component/page-list';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { assertExists } from '@blocksuite/global/utils';
@@ -12,14 +12,19 @@ import {
PageIcon,
} from '@blocksuite/icons';
import type { PageMeta } from '@blocksuite/store';
import { Divider } from '@toeverything/components/divider';
import {
Menu,
MenuIcon,
MenuItem,
MenuSeparator,
} from '@toeverything/components/menu';
import {
useBlockSuitePageMeta,
usePageMetaHelper,
} from '@toeverything/hooks/use-block-suite-page-meta';
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
import { useAtom, useSetAtom } from 'jotai';
import { useCallback, useState } from 'react';
import { useCallback, useRef, useState } from 'react';
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
import { pageSettingFamily, setPageModeAtom } from '../../../atoms';
@@ -34,9 +39,10 @@ type PageMenuProps = {
rename?: () => void;
pageId: string;
};
// fixme: refactor this file
export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
const t = useAFFiNEI18N();
const ref = useRef(null);
// fixme(himself65): remove these hooks ASAP
const [workspace] = useCurrentWorkspace();
@@ -105,17 +111,25 @@ export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
const EditMenu = (
<>
<MenuItem
icon={<EditIcon />}
preFix={
<MenuIcon>
<EditIcon />
</MenuIcon>
}
data-testid="editor-option-menu-rename"
onClick={rename}
onSelect={rename}
style={menuItemStyle}
>
{t['Rename']()}
</MenuItem>
<MenuItem
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
preFix={
<MenuIcon>
{mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
</MenuIcon>
}
data-testid="editor-option-menu-edgeless"
onClick={handleSwitchMode}
onSelect={handleSwitchMode}
style={menuItemStyle}
>
{t['Convert to ']()}
@@ -123,14 +137,16 @@ export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
</MenuItem>
<MenuItem
data-testid="editor-option-menu-favorite"
onClick={handleFavorite}
onSelect={handleFavorite}
style={menuItemStyle}
icon={
favorite ? (
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
) : (
<FavoriteIcon />
)
preFix={
<MenuIcon>
{favorite ? (
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
) : (
<FavoriteIcon />
)}
</MenuIcon>
}
>
{favorite ? t['Remove from favorites']() : t['Add to Favorites']()}
@@ -144,28 +160,36 @@ export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
>
{t['com.affine.header.option.add-tag']()}
</MenuItem> */}
<Divider size="thinner" dividerColor="var(--affine-border-color)" />
<MenuSeparator />
<MenuItem
icon={<DuplicateIcon />}
preFix={
<MenuIcon>
<DuplicateIcon />
</MenuIcon>
}
data-testid="editor-option-menu-duplicate"
onClick={duplicate}
onSelect={duplicate}
style={menuItemStyle}
>
{t['com.affine.header.option.duplicate']()}
</MenuItem>
<MenuItem
icon={<ImportIcon />}
preFix={
<MenuIcon>
<ImportIcon />
</MenuIcon>
}
data-testid="editor-option-menu-import"
onClick={importFile}
onSelect={importFile}
style={menuItemStyle}
>
{t['Import']()}
</MenuItem>
<Export />
<Divider size="thinner" dividerColor="var(--affine-border-color)" />
<MenuSeparator />
<MoveToTrash
data-testid="editor-option-menu-delete"
onItemClick={() => {
onSelect={() => {
setOpenConfirm(true);
}}
/>
@@ -176,21 +200,19 @@ export const PageMenu = ({ rename, pageId }: PageMenuProps) => {
}
return (
<>
<FlexWrapper alignItems="center" justifyContent="center">
<FlexWrapper alignItems="center" justifyContent="center" ref={ref}>
<Menu
content={EditMenu}
placement="bottom-end"
disablePortal={true}
trigger="click"
menuStyles={{
borderRadius: '8px',
padding: '8px',
background: 'var(--affine-background-overlay-panel-color)',
items={EditMenu}
portalOptions={{
container: ref.current,
}}
// menuStyles={{
// borderRadius: '8px',
// padding: '8px',
// background: 'var(--affine-background-overlay-panel-color)',
// }}
>
<div>
<HeaderDropDownButton />
</div>
<HeaderDropDownButton />
</Menu>
<MoveToTrash.ConfirmModal
open={openConfirm}

View File

@@ -1,38 +1,28 @@
import { styled } from '@affine/component';
import { ArrowDownSmallIcon } from '@blocksuite/icons';
import {
IconButton,
type IconButtonProps,
} from '@toeverything/components/button';
import { forwardRef } from 'react';
const StyledIconButtonWithAnimate = styled(IconButton)(() => {
return {
svg: {
transition: 'transform 0.15s ease-in-out',
},
':hover': {
svg: {
transform: 'translateY(3px)',
},
backgroundColor: 'transparent !important',
},
};
});
import { headerMenuTrigger } from './styles.css';
// fixme(himself65): need to refactor
export const HeaderDropDownButton = ({
onClick,
...props
}: Omit<IconButtonProps, 'children'>) => {
export const HeaderDropDownButton = forwardRef<
HTMLButtonElement,
Omit<IconButtonProps, 'children'>
>((props, ref) => {
return (
<StyledIconButtonWithAnimate
data-testid="header-dropDownButton"
<IconButton
ref={ref}
{...props}
onClick={e => {
onClick?.(e);
}}
data-testid="header-dropDownButton"
className={headerMenuTrigger}
withoutHoverStyle={true}
type="plain"
>
<ArrowDownSmallIcon />
</StyledIconButtonWithAnimate>
</IconButton>
);
};
});
HeaderDropDownButton.displayName = 'HeaderDropDownButton';

View File

@@ -0,0 +1,10 @@
import { globalStyle, style } from '@vanilla-extract/css';
export const headerMenuTrigger = style({});
globalStyle(`${headerMenuTrigger} svg`, {
transition: 'transform 0.15s ease-in-out',
});
globalStyle(`${headerMenuTrigger}:hover svg`, {
transform: 'translateY(3px)',
});

View File

@@ -1,4 +1,3 @@
import { Menu, MenuItem } from '@affine/component';
import { WorkspaceList } from '@affine/component/workspace-list';
import type {
AffineCloudWorkspace,
@@ -19,6 +18,7 @@ import type { DragEndEvent } from '@dnd-kit/core';
import { Popover } from '@mui/material';
import { IconButton } from '@toeverything/components/button';
import { Divider } from '@toeverything/components/divider';
import { Menu, MenuIcon, MenuItem } from '@toeverything/components/menu';
import { useSetAtom } from 'jotai';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { signOut, useSession } from 'next-auth/react';
@@ -35,6 +35,7 @@ import {
StyledCreateWorkspaceCardPillContent,
StyledCreateWorkspaceCardPillIcon,
StyledImportWorkspaceCardPill,
StyledItem,
StyledModalBody,
StyledModalContent,
StyledModalFooterContent,
@@ -68,13 +69,12 @@ const AccountMenu = () => {
const setOpen = useSetAtom(openSettingModalAtom);
return (
<div>
{/* <div>Unlimted</div>
<Divider size="thinner" dividerColor="var(--affine-border-color)" />
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
{t['com.affine.workspace.cloud.join']()}
</MenuItem> */}
<MenuItem
icon={<AccountIcon />}
preFix={
<MenuIcon>
<AccountIcon />
</MenuIcon>
}
data-testid="editor-option-menu-import"
onClick={useCallback(() => {
setOpen(prev => ({ ...prev, open: true, activeTab: 'account' }));
@@ -84,7 +84,11 @@ const AccountMenu = () => {
</MenuItem>
<Divider />
<MenuItem
icon={<SignOutIcon />}
preFix={
<MenuIcon>
<SignOutIcon />
</MenuIcon>
}
data-testid="editor-option-menu-import"
onClick={useCallback(() => {
signOut().catch(console.error);
@@ -188,11 +192,7 @@ export const WorkspaceListModal = ({
{!isLoggedIn ? (
<StyledModalHeaderContent>
<StyledSignInCardPill>
<MenuItem
style={{
height: 'auto',
padding: '0px 12px',
}}
<StyledItem
onClick={async () => {
if (!runtimeConfig.enableCloud) {
setDisableCloudOpen(true);
@@ -218,7 +218,7 @@ export const WorkspaceListModal = ({
</StyledSignInCardPillTextSecondary>
</StyledSignInCardPillTextCotainer>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledItem>
</StyledSignInCardPill>
<Divider style={{ margin: '12px 0px' }} />
</StyledModalHeaderContent>
@@ -227,12 +227,7 @@ export const WorkspaceListModal = ({
<StyledModalHeader>
<StyledModalTitle>{session?.user.email}</StyledModalTitle>
<StyledOperationWrapper>
<Menu
placement="bottom-end"
trigger={['click']}
content={<AccountMenu />}
zIndex={1000}
>
<Menu items={<AccountMenu />}>
<IconButton
data-testid="more-button"
icon={<MoreHorizontalIcon />}
@@ -287,14 +282,7 @@ export const WorkspaceListModal = ({
</StyledModalContent>
{runtimeConfig.enableSQLiteProvider && environment.isDesktop ? (
<StyledImportWorkspaceCardPill>
<MenuItem
onClick={onAddWorkspace}
data-testid="add-workspace"
style={{
height: 'auto',
padding: '8px 12px',
}}
>
<StyledItem onClick={onAddWorkspace} data-testid="add-workspace">
<StyledCreateWorkspaceCardPillContent>
<StyledCreateWorkspaceCardPillIcon>
<ImportIcon />
@@ -303,20 +291,13 @@ export const WorkspaceListModal = ({
<p>{t['com.affine.workspace.local.import']()}</p>
</div>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledItem>
</StyledImportWorkspaceCardPill>
) : null}
</StyledModalBody>
<StyledModalFooterContent>
<StyledCreateWorkspaceCardPill>
<MenuItem
style={{
height: 'auto',
padding: '8px 12px',
}}
onClick={onNewWorkspace}
data-testid="new-workspace"
>
<StyledItem onClick={onNewWorkspace} data-testid="new-workspace">
<StyledCreateWorkspaceCardPillContent>
<StyledCreateWorkspaceCardPillIcon>
<PlusIcon />
@@ -325,7 +306,7 @@ export const WorkspaceListModal = ({
<p>{t['New Workspace']()}</p>
</div>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledItem>
</StyledCreateWorkspaceCardPill>
</StyledModalFooterContent>
</Popover>

View File

@@ -241,3 +241,32 @@ export const StyledWorkspaceFlavourTitle = styled('div')(() => {
marginBottom: '4px',
};
});
export const StyledItem = styled('button')<{
active?: boolean;
}>(({ active = false }) => {
return {
height: 'auto',
padding: '8px 12px',
width: '100%',
borderRadius: '5px',
fontSize: 'var(--affine-font-sm)',
...displayFlex('flex-start', 'center'),
cursor: 'pointer',
position: 'relative',
backgroundColor: 'transparent',
color: 'var(--affine-text-primary-color)',
svg: {
color: 'var(--affine-icon-color)',
},
':hover': {
backgroundColor: 'var(--affine-hover-color)',
},
...(active
? {
backgroundColor: 'var(--affine-hover-color)',
}
: {}),
};
});

View File

@@ -1,5 +1,4 @@
import { Menu } from '@affine/component';
import { MenuItem } from '@affine/component/app-sidebar';
import { MenuItem as CollectionItem } from '@affine/component/app-sidebar';
import {
EditCollectionModel,
useCollectionManager,
@@ -21,6 +20,13 @@ import type { PageMeta, Workspace } from '@blocksuite/store';
import type { DragEndEvent } from '@dnd-kit/core';
import { useDroppable } from '@dnd-kit/core';
import * as Collapsible from '@radix-ui/react-collapsible';
import { IconButton } from '@toeverything/components/button';
import {
Menu,
MenuIcon,
MenuItem,
type MenuItemProps,
} from '@toeverything/components/menu';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import type { ReactElement } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
@@ -60,7 +66,7 @@ const CollectionOperations = ({
icon: ReactElement;
name: string;
click: () => void;
className?: string;
type?: MenuItemProps['type'];
element?: undefined;
}
| {
@@ -70,12 +76,20 @@ const CollectionOperations = ({
>(
() => [
{
icon: <FilterIcon />,
icon: (
<MenuIcon>
<FilterIcon />
</MenuIcon>
),
name: t['Edit Filter'](),
click: showUpdateCollection,
},
{
icon: <UnpinIcon />,
icon: (
<MenuIcon>
<UnpinIcon />
</MenuIcon>
),
name: t['Unpin'](),
click: () => {
return setting.updateCollection({
@@ -88,12 +102,16 @@ const CollectionOperations = ({
element: <div key="divider" className={styles.menuDividerStyle}></div>,
},
{
icon: <DeleteIcon />,
icon: (
<MenuIcon>
<DeleteIcon />
</MenuIcon>
),
name: t['Delete'](),
click: () => {
return setting.deleteCollection(view.id);
},
className: styles.deleteFolder,
type: 'danger',
},
],
[setting, showUpdateCollection, t, view]
@@ -108,8 +126,8 @@ const CollectionOperations = ({
<MenuItem
data-testid="collection-option"
key={action.name}
className={action.className}
icon={action.icon}
type={action.type}
preFix={action.icon}
onClick={action.click}
>
{action.name}
@@ -181,6 +199,7 @@ const CollectionRenderer = ({
const pagesToRender = pages.filter(
page => filterPage(collection, page) && !page.trash
);
return (
<Collapsible.Root open={!collapsed}>
<EditCollectionModel
@@ -191,7 +210,7 @@ const CollectionRenderer = ({
open={show}
onClose={() => showUpdateCollection(false)}
/>
<MenuItem
<CollectionItem
data-testid="collection-item"
data-type="collection-list-item"
ref={setNodeRef}
@@ -200,9 +219,7 @@ const CollectionRenderer = ({
icon={<ViewLayersIcon />}
postfix={
<Menu
trigger="click"
placement="bottom-start"
content={
items={
<CollectionOperations
view={collection}
showUpdateCollection={() => showUpdateCollection(true)}
@@ -210,9 +227,13 @@ const CollectionRenderer = ({
/>
}
>
<div data-testid="collection-options" className={styles.more}>
<IconButton
data-testid="collection-options"
type="plain"
withoutHoverStyle
>
<MoreHorizontalIcon />
</div>
</IconButton>
</Menu>
}
collapsed={pagesToRender.length > 0 ? collapsed : undefined}
@@ -227,7 +248,7 @@ const CollectionRenderer = ({
>
<div>{collection.name}</div>
</div>
</MenuItem>
</CollectionItem>
<Collapsible.Content className={styles.collapsibleContent}>
<div style={{ marginLeft: 20, marginTop: -4 }}>
{pagesToRender.map(page => {
@@ -260,13 +281,13 @@ export const CollectionsList = ({ workspace }: CollectionsListProps) => {
const t = useAFFiNEI18N();
if (pinedCollections.length === 0) {
return (
<MenuItem
<CollectionItem
data-testid="slider-bar-collection-null-description"
icon={<InformationIcon />}
disabled
>
<span>{t['Create a collection']()}</span>
</MenuItem>
</CollectionItem>
);
}
return (

View File

@@ -1,5 +1,4 @@
import { Menu } from '@affine/component';
import { MenuItem } from '@affine/component/app-sidebar';
import { MenuItem as CollectionItem } from '@affine/component/app-sidebar';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import {
DeleteIcon,
@@ -11,6 +10,13 @@ import {
} from '@blocksuite/icons';
import type { PageMeta, Workspace } from '@blocksuite/store';
import * as Collapsible from '@radix-ui/react-collapsible';
import { IconButton } from '@toeverything/components/button';
import {
Menu,
MenuIcon,
MenuItem,
type MenuItemProps,
} from '@toeverything/components/menu';
import { useBlockSuitePageReferences } from '@toeverything/hooks/use-block-suite-page-references';
import { useAtomValue } from 'jotai/index';
import type { ReactElement } from 'react';
@@ -46,7 +52,7 @@ export const PageOperations = ({
icon: ReactElement;
name: string;
click: () => void;
className?: string;
type?: MenuItemProps['type'];
element?: undefined;
}
| {
@@ -58,7 +64,11 @@ export const PageOperations = ({
...(inAllowList
? [
{
icon: <FilterMinusIcon />,
icon: (
<MenuIcon>
<FilterMinusIcon />
</MenuIcon>
),
name: t['Remove special filter'](),
click: () => removeFromAllowList(page.id),
},
@@ -67,7 +77,11 @@ export const PageOperations = ({
...(!inExcludeList
? [
{
icon: <FilterUndoIcon />,
icon: (
<MenuIcon>
<FilterUndoIcon />
</MenuIcon>
),
name: t['Exclude from filter'](),
click: () => addToExcludeList(page.id),
},
@@ -77,12 +91,16 @@ export const PageOperations = ({
element: <div key="divider" className={styles.menuDividerStyle}></div>,
},
{
icon: <DeleteIcon />,
icon: (
<MenuIcon>
<DeleteIcon />
</MenuIcon>
),
name: t['Delete'](),
click: () => {
removeToTrash(page.id);
},
className: styles.deleteFolder,
type: 'danger',
},
],
[
@@ -105,8 +123,8 @@ export const PageOperations = ({
<MenuItem
data-testid="collection-page-option"
key={action.name}
className={action.className}
icon={action.icon}
type={action.type}
preFix={action.icon}
onClick={action.click}
>
{action.name}
@@ -133,6 +151,7 @@ export const Page = ({
workspace: Workspace;
allPageMeta: Record<string, PageMeta>;
}) => {
const ref = React.useRef(null);
const [collapsed, setCollapsed] = React.useState(true);
const params = useParams();
const { jumpToPage } = useNavigateHelper();
@@ -148,7 +167,7 @@ export const Page = ({
const referencesToRender = references.filter(id => !allPageMeta[id]?.trash);
return (
<Collapsible.Root open={!collapsed}>
<MenuItem
<CollectionItem
data-testid="collection-page"
data-type="collection-list-item"
icon={icon}
@@ -157,31 +176,35 @@ export const Page = ({
active={active}
collapsed={referencesToRender.length > 0 ? collapsed : undefined}
onCollapsedChange={setCollapsed}
ref={ref}
postfix={
<Menu
trigger="click"
placement="bottom-start"
content={
<div style={{ width: 220 }}>
<PageOperations
inAllowList={inAllowList}
removeFromAllowList={removeFromAllowList}
inExcludeList={inExcludeList}
addToExcludeList={addToExcludeList}
page={page}
workspace={workspace}
/>
</div>
items={
<PageOperations
inAllowList={inAllowList}
removeFromAllowList={removeFromAllowList}
inExcludeList={inExcludeList}
addToExcludeList={addToExcludeList}
page={page}
workspace={workspace}
/>
}
portalOptions={{
container: ref.current,
}}
>
<div data-testid="collection-page-options" className={styles.more}>
<MoreHorizontalIcon></MoreHorizontalIcon>
</div>
<IconButton
data-testid="collection-page-options"
type="plain"
withoutHoverStyle
>
<MoreHorizontalIcon />
</IconButton>
</Menu>
}
>
{page.title || t['Untitled']()}
</MenuItem>
</CollectionItem>
<Collapsible.Content className={styles.collapsibleContent}>
{referencesToRender.map(id => {
return (