diff --git a/src/components/Dialog/context.ts b/src/components/Dialog/context.ts index 2d9a41085d..b28b9f5a25 100644 --- a/src/components/Dialog/context.ts +++ b/src/components/Dialog/context.ts @@ -1,27 +1,19 @@ import React from 'react' import {useDialogStateContext} from '#/state/dialogs' -import { - DialogContextProps, - DialogControlProps, - DialogControlWithRefProps, - DialogParams, -} from '#/components/Dialog/types' +import {DialogContextProps, DialogControlProps} from '#/components/Dialog/types' -export const Context = React.createContext>({ - params: {}, +export const Context = React.createContext({ close: () => {}, }) -export function useDialogContext() { - return React.useContext(Context) as DialogContextProps +export function useDialogContext() { + return React.useContext(Context) } -export function useDialogControl< - Params extends DialogParams, ->(): DialogControlWithRefProps { +export function useDialogControl() { const id = React.useId() - const control = React.useRef>({ + const control = React.useRef({ open: () => {}, close: () => {}, }) @@ -37,7 +29,7 @@ export function useDialogControl< return { ref: control, - open: (params, options) => control.current.open(params, options), + open: () => control.current.open(), close: () => control.current.close(), } } diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx index 8103f6aebc..702ec43ccd 100644 --- a/src/components/Dialog/index.tsx +++ b/src/components/Dialog/index.tsx @@ -16,7 +16,6 @@ import { DialogOuterProps, DialogControlProps, DialogInnerProps, - DialogParams, } from '#/components/Dialog/types' import {Context} from '#/components/Dialog/context' @@ -25,27 +24,21 @@ export * from '#/components/Dialog/types' // @ts-ignore export const Input = createInput(BottomSheetTextInput) -export function Outer({ +export function Outer({ children, control, onClose, nativeOptions, -}: React.PropsWithChildren>) { + defaultOpen, +}: React.PropsWithChildren) { const t = useTheme() const sheet = React.useRef(null) const sheetOptions = nativeOptions?.sheet || {} const hasSnapPoints = !!sheetOptions.snapPoints - const [params, setParams] = React.useState({}) - const open = React.useCallback['open']>( - (params, {snapIndex} = {}) => { - if (params) { - setParams(params) - } - sheet.current?.snapToIndex(snapIndex || 0) - }, - [setParams], - ) + const open = React.useCallback(({index} = {}) => { + sheet.current?.snapToIndex(index || 0) + }, []) const close = React.useCallback(() => { sheet.current?.close() @@ -61,7 +54,7 @@ export function Outer({ [open, close], ) - const context = React.useMemo(() => ({close, params}), [close, params]) + const context = React.useMemo(() => ({close}), [close]) return ( @@ -73,7 +66,7 @@ export function Outer({ keyboardBlurBehavior="restore" {...sheetOptions} ref={sheet} - index={-1} + index={defaultOpen ? 0 : -1} backgroundStyle={{backgroundColor: 'transparent'}} backdropComponent={props => ( e.stopPropagation() -export function Outer({ +export function Outer({ + children, control, onClose, - children, -}: React.PropsWithChildren>) { + defaultOpen, +}: React.PropsWithChildren) { const {_} = useLingui() const t = useTheme() const {gtMobile} = useBreakpoints() - const [isOpen, setIsOpen] = React.useState(false) + const [isOpen, setIsOpen] = React.useState(defaultOpen) const [isVisible, setIsVisible] = React.useState(true) - const [params, setParams] = React.useState({}) - const open = React.useCallback['open']>( - params => { - if (params) { - setParams(params) - } - setIsOpen(true) - }, - [setIsOpen, setParams], - ) + const open = React.useCallback(() => { + setIsOpen(true) + }, [setIsOpen]) const close = React.useCallback(async () => { setIsVisible(false) @@ -75,10 +64,9 @@ export function Outer({ const context = React.useMemo( () => ({ - params, close, }), - [close, params], + [close], ) return ( diff --git a/src/components/Dialog/types.ts b/src/components/Dialog/types.ts index d9ab4a2e44..9ea477a2f7 100644 --- a/src/components/Dialog/types.ts +++ b/src/components/Dialog/types.ts @@ -4,24 +4,24 @@ import {BottomSheetProps} from '@gorhom/bottom-sheet' type A11yProps = Required -export type DialogParams = Record - -export type DialogContextProps = { - params: Params +export type DialogContextProps = { close: () => void } -export type DialogControlProps = { - open: (params?: Params, options?: {snapIndex?: number}) => void +export type DialogControlOpenOptions = {index?: number} + +export type DialogControlProps = { + open: (options?: DialogControlOpenOptions) => void close: () => void } -export type DialogControlWithRefProps = { - ref: React.RefObject> -} & DialogControlProps - -export type DialogOuterProps = { - control: DialogControlWithRefProps +export type DialogOuterProps = { + defaultOpen?: boolean + control: { + ref: React.RefObject + open: (index?: number) => void + close: () => void + } onClose?: () => void nativeOptions?: { sheet?: Omit diff --git a/src/components/dialogs/ReportDialog/index.tsx b/src/components/dialogs/ReportDialog/index.tsx index 6d720f83b3..9fcb4de911 100644 --- a/src/components/dialogs/ReportDialog/index.tsx +++ b/src/components/dialogs/ReportDialog/index.tsx @@ -1,10 +1,11 @@ import React from 'react' +import {View} from 'react-native' -import * as Dialog from '#/components/Dialog' import {Text} from '#/components/Typography' -import {Context} from '#/components/dialogs' +import * as Dialog from '#/components/Dialog' +import {GlobalDialogProps} from '#/components/dialogs' -export type DialogParams = +export type ReportDialogProps = | { type: 'post' uri: string @@ -15,25 +16,20 @@ export type DialogParams = did: string } -export function useReportDialogControl() { - return React.useContext(Context).report -} +export function ReportDialog(props: GlobalDialogProps) { + const control = Dialog.useDialogControl() -export function ReportDialog() { - const control = useReportDialogControl() + const onClose = React.useCallback(() => { + props.cleanup() + }, [props]) return ( - - - - - - + + + + hello {JSON.stringify(props)} + + ) } - -function Inner() { - const ctx = Dialog.useDialogContext() - return hello {JSON.stringify(ctx.params)} -} diff --git a/src/components/dialogs/index.tsx b/src/components/dialogs/index.tsx index 67d4c7f40c..58eecb7705 100644 --- a/src/components/dialogs/index.tsx +++ b/src/components/dialogs/index.tsx @@ -2,31 +2,94 @@ import React from 'react' import * as Dialog from '#/components/Dialog' -import {DialogParams as ReportDialogParams} from '#/components/dialogs/ReportDialog' +/** + * Type util for global dialog components. Wrap individual dialog component + * types with this to get types for the additional properties applied by the + * the global dialon controller. + */ +export type GlobalDialogProps = { + params: T + cleanup(): void + options?: Dialog.DialogControlOpenOptions +} + +type ActiveDialog> = { + component: T + props: React.ComponentProps + options?: Dialog.DialogControlOpenOptions +} + +type ContextProps = { + activeDialogs: ActiveDialog[] + open>( + component: T, + props: React.ComponentProps['params'], + options?: Dialog.DialogControlOpenOptions, + ): void + popTopDialog(): void +} + +export const Context = React.createContext({ + activeDialogs: [], + open() {}, + popTopDialog() {}, +}) /** - * Global dialog context. Name each dialog and specify its parameters. + * Hook to open a "global" dialog. + * + * @example + * ```tsx + * const openGlobalDialog = useOpenGlobalDialog() + * openGlobalDialog( + * MyDialog, + * // component props + * {foo: 'bar'}, + * // base Dialog options + * { index: 1 } + * ) + * ``` */ -type Context = { - report: Dialog.DialogControlWithRefProps +export function useOpenGlobalDialog() { + return React.useContext(Context).open } /** - * Global dialog context. - */ -export const Context = React.createContext({} as Context) - -/** - * Global dialog context provider. + * Provider for "global" dialogs _only_. ALL dialogs are still registered by + * `#/state/dialogs` as well. */ export function Provider({children}: React.PropsWithChildren<{}>) { - const report = Dialog.useDialogControl() - const ctx = React.useMemo( + const [activeDialogs, setActiveDialogs] = React.useState[]>( + [], + ) + const ctx = React.useMemo( () => ({ - report, + activeDialogs, + open(component, props, options) { + setActiveDialogs(s => [...s, {component, props, options}]) + }, + popTopDialog() { + setActiveDialogs(s => s.slice(0, -1)) + }, }), - [report], + [activeDialogs, setActiveDialogs], ) return {children} } + +/** + * Outlet for any active "global" dialogs. Since these are rendered into a + * portal in the root, this is technically not their final resting place. + */ +export function GlobalDialog() { + const {activeDialogs, popTopDialog} = React.useContext(Context) + + const cleanup = React.useCallback(() => { + popTopDialog() + }, [popTopDialog]) + + return activeDialogs.map(({component: Comp, props, options}, i) => { + return + }) +} diff --git a/src/view/com/util/forms/PostDropdownBtn.tsx b/src/view/com/util/forms/PostDropdownBtn.tsx index 0392d94647..eed7cd593c 100644 --- a/src/view/com/util/forms/PostDropdownBtn.tsx +++ b/src/view/com/util/forms/PostDropdownBtn.tsx @@ -31,7 +31,8 @@ import {useLingui} from '@lingui/react' import {useSession} from '#/state/session' import {isWeb} from '#/platform/detection' import {richTextToString} from '#/lib/strings/rich-text-helpers' -import {useReportDialogControl} from '#/components/dialogs/ReportDialog' +import {useOpenGlobalDialog} from '#/components/dialogs' +import {ReportDialog} from '#/components/dialogs/ReportDialog' let PostDropdownBtn = ({ testID, @@ -64,7 +65,7 @@ let PostDropdownBtn = ({ const hiddenPosts = useHiddenPosts() const {hidePost} = useHiddenPostsApi() const openLink = useOpenLink() - const reportDialogControl = useReportDialogControl() + const openDialog = useOpenGlobalDialog() const rootUri = record.reply?.root?.uri || postUri const isThreadMuted = mutedThreads.includes(rootUri) @@ -211,7 +212,7 @@ let PostDropdownBtn = ({ hasSession && { label: _(msg`Report post`), onPress() { - reportDialogControl.open({type: 'post', uri: postUri, cid: postCid}) + openDialog(ReportDialog, {type: 'post', uri: postUri, cid: postCid}) // openModal({ // name: 'report', // uri: postUri, diff --git a/src/view/screens/Storybook/Dialogs.tsx b/src/view/screens/Storybook/Dialogs.tsx index db568c6bd5..e24a23d286 100644 --- a/src/view/screens/Storybook/Dialogs.tsx +++ b/src/view/screens/Storybook/Dialogs.tsx @@ -7,11 +7,14 @@ import {H3, P} from '#/components/Typography' import * as Dialog from '#/components/Dialog' import * as Prompt from '#/components/Prompt' import {useDialogStateControlContext} from '#/state/dialogs' +import {useOpenGlobalDialog} from '#/components/dialogs' +import {ReportDialog} from '#/components/dialogs/ReportDialog' export function Dialogs() { const control = Dialog.useDialogControl() const prompt = Prompt.usePromptControl() const {closeAllDialogs} = useDialogStateControlContext() + const openDialog = useOpenGlobalDialog() return ( @@ -36,6 +39,19 @@ export function Dialogs() { Open prompt + + + + This is a prompt diff --git a/src/view/screens/Storybook/index.tsx b/src/view/screens/Storybook/index.tsx index 40929555e5..56cfbfaf98 100644 --- a/src/view/screens/Storybook/index.tsx +++ b/src/view/screens/Storybook/index.tsx @@ -66,6 +66,7 @@ export function Storybook() { + @@ -83,7 +84,6 @@ export function Storybook() { - diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index 2954921ad7..691f5d67eb 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -29,7 +29,7 @@ import {useSession} from '#/state/session' import {useCloseAnyActiveElement} from '#/state/util' import * as notifications from 'lib/notifications/notifications' import {Outlet as PortalOutlet} from '#/components/Portal' -import {ReportDialog} from '#/components/dialogs/ReportDialog' +import {GlobalDialog} from '#/components/dialogs' function ShellInner() { const isDrawerOpen = useIsDrawerOpen() @@ -97,7 +97,7 @@ function ShellInner() { - + ) diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx index 50d91a3557..72c37b166c 100644 --- a/src/view/shell/index.web.tsx +++ b/src/view/shell/index.web.tsx @@ -16,7 +16,7 @@ import {useIsDrawerOpen, useSetDrawerOpen} from '#/state/shell' import {useCloseAllActiveElements} from '#/state/util' import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock' import {Outlet as PortalOutlet} from '#/components/Portal' -import {ReportDialog} from '#/components/dialogs/ReportDialog' +import {GlobalDialog} from '#/components/dialogs' function ShellInner() { const isDrawerOpen = useIsDrawerOpen() @@ -43,7 +43,7 @@ function ShellInner() { - + {!isDesktop && isDrawerOpen && (