fix: a series of setting issues (#3032)

(cherry picked from commit 87ba71e77e)
This commit is contained in:
Qi
2023-07-05 22:11:42 +08:00
committed by Alex Yang
parent 0a607e0450
commit 8f1bfa46a9
24 changed files with 235 additions and 463 deletions

View File

@@ -1,35 +0,0 @@
import { Button } from '@affine/component';
import type { ContactModalProps } from '@affine/component/contact-modal';
import { ContactModal } from '@affine/component/contact-modal';
import type { StoryFn } from '@storybook/react';
import { useState } from 'react';
export default {
title: 'AFFiNE/ContactModal',
component: ContactModal,
};
export const Basic: StoryFn<ContactModalProps> = args => {
const [open, setOpen] = useState(false);
return (
<>
<Button
onClick={() => {
setOpen(true);
}}
>
Open
</Button>
<ContactModal
{...args}
open={open}
onClose={() => {
setOpen(false);
}}
/>
</>
);
};
Basic.args = {
logoSrc: '/imgs/affine-text-logo.png',
};

View File

@@ -2,13 +2,21 @@ import { atom } from 'jotai';
import { atomFamily, atomWithStorage } from 'jotai/utils'; import { atomFamily, atomWithStorage } from 'jotai/utils';
import type { CreateWorkspaceMode } from '../components/affine/create-workspace-modal'; import type { CreateWorkspaceMode } from '../components/affine/create-workspace-modal';
import type { SettingProps } from '../components/affine/setting-modal';
// modal atoms // modal atoms
export const openWorkspacesModalAtom = atom(false); export const openWorkspacesModalAtom = atom(false);
export const openCreateWorkspaceModalAtom = atom<CreateWorkspaceMode>(false); export const openCreateWorkspaceModalAtom = atom<CreateWorkspaceMode>(false);
export const openQuickSearchModalAtom = atom(false); export const openQuickSearchModalAtom = atom(false);
export const openOnboardingModalAtom = atom(false); export const openOnboardingModalAtom = atom(false);
export const openSettingModalAtom = atom(false);
export type SettingAtom = Pick<SettingProps, 'activeTab' | 'workspace'> & {
open: boolean;
};
export const openSettingModalAtom = atom<SettingAtom>({
open: false,
});
export const openDisableCloudAlertModalAtom = atom(false); export const openDisableCloudAlertModalAtom = atom(false);

View File

@@ -1,4 +1,10 @@
import { Menu, MenuItem, MenuTrigger, styled } from '@affine/component'; import {
type ButtonProps,
Menu,
MenuItem,
MenuTrigger,
styled,
} from '@affine/component';
import { LOCALES } from '@affine/i18n'; import { LOCALES } from '@affine/i18n';
import { useI18N } from '@affine/i18n'; import { useI18N } from '@affine/i18n';
import type { FC, ReactElement } from 'react'; import type { FC, ReactElement } from 'react';
@@ -41,7 +47,9 @@ const LanguageMenuContent: FC<{
</> </>
); );
}; };
export const LanguageMenu: FC = () => { export const LanguageMenu: FC<{ triggerProps: ButtonProps }> = ({
triggerProps,
}) => {
const i18n = useI18N(); const i18n = useI18N();
const currentLanguage = LOCALES.find(item => item.tag === i18n.language); const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
@@ -62,6 +70,7 @@ export const LanguageMenu: FC = () => {
<MenuTrigger <MenuTrigger
data-testid="language-menu-button" data-testid="language-menu-button"
style={{ textTransform: 'capitalize' }} style={{ textTransform: 'capitalize' }}
{...triggerProps}
> >
{currentLanguage?.originalName} {currentLanguage?.originalName}
</MenuTrigger> </MenuTrigger>

View File

@@ -0,0 +1,35 @@
import {
DiscordIcon,
GithubIcon,
RedditIcon,
TelegramIcon,
TwitterIcon,
} from './icons';
export const relatedLinks = [
{
icon: <GithubIcon />,
title: 'GitHub',
link: 'https://github.com/toeverything/AFFiNE',
},
{
icon: <RedditIcon />,
title: 'Reddit',
link: 'https://www.reddit.com/r/Affine/',
},
{
icon: <TwitterIcon />,
title: 'Twitter',
link: 'https://twitter.com/AffineOfficial',
},
{
icon: <TelegramIcon />,
title: 'Telegram',
link: 'https://t.me/affineworkos',
},
{
icon: <DiscordIcon />,
title: 'Discord',
link: 'https://discord.gg/Arn7TqJBvG',
},
];

View File

@@ -1,5 +1,4 @@
import { Switch } from '@affine/component'; import { Switch } from '@affine/component';
import { relatedLinks } from '@affine/component/contact-modal';
import { SettingHeader } from '@affine/component/setting-components'; import { SettingHeader } from '@affine/component/setting-components';
import { SettingRow } from '@affine/component/setting-components'; import { SettingRow } from '@affine/component/setting-components';
import { SettingWrapper } from '@affine/component/setting-components'; import { SettingWrapper } from '@affine/component/setting-components';
@@ -8,6 +7,7 @@ import { ArrowRightSmallIcon, OpenInNewIcon } from '@blocksuite/icons';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { type AppSetting, useAppSetting } from '../../../../../atoms/settings'; import { type AppSetting, useAppSetting } from '../../../../../atoms/settings';
import { relatedLinks } from './config';
import { communityItem, communityWrapper, link } from './style.css'; import { communityItem, communityWrapper, link } from './style.css';
export const AboutAffine = () => { export const AboutAffine = () => {

View File

@@ -21,6 +21,7 @@ export const ThemeSettings = () => {
return ( return (
<RadioButtonGroup <RadioButtonGroup
width={250}
className={settingWrapper} className={settingWrapper}
defaultValue={theme} defaultValue={theme}
onValueChange={useCallback( onValueChange={useCallback(
@@ -30,13 +31,17 @@ export const ThemeSettings = () => {
[setTheme] [setTheme]
)} )}
> >
<RadioButton value="system" data-testid="system-theme-trigger"> <RadioButton
bold={true}
value="system"
data-testid="system-theme-trigger"
>
{t['system']()} {t['system']()}
</RadioButton> </RadioButton>
<RadioButton value="light" data-testid="light-theme-trigger"> <RadioButton bold={true} value="light" data-testid="light-theme-trigger">
{t['light']()} {t['light']()}
</RadioButton> </RadioButton>
<RadioButton value="dark" data-testid="dark-theme-trigger"> <RadioButton bold={true} value="dark" data-testid="dark-theme-trigger">
{t['dark']()} {t['dark']()}
</RadioButton> </RadioButton>
</RadioButtonGroup> </RadioButtonGroup>
@@ -72,7 +77,7 @@ export const AppearanceSettings = () => {
desc={t['Select the language for the interface.']()} desc={t['Select the language for the interface.']()}
> >
<div className={settingWrapper}> <div className={settingWrapper}>
<LanguageMenu /> <LanguageMenu triggerProps={{ size: 'small' }} />
</div> </div>
</SettingRow> </SettingRow>
{runtimeConfig.enableNewSettingUnstableApi && environment.isDesktop ? ( {runtimeConfig.enableNewSettingUnstableApi && environment.isDesktop ? (
@@ -104,6 +109,7 @@ export const AppearanceSettings = () => {
> >
<RadioButtonGroup <RadioButtonGroup
className={settingWrapper} className={settingWrapper}
width={250}
defaultValue={appSettings.windowFrameStyle} defaultValue={appSettings.windowFrameStyle}
onValueChange={(value: AppSetting['windowFrameStyle']) => { onValueChange={(value: AppSetting['windowFrameStyle']) => {
setAppSettings({ windowFrameStyle: value }); setAppSettings({ windowFrameStyle: value });

View File

@@ -2,18 +2,15 @@ import {
SettingModal as SettingModalBase, SettingModal as SettingModalBase,
type SettingModalProps, type SettingModalProps,
} from '@affine/component/setting-components'; } from '@affine/component/setting-components';
import type {
AffineCloudWorkspace,
LocalWorkspace,
} from '@affine/env/workspace';
import { WorkspaceFlavour } from '@affine/env/workspace'; import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ContactWithUsIcon } from '@blocksuite/icons'; import { ContactWithUsIcon } from '@blocksuite/icons';
import type React from 'react'; import type React from 'react';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo } from 'react';
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace'; import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
import { useWorkspaces } from '../../../hooks/use-workspaces'; import { useWorkspaces } from '../../../hooks/use-workspaces';
import type { AllWorkspace } from '../../../shared';
import { AccountSetting } from './account-setting'; import { AccountSetting } from './account-setting';
import { import {
GeneralSetting, GeneralSetting,
@@ -22,80 +19,79 @@ import {
} from './general-setting'; } from './general-setting';
import { SettingSidebar } from './setting-sidebar'; import { SettingSidebar } from './setting-sidebar';
import { settingContent } from './style.css'; import { settingContent } from './style.css';
import type { Workspace } from './type';
import { WorkSpaceSetting } from './workspace-setting'; import { WorkSpaceSetting } from './workspace-setting';
export const SettingModal: React.FC<SettingModalProps> = ({ type ActiveTab = GeneralSettingKeys | 'workspace' | 'account';
export type SettingProps = {
activeTab?: ActiveTab;
workspace?: AllWorkspace;
onSettingClick: (params: {
activeTab: ActiveTab;
workspace?: AllWorkspace;
}) => void;
};
export const SettingModal: React.FC<SettingModalProps & SettingProps> = ({
open, open,
setOpen, setOpen,
activeTab = 'appearance',
workspace = null,
onSettingClick,
}) => { }) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const workspaces = useWorkspaces(); const workspaces = useWorkspaces();
const [currentWorkspace] = useCurrentWorkspace(); const [currentWorkspace] = useCurrentWorkspace();
const generalSettingList = useGeneralSettingList(); const generalSettingList = useGeneralSettingList();
const workspaceList = useMemo(() => { const workspaceList = useMemo(() => {
return workspaces.filter( return workspaces.filter(
({ flavour }) => flavour !== WorkspaceFlavour.PUBLIC ({ flavour }) => flavour !== WorkspaceFlavour.PUBLIC
) as Workspace[]; ) as AllWorkspace[];
}, [workspaces]); }, [workspaces]);
const [currentRef, setCurrentRef] = useState<{ const onGeneralSettingClick = useCallback(
workspace: Workspace | null; (key: GeneralSettingKeys) => {
generalKey: GeneralSettingKeys | null; onSettingClick({
isAccount: boolean; activeTab: key,
}>({ });
workspace: null, },
generalKey: generalSettingList[0].key, [onSettingClick]
isAccount: false, );
}); const onWorkspaceSettingClick = useCallback(
(workspace: AllWorkspace) => {
const onGeneralSettingClick = useCallback((key: GeneralSettingKeys) => { onSettingClick({
setCurrentRef({ activeTab: 'workspace',
workspace: null, workspace,
generalKey: key, });
isAccount: false, },
}); [onSettingClick]
}, []); );
const onWorkspaceSettingClick = useCallback((workspace: Workspace) => {
setCurrentRef({
workspace: workspace,
generalKey: null,
isAccount: false,
});
}, []);
const onAccountSettingClick = useCallback(() => { const onAccountSettingClick = useCallback(() => {
setCurrentRef({ onSettingClick({ activeTab: 'account' });
workspace: null, }, [onSettingClick]);
generalKey: null,
isAccount: true,
});
}, []);
return ( return (
<SettingModalBase open={open} setOpen={setOpen}> <SettingModalBase open={open} setOpen={setOpen}>
<SettingSidebar <SettingSidebar
generalSettingList={generalSettingList} generalSettingList={generalSettingList}
onGeneralSettingClick={onGeneralSettingClick} onGeneralSettingClick={onGeneralSettingClick}
currentWorkspace={ currentWorkspace={currentWorkspace as AllWorkspace}
currentWorkspace as AffineCloudWorkspace | LocalWorkspace
}
workspaceList={workspaceList} workspaceList={workspaceList}
onWorkspaceSettingClick={onWorkspaceSettingClick} onWorkspaceSettingClick={onWorkspaceSettingClick}
selectedGeneralKey={currentRef.generalKey} selectedGeneralKey={activeTab}
selectedWorkspace={currentRef.workspace} selectedWorkspace={workspace}
onAccountSettingClick={onAccountSettingClick} onAccountSettingClick={onAccountSettingClick}
/> />
<div className={settingContent}> <div className={settingContent}>
<div className="wrapper"> <div className="wrapper">
<div className="content"> <div className="content">
{currentRef.workspace ? ( {activeTab === 'workspace' && workspace ? (
<WorkSpaceSetting workspace={currentRef.workspace} /> <WorkSpaceSetting workspace={workspace} />
) : null} ) : null}
{currentRef.generalKey ? ( {generalSettingList.find(v => v.key === activeTab) ? (
<GeneralSetting generalKey={currentRef.generalKey} /> <GeneralSetting generalKey={activeTab as GeneralSettingKeys} />
) : null} ) : null}
{currentRef.isAccount ? <AccountSetting /> : null} {activeTab === 'account' ? <AccountSetting /> : null}
</div> </div>
<div className="footer"> <div className="footer">
<ContactWithUsIcon /> <ContactWithUsIcon />

View File

@@ -1,18 +1,14 @@
import { UserAvatar } from '@affine/component/user-avatar'; import { UserAvatar } from '@affine/component/user-avatar';
import { WorkspaceAvatar } from '@affine/component/workspace-avatar'; import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
import type {
AffineCloudWorkspace,
LocalWorkspace,
} from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name'; import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
import clsx from 'clsx'; import clsx from 'clsx';
import type { AllWorkspace } from '../../../../shared';
import type { import type {
GeneralSettingKeys, GeneralSettingKeys,
GeneralSettingList, GeneralSettingList,
} from '../general-setting'; } from '../general-setting';
import type { Workspace } from '../type';
import { import {
accountButton, accountButton,
settingSlideBar, settingSlideBar,
@@ -34,13 +30,11 @@ export const SettingSidebar = ({
}: { }: {
generalSettingList: GeneralSettingList; generalSettingList: GeneralSettingList;
onGeneralSettingClick: (key: GeneralSettingKeys) => void; onGeneralSettingClick: (key: GeneralSettingKeys) => void;
currentWorkspace: Workspace; currentWorkspace: AllWorkspace;
workspaceList: Workspace[]; workspaceList: AllWorkspace[];
onWorkspaceSettingClick: ( onWorkspaceSettingClick: (workspace: AllWorkspace) => void;
workspace: AffineCloudWorkspace | LocalWorkspace
) => void;
selectedWorkspace: Workspace | null; selectedWorkspace: AllWorkspace | null;
selectedGeneralKey: string | null; selectedGeneralKey: string | null;
onAccountSettingClick: () => void; onAccountSettingClick: () => void;
}) => { }) => {
@@ -118,7 +112,7 @@ const WorkspaceListItem = ({
isCurrent, isCurrent,
isActive, isActive,
}: { }: {
workspace: AffineCloudWorkspace | LocalWorkspace; workspace: AllWorkspace;
onClick: () => void; onClick: () => void;
isCurrent: boolean; isCurrent: boolean;
isActive: boolean; isActive: boolean;

View File

@@ -34,6 +34,7 @@ export const sidebarItemsWrapper = style({
selectors: { selectors: {
'&.scroll': { '&.scroll': {
flexGrow: 1, flexGrow: 1,
overflowY: 'auto',
}, },
}, },
}); });

View File

@@ -1,6 +0,0 @@
import type {
AffineCloudWorkspace,
LocalWorkspace,
} from '@affine/env/workspace';
export type Workspace = AffineCloudWorkspace | LocalWorkspace;

View File

@@ -3,9 +3,13 @@ import { Suspense, useCallback } from 'react';
import { getUIAdapter } from '../../../../adapters/workspace'; import { getUIAdapter } from '../../../../adapters/workspace';
import { useOnTransformWorkspace } from '../../../../hooks/root/use-on-transform-workspace'; import { useOnTransformWorkspace } from '../../../../hooks/root/use-on-transform-workspace';
import { useAppHelper } from '../../../../hooks/use-workspaces'; import { useAppHelper } from '../../../../hooks/use-workspaces';
import type { Workspace } from '../type'; import type { AllWorkspace } from '../../../../shared';
export const WorkSpaceSetting = ({ workspace }: { workspace: Workspace }) => { export const WorkSpaceSetting = ({
workspace,
}: {
workspace: AllWorkspace;
}) => {
const helper = useAppHelper(); const helper = useAppHelper();
const { NewSettingsDetail } = getUIAdapter(workspace.flavour); const { NewSettingsDetail } = getUIAdapter(workspace.flavour);

View File

@@ -2,9 +2,9 @@ import { MuiFade, Tooltip } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon, NewIcon, UserGuideIcon } from '@blocksuite/icons'; import { CloseIcon, NewIcon, UserGuideIcon } from '@blocksuite/icons';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { lazy, Suspense, useState } from 'react'; import { useCallback, useState } from 'react';
import { openOnboardingModalAtom } from '../../../atoms'; import { openOnboardingModalAtom, openSettingModalAtom } from '../../../atoms';
import { useCurrentMode } from '../../../hooks/current/use-current-mode'; import { useCurrentMode } from '../../../hooks/current/use-current-mode';
import { ShortcutsModal } from '../shortcuts-modal'; import { ShortcutsModal } from '../shortcuts-modal';
import { ContactIcon, HelpIcon, KeyboardIcon } from './icons'; import { ContactIcon, HelpIcon, KeyboardIcon } from './icons';
@@ -14,11 +14,7 @@ import {
StyledIsland, StyledIsland,
StyledTriggerWrapper, StyledTriggerWrapper,
} from './style'; } from './style';
const ContactModal = lazy(() =>
import('@affine/component/contact-modal').then(({ ContactModal }) => ({
default: ContactModal,
}))
);
const DEFAULT_SHOW_LIST: IslandItemNames[] = [ const DEFAULT_SHOW_LIST: IslandItemNames[] = [
'whatNew', 'whatNew',
'contact', 'contact',
@@ -33,6 +29,7 @@ export const HelpIsland = ({
}) => { }) => {
const mode = useCurrentMode(); const mode = useCurrentMode();
const [, setOpenOnboarding] = useAtom(openOnboardingModalAtom); const [, setOpenOnboarding] = useAtom(openOnboardingModalAtom);
const [, setOpenSettingModalAtom] = useAtom(openSettingModalAtom);
const [spread, setShowSpread] = useState(false); const [spread, setShowSpread] = useState(false);
// const { triggerShortcutsModal, triggerContactModal } = useModal(); // const { triggerShortcutsModal, triggerContactModal } = useModal();
// const blockHub = useGlobalState(store => store.blockHub); // const blockHub = useGlobalState(store => store.blockHub);
@@ -52,8 +49,18 @@ export const HelpIsland = ({
// useEffect(() => { // useEffect(() => {
// spread && blockHub?.toggleMenu(false); // spread && blockHub?.toggleMenu(false);
// }, [blockHub, spread]); // }, [blockHub, spread]);
const [open, setOpen] = useState(false);
const [openShortCut, setOpenShortCut] = useState(false); const [openShortCut, setOpenShortCut] = useState(false);
const openAbout = useCallback(() => {
setShowSpread(false);
setOpenSettingModalAtom({
open: true,
activeTab: 'about',
});
}, [setOpenSettingModalAtom]);
return ( return (
<> <>
<StyledIsland <StyledIsland
@@ -83,10 +90,7 @@ export const HelpIsland = ({
<Tooltip content={t['Contact Us']()} placement="left-end"> <Tooltip content={t['Contact Us']()} placement="left-end">
<StyledIconWrapper <StyledIconWrapper
data-testid="right-bottom-contact-us-icon" data-testid="right-bottom-contact-us-icon"
onClick={() => { onClick={openAbout}
setShowSpread(false);
setOpen(true);
}}
> >
<ContactIcon /> <ContactIcon />
</StyledIconWrapper> </StyledIconWrapper>
@@ -136,13 +140,6 @@ export const HelpIsland = ({
</StyledTriggerWrapper> </StyledTriggerWrapper>
</MuiFade> </MuiFade>
</StyledIsland> </StyledIsland>
<Suspense>
<ContactModal
open={open}
onClose={() => setOpen(false)}
logoSrc="/imgs/affine-text-logo.png"
/>
</Suspense>
<ShortcutsModal <ShortcutsModal
open={openShortCut} open={openShortCut}
onClose={() => setOpenShortCut(false)} onClose={() => setOpenShortCut(false)}

View File

@@ -54,6 +54,7 @@ export const WorkspaceModeFilterTab = ({ ...props }: WorkspaceTitleProps) => {
<Header {...props}> <Header {...props}>
<div className={styles.allPageListTitleWrapper}> <div className={styles.allPageListTitleWrapper}>
<RadioButtonGroup <RadioButtonGroup
width={300}
defaultValue={value} defaultValue={value}
onValueChange={handleValueChange} onValueChange={handleValueChange}
> >

View File

@@ -38,6 +38,7 @@ import type { FC, PropsWithChildren, ReactElement } from 'react';
import { lazy, Suspense, useCallback, useEffect, useMemo } from 'react'; import { lazy, Suspense, useCallback, useEffect, useMemo } from 'react';
import { WorkspaceAdapters } from '../adapters/workspace'; import { WorkspaceAdapters } from '../adapters/workspace';
import type { SettingAtom } from '../atoms';
import { import {
openQuickSearchModalAtom, openQuickSearchModalAtom,
openSettingModalAtom, openSettingModalAtom,
@@ -100,14 +101,33 @@ export const QuickSearch: FC = () => {
}; };
export const Setting: FC = () => { export const Setting: FC = () => {
const [currentWorkspace] = useCurrentWorkspace(); const [currentWorkspace] = useCurrentWorkspace();
const [openSettingModal, setOpenSettingModalAtom] = const [{ open, workspace, activeTab }, setOpenSettingModalAtom] =
useAtom(openSettingModalAtom); useAtom(openSettingModalAtom);
const blockSuiteWorkspace = currentWorkspace?.blockSuiteWorkspace; const blockSuiteWorkspace = currentWorkspace?.blockSuiteWorkspace;
const onSettingClick = useCallback(
({
activeTab,
workspace,
}: Pick<SettingAtom, 'activeTab' | 'workspace'>) => {
setOpenSettingModalAtom(prev => ({ ...prev, activeTab, workspace }));
},
[setOpenSettingModalAtom]
);
if (!blockSuiteWorkspace) { if (!blockSuiteWorkspace) {
return null; return null;
} }
return ( return (
<SettingModal open={openSettingModal} setOpen={setOpenSettingModalAtom} /> <SettingModal
open={open}
activeTab={activeTab || 'appearance'}
workspace={workspace}
onSettingClick={onSettingClick}
setOpen={open => {
setOpenSettingModalAtom(prev => ({ ...prev, open }));
}}
/>
); );
}; };
@@ -336,7 +356,7 @@ export const WorkspaceLayoutInner: FC<PropsWithChildren> = ({ children }) => {
const [, setOpenSettingModalAtom] = useAtom(openSettingModalAtom); const [, setOpenSettingModalAtom] = useAtom(openSettingModalAtom);
const handleOpenSettingModal = useCallback(() => { const handleOpenSettingModal = useCallback(() => {
setOpenSettingModalAtom(true); setOpenSettingModalAtom({ activeTab: 'appearance', open: true });
}, [setOpenSettingModalAtom]); }, [setOpenSettingModalAtom]);
const resizing = useAtomValue(appSidebarResizingAtom); const resizing = useAtomValue(appSidebarResizingAtom);

View File

@@ -13,10 +13,12 @@ import {
openCreateWorkspaceModalAtom, openCreateWorkspaceModalAtom,
openDisableCloudAlertModalAtom, openDisableCloudAlertModalAtom,
openOnboardingModalAtom, openOnboardingModalAtom,
openSettingModalAtom,
openWorkspacesModalAtom, openWorkspacesModalAtom,
} from '../atoms'; } from '../atoms';
import { useRouterHelper } from '../hooks/use-router-helper'; import { useRouterHelper } from '../hooks/use-router-helper';
import { useWorkspaces } from '../hooks/use-workspaces'; import { useWorkspaces } from '../hooks/use-workspaces';
import type { AllWorkspace } from '../shared';
const WorkspaceListModal = lazy(() => const WorkspaceListModal = lazy(() =>
import('../components/pure/workspace-list-modal').then(module => ({ import('../components/pure/workspace-list-modal').then(module => ({
@@ -93,6 +95,20 @@ export const AllWorkspaceModals = (): ReactElement => {
rootCurrentWorkspaceIdAtom rootCurrentWorkspaceIdAtom
); );
const [transitioning, transition] = useTransition(); const [transitioning, transition] = useTransition();
const [, setOpenSettingModalAtom] = useAtom(openSettingModalAtom);
const handleOpenSettingModal = useCallback(
(workspace: AllWorkspace) => {
setOpenWorkspacesModal(false);
setOpenSettingModalAtom({
open: true,
activeTab: 'workspace',
workspace,
});
},
[setOpenSettingModalAtom, setOpenWorkspacesModal]
);
return ( return (
<> <>
<Suspense> <Suspense>
@@ -129,18 +145,7 @@ export const AllWorkspaceModals = (): ReactElement => {
}, },
[jumpToSubPath, setCurrentWorkspaceId, setOpenWorkspacesModal] [jumpToSubPath, setCurrentWorkspaceId, setOpenWorkspacesModal]
)} )}
onClickWorkspaceSetting={useCallback( onClickWorkspaceSetting={handleOpenSettingModal}
workspace => {
setOpenWorkspacesModal(false);
setCurrentWorkspaceId(workspace.id);
jumpToSubPath(workspace.id, WorkspaceSubPath.SETTING).catch(
error => {
console.error(error);
}
);
},
[jumpToSubPath, setCurrentWorkspaceId, setOpenWorkspacesModal]
)}
onNewWorkspace={useCallback(() => { onNewWorkspace={useCallback(() => {
setOpenCreateWorkspaceModal('new'); setOpenCreateWorkspaceModal('new');
}, [setOpenCreateWorkspaceModal])} }, [setOpenCreateWorkspaceModal])}

View File

@@ -1,138 +0,0 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { FlexWrapper, Modal, ModalCloseButton, ModalWrapper } from '../..';
import {
DiscordIcon,
DocIcon,
GithubIcon,
LinkIcon,
LogoIcon,
RedditIcon,
TelegramIcon,
TwitterIcon,
} from './icons';
import {
StyledBigLink,
StyledLogo,
StyledModalFooter,
StyledModalHeader,
StyledPrivacyContainer,
StyledSmallLink,
StyledSubTitle,
} from './style';
export const relatedLinks = [
{
icon: <GithubIcon />,
title: 'GitHub',
link: 'https://github.com/toeverything/AFFiNE',
},
{
icon: <RedditIcon />,
title: 'Reddit',
link: 'https://www.reddit.com/r/Affine/',
},
{
icon: <TwitterIcon />,
title: 'Twitter',
link: 'https://twitter.com/AffineOfficial',
},
{
icon: <TelegramIcon />,
title: 'Telegram',
link: 'https://t.me/affineworkos',
},
{
icon: <DiscordIcon />,
title: 'Discord',
link: 'https://discord.gg/Arn7TqJBvG',
},
];
export type ContactModalProps = {
open: boolean;
onClose: () => void;
logoSrc: string;
};
export const ContactModal = ({
open,
onClose,
logoSrc,
}: ContactModalProps): JSX.Element => {
const t = useAFFiNEI18N();
const topLinkList = [
{
icon: <LogoIcon />,
title: t['Official Website'](),
subTitle: 'AFFiNE.pro',
link: 'https://affine.pro',
},
{
icon: <DocIcon />,
title: t['Check Our Docs'](),
subTitle: 'Open Source',
link: 'https://community.affine.pro',
},
];
const date = new Date();
const year = date.getFullYear();
return (
<Modal open={open} onClose={onClose} data-testid="contact-us-modal-content">
<ModalWrapper width={720} height={436} style={{ letterSpacing: '1px' }}>
<StyledModalHeader>
<StyledLogo src={logoSrc} alt="" />
<ModalCloseButton
onClick={() => {
onClose();
}}
/>
</StyledModalHeader>
<FlexWrapper alignItems="center" justifyContent="center">
{topLinkList.map(({ icon, title, subTitle, link }) => {
return (
<StyledBigLink key={title} href={link} target="_blank">
{icon}
<p>{title}</p>
<p>
{subTitle}
<LinkIcon />
</p>
</StyledBigLink>
);
})}
</FlexWrapper>
<StyledSubTitle>
{t['Get in touch! Join our communities']()}
</StyledSubTitle>
<FlexWrapper justifyContent="center">
{relatedLinks.map(({ icon, title, link }) => {
return (
<StyledSmallLink key={title} href={link} target="_blank">
{icon}
<p>{title}</p>
</StyledSmallLink>
);
})}
</FlexWrapper>
<StyledModalFooter>
<p>Copyright &copy; {year} Toeverything</p>
<StyledPrivacyContainer>
<a href="https://affine.pro/terms" target="_blank" rel="noreferrer">
Terms
</a>
<a
href="https://affine.pro/privacy"
target="_blank"
rel="noreferrer"
>
Privacy
</a>
</StyledPrivacyContainer>
</StyledModalFooter>
</ModalWrapper>
</Modal>
);
};

View File

@@ -1,144 +0,0 @@
import { absoluteCenter, displayFlex, styled } from '../..';
export const StyledBigLink = styled('a')(() => {
return {
width: '268px',
height: '76px',
paddingLeft: '96px',
fontSize: '24px',
lineHeight: '36px',
color: 'var(--affine-text-primary-color)',
borderRadius: '10px',
flexDirection: 'column',
...displayFlex('center'),
position: 'relative',
transition: 'background .15s',
letterSpacing: '1px',
':visited': {
color: 'var(--affine-text-primary-color)',
},
':hover': {
background: 'rgba(68, 97, 242, 0.1)',
},
':not(:last-of-type)': {
marginRight: '48px',
},
svg: {
width: '48px',
height: '48px',
marginRight: '24px',
color: 'var(--affine-primary-color)',
...absoluteCenter({ vertical: true, position: { left: '26px' } }),
},
p: {
width: '100%',
height: '24px',
lineHeight: '24px',
...displayFlex('flex-start', 'center'),
':first-of-type': {
marginBottom: '4px',
fontSize: '18px',
fontWeight: '600',
},
':last-of-type': {
fontSize: '16px',
color: 'var(--affine-primary-color)',
fontWeight: '500',
},
svg: {
width: '20px',
height: '20px',
position: 'static',
transform: 'translate(0,0)',
marginLeft: '4px',
},
},
};
});
export const StyledSmallLink = styled('a')(() => {
return {
width: '124px',
height: '76px',
fontSize: '16px',
fontWeight: '500',
borderRadius: '5px',
color: 'var(--affine-text-primary-color)',
transition: 'background .15s, color .15s',
...displayFlex('center', 'center'),
flexWrap: 'wrap',
':visited': {
color: 'var(--affine-text-primary-color)',
},
':hover': {
color: 'var(--affine-primary-color)',
background: 'var(--affine-hover-color)',
},
svg: {
width: '22px',
color: 'var(--affine-primary-color)',
},
p: {
width: '100%',
textAlign: 'center',
},
};
});
export const StyledSubTitle = styled('div')(() => {
return {
fontSize: '18px',
fontWeight: '600',
color: 'var(--affine-text-primary-color)',
marginTop: '52px',
marginBottom: '8px',
textAlign: 'center',
};
});
export const StyledLogo = styled('img')({
height: '18px',
width: 'auto',
marginTop: '24px',
});
export const StyledModalHeader = styled('div')(() => {
return {
height: '72px',
padding: '0 40px',
marginBottom: '24px',
};
});
export const StyledModalFooter = styled('div')(() => {
return {
fontSize: '14px',
lineHeight: '20px',
textAlign: 'center',
color: 'var(--affine-text-primary-color)',
marginTop: '40px',
};
});
export const StyledPrivacyContainer = styled('div')(() => {
return {
marginTop: '4px',
position: 'relative',
a: {
height: '16px',
lineHeight: '16px',
color: 'var(--affine-icon-color)',
padding: '0 8px',
':visited': {
color: 'var(--affine-icon-color)',
},
':first-of-type': {
borderRight: '1px solid var(--affine-border-color)',
},
':hover': {
color: 'var(--affine-primary-color)',
},
},
};
});

View File

@@ -31,7 +31,8 @@ export const SettingModal: FC<PropsWithChildren<SettingModalProps>> = ({
maxWidth: '70vw', maxWidth: '70vw',
overflow: 'hidden', overflow: 'hidden',
display: 'flex', display: 'flex',
backgroundColor: 'var(--affine-white)', backgroundColor: 'var(--affine-background-overlay-panel-color)',
boxShadow: 'var(--affine-popover-shadow)',
}} }}
> >
<ModalCloseButton top={16} right={20} onClick={handleClose} /> <ModalCloseButton top={16} right={20} onClick={handleClose} />

View File

@@ -3,33 +3,45 @@ import type {
RadioGroupProps, RadioGroupProps,
} from '@radix-ui/react-radio-group'; } from '@radix-ui/react-radio-group';
import * as RadioGroup from '@radix-ui/react-radio-group'; import * as RadioGroup from '@radix-ui/react-radio-group';
import { forwardRef } from 'react'; import clsx from 'clsx';
import { type CSSProperties, forwardRef } from 'react';
import * as styles from './styles.css'; import * as styles from './styles.css';
export const RadioButton = forwardRef<HTMLButtonElement, RadioGroupItemProps>( export const RadioButton = forwardRef<
({ children, ...props }, ref) => { HTMLButtonElement,
return ( RadioGroupItemProps & { bold?: boolean }
<RadioGroup.Item ref={ref} {...props}> >(({ children, bold, className, ...props }, ref) => {
<span className={styles.radioUncheckedButton}>{children}</span> return (
<RadioGroup.Indicator className={styles.radioButton}> <RadioGroup.Item
{children} ref={ref}
</RadioGroup.Indicator> {...props}
</RadioGroup.Item> className={clsx(styles.radioButton, className)}
); >
} <span className={clsx(styles.radioUncheckedButton, { bold })}>
); {children}
</span>
<RadioGroup.Indicator
className={clsx(styles.radioButtonContent, { bold })}
>
{children}
</RadioGroup.Indicator>
</RadioGroup.Item>
);
});
RadioButton.displayName = 'RadioButton'; RadioButton.displayName = 'RadioButton';
export const RadioButtonGroup = forwardRef<HTMLDivElement, RadioGroupProps>( export const RadioButtonGroup = forwardRef<
({ ...props }, ref) => { HTMLDivElement,
return ( RadioGroupProps & { width?: CSSProperties['width'] }
<RadioGroup.Root >(({ className, style, width, ...props }, ref) => {
ref={ref} return (
className={styles.radioButtonGroup} <RadioGroup.Root
{...props} ref={ref}
></RadioGroup.Root> className={clsx(styles.radioButtonGroup, className)}
); style={{ width, ...style }}
} {...props}
); ></RadioGroup.Root>
);
});
RadioButtonGroup.displayName = 'RadioButtonGroup'; RadioButtonGroup.displayName = 'RadioButtonGroup';

View File

@@ -54,11 +54,13 @@ export const dropdownIcon = style({
}); });
export const radioButton = style({ export const radioButton = style({
flexGrow: 1,
});
export const radioButtonContent = style({
fontSize: 'var(--affine-font-xs)', fontSize: 'var(--affine-font-xs)',
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
padding: '0 30px',
height: '24px', height: '24px',
borderRadius: '8px', borderRadius: '8px',
filter: 'drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.1))', filter: 'drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.1))',
@@ -71,11 +73,14 @@ export const radioButton = style({
'&[data-state="checked"]': { '&[data-state="checked"]': {
background: 'var(--affine-white)', background: 'var(--affine-white)',
}, },
'&.bold': {
fontWeight: 600,
},
}, },
}); });
export const radioUncheckedButton = style([ export const radioUncheckedButton = style([
radioButton, radioButtonContent,
{ {
selectors: { selectors: {
'[data-state="checked"] > &': { '[data-state="checked"] > &': {
@@ -87,7 +92,8 @@ export const radioUncheckedButton = style([
export const radioButtonGroup = style({ export const radioButtonGroup = style({
display: 'inline-flex', display: 'inline-flex',
alignItems: 'flex-start', justifyContent: 'space-between',
alignItems: 'center',
background: 'var(--affine-hover-color)', background: 'var(--affine-hover-color)',
borderRadius: '10px', borderRadius: '10px',
padding: '2px', padding: '2px',

View File

@@ -7,21 +7,21 @@ import { SIZE_DEFAULT, SIZE_MIDDLE, SIZE_SMALL } from './interface';
export const SIZE_CONFIG = { export const SIZE_CONFIG = {
[SIZE_SMALL]: { [SIZE_SMALL]: {
iconSize: 16, iconSize: 16,
fontSize: 16, fontSize: 'var(--affine-font-xs)',
borderRadius: 4, borderRadius: 4,
height: 26, height: 28,
padding: 6, padding: 6,
}, },
[SIZE_MIDDLE]: { [SIZE_MIDDLE]: {
iconSize: 20, iconSize: 20,
fontSize: 16, fontSize: 'var(--affine-font-sm)',
borderRadius: 4, borderRadius: 4,
height: 32, height: 32,
padding: 12, padding: 12,
}, },
[SIZE_DEFAULT]: { [SIZE_DEFAULT]: {
iconSize: 24, iconSize: 24,
fontSize: 16, fontSize: 'var(--affine-font-base)',
height: 38, height: 38,
padding: 24, padding: 24,
borderRadius: 4, borderRadius: 4,

View File

@@ -105,12 +105,11 @@ export const StyledMenuItem = styled('button')<{
export const StyledButton = styled(Button)(() => { export const StyledButton = styled(Button)(() => {
return { return {
width: '100%', width: '100%',
height: '32px', // height: '32px',
borderRadius: '8px', borderRadius: '8px',
backgroundColor: 'transparent', backgroundColor: 'transparent',
...displayFlex('space-between', 'center'), ...displayFlex('space-between', 'center'),
border: `1px solid var(--affine-border-color)`, border: `1px solid var(--affine-border-color)`,
padding: '0 10px', padding: '0 10px',
fontSize: 'var(--affine-font-base)',
}; };
}); });

View File

@@ -14,6 +14,7 @@ test('Click right-bottom corner contact icon', async ({ page }) => {
expect(await rightBottomContactUs.isVisible()).toEqual(true); expect(await rightBottomContactUs.isVisible()).toEqual(true);
await rightBottomContactUs.click(); await rightBottomContactUs.click();
const contactUsModal = page.locator('[data-testid=contact-us-modal-content]');
await expect(contactUsModal).toContainText('Check Our Docs'); const title = await page.getByTestId('about-title');
await expect(title).toBeVisible();
}); });