import { useT } from '@app/i18n' import { IconX as X } from '@tabler/icons-react' import { type ReactNode, useEffect, useId, useRef } from 'react' /** * Feature-local dialog chrome (overlay + panel + Escape/outside-click close), * matching `shared/ui/form-modal`'s look. It exists because the issue forms need * multi-column pickers that the config-driven `FormModal` cannot lay out. */ export function Dialog({ title, description, open, onClose, width = 'max-w-[560px]', panelClassName = '', header, children, footer, }: { title: string description?: string open: boolean onClose: () => void width?: string /** Extra classes for the panel (feature-specific chrome). */ panelClassName?: string /** Replaces the default title block; the close button and the a11y name stay. */ header?: ReactNode children: ReactNode footer?: ReactNode }) { const { t } = useT() const panel = useRef(null) const closeRef = useRef(onClose) const titleId = useId() const descriptionId = useId() useEffect(() => { closeRef.current = onClose }, [onClose]) useEffect(() => { if (!open) return const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null const previousOverflow = document.body.style.overflow document.body.style.overflow = 'hidden' const frame = requestAnimationFrame(() => { const firstField = panel.current?.querySelector( 'input:not([disabled]), textarea:not([disabled]), select:not([disabled])', ) ;(firstField ?? panel.current)?.focus() }) const onKey = (event: KeyboardEvent) => { if (event.defaultPrevented) return if (event.key === 'Escape') { event.preventDefault() closeRef.current() } if (event.key !== 'Tab') return const controls = Array.from( panel.current?.querySelectorAll( 'button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), a[href], [tabindex="0"]', ) ?? [], ).filter((element) => element.getClientRects().length > 0) const first = controls[0] const last = controls.at(-1) if (!first) { event.preventDefault() panel.current?.focus() return } if (event.shiftKey && (document.activeElement === first || document.activeElement === panel.current)) { event.preventDefault() last?.focus() } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault() first.focus() } } window.addEventListener('keydown', onKey) return () => { cancelAnimationFrame(frame) document.body.style.overflow = previousOverflow window.removeEventListener('keydown', onKey) if (previousFocus?.isConnected) previousFocus.focus() } }, [open]) if (!open) return null return (
event.stopPropagation()} className={`issue-dialog flex max-h-[86vh] w-full flex-col rounded-xl border-2 border-shell-hairline bg-shell-surface p-5 shadow-xl ${width} ${panelClassName}`} >
{header ? ( <>

{title}

{header} ) : (

{title}

{description ? (

{description}

) : null}
)}
{children}
{footer ? (
{footer}
) : null}
) } export const fieldLabelClass = 'issue-field-label text-[12px] font-medium text-shell-ink' export const fieldInputClass = 'issue-field-input h-10 w-full rounded-md border-2 border-shell-hairline bg-shell-field px-3 text-[13.5px] text-shell-ink outline-none transition-colors focus:border-shell-ink' export const smallInputClass = 'issue-field-input issue-field-small h-7 w-full rounded-md border-2 border-shell-hairline bg-shell-field px-2 text-[12.5px] outline-none focus-visible:border-shell-ink'