diff --git a/apps/desktop/layer/renderer/src/components/ui/modal/stacked/context.tsx b/apps/desktop/layer/renderer/src/components/ui/modal/stacked/context.tsx index 63c1cf637..bf556f8ff 100644 --- a/apps/desktop/layer/renderer/src/components/ui/modal/stacked/context.tsx +++ b/apps/desktop/layer/renderer/src/components/ui/modal/stacked/context.tsx @@ -2,6 +2,8 @@ import type { FC, RefObject } from "react" import { createContext as reactCreateContext } from "react" import { createContext as createContextSelector } from "use-context-selector" +import type { ModalProps } from "./types" + export type CurrentModalContentProps = ModalActionsInternal & { ref: RefObject modalElementRef: RefObject @@ -37,3 +39,10 @@ export type ModalActionsInternal = { setClickOutSideToDismiss: (value: boolean) => void getIndex: () => number } + +type Disposer = () => void +type PresentModalContextInternalFn = (props: ModalProps & { id?: string }) => Disposer +export const PresentModalContextInternal = reactCreateContext(() => { + warnNoProvider() + return () => {} +}) diff --git a/apps/desktop/layer/renderer/src/components/ui/modal/stacked/hooks.tsx b/apps/desktop/layer/renderer/src/components/ui/modal/stacked/hooks.tsx index 6eb115ead..1d5ca0ded 100644 --- a/apps/desktop/layer/renderer/src/components/ui/modal/stacked/hooks.tsx +++ b/apps/desktop/layer/renderer/src/components/ui/modal/stacked/hooks.tsx @@ -1,75 +1,30 @@ import { Button } from "@follow/components/ui/button/index.js" -import { nextFrame } from "@follow/utils/dom" import { atom, useAtomValue } from "jotai" import type { DragControls } from "motion/react" import type { ResizeCallback, ResizeStartCallback } from "re-resizable" -import { use, useId, useRef, useState } from "react" +import { use, useState } from "react" import { flushSync } from "react-dom" import { useTranslation } from "react-i18next" import { useContextSelector } from "use-context-selector" import { useEventCallback } from "usehooks-ts" -import { getUISettings } from "~/atoms/settings/ui" import { jotaiStore } from "~/lib/jotai" import { modalStackAtom } from "./atom" import { ModalEventBus } from "./bus" -import { CurrentModalContext, CurrentModalStateContext } from "./context" -import type { DialogInstance, ModalProps, ModalStackOptions } from "./types" +import { + CurrentModalContext, + CurrentModalStateContext, + PresentModalContextInternal, +} from "./context" +import type { DialogInstance, ModalProps } from "./types" export const modalIdToPropsMap = {} as Record -export const useModalStack = (options?: ModalStackOptions) => { - const id = useId() - const currentCount = useRef(0) - const { wrapper } = options || {} +export const useModalStack = () => { + const present = use(PresentModalContextInternal) return { - present: useEventCallback((props: ModalProps & { id?: string }) => { - const presentSync = (props: ModalProps & { id?: string }) => { - const fallbackModelId = `${id}-${++currentCount.current}` - const modalId = props.id ?? fallbackModelId - - const currentStack = jotaiStore.get(modalStackAtom) - - const existingModal = currentStack.find((item) => item.id === modalId) - - if (existingModal) { - // Move to top - jotaiStore.set(modalStackAtom, (p) => { - const index = p.indexOf(existingModal) - return [...p.slice(0, index), ...p.slice(index + 1), existingModal] - }) - } else { - // NOTE: The props of the Command Modal are immutable, so we'll just take the store value and inject it. - // There is no need to inject `overlay` props, this is rendered responsively based on ui changes. - const uiSettings = getUISettings() - const modalConfig: Partial = { - draggable: uiSettings.modalDraggable, - modal: true, - } - jotaiStore.set(modalStackAtom, (p) => { - const modalProps: ModalProps = { - ...modalConfig, - ...props, - - wrapper, - } - modalIdToPropsMap[modalId] = modalProps - return p.concat({ - id: modalId, - ...modalProps, - }) - }) - } - - return () => { - jotaiStore.set(modalStackAtom, (p) => p.filter((item) => item.id !== modalId)) - } - } - - return nextFrame(() => presentSync(props)) - }), - + present, ...actions, } } diff --git a/apps/desktop/layer/renderer/src/components/ui/modal/stacked/provider.tsx b/apps/desktop/layer/renderer/src/components/ui/modal/stacked/provider.tsx index fcffdb1cc..b30506f3a 100644 --- a/apps/desktop/layer/renderer/src/components/ui/modal/stacked/provider.tsx +++ b/apps/desktop/layer/renderer/src/components/ui/modal/stacked/provider.tsx @@ -1,17 +1,73 @@ +import { nextFrame } from "@follow/utils/dom" import type { FC, PropsWithChildren } from "react" +import { useId, useRef } from "react" +import { useEventCallback } from "usehooks-ts" +import { getUISettings } from "~/atoms/settings/ui" +import { jotaiStore } from "~/lib/jotai" + +import { modalStackAtom } from "./atom" +import { PresentModalContextInternal } from "./context" +import { modalIdToPropsMap } from "./hooks" import { ModalStack } from "./modal-stack" import type { ModalProps } from "./types" declare global { interface Window { - presentModal: (modal: ModalProps) => void + presentModal: (modal: ModalProps & { id?: string }) => void } } -export const ModalStackProvider: FC = ({ children }) => ( - <> - {children} - - -) +export const ModalStackProvider: FC = ({ children }) => { + const id = useId() + const currentCount = useRef(0) + const presentModal = useEventCallback((props: ModalProps & { id?: string }) => { + const fallbackModelId = `${id}-${++currentCount.current}` + const modalId = props.id ?? fallbackModelId + const presentSync = (props: ModalProps & { id?: string }) => { + const currentStack = jotaiStore.get(modalStackAtom) + + const existingModal = currentStack.find((item) => item.id === modalId) + + if (existingModal) { + // Move to top + jotaiStore.set(modalStackAtom, (p) => { + const index = p.indexOf(existingModal) + return [...p.slice(0, index), ...p.slice(index + 1), existingModal] + }) + } else { + // NOTE: The props of the Command Modal are immutable, so we'll just take the store value and inject it. + // There is no need to inject `overlay` props, this is rendered responsively based on ui changes. + const uiSettings = getUISettings() + const modalConfig: Partial = { + draggable: uiSettings.modalDraggable, + modal: true, + } + jotaiStore.set(modalStackAtom, (p) => { + const modalProps: ModalProps = { + ...modalConfig, + ...props, + } + modalIdToPropsMap[modalId] = modalProps + return p.concat({ + id: modalId, + ...modalProps, + }) + }) + } + } + + nextFrame(() => presentSync(props)) + + return () => { + jotaiStore.set(modalStackAtom, (p) => p.filter((item) => item.id !== modalId)) + } + }) + + return ( + + {children} + + + ) +} diff --git a/apps/desktop/layer/renderer/src/components/ui/modal/stacked/types.tsx b/apps/desktop/layer/renderer/src/components/ui/modal/stacked/types.tsx index 2fb4c03bf..269e5c32a 100644 --- a/apps/desktop/layer/renderer/src/components/ui/modal/stacked/types.tsx +++ b/apps/desktop/layer/renderer/src/components/ui/modal/stacked/types.tsx @@ -32,9 +32,6 @@ export interface ModalProps { autoFocus?: boolean } -export interface ModalStackOptions { - wrapper?: FC -} export interface DialogInstance { ask: (options: { diff --git a/apps/desktop/layer/renderer/src/providers/lazy/index.electron.ts b/apps/desktop/layer/renderer/src/providers/lazy/index.electron.ts index 8a0432210..bc1d2e1dd 100644 --- a/apps/desktop/layer/renderer/src/providers/lazy/index.electron.ts +++ b/apps/desktop/layer/renderer/src/providers/lazy/index.electron.ts @@ -2,6 +2,5 @@ export { ContextMenuProvider as LazyContextMenuProvider } from "../context-menu- export { ExtensionExposeProvider as LazyExtensionExposeProvider } from "../extension-expose-provider" export { ExternalJumpInProvider as LazyExternalJumpInProvider } from "../external-jump-in-provider" export { LottieRenderContainer as LazyLottieRenderContainer } from "~/components/ui/lottie-container" -export { ModalStackProvider as LazyModalStackProvider } from "~/components/ui/modal" export const LazyReloadPrompt = () => null export const LazyPWAPrompt = () => null diff --git a/apps/desktop/layer/renderer/src/providers/lazy/index.ts b/apps/desktop/layer/renderer/src/providers/lazy/index.ts index 26ccbca6c..196751387 100644 --- a/apps/desktop/layer/renderer/src/providers/lazy/index.ts +++ b/apps/desktop/layer/renderer/src/providers/lazy/index.ts @@ -10,11 +10,6 @@ const LazyContextMenuProvider = lazy(() => default: res.ContextMenuProvider, })), ) -const LazyModalStackProvider = lazy(() => - import("../../components/ui/modal/stacked/provider").then((res) => ({ - default: res.ModalStackProvider, - })), -) const LazyExtensionExposeProvider = lazy(() => import("./../extension-expose-provider").then((res) => ({ @@ -52,7 +47,6 @@ export { LazyContextMenuProvider, LazyExtensionExposeProvider, LazyLottieRenderContainer, - LazyModalStackProvider, LazyPWAPrompt, LazyReloadPrompt, } diff --git a/apps/desktop/layer/renderer/src/providers/root-providers.tsx b/apps/desktop/layer/renderer/src/providers/root-providers.tsx index 70e0a1e17..00a56ac2f 100644 --- a/apps/desktop/layer/renderer/src/providers/root-providers.tsx +++ b/apps/desktop/layer/renderer/src/providers/root-providers.tsx @@ -12,6 +12,7 @@ import { Suspense } from "react" import { GoogleReCaptchaProvider } from "react-google-recaptcha-v3" import { HotkeysProvider } from "react-hotkeys-hook" +import { ModalStackProvider } from "~/components/ui/modal" import { HotKeyScopeMap } from "~/constants/hotkeys" import { jotaiStore } from "~/lib/jotai" import { persistConfig, queryClient } from "~/lib/query-client" @@ -24,7 +25,6 @@ import { LazyExtensionExposeProvider, LazyExternalJumpInProvider, LazyLottieRenderContainer, - LazyModalStackProvider, LazyPWAPrompt, LazyReloadPrompt, // specific import should add `index` postfix @@ -39,40 +39,41 @@ export const RootProviders: FC = ({ children }) => ( useEnterprise={true} useRecaptchaNet={true} > - - - - + + + + - - + + + - - + + - - - - {import.meta.env.DEV && } + + + + {import.meta.env.DEV && } - {children} + {children} - - - - - - - - {!window.__RN__ && } - + + + + + + + {!window.__RN__ && } + + - - + - - - + + + + )