mirror of
https://github.com/stack-auth/stack.git
synced 2026-07-06 21:02:52 +08:00
<!-- Make sure you've read the CONTRIBUTING.md guidelines: https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md --> <!-- ELLIPSIS_HIDDEN --> ---- > [!IMPORTANT] > Fix dimension calculation in `Stepper` component by using `offsetWidth` and `offsetHeight`. > > - **UI Fix**: > - In `Stepper` component, replace `getBoundingClientRect()` with `offsetWidth` and `offsetHeight` for dimension calculation in `useEffect`. > - Affects how dimensions are set in `setDimensions()` function. > > <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=stack-auth%2Fstack-auth&utm_source=github&utm_medium=referral)<sup> for4c5673a350. You can [customize](https://app.ellipsis.dev/stack-auth/settings/summaries) this summary. It will automatically update as commits are pushed.</sup> <!-- ELLIPSIS_HIDDEN --> <!-- RECURSEML_SUMMARY:START --> ## Review by RecurseML _🔍 Review performed on [9318e2b..4c5673a](9318e2b6ce...4c5673a350)_ ✨ No bugs found, your code is sparkling clean <details> <summary>✅ Files analyzed, no issues (1)</summary> • `apps/dashboard/src/components/stepper.tsx` </details> [](https://discord.gg/n3SsVDAW6U) <!-- RECURSEML_SUMMARY:END --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - Bug Fixes - Improved Stepper sizing to accurately reflect element layout, reducing misalignment, clipping, and overflow in various layouts and themes. - Increased stability during window/container resizes, minimizing visual jitter and reflow glitches. - Performance - More efficient measurement path for Stepper dimensions, reducing unnecessary calculations while preserving responsive updates. - Style - Subtle visual consistency improvements from more precise width/height handling, leading to cleaner alignment and spacing across steps. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Konsti Wohlwend <n2d4xc@gmail.com>
147 lines
3.9 KiB
TypeScript
147 lines
3.9 KiB
TypeScript
"use client";
|
|
|
|
import { cn } from "@/lib/utils";
|
|
import React, { createContext, useContext, useEffect, useRef, useState } from "react";
|
|
|
|
type StepperContextType = {
|
|
currentStep: number,
|
|
totalSteps: number,
|
|
goToStep: (step: number) => void,
|
|
nextStep: () => void,
|
|
previousStep: () => void,
|
|
direction: 'forward' | 'backward',
|
|
};
|
|
|
|
const StepperContext = createContext<StepperContextType | null>(null);
|
|
|
|
export function useStepperContext() {
|
|
const context = useContext(StepperContext);
|
|
if (!context) {
|
|
throw new Error("useStepperContext must be used within a Stepper");
|
|
}
|
|
return context;
|
|
}
|
|
|
|
type StepperProps = {
|
|
children: React.ReactNode,
|
|
currentStep: number,
|
|
onStepChange: (step: number) => void,
|
|
className?: string,
|
|
};
|
|
|
|
export function Stepper({ children, currentStep, onStepChange, className }: StepperProps) {
|
|
const [direction, setDirection] = useState<'forward' | 'backward'>('forward');
|
|
const [dimensions, setDimensions] = useState<{ width: number, height: number }>({ width: 0, height: 0 });
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
const previousStepRef = useRef(currentStep);
|
|
|
|
const childrenArray = React.Children.toArray(children);
|
|
const totalSteps = childrenArray.length;
|
|
|
|
useEffect(() => {
|
|
if (currentStep > previousStepRef.current) {
|
|
setDirection('forward');
|
|
} else if (currentStep < previousStepRef.current) {
|
|
setDirection('backward');
|
|
}
|
|
previousStepRef.current = currentStep;
|
|
}, [currentStep]);
|
|
|
|
useEffect(() => {
|
|
const updateDimensions = () => {
|
|
if (contentRef.current) {
|
|
setDimensions({
|
|
width: contentRef.current.offsetWidth,
|
|
height: contentRef.current.offsetHeight,
|
|
});
|
|
}
|
|
};
|
|
|
|
updateDimensions();
|
|
|
|
// Use ResizeObserver for smooth size transitions
|
|
const resizeObserver = new ResizeObserver(updateDimensions);
|
|
if (contentRef.current) {
|
|
resizeObserver.observe(contentRef.current);
|
|
}
|
|
|
|
return () => {
|
|
resizeObserver.disconnect();
|
|
};
|
|
}, [currentStep]);
|
|
|
|
const goToStep = (step: number) => {
|
|
if (step >= 0 && step < totalSteps) {
|
|
onStepChange(step);
|
|
}
|
|
};
|
|
|
|
const nextStep = () => {
|
|
goToStep(currentStep + 1);
|
|
};
|
|
|
|
const goToPreviousStep = () => {
|
|
goToStep(currentStep - 1);
|
|
};
|
|
|
|
const contextValue: StepperContextType = {
|
|
currentStep,
|
|
totalSteps,
|
|
goToStep,
|
|
nextStep,
|
|
previousStep: goToPreviousStep,
|
|
direction,
|
|
};
|
|
|
|
return (
|
|
<StepperContext.Provider value={contextValue}>
|
|
<div
|
|
ref={containerRef}
|
|
className={cn("relative overflow-hidden transition-all duration-300 ease-in-out", className)}
|
|
style={{
|
|
width: dimensions.width || 'auto',
|
|
height: dimensions.height || 'auto',
|
|
}}
|
|
>
|
|
<div className="relative">
|
|
{childrenArray.map((child, index) => (
|
|
<div
|
|
key={index}
|
|
ref={index === currentStep ? contentRef : undefined}
|
|
className={cn(
|
|
"transition-all duration-300 ease-in-out",
|
|
index === currentStep ? "" : "absolute inset-0 pointer-events-none"
|
|
)}
|
|
style={{
|
|
opacity: index === currentStep ? 1 : 0,
|
|
transform: index === currentStep
|
|
? 'translateX(0)'
|
|
: index < currentStep
|
|
? 'translateX(-20px)'
|
|
: 'translateX(20px)',
|
|
}}
|
|
>
|
|
{index === currentStep && child}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</StepperContext.Provider>
|
|
);
|
|
}
|
|
|
|
type StepperPageProps = {
|
|
children: React.ReactNode,
|
|
className?: string,
|
|
};
|
|
|
|
export function StepperPage({ children, className }: StepperPageProps) {
|
|
return (
|
|
<div className={cn("w-full", className)}>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|