Spaces:
Build error
Build error
'use client'; | |
import * as DialogPrimitive from '@radix-ui/react-dialog'; | |
import { VisuallyHidden } from '@radix-ui/react-visually-hidden'; | |
import { cva, VariantProps } from 'class-variance-authority'; | |
import * as React from 'react'; | |
import { CloseButton } from '../button'; | |
import { cn, ComponentAnatomy, defineStyleAnatomy } from '../core/styling'; | |
import { useState } from 'react'; | |
function useDrawerBodyBehavior(id: string, open: boolean | undefined) { | |
const [openDrawers, setOpenDrawers] = useState<string[]>([]); | |
React.useEffect(() => { | |
const body = document.querySelector('body'); | |
if (!body) return; | |
if (open) { | |
setOpenDrawers((prev) => [...prev, id]); | |
} else { | |
setOpenDrawers((prev) => { | |
let next = prev.filter((i) => i !== id); | |
return next; | |
}); | |
} | |
return () => { | |
setOpenDrawers((prev) => prev.filter((i) => i !== id)); | |
}; | |
}, [open]); | |
} | |
/* ------------------------------------------------------------------------------------------------- | |
* Anatomy | |
* -----------------------------------------------------------------------------------------------*/ | |
export const DrawerAnatomy = defineStyleAnatomy({ | |
overlay: cva([ | |
'UI-Drawer__overlay', | |
'fixed inset-0 z-[50] bg-black/80', | |
'data-[state=open]:animate-in data-[state=closed]:animate-out', | |
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0', | |
// "transition-opacity duration-300", | |
]), | |
content: cva( | |
[ | |
'UI-Drawer__content', | |
'fixed z-50 w-full gap-4 bg-[--background] p-6 shadow-lg overflow-y-auto', | |
'transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-500 data-[state=open]:duration-500', | |
'focus:outline-none focus-visible:outline-none', | |
process.env.NEXT_PUBLIC_PLATFORM === 'desktop' && 'select-none', | |
], | |
{ | |
variants: { | |
side: { | |
mangaReader: | |
'w-full inset-x-0 top-0 border data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom', | |
top: 'w-full lg:w-[calc(100%_-_20px)] inset-x-0 top-0 border data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top', | |
bottom: | |
'w-full lg:w-[calc(100%_-_20px)] inset-x-0 bottom-0 border data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom', | |
left: 'inset-y-0 left-0 h-full lg:h-[calc(100%_-_20px)] border data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left', | |
right: | |
'inset-y-0 right-0 h-full lg:h-[calc(100%_-_20px)] border data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right', | |
}, | |
size: { sm: null, md: null, lg: null, xl: null, full: null }, | |
}, | |
defaultVariants: { | |
side: 'right', | |
size: 'md', | |
}, | |
compoundVariants: [ | |
{ size: 'sm', side: 'left', className: 'sm:max-w-sm' }, | |
{ size: 'sm', side: 'right', className: 'sm:max-w-sm' }, | |
{ size: 'md', side: 'left', className: 'sm:max-w-md' }, | |
{ size: 'md', side: 'right', className: 'sm:max-w-md' }, | |
{ size: 'lg', side: 'left', className: 'sm:max-w-2xl' }, | |
{ size: 'lg', side: 'right', className: 'sm:max-w-2xl' }, | |
{ size: 'xl', side: 'left', className: 'sm:max-w-5xl' }, | |
{ size: 'xl', side: 'right', className: 'sm:max-w-5xl' }, | |
/**/ | |
{ size: 'full', side: 'top', className: 'h-dvh' }, | |
{ size: 'full', side: 'bottom', className: 'h-dvh' }, | |
], | |
} | |
), | |
close: cva(['UI-Drawer__close', 'absolute right-4 top-4']), | |
header: cva([ | |
'UI-Drawer__header', | |
'flex flex-col space-y-1.5 text-center sm:text-left', | |
]), | |
footer: cva([ | |
'UI-Drawer__footer', | |
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', | |
]), | |
title: cva([ | |
'UI-Drawer__title', | |
'text-xl font-semibold leading-none tracking-tight', | |
]), | |
description: cva(['UI-Drawer__description', 'text-sm text-[--muted]']), | |
}); | |
/* ------------------------------------------------------------------------------------------------- | |
* Drawer | |
* -----------------------------------------------------------------------------------------------*/ | |
export type DrawerProps = Omit< | |
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Root>, | |
'modal' | |
> & | |
Pick< | |
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>, | |
| 'onOpenAutoFocus' | |
| 'onCloseAutoFocus' | |
| 'onEscapeKeyDown' | |
| 'onPointerDownCapture' | |
| 'onInteractOutside' | |
> & | |
VariantProps<typeof DrawerAnatomy.content> & | |
ComponentAnatomy<typeof DrawerAnatomy> & { | |
/** | |
* Interaction with outside elements will be enabled and other elements will be visible to screen readers. | |
*/ | |
allowOutsideInteraction?: boolean; | |
/** | |
* The button that opens the modal | |
*/ | |
trigger?: React.ReactElement; | |
/** | |
* Title of the modal | |
*/ | |
title?: React.ReactNode; | |
/** | |
* An optional accessible description to be announced when the dialog is opened. | |
*/ | |
description?: React.ReactNode; | |
/** | |
* Footer of the modal | |
*/ | |
footer?: React.ReactNode; | |
/** | |
* Optional replacement for the default close button | |
*/ | |
closeButton?: React.ReactElement; | |
/** | |
* Whether to hide the close button | |
*/ | |
hideCloseButton?: boolean; | |
/** | |
* Portal container | |
*/ | |
portalContainer?: HTMLElement; | |
mangaReader?: boolean; | |
}; | |
export function Drawer(props: DrawerProps) { | |
const { | |
allowOutsideInteraction = false, | |
trigger, | |
title, | |
footer, | |
description, | |
children, | |
closeButton, | |
overlayClass, | |
contentClass, | |
closeClass, | |
headerClass, | |
footerClass, | |
titleClass, | |
descriptionClass, | |
hideCloseButton, | |
side = 'right', | |
size, | |
open, | |
// Content | |
onOpenAutoFocus, | |
onCloseAutoFocus, | |
onEscapeKeyDown, | |
onPointerDownCapture, | |
onInteractOutside, | |
portalContainer, | |
mangaReader, | |
...rest | |
} = props; | |
const id = React.useId(); | |
useDrawerBodyBehavior(id, open); | |
return ( | |
<DialogPrimitive.Root | |
modal={!allowOutsideInteraction} | |
open={open} | |
{...rest} | |
> | |
{trigger && ( | |
<DialogPrimitive.Trigger asChild>{trigger}</DialogPrimitive.Trigger> | |
)} | |
<DialogPrimitive.Portal container={portalContainer}> | |
<DialogPrimitive.Overlay | |
className={cn(DrawerAnatomy.overlay(), overlayClass)} | |
/> | |
<DialogPrimitive.Content | |
className={cn( | |
DrawerAnatomy.content({ | |
size, | |
side: mangaReader ? 'mangaReader' : side, | |
}), | |
// process.env.NEXT_PUBLIC_PLATFORM === "desktop" && "pt-12", | |
!mangaReader && 'lg:m-[10px] rounded-[--radius]', | |
contentClass | |
)} | |
style={{ | |
marginTop: | |
process.env.NEXT_PUBLIC_PLATFORM === 'desktop' && !mangaReader | |
? '30px' | |
: undefined, | |
height: | |
process.env.NEXT_PUBLIC_PLATFORM === 'desktop' && | |
!mangaReader && | |
(side === 'left' || side === 'right') | |
? 'calc(100dvh - 50px)' | |
: undefined, | |
}} | |
onOpenAutoFocus={onOpenAutoFocus} | |
onCloseAutoFocus={onCloseAutoFocus} | |
onEscapeKeyDown={onEscapeKeyDown} | |
onPointerDownCapture={onPointerDownCapture} | |
onInteractOutside={onInteractOutside} | |
tabIndex={-1} | |
> | |
{!title && !description ? ( | |
<VisuallyHidden> | |
<DialogPrimitive.Title>Drawer</DialogPrimitive.Title> | |
</VisuallyHidden> | |
) : ( | |
<div className={cn(DrawerAnatomy.header(), headerClass)}> | |
<DialogPrimitive.Title | |
className={cn( | |
DrawerAnatomy.title(), | |
process.env.NEXT_PUBLIC_PLATFORM === 'desktop' && 'relative', | |
titleClass | |
)} | |
> | |
{title} | |
</DialogPrimitive.Title> | |
{description && ( | |
<DialogPrimitive.Description | |
className={cn(DrawerAnatomy.description(), descriptionClass)} | |
> | |
{description} | |
</DialogPrimitive.Description> | |
)} | |
</div> | |
)} | |
{children} | |
{footer && ( | |
<div className={cn(DrawerAnatomy.footer(), footerClass)}> | |
{footer} | |
</div> | |
)} | |
{!hideCloseButton && ( | |
<DialogPrimitive.Close | |
className={cn( | |
DrawerAnatomy.close(), | |
// process.env.NEXT_PUBLIC_PLATFORM === "desktop" && "!top-10 !right-4", | |
closeClass | |
)} | |
asChild | |
> | |
{closeButton ? closeButton : <CloseButton />} | |
</DialogPrimitive.Close> | |
)} | |
</DialogPrimitive.Content> | |
</DialogPrimitive.Portal> | |
</DialogPrimitive.Root> | |
); | |
} | |
Drawer.displayName = 'Drawer'; | |