139 行
4.4 KiB
TypeScript
139 行
4.4 KiB
TypeScript
'use client'
|
|
|
|
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
import { X } from 'lucide-react'
|
|
import * as React from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
|
|
import LocalisedText from '@/components/LocalisedText'
|
|
import { translateUIMessage } from '@/lib/i18n'
|
|
import { useClientMotion } from '@/lib/useClientMotion'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
const Dialog = DialogPrimitive.Root
|
|
|
|
const DialogTrigger = DialogPrimitive.Trigger
|
|
|
|
const DialogPortal = DialogPrimitive.Portal
|
|
|
|
const DialogClose = DialogPrimitive.Close
|
|
|
|
const DialogOverlay = React.forwardRef<
|
|
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
>(
|
|
({ className, ...props }, ref) => (
|
|
<DialogPrimitive.Overlay
|
|
ref={ref}
|
|
className={cn (
|
|
'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',
|
|
className)}
|
|
{...props}/>))
|
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
|
|
|
const DialogContent = React.forwardRef<
|
|
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
|
& { closeLabel?: React.ReactNode }
|
|
>(
|
|
({ className, children, closeLabel, style, ...props }, ref) => {
|
|
const { i18n } = useTranslation ('shared')
|
|
const motion = useClientMotion ()
|
|
const animationDuration = `${motion.duration ('popover')}ms`
|
|
const close = translateUIMessage ('dialogue.close', 'shared', i18n.language)
|
|
return (
|
|
<DialogPortal>
|
|
<DialogOverlay style={{ animationDuration }}/>
|
|
<DialogPrimitive.Content
|
|
ref={ref}
|
|
style={{ animationDuration, ...style }}
|
|
className={cn (
|
|
'fixed left-[50%] top-[50%] z-50 grid w-[calc(100%-2rem)] max-w-lg',
|
|
'translate-x-[-50%] translate-y-[-50%]',
|
|
'gap-5 rounded-2xl border border-border',
|
|
'bg-background p-6 text-foreground shadow-2xl',
|
|
'duration-200',
|
|
'data-[state=open]:animate-in data-[state=closed]:animate-out',
|
|
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
|
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
|
className)}
|
|
{...props}>
|
|
{children}
|
|
|
|
<DialogPrimitive.Close
|
|
className={cn (
|
|
'absolute left-4 top-4 rounded-full p-1',
|
|
'text-slate-500 transition-colors',
|
|
'hover:bg-slate-200 hover:text-slate-900',
|
|
'dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-slate-50',
|
|
'focus:outline-none focus:ring-2 focus:ring-slate-400')}>
|
|
<X className="h-4 w-4"/>
|
|
<span className="sr-only">
|
|
{closeLabel ?? (
|
|
<LocalisedText language={close.language}>{close.text}</LocalisedText>)}
|
|
</span>
|
|
</DialogPrimitive.Close>
|
|
</DialogPrimitive.Content>
|
|
</DialogPortal>)
|
|
})
|
|
DialogContent.displayName = DialogPrimitive.Content.displayName
|
|
|
|
const DialogHeader = (
|
|
{ className, ...props }: React.HTMLAttributes<HTMLDivElement>,
|
|
) => (
|
|
<div
|
|
className={cn (
|
|
'flex flex-col space-y-1.5 text-center md:text-left',
|
|
className)}
|
|
{...props}/>)
|
|
DialogHeader.displayName = 'DialogHeader'
|
|
|
|
const DialogFooter = (
|
|
{ className, ...props }: React.HTMLAttributes<HTMLDivElement>,
|
|
) => (
|
|
<div
|
|
className={cn (
|
|
'flex flex-col-reverse md:flex-row md:justify-end md:space-x-2',
|
|
className)}
|
|
{...props}/>)
|
|
DialogFooter.displayName = 'DialogFooter'
|
|
|
|
const DialogTitle = React.forwardRef<
|
|
React.ElementRef<typeof DialogPrimitive.Title>,
|
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
>(
|
|
({ className, ...props }, ref) => (
|
|
<DialogPrimitive.Title
|
|
ref={ref}
|
|
className={cn (
|
|
'text-lg font-semibold leading-none tracking-tight',
|
|
className)}
|
|
{...props}/>))
|
|
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
|
|
|
const DialogDescription = React.forwardRef<
|
|
React.ElementRef<typeof DialogPrimitive.Description>,
|
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
>(
|
|
({ className, ...props }, ref) => (
|
|
<DialogPrimitive.Description
|
|
ref={ref}
|
|
className={cn ('text-sm text-muted-foreground', className)}
|
|
{...props}/>))
|
|
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
|
|
|
export {
|
|
Dialog,
|
|
DialogPortal,
|
|
DialogOverlay,
|
|
DialogClose,
|
|
DialogTrigger,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogFooter,
|
|
DialogTitle,
|
|
DialogDescription,
|
|
}
|