feat: new workspace switch dropdown design (#3700)

Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
danielchim
2023-08-17 06:18:43 +08:00
committed by GitHub
parent f369ca39f7
commit 9ab9c0c70d
11 changed files with 368 additions and 192 deletions

View File

@@ -1,11 +1,4 @@
import {
Menu,
MenuItem,
Modal,
ModalCloseButton,
ModalWrapper,
Tooltip,
} from '@affine/component';
import { Menu, MenuItem, Tooltip } from '@affine/component';
import { ScrollableContainer } from '@affine/component';
import { WorkspaceList } from '@affine/component/workspace-list';
import type {
@@ -15,28 +8,38 @@ import type {
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
import { HelpIcon, ImportIcon, PlusIcon } from '@blocksuite/icons';
import type { DragEndEvent } from '@dnd-kit/core';
import { useCallback, useRef } from 'react';
import type { AllWorkspace } from '../../../shared';
import { Footer } from '../footer';
import {
StyledCreateWorkspaceCard,
CloudWorkspaceIcon,
HelpIcon,
ImportIcon,
MoreHorizontalIcon,
PlusIcon,
} from '@blocksuite/icons';
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 { useSetAtom } from 'jotai';
import { useCallback } from 'react';
import { openDisableCloudAlertModalAtom } from '../../../atoms';
import type { AllWorkspace } from '../../../shared';
import {
StyledCreateWorkspaceCardPill,
StyledCreateWorkspaceCardPillContainer,
StyledCreateWorkspaceCardPillContent,
StyledCreateWorkspaceCardPillIcon,
StyledCreateWorkspaceCardPillTextSecondary,
StyledHelperContainer,
StyledImportWorkspaceCardPill,
StyledModalBody,
StyledModalContent,
StyledModalHeader,
StyledModalHeaderLeft,
StyledModalTitle,
StyledOperationWrapper,
StyleWorkspaceAdd,
StyleWorkspaceInfo,
StyleWorkspaceTitle,
StyledSignInCardPill,
StyledSignInCardPillTextCotainer,
StyledSignInCardPillTextPrimary,
StyledSignInCardPillTextSecondary,
} from './styles';
interface WorkspaceModalProps {
@@ -52,105 +55,94 @@ interface WorkspaceModalProps {
onMoveWorkspace: (activeId: string, overId: string) => void;
}
const CreateWorkspaceCard = ({
onNewWorkspace,
onAddWorkspace,
}: {
onNewWorkspace: () => void;
onAddWorkspace: () => void;
}) => {
const AccountMenu = () => {
const t = useAFFiNEI18N();
const anchorEL = useRef<HTMLDivElement>(null);
return (
<div>
<div>Unlimted</div>
<Divider></Divider>
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
{t['com.affine.workspace.cloud.join']()}
</MenuItem>
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
{t['com.affine.workspace.cloud.account.settings']()}
</MenuItem>
<Divider></Divider>
<MenuItem icon={<ImportIcon />} data-testid="editor-option-menu-import">
{t['com.affine.workspace.cloud.account.logout']()}
</MenuItem>
</div>
);
};
if (runtimeConfig.enableSQLiteProvider && environment.isDesktop) {
return (
<Menu
placement="auto"
trigger={['click']}
zIndex={1000}
content={
<StyledCreateWorkspaceCardPillContainer>
<StyledCreateWorkspaceCardPill>
<MenuItem
style={{
height: 'auto',
padding: '8px 12px',
}}
onClick={onNewWorkspace}
data-testid="new-workspace"
>
<StyledCreateWorkspaceCardPillContent>
<div>
<p>{t['New Workspace']()}</p>
<StyledCreateWorkspaceCardPillTextSecondary>
<p>{t['Create your own workspace']()}</p>
</StyledCreateWorkspaceCardPillTextSecondary>
</div>
<StyledCreateWorkspaceCardPillIcon>
<PlusIcon />
</StyledCreateWorkspaceCardPillIcon>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledCreateWorkspaceCardPill>
<StyledCreateWorkspaceCardPill>
<MenuItem
onClick={onAddWorkspace}
data-testid="add-workspace"
style={{
height: 'auto',
padding: '8px 12px',
}}
>
<StyledCreateWorkspaceCardPillContent>
<div>
<p>{t['Add Workspace']()}</p>
<StyledCreateWorkspaceCardPillTextSecondary>
<p>{t['Add Workspace Hint']()}</p>
</StyledCreateWorkspaceCardPillTextSecondary>
</div>
<StyledCreateWorkspaceCardPillIcon>
<ImportIcon />
</StyledCreateWorkspaceCardPillIcon>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledCreateWorkspaceCardPill>
</StyledCreateWorkspaceCardPillContainer>
}
>
<StyledCreateWorkspaceCard
ref={anchorEL}
data-testid="add-or-new-workspace"
>
<StyleWorkspaceAdd className="add-icon">
<PlusIcon />
</StyleWorkspaceAdd>
const CloudWorkSpaceList = ({
disabled,
workspaces,
onClickWorkspace,
onClickWorkspaceSetting,
currentWorkspaceId,
onMoveWorkspace,
}: WorkspaceModalProps) => {
const t = useAFFiNEI18N();
<StyleWorkspaceInfo>
<StyleWorkspaceTitle>{t['New Workspace']()}</StyleWorkspaceTitle>
<p>{t['Create Or Import']()}</p>
</StyleWorkspaceInfo>
</StyledCreateWorkspaceCard>
</Menu>
);
} else {
return (
<div>
<StyledCreateWorkspaceCard
onClick={onNewWorkspace}
data-testid="new-workspace"
>
<StyleWorkspaceAdd className="add-icon">
<PlusIcon />
</StyleWorkspaceAdd>
return (
<>
<StyledModalHeader>
<StyledModalHeaderLeft>
<StyledModalTitle>
{t['com.affine.workspace.cloud.sync']()}
</StyledModalTitle>
<Tooltip
content={t['Workspace description']()}
placement="top-start"
disablePortal={true}
>
<StyledHelperContainer>
<HelpIcon />
</StyledHelperContainer>
</Tooltip>
</StyledModalHeaderLeft>
<StyleWorkspaceInfo>
<StyleWorkspaceTitle>{t['New Workspace']()}</StyleWorkspaceTitle>
<p>{t['Create Or Import']()}</p>
</StyleWorkspaceInfo>
</StyledCreateWorkspaceCard>
</div>
);
}
<StyledOperationWrapper>
<Menu
placement="bottom-end"
trigger={['click']}
content={<AccountMenu />}
zIndex={1000}
>
<IconButton
data-testid="previous-image-button"
icon={<MoreHorizontalIcon />}
type="plain"
/>
</Menu>
</StyledOperationWrapper>
</StyledModalHeader>
<StyledModalContent>
<WorkspaceList
disabled={disabled}
items={
workspaces.filter(
({ flavour }) => flavour !== WorkspaceFlavour.PUBLIC
) as (AffineCloudWorkspace | LocalWorkspace)[]
}
currentWorkspaceId={currentWorkspaceId}
onClick={onClickWorkspace}
onSettingClick={onClickWorkspaceSetting}
onDragEnd={useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (active.id !== over?.id) {
onMoveWorkspace(active.id as string, over?.id as string);
}
},
[onMoveWorkspace]
)}
/>
<Divider />
</StyledModalContent>
</>
);
};
export const WorkspaceListModal = ({
@@ -166,19 +158,77 @@ export const WorkspaceListModal = ({
onMoveWorkspace,
}: WorkspaceModalProps) => {
const t = useAFFiNEI18N();
const setOpen = useSetAtom(openDisableCloudAlertModalAtom);
// TODO: AFFiNE Cloud support
const isLoggedIn = false;
const anchorEl = document.getElementById('current-workspace');
return (
<Modal open={open} onClose={onClose}>
<ModalWrapper
width={720}
height={690}
style={{
display: 'flex',
flexDirection: 'column',
}}
>
<StyledModalHeader>
<StyledModalHeaderLeft>
<StyledModalTitle>{t['My Workspaces']()}</StyledModalTitle>
<Popover
sx={{
color: 'success.main',
zIndex: 999,
'& .MuiPopover-paper': {
borderRadius: '8px',
boxShadow: 'var(--affine-shadow-1)',
},
}}
open={open}
anchorEl={anchorEl}
onClose={onClose}
>
<StyledModalBody>
<ScrollableContainer>
{isLoggedIn ? (
<CloudWorkSpaceList
disabled={disabled}
open={open}
onClose={onClose}
workspaces={workspaces}
onClickWorkspace={onClickWorkspace}
onClickWorkspaceSetting={onClickWorkspaceSetting}
onNewWorkspace={onNewWorkspace}
onAddWorkspace={onAddWorkspace}
currentWorkspaceId={currentWorkspaceId}
onMoveWorkspace={onMoveWorkspace}
/>
) : (
<>
<StyledModalContent>
<StyledSignInCardPill>
<MenuItem
style={{
height: 'auto',
padding: '8px 12px',
}}
onClick={async () => {
if (!runtimeConfig.enableCloud) {
setOpen(true);
}
}}
data-testid="cloud-signin-button"
>
<StyledCreateWorkspaceCardPillContent>
<StyledCreateWorkspaceCardPillIcon>
<CloudWorkspaceIcon />
</StyledCreateWorkspaceCardPillIcon>
<StyledSignInCardPillTextCotainer>
<StyledSignInCardPillTextPrimary>
{t['com.affine.workspace.cloud.auth']()}
</StyledSignInCardPillTextPrimary>
<StyledSignInCardPillTextSecondary>
Sync with AFFiNE Cloud
</StyledSignInCardPillTextSecondary>
</StyledSignInCardPillTextCotainer>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledSignInCardPill>
<Divider />
</StyledModalContent>
</>
)}
<StyledModalHeader>
<StyledModalTitle>{t['Local Workspace']()}</StyledModalTitle>
<Tooltip
content={t['Workspace description']()}
placement="top-start"
@@ -188,19 +238,7 @@ export const WorkspaceListModal = ({
<HelpIcon />
</StyledHelperContainer>
</Tooltip>
</StyledModalHeaderLeft>
<StyledOperationWrapper>
<ModalCloseButton
data-testid="close-workspace-modal"
onClick={() => {
onClose();
}}
absolute={false}
/>
</StyledOperationWrapper>
</StyledModalHeader>
<ScrollableContainer>
</StyledModalHeader>
<StyledModalContent>
<WorkspaceList
disabled={disabled}
@@ -222,14 +260,51 @@ export const WorkspaceListModal = ({
[onMoveWorkspace]
)}
/>
<CreateWorkspaceCard
onNewWorkspace={onNewWorkspace}
onAddWorkspace={onAddWorkspace}
/>
</StyledModalContent>
<StyledModalContent>
{runtimeConfig.enableSQLiteProvider && environment.isDesktop ? (
<StyledImportWorkspaceCardPill>
<MenuItem
onClick={onAddWorkspace}
data-testid="add-workspace"
style={{
height: 'auto',
padding: '8px 12px',
}}
>
<StyledCreateWorkspaceCardPillContent>
<StyledCreateWorkspaceCardPillIcon>
<ImportIcon />
</StyledCreateWorkspaceCardPillIcon>
<div>
<p>{t['com.affine.workspace.local.import']()}</p>
</div>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledImportWorkspaceCardPill>
) : null}
<StyledCreateWorkspaceCardPill>
<MenuItem
style={{
height: 'auto',
padding: '8px 12px',
}}
onClick={onNewWorkspace}
data-testid="new-workspace"
>
<StyledCreateWorkspaceCardPillContent>
<StyledCreateWorkspaceCardPillIcon>
<PlusIcon />
</StyledCreateWorkspaceCardPillIcon>
<div>
<p>{t['New Workspace']()}</p>
</div>
</StyledCreateWorkspaceCardPillContent>
</MenuItem>
</StyledCreateWorkspaceCardPill>
</StyledModalContent>
</ScrollableContainer>
<Footer />
</ModalWrapper>
</Modal>
</StyledModalBody>
</Popover>
);
};

View File

@@ -81,11 +81,33 @@ export const StyledCreateWorkspaceCardPillContainer = styled('div')(() => {
});
export const StyledCreateWorkspaceCardPill = styled('div')(() => {
return {
borderRadius: '8px',
display: 'flex',
width: '100%',
height: '58px',
background: 'var(--affine-background-primary-color)',
border: `1px solid var(--affine-border-color)`,
};
});
export const StyledSignInCardPill = styled('div')(() => {
return {
borderRadius: '8px',
display: 'flex',
width: '100%',
height: '58px',
marginBottom: '6px',
background: 'var(--affine-background-primary-color)',
};
});
export const StyledImportWorkspaceCardPill = styled('div')(() => {
return {
borderRadius: '5px',
display: 'flex',
boxShadow: '0px 0px 6px 0px rgba(0, 0, 0, 0.1)',
background: 'var(--affine-background-primary-color)',
width: '100%',
marginBottom: '12px',
};
});
@@ -94,7 +116,6 @@ export const StyledCreateWorkspaceCardPillContent = styled('div')(() => {
display: 'flex',
gap: '12px',
alignItems: 'center',
justifyContent: 'space-between',
};
});
@@ -106,13 +127,30 @@ export const StyledCreateWorkspaceCardPillIcon = styled('div')(() => {
};
});
export const StyledCreateWorkspaceCardPillTextSecondary = styled('div')(() => {
export const StyledSignInCardPillTextCotainer = styled('div')(() => {
return {
display: 'flex',
flexDirection: 'column',
};
});
export const StyledSignInCardPillTextSecondary = styled('div')(() => {
return {
fontSize: '12px',
color: 'var(--affine-text-secondary-color)',
};
});
export const StyledSignInCardPillTextPrimary = styled('div')(() => {
return {
fontSize: 'var(--affine-font-base)',
fontWeight: 600,
lineHeight: '24px',
maxWidth: '200px',
...textEllipsis(1),
};
});
export const StyledModalHeaderLeft = styled('div')(() => {
return { ...displayFlex('flex-start', 'center') };
});
@@ -120,6 +158,7 @@ export const StyledModalTitle = styled('div')(() => {
return {
fontWeight: 600,
fontSize: 'var(--affine-font-h6)',
color: 'var(--affine-text-primary-color)',
};
});
@@ -134,10 +173,9 @@ export const StyledHelperContainer = styled('div')(() => {
});
export const StyledModalContent = styled('div')({
height: '540px',
padding: '8px 40px',
...displayFlex('space-between', 'flex-start', 'flex-start'),
flexWrap: 'wrap',
flexDirection: 'column',
});
export const StyledOperationWrapper = styled('div')(() => {
return {
@@ -162,11 +200,22 @@ export const StyleWorkspaceAdd = styled('div')(() => {
export const StyledModalHeader = styled('div')(() => {
return {
width: '100%',
marginTop: '10px',
left: 0,
top: 0,
borderRadius: '24px 24px 0 0',
padding: '10px 40px',
padding: '12px 14px',
...displayFlex('space-between', 'center'),
};
});
export const StyledModalBody = styled('div')(() => {
return {
borderRadius: '8px',
padding: '12px',
display: 'inline-flex',
flexDirection: 'column',
alignItems: 'flex-start',
gap: '12px',
background: 'var(--affine-white)',
};
});

View File

@@ -32,6 +32,7 @@ export const WorkspaceSelector = ({
// Open dialog when `Enter` or `Space` pressed
// TODO-Doma Refactor with `@radix-ui/react-dialog` or other libraries that handle these out of the box and be accessible by default
// TODO: Delete this?
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
@@ -50,6 +51,7 @@ export const WorkspaceSelector = ({
onClick={onClick}
onKeyDown={handleKeyDown}
data-testid="current-workspace"
id="current-workspace"
>
<WorkspaceAvatar
data-testid="workspace-avatar"