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.
224 lines
6.7 KiB
224 lines
6.7 KiB
"use client";
|
|
|
|
import type { HTMLAttributes } from "react";
|
|
import { useEffect, useId, useState } from "react";
|
|
import { useI18n } from "@/translations/provider";
|
|
|
|
const EXIT_ANIMATION_MS = 220;
|
|
|
|
export type CallResultSheetProps = Omit<
|
|
HTMLAttributes<HTMLDivElement>,
|
|
"title" | "onSubmit"
|
|
> & {
|
|
closeOnOutside?: boolean;
|
|
onClose?: () => void;
|
|
onSubmit?: (value: string) => void;
|
|
onOtherReasonsClick?: () => void;
|
|
};
|
|
|
|
export function CallResultSheet({
|
|
closeOnOutside = true,
|
|
onClose,
|
|
onSubmit,
|
|
onOtherReasonsClick,
|
|
className,
|
|
...props
|
|
}: CallResultSheetProps) {
|
|
const { dictionary: t } = useI18n();
|
|
const callResultOptions = t.sheets.callOptions;
|
|
const groupId = useId();
|
|
const [isVisible, setIsVisible] = useState(true);
|
|
const [isEntering, setIsEntering] = useState(true);
|
|
const [isClosing, setIsClosing] = useState(false);
|
|
const [selectedReason, setSelectedReason] = useState<string>(
|
|
callResultOptions[0],
|
|
);
|
|
|
|
const closeSheet = () => {
|
|
if (isClosing) {
|
|
return;
|
|
}
|
|
|
|
setIsClosing(true);
|
|
};
|
|
|
|
useEffect(() => {
|
|
const frameId = window.requestAnimationFrame(() => {
|
|
setIsEntering(false);
|
|
});
|
|
|
|
return () => {
|
|
window.cancelAnimationFrame(frameId);
|
|
};
|
|
}, []);
|
|
|
|
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]);
|
|
|
|
useEffect(() => {
|
|
if (!isClosing) {
|
|
return;
|
|
}
|
|
|
|
const timeoutId = window.setTimeout(() => {
|
|
setIsVisible(false);
|
|
onClose?.();
|
|
}, EXIT_ANIMATION_MS);
|
|
|
|
return () => {
|
|
window.clearTimeout(timeoutId);
|
|
};
|
|
}, [isClosing, onClose]);
|
|
|
|
if (!isVisible) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className={[
|
|
"fixed inset-0 z-50 flex items-end justify-center transition-all duration-[220ms]",
|
|
isClosing || isEntering
|
|
? "bg-[#171717]/0 opacity-0"
|
|
: "bg-[#171717]/55 opacity-100",
|
|
]
|
|
.filter(Boolean)
|
|
.join(" ")}
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-label={t.sheets.callResult}
|
|
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",
|
|
isClosing || isEntering ? "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.sheets.callResult}
|
|
</h2>
|
|
|
|
<div
|
|
className="mt-5 w-full"
|
|
role="radiogroup"
|
|
aria-labelledby={groupId}
|
|
>
|
|
<span id={groupId} className="sr-only">
|
|
{t.sheets.selectCallResult}
|
|
</span>
|
|
|
|
<div className="flex flex-col gap-[14px]">
|
|
{callResultOptions.map((option) => {
|
|
const checked = selectedReason === option;
|
|
|
|
return (
|
|
<label
|
|
key={option}
|
|
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="call-result"
|
|
type="radio"
|
|
value={option}
|
|
onChange={() => {
|
|
setSelectedReason(option);
|
|
|
|
if (
|
|
option ===
|
|
callResultOptions[callResultOptions.length - 1]
|
|
) {
|
|
onOtherReasonsClick?.();
|
|
closeSheet();
|
|
}
|
|
}}
|
|
/>
|
|
<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}
|
|
</span>
|
|
</label>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-8 grid w-full grid-cols-[1fr_2fr] gap-3">
|
|
<button
|
|
type="button"
|
|
className="appearance-none border-0 bg-transparent p-0 text-left"
|
|
onClick={closeSheet}
|
|
>
|
|
<div className="inline-flex w-full items-center justify-center rounded-[14px] border border-[#9A9A9A] bg-[#F7F7F7] px-4 py-[18px] text-[16px] font-bold text-[#8B8B8B] shadow-[inset_0_1px_0_rgba(255,255,255,0.8)] transition-opacity active:opacity-90">
|
|
{t.common.cancel}
|
|
</div>
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
className="appearance-none border-0 bg-transparent p-0 text-left"
|
|
onClick={() => {
|
|
onSubmit?.(selectedReason);
|
|
closeSheet();
|
|
}}
|
|
>
|
|
<div className="inline-flex w-full items-center justify-center rounded-[14px] bg-[#F0445B] px-4 py-[18px] text-[16px] font-semibold text-white shadow-[0_10px_18px_rgba(240,68,91,0.28)] transition-opacity active:opacity-90">
|
|
{t.common.submit}
|
|
</div>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default CallResultSheet;
|