Use four-step carousel per program

This commit is contained in:
2026-06-24 09:31:52 +09:00
parent 0c494c9bee
commit 8f5c9051b4

View File

@@ -523,17 +523,23 @@ function FlowRow({
return 'disabled'; return 'disabled';
}; };
const isRowClickable = Boolean(onLabelClick) && !isEditing; const isRowClickable = Boolean(onLabelClick) && !isEditing;
const isWrappedStepLayout = steps.length > 5; const maxVisibleSteps = 4;
const isSingleStepLayout = steps.length === 1; const [stepWindowStart, setStepWindowStart] = useState(0);
const stepRows = isWrappedStepLayout const maxWindowStart = Math.max(0, steps.length - maxVisibleSteps);
? steps.reduce((rows, step, index) => { const visibleStepItems = steps
const rowIndex = Math.floor(index / 4); .map((step, index) => ({ step, index }))
rows[rowIndex] = [...(rows[rowIndex] ?? []), { step, index }]; .slice(stepWindowStart, stepWindowStart + maxVisibleSteps);
return rows; const placeholderCount = Math.max(0, maxVisibleSteps - visibleStepItems.length);
}, []) const canMovePrev = stepWindowStart > 0;
: []; const canMoveNext = stepWindowStart < maxWindowStart;
const wrappedCardColumns = ['xl:col-start-1', 'xl:col-start-3', 'xl:col-start-5', 'xl:col-start-7'];
const wrappedArrowColumns = ['xl:col-start-2', 'xl:col-start-4', 'xl:col-start-6']; useEffect(() => {
setStepWindowStart((current) => Math.min(current, maxWindowStart));
}, [maxWindowStart]);
const moveStepWindow = (direction) => {
setStepWindowStart((current) => Math.min(maxWindowStart, Math.max(0, current + direction)));
};
const renderStepCard = (step, index, className) => ( const renderStepCard = (step, index, className) => (
<div className={`relative z-10 flex flex-col gap-2 ${className}`}> <div className={`relative z-10 flex flex-col gap-2 ${className}`}>
@@ -687,46 +693,59 @@ function FlowRow({
)} )}
</div> </div>
</div> </div>
{isWrappedStepLayout ? ( <div className="relative overflow-hidden rounded-[24px] bg-white/25 px-9 py-1 ring-1 ring-white/70">
<div className="space-y-5"> {steps.length > maxVisibleSteps && (
{stepRows.map((row, rowIndex) => ( <>
<div <button
key={rowIndex} type="button"
className="relative flex flex-col gap-3 xl:grid xl:grid-cols-[minmax(0,1fr)_28px_minmax(0,1fr)_28px_minmax(0,1fr)_28px_minmax(0,1fr)] xl:gap-0 xl:items-stretch" disabled={!canMovePrev}
onClick={(event) => {
event.stopPropagation();
moveStepWindow(-1);
}}
className="absolute left-2 top-1/2 z-20 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full bg-white/95 text-slate-500 shadow-sm ring-1 ring-slate-200 hover:bg-slate-50 disabled:opacity-25"
aria-label="이전 스텝 보기"
> >
{row.map(({ step, index }, itemIndex) => (
<React.Fragment key={step.id}> </button>
{renderStepCard( <button
step, type="button"
index, disabled={!canMoveNext}
`min-w-0 ${wrappedCardColumns[itemIndex]}` onClick={(event) => {
event.stopPropagation();
moveStepWindow(1);
}}
className="absolute right-2 top-1/2 z-20 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full bg-white/95 text-slate-500 shadow-sm ring-1 ring-slate-200 hover:bg-slate-50 disabled:opacity-25"
aria-label="다음 스텝 보기"
>
</button>
</>
)} )}
{itemIndex < row.length - 1 && ( <div className="relative grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-4 xl:gap-5">
<div className={`flex items-center justify-center ${wrappedArrowColumns[itemIndex]}`}> {visibleStepItems.map(({ step, index }, itemIndex) => (
<ArrowRight className={`hidden h-5 w-5 xl:block ${accent.arrowText}`} /> <div key={step.id} className="relative">
<ArrowDown className={`h-5 w-5 xl:hidden ${accent.arrowText}`} /> {renderStepCard(step, index, 'min-w-0')}
</div> {itemIndex < visibleStepItems.length - 1 && (
<ArrowDown className={`mx-auto mt-2 h-5 w-5 xl:hidden ${accent.arrowText}`} />
)} )}
</React.Fragment>
))}
</div> </div>
))} ))}
</div> {Array.from({ length: placeholderCount }).map((_, index) => (
) : ( <div
<div className="relative flex flex-col gap-3 xl:flex-row xl:items-stretch"> key={`placeholder-${index}`}
{steps.map((step, index) => ( className="hidden min-h-[220px] rounded-2xl border border-dashed border-transparent xl:block"
<React.Fragment key={step.id}> />
{renderStepCard(step, index, isSingleStepLayout ? 'w-full max-w-[320px]' : 'min-w-0 flex-1')} ))}
{index < steps.length - 1 && ( {visibleStepItems.slice(0, -1).map((item, index) => (
<div className="flex items-center justify-center xl:w-7"> <ArrowRight
<ArrowRight className={`hidden h-5 w-5 xl:block ${accent.arrowText}`} /> key={`arrow-${item.step.id}`}
<ArrowDown className={`h-5 w-5 xl:hidden ${accent.arrowText}`} /> className={`pointer-events-none absolute top-1/2 z-20 hidden h-5 w-5 -translate-x-1/2 -translate-y-1/2 xl:block ${accent.arrowText}`}
</div> style={{ left: `${25 * (index + 1)}%` }}
)} />
</React.Fragment>
))} ))}
</div> </div>
)} </div>
</section> </section>
); );
} }