diff --git a/frontend/lib/registry/components/common/ConfigField.tsx b/frontend/lib/registry/components/common/ConfigField.tsx index 0b11780d..954ddc9b 100644 --- a/frontend/lib/registry/components/common/ConfigField.tsx +++ b/frontend/lib/registry/components/common/ConfigField.tsx @@ -1,6 +1,6 @@ "use client"; -import React from "react"; +import React, { useState } from "react"; import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; import { @@ -13,7 +13,18 @@ import { import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; -import { Plus, X } from "lucide-react"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { Plus, X, Check, ChevronsUpDown } from "lucide-react"; +import { cn } from "@/lib/utils"; import { ConfigFieldDefinition, ConfigOption } from "./ConfigPanelTypes"; interface ConfigFieldProps { @@ -29,6 +40,8 @@ export function ConfigField({ onChange, tableColumns, }: ConfigFieldProps) { + const [comboboxOpen, setComboboxOpen] = useState(false); + const handleChange = (newValue: any) => { onChange(field.key, newValue); }; @@ -41,7 +54,7 @@ export function ConfigField({ value={value ?? ""} onChange={(e) => handleChange(e.target.value)} placeholder={field.placeholder} - className="h-8 text-xs" + className="h-7 text-xs" /> ); @@ -59,7 +72,7 @@ export function ConfigField({ min={field.min} max={field.max} step={field.step} - className="h-8 text-xs" + className="h-7 text-xs" /> ); @@ -77,7 +90,7 @@ export function ConfigField({ value={value ?? ""} onValueChange={handleChange} > - + @@ -103,25 +116,25 @@ export function ConfigField({ case "color": return ( -
+
handleChange(e.target.value)} - className="h-8 w-8 cursor-pointer rounded border" + className="h-7 w-7 cursor-pointer rounded border" /> handleChange(e.target.value)} placeholder="#000000" - className="h-8 flex-1 text-xs" + className="h-7 flex-1 text-xs" />
); case "slider": return ( -
+
({ min={field.min} max={field.max} step={field.step} - className="h-8 w-20 text-xs" + className="h-7 w-16 text-xs" /> - - {field.min ?? 0} ~ {field.max ?? 100} + + {field.min ?? 0}~{field.max ?? 100}
); case "multi-select": return ( -
+
{(field.options || []).map((opt) => { const selected = Array.isArray(value) && value.includes(opt.value); return ( @@ -230,7 +243,7 @@ export function ConfigField({ value={value ?? ""} onValueChange={handleChange} > - + @@ -244,21 +257,123 @@ export function ConfigField({ ); } + case "checkbox": + return ( +
+ + {field.description && ( + + )} +
+ ); + + case "combobox": { + const options = field.options || []; + const selectedLabel = options.find((opt) => opt.value === value)?.label; + return ( + + + + + + + + + + 결과 없음 + + + {options.map((opt) => ( + { + handleChange(currentValue === value ? "" : currentValue); + setComboboxOpen(false); + }} + className="text-xs" + > + + {opt.label} + + ))} + + + + + + ); + } + default: return null; } }; - return ( -
-
- - {field.type === "switch" && renderField()} + // textarea, multi-select, key-value는 전체 폭 수직 레이아웃 + const isFullWidth = ["textarea", "multi-select", "key-value"].includes(field.type); + // checkbox는 description을 인라인으로 표시하므로 별도 처리 + const isCheckbox = field.type === "checkbox"; + + if (isFullWidth) { + return ( +
+ + {field.description && !isCheckbox && ( +

{field.description}

+ )} + {renderField()} +
+ ); + } + + // switch, checkbox: 라벨 왼쪽, 컨트롤 오른쪽 (고정폭 없이) + if (field.type === "switch" || isCheckbox) { + return ( +
+ + {renderField()} +
+ ); + } + + // 기본: 수평 property row (라벨 왼쪽, 컨트롤 오른쪽 고정폭) + return ( +
+ +
+ {renderField()}
- {field.description && ( -

{field.description}

- )} - {field.type !== "switch" && renderField()}
); } diff --git a/frontend/lib/registry/components/common/ConfigPanelTypes.ts b/frontend/lib/registry/components/common/ConfigPanelTypes.ts index cbd31ab9..3a23f537 100644 --- a/frontend/lib/registry/components/common/ConfigPanelTypes.ts +++ b/frontend/lib/registry/components/common/ConfigPanelTypes.ts @@ -10,7 +10,9 @@ export type ConfigFieldType = | "slider" | "multi-select" | "key-value" - | "column-picker"; + | "column-picker" + | "checkbox" + | "combobox"; export interface ConfigOption { label: string; @@ -40,6 +42,7 @@ export interface ConfigSectionDefinition { defaultOpen?: boolean; fields: ConfigFieldDefinition[]; condition?: (config: T) => boolean; + group?: string; } export interface ConfigPanelBuilderProps { diff --git a/frontend/lib/registry/components/common/ConfigSection.tsx b/frontend/lib/registry/components/common/ConfigSection.tsx index 2f8275c9..3dd60c05 100644 --- a/frontend/lib/registry/components/common/ConfigSection.tsx +++ b/frontend/lib/registry/components/common/ConfigSection.tsx @@ -14,39 +14,45 @@ export function ConfigSection({ section, children }: ConfigSectionProps) { if (section.collapsible) { return ( -
+
- {isOpen &&
{children}
} + {isOpen &&
{children}
}
); } return ( -
-
-

{section.title}

+
+
+

+ {section.title} +

{section.description && ( -

+ {section.description} -

+ )}
-
{children}
+
{children}
); }