init
This commit is contained in:
		
							
								
								
									
										24
									
								
								src/components/About.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/components/About.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| function About() { | ||||
|   return ( | ||||
|     <section id="rolunk" className="bg-slate-900 py-24 sm:py-32"> | ||||
|       <div className="container mx-auto grid grid-cols-1 items-center gap-12 px-6 lg:grid-cols-2"> | ||||
|         <div> | ||||
|           <h2 className="text-4xl font-bold tracking-tight text-amber-400">Történetünk</h2> | ||||
|           <p className="mt-6 text-lg leading-8 text-slate-300"> | ||||
|             Az autentikus magyar ízek iránti szenvedélyből született a <strong>Budapest Bisztró</strong>, hogy elhozza kultúránk melegségét és gazdagságát az Ön asztalára. Séfjeink generációkon át örökített családi recepteket követnek, a legjobb helyi alapanyagokat felhasználva, hogy ételeink egyszerre legyenek hagyományosak és újszerűek. | ||||
|           </p> | ||||
|           <p className="mt-4 text-lg leading-8 text-slate-300"> | ||||
|             Lépjen be éttermünkbe, és merüljön el Budapest meghitt hangulatában, ahol minden étkezés az örökség és a vendégszeretet ünnepe. | ||||
|           </p> | ||||
|         </div> | ||||
|         <img | ||||
|           src="https://plus.unsplash.com/premium_photo-1673108852141-e8c3c22a4a22?auto=format&fit=crop" | ||||
|           alt="Séf ételt készít" | ||||
|           className="rounded-lg shadow-2xl" | ||||
|         /> | ||||
|       </div> | ||||
|     </section> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| export default About; | ||||
							
								
								
									
										14
									
								
								src/components/Contact.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/components/Contact.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| function Contact() { | ||||
|   return ( | ||||
|     <section id="kapcsolat" className="py-24 bg-slate-900"> | ||||
|       <div className="container mx-auto px-6 text-center"> | ||||
|         <h2 className="mb-8 text-3xl font-bold">Kapcsolat</h2> | ||||
|         <p className="mb-4 text-lg text-slate-300">123 Budapest utca, Budapest, Magyarország</p> | ||||
|         <p className="mb-4 text-lg text-slate-300">Telefon: +36 1 234 5678</p> | ||||
|         <p className="text-lg text-slate-300">E-mail: info@gulyaskiraly.hu</p> | ||||
|       </div> | ||||
|     </section> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| export default Contact; | ||||
							
								
								
									
										45
									
								
								src/components/Delivery.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/components/Delivery.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| function Delivery() { | ||||
|   return ( | ||||
|     <section id="szallitas" className="py-24 text-center bg-slate-900"> | ||||
|       <div className="container mx-auto px-6"> | ||||
|         <h2 className="text-4xl font-bold tracking-tight text-amber-400"> | ||||
|           Inkább otthon étkeznél? | ||||
|         </h2> | ||||
|         <p className="mx-auto mt-4 max-w-2xl text-lg text-slate-300"> | ||||
|           Élvezd a Budapest Bisztró autentikus ízeit otthonod kényelméből! | ||||
|           Rendelj most hivatalos házhozszállító partnereinken keresztül. | ||||
|         </p> | ||||
|  | ||||
|         <div className="mt-10 flex flex-wrap justify-center gap-6"> | ||||
|           <a | ||||
|             href="https://wolt.com/hu" | ||||
|             target="_blank" | ||||
|             rel="noopener noreferrer" | ||||
|             className="flex items-center rounded-md px-6 py-3 shadow-md transition hover:scale-105" | ||||
|           > | ||||
|             <img | ||||
|               src="https://atable.hu/wp-content/uploads/2024/09/wolt.png" | ||||
|               alt="Wolt logó" | ||||
|               className="h-32 w-auto" | ||||
|             /> | ||||
|           </a> | ||||
|  | ||||
|           <a | ||||
|             href="https://www.foodora.hu" | ||||
|             target="_blank" | ||||
|             rel="noopener noreferrer" | ||||
|             className="flex items-center rounded-md px-6 py-3 shadow-md transition hover:scale-105" | ||||
|           > | ||||
|             <img | ||||
|               src="https://atable.hu/wp-content/uploads/2024/09/foodora.png" | ||||
|               alt="Foodora logó" | ||||
|               className="h-32 w-auto" | ||||
|             /> | ||||
|           </a> | ||||
|         </div> | ||||
|       </div> | ||||
|     </section> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| export default Delivery; | ||||
							
								
								
									
										12
									
								
								src/components/Footer.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/components/Footer.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| function Footer() { | ||||
|   return ( | ||||
|     <footer className="border-t border-slate-800 bg-slate-900 py-12"> | ||||
|       <div className="container mx-auto px-6 text-center text-slate-400"> | ||||
|         <p>© 2025 Budapest Bisztró. Minden jog fenntartva.</p> | ||||
|         <p className="mt-2">123 Budapest utca, Budapest, Magyarország | info@budapestbisztro.hu</p> | ||||
|       </div> | ||||
|     </footer> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| export default Footer; | ||||
							
								
								
									
										19
									
								
								src/components/Header.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/components/Header.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| function Header() { | ||||
|   return ( | ||||
|     <header className="fixed top-0 z-50 w-full bg-black/20 backdrop-blur-lg"> | ||||
|       <nav className="container mx-auto flex h-20 items-center justify-between px-6"> | ||||
|         <a href="#" className="text-3xl font-bold text-amber-400"> | ||||
|           Budapest Bisztró | ||||
|         </a> | ||||
|         <ul className="hidden items-center space-x-8 md:flex"> | ||||
|           <li><a href="#rolunk" className="text-slate-300 transition hover:text-amber-400">Rólunk</a></li> | ||||
|           <li><a href="#menu" className="text-slate-300 transition hover:text-amber-400">Menü</a></li> | ||||
|           <li><a href="#foglalas" className="text-slate-300 transition hover:text-amber-400">Foglalás</a></li> | ||||
|           <li><a href="#szallitas" className="text-slate-300 transition hover:text-amber-400">Szállítás</a></li> | ||||
|         </ul> | ||||
|       </nav> | ||||
|     </header> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| export default Header; | ||||
							
								
								
									
										90
									
								
								src/components/Hero.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/components/Hero.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| import { useState, useEffect, useCallback } from 'react'; | ||||
| import { ChevronLeft, ChevronRight } from 'lucide-react'; | ||||
|  | ||||
| const heroImages = [ | ||||
|   'https://images.unsplash.com/photo-1556742393-d75f468bfcb0?q=80&w=2070&auto=format&fit=crop', | ||||
|   'https://images.unsplash.com/photo-1552566626-52f8b828add9?q=80&w=2070&auto=format&fit=crop', | ||||
|   'https://plus.unsplash.com/premium_photo-1674106347866-8282d8c19f84?q=80&w=2070&auto=format&fit=crop' | ||||
| ]; | ||||
|  | ||||
| function Hero() { | ||||
|   const [currentIndex, setCurrentIndex] = useState(0); | ||||
|  | ||||
|   const nextSlide = useCallback(() => { | ||||
|     setCurrentIndex((prevIndex) => (prevIndex + 1) % heroImages.length); | ||||
|   }, []); | ||||
|  | ||||
|   const prevSlide = () => { | ||||
|     setCurrentIndex((prevIndex) => (prevIndex - 1 + heroImages.length) % heroImages.length); | ||||
|   }; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     const timer = setInterval(() => { | ||||
|       nextSlide(); | ||||
|     }, 5000); | ||||
|  | ||||
|     return () => clearInterval(timer); | ||||
|   }, [nextSlide]); | ||||
|  | ||||
|   return ( | ||||
|     <section className="relative h-screen w-full overflow-hidden"> | ||||
|       {heroImages.map((src, index) => ( | ||||
|         <div | ||||
|           key={index} | ||||
|           className={`absolute inset-0 h-full w-full bg-cover bg-center transition-opacity duration-1000 ease-in-out ${index === currentIndex ? 'opacity-100' : 'opacity-0' | ||||
|             }`} | ||||
|           style={{ backgroundImage: `url('${src}')` }} | ||||
|         > | ||||
|           {index === currentIndex && ( | ||||
|             <div className="h-full w-full animate-zoom" /> | ||||
|           )} | ||||
|         </div> | ||||
|       ))} | ||||
|  | ||||
|       <div className="absolute inset-0 bg-black/60" /> | ||||
|  | ||||
|       <div className="absolute inset-0 z-10 flex flex-col items-center justify-center text-center text-white"> | ||||
|         <h1 className="mb-4 text-6xl font-bold tracking-tight"> | ||||
|           Magyarország szíve, tálalva egy tányéron | ||||
|         </h1> | ||||
|         <p className="mb-8 max-w-2xl text-xl text-slate-300"> | ||||
|           Éld át a hagyományos magyar konyha gazdag, autentikus ízeit egy modern, elegáns környezetben. | ||||
|         </p> | ||||
|         <a href="#foglalas" className="rounded-md bg-amber-500 px-8 py-3 text-lg font-semibold text-slate-900 transition hover:bg-amber-400"> | ||||
|           Asztalfoglalás | ||||
|         </a> | ||||
|       </div> | ||||
|  | ||||
|       <div className="absolute inset-0 z-10"> | ||||
|         <button | ||||
|           onClick={prevSlide} | ||||
|           className="absolute left-4 top-1/2 -translate-y-1/2 rounded-full bg-black/30 p-2 text-white transition hover:bg-black/50" | ||||
|           aria-label="Előző dia" | ||||
|         > | ||||
|           <ChevronLeft size={32} /> | ||||
|         </button> | ||||
|         <button | ||||
|           onClick={nextSlide} | ||||
|           className="absolute right-4 top-1/2 -translate-y-1/2 rounded-full bg-black/30 p-2 text-white transition hover:bg-black/50" | ||||
|           aria-label="Következő dia" | ||||
|         > | ||||
|           <ChevronRight size={32} /> | ||||
|         </button> | ||||
|       </div> | ||||
|  | ||||
|       <div className="absolute bottom-8 left-1/2 z-10 flex -translate-x-1/2 space-x-3"> | ||||
|         {heroImages.map((_, index) => ( | ||||
|           <button | ||||
|             key={index} | ||||
|             onClick={() => setCurrentIndex(index)} | ||||
|             className={`h-3 w-3 rounded-full transition-colors ${currentIndex === index ? 'bg-amber-400' : 'bg-white/50 hover:bg-white/75' | ||||
|               }`} | ||||
|             aria-label={`Ugrás a(z) ${index + 1}. diára`} | ||||
|           /> | ||||
|         ))} | ||||
|       </div> | ||||
|     </section> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| export default Hero; | ||||
							
								
								
									
										58
									
								
								src/components/Menu.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/components/Menu.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| const menuItems = [ | ||||
|   { | ||||
|     name: 'Gulyásleves', | ||||
|     description: 'Gazdag marhahúsos és zöldséges pörkölt, fűszerpaprikával ízesítve.', | ||||
|     price: '4 990 Ft', | ||||
|     image: 'https://images.unsplash.com/photo-1750677637369-7fae4d9e361b?q=80&w=1964&auto=format&fit=crop', | ||||
|   }, | ||||
|   { | ||||
|     name: 'Rántott csirke burgonyapürével', | ||||
|     description: 'Ropogós panírozott csirke, krémes burgonyapürével és friss salátával.', | ||||
|     price: '5 900 Ft', | ||||
|     image: 'https://plus.unsplash.com/premium_photo-1701109142322-22b8a71f5b7a?q=80&w=1974&auto=format&fit=crop', | ||||
|   }, | ||||
|   { | ||||
|     name: 'Csirkepaprikás nokedlivel', | ||||
|     description: 'Tejfölös paprikás csirke friss házi galuskával tálalva.', | ||||
|     price: '5 500 Ft', | ||||
|     image: 'https://plus.unsplash.com/premium_photo-1664206964048-26b7a0ebf093?q=80&w=2070&auto=format&fit=crop', | ||||
|   }, | ||||
|   { | ||||
|     name: 'Sült csülök káposztával', | ||||
|     description: 'Ropogósra sült csülök, párolt savanyú káposztával és tört burgonyával.', | ||||
|     price: '6 500 Ft', | ||||
|     image: 'https://images.unsplash.com/photo-1600891964599-f61ba0e24092?q=80&w=1964&auto=format&fit=crop', | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| function Menu() { | ||||
|   return ( | ||||
|     <section id="menu" className="py-24 sm:py-32"> | ||||
|       <div className="container mx-auto px-6"> | ||||
|         <div className="text-center"> | ||||
|           <h2 className="text-4xl font-bold tracking-tight text-amber-400">Konyhánkból</h2> | ||||
|           <p className="mt-4 text-lg text-slate-300">A legkedveltebb ételeink válogatása.</p> | ||||
|         </div> | ||||
|         <div className="mt-16 grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-4"> | ||||
|           {menuItems.map((item) => ( | ||||
|             <div key={item.name} className="group relative overflow-hidden rounded-lg shadow-lg"> | ||||
|               <img | ||||
|                 src={item.image} | ||||
|                 alt={item.name} | ||||
|                 className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-110" | ||||
|               /> | ||||
|               <div className="absolute inset-0 bg-gradient-to-t from-black/80 to-transparent" /> | ||||
|               <div className="absolute bottom-0 left-0 p-6 text-white"> | ||||
|                 <h3 className="text-2xl font-bold">{item.name}</h3> | ||||
|                 <p className="text-slate-300">{item.description}</p> | ||||
|                 <p className="mt-4 text-xl font-semibold text-amber-400">{item.price}</p> | ||||
|               </div> | ||||
|             </div> | ||||
|           ))} | ||||
|         </div> | ||||
|       </div> | ||||
|     </section> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| export default Menu; | ||||
							
								
								
									
										57
									
								
								src/components/Reservations.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/components/Reservations.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| import { Button } from './ui/button'; | ||||
| import { Calendar } from './ui/calendar'; | ||||
| import { Input } from './ui/input'; | ||||
| import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select'; | ||||
|  | ||||
| function Foglalas() { | ||||
|   return ( | ||||
|     <section id="foglalas" className="bg-slate-900 py-24 sm:py-32"> | ||||
|       <div className="container mx-auto grid grid-cols-1 items-center gap-12 px-6 lg:grid-cols-2"> | ||||
|         <div className="text-center lg:text-left"> | ||||
|           <h2 className="text-4xl font-bold tracking-tight text-amber-400">Foglaljon Asztalt</h2> | ||||
|           <p className="mt-4 text-lg text-slate-300"> | ||||
|             Biztosítsa helyét egy felejthetetlen gasztronómiai élményhez. Örömmel látjuk vendégül! | ||||
|           </p> | ||||
|           <img | ||||
|             src="https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?q=80&w=2070&auto=format&fit=crop" | ||||
|             alt="Étterem belső" | ||||
|             className="mt-8 rounded-lg shadow-2xl" | ||||
|           /> | ||||
|         </div> | ||||
|         <div className="rounded-lg bg-slate-950 p-8 shadow-2xl"> | ||||
|           <form className="space-y-6"> | ||||
|             <Input placeholder="Teljes Név" /> | ||||
|             <Input type="email" placeholder="Email Cím" /> | ||||
|             <div className="grid grid-cols-1 gap-6 sm:grid-cols-2"> | ||||
|               <Select> | ||||
|                 <SelectTrigger><SelectValue placeholder="Vendégek száma" /></SelectTrigger> | ||||
|                 <SelectContent> | ||||
|                   <SelectItem value="1">1 fő</SelectItem> | ||||
|                   <SelectItem value="2">2 fő</SelectItem> | ||||
|                   <SelectItem value="3">3 fő</SelectItem> | ||||
|                   <SelectItem value="4">4 fő</SelectItem> | ||||
|                   <SelectItem value="5">5+ fő</SelectItem> | ||||
|                 </SelectContent> | ||||
|               </Select> | ||||
|               <Select> | ||||
|                 <SelectTrigger><SelectValue placeholder="Időpont" /></SelectTrigger> | ||||
|                 <SelectContent> | ||||
|                   <SelectItem value="18:00">18:00</SelectItem> | ||||
|                   <SelectItem value="19:00">19:00</SelectItem> | ||||
|                   <SelectItem value="20:00">20:00</SelectItem> | ||||
|                   <SelectItem value="21:00">21:00</SelectItem> | ||||
|                 </SelectContent> | ||||
|               </Select> | ||||
|             </div> | ||||
|             <Calendar mode="single" className="rounded-md border border-slate-800" /> | ||||
|             <Button type="submit" size="lg" className="w-full bg-amber-500 text-slate-900 hover:bg-amber-400"> | ||||
|               Foglalás | ||||
|             </Button> | ||||
|           </form> | ||||
|         </div> | ||||
|       </div> | ||||
|     </section> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| export default Foglalas; | ||||
							
								
								
									
										64
									
								
								src/components/ui/accordion.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/components/ui/accordion.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| import * as React from "react" | ||||
| import * as AccordionPrimitive from "@radix-ui/react-accordion" | ||||
| import { ChevronDownIcon } from "lucide-react" | ||||
|  | ||||
| import { cn } from "@/lib/utils" | ||||
|  | ||||
| function Accordion({ | ||||
|   ...props | ||||
| }: React.ComponentProps<typeof AccordionPrimitive.Root>) { | ||||
|   return <AccordionPrimitive.Root data-slot="accordion" {...props} /> | ||||
| } | ||||
|  | ||||
| function AccordionItem({ | ||||
|   className, | ||||
|   ...props | ||||
| }: React.ComponentProps<typeof AccordionPrimitive.Item>) { | ||||
|   return ( | ||||
|     <AccordionPrimitive.Item | ||||
|       data-slot="accordion-item" | ||||
|       className={cn("border-b last:border-b-0", className)} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function AccordionTrigger({ | ||||
|   className, | ||||
|   children, | ||||
|   ...props | ||||
| }: React.ComponentProps<typeof AccordionPrimitive.Trigger>) { | ||||
|   return ( | ||||
|     <AccordionPrimitive.Header className="flex"> | ||||
|       <AccordionPrimitive.Trigger | ||||
|         data-slot="accordion-trigger" | ||||
|         className={cn( | ||||
|           "focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180", | ||||
|           className | ||||
|         )} | ||||
|         {...props} | ||||
|       > | ||||
|         {children} | ||||
|         <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" /> | ||||
|       </AccordionPrimitive.Trigger> | ||||
|     </AccordionPrimitive.Header> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function AccordionContent({ | ||||
|   className, | ||||
|   children, | ||||
|   ...props | ||||
| }: React.ComponentProps<typeof AccordionPrimitive.Content>) { | ||||
|   return ( | ||||
|     <AccordionPrimitive.Content | ||||
|       data-slot="accordion-content" | ||||
|       className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm" | ||||
|       {...props} | ||||
|     > | ||||
|       <div className={cn("pt-0 pb-4", className)}>{children}</div> | ||||
|     </AccordionPrimitive.Content> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } | ||||
							
								
								
									
										60
									
								
								src/components/ui/button.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/components/ui/button.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| import * as React from "react" | ||||
| import { Slot } from "@radix-ui/react-slot" | ||||
| import { cva, type VariantProps } from "class-variance-authority" | ||||
|  | ||||
| import { cn } from "@/lib/utils" | ||||
|  | ||||
| const buttonVariants = cva( | ||||
|   "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", | ||||
|   { | ||||
|     variants: { | ||||
|       variant: { | ||||
|         default: "bg-primary text-primary-foreground hover:bg-primary/90", | ||||
|         destructive: | ||||
|           "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", | ||||
|         outline: | ||||
|           "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", | ||||
|         secondary: | ||||
|           "bg-secondary text-secondary-foreground hover:bg-secondary/80", | ||||
|         ghost: | ||||
|           "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", | ||||
|         link: "text-primary underline-offset-4 hover:underline", | ||||
|       }, | ||||
|       size: { | ||||
|         default: "h-9 px-4 py-2 has-[>svg]:px-3", | ||||
|         sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", | ||||
|         lg: "h-10 rounded-md px-6 has-[>svg]:px-4", | ||||
|         icon: "size-9", | ||||
|         "icon-sm": "size-8", | ||||
|         "icon-lg": "size-10", | ||||
|       }, | ||||
|     }, | ||||
|     defaultVariants: { | ||||
|       variant: "default", | ||||
|       size: "default", | ||||
|     }, | ||||
|   } | ||||
| ) | ||||
|  | ||||
| function Button({ | ||||
|   className, | ||||
|   variant, | ||||
|   size, | ||||
|   asChild = false, | ||||
|   ...props | ||||
| }: React.ComponentProps<"button"> & | ||||
|   VariantProps<typeof buttonVariants> & { | ||||
|     asChild?: boolean | ||||
|   }) { | ||||
|   const Comp = asChild ? Slot : "button" | ||||
|  | ||||
|   return ( | ||||
|     <Comp | ||||
|       data-slot="button" | ||||
|       className={cn(buttonVariants({ variant, size, className }))} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export { Button, buttonVariants } | ||||
							
								
								
									
										211
									
								
								src/components/ui/calendar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								src/components/ui/calendar.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,211 @@ | ||||
| import * as React from "react" | ||||
| import { | ||||
|   ChevronDownIcon, | ||||
|   ChevronLeftIcon, | ||||
|   ChevronRightIcon, | ||||
| } from "lucide-react" | ||||
| import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker" | ||||
|  | ||||
| import { cn } from "@/lib/utils" | ||||
| import { Button, buttonVariants } from "@/components/ui/button" | ||||
|  | ||||
| function Calendar({ | ||||
|   className, | ||||
|   classNames, | ||||
|   showOutsideDays = true, | ||||
|   captionLayout = "label", | ||||
|   buttonVariant = "ghost", | ||||
|   formatters, | ||||
|   components, | ||||
|   ...props | ||||
| }: React.ComponentProps<typeof DayPicker> & { | ||||
|   buttonVariant?: React.ComponentProps<typeof Button>["variant"] | ||||
| }) { | ||||
|   const defaultClassNames = getDefaultClassNames() | ||||
|  | ||||
|   return ( | ||||
|     <DayPicker | ||||
|       showOutsideDays={showOutsideDays} | ||||
|       className={cn( | ||||
|         "bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent", | ||||
|         String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`, | ||||
|         String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, | ||||
|         className | ||||
|       )} | ||||
|       captionLayout={captionLayout} | ||||
|       formatters={{ | ||||
|         formatMonthDropdown: (date) => | ||||
|           date.toLocaleString("default", { month: "short" }), | ||||
|         ...formatters, | ||||
|       }} | ||||
|       classNames={{ | ||||
|         root: cn("w-fit", defaultClassNames.root), | ||||
|         months: cn( | ||||
|           "flex gap-4 flex-col md:flex-row relative", | ||||
|           defaultClassNames.months | ||||
|         ), | ||||
|         month: cn("flex flex-col w-full gap-4", defaultClassNames.month), | ||||
|         nav: cn( | ||||
|           "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between", | ||||
|           defaultClassNames.nav | ||||
|         ), | ||||
|         button_previous: cn( | ||||
|           buttonVariants({ variant: buttonVariant }), | ||||
|           "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", | ||||
|           defaultClassNames.button_previous | ||||
|         ), | ||||
|         button_next: cn( | ||||
|           buttonVariants({ variant: buttonVariant }), | ||||
|           "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", | ||||
|           defaultClassNames.button_next | ||||
|         ), | ||||
|         month_caption: cn( | ||||
|           "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)", | ||||
|           defaultClassNames.month_caption | ||||
|         ), | ||||
|         dropdowns: cn( | ||||
|           "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5", | ||||
|           defaultClassNames.dropdowns | ||||
|         ), | ||||
|         dropdown_root: cn( | ||||
|           "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md", | ||||
|           defaultClassNames.dropdown_root | ||||
|         ), | ||||
|         dropdown: cn( | ||||
|           "absolute bg-popover inset-0 opacity-0", | ||||
|           defaultClassNames.dropdown | ||||
|         ), | ||||
|         caption_label: cn( | ||||
|           "select-none font-medium", | ||||
|           captionLayout === "label" | ||||
|             ? "text-sm" | ||||
|             : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5", | ||||
|           defaultClassNames.caption_label | ||||
|         ), | ||||
|         table: "w-full border-collapse", | ||||
|         weekdays: cn("flex", defaultClassNames.weekdays), | ||||
|         weekday: cn( | ||||
|           "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none", | ||||
|           defaultClassNames.weekday | ||||
|         ), | ||||
|         week: cn("flex w-full mt-2", defaultClassNames.week), | ||||
|         week_number_header: cn( | ||||
|           "select-none w-(--cell-size)", | ||||
|           defaultClassNames.week_number_header | ||||
|         ), | ||||
|         week_number: cn( | ||||
|           "text-[0.8rem] select-none text-muted-foreground", | ||||
|           defaultClassNames.week_number | ||||
|         ), | ||||
|         day: cn( | ||||
|           "relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none", | ||||
|           defaultClassNames.day | ||||
|         ), | ||||
|         range_start: cn( | ||||
|           "rounded-l-md bg-accent", | ||||
|           defaultClassNames.range_start | ||||
|         ), | ||||
|         range_middle: cn("rounded-none", defaultClassNames.range_middle), | ||||
|         range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end), | ||||
|         today: cn( | ||||
|           "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", | ||||
|           defaultClassNames.today | ||||
|         ), | ||||
|         outside: cn( | ||||
|           "text-muted-foreground aria-selected:text-muted-foreground", | ||||
|           defaultClassNames.outside | ||||
|         ), | ||||
|         disabled: cn( | ||||
|           "text-muted-foreground opacity-50", | ||||
|           defaultClassNames.disabled | ||||
|         ), | ||||
|         hidden: cn("invisible", defaultClassNames.hidden), | ||||
|         ...classNames, | ||||
|       }} | ||||
|       components={{ | ||||
|         Root: ({ className, rootRef, ...props }) => { | ||||
|           return ( | ||||
|             <div | ||||
|               data-slot="calendar" | ||||
|               ref={rootRef} | ||||
|               className={cn(className)} | ||||
|               {...props} | ||||
|             /> | ||||
|           ) | ||||
|         }, | ||||
|         Chevron: ({ className, orientation, ...props }) => { | ||||
|           if (orientation === "left") { | ||||
|             return ( | ||||
|               <ChevronLeftIcon className={cn("size-4", className)} {...props} /> | ||||
|             ) | ||||
|           } | ||||
|  | ||||
|           if (orientation === "right") { | ||||
|             return ( | ||||
|               <ChevronRightIcon | ||||
|                 className={cn("size-4", className)} | ||||
|                 {...props} | ||||
|               /> | ||||
|             ) | ||||
|           } | ||||
|  | ||||
|           return ( | ||||
|             <ChevronDownIcon className={cn("size-4", className)} {...props} /> | ||||
|           ) | ||||
|         }, | ||||
|         DayButton: CalendarDayButton, | ||||
|         WeekNumber: ({ children, ...props }) => { | ||||
|           return ( | ||||
|             <td {...props}> | ||||
|               <div className="flex size-(--cell-size) items-center justify-center text-center"> | ||||
|                 {children} | ||||
|               </div> | ||||
|             </td> | ||||
|           ) | ||||
|         }, | ||||
|         ...components, | ||||
|       }} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function CalendarDayButton({ | ||||
|   className, | ||||
|   day, | ||||
|   modifiers, | ||||
|   ...props | ||||
| }: React.ComponentProps<typeof DayButton>) { | ||||
|   const defaultClassNames = getDefaultClassNames() | ||||
|  | ||||
|   const ref = React.useRef<HTMLButtonElement>(null) | ||||
|   React.useEffect(() => { | ||||
|     if (modifiers.focused) ref.current?.focus() | ||||
|   }, [modifiers.focused]) | ||||
|  | ||||
|   return ( | ||||
|     <Button | ||||
|       ref={ref} | ||||
|       variant="ghost" | ||||
|       size="icon" | ||||
|       data-day={day.date.toLocaleDateString()} | ||||
|       data-selected-single={ | ||||
|         modifiers.selected && | ||||
|         !modifiers.range_start && | ||||
|         !modifiers.range_end && | ||||
|         !modifiers.range_middle | ||||
|       } | ||||
|       data-range-start={modifiers.range_start} | ||||
|       data-range-end={modifiers.range_end} | ||||
|       data-range-middle={modifiers.range_middle} | ||||
|       className={cn( | ||||
|         "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70", | ||||
|         defaultClassNames.day, | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export { Calendar, CalendarDayButton } | ||||
							
								
								
									
										92
									
								
								src/components/ui/card.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/components/ui/card.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| import * as React from "react" | ||||
|  | ||||
| import { cn } from "@/lib/utils" | ||||
|  | ||||
| function Card({ className, ...props }: React.ComponentProps<"div">) { | ||||
|   return ( | ||||
|     <div | ||||
|       data-slot="card" | ||||
|       className={cn( | ||||
|         "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function CardHeader({ className, ...props }: React.ComponentProps<"div">) { | ||||
|   return ( | ||||
|     <div | ||||
|       data-slot="card-header" | ||||
|       className={cn( | ||||
|         "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function CardTitle({ className, ...props }: React.ComponentProps<"div">) { | ||||
|   return ( | ||||
|     <div | ||||
|       data-slot="card-title" | ||||
|       className={cn("leading-none font-semibold", className)} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function CardDescription({ className, ...props }: React.ComponentProps<"div">) { | ||||
|   return ( | ||||
|     <div | ||||
|       data-slot="card-description" | ||||
|       className={cn("text-muted-foreground text-sm", className)} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function CardAction({ className, ...props }: React.ComponentProps<"div">) { | ||||
|   return ( | ||||
|     <div | ||||
|       data-slot="card-action" | ||||
|       className={cn( | ||||
|         "col-start-2 row-span-2 row-start-1 self-start justify-self-end", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function CardContent({ className, ...props }: React.ComponentProps<"div">) { | ||||
|   return ( | ||||
|     <div | ||||
|       data-slot="card-content" | ||||
|       className={cn("px-6", className)} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function CardFooter({ className, ...props }: React.ComponentProps<"div">) { | ||||
|   return ( | ||||
|     <div | ||||
|       data-slot="card-footer" | ||||
|       className={cn("flex items-center px-6 [.border-t]:pt-6", className)} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export { | ||||
|   Card, | ||||
|   CardHeader, | ||||
|   CardFooter, | ||||
|   CardTitle, | ||||
|   CardAction, | ||||
|   CardDescription, | ||||
|   CardContent, | ||||
| } | ||||
							
								
								
									
										241
									
								
								src/components/ui/carousel.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								src/components/ui/carousel.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,241 @@ | ||||
| "use client" | ||||
|  | ||||
| import * as React from "react" | ||||
| import useEmblaCarousel, { | ||||
|   type UseEmblaCarouselType, | ||||
| } from "embla-carousel-react" | ||||
| import { ArrowLeft, ArrowRight } from "lucide-react" | ||||
|  | ||||
| import { cn } from "@/lib/utils" | ||||
| import { Button } from "@/components/ui/button" | ||||
|  | ||||
| type CarouselApi = UseEmblaCarouselType[1] | ||||
| type UseCarouselParameters = Parameters<typeof useEmblaCarousel> | ||||
| type CarouselOptions = UseCarouselParameters[0] | ||||
| type CarouselPlugin = UseCarouselParameters[1] | ||||
|  | ||||
| type CarouselProps = { | ||||
|   opts?: CarouselOptions | ||||
|   plugins?: CarouselPlugin | ||||
|   orientation?: "horizontal" | "vertical" | ||||
|   setApi?: (api: CarouselApi) => void | ||||
| } | ||||
|  | ||||
| type CarouselContextProps = { | ||||
|   carouselRef: ReturnType<typeof useEmblaCarousel>[0] | ||||
|   api: ReturnType<typeof useEmblaCarousel>[1] | ||||
|   scrollPrev: () => void | ||||
|   scrollNext: () => void | ||||
|   canScrollPrev: boolean | ||||
|   canScrollNext: boolean | ||||
| } & CarouselProps | ||||
|  | ||||
| const CarouselContext = React.createContext<CarouselContextProps | null>(null) | ||||
|  | ||||
| function useCarousel() { | ||||
|   const context = React.useContext(CarouselContext) | ||||
|  | ||||
|   if (!context) { | ||||
|     throw new Error("useCarousel must be used within a <Carousel />") | ||||
|   } | ||||
|  | ||||
|   return context | ||||
| } | ||||
|  | ||||
| function Carousel({ | ||||
|   orientation = "horizontal", | ||||
|   opts, | ||||
|   setApi, | ||||
|   plugins, | ||||
|   className, | ||||
|   children, | ||||
|   ...props | ||||
| }: React.ComponentProps<"div"> & CarouselProps) { | ||||
|   const [carouselRef, api] = useEmblaCarousel( | ||||
|     { | ||||
|       ...opts, | ||||
|       axis: orientation === "horizontal" ? "x" : "y", | ||||
|     }, | ||||
|     plugins | ||||
|   ) | ||||
|   const [canScrollPrev, setCanScrollPrev] = React.useState(false) | ||||
|   const [canScrollNext, setCanScrollNext] = React.useState(false) | ||||
|  | ||||
|   const onSelect = React.useCallback((api: CarouselApi) => { | ||||
|     if (!api) return | ||||
|     setCanScrollPrev(api.canScrollPrev()) | ||||
|     setCanScrollNext(api.canScrollNext()) | ||||
|   }, []) | ||||
|  | ||||
|   const scrollPrev = React.useCallback(() => { | ||||
|     api?.scrollPrev() | ||||
|   }, [api]) | ||||
|  | ||||
|   const scrollNext = React.useCallback(() => { | ||||
|     api?.scrollNext() | ||||
|   }, [api]) | ||||
|  | ||||
|   const handleKeyDown = React.useCallback( | ||||
|     (event: React.KeyboardEvent<HTMLDivElement>) => { | ||||
|       if (event.key === "ArrowLeft") { | ||||
|         event.preventDefault() | ||||
|         scrollPrev() | ||||
|       } else if (event.key === "ArrowRight") { | ||||
|         event.preventDefault() | ||||
|         scrollNext() | ||||
|       } | ||||
|     }, | ||||
|     [scrollPrev, scrollNext] | ||||
|   ) | ||||
|  | ||||
|   React.useEffect(() => { | ||||
|     if (!api || !setApi) return | ||||
|     setApi(api) | ||||
|   }, [api, setApi]) | ||||
|  | ||||
|   React.useEffect(() => { | ||||
|     if (!api) return | ||||
|     onSelect(api) | ||||
|     api.on("reInit", onSelect) | ||||
|     api.on("select", onSelect) | ||||
|  | ||||
|     return () => { | ||||
|       api?.off("select", onSelect) | ||||
|     } | ||||
|   }, [api, onSelect]) | ||||
|  | ||||
|   return ( | ||||
|     <CarouselContext.Provider | ||||
|       value={{ | ||||
|         carouselRef, | ||||
|         api: api, | ||||
|         opts, | ||||
|         orientation: | ||||
|           orientation || (opts?.axis === "y" ? "vertical" : "horizontal"), | ||||
|         scrollPrev, | ||||
|         scrollNext, | ||||
|         canScrollPrev, | ||||
|         canScrollNext, | ||||
|       }} | ||||
|     > | ||||
|       <div | ||||
|         onKeyDownCapture={handleKeyDown} | ||||
|         className={cn("relative", className)} | ||||
|         role="region" | ||||
|         aria-roledescription="carousel" | ||||
|         data-slot="carousel" | ||||
|         {...props} | ||||
|       > | ||||
|         {children} | ||||
|       </div> | ||||
|     </CarouselContext.Provider> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function CarouselContent({ className, ...props }: React.ComponentProps<"div">) { | ||||
|   const { carouselRef, orientation } = useCarousel() | ||||
|  | ||||
|   return ( | ||||
|     <div | ||||
|       ref={carouselRef} | ||||
|       className="overflow-hidden" | ||||
|       data-slot="carousel-content" | ||||
|     > | ||||
|       <div | ||||
|         className={cn( | ||||
|           "flex", | ||||
|           orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col", | ||||
|           className | ||||
|         )} | ||||
|         {...props} | ||||
|       /> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function CarouselItem({ className, ...props }: React.ComponentProps<"div">) { | ||||
|   const { orientation } = useCarousel() | ||||
|  | ||||
|   return ( | ||||
|     <div | ||||
|       role="group" | ||||
|       aria-roledescription="slide" | ||||
|       data-slot="carousel-item" | ||||
|       className={cn( | ||||
|         "min-w-0 shrink-0 grow-0 basis-full", | ||||
|         orientation === "horizontal" ? "pl-4" : "pt-4", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function CarouselPrevious({ | ||||
|   className, | ||||
|   variant = "outline", | ||||
|   size = "icon", | ||||
|   ...props | ||||
| }: React.ComponentProps<typeof Button>) { | ||||
|   const { orientation, scrollPrev, canScrollPrev } = useCarousel() | ||||
|  | ||||
|   return ( | ||||
|     <Button | ||||
|       data-slot="carousel-previous" | ||||
|       variant={variant} | ||||
|       size={size} | ||||
|       className={cn( | ||||
|         "absolute size-8 rounded-full", | ||||
|         orientation === "horizontal" | ||||
|           ? "top-1/2 -left-12 -translate-y-1/2" | ||||
|           : "-top-12 left-1/2 -translate-x-1/2 rotate-90", | ||||
|         className | ||||
|       )} | ||||
|       disabled={!canScrollPrev} | ||||
|       onClick={scrollPrev} | ||||
|       {...props} | ||||
|     > | ||||
|       <ArrowLeft /> | ||||
|       <span className="sr-only">Previous slide</span> | ||||
|     </Button> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function CarouselNext({ | ||||
|   className, | ||||
|   variant = "outline", | ||||
|   size = "icon", | ||||
|   ...props | ||||
| }: React.ComponentProps<typeof Button>) { | ||||
|   const { orientation, scrollNext, canScrollNext } = useCarousel() | ||||
|  | ||||
|   return ( | ||||
|     <Button | ||||
|       data-slot="carousel-next" | ||||
|       variant={variant} | ||||
|       size={size} | ||||
|       className={cn( | ||||
|         "absolute size-8 rounded-full", | ||||
|         orientation === "horizontal" | ||||
|           ? "top-1/2 -right-12 -translate-y-1/2" | ||||
|           : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90", | ||||
|         className | ||||
|       )} | ||||
|       disabled={!canScrollNext} | ||||
|       onClick={scrollNext} | ||||
|       {...props} | ||||
|     > | ||||
|       <ArrowRight /> | ||||
|       <span className="sr-only">Next slide</span> | ||||
|     </Button> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export { | ||||
|   type CarouselApi, | ||||
|   Carousel, | ||||
|   CarouselContent, | ||||
|   CarouselItem, | ||||
|   CarouselPrevious, | ||||
|   CarouselNext, | ||||
| } | ||||
							
								
								
									
										21
									
								
								src/components/ui/input.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/components/ui/input.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| import * as React from "react" | ||||
|  | ||||
| import { cn } from "@/lib/utils" | ||||
|  | ||||
| function Input({ className, type, ...props }: React.ComponentProps<"input">) { | ||||
|   return ( | ||||
|     <input | ||||
|       type={type} | ||||
|       data-slot="input" | ||||
|       className={cn( | ||||
|         "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", | ||||
|         "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", | ||||
|         "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export { Input } | ||||
							
								
								
									
										185
									
								
								src/components/ui/select.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								src/components/ui/select.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,185 @@ | ||||
| import * as React from "react" | ||||
| import * as SelectPrimitive from "@radix-ui/react-select" | ||||
| import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react" | ||||
|  | ||||
| import { cn } from "@/lib/utils" | ||||
|  | ||||
| function Select({ | ||||
|   ...props | ||||
| }: React.ComponentProps<typeof SelectPrimitive.Root>) { | ||||
|   return <SelectPrimitive.Root data-slot="select" {...props} /> | ||||
| } | ||||
|  | ||||
| function SelectGroup({ | ||||
|   ...props | ||||
| }: React.ComponentProps<typeof SelectPrimitive.Group>) { | ||||
|   return <SelectPrimitive.Group data-slot="select-group" {...props} /> | ||||
| } | ||||
|  | ||||
| function SelectValue({ | ||||
|   ...props | ||||
| }: React.ComponentProps<typeof SelectPrimitive.Value>) { | ||||
|   return <SelectPrimitive.Value data-slot="select-value" {...props} /> | ||||
| } | ||||
|  | ||||
| function SelectTrigger({ | ||||
|   className, | ||||
|   size = "default", | ||||
|   children, | ||||
|   ...props | ||||
| }: React.ComponentProps<typeof SelectPrimitive.Trigger> & { | ||||
|   size?: "sm" | "default" | ||||
| }) { | ||||
|   return ( | ||||
|     <SelectPrimitive.Trigger | ||||
|       data-slot="select-trigger" | ||||
|       data-size={size} | ||||
|       className={cn( | ||||
|         "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     > | ||||
|       {children} | ||||
|       <SelectPrimitive.Icon asChild> | ||||
|         <ChevronDownIcon className="size-4 opacity-50" /> | ||||
|       </SelectPrimitive.Icon> | ||||
|     </SelectPrimitive.Trigger> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function SelectContent({ | ||||
|   className, | ||||
|   children, | ||||
|   position = "popper", | ||||
|   align = "center", | ||||
|   ...props | ||||
| }: React.ComponentProps<typeof SelectPrimitive.Content>) { | ||||
|   return ( | ||||
|     <SelectPrimitive.Portal> | ||||
|       <SelectPrimitive.Content | ||||
|         data-slot="select-content" | ||||
|         className={cn( | ||||
|           "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md", | ||||
|           position === "popper" && | ||||
|             "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", | ||||
|           className | ||||
|         )} | ||||
|         position={position} | ||||
|         align={align} | ||||
|         {...props} | ||||
|       > | ||||
|         <SelectScrollUpButton /> | ||||
|         <SelectPrimitive.Viewport | ||||
|           className={cn( | ||||
|             "p-1", | ||||
|             position === "popper" && | ||||
|               "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1" | ||||
|           )} | ||||
|         > | ||||
|           {children} | ||||
|         </SelectPrimitive.Viewport> | ||||
|         <SelectScrollDownButton /> | ||||
|       </SelectPrimitive.Content> | ||||
|     </SelectPrimitive.Portal> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function SelectLabel({ | ||||
|   className, | ||||
|   ...props | ||||
| }: React.ComponentProps<typeof SelectPrimitive.Label>) { | ||||
|   return ( | ||||
|     <SelectPrimitive.Label | ||||
|       data-slot="select-label" | ||||
|       className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function SelectItem({ | ||||
|   className, | ||||
|   children, | ||||
|   ...props | ||||
| }: React.ComponentProps<typeof SelectPrimitive.Item>) { | ||||
|   return ( | ||||
|     <SelectPrimitive.Item | ||||
|       data-slot="select-item" | ||||
|       className={cn( | ||||
|         "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     > | ||||
|       <span className="absolute right-2 flex size-3.5 items-center justify-center"> | ||||
|         <SelectPrimitive.ItemIndicator> | ||||
|           <CheckIcon className="size-4" /> | ||||
|         </SelectPrimitive.ItemIndicator> | ||||
|       </span> | ||||
|       <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> | ||||
|     </SelectPrimitive.Item> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function SelectSeparator({ | ||||
|   className, | ||||
|   ...props | ||||
| }: React.ComponentProps<typeof SelectPrimitive.Separator>) { | ||||
|   return ( | ||||
|     <SelectPrimitive.Separator | ||||
|       data-slot="select-separator" | ||||
|       className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)} | ||||
|       {...props} | ||||
|     /> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function SelectScrollUpButton({ | ||||
|   className, | ||||
|   ...props | ||||
| }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) { | ||||
|   return ( | ||||
|     <SelectPrimitive.ScrollUpButton | ||||
|       data-slot="select-scroll-up-button" | ||||
|       className={cn( | ||||
|         "flex cursor-default items-center justify-center py-1", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     > | ||||
|       <ChevronUpIcon className="size-4" /> | ||||
|     </SelectPrimitive.ScrollUpButton> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function SelectScrollDownButton({ | ||||
|   className, | ||||
|   ...props | ||||
| }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) { | ||||
|   return ( | ||||
|     <SelectPrimitive.ScrollDownButton | ||||
|       data-slot="select-scroll-down-button" | ||||
|       className={cn( | ||||
|         "flex cursor-default items-center justify-center py-1", | ||||
|         className | ||||
|       )} | ||||
|       {...props} | ||||
|     > | ||||
|       <ChevronDownIcon className="size-4" /> | ||||
|     </SelectPrimitive.ScrollDownButton> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export { | ||||
|   Select, | ||||
|   SelectContent, | ||||
|   SelectGroup, | ||||
|   SelectItem, | ||||
|   SelectLabel, | ||||
|   SelectScrollDownButton, | ||||
|   SelectScrollUpButton, | ||||
|   SelectSeparator, | ||||
|   SelectTrigger, | ||||
|   SelectValue, | ||||
| } | ||||
		Reference in New Issue
	
	Block a user