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) { if (!isDesignMode) {
const compType = (component as any).componentType || component.componentConfig?.type || ""; const compType = (component as any).componentType || component.componentConfig?.type || "";
// 테이블: 부모 flex 컨테이너가 높이 관리 (flex: 1) // 레이아웃 계열: 부모 래퍼를 꽉 채움 (ResponsiveGridRenderer가 % 높이 관리)
const flexGrowTypes = [ const fillParentTypes = [
"table-list", "v2-table-list", "table-list", "v2-table-list",
"split-panel-layout", "split-panel-layout2", "split-panel-layout", "split-panel-layout2",
"v2-split-panel-layout", "screen-split-panel", "v2-split-panel-layout", "screen-split-panel",
"v2-tab-container", "tab-container", "v2-tab-container", "tab-container",
"tabs-widget", "v2-tabs-widget", "tabs-widget", "v2-tabs-widget",
]; ];
if (flexGrowTypes.some(t => compType === t)) { if (fillParentTypes.some(t => compType === t)) {
return "100%"; return "100%";
} }
const autoHeightTypes = [ const autoHeightTypes = [

View File

@ -23,8 +23,9 @@ function getComponentTypeId(component: ComponentData): string {
} }
/** /**
* . * (%) .
* , . * 가로: 컨테이너 %
* 세로: 컨테이너 %
*/ */
function ProportionalRenderer({ function ProportionalRenderer({
components, components,
@ -47,19 +48,12 @@ function ProportionalRenderer({
}, []); }, []);
const topLevel = components.filter((c) => !c.parentId); 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 ( return (
<div <div
ref={containerRef} ref={containerRef}
data-screen-runtime="true" data-screen-runtime="true"
className="bg-background relative w-full overflow-x-hidden" className="bg-background relative h-full w-full overflow-hidden"
style={{ minHeight: containerW > 0 ? `${maxBottom * ratio}px` : "200px" }}
> >
{containerW > 0 && {containerW > 0 &&
topLevel.map((component) => { topLevel.map((component) => {
@ -72,9 +66,9 @@ function ProportionalRenderer({
style={{ style={{
position: "absolute", position: "absolute",
left: `${(component.position.x / canvasWidth) * 100}%`, 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}%`, 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, 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", isDesignMode && column.hidden && "bg-muted/50 opacity-40",
)} )}
style={{ style={{
width: getColumnWidth(column), width: column.columnName === "__checkbox__" ? 48 : getColumnWidth(column),
minWidth: "100px", // 최소 너비 보장 minWidth: column.columnName === "__checkbox__" ? "48px" : "100px",
maxWidth: "300px", // 최대 너비 제한 maxWidth: column.columnName === "__checkbox__" ? "48px" : "300px",
boxSizing: "border-box", boxSizing: "border-box",
overflow: "hidden", overflow: "hidden",
textOverflow: "ellipsis", textOverflow: "ellipsis",
whiteSpace: "nowrap", // 텍스트 줄바꿈 방지 whiteSpace: "nowrap",
backgroundColor: "hsl(var(--muted) / 0.4)", backgroundColor: "hsl(var(--muted) / 0.4)",
// sticky 위치 설정
...(column.fixed === "left" && { left: leftFixedWidth }), ...(column.fixed === "left" && { left: leftFixedWidth }),
...(column.fixed === "right" && { right: rightFixedWidth }), ...(column.fixed === "right" && { right: rightFixedWidth }),
}} }}
onClick={() => column.sortable && sortHandler(column.columnName)} 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__" ? ( {column.columnName === "__checkbox__" ? (
checkboxConfig.selectAll && ( checkboxConfig.selectAll && (
<Checkbox <Checkbox
checked={isAllSelected} checked={isAllSelected}
onCheckedChange={handleSelectAll} onCheckedChange={handleSelectAll}
aria-label="전체 선택" 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}`} key={`cell-${column.columnName}`}
id={isCurrentSearchResult ? "current-search-result" : undefined} id={isCurrentSearchResult ? "current-search-result" : undefined}
className={cn( className={cn(
"text-foreground h-10 px-3 py-[7px] align-middle text-[11px] transition-colors", "text-foreground h-10 align-middle text-[11px] transition-colors",
// 이미지 셀은 overflow/ellipsis 제외 (이미지 잘림 방지) column.columnName === "__checkbox__" ? "px-0 py-[7px] text-center" : "px-3 py-[7px]",
!isReactElement && "whitespace-nowrap", !isReactElement && "whitespace-nowrap",
`text-${column.align}`, column.columnName !== "__checkbox__" && `text-${column.align}`,
// 고정 컬럼 스타일
column.fixed === "left" && column.fixed === "left" &&
"border-border bg-background/90 sticky z-10 border-r backdrop-blur-sm", "border-border bg-background/90 sticky z-10 border-r backdrop-blur-sm",
column.fixed === "right" && column.fixed === "right" &&
"border-border bg-background/90 sticky z-10 border-l backdrop-blur-sm", "border-border bg-background/90 sticky z-10 border-l backdrop-blur-sm",
// 편집 가능 셀 스타일
onCellDoubleClick && column.columnName !== "__checkbox__" && "cursor-text", onCellDoubleClick && column.columnName !== "__checkbox__" && "cursor-text",
)} )}
style={{ style={{
width: getColumnWidth(column), width: column.columnName === "__checkbox__" ? 48 : getColumnWidth(column),
minWidth: "100px", // 최소 너비 보장 minWidth: column.columnName === "__checkbox__" ? "48px" : "100px",
maxWidth: "300px", // 최대 너비 제한 maxWidth: column.columnName === "__checkbox__" ? "48px" : "300px",
boxSizing: "border-box", boxSizing: "border-box",
// 이미지 셀은 overflow 허용
...(isReactElement ? {} : { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }), ...(isReactElement ? {} : { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }),
// sticky 위치 설정
...(column.fixed === "left" && { left: leftFixedWidth }), ...(column.fixed === "left" && { left: leftFixedWidth }),
...(column.fixed === "right" && { right: rightFixedWidth }), ...(column.fixed === "right" && { right: rightFixedWidth }),
}} }}

View File

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