168 lines
5.2 KiB
TypeScript
168 lines
5.2 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect } from 'react'
|
|
|
|
type Width =
|
|
| 'sm:max-w-sm'
|
|
| 'sm:max-w-md'
|
|
| 'sm:max-w-lg'
|
|
| 'sm:max-w-xl'
|
|
| 'sm:max-w-2xl'
|
|
| string
|
|
|
|
type ModalProps = {
|
|
id: string
|
|
title: string
|
|
children?: React.ReactNode
|
|
show: boolean
|
|
onClose?: () => void
|
|
onSave?: () => void
|
|
hideCloseButton?: boolean
|
|
closeButtonColor?: 'blue' | 'red' | 'green' | 'teal'
|
|
closeButtonTitle?: string
|
|
disableSave?: boolean
|
|
maxWidth?: Width
|
|
}
|
|
|
|
export default function Modal({
|
|
id,
|
|
title,
|
|
children,
|
|
show,
|
|
onClose,
|
|
onSave,
|
|
hideCloseButton = false,
|
|
closeButtonColor = 'blue',
|
|
closeButtonTitle = 'Speichern',
|
|
disableSave,
|
|
maxWidth = 'sm:max-w-lg',
|
|
}: ModalProps) {
|
|
/* ───────── Overlay-Lifecycle ───────── */
|
|
useEffect(() => {
|
|
const modalEl = document.getElementById(id)
|
|
const hs = (window as any).HSOverlay
|
|
if (!modalEl || !hs) return
|
|
|
|
/* ► Collection kann undefined oder ein Objekt sein.
|
|
► Wir sichern uns ab und behandeln nur echte Arrays. */
|
|
const getCollection = (): any[] =>
|
|
Array.isArray(hs.collection) ? hs.collection : []
|
|
|
|
const destroyIfExists = () => {
|
|
const inst = getCollection().find((i) => i.element === modalEl)
|
|
inst?.destroy?.()
|
|
if (inst) {
|
|
hs.collection = getCollection().filter((i) => i !== inst)
|
|
}
|
|
}
|
|
|
|
const handleClose = () => onClose?.()
|
|
modalEl.addEventListener('hsOverlay:close', handleClose)
|
|
|
|
try {
|
|
if (show) {
|
|
destroyIfExists()
|
|
hs.autoInit?.()
|
|
hs.open?.(modalEl)
|
|
} else {
|
|
hs.close?.(modalEl)
|
|
destroyIfExists()
|
|
}
|
|
} catch (err) {
|
|
// eslint-disable-next-line no-console
|
|
console.error('[Modal] HSOverlay Fehler:', err)
|
|
}
|
|
|
|
return () => {
|
|
modalEl.removeEventListener('hsOverlay:close', handleClose)
|
|
destroyIfExists()
|
|
}
|
|
}, [show, id, onClose])
|
|
|
|
/* ───────── Render ───────── */
|
|
return (
|
|
<div
|
|
id={id}
|
|
data-hs-overlay="true"
|
|
role="dialog"
|
|
tabIndex={-1}
|
|
aria-labelledby={`${id}-label`}
|
|
onMouseDown={(e) => {
|
|
if (e.target === e.currentTarget) onClose?.()
|
|
}}
|
|
className="hs-overlay hidden fixed inset-0 z-80 overflow-y-auto overflow-x-hidden"
|
|
>
|
|
{/* Backdrop */}
|
|
<div className="fixed inset-0 -z-10 bg-black/50 dark:bg-neutral-900/70" />
|
|
|
|
{/* Dialog */}
|
|
<div className={`hs-overlay-open:mt-7 hs-overlay-open:opacity-100 hs-overlay-open:duration-500
|
|
mt-0 opacity-0 transition-all ease-out
|
|
${maxWidth} sm:w-full m-3 sm:mx-auto
|
|
min-h-[calc(100%-56px)] flex items-center`}
|
|
>
|
|
<div className="w-full flex flex-col bg-white dark:bg-neutral-800 border border-gray-200 dark:border-neutral-700 shadow-2xs dark:shadow-neutral-700/70 rounded-xl">
|
|
{/* Header */}
|
|
<div className="flex justify-between items-center py-3 px-4 border-b border-gray-200 dark:border-neutral-700">
|
|
<h3 id={`${id}-label`} className="font-bold text-gray-800 dark:text-white">
|
|
{title}
|
|
</h3>
|
|
|
|
{!hideCloseButton && (
|
|
<button
|
|
type="button"
|
|
aria-label="Close"
|
|
data-hs-overlay={`#${id}`}
|
|
onClick={onClose}
|
|
className="size-8 inline-flex justify-center items-center rounded-full bg-gray-100 hover:bg-gray-200 dark:bg-neutral-700 dark:hover:bg-neutral-600 text-gray-800 dark:text-neutral-400"
|
|
>
|
|
<svg
|
|
className="size-4"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
strokeWidth={2}
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
>
|
|
<path d="M18 6 6 18" />
|
|
<path d="m6 6 12 12" />
|
|
</svg>
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{/* Body */}
|
|
<div className="p-4 overflow-y-auto max-h-[70vh] sm:max-h-[60vh]">{children}</div>
|
|
|
|
{/* Footer */}
|
|
<div className="flex justify-end items-center gap-x-2 py-3 px-4 border-t border-gray-200 dark:border-neutral-700">
|
|
{!hideCloseButton && (
|
|
<button
|
|
type="button"
|
|
data-hs-overlay={`#${id}`}
|
|
onClick={onClose}
|
|
className="py-2 px-3 text-sm font-medium rounded-lg border border-gray-200 dark:border-neutral-700 bg-white dark:bg-neutral-800 text-gray-800 dark:text-white shadow-2xs hover:bg-gray-50 dark:hover:bg-neutral-700"
|
|
>
|
|
Schließen
|
|
</button>
|
|
)}
|
|
|
|
{onSave && (
|
|
<button
|
|
type="button"
|
|
onClick={onSave}
|
|
disabled={disableSave}
|
|
className={`py-2 px-3 text-sm font-medium rounded-lg border border-transparent bg-${closeButtonColor}-600 hover:bg-${closeButtonColor}-700 focus:bg-${closeButtonColor}-700 text-white`}
|
|
>
|
|
{closeButtonTitle}
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|