mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-20 08:15:26 +02:00
171 lines
3.7 KiB
TypeScript
171 lines
3.7 KiB
TypeScript
import React, {ReactElement, useEffect, useRef, useState} from "react"
|
|
|
|
import {Paper} from "@mui/material"
|
|
|
|
import {whenElementHasBounds} from "~/utils"
|
|
|
|
export interface MultiStepFormProps {
|
|
steps: ReactElement[]
|
|
index: number
|
|
|
|
duration?: number
|
|
easing?: string
|
|
}
|
|
|
|
function MultiStepForm({
|
|
steps,
|
|
index,
|
|
duration = 900,
|
|
easing = "ease-in-out",
|
|
}: MultiStepFormProps): ReactElement {
|
|
const $currentElement = useRef<HTMLDivElement>(null)
|
|
const $timeout = useRef<any>()
|
|
|
|
const [currentIndex, setCurrentIndex] = useState<number>(index)
|
|
|
|
const [currentSize, setCurrentSize] = useState<any>(null)
|
|
const [nextSize, setNextSize] = useState<any>(null)
|
|
|
|
const isTransitioning = currentIndex < index
|
|
|
|
useEffect(() => {
|
|
if (index > currentIndex) {
|
|
$timeout.current = setTimeout(() => {
|
|
setCurrentSize(null)
|
|
setNextSize(null)
|
|
setCurrentIndex(index)
|
|
}, duration)
|
|
} else if (index < currentIndex) {
|
|
// "Going-back" animation is not supported
|
|
setCurrentIndex(index)
|
|
setCurrentSize(null)
|
|
setNextSize(null)
|
|
}
|
|
|
|
return $timeout.current?.cancel!
|
|
}, [index, currentIndex])
|
|
|
|
const hasSize = Boolean(
|
|
(currentSize?.width || nextSize?.width) &&
|
|
(currentSize?.height || nextSize?.height),
|
|
)
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
position: "relative",
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
width:
|
|
Math.max(currentSize?.width || 0, nextSize?.width || 0) ||
|
|
"100%",
|
|
height:
|
|
Math.max(currentSize?.height || 0, nextSize?.height || 0) ||
|
|
"100%",
|
|
overflow: hasSize ? "hidden" : "visible",
|
|
}}
|
|
>
|
|
<Paper
|
|
key="paper"
|
|
style={{
|
|
...currentSize,
|
|
position: "absolute",
|
|
transition: easing,
|
|
transitionDuration: isTransitioning
|
|
? `${duration}ms`
|
|
: "0ms",
|
|
transformOrigin: isTransitioning
|
|
? `${nextSize?.x - currentSize.x}px ${
|
|
nextSize?.y - currentSize.y
|
|
}px`
|
|
: "",
|
|
// Scale the difference between currentSize and nextSize
|
|
transform: isTransitioning
|
|
? `
|
|
scaleX(${nextSize.width / currentSize.width})
|
|
scaleY(${nextSize.height / currentSize.height})
|
|
translateX(${nextSize.x - currentSize.x}px)
|
|
translateY(${nextSize.y - currentSize.y}px)
|
|
`
|
|
: "scale(1)",
|
|
}}
|
|
></Paper>
|
|
<div
|
|
// @ts-ignore
|
|
ref={ref => {
|
|
if (ref === null) {
|
|
return
|
|
}
|
|
|
|
// @ts-ignore
|
|
$currentElement.current = ref
|
|
|
|
const {
|
|
width = 0,
|
|
height = 0,
|
|
x,
|
|
y,
|
|
} = ref.getBoundingClientRect()
|
|
|
|
if (
|
|
width !== 0 &&
|
|
height !== 0 &&
|
|
width !== currentSize?.width &&
|
|
height !== currentSize?.height
|
|
) {
|
|
setCurrentSize({
|
|
width: width,
|
|
height: height,
|
|
x: x,
|
|
y: y,
|
|
})
|
|
}
|
|
}}
|
|
style={{
|
|
position: "absolute",
|
|
width: "max-content",
|
|
height: "max-content",
|
|
transition: easing,
|
|
transitionDuration: isTransitioning ? `${duration}ms` : "",
|
|
transform: isTransitioning ? "translateX(-100%)" : "",
|
|
}}
|
|
>
|
|
{steps[currentIndex]}
|
|
</div>
|
|
<div
|
|
// @ts-ignore
|
|
ref={whenElementHasBounds(rect => {
|
|
if (nextSize === null) {
|
|
setNextSize({
|
|
width: rect.width,
|
|
height: rect.height,
|
|
x: rect.x,
|
|
y: rect.y,
|
|
})
|
|
}
|
|
})}
|
|
style={{
|
|
position: "absolute",
|
|
width: "max-content",
|
|
height: "max-content",
|
|
transition: easing,
|
|
transitionDuration: isTransitioning ? `${duration}ms` : "",
|
|
transform: isTransitioning
|
|
? ""
|
|
: nextSize?.x
|
|
? "translateX(100%)"
|
|
: "",
|
|
}}
|
|
>
|
|
{currentIndex === steps.length - 1
|
|
? null
|
|
: steps[currentIndex + 1]}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default MultiStepForm
|