세부설정

This commit is contained in:
kjs 2025-10-14 17:40:51 +09:00
parent a2c3737f7a
commit a7de47e7ea
9 changed files with 153 additions and 84 deletions

View File

@ -161,7 +161,9 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
>
{/* 동적 컴포넌트 렌더링 */}
<div
className={`h-full w-full max-w-full ${component.componentConfig?.type === "table-list" ? "overflow-hidden" : "overflow-hidden"}`}
className={`h-full w-full max-w-full ${
component.componentConfig?.type === "table-list" ? "overflow-hidden" : "overflow-hidden"
}`}
>
<DynamicComponentRenderer
component={component}

View File

@ -464,15 +464,24 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
return comp;
}
// 중첩 경로를 고려한 안전한 복사
const newComp = { ...comp };
let current: any = newComp;
// 경로를 따라 내려가면서 각 레벨을 새 객체로 복사
let current: any = newComp;
for (let i = 0; i < pathParts.length - 1; i++) {
if (!current[pathParts[i]]) {
current[pathParts[i]] = {};
const key = pathParts[i];
// 다음 레벨이 없거나 객체가 아니면 새 객체 생성
if (!current[key] || typeof current[key] !== "object" || Array.isArray(current[key])) {
current[key] = {};
} else {
// 기존 객체를 복사하여 불변성 유지
current[key] = { ...current[key] };
}
current = current[pathParts[i]];
current = current[key];
}
// 최종 값 설정
current[pathParts[pathParts.length - 1]] = value;
console.log("✅ 컴포넌트 업데이트 완료:", {

View File

@ -814,6 +814,10 @@ const PropertiesPanelComponent: React.FC<PropertiesPanelProps> = ({
input: 9,
"two-thirds": 8,
"three-quarters": 9,
fiveTwelfths: 5,
sevenTwelfths: 7,
fiveSixths: 10,
elevenTwelfths: 11,
};
const gridColumns = columnsMap[value] || 6;
onUpdateProperty("gridColumns", gridColumns);
@ -918,21 +922,30 @@ const PropertiesPanelComponent: React.FC<PropertiesPanelProps> = ({
</div>
</div>
<div>
<div className="col-span-2">
<Label htmlFor="height" className="text-sm font-medium">
(px)
(40px )
</Label>
<Input
id="height"
type="number"
value={localInputs.height}
onChange={(e) => {
const newValue = e.target.value;
setLocalInputs((prev) => ({ ...prev, height: newValue }));
onUpdateProperty("size.height", Number(newValue));
}}
className="mt-1"
/>
<div className="mt-1 flex items-center space-x-2">
<Input
id="height"
type="number"
min="1"
max="20"
value={Math.round((localInputs.height || 40) / 40)}
onChange={(e) => {
const rows = Math.max(1, Math.min(20, Number(e.target.value)));
const newHeight = rows * 40;
setLocalInputs((prev) => ({ ...prev, height: newHeight.toString() }));
onUpdateProperty("size.height", newHeight);
}}
className="flex-1"
/>
<span className="text-sm text-gray-500"> = {localInputs.height || 40}px</span>
</div>
<p className="mt-1 text-xs text-gray-500">
1 = 40px ( {Math.round((localInputs.height || 40) / 40)})
</p>
</div>
</>
) : (

View File

@ -9,14 +9,19 @@
*/
export type ColumnSpanPreset =
| "full" // 12 컬럼 (100%)
| "half" // 6 컬럼 (50%)
| "third" // 4 컬럼 (33%)
| "twoThirds" // 8 컬럼 (67%)
| "quarter" // 3 컬럼 (25%)
| "eleven-twelfths" // 11 컬럼 (92%)
| "five-sixths" // 10 컬럼 (83%)
| "threeQuarters" // 9 컬럼 (75%)
| "twoThirds" // 8 컬럼 (67%)
| "seven-twelfths" // 7 컬럼 (58%)
| "half" // 6 컬럼 (50%)
| "five-twelfths" // 5 컬럼 (42%)
| "third" // 4 컬럼 (33%)
| "quarter" // 3 컬럼 (25%)
| "small" // 2 컬럼 (17%)
| "twelfth" // 1 컬럼 (8%)
| "label" // 3 컬럼 (25%) - 폼 라벨 전용
| "input" // 9 컬럼 (75%) - 폼 입력 전용
| "small" // 2 컬럼 (17%)
| "medium" // 4 컬럼 (33%)
| "large" // 8 컬럼 (67%)
| "auto"; // 자동 계산
@ -26,14 +31,19 @@ export type ColumnSpanPreset =
*/
export const COLUMN_SPAN_VALUES: Record<ColumnSpanPreset, number> = {
full: 12,
half: 6,
third: 4,
twoThirds: 8,
quarter: 3,
"eleven-twelfths": 11,
"five-sixths": 10,
threeQuarters: 9,
twoThirds: 8,
"seven-twelfths": 7,
half: 6,
"five-twelfths": 5,
third: 4,
quarter: 3,
small: 2,
twelfth: 1,
label: 3,
input: 9,
small: 2,
medium: 4,
large: 8,
auto: 0, // 자동 계산 시 0
@ -54,33 +64,19 @@ export interface ColumnSpanPresetInfo {
*
*/
export const COLUMN_SPAN_PRESETS: Record<ColumnSpanPreset, ColumnSpanPresetInfo> = {
full: {
value: 12,
label: "전체",
percentage: "100%",
class: "col-span-12",
description: "전체 너비 (테이블, 제목 등)",
twelfth: {
value: 1,
label: "1/12",
percentage: "8.33%",
class: "col-span-1",
description: "최소 너비",
},
half: {
value: 6,
label: "절반",
percentage: "50%",
class: "col-span-6",
description: "2분할 레이아웃",
},
third: {
value: 4,
label: "1/3",
percentage: "33%",
class: "col-span-4",
description: "3분할 레이아웃",
},
twoThirds: {
value: 8,
label: "2/3",
percentage: "67%",
class: "col-span-8",
description: "큰 컴포넌트",
small: {
value: 2,
label: "1/6",
percentage: "16.67%",
class: "col-span-2",
description: "아이콘, 체크박스",
},
quarter: {
value: 3,
@ -89,6 +85,41 @@ export const COLUMN_SPAN_PRESETS: Record<ColumnSpanPreset, ColumnSpanPresetInfo>
class: "col-span-3",
description: "4분할 레이아웃",
},
third: {
value: 4,
label: "1/3",
percentage: "33.33%",
class: "col-span-4",
description: "3분할 레이아웃",
},
"five-twelfths": {
value: 5,
label: "5/12",
percentage: "41.67%",
class: "col-span-5",
description: "중간 크기",
},
half: {
value: 6,
label: "절반",
percentage: "50%",
class: "col-span-6",
description: "2분할 레이아웃",
},
"seven-twelfths": {
value: 7,
label: "7/12",
percentage: "58.33%",
class: "col-span-7",
description: "큰 크기",
},
twoThirds: {
value: 8,
label: "2/3",
percentage: "66.67%",
class: "col-span-8",
description: "큰 컴포넌트",
},
threeQuarters: {
value: 9,
label: "3/4",
@ -96,6 +127,27 @@ export const COLUMN_SPAN_PRESETS: Record<ColumnSpanPreset, ColumnSpanPresetInfo>
class: "col-span-9",
description: "입력 필드",
},
"five-sixths": {
value: 10,
label: "5/6",
percentage: "83.33%",
class: "col-span-10",
description: "매우 큰 컴포넌트",
},
"eleven-twelfths": {
value: 11,
label: "11/12",
percentage: "91.67%",
class: "col-span-11",
description: "거의 전체",
},
full: {
value: 12,
label: "전체",
percentage: "100%",
class: "col-span-12",
description: "전체 너비 (테이블, 제목 등)",
},
label: {
value: 3,
label: "라벨용",
@ -110,24 +162,17 @@ export const COLUMN_SPAN_PRESETS: Record<ColumnSpanPreset, ColumnSpanPresetInfo>
class: "col-span-9",
description: "폼 입력 전용",
},
small: {
value: 2,
label: "작게",
percentage: "17%",
class: "col-span-2",
description: "아이콘, 체크박스",
},
medium: {
value: 4,
label: "보통",
percentage: "33%",
percentage: "33.33%",
class: "col-span-4",
description: "보통 크기 컴포넌트",
},
large: {
value: 8,
label: "크게",
percentage: "67%",
percentage: "66.67%",
class: "col-span-8",
description: "큰 컴포넌트",
},

View File

@ -5,14 +5,14 @@
export const INPUT_CLASSES = {
// 기본 input 스타일
base: `
w-full h-10 px-3 py-2 text-sm
w-full h-full px-3 py-2 text-sm
border border-gray-300 rounded-md
bg-white text-gray-900
outline-none transition-all duration-200
focus:border-orange-500 focus:ring-2 focus:ring-orange-100
disabled:bg-gray-100 disabled:text-gray-400 disabled:cursor-not-allowed
placeholder:text-gray-400
max-w-full overflow-hidden
max-w-full box-border
`,
// 선택된 상태
@ -32,36 +32,36 @@ export const INPUT_CLASSES = {
// 컨테이너
container: `
relative w-full max-w-full overflow-hidden
relative w-full h-full max-w-full box-border
`,
// textarea
textarea: `
w-full min-h-[80px] px-3 py-2 text-sm
w-full h-full px-3 py-2 text-sm
border border-gray-300 rounded-md
bg-white text-gray-900
outline-none transition-all duration-200
focus:border-orange-500 focus:ring-2 focus:ring-orange-100
disabled:bg-gray-100 disabled:text-gray-400 disabled:cursor-not-allowed
resize-vertical
max-w-full overflow-hidden
resize-none
max-w-full box-border
`,
// select
select: `
w-full h-10 px-3 py-2 text-sm
w-full h-full px-3 py-2 text-sm
border border-gray-300 rounded-md
bg-white text-gray-900
outline-none transition-all duration-200
focus:border-orange-500 focus:ring-2 focus:ring-orange-100
disabled:bg-gray-100 disabled:text-gray-400 disabled:cursor-not-allowed
cursor-pointer
max-w-full overflow-hidden
max-w-full box-border
`,
// flex 컨테이너 (email, tel, url 등)
flexContainer: `
flex items-center gap-2 w-full h-10 max-w-full overflow-hidden
flex items-center gap-2 w-full h-full max-w-full box-border
`,
// 구분자 (@ , ~ 등)

View File

@ -326,7 +326,7 @@ export const DateInputComponent: React.FC<DateInputComponentProps> = ({
</label>
)}
<div className="flex h-10 w-full items-center gap-2">
<div className="box-border flex h-full w-full items-center gap-2">
{/* 시작일 */}
<input
type="date"
@ -396,7 +396,7 @@ export const DateInputComponent: React.FC<DateInputComponentProps> = ({
}
}
}}
className={`h-10 w-full rounded-md border px-3 py-2 text-sm transition-all duration-200 outline-none ${isSelected ? "border-blue-500 ring-2 ring-blue-100" : "border-gray-300"} ${componentConfig.disabled ? "bg-gray-100 text-gray-400" : "bg-white text-gray-900"} placeholder:text-gray-400 focus:border-orange-500 focus:ring-2 focus:ring-orange-100 disabled:cursor-not-allowed`}
className={`box-border h-full w-full rounded-md border px-3 py-2 text-sm transition-all duration-200 outline-none ${isSelected ? "border-blue-500 ring-2 ring-blue-100" : "border-gray-300"} ${componentConfig.disabled ? "bg-gray-100 text-gray-400" : "bg-white text-gray-900"} placeholder:text-gray-400 focus:border-orange-500 focus:ring-2 focus:ring-orange-100 disabled:cursor-not-allowed`}
/>
</div>
);
@ -423,7 +423,7 @@ export const DateInputComponent: React.FC<DateInputComponentProps> = ({
disabled={componentConfig.disabled || false}
required={componentConfig.required || false}
readOnly={componentConfig.readonly || finalAutoGeneration?.enabled || false}
className={`h-10 w-full rounded-md border px-3 py-2 text-sm shadow-sm transition-all duration-200 outline-none ${isSelected ? "border-blue-500 ring-2 ring-blue-100" : "border-gray-300"} ${componentConfig.disabled ? "bg-gray-100 text-gray-400" : "bg-white text-gray-900"} placeholder:text-gray-400 focus:border-orange-500 focus:ring-2 focus:ring-orange-100 disabled:cursor-not-allowed`}
className={`box-border h-full w-full rounded-md border px-3 py-2 text-sm shadow-sm transition-all duration-200 outline-none ${isSelected ? "border-blue-500 ring-2 ring-blue-100" : "border-gray-300"} ${componentConfig.disabled ? "bg-gray-100 text-gray-400" : "bg-white text-gray-900"} placeholder:text-gray-400 focus:border-orange-500 focus:ring-2 focus:ring-orange-100 disabled:cursor-not-allowed`}
onClick={handleClick}
onDragStart={onDragStart}
onDragEnd={onDragEnd}

View File

@ -119,7 +119,7 @@ export const NumberInputComponent: React.FC<NumberInputComponentProps> = ({
</label>
)}
<div className="flex h-10 w-full items-center gap-1">
<div className="box-border flex h-full w-full items-center gap-1">
{/* 통화 기호 */}
<span className="pl-2 text-base font-semibold text-green-600"></span>
@ -157,7 +157,7 @@ export const NumberInputComponent: React.FC<NumberInputComponentProps> = ({
</label>
)}
<div className="flex h-10 w-full items-center gap-1">
<div className="box-border flex h-full w-full items-center gap-1">
{/* 숫자 입력 */}
<input
type="text"
@ -215,7 +215,7 @@ export const NumberInputComponent: React.FC<NumberInputComponentProps> = ({
min={componentConfig.min}
max={componentConfig.max}
step={step}
className={`h-10 w-full rounded-md border px-3 py-2 text-sm shadow-sm transition-all duration-200 outline-none ${isSelected ? "border-blue-500 ring-2 ring-blue-100" : "border-gray-300"} ${componentConfig.disabled ? "bg-gray-100 text-gray-400" : "bg-white text-gray-900"} placeholder:text-gray-400 focus:border-orange-500 focus:ring-2 focus:ring-orange-100 disabled:cursor-not-allowed`}
className={`box-border h-full w-full rounded-md border px-3 py-2 text-sm shadow-sm transition-all duration-200 outline-none ${isSelected ? "border-blue-500 ring-2 ring-blue-100" : "border-gray-300"} ${componentConfig.disabled ? "bg-gray-100 text-gray-400" : "bg-white text-gray-900"} placeholder:text-gray-400 focus:border-orange-500 focus:ring-2 focus:ring-orange-100 disabled:cursor-not-allowed`}
onClick={handleClick}
onDragStart={onDragStart}
onDragEnd={onDragEnd}

View File

@ -434,7 +434,7 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
<div className="w-full">
<div
className={cn(
"flex min-h-[40px] w-full flex-wrap gap-2 rounded-lg border border-gray-300 bg-white px-3 py-2",
"box-border flex h-full w-full flex-wrap gap-2 rounded-lg border border-gray-300 bg-white px-3 py-2",
!isDesignMode && "hover:border-orange-400",
isSelected && "ring-2 ring-orange-500",
)}

View File

@ -312,7 +312,7 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
</label>
)}
<div className="flex h-10 w-full items-center gap-2">
<div className="box-border flex h-full w-full items-center gap-2">
{/* 사용자명 입력 */}
<input
type="text"
@ -423,7 +423,7 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
</label>
)}
<div className="flex h-10 w-full items-center gap-1.5">
<div className="box-border flex h-full w-full items-center gap-1.5">
{/* 첫 번째 부분 (지역번호) */}
<input
type="text"
@ -513,7 +513,7 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
</label>
)}
<div className="flex h-10 w-full items-center gap-1">
<div className="box-border flex h-full w-full items-center gap-1">
{/* 프로토콜 선택 */}
<select
value={urlProtocol}
@ -602,7 +602,7 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
});
}
}}
className={`min-h-[80px] w-full max-w-full resize-y rounded-md border px-3 py-2 text-sm transition-all duration-200 outline-none ${isSelected ? "border-blue-500 ring-2 ring-blue-100" : "border-gray-300"} ${componentConfig.disabled ? "bg-gray-100 text-gray-400" : "bg-white text-gray-900"} placeholder:text-gray-400 focus:border-orange-500 focus:ring-2 focus:ring-orange-100 disabled:cursor-not-allowed`}
className={`box-border h-full w-full max-w-full resize-none rounded-md border px-3 py-2 text-sm transition-all duration-200 outline-none ${isSelected ? "border-blue-500 ring-2 ring-blue-100" : "border-gray-300"} ${componentConfig.disabled ? "bg-gray-100 text-gray-400" : "bg-white text-gray-900"} placeholder:text-gray-400 focus:border-orange-500 focus:ring-2 focus:ring-orange-100 disabled:cursor-not-allowed`}
/>
</div>
);
@ -653,7 +653,7 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
disabled={componentConfig.disabled || false}
required={componentConfig.required || false}
readOnly={componentConfig.readonly || (testAutoGeneration.enabled && testAutoGeneration.type !== "none")}
className={`h-10 w-full max-w-full rounded-md border px-3 py-2 text-sm shadow-sm transition-all duration-200 outline-none ${isSelected ? "border-blue-500 ring-2 ring-blue-100" : "border-gray-300"} ${componentConfig.disabled ? "bg-gray-100 text-gray-400" : "bg-white text-gray-900"} placeholder:text-gray-400 focus:border-orange-500 focus:ring-2 focus:ring-orange-100 disabled:cursor-not-allowed`}
className={`box-border h-full w-full max-w-full rounded-md border px-3 py-2 text-sm shadow-sm transition-all duration-200 outline-none ${isSelected ? "border-blue-500 ring-2 ring-blue-100" : "border-gray-300"} ${componentConfig.disabled ? "bg-gray-100 text-gray-400" : "bg-white text-gray-900"} placeholder:text-gray-400 focus:border-orange-500 focus:ring-2 focus:ring-orange-100 disabled:cursor-not-allowed`}
onClick={handleClick}
onDragStart={onDragStart}
onDragEnd={onDragEnd}