기본 폼·검색+필터·다이얼로그 폼·파일 업로드 등 입력 UI 4종입니다.
Input + Select + Button 조합으로 만드는 등록/수정 폼입니다.
<div className="rounded-md border bg-muted/30 p-4">
<div className="grid grid-cols-2 gap-3 md:grid-cols-4">
<div className="space-y-1">
<Label htmlFor="name" className="text-[10px]">이름</Label>
<Input id="name" placeholder="홍길동" className="h-8 text-xs" />
</div>
{/* ... */}
</div>
<div className="mt-3 flex justify-end gap-2">
<Button size="sm" variant="outline">취소</Button>
<Button size="sm">저장</Button>
</div>
</div>검색어 입력 + 필터 드롭다운 조합으로 목록 상단에 두는 검색바입니다.
interface SearchFilterValue {
search: string
status: "all" | "active" | "inactive"
sort: "newest" | "oldest" | "name"
}
// 상태를 컴포넌트가 소유하지 않고 value/onChange로 부모가 소유하는 controlled 컴포넌트다.
function SearchFilterDemo({ value, onChange, onReset }: {
value: SearchFilterValue
onChange: (value: SearchFilterValue) => void
onReset?: () => void
}) {
return (
<div className="flex flex-wrap items-center gap-2">
<Input value={value.search} onChange={(e) => onChange({ ...value, search: e.target.value })} className="h-8 pl-8 text-xs" />
<Select value={value.status} onValueChange={(status) => onChange({ ...value, status })}>{/* 상태 필터 */}</Select>
<Select value={value.sort} onValueChange={(sort) => onChange({ ...value, sort })}>{/* 정렬 필터 */}</Select>
<Button size="sm" variant="outline" onClick={() => (onReset ? onReset() : onChange(SEARCH_FILTER_DEFAULT))}>초기화</Button>
<Button size="sm">검색</Button>
</div>
)
}Dialog 안에 폼을 담아 목록 화면에서 바로 등록/삭제 확인을 처리합니다.
<Dialog>
<DialogTrigger asChild>
<Button size="sm">등록 다이얼로그</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>새 항목 등록</DialogTitle>
<DialogDescription>필요한 정보를 입력하고 저장 버튼을 클릭하세요.</DialogDescription>
</DialogHeader>
<div className="grid gap-3 py-3">{/* 필드 */}</div>
<DialogFooter>
<Button size="sm" variant="outline">취소</Button>
<Button size="sm">저장</Button>
</DialogFooter>
</DialogContent>
</Dialog>드래그앤드롭 영역 + 업로드 진행 상태 리스트 조합입니다.
파일을 드래그하거나 클릭해서 업로드
JSON, CSV (최대 10MB)
업로드 진행
<div className="rounded-lg border-2 border-dashed border-border p-6 text-center hover:border-primary/50">
<CloudArrowUpIcon size={24} className="text-muted-foreground" />
<p className="text-xs font-medium">파일을 드래그하거나 클릭해서 업로드</p>
<p className="text-[10px] text-muted-foreground">JSON, CSV (최대 10MB)</p>
</div>
<div className="space-y-1 rounded border p-2">
<div className="flex items-center justify-between">
<span className="text-xs font-medium">{file.name}</span>
<Badge variant={statusVariant}>{file.status}</Badge>
</div>
<Progress value={file.pct} className="h-1.5" />
</div>