세부설정
This commit is contained in:
parent
a2c3737f7a
commit
a7de47e7ea
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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("✅ 컴포넌트 업데이트 완료:", {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -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: "큰 컴포넌트",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
`,
|
||||
|
||||
// 구분자 (@ , ~ 등)
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
Loading…
Reference in New Issue