mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-23 03:25:07 +03:00
Redesign the UI
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
import { AuthContainer } from "./Auth";
|
||||
import { useState, useEffect, useRef, useLayoutEffect, useCallback, type RefObject } from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { motion, AnimatePresence } from "motion/react";
|
||||
import useDownloadAppScreen from "@/core/hooks/useDownloadAppScreen";
|
||||
import { LoginForm } from "./LoginForm";
|
||||
import { RegisterForm } from "./RegisterForm";
|
||||
import type { Variants, Transition } from "motion/react";
|
||||
import styles from "./auth.module.scss";
|
||||
|
||||
const slideVariants: Variants = {
|
||||
enter: (direction: number) => ({
|
||||
x: direction > 0 ? 300 : -300,
|
||||
opacity: 0
|
||||
}),
|
||||
center: {
|
||||
x: 0,
|
||||
opacity: 1
|
||||
},
|
||||
exit: (direction: number) => ({
|
||||
x: direction > 0 ? -300 : 300,
|
||||
opacity: 0
|
||||
})
|
||||
};
|
||||
|
||||
const slideTransition: Transition = {
|
||||
x: {
|
||||
type: "spring",
|
||||
stiffness: 300,
|
||||
damping: 30
|
||||
},
|
||||
opacity: { duration: 0.2 }
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default function AuthPage() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const { navigate: navigateDownloadApp } = useDownloadAppScreen();
|
||||
if (navigateDownloadApp) return navigateDownloadApp;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [direction, setDirection] = useState(0);
|
||||
const prevMode = useRef(searchParams.get("mode") || "login");
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const loginFormRef = useRef<HTMLDivElement>(null);
|
||||
const registerFormRef = useRef<HTMLDivElement>(null);
|
||||
const [containerHeight, setContainerHeight] = useState<number | "auto">("auto");
|
||||
const currentMode = searchParams.get("mode") || "login";
|
||||
const enteringElementRef = useRef<"login" | "register" | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (prevMode.current !== currentMode) {
|
||||
setDirection(currentMode === "register" ? 1 : -1);
|
||||
prevMode.current = currentMode;
|
||||
enteringElementRef.current = currentMode as "login" | "register";
|
||||
}
|
||||
}, [currentMode]);
|
||||
|
||||
const measureActiveHeight = useCallback(() => {
|
||||
const activeComponent = currentMode === "login" ? loginFormRef.current : registerFormRef.current;
|
||||
if (activeComponent) {
|
||||
const height = activeComponent.scrollHeight;
|
||||
if (height > 0) {
|
||||
setContainerHeight(height);
|
||||
}
|
||||
}
|
||||
}, [currentMode, loginFormRef, registerFormRef]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
// Always measure, but prioritize the entering element during transitions
|
||||
// Use double requestAnimationFrame to ensure DOM is fully updated and layout is complete
|
||||
let rafId2: number | null = null;
|
||||
const rafId1 = requestAnimationFrame(() => {
|
||||
rafId2 = requestAnimationFrame(() => {
|
||||
measureActiveHeight();
|
||||
});
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(rafId1);
|
||||
if (rafId2 !== null) {
|
||||
cancelAnimationFrame(rafId2);
|
||||
}
|
||||
};
|
||||
}, [currentMode]);
|
||||
|
||||
function switchMode(newMode: "login" | "register") {
|
||||
navigate(`/auth?mode=${newMode}`, { replace: true });
|
||||
}
|
||||
|
||||
function handleAnimationComplete(
|
||||
currentMode: "login" | "register",
|
||||
mode: "login" | "register",
|
||||
enteringElementRef: RefObject<"login" | "register" | null>,
|
||||
formRef: React.RefObject<HTMLDivElement | null>,
|
||||
setContainerHeight: (height: number) => void
|
||||
) {
|
||||
return () => {
|
||||
if (currentMode === mode && enteringElementRef.current === mode) {
|
||||
enteringElementRef.current = null;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
if (formRef.current && currentMode === mode) {
|
||||
const height = formRef.current.scrollHeight;
|
||||
if (height > 0) {
|
||||
setContainerHeight(height);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthContainer>
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={{
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: containerHeight === "auto" ? "auto" : `${containerHeight}px`,
|
||||
transition: "height 0.3s ease"
|
||||
}}
|
||||
>
|
||||
<AnimatePresence mode="sync" custom={direction}>
|
||||
{currentMode === "login" ? (
|
||||
<motion.div
|
||||
key="login"
|
||||
ref={loginFormRef}
|
||||
custom={direction}
|
||||
variants={slideVariants}
|
||||
initial="enter"
|
||||
animate="center"
|
||||
exit="exit"
|
||||
transition={slideTransition}
|
||||
onAnimationComplete={handleAnimationComplete("login", "login", enteringElementRef, loginFormRef, setContainerHeight)}
|
||||
className={styles.formWrapper}
|
||||
>
|
||||
<LoginForm onSwitchMode={() => switchMode("register")} />
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
key="register"
|
||||
ref={registerFormRef}
|
||||
custom={direction}
|
||||
variants={slideVariants}
|
||||
initial="enter"
|
||||
animate="center"
|
||||
exit="exit"
|
||||
transition={slideTransition}
|
||||
onAnimationComplete={handleAnimationComplete("register", "register", enteringElementRef, registerFormRef, setContainerHeight)}
|
||||
className={styles.formWrapper}
|
||||
>
|
||||
<RegisterForm onSwitchMode={() => switchMode("login")} />
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</AuthContainer>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user