127 lines
3.7 KiB
TypeScript
127 lines
3.7 KiB
TypeScript
import { useTranslate } from "@workspace/i18n"
|
|
import { Button } from "@workspace/ui/components/button"
|
|
import {
|
|
createDrawerHandle,
|
|
Drawer,
|
|
DrawerClose,
|
|
DrawerContent,
|
|
DrawerDescription,
|
|
DrawerHeader,
|
|
DrawerTitle,
|
|
} from "@workspace/ui/components/drawer"
|
|
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 { DrawerOptions } from "./types"
|
|
|
|
type DrawerRequestOptions = Omit<DrawerOptions<unknown>, "scope">
|
|
const drawerChannel = Symbol("overlay.drawer")
|
|
|
|
function getDrawerController(scope: OverlayScope) {
|
|
return scope.getChannel(drawerChannel, () => {
|
|
const handle = createDrawerHandle<OverlayRequest<DrawerRequestOptions>>()
|
|
return new OverlayController<DrawerRequestOptions, typeof handle>({
|
|
handle,
|
|
isDismissible: (options) => options.dismissible !== false,
|
|
scope,
|
|
})
|
|
})
|
|
}
|
|
|
|
export function openDrawer<Result = void>(options: DrawerOptions<Result>) {
|
|
const { scope = globalOverlayScope, ...drawerOptions } = options
|
|
return getDrawerController(scope).open<Result | undefined>(
|
|
drawerOptions as DrawerRequestOptions,
|
|
undefined
|
|
)
|
|
}
|
|
|
|
export interface DrawerHostProps {
|
|
scope?: OverlayScope
|
|
}
|
|
|
|
export function DrawerHost({ scope }: DrawerHostProps) {
|
|
const controller = useOverlayController(scope, getDrawerController)
|
|
const activeOptions = controller.getActive()?.options
|
|
|
|
return (
|
|
<Drawer<OverlayRequest<DrawerRequestOptions>>
|
|
disablePointerDismissal={activeOptions?.dismissible === false}
|
|
handle={controller.handle}
|
|
modal={activeOptions?.modal}
|
|
showSwipeHandle={activeOptions?.showSwipeHandle}
|
|
snapPoints={activeOptions?.snapPoints}
|
|
swipeDirection={activeOptions?.swipeDirection}
|
|
onOpenChange={(open, details) => {
|
|
if (!open) controller.handleCloseRequest(details)
|
|
}}
|
|
onOpenChangeComplete={(open) => {
|
|
if (!open) controller.handleCloseComplete()
|
|
}}
|
|
>
|
|
{({ payload }) =>
|
|
payload ? (
|
|
<DrawerContentView
|
|
key={payload.id}
|
|
options={payload.options}
|
|
onDismiss={controller.dismiss}
|
|
onResolve={(value) => controller.resolve(value)}
|
|
/>
|
|
) : null
|
|
}
|
|
</Drawer>
|
|
)
|
|
}
|
|
|
|
function DrawerContentView({
|
|
onDismiss,
|
|
onResolve,
|
|
options,
|
|
}: {
|
|
onDismiss: () => void
|
|
onResolve: (value: unknown) => void
|
|
options: DrawerRequestOptions
|
|
}) {
|
|
const t = useTranslate()
|
|
const dismissible = options.dismissible !== false
|
|
|
|
return (
|
|
<DrawerContent className={options.className}>
|
|
<DrawerHeader>
|
|
<DrawerTitle>{options.title}</DrawerTitle>
|
|
{options.description != null && (
|
|
<DrawerDescription>{options.description}</DrawerDescription>
|
|
)}
|
|
</DrawerHeader>
|
|
<div className="min-h-0 flex-1 overflow-auto p-4">
|
|
{renderOverlayContent(options.content, {
|
|
close: onResolve,
|
|
dismiss: onDismiss,
|
|
})}
|
|
</div>
|
|
{dismissible && (
|
|
<DrawerClose
|
|
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" />
|
|
</DrawerClose>
|
|
)}
|
|
</DrawerContent>
|
|
)
|
|
}
|
|
|
|
export type { DrawerOptions } from "./types"
|