화면관리ui수정
This commit is contained in:
@@ -176,8 +176,13 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
||||
onSelectedRowsChange,
|
||||
refreshKey,
|
||||
onConfigChange,
|
||||
...safeProps
|
||||
isPreview,
|
||||
autoGeneration,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
// DOM 안전한 props만 필터링
|
||||
const safeProps = filterDOMProps(restProps);
|
||||
|
||||
// 컴포넌트의 columnName에 해당하는 formData 값 추출
|
||||
const fieldName = (component as any).columnName || component.id;
|
||||
@@ -232,6 +237,9 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
||||
};
|
||||
|
||||
// 렌더러 props 구성
|
||||
// component.style에서 height 제거 (RealtimePreviewDynamic에서 size.height로 처리)
|
||||
const { height: _height, ...styleWithoutHeight } = component.style || {};
|
||||
|
||||
const rendererProps = {
|
||||
component,
|
||||
isSelected,
|
||||
@@ -240,7 +248,7 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
||||
onDragEnd,
|
||||
size: component.size || newComponent.defaultSize,
|
||||
position: component.position,
|
||||
style: component.style,
|
||||
style: styleWithoutHeight,
|
||||
config: component.componentConfig,
|
||||
componentConfig: component.componentConfig,
|
||||
value: currentValue, // formData에서 추출한 현재 값 전달
|
||||
|
||||
@@ -5,6 +5,7 @@ import { WebTypeRegistry } from "./WebTypeRegistry";
|
||||
import { DynamicComponentProps } from "./types";
|
||||
// import { getWidgetComponentByWebType, getWidgetComponentByName } from "@/components/screen/widgets/types"; // 임시 비활성화
|
||||
import { useWebTypes } from "@/hooks/admin/useWebTypes";
|
||||
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
||||
|
||||
/**
|
||||
* 동적 웹타입 렌더러 컴포넌트
|
||||
@@ -160,8 +161,10 @@ export const DynamicWebTypeRenderer: React.FC<DynamicComponentProps> = ({
|
||||
|
||||
// 기본 폴백: Input 컴포넌트 사용
|
||||
const { Input } = require("@/components/ui/input");
|
||||
const { filterDOMProps } = require("@/lib/utils/domPropsFilter");
|
||||
console.log(`✅ 폴백: ${webType} 웹타입 → 기본 Input 사용`);
|
||||
return <Input placeholder={`${webType}`} disabled={props.readonly} className="w-full" {...props} />;
|
||||
const safeFallbackProps = filterDOMProps(props);
|
||||
return <Input placeholder={`${webType}`} disabled={props.readonly} className="w-full" {...safeFallbackProps} />;
|
||||
} catch (error) {
|
||||
console.error(`웹타입 "${webType}" 폴백 컴포넌트 렌더링 실패:`, error);
|
||||
return (
|
||||
|
||||
@@ -597,8 +597,27 @@ export const AccordionBasicComponent: React.FC<AccordionBasicComponentProps> = (
|
||||
formData: _formData,
|
||||
onFormDataChange: _onFormDataChange,
|
||||
componentConfig: _componentConfig,
|
||||
...domProps
|
||||
autoGeneration: _autoGeneration,
|
||||
hidden: _hidden,
|
||||
isInModal: _isInModal,
|
||||
isPreview: _isPreview,
|
||||
originalData: _originalData,
|
||||
allComponents: _allComponents,
|
||||
selectedRows: _selectedRows,
|
||||
selectedRowsData: _selectedRowsData,
|
||||
refreshKey: _refreshKey,
|
||||
onUpdateLayout: _onUpdateLayout,
|
||||
onSelectedRowsChange: _onSelectedRowsChange,
|
||||
onConfigChange: _onConfigChange,
|
||||
onZoneClick: _onZoneClick,
|
||||
selectedScreen: _selectedScreen,
|
||||
onZoneComponentDrop: _onZoneComponentDrop,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
// filterDOMProps import 추가 필요
|
||||
const { filterDOMProps } = require("@/lib/utils/domPropsFilter");
|
||||
const domProps = filterDOMProps(restProps);
|
||||
|
||||
// 사용할 아이템들 결정 (우선순위: 데이터소스 > 정적아이템 > 기본아이템)
|
||||
const finalItems = (() => {
|
||||
|
||||
@@ -186,7 +186,8 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||
// selectedRowsData,
|
||||
// });
|
||||
|
||||
// 스타일 계산 (위치는 RealtimePreviewDynamic에서 처리하므로 제외)
|
||||
// 스타일 계산
|
||||
// height: 100%로 부모(RealtimePreviewDynamic의 내부 div)의 높이를 따라감
|
||||
const componentStyle: React.CSSProperties = {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
@@ -194,9 +195,11 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||
...style,
|
||||
};
|
||||
|
||||
// 디자인 모드 스타일
|
||||
|
||||
// 디자인 모드 스타일 (border 속성 분리하여 충돌 방지)
|
||||
if (isDesignMode) {
|
||||
componentStyle.border = "1px dashed #cbd5e1";
|
||||
componentStyle.borderWidth = "1px";
|
||||
componentStyle.borderStyle = "dashed";
|
||||
componentStyle.borderColor = isSelected ? "#3b82f6" : "#cbd5e1";
|
||||
}
|
||||
|
||||
@@ -483,8 +486,7 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
minHeight: "100%",
|
||||
maxHeight: "100%",
|
||||
minHeight: "40px",
|
||||
border: "none",
|
||||
borderRadius: "0.5rem",
|
||||
background: componentConfig.disabled
|
||||
|
||||
@@ -179,6 +179,18 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
|
||||
tableName: _tableName,
|
||||
onRefresh: _onRefresh,
|
||||
onClose: _onClose,
|
||||
autoGeneration: _autoGeneration,
|
||||
hidden: _hidden,
|
||||
isInModal: _isInModal,
|
||||
isPreview: _isPreview,
|
||||
originalData: _originalData,
|
||||
allComponents: _allComponents,
|
||||
selectedRows: _selectedRows,
|
||||
selectedRowsData: _selectedRowsData,
|
||||
refreshKey: _refreshKey,
|
||||
onUpdateLayout: _onUpdateLayout,
|
||||
onSelectedRowsChange: _onSelectedRowsChange,
|
||||
onConfigChange: _onConfigChange,
|
||||
...domProps
|
||||
} = props;
|
||||
|
||||
@@ -303,10 +315,10 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
|
||||
// 이메일 타입 전용 UI
|
||||
if (webType === "email") {
|
||||
return (
|
||||
<div className={`relative w-full ${className || ""}`} {...safeDomProps}>
|
||||
<div className={`flex w-full flex-col ${className || ""}`} {...safeDomProps}>
|
||||
{/* 라벨 렌더링 */}
|
||||
{component.label && component.style?.labelDisplay !== false && (
|
||||
<label className="absolute -top-6 left-0 text-sm font-medium text-slate-600">
|
||||
<label className="mb-1.5 text-sm font-medium text-slate-600">
|
||||
{component.label}
|
||||
{component.required && <span className="text-red-500">*</span>}
|
||||
</label>
|
||||
@@ -405,10 +417,10 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
|
||||
// 전화번호 타입 전용 UI
|
||||
if (webType === "tel") {
|
||||
return (
|
||||
<div className={`relative w-full ${className || ""}`} {...safeDomProps}>
|
||||
<div className={`flex w-full flex-col ${className || ""}`} {...safeDomProps}>
|
||||
{/* 라벨 렌더링 */}
|
||||
{component.label && component.style?.labelDisplay !== false && (
|
||||
<label className="absolute -top-6 left-0 text-sm font-medium text-slate-600">
|
||||
<label className="mb-1.5 text-sm font-medium text-slate-600">
|
||||
{component.label}
|
||||
{component.required && <span className="text-red-500">*</span>}
|
||||
</label>
|
||||
@@ -486,10 +498,10 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
|
||||
// URL 타입 전용 UI
|
||||
if (webType === "url") {
|
||||
return (
|
||||
<div className={`relative w-full ${className || ""}`} {...safeDomProps}>
|
||||
<div className={`flex w-full flex-col ${className || ""}`} {...safeDomProps}>
|
||||
{/* 라벨 렌더링 */}
|
||||
{component.label && component.style?.labelDisplay !== false && (
|
||||
<label className="absolute -top-6 left-0 text-sm font-medium text-slate-600">
|
||||
<label className="mb-1.5 text-sm font-medium text-slate-600">
|
||||
{component.label}
|
||||
{component.required && <span className="text-red-500">*</span>}
|
||||
</label>
|
||||
@@ -541,10 +553,10 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
|
||||
// textarea 타입인 경우 별도 렌더링
|
||||
if (webType === "textarea") {
|
||||
return (
|
||||
<div className={`relative w-full ${className || ""}`} {...safeDomProps}>
|
||||
<div className={`flex w-full flex-col ${className || ""}`} {...safeDomProps}>
|
||||
{/* 라벨 렌더링 */}
|
||||
{component.label && component.style?.labelDisplay !== false && (
|
||||
<label className="absolute -top-6 left-0 text-sm font-medium text-slate-600">
|
||||
<label className="mb-1.5 text-sm font-medium text-slate-600">
|
||||
{component.label}
|
||||
{component.required && <span className="text-red-500">*</span>}
|
||||
</label>
|
||||
@@ -582,10 +594,10 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`relative w-full max-w-full overflow-hidden ${className || ""}`} {...safeDomProps}>
|
||||
<div className={`flex w-full max-w-full flex-col ${className || ""}`} {...safeDomProps}>
|
||||
{/* 라벨 렌더링 */}
|
||||
{component.label && component.style?.labelDisplay !== false && (
|
||||
<label className="absolute -top-6 left-0 text-sm font-medium text-slate-600">
|
||||
<label className="mb-1.5 text-sm font-medium text-slate-600">
|
||||
{component.label}
|
||||
{component.required && <span className="text-red-500">*</span>}
|
||||
</label>
|
||||
|
||||
@@ -40,6 +40,7 @@ const REACT_ONLY_PROPS = new Set([
|
||||
// 상태 관련
|
||||
"mode",
|
||||
"isInModal",
|
||||
"isPreview",
|
||||
|
||||
// 테이블 관련
|
||||
"selectedRows",
|
||||
@@ -48,6 +49,9 @@ const REACT_ONLY_PROPS = new Set([
|
||||
// 컴포넌트 기능 관련
|
||||
"autoGeneration",
|
||||
"hidden", // 이미 SAFE_DOM_PROPS에 있지만 커스텀 구현을 위해 제외
|
||||
|
||||
// 필터링할 특수 속성
|
||||
"readonly", // readOnly로 변환되므로 원본은 제거
|
||||
]);
|
||||
|
||||
// DOM에 안전하게 전달할 수 있는 표준 HTML 속성들
|
||||
@@ -67,6 +71,28 @@ const SAFE_DOM_PROPS = new Set([
|
||||
"hidden",
|
||||
"spellCheck",
|
||||
"translate",
|
||||
|
||||
// 폼 관련 속성
|
||||
"readOnly",
|
||||
"disabled",
|
||||
"required",
|
||||
"placeholder",
|
||||
"value",
|
||||
"defaultValue",
|
||||
"checked",
|
||||
"defaultChecked",
|
||||
"name",
|
||||
"type",
|
||||
"accept",
|
||||
"autoComplete",
|
||||
"autoFocus",
|
||||
"multiple",
|
||||
"pattern",
|
||||
"min",
|
||||
"max",
|
||||
"step",
|
||||
"minLength",
|
||||
"maxLength",
|
||||
|
||||
// ARIA 속성 (aria-로 시작)
|
||||
// data 속성 (data-로 시작)
|
||||
@@ -115,6 +141,12 @@ export function filterDOMProps<T extends Record<string, any>>(props: T): Partial
|
||||
continue;
|
||||
}
|
||||
|
||||
// readonly → readOnly 변환 (React의 camelCase 규칙)
|
||||
if (key === "readonly") {
|
||||
filtered["readOnly" as keyof T] = value;
|
||||
continue;
|
||||
}
|
||||
|
||||
// aria- 또는 data- 속성은 안전하게 포함
|
||||
if (key.startsWith("aria-") || key.startsWith("data-")) {
|
||||
filtered[key as keyof T] = value;
|
||||
|
||||
Reference in New Issue
Block a user