mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-21 10:01:43 +03:00
refactor(mobile): determine the currently active tab through a persistent state (#9018)
close AF-1868 Only tap on specific tab can change active tab
This commit is contained in:
parent
f073df3ee5
commit
8fe188e773
@ -1,51 +1,29 @@
|
|||||||
import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
|
import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
|
||||||
import { JournalService } from '@affine/core/modules/journal';
|
|
||||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
|
||||||
import track from '@affine/track';
|
import track from '@affine/track';
|
||||||
import { EditIcon } from '@blocksuite/icons/rc';
|
import { EditIcon } from '@blocksuite/icons/rc';
|
||||||
import {
|
import { useService, WorkspaceService } from '@toeverything/infra';
|
||||||
DocsService,
|
|
||||||
useLiveData,
|
|
||||||
useServices,
|
|
||||||
WorkspaceService,
|
|
||||||
} from '@toeverything/infra';
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { tabItem } from './styles.css';
|
import type { AppTabCustomFCProps } from './data';
|
||||||
|
import { TabItem } from './tab-item';
|
||||||
|
|
||||||
export const AppTabCreate = () => {
|
export const AppTabCreate = ({ tab }: AppTabCustomFCProps) => {
|
||||||
const { docsService, workbenchService, workspaceService, journalService } =
|
const workspaceService = useService(WorkspaceService);
|
||||||
useServices({
|
|
||||||
DocsService,
|
|
||||||
WorkbenchService,
|
|
||||||
WorkspaceService,
|
|
||||||
JournalService,
|
|
||||||
});
|
|
||||||
const workbench = workbenchService.workbench;
|
|
||||||
const currentWorkspace = workspaceService.workspace;
|
const currentWorkspace = workspaceService.workspace;
|
||||||
const location = useLiveData(workbench.location$);
|
|
||||||
const pageHelper = usePageHelper(currentWorkspace.docCollection);
|
const pageHelper = usePageHelper(currentWorkspace.docCollection);
|
||||||
|
|
||||||
const maybeDocId = location.pathname.split('/')[1].split('?')[0];
|
const createPage = useCallback(
|
||||||
const doc = useLiveData(docsService.list.doc$(maybeDocId));
|
(isActive: boolean) => {
|
||||||
const journalDate = useLiveData(journalService.journalDate$(maybeDocId));
|
|
||||||
const isActive = !!doc && !journalDate;
|
|
||||||
|
|
||||||
const createPage = useCallback(() => {
|
|
||||||
if (isActive) return;
|
if (isActive) return;
|
||||||
pageHelper.createPage(undefined, true);
|
pageHelper.createPage(undefined, true);
|
||||||
track.$.navigationPanel.$.createDoc();
|
track.$.navigationPanel.$.createDoc();
|
||||||
}, [isActive, pageHelper]);
|
},
|
||||||
|
[pageHelper]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<TabItem id={tab.key} onClick={createPage} label="New Page">
|
||||||
className={tabItem}
|
|
||||||
data-active={isActive}
|
|
||||||
role="tab"
|
|
||||||
aria-label="New Page"
|
|
||||||
onClick={createPage}
|
|
||||||
>
|
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
</div>
|
</TabItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
import { AllDocsIcon, MobileHomeIcon } from '@blocksuite/icons/rc';
|
||||||
|
import type { Framework } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { AppTabCreate } from './create';
|
||||||
|
import { AppTabJournal } from './journal';
|
||||||
|
|
||||||
|
interface AppTabBase {
|
||||||
|
key: string;
|
||||||
|
onClick?: (framework: Framework, isActive: boolean) => void;
|
||||||
|
}
|
||||||
|
export interface AppTabLink extends AppTabBase {
|
||||||
|
Icon: React.FC;
|
||||||
|
to: string;
|
||||||
|
LinkComponent?: React.FC;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppTabCustom extends AppTabBase {
|
||||||
|
custom: (props: AppTabCustomFCProps) => React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Tab = AppTabLink | AppTabCustom;
|
||||||
|
|
||||||
|
export interface AppTabCustomFCProps {
|
||||||
|
tab: Tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tabs: Tab[] = [
|
||||||
|
{
|
||||||
|
key: 'home',
|
||||||
|
to: '/home',
|
||||||
|
Icon: MobileHomeIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'all',
|
||||||
|
to: '/all',
|
||||||
|
Icon: AllDocsIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'journal',
|
||||||
|
custom: AppTabJournal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'new',
|
||||||
|
custom: AppTabCreate,
|
||||||
|
},
|
||||||
|
];
|
@ -1,59 +1,14 @@
|
|||||||
import { SafeArea } from '@affine/component';
|
import { SafeArea } from '@affine/component';
|
||||||
import {
|
import { WorkbenchLink } from '@affine/core/modules/workbench';
|
||||||
WorkbenchLink,
|
|
||||||
WorkbenchService,
|
|
||||||
} from '@affine/core/modules/workbench';
|
|
||||||
import { AllDocsIcon, MobileHomeIcon } from '@blocksuite/icons/rc';
|
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import type { Location } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { VirtualKeyboardService } from '../../modules/virtual-keyboard/services/virtual-keyboard';
|
import { VirtualKeyboardService } from '../../modules/virtual-keyboard/services/virtual-keyboard';
|
||||||
import { AppTabCreate } from './create';
|
import { type AppTabLink, tabs } from './data';
|
||||||
import { AppTabJournal } from './journal';
|
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
import { TabItem } from './tab-item';
|
||||||
interface AppTabBaseProps {
|
|
||||||
key: string;
|
|
||||||
}
|
|
||||||
interface AppTabLinkProps extends AppTabBaseProps {
|
|
||||||
Icon: React.FC;
|
|
||||||
to: string;
|
|
||||||
LinkComponent?: React.FC;
|
|
||||||
isActive?: (location: Location) => boolean;
|
|
||||||
}
|
|
||||||
interface AppTabCustomProps extends AppTabBaseProps {
|
|
||||||
node: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Route = AppTabLinkProps | AppTabCustomProps;
|
|
||||||
|
|
||||||
const routes: Route[] = [
|
|
||||||
{
|
|
||||||
key: 'home',
|
|
||||||
to: '/home',
|
|
||||||
Icon: MobileHomeIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'all',
|
|
||||||
to: '/all',
|
|
||||||
Icon: AllDocsIcon,
|
|
||||||
isActive: location =>
|
|
||||||
location.pathname === '/all' ||
|
|
||||||
location.pathname.startsWith('/collection') ||
|
|
||||||
location.pathname.startsWith('/tag'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'journal',
|
|
||||||
node: <AppTabJournal />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'new',
|
|
||||||
node: <AppTabCreate />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const AppTabs = ({ background }: { background?: string }) => {
|
export const AppTabs = ({ background }: { background?: string }) => {
|
||||||
const virtualKeyboardService = useService(VirtualKeyboardService);
|
const virtualKeyboardService = useService(VirtualKeyboardService);
|
||||||
@ -72,12 +27,14 @@ export const AppTabs = ({ background }: { background?: string }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ul className={styles.appTabsInner} id="app-tabs" role="tablist">
|
<ul className={styles.appTabsInner} id="app-tabs" role="tablist">
|
||||||
{routes.map(route => {
|
{tabs.map(tab => {
|
||||||
if ('to' in route) {
|
if ('to' in tab) {
|
||||||
return <AppTabLink route={route} key={route.key} />;
|
return <AppTabLink route={tab} key={tab.key} />;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={route.key}>{route.node}</React.Fragment>
|
<React.Fragment key={tab.key}>
|
||||||
|
{<tab.custom tab={tab} />}
|
||||||
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
@ -87,27 +44,19 @@ export const AppTabs = ({ background }: { background?: string }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AppTabLink = ({ route }: { route: AppTabLinkProps }) => {
|
const AppTabLink = ({ route }: { route: AppTabLink }) => {
|
||||||
const workbench = useService(WorkbenchService).workbench;
|
|
||||||
const location = useLiveData(workbench.location$);
|
|
||||||
const Link = route.LinkComponent || WorkbenchLink;
|
const Link = route.LinkComponent || WorkbenchLink;
|
||||||
|
|
||||||
const isActive = route.isActive
|
|
||||||
? route.isActive(location)
|
|
||||||
: location.pathname === route.to;
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
data-active={isActive}
|
className={styles.tabItem}
|
||||||
to={route.to}
|
to={route.to}
|
||||||
key={route.to}
|
key={route.to}
|
||||||
className={styles.tabItem}
|
|
||||||
role="tab"
|
|
||||||
aria-label={route.to.slice(1)}
|
|
||||||
replaceHistory
|
replaceHistory
|
||||||
>
|
>
|
||||||
<li style={{ lineHeight: 0 }}>
|
<TabItem id={route.key} label={route.to.slice(1)}>
|
||||||
<route.Icon />
|
<route.Icon />
|
||||||
</li>
|
</TabItem>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -6,9 +6,10 @@ import { TodayIcon } from '@blocksuite/icons/rc';
|
|||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { tabItem } from './styles.css';
|
import type { AppTabCustomFCProps } from './data';
|
||||||
|
import { TabItem } from './tab-item';
|
||||||
|
|
||||||
export const AppTabJournal = () => {
|
export const AppTabJournal = ({ tab }: AppTabCustomFCProps) => {
|
||||||
const workbench = useService(WorkbenchService).workbench;
|
const workbench = useService(WorkbenchService).workbench;
|
||||||
const location = useLiveData(workbench.location$);
|
const location = useLiveData(workbench.location$);
|
||||||
const journalService = useService(JournalService);
|
const journalService = useService(JournalService);
|
||||||
@ -26,14 +27,8 @@ export const AppTabJournal = () => {
|
|||||||
const Icon = journalDate ? JournalIcon : TodayIcon;
|
const Icon = journalDate ? JournalIcon : TodayIcon;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<TabItem onClick={handleOpenToday} id={tab.key} label="Journal">
|
||||||
className={tabItem}
|
|
||||||
onClick={handleOpenToday}
|
|
||||||
data-active={!!journalDate}
|
|
||||||
role="tab"
|
|
||||||
aria-label="Journal"
|
|
||||||
>
|
|
||||||
<Icon />
|
<Icon />
|
||||||
</div>
|
</TabItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
import {
|
||||||
|
GlobalCacheService,
|
||||||
|
LiveData,
|
||||||
|
useLiveData,
|
||||||
|
useService,
|
||||||
|
} from '@toeverything/infra';
|
||||||
|
import { type PropsWithChildren, useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { tabItem } from './styles.css';
|
||||||
|
|
||||||
|
export interface TabItemProps extends PropsWithChildren {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
onClick?: (isActive: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheKey = 'activeAppTabId';
|
||||||
|
export const TabItem = ({ id, label, children, onClick }: TabItemProps) => {
|
||||||
|
const globalCache = useService(GlobalCacheService).globalCache;
|
||||||
|
const activeTabId$ = useMemo(
|
||||||
|
() => LiveData.from(globalCache.watch(cacheKey), 'home'),
|
||||||
|
[globalCache]
|
||||||
|
);
|
||||||
|
const activeTabId = useLiveData(activeTabId$) ?? 'home';
|
||||||
|
|
||||||
|
const isActive = id === activeTabId;
|
||||||
|
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
globalCache.set(cacheKey, id);
|
||||||
|
onClick?.(isActive);
|
||||||
|
}, [globalCache, id, isActive, onClick]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={tabItem}
|
||||||
|
role="tab"
|
||||||
|
aria-label={label}
|
||||||
|
data-active={isActive}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user