You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

205 lines
6.6 KiB

"use client";
import type { HTMLAttributes } from "react";
import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
import { useI18n } from "@/translations/provider";
const EXIT_ANIMATION_MS = 220;
export type OutcomeSelectionSheetProps = Omit<
HTMLAttributes<HTMLDivElement>,
"title" | "onSubmit"
> & {
closeOnOutside?: boolean;
onClose?: () => void;
onSubmit?: (status: "success" | "failure") => void;
};
export function OutcomeSelectionSheet({
closeOnOutside = true,
onClose,
onSubmit,
className,
...props
}: OutcomeSelectionSheetProps) {
const { dictionary: t } = useI18n();
const groupId = useId();
const [isVisible, setIsVisible] = useState(true);
const [isClosing, setIsClosing] = useState(false);
const [selectedStatus, setSelectedStatus] = useState<"success" | "failure">(
"success",
);
const isMountedRef = useRef(true);
useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
const closeSheet = useCallback(() => {
if (isClosing) {
return;
}
setIsClosing(true);
window.setTimeout(() => {
if (isMountedRef.current) {
setIsVisible(false);
}
onClose?.();
}, EXIT_ANIMATION_MS);
}, [isClosing, onClose]);
useEffect(() => {
if (!isVisible) {
return;
}
const previousBodyOverflow = document.body.style.overflow;
const previousHtmlOverflow = document.documentElement.style.overflow;
document.body.style.overflow = "hidden";
document.documentElement.style.overflow = "hidden";
return () => {
document.body.style.overflow = previousBodyOverflow;
document.documentElement.style.overflow = previousHtmlOverflow;
};
}, [isVisible]);
if (!isVisible) {
return null;
}
const options = [
{ value: "success" as const, label: t["We reached an agreement"] },
{ value: "failure" as const, label: t["We did not reach an agreement"] },
];
return (
<div
className={[
"fixed inset-0 z-50 flex items-end justify-center transition-all duration-[220ms] animate-in fade-in",
isClosing
? "bg-[#171717]/0 opacity-0"
: "bg-[#171717]/55 opacity-100",
]
.filter(Boolean)
.join(" ")}
role="dialog"
aria-modal="true"
aria-label={t["What was the outcome of your contact?"]}
tabIndex={-1}
onClick={(event) => {
if (closeOnOutside && event.target === event.currentTarget) {
closeSheet();
}
}}
onKeyDown={(event) => {
if (
closeOnOutside &&
event.target === event.currentTarget &&
(event.key === "Escape" || event.key === "Enter" || event.key === " ")
) {
event.preventDefault();
closeSheet();
}
}}
>
<section
{...props}
className={[
"w-full sm:max-w-[375px] rounded-t-[34px] bg-[#F9F8F8] p-3.5 text-center shadow-[0_20px_60px_rgba(15,23,42,0.08)] transition-transform duration-[220ms] ease-out will-change-transform animate-in slide-in-from-bottom",
isClosing ? "translate-y-full" : "translate-y-0",
className,
]
.filter(Boolean)
.join(" ")}
>
<div className="mx-auto flex flex-col items-center">
<h2 className="text-[18px] leading-[1.2] font-bold tracking-[-0.03em] text-[#171717]">
{t["What was the outcome of your contact?"]}
</h2>
<div
className="mt-5 w-full"
role="radiogroup"
aria-labelledby={groupId}
>
<span id={groupId} className="sr-only">
{t["What was the outcome of your contact?"]}
</span>
<div className="flex flex-col gap-[14px]">
{options.map((option) => {
const checked = selectedStatus === option.value;
return (
<label
key={option.value}
className="flex cursor-pointer items-center gap-3 rounded-[14px] bg-[#ECECEC] px-[14px] py-[18px] text-left"
>
<input
checked={checked}
className="sr-only"
name="outcome-status"
type="radio"
value={option.value}
onChange={() => {
setSelectedStatus(option.value);
}}
/>
<span
aria-hidden="true"
className={[
"flex h-6 w-6 shrink-0 items-center justify-center rounded-full border",
checked ? "border-[#F0445B]" : "border-[#9E9E9E]",
]
.filter(Boolean)
.join(" ")}
>
{checked ? (
<span className="h-3 w-3 rounded-full bg-[#F0445B]" />
) : null}
</span>
<span className="text-[16px] leading-none font-semibold text-[#262626]">
{option.label}
</span>
</label>
);
})}
</div>
</div>
<div className="mt-8 grid w-full grid-cols-[33fr_67fr] gap-3">
<button
type="button"
className="appearance-none border-0 bg-transparent p-0 text-left min-w-0"
onClick={closeSheet}
>
<div className="inline-flex w-full items-center justify-center rounded-[14px] border border-[#9A9A9A] bg-[#F7F7F7] px-2 h-[52px] text-[16px] font-bold text-[#8B8B8B] shadow-[inset_0_1px_0_rgba(255,255,255,0.8)] transition-opacity active:opacity-90 min-w-0">
<span className="truncate">{t["Cancel"]}</span>
</div>
</button>
<button
type="button"
className="appearance-none border-0 bg-transparent p-0 text-left min-w-0"
onClick={() => {
onSubmit?.(selectedStatus);
closeSheet();
}}
>
<div className="inline-flex w-full items-center justify-center rounded-[14px] bg-[#F0445B] px-4 h-[52px] text-[16px] font-semibold text-white shadow-[0_10px_18px_rgba(240,68,91,0.28)] transition-opacity active:opacity-90 min-w-0">
<span className="truncate">{t["Submit"]}</span>
</div>
</button>
</div>
</div>
</section>
</div>
);
}
export default OutcomeSelectionSheet;