mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
155 lines
3.7 KiB
TypeScript
155 lines
3.7 KiB
TypeScript
import clsx from "clsx";
|
|
import { X } from "lucide-react";
|
|
import type { ModalOverlayProps } from "react-aria-components";
|
|
import {
|
|
Dialog,
|
|
DialogTrigger,
|
|
Heading,
|
|
Modal,
|
|
ModalOverlay,
|
|
} from "react-aria-components";
|
|
import { useNavigate } from "react-router";
|
|
import * as R from "remeda";
|
|
import { SendouButton } from "~/components/elements/Button";
|
|
import styles from "./Dialog.module.css";
|
|
|
|
interface SendouDialogProps extends ModalOverlayProps {
|
|
trigger?: React.ReactNode;
|
|
children?: React.ReactNode;
|
|
heading?: string;
|
|
showHeading?: boolean;
|
|
onClose?: () => void;
|
|
/** When closing the modal which URL to navigate to */
|
|
onCloseTo?: string;
|
|
overlayClassName?: string;
|
|
"aria-label"?: string;
|
|
/** If true, the modal takes over the full screen with the content below hidden */
|
|
isFullScreen?: boolean;
|
|
/** If true, shows the close button even if onClose is not provided */
|
|
showCloseButton?: boolean;
|
|
}
|
|
|
|
/**
|
|
* This component allows you to create a dialog with a customizable trigger and content.
|
|
* It supports both controlled and uncontrolled modes for managing the dialog's open state.
|
|
*
|
|
* @example
|
|
* // Example usage with implicit isOpen
|
|
* return (
|
|
* <SendouDialog
|
|
* heading="Dialog Title"
|
|
* onCloseTo={previousPageUrl()}
|
|
* >
|
|
* This is the dialog content.
|
|
* </SendouDialog>
|
|
* );
|
|
*
|
|
* @example
|
|
* // Example usage with a SendouButton as the trigger
|
|
* return (
|
|
* <SendouDialog
|
|
* heading="Dialog Title"
|
|
* trigger={<SendouButton>Open Dialog</SendouButton>}
|
|
* >
|
|
* This is the dialog content.
|
|
* </SendouDialog>
|
|
* );
|
|
*/
|
|
export function SendouDialog({
|
|
trigger,
|
|
children,
|
|
...rest
|
|
}: SendouDialogProps) {
|
|
if (!trigger) {
|
|
const props =
|
|
typeof rest.isOpen === "boolean" ? rest : { isOpen: true, ...rest };
|
|
return <DialogModal {...props}>{children}</DialogModal>;
|
|
}
|
|
|
|
return (
|
|
<DialogTrigger>
|
|
{trigger}
|
|
<DialogModal {...rest} isControlledByTrigger>
|
|
{children}
|
|
</DialogModal>
|
|
</DialogTrigger>
|
|
);
|
|
}
|
|
|
|
function DialogModal({
|
|
children,
|
|
heading,
|
|
showHeading = true,
|
|
className,
|
|
showCloseButton: showCloseButtonProp,
|
|
isControlledByTrigger,
|
|
...rest
|
|
}: Omit<SendouDialogProps, "trigger"> & { isControlledByTrigger?: boolean }) {
|
|
const navigate = useNavigate();
|
|
|
|
const showCloseButton = showCloseButtonProp || rest.onClose || rest.onCloseTo;
|
|
const onClose = () => {
|
|
if (rest.onCloseTo) {
|
|
navigate(rest.onCloseTo);
|
|
} else if (rest.onClose) {
|
|
rest.onClose();
|
|
}
|
|
};
|
|
|
|
const defaultOnOpenChange = (isOpen: boolean) => {
|
|
if (!isOpen) {
|
|
if (rest.onCloseTo) {
|
|
navigate(rest.onCloseTo);
|
|
} else if (rest.onClose) {
|
|
rest.onClose();
|
|
}
|
|
}
|
|
};
|
|
|
|
const overlayProps = isControlledByTrigger
|
|
? R.omit(rest, ["onOpenChange"])
|
|
: { ...rest, onOpenChange: rest.onOpenChange ?? defaultOnOpenChange };
|
|
|
|
return (
|
|
<ModalOverlay
|
|
className={clsx(rest.overlayClassName, styles.overlay, {
|
|
[styles.fullScreenOverlay]: rest.isFullScreen,
|
|
})}
|
|
{...overlayProps}
|
|
>
|
|
<Modal
|
|
className={clsx(className, styles.modal, "scrollbar", {
|
|
[styles.fullScreenModal]: rest.isFullScreen,
|
|
})}
|
|
>
|
|
<Dialog className={styles.dialog} aria-label={rest["aria-label"]}>
|
|
{showHeading ? (
|
|
<div
|
|
className={clsx(styles.headingContainer, {
|
|
[styles.noHeading]: !heading,
|
|
})}
|
|
>
|
|
{heading ? (
|
|
<Heading slot="title" className={styles.heading}>
|
|
{heading}
|
|
</Heading>
|
|
) : null}
|
|
{showCloseButton ? (
|
|
<SendouButton
|
|
icon={<X />}
|
|
shape="circle"
|
|
variant="minimal-destructive"
|
|
className="ml-auto"
|
|
slot="close"
|
|
onPress={onClose}
|
|
/>
|
|
) : null}
|
|
</div>
|
|
) : null}
|
|
{children}
|
|
</Dialog>
|
|
</Modal>
|
|
</ModalOverlay>
|
|
);
|
|
}
|