init project
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '@/components/actions/Button';
|
||||
import { useScrollLock } from '@/components/foundation/client-hooks';
|
||||
import { classNames } from '@/lib/components/classNames';
|
||||
import { useEffect, useId, useRef, type ReactNode, type RefObject } from 'react';
|
||||
import styles from './Dialog.module.css';
|
||||
|
||||
interface DialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
title: string;
|
||||
description?: string;
|
||||
children: ReactNode;
|
||||
closeLabel: string;
|
||||
initialFocusRef?: RefObject<HTMLElement | null>;
|
||||
returnFocusRef?: RefObject<HTMLElement | null>;
|
||||
variant?: 'modal' | 'drawer';
|
||||
dismissible?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Dialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
closeLabel,
|
||||
initialFocusRef,
|
||||
returnFocusRef,
|
||||
variant = 'modal',
|
||||
dismissible = true,
|
||||
className,
|
||||
}: DialogProps) {
|
||||
const dialogRef = useRef<HTMLDialogElement>(null);
|
||||
const closeRef = useRef<HTMLButtonElement>(null);
|
||||
const titleId = useId();
|
||||
const descriptionId = useId();
|
||||
useScrollLock(open);
|
||||
|
||||
useEffect(() => {
|
||||
const dialog = dialogRef.current;
|
||||
if (!dialog) return;
|
||||
if (open && !dialog.open) {
|
||||
dialog.showModal();
|
||||
window.requestAnimationFrame(() => (initialFocusRef?.current ?? closeRef.current)?.focus());
|
||||
} else if (!open && dialog.open) {
|
||||
dialog.close();
|
||||
}
|
||||
}, [initialFocusRef, open]);
|
||||
|
||||
const close = () => {
|
||||
dialogRef.current?.close();
|
||||
onOpenChange(false);
|
||||
window.requestAnimationFrame(() => returnFocusRef?.current?.focus());
|
||||
};
|
||||
|
||||
return (
|
||||
<dialog
|
||||
ref={dialogRef}
|
||||
className={classNames(styles.dialog, styles[variant], className)}
|
||||
aria-labelledby={titleId}
|
||||
aria-describedby={description ? descriptionId : undefined}
|
||||
onCancel={(event) => {
|
||||
if (!dismissible) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
close();
|
||||
}}
|
||||
onClose={() => onOpenChange(false)}
|
||||
onClick={(event) => {
|
||||
if (dismissible && event.target === dialogRef.current) close();
|
||||
}}
|
||||
>
|
||||
<div className={styles.panel}>
|
||||
<header className={styles.header}>
|
||||
<div>
|
||||
<h2 id={titleId}>{title}</h2>
|
||||
{description ? <p id={descriptionId}>{description}</p> : null}
|
||||
</div>
|
||||
{dismissible ? (
|
||||
<Button
|
||||
ref={closeRef}
|
||||
intent="ghost"
|
||||
size="small"
|
||||
icon="close"
|
||||
aria-label={closeLabel}
|
||||
onClick={close}
|
||||
/>
|
||||
) : null}
|
||||
</header>
|
||||
<div className={styles.content}>{children}</div>
|
||||
</div>
|
||||
</dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user