168 lines
4.8 KiB
TypeScript
168 lines
4.8 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import React, { useMemo } from "react";
|
||
|
|
import { ComponentData } from "@/types/screen";
|
||
|
|
import { FlowVisibilityConfig } from "@/types/control-management";
|
||
|
|
import { useCurrentFlowStep } from "@/stores/flowStepStore";
|
||
|
|
|
||
|
|
interface FlowButtonGroupProps {
|
||
|
|
/**
|
||
|
|
* 그룹에 속한 버튼 컴포넌트들
|
||
|
|
*/
|
||
|
|
buttons: ComponentData[];
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 그룹 설정 (첫 번째 버튼의 설정 사용)
|
||
|
|
*/
|
||
|
|
groupConfig: FlowVisibilityConfig;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 버튼 렌더링 함수
|
||
|
|
*/
|
||
|
|
renderButton: (button: ComponentData, isVisible: boolean) => React.ReactNode;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 디자인 모드 여부
|
||
|
|
*/
|
||
|
|
isDesignMode?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* FlowButtonGroup 컴포넌트
|
||
|
|
*
|
||
|
|
* 플로우 단계별로 버튼을 표시/숨기고, auto-compact 모드일 때
|
||
|
|
* Flexbox로 자동 정렬하는 버튼 그룹 컨테이너입니다.
|
||
|
|
*
|
||
|
|
* **특징:**
|
||
|
|
* - 같은 groupId를 가진 버튼들을 하나의 Flexbox 컨테이너로 묶음
|
||
|
|
* - 현재 플로우 단계에 따라 버튼을 동적으로 표시/숨김
|
||
|
|
* - 숨겨진 버튼은 렌더링하지 않아 빈 공간이 자동으로 제거됨
|
||
|
|
* - 그룹 내 정렬, 간격, 방향을 세밀하게 제어 가능
|
||
|
|
*/
|
||
|
|
export const FlowButtonGroup: React.FC<FlowButtonGroupProps> = ({
|
||
|
|
buttons,
|
||
|
|
groupConfig,
|
||
|
|
renderButton,
|
||
|
|
isDesignMode = false,
|
||
|
|
}) => {
|
||
|
|
// 현재 플로우 단계
|
||
|
|
const currentStep = useCurrentFlowStep(groupConfig.targetFlowComponentId);
|
||
|
|
|
||
|
|
// 각 버튼의 표시 여부 계산
|
||
|
|
const buttonVisibility = useMemo(() => {
|
||
|
|
return buttons.map((button) => {
|
||
|
|
const config = (button as any).webTypeConfig?.flowVisibilityConfig as FlowVisibilityConfig | undefined;
|
||
|
|
|
||
|
|
// 플로우 제어 비활성화 시 항상 표시
|
||
|
|
if (!config?.enabled) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 플로우 단계가 선택되지 않은 경우
|
||
|
|
if (currentStep === null) {
|
||
|
|
// 화이트리스트 모드일 때는 단계 미선택 시 숨김
|
||
|
|
if (config.mode === "whitelist") {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
const { mode, visibleSteps = [], hiddenSteps = [] } = config;
|
||
|
|
|
||
|
|
if (mode === "whitelist") {
|
||
|
|
return visibleSteps.includes(currentStep);
|
||
|
|
} else if (mode === "blacklist") {
|
||
|
|
return !hiddenSteps.includes(currentStep);
|
||
|
|
} else if (mode === "all") {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
});
|
||
|
|
}, [buttons, currentStep]);
|
||
|
|
|
||
|
|
// 표시할 버튼 필터링
|
||
|
|
const visibleButtons = useMemo(() => {
|
||
|
|
return buttons.filter((_, index) => buttonVisibility[index]);
|
||
|
|
}, [buttons, buttonVisibility]);
|
||
|
|
|
||
|
|
// 그룹 스타일 계산
|
||
|
|
const groupStyle: React.CSSProperties = useMemo(() => {
|
||
|
|
const direction = groupConfig.groupDirection || "horizontal";
|
||
|
|
const gap = groupConfig.groupGap ?? 8;
|
||
|
|
const align = groupConfig.groupAlign || "start";
|
||
|
|
|
||
|
|
let justifyContent: string;
|
||
|
|
switch (align) {
|
||
|
|
case "start":
|
||
|
|
justifyContent = "flex-start";
|
||
|
|
break;
|
||
|
|
case "center":
|
||
|
|
justifyContent = "center";
|
||
|
|
break;
|
||
|
|
case "end":
|
||
|
|
justifyContent = "flex-end";
|
||
|
|
break;
|
||
|
|
case "space-between":
|
||
|
|
justifyContent = "space-between";
|
||
|
|
break;
|
||
|
|
case "space-around":
|
||
|
|
justifyContent = "space-around";
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
justifyContent = "flex-start";
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
display: "flex",
|
||
|
|
flexDirection: direction === "vertical" ? "column" : "row",
|
||
|
|
gap: `${gap}px`,
|
||
|
|
justifyContent,
|
||
|
|
alignItems: "center",
|
||
|
|
flexWrap: "wrap", // 넘칠 경우 줄바꿈
|
||
|
|
width: "100%", // 🆕 전체 너비를 차지하도록 설정 (끝점/중앙 정렬을 위해 필수)
|
||
|
|
};
|
||
|
|
}, [groupConfig]);
|
||
|
|
|
||
|
|
// 디자인 모드에서는 모든 버튼 표시 (반투명 처리)
|
||
|
|
if (isDesignMode) {
|
||
|
|
return (
|
||
|
|
<div style={groupStyle} className="flow-button-group">
|
||
|
|
{buttons.map((button, index) => (
|
||
|
|
<div
|
||
|
|
key={button.id}
|
||
|
|
style={{
|
||
|
|
opacity: buttonVisibility[index] ? 1 : 0.3,
|
||
|
|
position: "relative",
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{renderButton(button, buttonVisibility[index])}
|
||
|
|
{!buttonVisibility[index] && (
|
||
|
|
<div
|
||
|
|
className="pointer-events-none absolute inset-0 flex items-center justify-center bg-gray-900/10"
|
||
|
|
style={{
|
||
|
|
border: "1px dashed #94a3b8",
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<span className="text-[10px] text-gray-500">숨김</span>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 실제 뷰 모드: 보이는 버튼만 렌더링 (auto-compact 동작)
|
||
|
|
return (
|
||
|
|
<div style={groupStyle} className="flow-button-group">
|
||
|
|
{visibleButtons.map((button) => (
|
||
|
|
<div key={button.id} style={{ position: "relative" }}>
|
||
|
|
{renderButton(button, true)}
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|