Last active
March 13, 2025 10:23
-
-
Save adnanalbeda/12d6fbe8a40d1a79a0ca9e772b0a3863 to your computer and use it in GitHub Desktop.
React-RadixUI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
Children, | |
cloneElement, | |
createContext, | |
useCallback, | |
useContext, | |
useMemo, | |
useState, | |
} from "react"; | |
import { Slot, SlotProps } from "@radix-ui/react-slot"; | |
import { DialogProps } from "@radix-ui/react-dialog"; | |
type Maybe<T> = T | null | undefined; | |
const MultiDialogContainerContext = createContext<unknown>(null); | |
MultiDialogContainerContext.displayName = "MultiDialogContainerContext"; | |
export function useMultiDialog<T = unknown>(): [ | |
Maybe<T>, | |
React.Dispatch<React.SetStateAction<Maybe<T>>>, | |
]; | |
export function useMultiDialog<T = unknown>( | |
v: T, | |
): [boolean, (v: boolean) => void]; | |
export function useMultiDialog<T = unknown>(v?: T) { | |
const s = useContext(MultiDialogContainerContext) as [ | |
Maybe<T>, | |
React.Dispatch<React.SetStateAction<Maybe<T>>>, | |
]; | |
if (!s) | |
throw new Error( | |
"Cannot use 'useMultiDialog' outside 'MultiDialogProvider'.", | |
); | |
if (v == null) return s; | |
const [dialog, setDialog] = s; | |
const onOpenChange = useCallback( | |
(o: boolean) => (o ? setDialog(v) : setDialog(null)), | |
[v], | |
); | |
const open = dialog === v; | |
const result = useMemo(() => [open, onOpenChange] as const, [open]); | |
return result; | |
} | |
export function MultiDialogTrigger<T = unknown>({ | |
value, | |
onClick, | |
...props | |
}: SlotProps & | |
React.RefAttributes<HTMLElement> & { | |
value: T; | |
}) { | |
const [, open] = useMultiDialog(value); | |
const oc = useCallback<React.MouseEventHandler<HTMLElement>>( | |
(e) => { | |
open(true); | |
onClick && onClick(e); | |
}, | |
[value, onClick], | |
); | |
return <Slot onClick={oc} {...props} />; | |
} | |
export function MultiDialogContainer<T = unknown>({ | |
value, | |
children, | |
...props | |
}: Omit<DialogProps, "open" | "onOpenChange"> & { | |
value: T; | |
children?: JSX.Element; | |
}) { | |
const [open, onOpenChange] = useMultiDialog(value); | |
return useMemo(() => { | |
Children.only(children); | |
return children | |
? cloneElement(children, { | |
...props, | |
open, | |
onOpenChange, | |
}) | |
: null; | |
}, [children, open]); | |
} | |
type Builder<T = unknown> = { | |
readonly Trigger: ( | |
...args: Parameters<typeof MultiDialogTrigger<T>> | |
) => React.ReactNode; | |
readonly Container: ( | |
...args: Parameters<typeof MultiDialogContainer<T>> | |
) => React.ReactNode; | |
}; | |
const builder = { | |
Trigger: MultiDialogTrigger, | |
Container: MultiDialogContainer, | |
} as const; | |
export type MultiDialogBuilder<T = unknown> = ( | |
builder: Builder<T>, | |
) => React.ReactNode; | |
export function MultiDialog<T = unknown>({ | |
defaultOpen = null, | |
children, | |
}: { | |
defaultOpen?: T | null; | |
children?: React.ReactNode | MultiDialogBuilder<T>; | |
}) { | |
const [state, setState] = useState<T | null>(defaultOpen); | |
const c = useMemo( | |
() => (typeof children === "function" ? children(builder) : children), | |
[children], | |
); | |
return ( | |
<MultiDialogContainerContext.Provider value={[state, setState]}> | |
{c} | |
</MultiDialogContainerContext.Provider> | |
); | |
} |
Hey @adnanalbeda! Great work with the multidialog.. works great! But i have one question.
How can i trigger the dialog close "manually"? Does not work as it does with standard shadcn componets with the open
and handleOpenChange
.
Thanks in advance!
//...
const [openPopovers, setOpenPopovers] = useState<OpenPopovers>({});
const handleOpenChange = (id: number, open: boolean) => {
setOpenPopovers((prev) => ({ ...prev, [id.toString()]: open }));
};
//...
return (
//...
<mdb.Container value="delete">
<Dialog open={openPopovers[file.id] || false} onOpenChange={(open) => handleOpenChange(file.id, open)}>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. Are you sure you want to permanently delete this file?
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
onClick={() => {
handleOpenChange(file.id, false);
}}
>
Cancel
</Button>
<Button
onClick={() => {
removeDocument(file.id);
}}
>
Confirm
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</mdb.Container>
//...
How can i trigger the dialog close "manually"? Does not work as it does with standard shadcn componets with the
open
andhandleOpenChange
.Thanks in advance!
Hi @mmo80 !
I'm glad you found my code useful.
I just updated the usage comment and explained how to do that. I hope you find it helpful.
You can also change the code a bit and pass setState to the builder function:
export type MultiDialogBuilder<T = unknown> = (
builder: Builder<T>,
setOpen: (v: T | null) => void
) => React.ReactNode;
export function MultiDialog<T = unknown>({
defaultOpen = null,
children,
}: {
defaultOpen?: T | null;
children?: React.ReactNode | MultiDialogBuilder<T>;
}) {
const [state, setState] = useState<T | null>(defaultOpen);
const c = useMemo(
() => (typeof children === "function" ? children(builder, setState) : children),
[children],
);
return (
<MultiDialogContainerContext.Provider value={[state, setState]}>
{c}
</MultiDialogContainerContext.Provider>
);
}
and use it to manually close the dialog:
<MuliDialog<Modals>>
{(mdb,setOpen) => (<>
<mdb.Trigger value="edit">
<button>Edit</button>
</mdb.Trigger>
<mdb.Trigger value="delete">
{/* Can be used with dropdown menu item or context menu item */}
<button>Edit</button>
</mdb.Trigger>
<mdb.Container value="edit">
<Dialog>
<DialogPortal>
<DialogOverlay />
<DialogContent>
EDIT FORM CONTENT
<button onClick={()=>{
// do some processing
setOpen(null);
}}>
SUBMIT
</button>
</DialogContent>
</DialogPortal>
</Dialog>
</mdb.Container>
<mdb.Container value="delete">
<Dialog>
<DialogPortal>
<DialogOverlay />
<DialogContent>
DELETE CONTENT
</DialogContent>
</DialogPortal>
</Dialog>
</mdb.Container>
</>)}
</MultiDialog>
Will try it out, tnx!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
multiDialog.tsx Usage
Manually Closing Dialog After Processing Data