|
|
import { |
|
|
Accordion, |
|
|
AccordionContent, |
|
|
AccordionItem, |
|
|
AccordionTrigger, |
|
|
} from "@/components/ui/accordion"; |
|
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; |
|
|
import { AspectRatio } from "@/components/ui/aspect-ratio"; |
|
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; |
|
|
import { Badge } from "@/components/ui/badge"; |
|
|
import { |
|
|
Breadcrumb, |
|
|
BreadcrumbItem, |
|
|
BreadcrumbLink, |
|
|
BreadcrumbList, |
|
|
BreadcrumbPage, |
|
|
BreadcrumbSeparator, |
|
|
} from "@/components/ui/breadcrumb"; |
|
|
import { Button } from "@/components/ui/button"; |
|
|
import { Calendar } from "@/components/ui/calendar"; |
|
|
import { |
|
|
Card, |
|
|
CardContent, |
|
|
CardDescription, |
|
|
CardFooter, |
|
|
CardHeader, |
|
|
CardTitle, |
|
|
} from "@/components/ui/card"; |
|
|
import { |
|
|
Carousel, |
|
|
CarouselContent, |
|
|
CarouselItem, |
|
|
CarouselNext, |
|
|
CarouselPrevious, |
|
|
} from "@/components/ui/carousel"; |
|
|
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 { |
|
|
ContextMenu, |
|
|
ContextMenuContent, |
|
|
ContextMenuItem, |
|
|
ContextMenuTrigger, |
|
|
} from "@/components/ui/context-menu"; |
|
|
import { |
|
|
Dialog, |
|
|
DialogContent, |
|
|
DialogDescription, |
|
|
DialogHeader, |
|
|
DialogTitle, |
|
|
DialogTrigger, |
|
|
} from "@/components/ui/dialog"; |
|
|
import { |
|
|
Drawer, |
|
|
DrawerClose, |
|
|
DrawerContent, |
|
|
DrawerDescription, |
|
|
DrawerFooter, |
|
|
DrawerHeader, |
|
|
DrawerTitle, |
|
|
DrawerTrigger, |
|
|
} from "@/components/ui/drawer"; |
|
|
import { |
|
|
DropdownMenu, |
|
|
DropdownMenuContent, |
|
|
DropdownMenuItem, |
|
|
DropdownMenuLabel, |
|
|
DropdownMenuSeparator, |
|
|
DropdownMenuTrigger, |
|
|
} from "@/components/ui/dropdown-menu"; |
|
|
import { |
|
|
HoverCard, |
|
|
HoverCardContent, |
|
|
HoverCardTrigger, |
|
|
} from "@/components/ui/hover-card"; |
|
|
import { Input } from "@/components/ui/input"; |
|
|
import { |
|
|
InputOTP, |
|
|
InputOTPGroup, |
|
|
InputOTPSlot, |
|
|
} from "@/components/ui/input-otp"; |
|
|
import { Label } from "@/components/ui/label"; |
|
|
import { |
|
|
Menubar, |
|
|
MenubarContent, |
|
|
MenubarItem, |
|
|
MenubarMenu, |
|
|
MenubarSeparator, |
|
|
MenubarTrigger, |
|
|
} from "@/components/ui/menubar"; |
|
|
import { |
|
|
Pagination, |
|
|
PaginationContent, |
|
|
PaginationItem, |
|
|
PaginationLink, |
|
|
PaginationNext, |
|
|
PaginationPrevious, |
|
|
} from "@/components/ui/pagination"; |
|
|
import { |
|
|
Popover, |
|
|
PopoverContent, |
|
|
PopoverTrigger, |
|
|
} from "@/components/ui/popover"; |
|
|
import { Progress } from "@/components/ui/progress"; |
|
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; |
|
|
import { |
|
|
ResizableHandle, |
|
|
ResizablePanel, |
|
|
ResizablePanelGroup, |
|
|
} from "@/components/ui/resizable"; |
|
|
import { ScrollArea } from "@/components/ui/scroll-area"; |
|
|
import { |
|
|
Select, |
|
|
SelectContent, |
|
|
SelectItem, |
|
|
SelectTrigger, |
|
|
SelectValue, |
|
|
} from "@/components/ui/select"; |
|
|
import { Separator } from "@/components/ui/separator"; |
|
|
import { |
|
|
Sheet, |
|
|
SheetContent, |
|
|
SheetDescription, |
|
|
SheetHeader, |
|
|
SheetTitle, |
|
|
SheetTrigger, |
|
|
} from "@/components/ui/sheet"; |
|
|
import { Skeleton } from "@/components/ui/skeleton"; |
|
|
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 { Toggle } from "@/components/ui/toggle"; |
|
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; |
|
|
import { |
|
|
Tooltip, |
|
|
TooltipContent, |
|
|
TooltipTrigger, |
|
|
} from "@/components/ui/tooltip"; |
|
|
import { useTheme } from "@/contexts/ThemeContext"; |
|
|
import { format } from "date-fns"; |
|
|
import { zhCN } from "date-fns/locale"; |
|
|
import { |
|
|
AlertCircle, |
|
|
CalendarIcon, |
|
|
Check, |
|
|
Clock, |
|
|
Moon, |
|
|
Sun, |
|
|
X, |
|
|
} from "lucide-react"; |
|
|
import { useState } from "react"; |
|
|
import { toast as sonnerToast } from "sonner"; |
|
|
import { AIChatBox, type Message } from "@/components/AIChatBox"; |
|
|
|
|
|
export default function ComponentsShowcase() { |
|
|
const { theme, toggleTheme } = useTheme(); |
|
|
const [date, setDate] = useState<Date | undefined>(new Date()); |
|
|
const [datePickerDate, setDatePickerDate] = useState<Date>(); |
|
|
const [selectedFruits, setSelectedFruits] = useState<string[]>([]); |
|
|
const [progress, setProgress] = useState(33); |
|
|
const [currentPage, setCurrentPage] = useState(2); |
|
|
const [openCombobox, setOpenCombobox] = useState(false); |
|
|
const [selectedFramework, setSelectedFramework] = useState(""); |
|
|
const [selectedMonth, setSelectedMonth] = useState(""); |
|
|
const [selectedYear, setSelectedYear] = useState(""); |
|
|
const [dialogInput, setDialogInput] = useState(""); |
|
|
const [dialogOpen, setDialogOpen] = useState(false); |
|
|
|
|
|
|
|
|
const [chatMessages, setChatMessages] = useState<Message[]>([ |
|
|
{ role: "system", content: "You are a helpful assistant." }, |
|
|
]); |
|
|
const [isChatLoading, setIsChatLoading] = useState(false); |
|
|
|
|
|
const handleDialogSubmit = () => { |
|
|
console.log("Dialog submitted with value:", dialogInput); |
|
|
sonnerToast.success("Submitted successfully", { |
|
|
description: `Input: ${dialogInput}`, |
|
|
}); |
|
|
setDialogInput(""); |
|
|
setDialogOpen(false); |
|
|
}; |
|
|
|
|
|
const handleDialogKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { |
|
|
if (e.key === "Enter" && !e.nativeEvent.isComposing) { |
|
|
e.preventDefault(); |
|
|
handleDialogSubmit(); |
|
|
} |
|
|
}; |
|
|
|
|
|
const handleChatSend = (content: string) => { |
|
|
|
|
|
const newMessages: Message[] = [...chatMessages, { role: "user", content }]; |
|
|
setChatMessages(newMessages); |
|
|
|
|
|
|
|
|
setIsChatLoading(true); |
|
|
setTimeout(() => { |
|
|
const aiResponse: Message = { |
|
|
role: "assistant", |
|
|
content: `This is a **demo response**. In a real app, you would call a tRPC mutation here:\n\n\`\`\`typescript\nconst chatMutation = trpc.ai.chat.useMutation({\n onSuccess: (response) => {\n setChatMessages(prev => [...prev, {\n role: "assistant",\n content: response.choices[0].message.content\n }]);\n }\n});\n\nchatMutation.mutate({ messages: newMessages });\n\`\`\`\n\nYour message was: "${content}"`, |
|
|
}; |
|
|
setChatMessages([...newMessages, aiResponse]); |
|
|
setIsChatLoading(false); |
|
|
}, 1500); |
|
|
}; |
|
|
|
|
|
return ( |
|
|
<div className="min-h-screen bg-background text-foreground"> |
|
|
<main className="container max-w-6xl mx-auto"> |
|
|
<div className="space-y-2 justify-between flex"> |
|
|
<h2 className="text-3xl font-bold tracking-tight mb-6"> |
|
|
Shadcn/ui Component Library |
|
|
</h2> |
|
|
<Button variant="outline" size="icon" onClick={toggleTheme}> |
|
|
{theme === "light" ? ( |
|
|
<Moon className="h-5 w-5" /> |
|
|
) : ( |
|
|
<Sun className="h-5 w-5" /> |
|
|
)} |
|
|
</Button> |
|
|
</div> |
|
|
|
|
|
<div className="space-y-12"> |
|
|
{/* Text Colors Section */} |
|
|
<section className="space-y-4"> |
|
|
<h3 className="text-2xl font-semibold">Text Colors</h3> |
|
|
<Card> |
|
|
<CardContent className="pt-6"> |
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> |
|
|
<div className="space-y-3"> |
|
|
<div> |
|
|
<p className="text-sm text-muted-foreground mb-1"> |
|
|
Foreground (Default) |
|
|
</p> |
|
|
<p className="text-foreground text-lg"> |
|
|
Default text color for main content |
|
|
</p> |
|
|
</div> |
|
|
<div> |
|
|
<p className="text-sm text-muted-foreground mb-1"> |
|
|
Muted Foreground |
|
|
</p> |
|
|
<p className="text-muted-foreground text-lg"> |
|
|
Muted text for secondary information |
|
|
</p> |
|
|
</div> |
|
|
<div> |
|
|
<p className="text-sm text-muted-foreground mb-1"> |
|
|
Primary |
|
|
</p> |
|
|
<p className="text-primary text-lg font-medium"> |
|
|
Primary brand color text |
|
|
</p> |
|
|
</div> |
|
|
<div> |
|
|
<p className="text-sm text-muted-foreground mb-1"> |
|
|
Secondary Foreground |
|
|
</p> |
|
|
<p className="text-secondary-foreground text-lg"> |
|
|
Secondary action text color |
|
|
</p> |
|
|
</div> |
|
|
</div> |
|
|
<div className="space-y-3"> |
|
|
<div> |
|
|
<p className="text-sm text-muted-foreground mb-1"> |
|
|
Accent Foreground |
|
|
</p> |
|
|
<p className="text-accent-foreground text-lg"> |
|
|
Accent text for emphasis |
|
|
</p> |
|
|
</div> |
|
|
<div> |
|
|
<p className="text-sm text-muted-foreground mb-1"> |
|
|
Destructive |
|
|
</p> |
|
|
<p className="text-destructive text-lg font-medium"> |
|
|
Error or destructive action text |
|
|
</p> |
|
|
</div> |
|
|
<div> |
|
|
<p className="text-sm text-muted-foreground mb-1"> |
|
|
Card Foreground |
|
|
</p> |
|
|
<p className="text-card-foreground text-lg"> |
|
|
Text color on card backgrounds |
|
|
</p> |
|
|
</div> |
|
|
<div> |
|
|
<p className="text-sm text-muted-foreground mb-1"> |
|
|
Popover Foreground |
|
|
</p> |
|
|
<p className="text-popover-foreground text-lg"> |
|
|
Text color in popovers |
|
|
</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</section> |
|
|
|
|
|
{/* Color Combinations Section */} |
|
|
<section className="space-y-4"> |
|
|
<h3 className="text-2xl font-semibold">Color Combinations</h3> |
|
|
<Card> |
|
|
<CardContent className="pt-6"> |
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> |
|
|
<div className="bg-primary text-primary-foreground rounded-lg p-4"> |
|
|
<p className="font-medium mb-1">Primary</p> |
|
|
<p className="text-sm opacity-90"> |
|
|
Primary background with foreground text |
|
|
</p> |
|
|
</div> |
|
|
<div className="bg-secondary text-secondary-foreground rounded-lg p-4"> |
|
|
<p className="font-medium mb-1">Secondary</p> |
|
|
<p className="text-sm opacity-90"> |
|
|
Secondary background with foreground text |
|
|
</p> |
|
|
</div> |
|
|
<div className="bg-muted text-muted-foreground rounded-lg p-4"> |
|
|
<p className="font-medium mb-1">Muted</p> |
|
|
<p className="text-sm opacity-90"> |
|
|
Muted background with foreground text |
|
|
</p> |
|
|
</div> |
|
|
<div className="bg-accent text-accent-foreground rounded-lg p-4"> |
|
|
<p className="font-medium mb-1">Accent</p> |
|
|
<p className="text-sm opacity-90"> |
|
|
Accent background with foreground text |
|
|
</p> |
|
|
</div> |
|
|
<div className="bg-destructive text-destructive-foreground rounded-lg p-4"> |
|
|
<p className="font-medium mb-1">Destructive</p> |
|
|
<p className="text-sm opacity-90"> |
|
|
Destructive background with foreground text |
|
|
</p> |
|
|
</div> |
|
|
<div className="bg-card text-card-foreground rounded-lg p-4 border"> |
|
|
<p className="font-medium mb-1">Card</p> |
|
|
<p className="text-sm opacity-90"> |
|
|
Card background with foreground text |
|
|
</p> |
|
|
</div> |
|
|
<div className="bg-popover text-popover-foreground rounded-lg p-4 border"> |
|
|
<p className="font-medium mb-1">Popover</p> |
|
|
<p className="text-sm opacity-90"> |
|
|
Popover background with foreground text |
|
|
</p> |
|
|
</div> |
|
|
<div className="bg-background text-foreground rounded-lg p-4 border"> |
|
|
<p className="font-medium mb-1">Background</p> |
|
|
<p className="text-sm opacity-90"> |
|
|
Default background with foreground text |
|
|
</p> |
|
|
</div> |
|
|
</div> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</section> |
|
|
|
|
|
{/* Buttons Section */} |
|
|
<section className="space-y-4"> |
|
|
<h3 className="text-2xl font-semibold">Buttons</h3> |
|
|
<Card> |
|
|
<CardContent className="pt-6"> |
|
|
<div className="flex flex-wrap gap-4"> |
|
|
<Button>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> |
|
|
<Button size="sm">Small</Button> |
|
|
<Button size="lg">Large</Button> |
|
|
<Button size="icon"> |
|
|
<Check className="h-4 w-4" /> |
|
|
</Button> |
|
|
</div> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</section> |
|
|
|
|
|
{/* Form Inputs Section */} |
|
|
<section className="space-y-4"> |
|
|
<h3 className="text-2xl font-semibold">Form Inputs</h3> |
|
|
<Card> |
|
|
<CardContent className="pt-6 space-y-6"> |
|
|
<div className="space-y-2"> |
|
|
<Label htmlFor="email">Email</Label> |
|
|
<Input id="email" type="email" placeholder="Email" /> |
|
|
</div> |
|
|
<div className="space-y-2"> |
|
|
<Label htmlFor="message">Message</Label> |
|
|
<Textarea |
|
|
id="message" |
|
|
placeholder="Type your message here." |
|
|
/> |
|
|
</div> |
|
|
<div className="space-y-2"> |
|
|
<Label>Select</Label> |
|
|
<Select> |
|
|
<SelectTrigger> |
|
|
<SelectValue placeholder="Select a fruit" /> |
|
|
</SelectTrigger> |
|
|
<SelectContent> |
|
|
<SelectItem value="apple">Apple</SelectItem> |
|
|
<SelectItem value="banana">Banana</SelectItem> |
|
|
<SelectItem value="orange">Orange</SelectItem> |
|
|
</SelectContent> |
|
|
</Select> |
|
|
</div> |
|
|
<div className="flex items-center space-x-2"> |
|
|
<Checkbox id="terms" /> |
|
|
<Label htmlFor="terms">Accept terms and conditions</Label> |
|
|
</div> |
|
|
<div className="flex items-center space-x-2"> |
|
|
<Switch id="airplane-mode" /> |
|
|
<Label htmlFor="airplane-mode">Airplane Mode</Label> |
|
|
</div> |
|
|
<div className="space-y-2"> |
|
|
<Label>Radio Group</Label> |
|
|
<RadioGroup defaultValue="option-one"> |
|
|
<div className="flex items-center space-x-2"> |
|
|
<RadioGroupItem value="option-one" id="option-one" /> |
|
|
<Label htmlFor="option-one">Option One</Label> |
|
|
</div> |
|
|
<div className="flex items-center space-x-2"> |
|
|
<RadioGroupItem value="option-two" id="option-two" /> |
|
|
<Label htmlFor="option-two">Option Two</Label> |
|
|
</div> |
|
|
</RadioGroup> |
|
|
</div> |
|
|
<div className="space-y-2"> |
|
|
<Label>Slider</Label> |
|
|
<Slider defaultValue={[50]} max={100} step={1} /> |
|
|
</div> |
|
|
<div className="space-y-2"> |
|
|
<Label>Input OTP</Label> |
|
|
<InputOTP maxLength={6}> |
|
|
<InputOTPGroup> |
|
|
<InputOTPSlot index={0} /> |
|
|
<InputOTPSlot index={1} /> |
|
|
<InputOTPSlot index={2} /> |
|
|
<InputOTPSlot index={3} /> |
|
|
<InputOTPSlot index={4} /> |
|
|
<InputOTPSlot index={5} /> |
|
|
</InputOTPGroup> |
|
|
</InputOTP> |
|
|
</div> |
|
|
<div className="space-y-2"> |
|
|
<Label>Date Time Picker</Label> |
|
|
<Popover> |
|
|
<PopoverTrigger asChild> |
|
|
<Button |
|
|
variant="outline" |
|
|
className={`w-full justify-start text-left font-normal ${ |
|
|
!datePickerDate && "text-muted-foreground" |
|
|
}`} |
|
|
> |
|
|
<CalendarIcon className="mr-2 h-4 w-4" /> |
|
|
{datePickerDate ? ( |
|
|
format(datePickerDate, "PPP HH:mm", { locale: zhCN }) |
|
|
) : ( |
|
|
<span>Select date and time</span> |
|
|
)} |
|
|
</Button> |
|
|
</PopoverTrigger> |
|
|
<PopoverContent className="w-auto p-0" align="start"> |
|
|
<div className="p-3 space-y-3"> |
|
|
<Calendar |
|
|
mode="single" |
|
|
selected={datePickerDate} |
|
|
onSelect={setDatePickerDate} |
|
|
/> |
|
|
<div className="border-t pt-3 space-y-2"> |
|
|
<Label className="flex items-center gap-2"> |
|
|
<Clock className="h-4 w-4" /> |
|
|
Time |
|
|
</Label> |
|
|
<div className="flex gap-2"> |
|
|
<Input |
|
|
type="time" |
|
|
value={ |
|
|
datePickerDate |
|
|
? format(datePickerDate, "HH:mm") |
|
|
: "00:00" |
|
|
} |
|
|
onChange={e => { |
|
|
const [hours, minutes] = |
|
|
e.target.value.split(":"); |
|
|
const newDate = datePickerDate |
|
|
? new Date(datePickerDate) |
|
|
: new Date(); |
|
|
newDate.setHours(parseInt(hours)); |
|
|
newDate.setMinutes(parseInt(minutes)); |
|
|
setDatePickerDate(newDate); |
|
|
}} |
|
|
/> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</PopoverContent> |
|
|
</Popover> |
|
|
{datePickerDate && ( |
|
|
<p className="text-sm text-muted-foreground"> |
|
|
Selected:{" "} |
|
|
{format(datePickerDate, "yyyy/MM/dd HH:mm", { |
|
|
locale: zhCN, |
|
|
})} |
|
|
</p> |
|
|
)} |
|
|
</div> |
|
|
<div className="space-y-2"> |
|
|
<Label>Searchable Dropdown</Label> |
|
|
<Popover open={openCombobox} onOpenChange={setOpenCombobox}> |
|
|
<PopoverTrigger asChild> |
|
|
<Button |
|
|
variant="outline" |
|
|
role="combobox" |
|
|
aria-expanded={openCombobox} |
|
|
className="w-full justify-between" |
|
|
> |
|
|
{selectedFramework |
|
|
? [ |
|
|
{ value: "react", label: "React" }, |
|
|
{ value: "vue", label: "Vue" }, |
|
|
{ value: "angular", label: "Angular" }, |
|
|
{ value: "svelte", label: "Svelte" }, |
|
|
{ value: "nextjs", label: "Next.js" }, |
|
|
{ value: "nuxt", label: "Nuxt" }, |
|
|
{ value: "remix", label: "Remix" }, |
|
|
].find(fw => fw.value === selectedFramework)?.label |
|
|
: "Select framework..."} |
|
|
<CalendarIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" /> |
|
|
</Button> |
|
|
</PopoverTrigger> |
|
|
<PopoverContent className="w-full p-0"> |
|
|
<Command> |
|
|
<CommandInput placeholder="Search frameworks..." /> |
|
|
<CommandList> |
|
|
<CommandEmpty>No framework found</CommandEmpty> |
|
|
<CommandGroup> |
|
|
{[ |
|
|
{ value: "react", label: "React" }, |
|
|
{ value: "vue", label: "Vue" }, |
|
|
{ value: "angular", label: "Angular" }, |
|
|
{ value: "svelte", label: "Svelte" }, |
|
|
{ value: "nextjs", label: "Next.js" }, |
|
|
{ value: "nuxt", label: "Nuxt" }, |
|
|
{ value: "remix", label: "Remix" }, |
|
|
].map(framework => ( |
|
|
<CommandItem |
|
|
key={framework.value} |
|
|
value={framework.value} |
|
|
onSelect={currentValue => { |
|
|
setSelectedFramework( |
|
|
currentValue === selectedFramework |
|
|
? "" |
|
|
: currentValue |
|
|
); |
|
|
setOpenCombobox(false); |
|
|
}} |
|
|
> |
|
|
<Check |
|
|
className={`mr-2 h-4 w-4 ${ |
|
|
selectedFramework === framework.value |
|
|
? "opacity-100" |
|
|
: "opacity-0" |
|
|
}`} |
|
|
/> |
|
|
{framework.label} |
|
|
</CommandItem> |
|
|
))} |
|
|
</CommandGroup> |
|
|
</CommandList> |
|
|
</Command> |
|
|
</PopoverContent> |
|
|
</Popover> |
|
|
{selectedFramework && ( |
|
|
<p className="text-sm text-muted-foreground"> |
|
|
Selected:{" "} |
|
|
{ |
|
|
[ |
|
|
{ value: "react", label: "React" }, |
|
|
{ value: "vue", label: "Vue" }, |
|
|
{ value: "angular", label: "Angular" }, |
|
|
{ value: "svelte", label: "Svelte" }, |
|
|
{ value: "nextjs", label: "Next.js" }, |
|
|
{ value: "nuxt", label: "Nuxt" }, |
|
|
{ value: "remix", label: "Remix" }, |
|
|
].find(fw => fw.value === selectedFramework)?.label |
|
|
} |
|
|
</p> |
|
|
)} |
|
|
</div> |
|
|
<div className="space-y-2"> |
|
|
<div className="grid grid-cols-2 gap-4"> |
|
|
<div className="space-y-2"> |
|
|
<Label htmlFor="month" className="text-sm font-medium"> |
|
|
Month |
|
|
</Label> |
|
|
<Select |
|
|
value={selectedMonth} |
|
|
onValueChange={setSelectedMonth} |
|
|
> |
|
|
<SelectTrigger id="month"> |
|
|
<SelectValue placeholder="MM" /> |
|
|
</SelectTrigger> |
|
|
<SelectContent> |
|
|
{Array.from({ length: 12 }, (_, i) => i + 1).map( |
|
|
month => ( |
|
|
<SelectItem |
|
|
key={month} |
|
|
value={month.toString().padStart(2, "0")} |
|
|
> |
|
|
{month.toString().padStart(2, "0")} |
|
|
</SelectItem> |
|
|
) |
|
|
)} |
|
|
</SelectContent> |
|
|
</Select> |
|
|
</div> |
|
|
<div className="space-y-2"> |
|
|
<Label htmlFor="year" className="text-sm font-medium"> |
|
|
Year |
|
|
</Label> |
|
|
<Select |
|
|
value={selectedYear} |
|
|
onValueChange={setSelectedYear} |
|
|
> |
|
|
<SelectTrigger id="year"> |
|
|
<SelectValue placeholder="YYYY" /> |
|
|
</SelectTrigger> |
|
|
<SelectContent> |
|
|
{Array.from( |
|
|
{ length: 10 }, |
|
|
(_, i) => new Date().getFullYear() - 5 + i |
|
|
).map(year => ( |
|
|
<SelectItem key={year} value={year.toString()}> |
|
|
{year} |
|
|
</SelectItem> |
|
|
))} |
|
|
</SelectContent> |
|
|
</Select> |
|
|
</div> |
|
|
</div> |
|
|
{selectedMonth && selectedYear && ( |
|
|
<p className="text-sm text-muted-foreground"> |
|
|
Selected: {selectedYear}/{selectedMonth}/ |
|
|
</p> |
|
|
)} |
|
|
</div> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</section> |
|
|
|
|
|
{/* Data Display Section */} |
|
|
<section className="space-y-4"> |
|
|
<h3 className="text-2xl font-semibold">Data Display</h3> |
|
|
<Card> |
|
|
<CardContent className="pt-6 space-y-6"> |
|
|
<div className="space-y-2"> |
|
|
<Label>Badges</Label> |
|
|
<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> |
|
|
</div> |
|
|
<Separator /> |
|
|
<div className="space-y-2"> |
|
|
<Label>Avatar</Label> |
|
|
<div className="flex gap-4"> |
|
|
<Avatar> |
|
|
<AvatarImage src="https://github.com/shadcn.png" /> |
|
|
<AvatarFallback>CN</AvatarFallback> |
|
|
</Avatar> |
|
|
<Avatar> |
|
|
<AvatarFallback>AB</AvatarFallback> |
|
|
</Avatar> |
|
|
</div> |
|
|
</div> |
|
|
<Separator /> |
|
|
<div className="space-y-2"> |
|
|
<Label>Progress</Label> |
|
|
<Progress value={progress} /> |
|
|
<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> |
|
|
</div> |
|
|
<Separator /> |
|
|
<div className="space-y-2"> |
|
|
<Label>Skeleton</Label> |
|
|
<div className="space-y-2"> |
|
|
<Skeleton className="h-4 w-full" /> |
|
|
<Skeleton className="h-4 w-3/4" /> |
|
|
<Skeleton className="h-4 w-1/2" /> |
|
|
</div> |
|
|
</div> |
|
|
<Separator /> |
|
|
<div className="space-y-2"> |
|
|
<Label>Pagination</Label> |
|
|
<Pagination> |
|
|
<PaginationContent> |
|
|
<PaginationItem> |
|
|
<PaginationPrevious |
|
|
href="#" |
|
|
onClick={e => { |
|
|
e.preventDefault(); |
|
|
setCurrentPage(Math.max(1, currentPage - 1)); |
|
|
}} |
|
|
/> |
|
|
</PaginationItem> |
|
|
{[1, 2, 3, 4, 5].map(page => ( |
|
|
<PaginationItem key={page}> |
|
|
<PaginationLink |
|
|
href="#" |
|
|
isActive={currentPage === page} |
|
|
onClick={e => { |
|
|
e.preventDefault(); |
|
|
setCurrentPage(page); |
|
|
}} |
|
|
> |
|
|
{page} |
|
|
</PaginationLink> |
|
|
</PaginationItem> |
|
|
))} |
|
|
<PaginationItem> |
|
|
<PaginationNext |
|
|
href="#" |
|
|
onClick={e => { |
|
|
e.preventDefault(); |
|
|
setCurrentPage(Math.min(5, currentPage + 1)); |
|
|
}} |
|
|
/> |
|
|
</PaginationItem> |
|
|
</PaginationContent> |
|
|
</Pagination> |
|
|
<p className="text-sm text-muted-foreground text-center"> |
|
|
Current page: {currentPage} |
|
|
</p> |
|
|
</div> |
|
|
<Separator /> |
|
|
<div className="space-y-2"> |
|
|
<Label>Table</Label> |
|
|
<Table> |
|
|
<TableCaption>A list of your recent invoices.</TableCaption> |
|
|
<TableHeader> |
|
|
<TableRow> |
|
|
<TableHead className="w-[100px]">Invoice</TableHead> |
|
|
<TableHead>Status</TableHead> |
|
|
<TableHead>Method</TableHead> |
|
|
<TableHead className="text-right">Amount</TableHead> |
|
|
</TableRow> |
|
|
</TableHeader> |
|
|
<TableBody> |
|
|
<TableRow> |
|
|
<TableCell className="font-medium">INV001</TableCell> |
|
|
<TableCell>Paid</TableCell> |
|
|
<TableCell>Credit Card</TableCell> |
|
|
<TableCell className="text-right">$250.00</TableCell> |
|
|
</TableRow> |
|
|
<TableRow> |
|
|
<TableCell className="font-medium">INV002</TableCell> |
|
|
<TableCell>Pending</TableCell> |
|
|
<TableCell>PayPal</TableCell> |
|
|
<TableCell className="text-right">$150.00</TableCell> |
|
|
</TableRow> |
|
|
<TableRow> |
|
|
<TableCell className="font-medium">INV003</TableCell> |
|
|
<TableCell>Unpaid</TableCell> |
|
|
<TableCell>Bank Transfer</TableCell> |
|
|
<TableCell className="text-right">$350.00</TableCell> |
|
|
</TableRow> |
|
|
</TableBody> |
|
|
</Table> |
|
|
</div> |
|
|
<Separator /> |
|
|
<div className="space-y-2"> |
|
|
<Label>Menubar</Label> |
|
|
<Menubar> |
|
|
<MenubarMenu> |
|
|
<MenubarTrigger>File</MenubarTrigger> |
|
|
<MenubarContent> |
|
|
<MenubarItem>New Tab</MenubarItem> |
|
|
<MenubarItem>New Window</MenubarItem> |
|
|
<MenubarSeparator /> |
|
|
<MenubarItem>Share</MenubarItem> |
|
|
<MenubarSeparator /> |
|
|
<MenubarItem>Print</MenubarItem> |
|
|
</MenubarContent> |
|
|
</MenubarMenu> |
|
|
<MenubarMenu> |
|
|
<MenubarTrigger>Edit</MenubarTrigger> |
|
|
<MenubarContent> |
|
|
<MenubarItem>Undo</MenubarItem> |
|
|
<MenubarItem>Redo</MenubarItem> |
|
|
</MenubarContent> |
|
|
</MenubarMenu> |
|
|
<MenubarMenu> |
|
|
<MenubarTrigger>View</MenubarTrigger> |
|
|
<MenubarContent> |
|
|
<MenubarItem>Reload</MenubarItem> |
|
|
<MenubarItem>Force Reload</MenubarItem> |
|
|
</MenubarContent> |
|
|
</MenubarMenu> |
|
|
</Menubar> |
|
|
</div> |
|
|
<Separator /> |
|
|
<div className="space-y-2"> |
|
|
<Label>Breadcrumb</Label> |
|
|
<Breadcrumb> |
|
|
<BreadcrumbList> |
|
|
<BreadcrumbItem> |
|
|
<BreadcrumbLink href="/">Home</BreadcrumbLink> |
|
|
</BreadcrumbItem> |
|
|
<BreadcrumbSeparator /> |
|
|
<BreadcrumbItem> |
|
|
<BreadcrumbLink href="/components"> |
|
|
Components |
|
|
</BreadcrumbLink> |
|
|
</BreadcrumbItem> |
|
|
<BreadcrumbSeparator /> |
|
|
<BreadcrumbItem> |
|
|
<BreadcrumbPage>Breadcrumb</BreadcrumbPage> |
|
|
</BreadcrumbItem> |
|
|
</BreadcrumbList> |
|
|
</Breadcrumb> |
|
|
</div> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</section> |
|
|
|
|
|
{/* Alerts Section */} |
|
|
<section className="space-y-4"> |
|
|
<h3 className="text-2xl font-semibold">Alerts</h3> |
|
|
<div className="space-y-4"> |
|
|
<Alert> |
|
|
<AlertCircle className="h-4 w-4" /> |
|
|
<AlertTitle>Heads up!</AlertTitle> |
|
|
<AlertDescription> |
|
|
You can add components to your app using the cli. |
|
|
</AlertDescription> |
|
|
</Alert> |
|
|
<Alert variant="destructive"> |
|
|
<X className="h-4 w-4" /> |
|
|
<AlertTitle>Error</AlertTitle> |
|
|
<AlertDescription> |
|
|
Your session has expired. Please log in again. |
|
|
</AlertDescription> |
|
|
</Alert> |
|
|
</div> |
|
|
</section> |
|
|
|
|
|
{/* Tabs Section */} |
|
|
<section className="space-y-4"> |
|
|
<h3 className="text-2xl font-semibold">Tabs</h3> |
|
|
<Tabs defaultValue="account" className="w-full"> |
|
|
<TabsList className="grid w-full grid-cols-3"> |
|
|
<TabsTrigger value="account">Account</TabsTrigger> |
|
|
<TabsTrigger value="password">Password</TabsTrigger> |
|
|
<TabsTrigger value="settings">Settings</TabsTrigger> |
|
|
</TabsList> |
|
|
<TabsContent value="account"> |
|
|
<Card> |
|
|
<CardHeader> |
|
|
<CardTitle>Account</CardTitle> |
|
|
<CardDescription> |
|
|
Make changes to your account here. |
|
|
</CardDescription> |
|
|
</CardHeader> |
|
|
<CardContent className="space-y-2"> |
|
|
<div className="space-y-1"> |
|
|
<Label htmlFor="name">Name</Label> |
|
|
<Input id="name" defaultValue="Pedro Duarte" /> |
|
|
</div> |
|
|
</CardContent> |
|
|
<CardFooter> |
|
|
<Button>Save changes</Button> |
|
|
</CardFooter> |
|
|
</Card> |
|
|
</TabsContent> |
|
|
<TabsContent value="password"> |
|
|
<Card> |
|
|
<CardHeader> |
|
|
<CardTitle>Password</CardTitle> |
|
|
<CardDescription> |
|
|
Change your password here. |
|
|
</CardDescription> |
|
|
</CardHeader> |
|
|
<CardContent className="space-y-2"> |
|
|
<div className="space-y-1"> |
|
|
<Label htmlFor="current">Current password</Label> |
|
|
<Input id="current" type="password" /> |
|
|
</div> |
|
|
<div className="space-y-1"> |
|
|
<Label htmlFor="new">New password</Label> |
|
|
<Input id="new" type="password" /> |
|
|
</div> |
|
|
</CardContent> |
|
|
<CardFooter> |
|
|
<Button>Save password</Button> |
|
|
</CardFooter> |
|
|
</Card> |
|
|
</TabsContent> |
|
|
<TabsContent value="settings"> |
|
|
<Card> |
|
|
<CardHeader> |
|
|
<CardTitle>Settings</CardTitle> |
|
|
<CardDescription> |
|
|
Manage your settings here. |
|
|
</CardDescription> |
|
|
</CardHeader> |
|
|
<CardContent> |
|
|
<p className="text-sm text-muted-foreground"> |
|
|
Settings content goes here. |
|
|
</p> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</TabsContent> |
|
|
</Tabs> |
|
|
</section> |
|
|
|
|
|
{/* Accordion Section */} |
|
|
<section className="space-y-4"> |
|
|
<h3 className="text-2xl font-semibold">Accordion</h3> |
|
|
<Accordion type="single" collapsible className="w-full"> |
|
|
<AccordionItem value="item-1"> |
|
|
<AccordionTrigger>Is it accessible?</AccordionTrigger> |
|
|
<AccordionContent> |
|
|
Yes. It adheres to the WAI-ARIA design pattern. |
|
|
</AccordionContent> |
|
|
</AccordionItem> |
|
|
<AccordionItem value="item-2"> |
|
|
<AccordionTrigger>Is it styled?</AccordionTrigger> |
|
|
<AccordionContent> |
|
|
Yes. It comes with default styles that matches the other |
|
|
components' aesthetic. |
|
|
</AccordionContent> |
|
|
</AccordionItem> |
|
|
<AccordionItem value="item-3"> |
|
|
<AccordionTrigger>Is it animated?</AccordionTrigger> |
|
|
<AccordionContent> |
|
|
Yes. It's animated by default, but you can disable it if you |
|
|
prefer. |
|
|
</AccordionContent> |
|
|
</AccordionItem> |
|
|
</Accordion> |
|
|
</section> |
|
|
|
|
|
{/* Collapsible Section */} |
|
|
<section className="space-y-4"> |
|
|
<h3 className="text-2xl font-semibold">Collapsible</h3> |
|
|
<Collapsible> |
|
|
<Card> |
|
|
<CardHeader> |
|
|
<CollapsibleTrigger asChild> |
|
|
<Button variant="ghost" className="w-full justify-between"> |
|
|
<CardTitle>@peduarte starred 3 repositories</CardTitle> |
|
|
</Button> |
|
|
</CollapsibleTrigger> |
|
|
</CardHeader> |
|
|
<CollapsibleContent> |
|
|
<CardContent> |
|
|
<div className="space-y-2"> |
|
|
<div className="rounded-md border px-4 py-3 font-mono text-sm"> |
|
|
@radix-ui/primitives |
|
|
</div> |
|
|
<div className="rounded-md border px-4 py-3 font-mono text-sm"> |
|
|
@radix-ui/colors |
|
|
</div> |
|
|
<div className="rounded-md border px-4 py-3 font-mono text-sm"> |
|
|
@stitches/react |
|
|
</div> |
|
|
</div> |
|
|
</CardContent> |
|
|
</CollapsibleContent> |
|
|
</Card> |
|
|
</Collapsible> |
|
|
</section> |
|
|
|
|
|
{/* Dialog, Sheet, Drawer Section */} |
|
|
<section className="space-y-4"> |
|
|
<h3 className="text-2xl font-semibold">Overlays</h3> |
|
|
<Card> |
|
|
<CardContent className="pt-6"> |
|
|
<div className="flex flex-wrap gap-4"> |
|
|
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}> |
|
|
<DialogTrigger asChild> |
|
|
<Button variant="outline">Open Dialog</Button> |
|
|
</DialogTrigger> |
|
|
<DialogContent> |
|
|
<DialogHeader> |
|
|
<DialogTitle>Test Input</DialogTitle> |
|
|
<DialogDescription> |
|
|
Enter some text below. Press Enter to submit (IME composition supported). |
|
|
</DialogDescription> |
|
|
</DialogHeader> |
|
|
<div className="space-y-4 py-4"> |
|
|
<div className="space-y-2"> |
|
|
<Label htmlFor="dialog-input">Input</Label> |
|
|
<Input |
|
|
id="dialog-input" |
|
|
placeholder="Type something..." |
|
|
value={dialogInput} |
|
|
onChange={(e) => setDialogInput(e.target.value)} |
|
|
onKeyDown={handleDialogKeyDown} |
|
|
autoFocus |
|
|
/> |
|
|
</div> |
|
|
</div> |
|
|
<div className="flex justify-end gap-2"> |
|
|
<Button |
|
|
variant="outline" |
|
|
onClick={() => setDialogOpen(false)} |
|
|
> |
|
|
Cancel |
|
|
</Button> |
|
|
<Button onClick={handleDialogSubmit}>Submit</Button> |
|
|
</div> |
|
|
</DialogContent> |
|
|
</Dialog> |
|
|
|
|
|
<Sheet> |
|
|
<SheetTrigger asChild> |
|
|
<Button variant="outline">Open Sheet</Button> |
|
|
</SheetTrigger> |
|
|
<SheetContent> |
|
|
<SheetHeader> |
|
|
<SheetTitle>Edit profile</SheetTitle> |
|
|
<SheetDescription> |
|
|
Make changes to your profile here. Click save when |
|
|
you're done. |
|
|
</SheetDescription> |
|
|
</SheetHeader> |
|
|
</SheetContent> |
|
|
</Sheet> |
|
|
|
|
|
<Drawer> |
|
|
<DrawerTrigger asChild> |
|
|
<Button variant="outline">Open Drawer</Button> |
|
|
</DrawerTrigger> |
|
|
<DrawerContent> |
|
|
<DrawerHeader> |
|
|
<DrawerTitle>Are you absolutely sure?</DrawerTitle> |
|
|
<DrawerDescription> |
|
|
This action cannot be undone. |
|
|
</DrawerDescription> |
|
|
</DrawerHeader> |
|
|
<DrawerFooter> |
|
|
<Button>Submit</Button> |
|
|
<DrawerClose asChild> |
|
|
<Button variant="outline">Cancel</Button> |
|
|
</DrawerClose> |
|
|
</DrawerFooter> |
|
|
</DrawerContent> |
|
|
</Drawer> |
|
|
|
|
|
<Popover> |
|
|
<PopoverTrigger asChild> |
|
|
<Button variant="outline">Open Popover</Button> |
|
|
</PopoverTrigger> |
|
|
<PopoverContent> |
|
|
<div className="space-y-2"> |
|
|
<h4 className="font-medium leading-none">Dimensions</h4> |
|
|
<p className="text-sm text-muted-foreground"> |
|
|
Set the dimensions for the layer. |
|
|
</p> |
|
|
</div> |
|
|
</PopoverContent> |
|
|
</Popover> |
|
|
|
|
|
<Tooltip> |
|
|
<TooltipTrigger asChild> |
|
|
<Button variant="outline">Hover me</Button> |
|
|
</TooltipTrigger> |
|
|
<TooltipContent> |
|
|
<p>Add to library</p> |
|
|
</TooltipContent> |
|
|
</Tooltip> |
|
|
</div> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</section> |
|
|
|
|
|
{/* Menus Section */} |
|
|
<section className="space-y-4"> |
|
|
<h3 className="text-2xl font-semibold">Menus</h3> |
|
|
<Card> |
|
|
<CardContent className="pt-6"> |
|
|
<div className="flex flex-wrap gap-4"> |
|
|
<DropdownMenu> |
|
|
<DropdownMenuTrigger asChild> |
|
|
<Button variant="outline">Dropdown Menu</Button> |
|
|
</DropdownMenuTrigger> |
|
|
<DropdownMenuContent> |
|
|
<DropdownMenuLabel>My Account</DropdownMenuLabel> |
|
|
<DropdownMenuSeparator /> |
|
|
<DropdownMenuItem>Profile</DropdownMenuItem> |
|
|
<DropdownMenuItem>Billing</DropdownMenuItem> |
|
|
<DropdownMenuItem>Team</DropdownMenuItem> |
|
|
<DropdownMenuItem>Subscription</DropdownMenuItem> |
|
|
</DropdownMenuContent> |
|
|
</DropdownMenu> |
|
|
|
|
|
<ContextMenu> |
|
|
<ContextMenuTrigger asChild> |
|
|
<Button variant="outline">Right Click Me</Button> |
|
|
</ContextMenuTrigger> |
|
|
<ContextMenuContent> |
|
|
<ContextMenuItem>Profile</ContextMenuItem> |
|
|
<ContextMenuItem>Billing</ContextMenuItem> |
|
|
<ContextMenuItem>Team</ContextMenuItem> |
|
|
<ContextMenuItem>Subscription</ContextMenuItem> |
|
|
</ContextMenuContent> |
|
|
</ContextMenu> |
|
|
|
|
|
<HoverCard> |
|
|
<HoverCardTrigger asChild> |
|
|
<Button variant="outline">Hover Card</Button> |
|
|
</HoverCardTrigger> |
|
|
<HoverCardContent> |
|
|
<div className="space-y-2"> |
|
|
<h4 className="text-sm font-semibold">@nextjs</h4> |
|
|
<p className="text-sm"> |
|
|
The React Framework – created and maintained by |
|
|
@vercel. |
|
|
</p> |
|
|
</div> |
|
|
</HoverCardContent> |
|
|
</HoverCard> |
|
|
</div> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</section> |
|
|
|
|
|
{/* Calendar Section */} |
|
|
<section className="space-y-4"> |
|
|
<h3 className="text-2xl font-semibold">Calendar</h3> |
|
|
<Card> |
|
|
<CardContent className="pt-6 flex justify-center"> |
|
|
<Calendar |
|
|
mode="single" |
|
|
selected={date} |
|
|
onSelect={setDate} |
|
|
className="rounded-md border" |
|
|
/> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</section> |
|
|
|
|
|
{/* Carousel Section */} |
|
|
<section className="space-y-4"> |
|
|
<h3 className="text-2xl font-semibold">Carousel</h3> |
|
|
<Card> |
|
|
<CardContent className="pt-6"> |
|
|
<Carousel className="w-full max-w-xs mx-auto"> |
|
|
<CarouselContent> |
|
|
{Array.from({ length: 5 }).map((_, index) => ( |
|
|
<CarouselItem key={index}> |
|
|
<div className="p-1"> |
|
|
<Card> |
|
|
<CardContent className="flex aspect-square items-center justify-center p-6"> |
|
|
<span className="text-4xl font-semibold"> |
|
|
{index + 1} |
|
|
</span> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</div> |
|
|
</CarouselItem> |
|
|
))} |
|
|
</CarouselContent> |
|
|
<CarouselPrevious /> |
|
|
<CarouselNext /> |
|
|
</Carousel> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</section> |
|
|
|
|
|
{/* Toggle Section */} |
|
|
<section className="space-y-4"> |
|
|
<h3 className="text-2xl font-semibold">Toggle</h3> |
|
|
<Card> |
|
|
<CardContent className="pt-6 space-y-4"> |
|
|
<div className="space-y-2"> |
|
|
<Label>Toggle</Label> |
|
|
<div className="flex gap-2"> |
|
|
<Toggle aria-label="Toggle italic"> |
|
|
<span className="font-bold">B</span> |
|
|
</Toggle> |
|
|
<Toggle aria-label="Toggle italic"> |
|
|
<span className="italic">I</span> |
|
|
</Toggle> |
|
|
<Toggle aria-label="Toggle underline"> |
|
|
<span className="underline">U</span> |
|
|
</Toggle> |
|
|
</div> |
|
|
</div> |
|
|
<Separator /> |
|
|
<div className="space-y-2"> |
|
|
<Label>Toggle Group</Label> |
|
|
<ToggleGroup type="multiple"> |
|
|
<ToggleGroupItem value="bold" aria-label="Toggle bold"> |
|
|
<span className="font-bold">B</span> |
|
|
</ToggleGroupItem> |
|
|
<ToggleGroupItem value="italic" aria-label="Toggle italic"> |
|
|
<span className="italic">I</span> |
|
|
</ToggleGroupItem> |
|
|
<ToggleGroupItem |
|
|
value="underline" |
|
|
aria-label="Toggle underline" |
|
|
> |
|
|
<span className="underline">U</span> |
|
|
</ToggleGroupItem> |
|
|
</ToggleGroup> |
|
|
</div> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</section> |
|
|
|
|
|
{/* Aspect Ratio & Scroll Area Section */} |
|
|
<section className="space-y-4"> |
|
|
<h3 className="text-2xl font-semibold">Layout Components</h3> |
|
|
<Card> |
|
|
<CardContent className="pt-6 space-y-6"> |
|
|
<div className="space-y-2"> |
|
|
<Label>Aspect Ratio (16/9)</Label> |
|
|
<AspectRatio ratio={16 / 9} className="bg-muted"> |
|
|
<div className="flex h-full items-center justify-center"> |
|
|
<p className="text-muted-foreground">16:9 Aspect Ratio</p> |
|
|
</div> |
|
|
</AspectRatio> |
|
|
</div> |
|
|
<Separator /> |
|
|
<div className="space-y-2"> |
|
|
<Label>Scroll Area</Label> |
|
|
<ScrollArea className="h-[200px] w-full rounded-md border overflow-hidden"> |
|
|
<div className="p-4"> |
|
|
<div className="space-y-4"> |
|
|
{Array.from({ length: 20 }).map((_, i) => ( |
|
|
<div key={i} className="text-sm"> |
|
|
Item {i + 1}: This is a scrollable content area |
|
|
</div> |
|
|
))} |
|
|
</div> |
|
|
</div> |
|
|
</ScrollArea> |
|
|
</div> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</section> |
|
|
|
|
|
{/* Resizable Section */} |
|
|
<section className="space-y-4"> |
|
|
<h3 className="text-2xl font-semibold">Resizable Panels</h3> |
|
|
<Card> |
|
|
<CardContent className="pt-6"> |
|
|
<ResizablePanelGroup |
|
|
direction="horizontal" |
|
|
className="min-h-[200px] rounded-lg border" |
|
|
> |
|
|
<ResizablePanel defaultSize={50}> |
|
|
<div className="flex h-full items-center justify-center p-6"> |
|
|
<span className="font-semibold">Panel One</span> |
|
|
</div> |
|
|
</ResizablePanel> |
|
|
<ResizableHandle /> |
|
|
<ResizablePanel defaultSize={50}> |
|
|
<div className="flex h-full items-center justify-center p-6"> |
|
|
<span className="font-semibold">Panel Two</span> |
|
|
</div> |
|
|
</ResizablePanel> |
|
|
</ResizablePanelGroup> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</section> |
|
|
|
|
|
{/* Toast Section */} |
|
|
<section className="space-y-4"> |
|
|
<h3 className="text-2xl font-semibold">Toast</h3> |
|
|
<Card> |
|
|
<CardContent className="pt-6 space-y-4"> |
|
|
<div className="space-y-2"> |
|
|
<Label>Sonner Toast</Label> |
|
|
<div className="flex flex-wrap gap-2"> |
|
|
<Button |
|
|
variant="outline" |
|
|
onClick={() => { |
|
|
sonnerToast.success("Operation successful", { |
|
|
description: "Your changes have been saved", |
|
|
}); |
|
|
}} |
|
|
> |
|
|
Success |
|
|
</Button> |
|
|
<Button |
|
|
variant="outline" |
|
|
onClick={() => { |
|
|
sonnerToast.error("Operation failed", { |
|
|
description: |
|
|
"Cannot complete operation, please try again", |
|
|
}); |
|
|
}} |
|
|
> |
|
|
Error |
|
|
</Button> |
|
|
<Button |
|
|
variant="outline" |
|
|
onClick={() => { |
|
|
sonnerToast.info("Information", { |
|
|
description: "This is an information message", |
|
|
}); |
|
|
}} |
|
|
> |
|
|
Info |
|
|
</Button> |
|
|
<Button |
|
|
variant="outline" |
|
|
onClick={() => { |
|
|
sonnerToast.warning("Warning", { |
|
|
description: |
|
|
"Please note the impact of this operation", |
|
|
}); |
|
|
}} |
|
|
> |
|
|
Warning |
|
|
</Button> |
|
|
<Button |
|
|
variant="outline" |
|
|
onClick={() => { |
|
|
sonnerToast.loading("Loading", { |
|
|
description: "Please wait", |
|
|
}); |
|
|
}} |
|
|
> |
|
|
Loading |
|
|
</Button> |
|
|
<Button |
|
|
variant="outline" |
|
|
onClick={() => { |
|
|
const promise = new Promise(resolve => |
|
|
setTimeout(resolve, 2000) |
|
|
); |
|
|
sonnerToast.promise(promise, { |
|
|
loading: "Processing...", |
|
|
success: "Processing complete!", |
|
|
error: "Processing failed", |
|
|
}); |
|
|
}} |
|
|
> |
|
|
Promise |
|
|
</Button> |
|
|
</div> |
|
|
</div> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</section> |
|
|
|
|
|
{/* AI ChatBox Section */} |
|
|
<section className="space-y-4"> |
|
|
<h3 className="text-2xl font-semibold">AI ChatBox</h3> |
|
|
<Card> |
|
|
<CardContent className="pt-6"> |
|
|
<div className="space-y-4"> |
|
|
<div className="text-sm text-muted-foreground"> |
|
|
<p> |
|
|
A ready-to-use chat interface component that integrates with the LLM system. |
|
|
Features markdown rendering, auto-scrolling, and loading states. |
|
|
</p> |
|
|
<p className="mt-2"> |
|
|
This is a demo with simulated responses. In a real app, you'd connect it to a tRPC mutation. |
|
|
</p> |
|
|
</div> |
|
|
<AIChatBox |
|
|
messages={chatMessages} |
|
|
onSendMessage={handleChatSend} |
|
|
isLoading={isChatLoading} |
|
|
placeholder="Try sending a message..." |
|
|
height="500px" |
|
|
emptyStateMessage="How can I help you today?" |
|
|
suggestedPrompts={[ |
|
|
"What is React?", |
|
|
"Explain TypeScript", |
|
|
"How to use tRPC?", |
|
|
"Best practices for web development", |
|
|
]} |
|
|
/> |
|
|
</div> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</section> |
|
|
</div> |
|
|
</main> |
|
|
|
|
|
<footer className="border-t py-6 mt-12"> |
|
|
<div className="container text-center text-sm text-muted-foreground"> |
|
|
<p>Shadcn/ui Component Showcase</p> |
|
|
</div> |
|
|
</footer> |
|
|
</div> |
|
|
); |
|
|
} |
|
|
|