838 lines
33 KiB
TypeScript
838 lines
33 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogAction,
|
|
AlertDialogCancel,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogFooter,
|
|
AlertDialogHeader,
|
|
AlertDialogTitle,
|
|
AlertDialogTrigger,
|
|
} from "@/components/ui/alert-dialog";
|
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Calendar } from "@/components/ui/calendar";
|
|
import { CustomCalendar } from "@/components/ui/custom-calendar";
|
|
import { ExampleFormDialog } from "@/components/examples/ExampleFormDialog";
|
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from "@/components/ui/dialog";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
|
import { Progress } from "@/components/ui/progress";
|
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
import { Separator } from "@/components/ui/separator";
|
|
import { Slider } from "@/components/ui/slider";
|
|
import { Switch } from "@/components/ui/switch";
|
|
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
import {
|
|
AlertCircle,
|
|
Check,
|
|
ChevronDown,
|
|
Info,
|
|
Loader2,
|
|
MoreHorizontal,
|
|
Plus,
|
|
Search,
|
|
Trash2,
|
|
User,
|
|
} from "lucide-react";
|
|
import { toast } from "sonner";
|
|
|
|
export default function UIComponentsDemoPage() {
|
|
const [date, setDate] = useState<Date | undefined>(new Date());
|
|
const [progress, setProgress] = useState(45);
|
|
const [switchOn, setSwitchOn] = useState(false);
|
|
const [checkboxChecked, setCheckboxChecked] = useState(false);
|
|
const [sliderValue, setSliderValue] = useState([50]);
|
|
const [radioValue, setRadioValue] = useState("option1");
|
|
|
|
return (
|
|
<div className="bg-background min-h-screen p-8">
|
|
<div className="mx-auto max-w-7xl space-y-8">
|
|
{/* 헤더 */}
|
|
<div className="space-y-4">
|
|
<div className="space-y-2">
|
|
<h1 className="text-4xl font-bold">shadcn/ui 컴포넌트 데모</h1>
|
|
<p className="text-muted-foreground text-lg">프로젝트에서 사용 가능한 모든 UI 컴포넌트를 확인하세요</p>
|
|
</div>
|
|
|
|
{/* 실전 예시 폼 */}
|
|
<Card className="bg-primary/5 border-primary/20">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<span className="text-primary">🎯</span>
|
|
실전 예시: 완전한 입력 폼
|
|
</CardTitle>
|
|
<CardDescription>
|
|
모든 shadcn/ui 컴포넌트를 활용한 완전한 폼 예시입니다. 유효성 검사, 에러 처리, 반응형 디자인이 모두
|
|
포함되어 있습니다.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<ExampleFormDialog />
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* 버튼 섹션 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Button (버튼)</CardTitle>
|
|
<CardDescription>다양한 스타일의 버튼 컴포넌트</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="flex flex-wrap gap-2">
|
|
<Button variant="default">Default</Button>
|
|
<Button variant="secondary">Secondary</Button>
|
|
<Button variant="destructive">Destructive</Button>
|
|
<Button variant="outline">Outline</Button>
|
|
<Button variant="ghost">Ghost</Button>
|
|
<Button variant="link">Link</Button>
|
|
</div>
|
|
<Separator />
|
|
<div className="flex flex-wrap gap-2">
|
|
<Button size="lg">Large</Button>
|
|
<Button size="default">Default</Button>
|
|
<Button size="sm">Small</Button>
|
|
<Button size="icon">
|
|
<Plus className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
<Separator />
|
|
<div className="flex flex-wrap gap-2">
|
|
<Button disabled>Disabled</Button>
|
|
<Button>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Loading
|
|
</Button>
|
|
<Button>
|
|
<Plus className="mr-2 h-4 w-4" />
|
|
With Icon
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Badge 섹션 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Badge (배지)</CardTitle>
|
|
<CardDescription>상태 표시 및 태그용 배지</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex flex-wrap gap-2">
|
|
<Badge>Default</Badge>
|
|
<Badge variant="secondary">Secondary</Badge>
|
|
<Badge variant="destructive">Destructive</Badge>
|
|
<Badge variant="outline">Outline</Badge>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Alert 섹션 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Alert (알림)</CardTitle>
|
|
<CardDescription>정보 표시용 알림 박스</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<Alert>
|
|
<Info className="h-4 w-4" />
|
|
<AlertTitle>기본 알림</AlertTitle>
|
|
<AlertDescription>
|
|
이것은 기본 알림 메시지입니다. 사용자에게 정보를 전달할 때 사용합니다.
|
|
</AlertDescription>
|
|
</Alert>
|
|
<Alert variant="destructive">
|
|
<AlertCircle className="h-4 w-4" />
|
|
<AlertTitle>오류 발생</AlertTitle>
|
|
<AlertDescription>문제가 발생했습니다. 다시 시도해주세요.</AlertDescription>
|
|
</Alert>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Input & Form 섹션 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Input & Form (입력 필드)</CardTitle>
|
|
<CardDescription>폼 입력 컴포넌트들</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
{/* Text Input */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="email">이메일</Label>
|
|
<Input id="email" type="email" placeholder="example@email.com" />
|
|
</div>
|
|
|
|
{/* Textarea */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="message">메시지</Label>
|
|
<Textarea id="message" placeholder="메시지를 입력하세요..." rows={4} />
|
|
</div>
|
|
|
|
{/* Select */}
|
|
<div className="space-y-2">
|
|
<Label>셀렉트</Label>
|
|
<Select>
|
|
<SelectTrigger className="w-full">
|
|
<SelectValue placeholder="옵션을 선택하세요" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="option1">옵션 1</SelectItem>
|
|
<SelectItem value="option2">옵션 2</SelectItem>
|
|
<SelectItem value="option3">옵션 3</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* Checkbox */}
|
|
<div className="flex items-center space-x-2">
|
|
<Checkbox
|
|
id="terms"
|
|
checked={checkboxChecked}
|
|
onCheckedChange={(checked) => setCheckboxChecked(checked as boolean)}
|
|
/>
|
|
<Label htmlFor="terms" className="cursor-pointer">
|
|
약관에 동의합니다
|
|
</Label>
|
|
</div>
|
|
|
|
{/* Radio Group */}
|
|
<div className="space-y-2">
|
|
<Label>라디오 그룹</Label>
|
|
<RadioGroup value={radioValue} onValueChange={setRadioValue}>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="option1" id="r1" />
|
|
<Label htmlFor="r1" className="cursor-pointer">
|
|
옵션 1
|
|
</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="option2" id="r2" />
|
|
<Label htmlFor="r2" className="cursor-pointer">
|
|
옵션 2
|
|
</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="option3" id="r3" />
|
|
<Label htmlFor="r3" className="cursor-pointer">
|
|
옵션 3
|
|
</Label>
|
|
</div>
|
|
</RadioGroup>
|
|
</div>
|
|
|
|
{/* Switch */}
|
|
<div className="flex items-center space-x-2">
|
|
<Switch id="airplane" checked={switchOn} onCheckedChange={setSwitchOn} />
|
|
<Label htmlFor="airplane" className="cursor-pointer">
|
|
비행기 모드 {switchOn ? "켜짐" : "꺼짐"}
|
|
</Label>
|
|
</div>
|
|
|
|
{/* Slider */}
|
|
<div className="space-y-2">
|
|
<Label>슬라이더 (값: {sliderValue[0]})</Label>
|
|
<Slider value={sliderValue} onValueChange={setSliderValue} max={100} step={1} />
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Dialog & Modal 섹션 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Dialog & Modal (대화상자)</CardTitle>
|
|
<CardDescription>모달 대화상자 컴포넌트</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="flex flex-wrap gap-2">
|
|
{/* Dialog */}
|
|
<Dialog>
|
|
<DialogTrigger asChild>
|
|
<Button variant="outline">Dialog 열기</Button>
|
|
</DialogTrigger>
|
|
<DialogContent className="max-w-[95vw] sm:max-w-[500px]">
|
|
<DialogHeader>
|
|
<DialogTitle className="text-base sm:text-lg">대화상자 제목</DialogTitle>
|
|
<DialogDescription className="text-xs sm:text-sm">
|
|
여기에 대화상자 설명이 들어갑니다.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="space-y-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="name">이름</Label>
|
|
<Input id="name" placeholder="이름을 입력하세요" />
|
|
</div>
|
|
</div>
|
|
<DialogFooter className="gap-2 sm:gap-0">
|
|
<Button variant="outline" className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm">
|
|
취소
|
|
</Button>
|
|
<Button className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm">확인</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Alert Dialog */}
|
|
<AlertDialog>
|
|
<AlertDialogTrigger asChild>
|
|
<Button variant="destructive">Alert Dialog 열기</Button>
|
|
</AlertDialogTrigger>
|
|
<AlertDialogContent>
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle>정말 삭제하시겠습니까?</AlertDialogTitle>
|
|
<AlertDialogDescription>이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?</AlertDialogDescription>
|
|
</AlertDialogHeader>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel>취소</AlertDialogCancel>
|
|
<AlertDialogAction>삭제</AlertDialogAction>
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Dropdown & Popover 섹션 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Dropdown & Popover (드롭다운)</CardTitle>
|
|
<CardDescription>드롭다운 메뉴 및 팝오버</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="flex flex-wrap gap-2">
|
|
{/* Dropdown Menu */}
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="outline">
|
|
드롭다운 메뉴
|
|
<ChevronDown className="ml-2 h-4 w-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent>
|
|
<DropdownMenuLabel>내 계정</DropdownMenuLabel>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem>프로필</DropdownMenuItem>
|
|
<DropdownMenuItem>설정</DropdownMenuItem>
|
|
<DropdownMenuItem>팀</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem className="text-destructive">로그아웃</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
|
|
{/* Popover */}
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<Button variant="outline">팝오버 열기</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-80">
|
|
<div className="space-y-2">
|
|
<h4 className="leading-none font-medium">팝오버 제목</h4>
|
|
<p className="text-muted-foreground text-sm">팝오버 내용이 여기에 표시됩니다.</p>
|
|
</div>
|
|
</PopoverContent>
|
|
</Popover>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Command 섹션 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Command (커맨드 팔레트)</CardTitle>
|
|
<CardDescription>검색 가능한 커맨드 인터페이스</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Command className="rounded-lg border shadow-md">
|
|
<CommandInput placeholder="명령어 검색..." />
|
|
<CommandList>
|
|
<CommandEmpty>검색 결과가 없습니다.</CommandEmpty>
|
|
<CommandGroup heading="제안">
|
|
<CommandItem>
|
|
<Search className="mr-2 h-4 w-4" />
|
|
<span>검색</span>
|
|
</CommandItem>
|
|
<CommandItem>
|
|
<User className="mr-2 h-4 w-4" />
|
|
<span>사용자</span>
|
|
</CommandItem>
|
|
<CommandItem>
|
|
<Plus className="mr-2 h-4 w-4" />
|
|
<span>새로 만들기</span>
|
|
</CommandItem>
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Tabs 섹션 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Tabs (탭)</CardTitle>
|
|
<CardDescription>탭 네비게이션</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Tabs defaultValue="tab1" className="w-full">
|
|
<TabsList className="grid w-full grid-cols-3">
|
|
<TabsTrigger value="tab1">탭 1</TabsTrigger>
|
|
<TabsTrigger value="tab2">탭 2</TabsTrigger>
|
|
<TabsTrigger value="tab3">탭 3</TabsTrigger>
|
|
</TabsList>
|
|
<TabsContent value="tab1" className="space-y-4">
|
|
<p className="text-muted-foreground text-sm">첫 번째 탭의 내용입니다.</p>
|
|
</TabsContent>
|
|
<TabsContent value="tab2" className="space-y-4">
|
|
<p className="text-muted-foreground text-sm">두 번째 탭의 내용입니다.</p>
|
|
</TabsContent>
|
|
<TabsContent value="tab3" className="space-y-4">
|
|
<p className="text-muted-foreground text-sm">세 번째 탭의 내용입니다.</p>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Accordion 섹션 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Accordion (아코디언)</CardTitle>
|
|
<CardDescription>접이식 패널</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Accordion type="single" collapsible className="w-full">
|
|
<AccordionItem value="item-1">
|
|
<AccordionTrigger>첫 번째 항목</AccordionTrigger>
|
|
<AccordionContent>첫 번째 항목의 내용입니다. 여기에 상세 정보가 표시됩니다.</AccordionContent>
|
|
</AccordionItem>
|
|
<AccordionItem value="item-2">
|
|
<AccordionTrigger>두 번째 항목</AccordionTrigger>
|
|
<AccordionContent>두 번째 항목의 내용입니다. 추가 설명이 들어갑니다.</AccordionContent>
|
|
</AccordionItem>
|
|
<AccordionItem value="item-3">
|
|
<AccordionTrigger>세 번째 항목</AccordionTrigger>
|
|
<AccordionContent>세 번째 항목의 내용입니다. 더 많은 정보를 확인할 수 있습니다.</AccordionContent>
|
|
</AccordionItem>
|
|
</Accordion>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Collapsible 섹션 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Collapsible (접이식)</CardTitle>
|
|
<CardDescription>간단한 접이식 컴포넌트</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Collapsible>
|
|
<CollapsibleTrigger asChild>
|
|
<Button variant="ghost" className="w-full justify-between">
|
|
<span>더 보기</span>
|
|
<ChevronDown className="h-4 w-4" />
|
|
</Button>
|
|
</CollapsibleTrigger>
|
|
<CollapsibleContent className="mt-2 space-y-2">
|
|
<div className="rounded-md border px-4 py-2 text-sm">추가 내용 1</div>
|
|
<div className="rounded-md border px-4 py-2 text-sm">추가 내용 2</div>
|
|
<div className="rounded-md border px-4 py-2 text-sm">추가 내용 3</div>
|
|
</CollapsibleContent>
|
|
</Collapsible>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Progress 섹션 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Progress (진행률)</CardTitle>
|
|
<CardDescription>진행 상태 표시</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-muted-foreground">업로드 중...</span>
|
|
<span className="font-medium">{progress}%</span>
|
|
</div>
|
|
<Progress value={progress} />
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button size="sm" onClick={() => setProgress(Math.max(0, progress - 10))}>
|
|
-10%
|
|
</Button>
|
|
<Button size="sm" onClick={() => setProgress(Math.min(100, progress + 10))}>
|
|
+10%
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Avatar 섹션 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Avatar (아바타)</CardTitle>
|
|
<CardDescription>사용자 프로필 이미지</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex gap-4">
|
|
<Avatar>
|
|
<AvatarImage src="https://github.com/shadcn.png" />
|
|
<AvatarFallback>CN</AvatarFallback>
|
|
</Avatar>
|
|
<Avatar>
|
|
<AvatarFallback>KJ</AvatarFallback>
|
|
</Avatar>
|
|
<Avatar>
|
|
<AvatarFallback>
|
|
<User className="h-4 w-4" />
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Table 섹션 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Table (테이블)</CardTitle>
|
|
<CardDescription>데이터 테이블</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Table>
|
|
<TableCaption>최근 거래 내역</TableCaption>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>ID</TableHead>
|
|
<TableHead>이름</TableHead>
|
|
<TableHead>상태</TableHead>
|
|
<TableHead className="text-right">금액</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
<TableRow>
|
|
<TableCell className="font-medium">001</TableCell>
|
|
<TableCell>김철수</TableCell>
|
|
<TableCell>
|
|
<Badge>완료</Badge>
|
|
</TableCell>
|
|
<TableCell className="text-right">₩100,000</TableCell>
|
|
</TableRow>
|
|
<TableRow>
|
|
<TableCell className="font-medium">002</TableCell>
|
|
<TableCell>이영희</TableCell>
|
|
<TableCell>
|
|
<Badge variant="secondary">대기</Badge>
|
|
</TableCell>
|
|
<TableCell className="text-right">₩250,000</TableCell>
|
|
</TableRow>
|
|
<TableRow>
|
|
<TableCell className="font-medium">003</TableCell>
|
|
<TableCell>박민수</TableCell>
|
|
<TableCell>
|
|
<Badge variant="destructive">취소</Badge>
|
|
</TableCell>
|
|
<TableCell className="text-right">₩50,000</TableCell>
|
|
</TableRow>
|
|
</TableBody>
|
|
</Table>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* ScrollArea 섹션 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Scroll Area (스크롤 영역)</CardTitle>
|
|
<CardDescription>커스텀 스크롤바가 있는 영역</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<ScrollArea className="h-72 w-full rounded-md border p-4">
|
|
<div className="space-y-2">
|
|
{Array.from({ length: 50 }).map((_, i) => (
|
|
<div key={i} className="text-muted-foreground text-sm">
|
|
항목 {i + 1}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</ScrollArea>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Calendar & Date Picker 섹션 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Calendar & Date Picker (캘린더 및 날짜 선택)</CardTitle>
|
|
<CardDescription>다양한 형태의 날짜 선택 컴포넌트</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
{/* Date Picker (권장 방식 - Custom Calendar 사용) */}
|
|
<div className="space-y-2">
|
|
<Label>Date Picker (Popover 방식 - 권장)</Label>
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<Button variant="outline" className="w-[280px] justify-start text-left font-normal">
|
|
{date ? (
|
|
date.toLocaleDateString("ko-KR", {
|
|
year: "numeric",
|
|
month: "long",
|
|
day: "numeric",
|
|
})
|
|
) : (
|
|
<span className="text-muted-foreground">날짜를 선택하세요</span>
|
|
)}
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width="24"
|
|
height="24"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
className="ml-auto h-4 w-4 opacity-50"
|
|
>
|
|
<rect width="18" height="18" x="3" y="4" rx="2" ry="2" />
|
|
<line x1="16" x2="16" y1="2" y2="6" />
|
|
<line x1="8" x2="8" y1="2" y2="6" />
|
|
<line x1="3" x2="21" y1="10" y2="10" />
|
|
</svg>
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-auto p-0">
|
|
<CustomCalendar mode="single" selected={date} onSelect={setDate} />
|
|
</PopoverContent>
|
|
</Popover>
|
|
{date && <p className="text-muted-foreground text-xs">선택된 날짜: {date.toISOString().split("T")[0]}</p>}
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
{/* 커스텀 Calendar (shadcn/ui 스타일) */}
|
|
<div className="space-y-2">
|
|
<Label>Custom Calendar (자체 제작 - shadcn/ui 스타일)</Label>
|
|
<CustomCalendar
|
|
mode="single"
|
|
selected={date}
|
|
onSelect={setDate}
|
|
className="rounded-md border shadow-sm"
|
|
/>
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
{/* 기존 react-day-picker Calendar */}
|
|
<div className="space-y-2">
|
|
<Label>React-Day-Picker Calendar (원본)</Label>
|
|
<Calendar mode="single" selected={date} onSelect={setDate} className="rounded-md border shadow-sm" />
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Separator 섹션 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Separator (구분선)</CardTitle>
|
|
<CardDescription>컨텐츠 구분용 선</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div>
|
|
<p className="text-sm">위 내용</p>
|
|
<Separator className="my-4" />
|
|
<p className="text-sm">아래 내용</p>
|
|
</div>
|
|
<div className="flex h-20 items-center space-x-4">
|
|
<div className="text-sm">왼쪽</div>
|
|
<Separator orientation="vertical" />
|
|
<div className="text-sm">오른쪽</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Toast 버튼 섹션 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Toast (토스트 알림)</CardTitle>
|
|
<CardDescription>Sonner 토스트 알림 (우측 상단에 표시됨)</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex flex-wrap gap-2">
|
|
<Button
|
|
onClick={() =>
|
|
toast.success("성공", {
|
|
description: "작업이 성공적으로 완료되었습니다.",
|
|
})
|
|
}
|
|
>
|
|
성공 토스트
|
|
</Button>
|
|
<Button
|
|
variant="destructive"
|
|
onClick={() =>
|
|
toast.error("오류", {
|
|
description: "작업 중 오류가 발생했습니다.",
|
|
})
|
|
}
|
|
>
|
|
오류 토스트
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() =>
|
|
toast("알림", {
|
|
description: "일반 알림 메시지입니다.",
|
|
})
|
|
}
|
|
>
|
|
일반 토스트
|
|
</Button>
|
|
<Button
|
|
variant="secondary"
|
|
onClick={() => {
|
|
const promise = new Promise((resolve) => setTimeout(resolve, 2000));
|
|
toast.promise(promise, {
|
|
loading: "처리 중...",
|
|
success: "완료!",
|
|
error: "실패",
|
|
});
|
|
}}
|
|
>
|
|
Promise 토스트
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Lucide Icons 섹션 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Lucide Icons (아이콘)</CardTitle>
|
|
<CardDescription>자주 사용하는 아이콘들</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-6 gap-4 sm:grid-cols-8 md:grid-cols-12">
|
|
{[
|
|
{ Icon: Plus, name: "Plus" },
|
|
{ Icon: Trash2, name: "Trash2" },
|
|
{ Icon: Search, name: "Search" },
|
|
{ Icon: User, name: "User" },
|
|
{ Icon: Check, name: "Check" },
|
|
{ Icon: ChevronDown, name: "ChevronDown" },
|
|
{ Icon: AlertCircle, name: "AlertCircle" },
|
|
{ Icon: Info, name: "Info" },
|
|
{ Icon: Loader2, name: "Loader2" },
|
|
{ Icon: MoreHorizontal, name: "MoreHorizontal" },
|
|
].map(({ Icon, name }) => (
|
|
<div
|
|
key={name}
|
|
className="hover:bg-accent flex flex-col items-center gap-2 rounded-lg border p-3"
|
|
title={name}
|
|
>
|
|
<Icon className="h-6 w-6" />
|
|
<span className="text-muted-foreground text-[10px]">{name}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 컬러 팔레트 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Color Palette (색상 팔레트)</CardTitle>
|
|
<CardDescription>프로젝트 색상 시스템</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
|
|
<div className="space-y-2">
|
|
<div className="bg-primary h-20 rounded-lg" />
|
|
<p className="text-xs font-medium">Primary</p>
|
|
<code className="text-muted-foreground text-[10px]">bg-primary</code>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<div className="bg-secondary h-20 rounded-lg" />
|
|
<p className="text-xs font-medium">Secondary</p>
|
|
<code className="text-muted-foreground text-[10px]">bg-secondary</code>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<div className="bg-destructive h-20 rounded-lg" />
|
|
<p className="text-xs font-medium">Destructive</p>
|
|
<code className="text-muted-foreground text-[10px]">bg-destructive</code>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<div className="bg-muted h-20 rounded-lg" />
|
|
<p className="text-xs font-medium">Muted</p>
|
|
<code className="text-muted-foreground text-[10px]">bg-muted</code>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<div className="bg-accent h-20 rounded-lg" />
|
|
<p className="text-xs font-medium">Accent</p>
|
|
<code className="text-muted-foreground text-[10px]">bg-accent</code>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<div className="bg-card h-20 rounded-lg border" />
|
|
<p className="text-xs font-medium">Card</p>
|
|
<code className="text-muted-foreground text-[10px]">bg-card</code>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<div className="bg-popover h-20 rounded-lg border" />
|
|
<p className="text-xs font-medium">Popover</p>
|
|
<code className="text-muted-foreground text-[10px]">bg-popover</code>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<div className="bg-background h-20 rounded-lg border" />
|
|
<p className="text-xs font-medium">Background</p>
|
|
<code className="text-muted-foreground text-[10px]">bg-background</code>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 푸터 */}
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
<p className="text-muted-foreground text-center text-sm">
|
|
모든 컴포넌트는{" "}
|
|
<a
|
|
href="https://ui.shadcn.com"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="hover:text-foreground underline"
|
|
>
|
|
shadcn/ui
|
|
</a>
|
|
를 기반으로 구축되었습니다.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|