| 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> |
| ); |
| } |
|
|