122 lines
3.4 KiB
TypeScript
122 lines
3.4 KiB
TypeScript
|
|
import { useTranslate } from "@workspace/i18n"
|
||
|
|
import { Button } from "@workspace/ui/components/button"
|
||
|
|
import {
|
||
|
|
createDialogHandle,
|
||
|
|
Dialog,
|
||
|
|
DialogClose,
|
||
|
|
DialogContent,
|
||
|
|
DialogDescription,
|
||
|
|
DialogHeader,
|
||
|
|
DialogTitle,
|
||
|
|
} from "@workspace/ui/components/dialog"
|
||
|
|
import { XIcon } from "@workspace/ui/components/icon"
|
||
|
|
|
||
|
|
import { OverlayController, type OverlayRequest } from "./internal/controller"
|
||
|
|
import { renderOverlayContent } from "./internal/render-content"
|
||
|
|
import { useOverlayController } from "./internal/use-controller"
|
||
|
|
import { overlayMessages } from "./messages"
|
||
|
|
import { globalOverlayScope, type OverlayScope } from "./scope"
|
||
|
|
import type { ModalOptions } from "./types"
|
||
|
|
|
||
|
|
type ModalRequestOptions = Omit<ModalOptions<unknown>, "scope">
|
||
|
|
const modalChannel = Symbol("overlay.modal")
|
||
|
|
|
||
|
|
function getModalController(scope: OverlayScope) {
|
||
|
|
return scope.getChannel(modalChannel, () => {
|
||
|
|
const handle = createDialogHandle<OverlayRequest<ModalRequestOptions>>()
|
||
|
|
return new OverlayController<ModalRequestOptions, typeof handle>({
|
||
|
|
handle,
|
||
|
|
isDismissible: (options) => options.dismissible !== false,
|
||
|
|
scope,
|
||
|
|
})
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
export function openModal<Result = void>(options: ModalOptions<Result>) {
|
||
|
|
const { scope = globalOverlayScope, ...modalOptions } = options
|
||
|
|
return getModalController(scope).open<Result | undefined>(
|
||
|
|
modalOptions as ModalRequestOptions,
|
||
|
|
undefined
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface ModalHostProps {
|
||
|
|
scope?: OverlayScope
|
||
|
|
}
|
||
|
|
|
||
|
|
export function ModalHost({ scope }: ModalHostProps) {
|
||
|
|
const controller = useOverlayController(scope, getModalController)
|
||
|
|
const activeOptions = controller.getActive()?.options
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Dialog<OverlayRequest<ModalRequestOptions>>
|
||
|
|
disablePointerDismissal={activeOptions?.dismissible === false}
|
||
|
|
handle={controller.handle}
|
||
|
|
modal={activeOptions?.modal}
|
||
|
|
onOpenChange={(open, details) => {
|
||
|
|
if (!open) controller.handleCloseRequest(details)
|
||
|
|
}}
|
||
|
|
onOpenChangeComplete={(open) => {
|
||
|
|
if (!open) controller.handleCloseComplete()
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{({ payload }) =>
|
||
|
|
payload ? (
|
||
|
|
<ModalContent
|
||
|
|
key={payload.id}
|
||
|
|
options={payload.options}
|
||
|
|
onDismiss={controller.dismiss}
|
||
|
|
onResolve={(value) => controller.resolve(value)}
|
||
|
|
/>
|
||
|
|
) : null
|
||
|
|
}
|
||
|
|
</Dialog>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
function ModalContent({
|
||
|
|
onDismiss,
|
||
|
|
onResolve,
|
||
|
|
options,
|
||
|
|
}: {
|
||
|
|
onDismiss: () => void
|
||
|
|
onResolve: (value: unknown) => void
|
||
|
|
options: ModalRequestOptions
|
||
|
|
}) {
|
||
|
|
const t = useTranslate()
|
||
|
|
const dismissible = options.dismissible !== false
|
||
|
|
|
||
|
|
return (
|
||
|
|
<DialogContent className={options.className} showCloseButton={false}>
|
||
|
|
<DialogHeader>
|
||
|
|
<DialogTitle>{options.title}</DialogTitle>
|
||
|
|
{options.description != null && (
|
||
|
|
<DialogDescription>{options.description}</DialogDescription>
|
||
|
|
)}
|
||
|
|
</DialogHeader>
|
||
|
|
{renderOverlayContent(options.content, {
|
||
|
|
close: onResolve,
|
||
|
|
dismiss: onDismiss,
|
||
|
|
})}
|
||
|
|
{dismissible && (
|
||
|
|
<DialogClose
|
||
|
|
aria-label={t(overlayMessages.close)}
|
||
|
|
onClick={onDismiss}
|
||
|
|
render={
|
||
|
|
<Button
|
||
|
|
type="button"
|
||
|
|
variant="ghost"
|
||
|
|
size="icon-sm"
|
||
|
|
className="absolute end-4 top-4"
|
||
|
|
/>
|
||
|
|
}
|
||
|
|
>
|
||
|
|
<XIcon aria-hidden="true" />
|
||
|
|
</DialogClose>
|
||
|
|
)}
|
||
|
|
</DialogContent>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
export type { ModalOptions } from "./types"
|