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.
 
 
 
 
 
 

209 lines
6.7 KiB

"use client";
import type { HTMLAttributes } from "react";
import { useCallback, useEffect, useId, useRef, useState } from "react";
import { useHardwareBackHandler } from "@/hooks/use-hardware-back-handler";
import { localeDirections } from "@/translations/config";
import { useI18n } from "@/translations/provider";
const EXIT_ANIMATION_MS = 200;
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, locale } = useI18n();
const direction = localeDirections[locale] ?? "ltr";
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]);
useHardwareBackHandler(closeSheet, isVisible && !isClosing);
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",
isClosing ? "flutter-scrim-exit" : "flutter-scrim",
]
.filter(Boolean)
.join(" ")}
role="dialog"
aria-modal="true"
aria-label={t["What was the outcome of your contact?"]}
dir={direction}
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 max-w-[834px] sm:max-w-[540px] rounded-t-[22px] bg-white p-3.5 text-start shadow-[0_20px_60px_rgba(15,23,42,0.08)]",
isClosing ? "flutter-sheet-surface-exit" : "flutter-sheet-surface",
className,
]
.filter(Boolean)
.join(" ")}
>
<div className="mx-auto flex flex-col">
<h2 className="w-full text-start 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-start"
>
<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-snug font-semibold text-[#262626] flex-1 text-start">
{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 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 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;