Skip to content

Instantly share code, notes, and snippets.

@adnanalbeda
Last active March 13, 2025 10:23
Show Gist options
  • Save adnanalbeda/12d6fbe8a40d1a79a0ca9e772b0a3863 to your computer and use it in GitHub Desktop.
Save adnanalbeda/12d6fbe8a40d1a79a0ca9e772b0a3863 to your computer and use it in GitHub Desktop.
React-RadixUI
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>
);
}
@adnanalbeda
Copy link
Author

adnanalbeda commented Apr 14, 2024

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!

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>

@mmo80
Copy link

mmo80 commented Apr 16, 2024

Will try it out, tnx!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment