This commit is contained in:
DDD1542 2026-03-18 00:05:40 +09:00
parent c63eaf8434
commit b2a569f908
4 changed files with 51 additions and 49 deletions

View File

@ -362,15 +362,15 @@ const RealtimePreviewDynamicComponent: React.FC<RealtimePreviewProps> = ({
// 런타임 모드에서 컴포넌트 타입별 높이 처리
if (!isDesignMode) {
const compType = (component as any).componentType || component.componentConfig?.type || "";
// 테이블: 부모 flex 컨테이너가 높이 관리 (flex: 1)
const flexGrowTypes = [
// 레이아웃 계열: 부모 래퍼를 꽉 채움 (ResponsiveGridRenderer가 % 높이 관리)
const fillParentTypes = [
"table-list", "v2-table-list",
"split-panel-layout", "split-panel-layout2",
"v2-split-panel-layout", "screen-split-panel",
"v2-tab-container", "tab-container",
"tabs-widget", "v2-tabs-widget",
];
if (flexGrowTypes.some(t => compType === t)) {
if (fillParentTypes.some(t => compType === t)) {
return "100%";
}
const autoHeightTypes = [

View File

@ -23,8 +23,9 @@ function getComponentTypeId(component: ComponentData): string {
}
/**
* .
* , .
* (%) .
* 가로: 컨테이너 %
* 세로: 컨테이너 %
*/
function ProportionalRenderer({
components,
@ -47,19 +48,12 @@ function ProportionalRenderer({
}, []);
const topLevel = components.filter((c) => !c.parentId);
const ratio = containerW > 0 ? containerW / canvasWidth : 1;
const maxBottom = topLevel.reduce((max, c) => {
const bottom = c.position.y + (c.size?.height || 40);
return Math.max(max, bottom);
}, 0);
return (
<div
ref={containerRef}
data-screen-runtime="true"
className="bg-background relative w-full overflow-x-hidden"
style={{ minHeight: containerW > 0 ? `${maxBottom * ratio}px` : "200px" }}
className="bg-background relative h-full w-full overflow-hidden"
>
{containerW > 0 &&
topLevel.map((component) => {
@ -72,9 +66,9 @@ function ProportionalRenderer({
style={{
position: "absolute",
left: `${(component.position.x / canvasWidth) * 100}%`,
top: `${component.position.y * ratio}px`,
top: `${(component.position.y / canvasHeight) * 100}%`,
width: `${((component.size?.width || 100) / canvasWidth) * 100}%`,
height: `${(component.size?.height || 40) * ratio}px`,
height: `${((component.size?.height || 40) / canvasHeight) * 100}%`,
zIndex: component.position.z || 1,
}}
>

View File

@ -144,28 +144,33 @@ export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
isDesignMode && column.hidden && "bg-muted/50 opacity-40",
)}
style={{
width: getColumnWidth(column),
minWidth: "100px", // 최소 너비 보장
maxWidth: "300px", // 최대 너비 제한
width: column.columnName === "__checkbox__" ? 48 : getColumnWidth(column),
minWidth: column.columnName === "__checkbox__" ? "48px" : "100px",
maxWidth: column.columnName === "__checkbox__" ? "48px" : "300px",
boxSizing: "border-box",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap", // 텍스트 줄바꿈 방지
whiteSpace: "nowrap",
backgroundColor: "hsl(var(--muted) / 0.4)",
// sticky 위치 설정
...(column.fixed === "left" && { left: leftFixedWidth }),
...(column.fixed === "right" && { right: rightFixedWidth }),
}}
onClick={() => column.sortable && sortHandler(column.columnName)}
>
<div className="flex items-center gap-2">
<div className={cn("flex items-center", column.columnName === "__checkbox__" ? "justify-center" : "gap-2")}>
{column.columnName === "__checkbox__" ? (
checkboxConfig.selectAll && (
<Checkbox
checked={isAllSelected}
onCheckedChange={handleSelectAll}
aria-label="전체 선택"
style={{ zIndex: 1 }}
style={{
width: 16,
height: 16,
borderWidth: 1.5,
borderColor: isAllSelected ? "hsl(var(--primary))" : "hsl(var(--muted-foreground) / 0.5)",
zIndex: 1,
}}
/>
)
) : (
@ -327,26 +332,22 @@ export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
key={`cell-${column.columnName}`}
id={isCurrentSearchResult ? "current-search-result" : undefined}
className={cn(
"text-foreground h-10 px-3 py-[7px] align-middle text-[11px] transition-colors",
// 이미지 셀은 overflow/ellipsis 제외 (이미지 잘림 방지)
"text-foreground h-10 align-middle text-[11px] transition-colors",
column.columnName === "__checkbox__" ? "px-0 py-[7px] text-center" : "px-3 py-[7px]",
!isReactElement && "whitespace-nowrap",
`text-${column.align}`,
// 고정 컬럼 스타일
column.columnName !== "__checkbox__" && `text-${column.align}`,
column.fixed === "left" &&
"border-border bg-background/90 sticky z-10 border-r backdrop-blur-sm",
column.fixed === "right" &&
"border-border bg-background/90 sticky z-10 border-l backdrop-blur-sm",
// 편집 가능 셀 스타일
onCellDoubleClick && column.columnName !== "__checkbox__" && "cursor-text",
)}
style={{
width: getColumnWidth(column),
minWidth: "100px", // 최소 너비 보장
maxWidth: "300px", // 최대 너비 제한
width: column.columnName === "__checkbox__" ? 48 : getColumnWidth(column),
minWidth: column.columnName === "__checkbox__" ? "48px" : "100px",
maxWidth: column.columnName === "__checkbox__" ? "48px" : "300px",
boxSizing: "border-box",
// 이미지 셀은 overflow 허용
...(isReactElement ? {} : { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }),
// sticky 위치 설정
...(column.fixed === "left" && { left: leftFixedWidth }),
...(column.fixed === "right" && { right: rightFixedWidth }),
}}

View File

@ -878,10 +878,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
const [isTableOptionsOpen, setIsTableOptionsOpen] = useState(false);
const [showGridLines, setShowGridLines] = useState(true);
const [viewMode, setViewMode] = useState<"table" | "card" | "grouped-card">("table");
// 체크박스 컬럼은 항상 기본 틀고정
const [frozenColumns, setFrozenColumns] = useState<string[]>(
(tableConfig.checkbox?.enabled ?? true) ? ["__checkbox__"] : [],
);
const [frozenColumns, setFrozenColumns] = useState<string[]>([]);
const [frozenColumnCount, setFrozenColumnCount] = useState<number>(0);
// 🆕 Search Panel (통합 검색) 관련 상태
@ -1173,14 +1170,10 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
frozenColumnCount, // 현재 틀고정 컬럼 수
onFrozenColumnCountChange: (count: number) => {
setFrozenColumnCount(count);
// 체크박스 컬럼은 항상 틀고정에 포함
const checkboxColumn = (tableConfig.checkbox?.enabled ?? true) ? ["__checkbox__"] : [];
// 표시 가능한 컬럼 중 처음 N개를 틀고정 컬럼으로 설정
const visibleCols = columnsToRegister
.filter((col) => col.visible !== false)
.map((col) => col.columnName || col.field);
const newFrozenColumns = [...checkboxColumn, ...visibleCols.slice(0, count)];
setFrozenColumns(newFrozenColumns);
setFrozenColumns(visibleCols.slice(0, count));
},
// 탭 관련 정보 (탭 내부의 테이블인 경우)
parentTabId,
@ -3080,11 +3073,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
if (state.groupByColumns) setGroupByColumns(state.groupByColumns);
if (state.frozenColumns) {
// 체크박스 컬럼이 항상 포함되도록 보장
const checkboxColumn = (tableConfig.checkbox?.enabled ?? true) ? "__checkbox__" : null;
const restoredFrozenColumns =
checkboxColumn && !state.frozenColumns.includes(checkboxColumn)
? [checkboxColumn, ...state.frozenColumns]
: state.frozenColumns;
const restoredFrozenColumns = (state.frozenColumns || []).filter((col: string) => col !== "__checkbox__");
setFrozenColumns(restoredFrozenColumns);
}
if (state.frozenColumnCount !== undefined) setFrozenColumnCount(state.frozenColumnCount); // 틀고정 컬럼 수 복원
@ -4233,7 +4222,19 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
if (!tableConfig.checkbox?.selectAll) return null;
if (tableConfig.checkbox?.multiple === false) return null;
return <Checkbox checked={isAllSelected} onCheckedChange={handleSelectAll} aria-label="전체 선택" />;
return (
<Checkbox
checked={isAllSelected}
onCheckedChange={handleSelectAll}
aria-label="전체 선택"
style={{
width: 16,
height: 16,
borderWidth: 1.5,
borderColor: isAllSelected ? "hsl(var(--primary))" : "hsl(var(--muted-foreground) / 0.5)",
}}
/>
);
};
const renderCheckboxCell = (row: any, index: number) => {
@ -4245,6 +4246,12 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
checked={isChecked}
onCheckedChange={(checked) => handleRowSelection(rowKey, checked as boolean)}
aria-label={`${index + 1} 선택`}
style={{
width: 16,
height: 16,
borderWidth: 1.5,
borderColor: isChecked ? "hsl(var(--primary))" : "hsl(var(--muted-foreground) / 0.5)",
}}
/>
);
};
@ -6201,7 +6208,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
"text-foreground text-[11px] font-normal",
inputType !== "image" && "overflow-hidden text-ellipsis whitespace-nowrap max-w-[170px]",
column.columnName === "__checkbox__"
? "px-0 py-[7px]"
? "px-0 py-[7px] text-center"
: "px-3 py-[7px]",
isFrozen && "sticky z-20 shadow-[2px_0_4px_rgba(0,0,0,0.08)]",
(inputType === "code" || inputType === "category") && "font-mono text-[10px] text-primary font-medium",
@ -6370,7 +6377,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
className={cn(
"text-foreground text-[11px] font-normal",
inputType !== "image" && "overflow-hidden text-ellipsis whitespace-nowrap max-w-[170px]",
column.columnName === "__checkbox__" ? "px-0 py-[7px]" : "px-3 py-[7px]",
column.columnName === "__checkbox__" ? "px-0 py-[7px] text-center" : "px-3 py-[7px]",
isFrozen && "sticky z-20 shadow-[2px_0_4px_rgba(0,0,0,0.08)]",
isCellFocused && !editingCell && "ring-primary bg-primary/5 ring-2 ring-inset",
editingCell?.rowIndex === index && editingCell?.colIndex === colIndex && "p-0",