refactor: use zustand in global modal (#940)

This commit is contained in:
Himself65 2023-02-09 22:03:15 -06:00 committed by GitHub
parent 323d7c96f7
commit 094e73b7fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 175 additions and 133 deletions

View File

@ -37,7 +37,8 @@
"quill-cursors": "^4.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"yjs": "^13.5.45"
"yjs": "^13.5.45",
"zustand": "^4.3.2"
},
"devDependencies": {
"@types/node": "18.7.18",

View File

@ -1,7 +1,7 @@
import React from 'react';
import { IconButton, IconButtonProps } from '@affine/component';
import { ArrowDownIcon } from '@blocksuite/icons';
import { useModal } from '@/providers/GlobalModalProvider';
import { useModal } from '@/store/globalModal';
import { styled } from '@affine/component';
const StyledIconButtonWithAnimate = styled(IconButton)(({ theme }) => {

View File

@ -1,5 +1,5 @@
import { CloudUnsyncedIcon } from '@blocksuite/icons';
import { useModal } from '@/providers/GlobalModalProvider';
import { useModal } from '@/store/globalModal';
import { useAppState } from '@/providers/app-state-provider';
import { IconButton } from '@affine/component';

View File

@ -9,7 +9,7 @@ import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './Icons';
import { Tooltip } from '@affine/component';
import { useTranslation } from '@affine/i18n';
import { useModal } from '@/providers/GlobalModalProvider';
import { useModal } from '@/store/globalModal';
import { MuiFade } from '@affine/component';
export type IslandItemNames = 'contact' | 'shortcuts';
export const HelpIsland = ({

View File

@ -11,7 +11,7 @@ import { Results } from './Results';
import { Footer } from './Footer';
import { Command } from 'cmdk';
import { useEffect, useState } from 'react';
import { useModal } from '@/providers/GlobalModalProvider';
import { useModal } from '@/store/globalModal';
import { getUaHelper } from '@/utils';
import { useRouter } from 'next/router';
import { PublishedResults } from './PublishedResults';

View File

@ -1,4 +1,4 @@
import { useModal } from '@/providers/GlobalModalProvider';
import { useModal } from '@/store/globalModal';
import { styled } from '@affine/component';
import { AffineIcon } from '../../icons/Icons';
import {

View File

@ -23,7 +23,7 @@ import {
import Link from 'next/link';
import { MuiCollapse } from '@affine/component';
import { Tooltip } from '@affine/component';
import { useModal } from '@/providers/GlobalModalProvider';
import { useModal } from '@/store/globalModal';
import { useAppState } from '@/providers/app-state-provider';
import { IconButton } from '@affine/component';
import useLocalStorage from '@/hooks/use-local-storage';

View File

@ -12,7 +12,7 @@ import type { PropsWithChildren, ReactElement, ReactNode } from 'react';
import type { NextPage } from 'next';
import { AppStateProvider } from '@/providers/app-state-provider';
import ConfirmProvider from '@/providers/ConfirmProvider';
import { ModalProvider } from '@/providers/GlobalModalProvider';
import { ModalProvider } from '@/store/globalModal';
// import AppStateProvider2 from '@/providers/app-state-provider2/provider';
import { useRouter } from 'next/router';

View File

@ -11,7 +11,7 @@ import { IconButton } from '@affine/component';
import NextLink from 'next/link';
import { PaperIcon, SearchIcon } from '@blocksuite/icons';
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
import { useModal } from '@/providers/GlobalModalProvider';
import { useModal } from '@/store/globalModal';
const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
ssr: false,

View File

@ -11,7 +11,7 @@ import {
import { Breadcrumbs } from '@affine/component';
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
import { SearchIcon } from '@blocksuite/icons';
import { useModal } from '@/providers/GlobalModalProvider';
import { useModal } from '@/store/globalModal';
const All = () => {
const { dataCenter } = useAppState();
const router = useRouter();

View File

@ -1,123 +0,0 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import {
createContext,
useCallback,
useContext,
useEffect,
useState,
} from 'react';
import type { PropsWithChildren } from 'react';
import ShortcutsModal from '@/components/shortcuts-modal';
import ContactModal from '@/components/contact-modal';
import QuickSearch from '@/components/quick-search';
import { ImportModal } from '@/components/import';
import { LoginModal } from '@/components/login-modal';
type ModalContextValue = {
triggerShortcutsModal: () => void;
triggerContactModal: () => void;
triggerQuickSearchModal: (visible?: boolean) => void;
triggerImportModal: () => void;
triggerLoginModal: () => void;
};
type ModalContextProps = PropsWithChildren<Record<string, unknown>>;
type ModalMap = {
contact: boolean;
shortcuts: boolean;
quickSearch: boolean;
import: boolean;
login: boolean;
};
export const ModalContext = createContext<ModalContextValue>({
triggerShortcutsModal: () => {},
triggerContactModal: () => {},
triggerQuickSearchModal: () => {},
triggerImportModal: () => {},
triggerLoginModal: () => {},
});
export const useModal = () => useContext(ModalContext);
export const ModalProvider = ({
children,
}: PropsWithChildren<ModalContextProps>) => {
const [modalMap, setModalMap] = useState<ModalMap>({
contact: false,
shortcuts: false,
quickSearch: false,
import: false,
login: false,
});
const triggerHandler = useCallback(
(key: keyof ModalMap, visible?: boolean) => {
setModalMap({
...modalMap,
[key]: visible ?? !modalMap[key],
});
},
[modalMap]
);
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.triggerHandler = () => triggerHandler('login');
}, [triggerHandler]);
return (
<ModalContext.Provider
value={{
triggerShortcutsModal: () => {
triggerHandler('shortcuts');
},
triggerContactModal: () => {
triggerHandler('contact');
},
triggerQuickSearchModal: (visible?) => {
triggerHandler('quickSearch', visible);
},
triggerImportModal: () => {
triggerHandler('import');
},
triggerLoginModal: () => {
triggerHandler('login');
},
}}
>
<ContactModal
open={modalMap.contact}
onClose={() => {
triggerHandler('contact', false);
}}
></ContactModal>
<ShortcutsModal
open={modalMap.shortcuts}
onClose={() => {
triggerHandler('shortcuts', false);
}}
></ShortcutsModal>
<QuickSearch
open={modalMap.quickSearch}
onClose={() => {
triggerHandler('quickSearch', false);
}}
></QuickSearch>
<ImportModal
open={modalMap.import}
onClose={() => {
triggerHandler('import', false);
}}
></ImportModal>
<LoginModal
open={modalMap.login}
onClose={() => {
triggerHandler('login', false);
}}
/>
{children}
</ModalContext.Provider>
);
};
export default ModalProvider;

View File

@ -0,0 +1,146 @@
import type React from 'react';
import { createContext, useCallback, useContext, useMemo } from 'react';
import { createStore, useStore } from 'zustand';
import { combine, subscribeWithSelector } from 'zustand/middleware';
import { UseBoundStore } from 'zustand/react';
import ContactModal from '@/components/contact-modal';
import ShortcutsModal from '@/components/shortcuts-modal';
import QuickSearch from '@/components/quick-search';
import { LoginModal } from '@/components/login-modal';
import ImportModal from '@/components/import';
export type ModalState = {
contact: boolean;
shortcuts: boolean;
quickSearch: boolean;
import: boolean;
login: boolean;
};
export type ModalActions = {
triggerShortcutsModal: () => void;
triggerContactModal: () => void;
triggerQuickSearchModal: (visible?: boolean) => void;
triggerImportModal: () => void;
triggerLoginModal: () => void;
};
const create = () =>
createStore(
subscribeWithSelector(
combine<ModalState, ModalActions>(
{
contact: false,
shortcuts: false,
quickSearch: false,
import: false,
login: false,
},
set => ({
triggerShortcutsModal: () => {
set(({ shortcuts }) => ({
shortcuts: !shortcuts,
}));
},
triggerContactModal: () => {
set(({ contact }) => ({
contact: !contact,
}));
},
triggerQuickSearchModal: (visible?: boolean) => {
set(({ quickSearch }) => ({
quickSearch: visible ?? !quickSearch,
}));
},
triggerImportModal: () => {
set(state => ({
import: !state.import,
}));
},
triggerLoginModal: () => {
set(({ login }) => ({
login: !login,
}));
},
})
)
)
);
type Store = ReturnType<typeof create>;
const ModalContext = createContext<Store | null>(null);
export const useModalApi = () => {
const api = useContext(ModalContext);
if (!api) {
throw new Error('cannot find modal context');
}
return api;
};
export const useModal: UseBoundStore<Store> = ((
selector: Parameters<UseBoundStore<Store>>[0],
equals: Parameters<UseBoundStore<Store>>[1]
) => {
const api = useModalApi();
return useStore(api, selector, equals);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as any;
const Modals: React.FC = function Modal() {
const api = useModalApi();
return (
<>
<ContactModal
open={useModal(state => state.contact)}
onClose={useCallback(() => {
api.setState({
contact: false,
});
}, [api])}
></ContactModal>
<ShortcutsModal
open={useModal(state => state.shortcuts)}
onClose={useCallback(() => {
api.setState({
shortcuts: false,
});
}, [api])}
></ShortcutsModal>
<QuickSearch
open={useModal(state => state.quickSearch)}
onClose={useCallback(() => {
api.setState({
quickSearch: false,
});
}, [api])}
></QuickSearch>
<ImportModal
open={useModal(state => state.import)}
onClose={useCallback(() => {
api.setState({
import: false,
});
}, [api])}
></ImportModal>
<LoginModal
open={useModal(state => state.login)}
onClose={useCallback(() => {
api.setState({
login: false,
});
}, [api])}
/>
</>
);
};
export const ModalProvider: React.FC<React.PropsWithChildren> =
function ModelProvider({ children }) {
return (
<ModalContext.Provider value={useMemo(() => create(), [])}>
<Modals />
{children}
</ModalContext.Provider>
);
};

View File

@ -90,6 +90,7 @@ importers:
react-dom: 18.2.0
typescript: ^4.9.5
yjs: ^13.5.45
zustand: ^4.3.2
dependencies:
'@affine/component': link:../component
'@affine/datacenter': link:../data-center
@ -120,6 +121,7 @@ importers:
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
yjs: 13.5.45
zustand: 4.3.2_react@18.2.0
devDependencies:
'@types/node': 18.7.18
'@types/react': 18.0.20
@ -18152,6 +18154,22 @@ packages:
resolution: {integrity: sha512-oyu0m54SGCtzh6EClBVqDDlAYRz4jrVtKwQ7ZnsEmMI9HnzuZFj8QFwAY1M5uniIYACdGvv0PBWPF2kO0aNofA==}
dev: false
/zustand/4.3.2_react@18.2.0:
resolution: {integrity: sha512-rd4haDmlwMTVWVqwvgy00ny8rtti/klRoZjFbL/MAcDnmD5qSw/RZc+Vddstdv90M5Lv6RPgWvm1Hivyn0QgJw==}
engines: {node: '>=12.7.0'}
peerDependencies:
immer: '>=9.0'
react: '>=16.8'
peerDependenciesMeta:
immer:
optional: true
react:
optional: true
dependencies:
react: 18.2.0
use-sync-external-store: 1.2.0_react@18.2.0
dev: false
/zwitch/1.0.5:
resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==}
dev: true