feat(pop-card-list): PopCardList 컴포넌트 구현
- PopCardList 컴포넌트 추가 (NumberInputModal, PackageUnitModal 포함) - ComponentEditorPanel, PopRenderer 충돌 해결 (modals + onRequestResize 통합) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -116,6 +116,8 @@ interface PopCanvasProps {
|
||||
onLockLayout?: () => void;
|
||||
onResetOverride?: (mode: GridMode) => void;
|
||||
onChangeGapPreset?: (preset: GapPreset) => void;
|
||||
/** 컴포넌트가 자신의 rowSpan/colSpan 변경을 요청 (CardList 확장 등) */
|
||||
onRequestResize?: (componentId: string, newRowSpan: number, newColSpan?: number) => void;
|
||||
/** 대시보드 페이지 미리보기 인덱스 (-1이면 기본 모드) */
|
||||
previewPageIndex?: number;
|
||||
/** 현재 활성 캔버스 ID ("main" 또는 모달 ID) */
|
||||
@@ -147,6 +149,7 @@ export default function PopCanvas({
|
||||
onLockLayout,
|
||||
onResetOverride,
|
||||
onChangeGapPreset,
|
||||
onRequestResize,
|
||||
previewPageIndex,
|
||||
activeCanvasId = "main",
|
||||
onActiveCanvasChange,
|
||||
@@ -761,6 +764,7 @@ export default function PopCanvas({
|
||||
onComponentMove={onMoveComponent}
|
||||
onComponentResize={onResizeComponent}
|
||||
onComponentResizeEnd={onResizeEnd}
|
||||
onRequestResize={onRequestResize}
|
||||
overrideGap={adjustedGap}
|
||||
overridePadding={adjustedPadding}
|
||||
previewPageIndex={previewPageIndex}
|
||||
|
||||
@@ -55,6 +55,7 @@ export default function PopDesigner({
|
||||
onBackToList,
|
||||
onScreenUpdate,
|
||||
}: PopDesignerProps) {
|
||||
|
||||
// ========================================
|
||||
// 레이아웃 상태
|
||||
// ========================================
|
||||
@@ -489,6 +490,56 @@ export default function PopDesigner({
|
||||
[layout, saveToHistory]
|
||||
);
|
||||
|
||||
// 컴포넌트가 자신의 rowSpan/colSpan을 동적으로 변경 요청 (CardList 확장 등)
|
||||
const handleRequestResize = useCallback(
|
||||
(componentId: string, newRowSpan: number, newColSpan?: number) => {
|
||||
const component = layout.components[componentId];
|
||||
if (!component) return;
|
||||
|
||||
const newPosition = {
|
||||
...component.position,
|
||||
rowSpan: newRowSpan,
|
||||
...(newColSpan !== undefined ? { colSpan: newColSpan } : {}),
|
||||
};
|
||||
|
||||
// 기본 모드(tablet_landscape)인 경우: 원본 position 직접 수정
|
||||
if (currentMode === "tablet_landscape") {
|
||||
const newLayout = {
|
||||
...layout,
|
||||
components: {
|
||||
...layout.components,
|
||||
[componentId]: {
|
||||
...component,
|
||||
position: newPosition,
|
||||
},
|
||||
},
|
||||
};
|
||||
setLayout(newLayout);
|
||||
saveToHistory(newLayout);
|
||||
setHasChanges(true);
|
||||
} else {
|
||||
// 다른 모드인 경우: 오버라이드에 저장
|
||||
const newLayout = {
|
||||
...layout,
|
||||
overrides: {
|
||||
...layout.overrides,
|
||||
[currentMode]: {
|
||||
...layout.overrides?.[currentMode],
|
||||
positions: {
|
||||
...layout.overrides?.[currentMode]?.positions,
|
||||
[componentId]: newPosition,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
setLayout(newLayout);
|
||||
saveToHistory(newLayout);
|
||||
setHasChanges(true);
|
||||
}
|
||||
},
|
||||
[layout, currentMode, saveToHistory]
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// Gap 프리셋 관리
|
||||
// ========================================
|
||||
@@ -830,6 +881,7 @@ export default function PopDesigner({
|
||||
onLockLayout={handleLockLayout}
|
||||
onResetOverride={handleResetOverride}
|
||||
onChangeGapPreset={handleChangeGapPreset}
|
||||
onRequestResize={handleRequestResize}
|
||||
previewPageIndex={previewPageIndex}
|
||||
activeCanvasId={activeCanvasId}
|
||||
onActiveCanvasChange={navigateToCanvas}
|
||||
|
||||
@@ -209,6 +209,7 @@ export default function ComponentEditorPanel({
|
||||
<ComponentSettingsForm
|
||||
component={component}
|
||||
onUpdate={onUpdateComponent}
|
||||
currentMode={currentMode}
|
||||
previewPageIndex={previewPageIndex}
|
||||
onPreviewPage={onPreviewPage}
|
||||
modals={modals}
|
||||
@@ -399,12 +400,13 @@ function PositionForm({ component, currentMode, isDefaultMode, columns, onUpdate
|
||||
interface ComponentSettingsFormProps {
|
||||
component: PopComponentDefinitionV5;
|
||||
onUpdate?: (updates: Partial<PopComponentDefinitionV5>) => void;
|
||||
currentMode?: GridMode;
|
||||
previewPageIndex?: number;
|
||||
onPreviewPage?: (pageIndex: number) => void;
|
||||
modals?: PopModalDefinition[];
|
||||
}
|
||||
|
||||
function ComponentSettingsForm({ component, onUpdate, previewPageIndex, onPreviewPage, modals }: ComponentSettingsFormProps) {
|
||||
function ComponentSettingsForm({ component, onUpdate, currentMode, previewPageIndex, onPreviewPage, modals }: ComponentSettingsFormProps) {
|
||||
// PopComponentRegistry에서 configPanel 가져오기
|
||||
const registeredComp = PopComponentRegistry.getComponent(component.type);
|
||||
const ConfigPanel = registeredComp?.configPanel;
|
||||
@@ -433,6 +435,8 @@ function ComponentSettingsForm({ component, onUpdate, previewPageIndex, onPrevie
|
||||
<ConfigPanel
|
||||
config={component.config || {}}
|
||||
onUpdate={handleConfigUpdate}
|
||||
currentMode={currentMode}
|
||||
currentColSpan={component.position.colSpan}
|
||||
onPreviewPage={onPreviewPage}
|
||||
previewPageIndex={previewPageIndex}
|
||||
modals={modals}
|
||||
|
||||
@@ -48,6 +48,8 @@ interface PopRendererProps {
|
||||
onComponentResize?: (componentId: string, newPosition: PopGridPosition) => void;
|
||||
/** 컴포넌트 크기 조정 완료 (히스토리 저장용) */
|
||||
onComponentResizeEnd?: (componentId: string) => void;
|
||||
/** 컴포넌트가 자신의 rowSpan/colSpan 변경을 요청 (CardList 확장 등) */
|
||||
onRequestResize?: (componentId: string, newRowSpan: number, newColSpan?: number) => void;
|
||||
/** Gap 오버라이드 (Gap 프리셋 적용된 값) */
|
||||
overrideGap?: number;
|
||||
/** Padding 오버라이드 (Gap 프리셋 적용된 값) */
|
||||
@@ -91,6 +93,7 @@ export default function PopRenderer({
|
||||
onComponentMove,
|
||||
onComponentResize,
|
||||
onComponentResizeEnd,
|
||||
onRequestResize,
|
||||
overrideGap,
|
||||
overridePadding,
|
||||
className,
|
||||
@@ -270,6 +273,7 @@ export default function PopRenderer({
|
||||
onComponentMove={onComponentMove}
|
||||
onComponentResize={onComponentResize}
|
||||
onComponentResizeEnd={onComponentResizeEnd}
|
||||
onRequestResize={onRequestResize}
|
||||
previewPageIndex={previewPageIndex}
|
||||
/>
|
||||
);
|
||||
@@ -279,7 +283,7 @@ export default function PopRenderer({
|
||||
return (
|
||||
<div
|
||||
key={comp.id}
|
||||
className="relative rounded-lg border-2 border-gray-200 bg-white transition-all z-10"
|
||||
className="relative overflow-hidden rounded-lg border-2 border-gray-200 bg-white transition-all z-10"
|
||||
style={positionStyle}
|
||||
>
|
||||
<ComponentContent
|
||||
@@ -287,7 +291,8 @@ export default function PopRenderer({
|
||||
effectivePosition={position}
|
||||
isDesignMode={false}
|
||||
isSelected={false}
|
||||
screenId={String(currentScreenId || "")}
|
||||
onRequestResize={onRequestResize}
|
||||
screenId={currentScreenId ? String(currentScreenId) : undefined}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -315,6 +320,7 @@ interface DraggableComponentProps {
|
||||
onComponentMove?: (componentId: string, newPosition: PopGridPosition) => void;
|
||||
onComponentResize?: (componentId: string, newPosition: PopGridPosition) => void;
|
||||
onComponentResizeEnd?: (componentId: string) => void;
|
||||
onRequestResize?: (componentId: string, newRowSpan: number, newColSpan?: number) => void;
|
||||
previewPageIndex?: number;
|
||||
}
|
||||
|
||||
@@ -333,6 +339,7 @@ function DraggableComponent({
|
||||
onComponentMove,
|
||||
onComponentResize,
|
||||
onComponentResizeEnd,
|
||||
onRequestResize,
|
||||
previewPageIndex,
|
||||
}: DraggableComponentProps) {
|
||||
const [{ isDragging }, drag] = useDrag(
|
||||
@@ -373,7 +380,8 @@ function DraggableComponent({
|
||||
isDesignMode={isDesignMode}
|
||||
isSelected={isSelected}
|
||||
previewPageIndex={previewPageIndex}
|
||||
screenId=""
|
||||
onRequestResize={onRequestResize}
|
||||
screenId={undefined}
|
||||
/>
|
||||
|
||||
{/* 리사이즈 핸들 (선택된 컴포넌트만) */}
|
||||
@@ -525,11 +533,12 @@ interface ComponentContentProps {
|
||||
isDesignMode: boolean;
|
||||
isSelected: boolean;
|
||||
previewPageIndex?: number;
|
||||
onRequestResize?: (componentId: string, newRowSpan: number, newColSpan?: number) => void;
|
||||
/** 화면 ID (이벤트 버스/액션 실행용) */
|
||||
screenId?: string;
|
||||
}
|
||||
|
||||
function ComponentContent({ component, effectivePosition, isDesignMode, isSelected, previewPageIndex, screenId }: ComponentContentProps) {
|
||||
function ComponentContent({ component, effectivePosition, isDesignMode, isSelected, previewPageIndex, onRequestResize, screenId }: ComponentContentProps) {
|
||||
const typeLabel = COMPONENT_TYPE_LABELS[component.type] || component.type;
|
||||
|
||||
// PopComponentRegistry에서 등록된 컴포넌트 가져오기
|
||||
@@ -543,7 +552,8 @@ function ComponentContent({ component, effectivePosition, isDesignMode, isSelect
|
||||
// 실제 컴포넌트가 등록되어 있으면 실제 데이터로 렌더링 (대시보드 등)
|
||||
if (ActualComp) {
|
||||
// 아이콘 컴포넌트는 클릭 이벤트가 필요하므로 pointer-events 허용
|
||||
const needsPointerEvents = component.type === "pop-icon";
|
||||
// CardList 컴포넌트도 버튼 클릭이 필요하므로 pointer-events 허용
|
||||
const needsPointerEvents = component.type === "pop-icon" || component.type === "pop-card-list";
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
@@ -555,7 +565,11 @@ function ComponentContent({ component, effectivePosition, isDesignMode, isSelect
|
||||
label={component.label}
|
||||
isDesignMode={isDesignMode}
|
||||
previewPageIndex={previewPageIndex}
|
||||
componentId={component.id}
|
||||
screenId={screenId}
|
||||
currentRowSpan={effectivePosition.rowSpan}
|
||||
currentColSpan={effectivePosition.colSpan}
|
||||
onRequestResize={onRequestResize}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -575,23 +589,36 @@ function ComponentContent({ component, effectivePosition, isDesignMode, isSelect
|
||||
);
|
||||
}
|
||||
|
||||
// 실제 모드: 컴포넌트 렌더링
|
||||
return renderActualComponent(component, screenId);
|
||||
// 실제 모드: 컴포넌트 렌더링 (뷰어 모드에서도 리사이즈 지원)
|
||||
return renderActualComponent(component, effectivePosition, onRequestResize, screenId);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 실제 컴포넌트 렌더링 (뷰어 모드)
|
||||
// ========================================
|
||||
|
||||
function renderActualComponent(component: PopComponentDefinitionV5, screenId?: string): React.ReactNode {
|
||||
function renderActualComponent(
|
||||
component: PopComponentDefinitionV5,
|
||||
effectivePosition?: PopGridPosition,
|
||||
onRequestResize?: (componentId: string, newRowSpan: number, newColSpan?: number) => void,
|
||||
screenId?: string,
|
||||
): React.ReactNode {
|
||||
// 레지스트리에서 등록된 실제 컴포넌트 조회
|
||||
const registeredComp = PopComponentRegistry.getComponent(component.type);
|
||||
const ActualComp = registeredComp?.component;
|
||||
|
||||
if (ActualComp) {
|
||||
return (
|
||||
<div className="w-full min-h-full">
|
||||
<ActualComp config={component.config} label={component.label} screenId={screenId} componentId={component.id} />
|
||||
<div className="h-full w-full overflow-hidden">
|
||||
<ActualComp
|
||||
config={component.config}
|
||||
label={component.label}
|
||||
componentId={component.id}
|
||||
screenId={screenId}
|
||||
currentRowSpan={effectivePosition?.rowSpan}
|
||||
currentColSpan={effectivePosition?.colSpan}
|
||||
onRequestResize={onRequestResize}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user