Merge pull request #19 from every-app/split-modal-dismissal
fix: exit closes modal + accessible labels
This commit is contained in:
parent
6f50850b5a
commit
7f892ca433
@ -1,15 +1,35 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { useEffect, type ReactNode } from "react";
|
||||
|
||||
export function Modal({
|
||||
maxWidth = "max-w-sm",
|
||||
children,
|
||||
onClose,
|
||||
labelledBy,
|
||||
}: {
|
||||
maxWidth?: string;
|
||||
children: ReactNode;
|
||||
onClose?: () => void;
|
||||
labelledBy?: string;
|
||||
}) {
|
||||
useEffect(() => {
|
||||
if (!onClose) return;
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key !== "Escape" || event.defaultPrevented) return;
|
||||
event.preventDefault();
|
||||
onClose();
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [onClose]);
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby={labelledBy}
|
||||
className={`card bg-base-100 border border-base-300 w-full ${maxWidth} shadow-xl`}
|
||||
>
|
||||
<div className="card-body gap-4">{children}</div>
|
||||
|
||||
@ -28,13 +28,17 @@ export function ExportToSheetsModal() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal maxWidth="max-w-md">
|
||||
<Modal
|
||||
maxWidth="max-w-md"
|
||||
onClose={closeExportToSheetsModal}
|
||||
labelledBy="export-to-sheets-title"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="inline-flex size-8 items-center justify-center rounded-full bg-success/15 text-success">
|
||||
<Check className="size-4" />
|
||||
</span>
|
||||
<h3 className="text-base font-semibold">
|
||||
<h3 id="export-to-sheets-title" className="text-base font-semibold">
|
||||
Copied {rowCount} row{rowCount === 1 ? "" : "s"} to your clipboard
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
@ -34,9 +34,13 @@ export function CheckConfirmModal({
|
||||
Math.ceil(totalChecks / KEYWORDS_PER_BATCH) * SECONDS_PER_BATCH;
|
||||
|
||||
return (
|
||||
<Modal maxWidth="max-w-md">
|
||||
<Modal
|
||||
maxWidth="max-w-md"
|
||||
onClose={onCancel}
|
||||
labelledBy="rank-check-confirm-title"
|
||||
>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">
|
||||
<h3 id="rank-check-confirm-title" className="text-lg font-semibold">
|
||||
Check {keywordCount} keyword
|
||||
{keywordCount !== 1 ? "s" : ""}
|
||||
</h3>
|
||||
|
||||
@ -255,7 +255,9 @@ export function KeywordSuggestionStep({
|
||||
|
||||
const sectionHeader = (title: string) => (
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold">{title}</h2>
|
||||
<h2 id="keyword-suggestions-title" className="text-lg font-semibold">
|
||||
{title}
|
||||
</h2>
|
||||
<button className="btn btn-ghost btn-sm btn-square" onClick={onClose}>
|
||||
<X className="size-4" />
|
||||
</button>
|
||||
|
||||
@ -127,8 +127,14 @@ export function RankTrackingConfigModal({
|
||||
const isPending = createMutation.isPending || updateMutation.isPending;
|
||||
|
||||
if (step === "keywords" && createdConfigId) {
|
||||
const closeKeywordStep = () => onSaved(createdConfigId);
|
||||
|
||||
return (
|
||||
<Modal maxWidth="max-w-3xl">
|
||||
<Modal
|
||||
maxWidth="max-w-3xl"
|
||||
onClose={closeKeywordStep}
|
||||
labelledBy="keyword-suggestions-title"
|
||||
>
|
||||
<KeywordSuggestionStep
|
||||
configId={createdConfigId}
|
||||
projectId={projectId}
|
||||
@ -136,16 +142,20 @@ export function RankTrackingConfigModal({
|
||||
locationCode={locationCode}
|
||||
languageCode={getLanguageCode(locationCode)}
|
||||
onDone={(id) => onSaved(id)}
|
||||
onClose={() => onSaved(createdConfigId ?? undefined)}
|
||||
onClose={closeKeywordStep}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal maxWidth="max-w-lg">
|
||||
<Modal
|
||||
maxWidth="max-w-lg"
|
||||
onClose={onClose}
|
||||
labelledBy="rank-config-modal-title"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold">
|
||||
<h2 id="rank-config-modal-title" className="text-lg font-semibold">
|
||||
{isEdit ? "Edit Domain Config" : "Add Domain"}
|
||||
</h2>
|
||||
<button className="btn btn-ghost btn-sm btn-square" onClick={onClose}>
|
||||
|
||||
@ -97,8 +97,11 @@ export function RankTrackingDomainList({
|
||||
</div>
|
||||
|
||||
{archiveTarget && (
|
||||
<Modal>
|
||||
<h3 className="text-lg font-semibold">
|
||||
<Modal
|
||||
onClose={() => setArchiveTarget(null)}
|
||||
labelledBy="archive-domain-title"
|
||||
>
|
||||
<h3 id="archive-domain-title" className="text-lg font-semibold">
|
||||
Archive {archiveTarget.domain}?
|
||||
</h3>
|
||||
<p className="text-sm text-base-content/70">
|
||||
|
||||
@ -129,8 +129,13 @@ export function RankTrackingTable({
|
||||
|
||||
{/* Confirm modal */}
|
||||
{showConfirm && (
|
||||
<Modal>
|
||||
<h3 className="text-lg font-semibold">Remove keywords?</h3>
|
||||
<Modal
|
||||
onClose={() => setShowConfirm(false)}
|
||||
labelledBy="remove-keywords-title"
|
||||
>
|
||||
<h3 id="remove-keywords-title" className="text-lg font-semibold">
|
||||
Remove keywords?
|
||||
</h3>
|
||||
<p className="text-sm text-base-content/70">
|
||||
This will stop tracking {selectedCount} keyword
|
||||
{selectedCount !== 1 ? "s" : ""}. Historical ranking data is
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user