feat: ScreenModal 및 V2Select 컴포넌트 개선

- ScreenModal에서 모달 크기 계산 로직을 개선하여, 콘텐츠가 화면 높이를 초과할 때만 스크롤이 필요하도록 수정하였습니다.
- V2Select 및 관련 컴포넌트에서 height 및 style props를 추가하여, 사용자 정의 스타일을 보다 효과적으로 적용할 수 있도록 하였습니다.
- DropdownSelect에서 height 스타일을 직접 전달하여, 다양한 높이 설정을 지원하도록 개선하였습니다.
- CategorySelectComponent에서 라벨 표시 및 높이 계산 로직을 추가하여, 사용자 경험을 향상시켰습니다.
This commit is contained in:
DDD1542
2026-02-05 14:07:18 +09:00
parent 1de67a88b5
commit dd867efd0a
10 changed files with 230 additions and 66 deletions

View File

@@ -8,6 +8,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Label } from "@/components/ui/label";
import { getCategoryValues } from "@/lib/api/tableCategoryValue";
import { TableCategoryValue } from "@/types/tableCategoryValue";
import { Loader2 } from "lucide-react";
@@ -23,6 +24,20 @@ interface CategorySelectComponentProps {
readonly?: boolean;
tableName?: string;
columnName?: string;
// 🔧 높이 조절을 위한 props 추가
style?: React.CSSProperties & {
labelDisplay?: boolean;
labelFontSize?: string | number;
labelColor?: string;
labelFontWeight?: string | number;
labelMarginBottom?: string | number;
};
size?: { width?: number | string; height?: number | string };
// 🔧 라벨 표시를 위한 props 추가
label?: string;
id?: string;
// 🔧 디자인 모드 처리
isDesignMode?: boolean;
}
/**
@@ -43,7 +58,27 @@ export const CategorySelectComponent: React.FC<
readonly = false,
tableName: propTableName,
columnName: propColumnName,
style,
size,
label: propLabel,
id: propId,
isDesignMode = false,
}) => {
// 🔧 높이 계산: size.height > style.height > 기본값(40px)
const componentHeight = size?.height || style?.height;
const heightStyle: React.CSSProperties = componentHeight
? { height: componentHeight }
: {};
// 🔧 라벨 관련 계산
const label = propLabel || component?.label;
const id = propId || component?.id;
const showLabel = label && style?.labelDisplay !== false;
// 라벨 높이 계산 (기본 20px)
const labelFontSize = style?.labelFontSize ? parseInt(String(style.labelFontSize)) : 14;
const labelMarginBottom = style?.labelMarginBottom ? parseInt(String(style.labelMarginBottom)) : 4;
const estimatedLabelHeight = labelFontSize + labelMarginBottom + 2;
const [categoryValues, setCategoryValues] = useState<TableCategoryValue[]>(
[]
);
@@ -97,12 +132,49 @@ export const CategorySelectComponent: React.FC<
onChange?.(newValue);
};
// 🔧 공통 라벨 렌더링 함수
const renderLabel = () => {
if (!showLabel) return null;
return (
<Label
htmlFor={id}
style={{
position: "absolute",
top: `-${estimatedLabelHeight}px`,
left: 0,
fontSize: style?.labelFontSize || "14px",
color: style?.labelColor || "#64748b",
fontWeight: style?.labelFontWeight || "500",
}}
className="text-sm font-medium whitespace-nowrap"
>
{label}
{required && <span className="text-orange-500 ml-0.5">*</span>}
</Label>
);
};
// 🔧 공통 wrapper 스타일
const wrapperStyle: React.CSSProperties = {
width: size?.width || style?.width,
height: componentHeight,
};
// 🔧 디자인 모드일 때 클릭 방지
const designModeClass = isDesignMode ? "pointer-events-none" : "";
// 로딩 중
if (isLoading) {
return (
<div className="flex h-10 w-full items-center justify-center rounded-md border bg-background px-3 text-sm text-muted-foreground">
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
...
<div id={id} className={`relative ${designModeClass}`} style={wrapperStyle}>
{renderLabel()}
<div
className="flex h-full w-full items-center justify-center rounded-md border bg-background px-3 text-sm text-muted-foreground"
style={heightStyle}
>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
...
</div>
</div>
);
}
@@ -110,8 +182,14 @@ export const CategorySelectComponent: React.FC<
// 에러
if (error) {
return (
<div className="flex h-10 w-full items-center rounded-md border border-destructive bg-destructive/10 px-3 text-sm text-destructive">
{error}
<div id={id} className={`relative ${designModeClass}`} style={wrapperStyle}>
{renderLabel()}
<div
className="flex h-full w-full items-center rounded-md border border-destructive bg-destructive/10 px-3 text-sm text-destructive"
style={heightStyle}
>
{error}
</div>
</div>
);
}
@@ -119,33 +197,44 @@ export const CategorySelectComponent: React.FC<
// 카테고리 값이 없음
if (categoryValues.length === 0) {
return (
<div className="flex h-10 w-full items-center rounded-md border bg-muted px-3 text-sm text-muted-foreground">
<div id={id} className={`relative ${designModeClass}`} style={wrapperStyle}>
{renderLabel()}
<div
className="flex h-full w-full items-center rounded-md border bg-muted px-3 text-sm text-muted-foreground"
style={heightStyle}
>
</div>
</div>
);
}
return (
<Select
value={value}
onValueChange={handleValueChange}
disabled={disabled || readonly}
required={required}
>
<SelectTrigger className={`w-full ${className}`}>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent>
{categoryValues.map((categoryValue) => (
<SelectItem
key={categoryValue.valueId}
value={categoryValue.valueCode}
>
{categoryValue.valueLabel}
</SelectItem>
))}
</SelectContent>
</Select>
<div id={id} className={`relative ${designModeClass}`} style={wrapperStyle}>
{renderLabel()}
<div className="h-full w-full">
<Select
value={value}
onValueChange={handleValueChange}
disabled={disabled || readonly}
required={required}
>
<SelectTrigger className={`w-full h-full ${className}`} style={heightStyle}>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent>
{categoryValues.map((categoryValue) => (
<SelectItem
key={categoryValue.valueId}
value={categoryValue.valueCode}
>
{categoryValue.valueLabel}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
);
};