Merge pull request 'initial top filter menu' (#2) from shadcn into main
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Vercel Production Deployment / Deploy-Production (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Vercel Production Deployment / Deploy-Production (push) Has been cancelled
				
			Reviewed-on: #2
This commit is contained in:
		
						commit
						5209923a5e
					
				
							
								
								
									
										17
									
								
								components.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								components.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "$schema": "https://ui.shadcn.com/schema.json",
 | 
				
			||||||
 | 
					  "style": "default",
 | 
				
			||||||
 | 
					  "rsc": true,
 | 
				
			||||||
 | 
					  "tsx": true,
 | 
				
			||||||
 | 
					  "tailwind": {
 | 
				
			||||||
 | 
					    "config": "tailwind.config.ts",
 | 
				
			||||||
 | 
					    "css": "src/styles/globals.css",
 | 
				
			||||||
 | 
					    "baseColor": "slate",
 | 
				
			||||||
 | 
					    "cssVariables": true,
 | 
				
			||||||
 | 
					    "prefix": ""
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "aliases": {
 | 
				
			||||||
 | 
					    "components": "~/components",
 | 
				
			||||||
 | 
					    "utils": "~/lib/utils"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										971
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										971
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										11
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								package.json
									
									
									
									
									
								
							@ -12,14 +12,25 @@
 | 
				
			|||||||
    "start": "next start"
 | 
					    "start": "next start"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "@radix-ui/react-dropdown-menu": "^2.0.6",
 | 
				
			||||||
 | 
					    "@radix-ui/react-navigation-menu": "^1.1.4",
 | 
				
			||||||
 | 
					    "@radix-ui/react-select": "^2.0.0",
 | 
				
			||||||
 | 
					    "@radix-ui/react-slider": "^1.1.2",
 | 
				
			||||||
 | 
					    "@radix-ui/react-slot": "^1.0.2",
 | 
				
			||||||
 | 
					    "@radix-ui/react-toast": "^1.1.5",
 | 
				
			||||||
    "@t3-oss/env-nextjs": "^0.10.1",
 | 
					    "@t3-oss/env-nextjs": "^0.10.1",
 | 
				
			||||||
    "@vercel/postgres": "^0.8.0",
 | 
					    "@vercel/postgres": "^0.8.0",
 | 
				
			||||||
 | 
					    "class-variance-authority": "^0.7.0",
 | 
				
			||||||
 | 
					    "clsx": "^2.1.1",
 | 
				
			||||||
    "drizzle-orm": "^0.29.4",
 | 
					    "drizzle-orm": "^0.29.4",
 | 
				
			||||||
    "geist": "^1.3.0",
 | 
					    "geist": "^1.3.0",
 | 
				
			||||||
 | 
					    "lucide-react": "^0.379.0",
 | 
				
			||||||
    "next": "^15.0.0-rc.0",
 | 
					    "next": "^15.0.0-rc.0",
 | 
				
			||||||
    "postgres": "^3.4.3",
 | 
					    "postgres": "^3.4.3",
 | 
				
			||||||
    "react": "^19.0.0-rc-935180c7e0-20240524",
 | 
					    "react": "^19.0.0-rc-935180c7e0-20240524",
 | 
				
			||||||
    "react-dom": "^19.0.0-rc-935180c7e0-20240524",
 | 
					    "react-dom": "^19.0.0-rc-935180c7e0-20240524",
 | 
				
			||||||
 | 
					    "tailwind-merge": "^2.3.0",
 | 
				
			||||||
 | 
					    "tailwindcss-animate": "^1.0.7",
 | 
				
			||||||
    "zod": "^3.23.8"
 | 
					    "zod": "^3.23.8"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,8 @@
 | 
				
			|||||||
import FormCard from "./FormCard";
 | 
					import FormCard from "./_components/FormCard";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function App() {
 | 
					export default function App() {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="container flex w-full flex-col justify-center">
 | 
					    <div className="container flex w-full flex-col justify-center">
 | 
				
			||||||
      <h1>Yes hello</h1>
 | 
					 | 
				
			||||||
      <FormCard />
 | 
					      <FormCard />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
@ -1,5 +1,14 @@
 | 
				
			|||||||
"use client";
 | 
					"use client";
 | 
				
			||||||
import { useActionState, useEffect, useState } from "react";
 | 
					import { useActionState, useEffect, useState } from "react";
 | 
				
			||||||
 | 
					import { Button } from "~/components/ui/button";
 | 
				
			||||||
 | 
					import { Input } from "~/components/ui/input";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Select,
 | 
				
			||||||
 | 
					  SelectContent,
 | 
				
			||||||
 | 
					  SelectItem,
 | 
				
			||||||
 | 
					  SelectTrigger,
 | 
				
			||||||
 | 
					  SelectValue,
 | 
				
			||||||
 | 
					} from "~/components/ui/select";
 | 
				
			||||||
import { addWine } from "~/server/actions/addWine";
 | 
					import { addWine } from "~/server/actions/addWine";
 | 
				
			||||||
import { getProducers } from "~/server/actions/allProducers";
 | 
					import { getProducers } from "~/server/actions/allProducers";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -35,19 +44,33 @@ export default function CreateWine() {
 | 
				
			|||||||
    loadProducers();
 | 
					    loadProducers();
 | 
				
			||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="container flex flex-col gap-4">
 | 
					    <div className="flex w-full flex-col">
 | 
				
			||||||
      <form action={formAction}>
 | 
					      <form action={formAction} className="flex flex-col gap-2">
 | 
				
			||||||
        <input className="border-2" type="text" name="name" />
 | 
					        <Input
 | 
				
			||||||
        <select name="producer">
 | 
					          placeholder="Enter wine name"
 | 
				
			||||||
 | 
					          className="border-2"
 | 
				
			||||||
 | 
					          type="text"
 | 
				
			||||||
 | 
					          name="name"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <Select name="producer">
 | 
				
			||||||
 | 
					          <SelectTrigger className="w-[280px]">
 | 
				
			||||||
 | 
					            <SelectValue placeholder="Select a producer" />
 | 
				
			||||||
 | 
					          </SelectTrigger>
 | 
				
			||||||
 | 
					          <SelectContent>
 | 
				
			||||||
            {producers.map((producer, i) => (
 | 
					            {producers.map((producer, i) => (
 | 
				
			||||||
            <option key={i} value={producer.id}>
 | 
					              <SelectItem key={i} value={producer.id}>
 | 
				
			||||||
                {producer.name}
 | 
					                {producer.name}
 | 
				
			||||||
            </option>
 | 
					              </SelectItem>
 | 
				
			||||||
            ))}
 | 
					            ))}
 | 
				
			||||||
        </select>
 | 
					          </SelectContent>
 | 
				
			||||||
        <button type="submit">Add wine</button>
 | 
					        </Select>
 | 
				
			||||||
 | 
					        <Button type="submit">Add wine</Button>
 | 
				
			||||||
      </form>
 | 
					      </form>
 | 
				
			||||||
      {JSON.stringify(formState)}
 | 
					      {formState.message === "success" ? (
 | 
				
			||||||
 | 
					        <span className="text-green-400">Wine succesfully submitted</span>
 | 
				
			||||||
 | 
					      ) : (
 | 
				
			||||||
 | 
					        ""
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										126
									
								
								src/app/_components/FilterMenu.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/app/_components/FilterMenu.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,126 @@
 | 
				
			|||||||
 | 
					"use client";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  NavigationMenu,
 | 
				
			||||||
 | 
					  NavigationMenuContent,
 | 
				
			||||||
 | 
					  NavigationMenuItem,
 | 
				
			||||||
 | 
					  NavigationMenuList,
 | 
				
			||||||
 | 
					  NavigationMenuTrigger,
 | 
				
			||||||
 | 
					} from "~/components/ui/navigation-menu";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Input } from "~/components/ui/input";
 | 
				
			||||||
 | 
					import { Slider } from "~/components/ui/slider";
 | 
				
			||||||
 | 
					import { ChangeEvent, useState } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Filtermenu() {
 | 
				
			||||||
 | 
					  const [minPrice, setMinPrice] = useState<number>(0);
 | 
				
			||||||
 | 
					  const [maxPrice, setMaxPrice] = useState<number>(9999);
 | 
				
			||||||
 | 
					  const [sliderValues, setSliderValues] = useState<[number, number]>([0, 9999]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleMinPriceChange = (e: ChangeEvent<HTMLInputElement>): void => {
 | 
				
			||||||
 | 
					    const value = Math.min(Number(e.target.value), maxPrice - 1);
 | 
				
			||||||
 | 
					    setMinPrice(value);
 | 
				
			||||||
 | 
					    setSliderValues([value, sliderValues[1]]);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleMaxPriceChange = (e: ChangeEvent<HTMLInputElement>) => {
 | 
				
			||||||
 | 
					    const value = Math.max(Number(e.target.value), minPrice + 1);
 | 
				
			||||||
 | 
					    setMaxPrice(value);
 | 
				
			||||||
 | 
					    setSliderValues([sliderValues[0], value]);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleSliderChange = (values: [number, number]) => {
 | 
				
			||||||
 | 
					    setSliderValues(values);
 | 
				
			||||||
 | 
					    setMinPrice(values[0]);
 | 
				
			||||||
 | 
					    setMaxPrice(values[1]);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="container flex justify-center gap-2">
 | 
				
			||||||
 | 
					      {/* TODO:
 | 
				
			||||||
 | 
					        Checkboxes instead of menuitems. Check boxes are added as badges under the search bar */}
 | 
				
			||||||
 | 
					      {/* Price filter */}
 | 
				
			||||||
 | 
					      <NavigationMenu>
 | 
				
			||||||
 | 
					        <NavigationMenuList>
 | 
				
			||||||
 | 
					          <NavigationMenuItem>
 | 
				
			||||||
 | 
					            <NavigationMenuTrigger>Price</NavigationMenuTrigger>
 | 
				
			||||||
 | 
					            <NavigationMenuContent className="min-w- flex overflow-auto p-2">
 | 
				
			||||||
 | 
					              <Input
 | 
				
			||||||
 | 
					                value={minPrice}
 | 
				
			||||||
 | 
					                onChange={handleMinPriceChange}
 | 
				
			||||||
 | 
					                className="min-w-20"
 | 
				
			||||||
 | 
					                type="number"
 | 
				
			||||||
 | 
					                defaultValue={0}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					              <Slider
 | 
				
			||||||
 | 
					                value={sliderValues}
 | 
				
			||||||
 | 
					                onValueChange={handleSliderChange}
 | 
				
			||||||
 | 
					                className="min-w-36"
 | 
				
			||||||
 | 
					                min={0}
 | 
				
			||||||
 | 
					                max={9999}
 | 
				
			||||||
 | 
					                defaultValue={[0, 9999]}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					              <Input
 | 
				
			||||||
 | 
					                value={maxPrice}
 | 
				
			||||||
 | 
					                onChange={handleMaxPriceChange}
 | 
				
			||||||
 | 
					                className="min-w-20"
 | 
				
			||||||
 | 
					                type="number"
 | 
				
			||||||
 | 
					                defaultValue={9999}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </NavigationMenuContent>
 | 
				
			||||||
 | 
					          </NavigationMenuItem>
 | 
				
			||||||
 | 
					        </NavigationMenuList>
 | 
				
			||||||
 | 
					      </NavigationMenu>
 | 
				
			||||||
 | 
					      <NavigationMenu>
 | 
				
			||||||
 | 
					        <NavigationMenuList>
 | 
				
			||||||
 | 
					          <NavigationMenuItem>
 | 
				
			||||||
 | 
					            <NavigationMenuTrigger>Type</NavigationMenuTrigger>
 | 
				
			||||||
 | 
					            <NavigationMenuContent>
 | 
				
			||||||
 | 
					              <p>Anything goes?</p>
 | 
				
			||||||
 | 
					            </NavigationMenuContent>
 | 
				
			||||||
 | 
					          </NavigationMenuItem>
 | 
				
			||||||
 | 
					        </NavigationMenuList>
 | 
				
			||||||
 | 
					      </NavigationMenu>
 | 
				
			||||||
 | 
					      {/* Producer filter */}
 | 
				
			||||||
 | 
					      <NavigationMenu>
 | 
				
			||||||
 | 
					        <NavigationMenuList>
 | 
				
			||||||
 | 
					          <NavigationMenuItem>
 | 
				
			||||||
 | 
					            <NavigationMenuTrigger>Producer</NavigationMenuTrigger>
 | 
				
			||||||
 | 
					            <NavigationMenuContent className="w-fit p-2">
 | 
				
			||||||
 | 
					              <Input placeholder="producer filter" />
 | 
				
			||||||
 | 
					              <ul>
 | 
				
			||||||
 | 
					                <li>checkboxes for producer filter</li>
 | 
				
			||||||
 | 
					              </ul>
 | 
				
			||||||
 | 
					            </NavigationMenuContent>
 | 
				
			||||||
 | 
					          </NavigationMenuItem>
 | 
				
			||||||
 | 
					        </NavigationMenuList>
 | 
				
			||||||
 | 
					      </NavigationMenu>
 | 
				
			||||||
 | 
					      {/* Country Filter */}
 | 
				
			||||||
 | 
					      <NavigationMenu>
 | 
				
			||||||
 | 
					        <NavigationMenuList>
 | 
				
			||||||
 | 
					          <NavigationMenuItem>
 | 
				
			||||||
 | 
					            <NavigationMenuTrigger>Producer</NavigationMenuTrigger>
 | 
				
			||||||
 | 
					            <NavigationMenuContent className="w-fit p-2">
 | 
				
			||||||
 | 
					              <ul>
 | 
				
			||||||
 | 
					                <li>ccountry filter</li>
 | 
				
			||||||
 | 
					              </ul>
 | 
				
			||||||
 | 
					            </NavigationMenuContent>
 | 
				
			||||||
 | 
					          </NavigationMenuItem>
 | 
				
			||||||
 | 
					        </NavigationMenuList>
 | 
				
			||||||
 | 
					      </NavigationMenu>
 | 
				
			||||||
 | 
					      {/* Region Filter */}
 | 
				
			||||||
 | 
					      <NavigationMenu>
 | 
				
			||||||
 | 
					        <NavigationMenuList>
 | 
				
			||||||
 | 
					          <NavigationMenuItem>
 | 
				
			||||||
 | 
					            <NavigationMenuTrigger>Region</NavigationMenuTrigger>
 | 
				
			||||||
 | 
					            <NavigationMenuContent className="w-fit p-2">
 | 
				
			||||||
 | 
					              <Input placeholder="producer filter" />
 | 
				
			||||||
 | 
					              <ul>
 | 
				
			||||||
 | 
					                <li>checkboxes for producer filter</li>
 | 
				
			||||||
 | 
					              </ul>
 | 
				
			||||||
 | 
					            </NavigationMenuContent>
 | 
				
			||||||
 | 
					          </NavigationMenuItem>
 | 
				
			||||||
 | 
					        </NavigationMenuList>
 | 
				
			||||||
 | 
					      </NavigationMenu>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,10 +1,26 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Card,
 | 
				
			||||||
 | 
					  CardContent,
 | 
				
			||||||
 | 
					  CardDescription,
 | 
				
			||||||
 | 
					  CardFooter,
 | 
				
			||||||
 | 
					  CardHeader,
 | 
				
			||||||
 | 
					  CardTitle,
 | 
				
			||||||
 | 
					} from "~/components/ui/card";
 | 
				
			||||||
import CreateWine from "./CreateWine";
 | 
					import CreateWine from "./CreateWine";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function FormCard() {
 | 
					export default function FormCard() {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="mx-auto p-4 shadow-md">
 | 
					    <Card>
 | 
				
			||||||
      <h1>Add a new wine</h1>
 | 
					      <CardHeader>
 | 
				
			||||||
 | 
					        <CardTitle>Add wine</CardTitle>
 | 
				
			||||||
 | 
					        <CardDescription>All fields are required so get to it!</CardDescription>
 | 
				
			||||||
 | 
					      </CardHeader>
 | 
				
			||||||
 | 
					      <CardContent>
 | 
				
			||||||
        <CreateWine />
 | 
					        <CreateWine />
 | 
				
			||||||
    </div>
 | 
					      </CardContent>
 | 
				
			||||||
 | 
					      <CardFooter>
 | 
				
			||||||
 | 
					        <p>Card Footer</p>
 | 
				
			||||||
 | 
					      </CardFooter>
 | 
				
			||||||
 | 
					    </Card>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,14 @@
 | 
				
			|||||||
import "~/styles/globals.css";
 | 
					import "~/styles/globals.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { GeistSans } from "geist/font/sans";
 | 
					import { Inter as FontSans } from "next/font/google";
 | 
				
			||||||
 | 
					import { cn } from "~/lib/utils";
 | 
				
			||||||
 | 
					import TopNav from "./_components/FilterMenu";
 | 
				
			||||||
 | 
					import Filtermenu from "./_components/FilterMenu";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const fontSans = FontSans({
 | 
				
			||||||
 | 
					  subsets: ["latin"],
 | 
				
			||||||
 | 
					  variable: "--font-sans",
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const metadata = {
 | 
					export const metadata = {
 | 
				
			||||||
  title: "Create T3 App",
 | 
					  title: "Create T3 App",
 | 
				
			||||||
@ -14,8 +22,16 @@ export default function RootLayout({
 | 
				
			|||||||
  children: React.ReactNode;
 | 
					  children: React.ReactNode;
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <html lang="en" className={`${GeistSans.variable}`}>
 | 
					    <html lang="en">
 | 
				
			||||||
      <body>{children}</body>
 | 
					      <body
 | 
				
			||||||
 | 
					        className={cn(
 | 
				
			||||||
 | 
					          "min-h-screen bg-background font-sans antialiased",
 | 
				
			||||||
 | 
					          fontSans.variable,
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <Filtermenu />
 | 
				
			||||||
 | 
					        {children}
 | 
				
			||||||
 | 
					      </body>
 | 
				
			||||||
    </html>
 | 
					    </html>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,8 @@
 | 
				
			|||||||
import App from "./_components/App";
 | 
					import App from "./App";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async function HomePage() {
 | 
					export default async function HomePage() {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <main>
 | 
					    <main className="w-full">
 | 
				
			||||||
      <App />
 | 
					      <App />
 | 
				
			||||||
    </main>
 | 
					    </main>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										56
									
								
								src/components/ui/button.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/components/ui/button.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					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 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    variants: {
 | 
				
			||||||
 | 
					      variant: {
 | 
				
			||||||
 | 
					        default: "bg-primary text-primary-foreground hover:bg-primary/90",
 | 
				
			||||||
 | 
					        destructive:
 | 
				
			||||||
 | 
					          "bg-destructive text-destructive-foreground hover:bg-destructive/90",
 | 
				
			||||||
 | 
					        outline:
 | 
				
			||||||
 | 
					          "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
 | 
				
			||||||
 | 
					        secondary:
 | 
				
			||||||
 | 
					          "bg-secondary text-secondary-foreground hover:bg-secondary/80",
 | 
				
			||||||
 | 
					        ghost: "hover:bg-accent hover:text-accent-foreground",
 | 
				
			||||||
 | 
					        link: "text-primary underline-offset-4 hover:underline",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      size: {
 | 
				
			||||||
 | 
					        default: "h-10 px-4 py-2",
 | 
				
			||||||
 | 
					        sm: "h-9 rounded-md px-3",
 | 
				
			||||||
 | 
					        lg: "h-11 rounded-md px-8",
 | 
				
			||||||
 | 
					        icon: "h-10 w-10",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    defaultVariants: {
 | 
				
			||||||
 | 
					      variant: "default",
 | 
				
			||||||
 | 
					      size: "default",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ButtonProps
 | 
				
			||||||
 | 
					  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
 | 
				
			||||||
 | 
					    VariantProps<typeof buttonVariants> {
 | 
				
			||||||
 | 
					  asChild?: boolean
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
 | 
				
			||||||
 | 
					  ({ className, variant, size, asChild = false, ...props }, ref) => {
 | 
				
			||||||
 | 
					    const Comp = asChild ? Slot : "button"
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <Comp
 | 
				
			||||||
 | 
					        className={cn(buttonVariants({ variant, size, className }))}
 | 
				
			||||||
 | 
					        ref={ref}
 | 
				
			||||||
 | 
					        {...props}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					Button.displayName = "Button"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Button, buttonVariants }
 | 
				
			||||||
							
								
								
									
										79
									
								
								src/components/ui/card.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/components/ui/card.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "~/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Card = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLDivElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLDivElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "rounded-lg border bg-card text-card-foreground shadow-sm",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					Card.displayName = "Card"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardHeader = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLDivElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLDivElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("flex flex-col space-y-1.5 p-6", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					CardHeader.displayName = "CardHeader"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardTitle = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLParagraphElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLHeadingElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <h3
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "text-2xl font-semibold leading-none tracking-tight",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					CardTitle.displayName = "CardTitle"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardDescription = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLParagraphElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLParagraphElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <p
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("text-sm text-muted-foreground", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					CardDescription.displayName = "CardDescription"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardContent = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLDivElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLDivElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					CardContent.displayName = "CardContent"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardFooter = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLDivElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLDivElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("flex items-center p-6 pt-0", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					CardFooter.displayName = "CardFooter"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
 | 
				
			||||||
							
								
								
									
										200
									
								
								src/components/ui/dropdown-menu.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								src/components/ui/dropdown-menu.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,200 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
 | 
				
			||||||
 | 
					import { Check, ChevronRight, Circle } from "lucide-react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "~/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DropdownMenu = DropdownMenuPrimitive.Root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DropdownMenuGroup = DropdownMenuPrimitive.Group
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DropdownMenuPortal = DropdownMenuPrimitive.Portal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DropdownMenuSub = DropdownMenuPrimitive.Sub
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DropdownMenuSubTrigger = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
 | 
				
			||||||
 | 
					    inset?: boolean
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					>(({ className, inset, children, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <DropdownMenuPrimitive.SubTrigger
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
 | 
				
			||||||
 | 
					      inset && "pl-8",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    {children}
 | 
				
			||||||
 | 
					    <ChevronRight className="ml-auto h-4 w-4" />
 | 
				
			||||||
 | 
					  </DropdownMenuPrimitive.SubTrigger>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					DropdownMenuSubTrigger.displayName =
 | 
				
			||||||
 | 
					  DropdownMenuPrimitive.SubTrigger.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DropdownMenuSubContent = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <DropdownMenuPrimitive.SubContent
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					DropdownMenuSubContent.displayName =
 | 
				
			||||||
 | 
					  DropdownMenuPrimitive.SubContent.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DropdownMenuContent = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof DropdownMenuPrimitive.Content>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
 | 
				
			||||||
 | 
					>(({ className, sideOffset = 4, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <DropdownMenuPrimitive.Portal>
 | 
				
			||||||
 | 
					    <DropdownMenuPrimitive.Content
 | 
				
			||||||
 | 
					      ref={ref}
 | 
				
			||||||
 | 
					      sideOffset={sideOffset}
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </DropdownMenuPrimitive.Portal>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DropdownMenuItem = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof DropdownMenuPrimitive.Item>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
 | 
				
			||||||
 | 
					    inset?: boolean
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					>(({ className, inset, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <DropdownMenuPrimitive.Item
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
 | 
				
			||||||
 | 
					      inset && "pl-8",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DropdownMenuCheckboxItem = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
 | 
				
			||||||
 | 
					>(({ className, children, checked, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <DropdownMenuPrimitive.CheckboxItem
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    checked={checked}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
 | 
				
			||||||
 | 
					      <DropdownMenuPrimitive.ItemIndicator>
 | 
				
			||||||
 | 
					        <Check className="h-4 w-4" />
 | 
				
			||||||
 | 
					      </DropdownMenuPrimitive.ItemIndicator>
 | 
				
			||||||
 | 
					    </span>
 | 
				
			||||||
 | 
					    {children}
 | 
				
			||||||
 | 
					  </DropdownMenuPrimitive.CheckboxItem>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					DropdownMenuCheckboxItem.displayName =
 | 
				
			||||||
 | 
					  DropdownMenuPrimitive.CheckboxItem.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DropdownMenuRadioItem = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
 | 
				
			||||||
 | 
					>(({ className, children, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <DropdownMenuPrimitive.RadioItem
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
 | 
				
			||||||
 | 
					      <DropdownMenuPrimitive.ItemIndicator>
 | 
				
			||||||
 | 
					        <Circle className="h-2 w-2 fill-current" />
 | 
				
			||||||
 | 
					      </DropdownMenuPrimitive.ItemIndicator>
 | 
				
			||||||
 | 
					    </span>
 | 
				
			||||||
 | 
					    {children}
 | 
				
			||||||
 | 
					  </DropdownMenuPrimitive.RadioItem>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DropdownMenuLabel = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof DropdownMenuPrimitive.Label>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
 | 
				
			||||||
 | 
					    inset?: boolean
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					>(({ className, inset, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <DropdownMenuPrimitive.Label
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "px-2 py-1.5 text-sm font-semibold",
 | 
				
			||||||
 | 
					      inset && "pl-8",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DropdownMenuSeparator = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <DropdownMenuPrimitive.Separator
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("-mx-1 my-1 h-px bg-muted", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DropdownMenuShortcut = ({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.HTMLAttributes<HTMLSpanElement>) => {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <span
 | 
				
			||||||
 | 
					      className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					  DropdownMenu,
 | 
				
			||||||
 | 
					  DropdownMenuTrigger,
 | 
				
			||||||
 | 
					  DropdownMenuContent,
 | 
				
			||||||
 | 
					  DropdownMenuItem,
 | 
				
			||||||
 | 
					  DropdownMenuCheckboxItem,
 | 
				
			||||||
 | 
					  DropdownMenuRadioItem,
 | 
				
			||||||
 | 
					  DropdownMenuLabel,
 | 
				
			||||||
 | 
					  DropdownMenuSeparator,
 | 
				
			||||||
 | 
					  DropdownMenuShortcut,
 | 
				
			||||||
 | 
					  DropdownMenuGroup,
 | 
				
			||||||
 | 
					  DropdownMenuPortal,
 | 
				
			||||||
 | 
					  DropdownMenuSub,
 | 
				
			||||||
 | 
					  DropdownMenuSubContent,
 | 
				
			||||||
 | 
					  DropdownMenuSubTrigger,
 | 
				
			||||||
 | 
					  DropdownMenuRadioGroup,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										25
									
								
								src/components/ui/input.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/components/ui/input.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "~/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface InputProps
 | 
				
			||||||
 | 
					  extends React.InputHTMLAttributes<HTMLInputElement> {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Input = React.forwardRef<HTMLInputElement, InputProps>(
 | 
				
			||||||
 | 
					  ({ className, type, ...props }, ref) => {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <input
 | 
				
			||||||
 | 
					        type={type}
 | 
				
			||||||
 | 
					        className={cn(
 | 
				
			||||||
 | 
					          "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
 | 
				
			||||||
 | 
					          className
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					        ref={ref}
 | 
				
			||||||
 | 
					        {...props}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					Input.displayName = "Input"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Input }
 | 
				
			||||||
							
								
								
									
										128
									
								
								src/components/ui/navigation-menu.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/components/ui/navigation-menu.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,128 @@
 | 
				
			|||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
 | 
				
			||||||
 | 
					import { cva } from "class-variance-authority"
 | 
				
			||||||
 | 
					import { ChevronDown } from "lucide-react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "~/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenu = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof NavigationMenuPrimitive.Root>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
 | 
				
			||||||
 | 
					>(({ className, children, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <NavigationMenuPrimitive.Root
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "relative z-10 flex max-w-max flex-1 items-center justify-center",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    {children}
 | 
				
			||||||
 | 
					    <NavigationMenuViewport />
 | 
				
			||||||
 | 
					  </NavigationMenuPrimitive.Root>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuList = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof NavigationMenuPrimitive.List>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <NavigationMenuPrimitive.List
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "group flex flex-1 list-none items-center justify-center space-x-1",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuItem = NavigationMenuPrimitive.Item
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const navigationMenuTriggerStyle = cva(
 | 
				
			||||||
 | 
					  "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuTrigger = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
 | 
				
			||||||
 | 
					>(({ className, children, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <NavigationMenuPrimitive.Trigger
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(navigationMenuTriggerStyle(), "group", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    {children}{" "}
 | 
				
			||||||
 | 
					    <ChevronDown
 | 
				
			||||||
 | 
					      className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
 | 
				
			||||||
 | 
					      aria-hidden="true"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </NavigationMenuPrimitive.Trigger>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuContent = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof NavigationMenuPrimitive.Content>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <NavigationMenuPrimitive.Content
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuLink = NavigationMenuPrimitive.Link
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuViewport = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <div className={cn("absolute left-0 top-full flex justify-center")}>
 | 
				
			||||||
 | 
					    <NavigationMenuPrimitive.Viewport
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      ref={ref}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					NavigationMenuViewport.displayName =
 | 
				
			||||||
 | 
					  NavigationMenuPrimitive.Viewport.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuIndicator = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <NavigationMenuPrimitive.Indicator
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
 | 
				
			||||||
 | 
					  </NavigationMenuPrimitive.Indicator>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					NavigationMenuIndicator.displayName =
 | 
				
			||||||
 | 
					  NavigationMenuPrimitive.Indicator.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					  navigationMenuTriggerStyle,
 | 
				
			||||||
 | 
					  NavigationMenu,
 | 
				
			||||||
 | 
					  NavigationMenuList,
 | 
				
			||||||
 | 
					  NavigationMenuItem,
 | 
				
			||||||
 | 
					  NavigationMenuContent,
 | 
				
			||||||
 | 
					  NavigationMenuTrigger,
 | 
				
			||||||
 | 
					  NavigationMenuLink,
 | 
				
			||||||
 | 
					  NavigationMenuIndicator,
 | 
				
			||||||
 | 
					  NavigationMenuViewport,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										160
									
								
								src/components/ui/select.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								src/components/ui/select.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,160 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import * as SelectPrimitive from "@radix-ui/react-select"
 | 
				
			||||||
 | 
					import { Check, ChevronDown, ChevronUp } from "lucide-react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "~/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Select = SelectPrimitive.Root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SelectGroup = SelectPrimitive.Group
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SelectValue = SelectPrimitive.Value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SelectTrigger = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof SelectPrimitive.Trigger>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
 | 
				
			||||||
 | 
					>(({ className, children, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <SelectPrimitive.Trigger
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    {children}
 | 
				
			||||||
 | 
					    <SelectPrimitive.Icon asChild>
 | 
				
			||||||
 | 
					      <ChevronDown className="h-4 w-4 opacity-50" />
 | 
				
			||||||
 | 
					    </SelectPrimitive.Icon>
 | 
				
			||||||
 | 
					  </SelectPrimitive.Trigger>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SelectScrollUpButton = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <SelectPrimitive.ScrollUpButton
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "flex cursor-default items-center justify-center py-1",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <ChevronUp className="h-4 w-4" />
 | 
				
			||||||
 | 
					  </SelectPrimitive.ScrollUpButton>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SelectScrollDownButton = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <SelectPrimitive.ScrollDownButton
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "flex cursor-default items-center justify-center py-1",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <ChevronDown className="h-4 w-4" />
 | 
				
			||||||
 | 
					  </SelectPrimitive.ScrollDownButton>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					SelectScrollDownButton.displayName =
 | 
				
			||||||
 | 
					  SelectPrimitive.ScrollDownButton.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SelectContent = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof SelectPrimitive.Content>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
 | 
				
			||||||
 | 
					>(({ className, children, position = "popper", ...props }, ref) => (
 | 
				
			||||||
 | 
					  <SelectPrimitive.Portal>
 | 
				
			||||||
 | 
					    <SelectPrimitive.Content
 | 
				
			||||||
 | 
					      ref={ref}
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md 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",
 | 
				
			||||||
 | 
					        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}
 | 
				
			||||||
 | 
					      {...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)]"
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        {children}
 | 
				
			||||||
 | 
					      </SelectPrimitive.Viewport>
 | 
				
			||||||
 | 
					      <SelectScrollDownButton />
 | 
				
			||||||
 | 
					    </SelectPrimitive.Content>
 | 
				
			||||||
 | 
					  </SelectPrimitive.Portal>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					SelectContent.displayName = SelectPrimitive.Content.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SelectLabel = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof SelectPrimitive.Label>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <SelectPrimitive.Label
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					SelectLabel.displayName = SelectPrimitive.Label.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SelectItem = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof SelectPrimitive.Item>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
 | 
				
			||||||
 | 
					>(({ className, children, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <SelectPrimitive.Item
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
 | 
				
			||||||
 | 
					      <SelectPrimitive.ItemIndicator>
 | 
				
			||||||
 | 
					        <Check className="h-4 w-4" />
 | 
				
			||||||
 | 
					      </SelectPrimitive.ItemIndicator>
 | 
				
			||||||
 | 
					    </span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
 | 
				
			||||||
 | 
					  </SelectPrimitive.Item>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					SelectItem.displayName = SelectPrimitive.Item.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SelectSeparator = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof SelectPrimitive.Separator>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <SelectPrimitive.Separator
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("-mx-1 my-1 h-px bg-muted", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					SelectSeparator.displayName = SelectPrimitive.Separator.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					  Select,
 | 
				
			||||||
 | 
					  SelectGroup,
 | 
				
			||||||
 | 
					  SelectValue,
 | 
				
			||||||
 | 
					  SelectTrigger,
 | 
				
			||||||
 | 
					  SelectContent,
 | 
				
			||||||
 | 
					  SelectLabel,
 | 
				
			||||||
 | 
					  SelectItem,
 | 
				
			||||||
 | 
					  SelectSeparator,
 | 
				
			||||||
 | 
					  SelectScrollUpButton,
 | 
				
			||||||
 | 
					  SelectScrollDownButton,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										29
									
								
								src/components/ui/slider.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/components/ui/slider.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as React from "react";
 | 
				
			||||||
 | 
					import * as SliderPrimitive from "@radix-ui/react-slider";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "~/lib/utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Slider = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof SliderPrimitive.Root>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <SliderPrimitive.Root
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "relative flex w-full touch-none select-none items-center",
 | 
				
			||||||
 | 
					      className,
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
 | 
				
			||||||
 | 
					      <SliderPrimitive.Range className="absolute h-full bg-primary" />
 | 
				
			||||||
 | 
					    </SliderPrimitive.Track>
 | 
				
			||||||
 | 
					    <SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
 | 
				
			||||||
 | 
					    <SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
 | 
				
			||||||
 | 
					  </SliderPrimitive.Root>
 | 
				
			||||||
 | 
					));
 | 
				
			||||||
 | 
					Slider.displayName = SliderPrimitive.Root.displayName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Slider };
 | 
				
			||||||
							
								
								
									
										129
									
								
								src/components/ui/toast.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								src/components/ui/toast.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,129 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import * as ToastPrimitives from "@radix-ui/react-toast"
 | 
				
			||||||
 | 
					import { cva, type VariantProps } from "class-variance-authority"
 | 
				
			||||||
 | 
					import { X } from "lucide-react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "~/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToastProvider = ToastPrimitives.Provider
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToastViewport = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof ToastPrimitives.Viewport>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <ToastPrimitives.Viewport
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					ToastViewport.displayName = ToastPrimitives.Viewport.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const toastVariants = cva(
 | 
				
			||||||
 | 
					  "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    variants: {
 | 
				
			||||||
 | 
					      variant: {
 | 
				
			||||||
 | 
					        default: "border bg-background text-foreground",
 | 
				
			||||||
 | 
					        destructive:
 | 
				
			||||||
 | 
					          "destructive group border-destructive bg-destructive text-destructive-foreground",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    defaultVariants: {
 | 
				
			||||||
 | 
					      variant: "default",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Toast = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof ToastPrimitives.Root>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
 | 
				
			||||||
 | 
					    VariantProps<typeof toastVariants>
 | 
				
			||||||
 | 
					>(({ className, variant, ...props }, ref) => {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <ToastPrimitives.Root
 | 
				
			||||||
 | 
					      ref={ref}
 | 
				
			||||||
 | 
					      className={cn(toastVariants({ variant }), className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					Toast.displayName = ToastPrimitives.Root.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToastAction = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof ToastPrimitives.Action>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <ToastPrimitives.Action
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					ToastAction.displayName = ToastPrimitives.Action.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToastClose = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof ToastPrimitives.Close>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <ToastPrimitives.Close
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    toast-close=""
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <X className="h-4 w-4" />
 | 
				
			||||||
 | 
					  </ToastPrimitives.Close>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					ToastClose.displayName = ToastPrimitives.Close.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToastTitle = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof ToastPrimitives.Title>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <ToastPrimitives.Title
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("text-sm font-semibold", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					ToastTitle.displayName = ToastPrimitives.Title.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToastDescription = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof ToastPrimitives.Description>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <ToastPrimitives.Description
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("text-sm opacity-90", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					ToastDescription.displayName = ToastPrimitives.Description.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ToastActionElement = React.ReactElement<typeof ToastAction>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					  type ToastProps,
 | 
				
			||||||
 | 
					  type ToastActionElement,
 | 
				
			||||||
 | 
					  ToastProvider,
 | 
				
			||||||
 | 
					  ToastViewport,
 | 
				
			||||||
 | 
					  Toast,
 | 
				
			||||||
 | 
					  ToastTitle,
 | 
				
			||||||
 | 
					  ToastDescription,
 | 
				
			||||||
 | 
					  ToastClose,
 | 
				
			||||||
 | 
					  ToastAction,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										35
									
								
								src/components/ui/toaster.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/components/ui/toaster.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Toast,
 | 
				
			||||||
 | 
					  ToastClose,
 | 
				
			||||||
 | 
					  ToastDescription,
 | 
				
			||||||
 | 
					  ToastProvider,
 | 
				
			||||||
 | 
					  ToastTitle,
 | 
				
			||||||
 | 
					  ToastViewport,
 | 
				
			||||||
 | 
					} from "~/components/ui/toast"
 | 
				
			||||||
 | 
					import { useToast } from "~/components/ui/use-toast"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function Toaster() {
 | 
				
			||||||
 | 
					  const { toasts } = useToast()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <ToastProvider>
 | 
				
			||||||
 | 
					      {toasts.map(function ({ id, title, description, action, ...props }) {
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					          <Toast key={id} {...props}>
 | 
				
			||||||
 | 
					            <div className="grid gap-1">
 | 
				
			||||||
 | 
					              {title && <ToastTitle>{title}</ToastTitle>}
 | 
				
			||||||
 | 
					              {description && (
 | 
				
			||||||
 | 
					                <ToastDescription>{description}</ToastDescription>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            {action}
 | 
				
			||||||
 | 
					            <ToastClose />
 | 
				
			||||||
 | 
					          </Toast>
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      })}
 | 
				
			||||||
 | 
					      <ToastViewport />
 | 
				
			||||||
 | 
					    </ToastProvider>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										194
									
								
								src/components/ui/use-toast.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								src/components/ui/use-toast.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,194 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Inspired by react-hot-toast library
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  ToastActionElement,
 | 
				
			||||||
 | 
					  ToastProps,
 | 
				
			||||||
 | 
					} from "~/components/ui/toast"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TOAST_LIMIT = 1
 | 
				
			||||||
 | 
					const TOAST_REMOVE_DELAY = 1000000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ToasterToast = ToastProps & {
 | 
				
			||||||
 | 
					  id: string
 | 
				
			||||||
 | 
					  title?: React.ReactNode
 | 
				
			||||||
 | 
					  description?: React.ReactNode
 | 
				
			||||||
 | 
					  action?: ToastActionElement
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const actionTypes = {
 | 
				
			||||||
 | 
					  ADD_TOAST: "ADD_TOAST",
 | 
				
			||||||
 | 
					  UPDATE_TOAST: "UPDATE_TOAST",
 | 
				
			||||||
 | 
					  DISMISS_TOAST: "DISMISS_TOAST",
 | 
				
			||||||
 | 
					  REMOVE_TOAST: "REMOVE_TOAST",
 | 
				
			||||||
 | 
					} as const
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let count = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function genId() {
 | 
				
			||||||
 | 
					  count = (count + 1) % Number.MAX_SAFE_INTEGER
 | 
				
			||||||
 | 
					  return count.toString()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ActionType = typeof actionTypes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Action =
 | 
				
			||||||
 | 
					  | {
 | 
				
			||||||
 | 
					      type: ActionType["ADD_TOAST"]
 | 
				
			||||||
 | 
					      toast: ToasterToast
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  | {
 | 
				
			||||||
 | 
					      type: ActionType["UPDATE_TOAST"]
 | 
				
			||||||
 | 
					      toast: Partial<ToasterToast>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  | {
 | 
				
			||||||
 | 
					      type: ActionType["DISMISS_TOAST"]
 | 
				
			||||||
 | 
					      toastId?: ToasterToast["id"]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  | {
 | 
				
			||||||
 | 
					      type: ActionType["REMOVE_TOAST"]
 | 
				
			||||||
 | 
					      toastId?: ToasterToast["id"]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface State {
 | 
				
			||||||
 | 
					  toasts: ToasterToast[]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const addToRemoveQueue = (toastId: string) => {
 | 
				
			||||||
 | 
					  if (toastTimeouts.has(toastId)) {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const timeout = setTimeout(() => {
 | 
				
			||||||
 | 
					    toastTimeouts.delete(toastId)
 | 
				
			||||||
 | 
					    dispatch({
 | 
				
			||||||
 | 
					      type: "REMOVE_TOAST",
 | 
				
			||||||
 | 
					      toastId: toastId,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }, TOAST_REMOVE_DELAY)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  toastTimeouts.set(toastId, timeout)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const reducer = (state: State, action: Action): State => {
 | 
				
			||||||
 | 
					  switch (action.type) {
 | 
				
			||||||
 | 
					    case "ADD_TOAST":
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        ...state,
 | 
				
			||||||
 | 
					        toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case "UPDATE_TOAST":
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        ...state,
 | 
				
			||||||
 | 
					        toasts: state.toasts.map((t) =>
 | 
				
			||||||
 | 
					          t.id === action.toast.id ? { ...t, ...action.toast } : t
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case "DISMISS_TOAST": {
 | 
				
			||||||
 | 
					      const { toastId } = action
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // ! Side effects ! - This could be extracted into a dismissToast() action,
 | 
				
			||||||
 | 
					      // but I'll keep it here for simplicity
 | 
				
			||||||
 | 
					      if (toastId) {
 | 
				
			||||||
 | 
					        addToRemoveQueue(toastId)
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        state.toasts.forEach((toast) => {
 | 
				
			||||||
 | 
					          addToRemoveQueue(toast.id)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        ...state,
 | 
				
			||||||
 | 
					        toasts: state.toasts.map((t) =>
 | 
				
			||||||
 | 
					          t.id === toastId || toastId === undefined
 | 
				
			||||||
 | 
					            ? {
 | 
				
			||||||
 | 
					                ...t,
 | 
				
			||||||
 | 
					                open: false,
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            : t
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case "REMOVE_TOAST":
 | 
				
			||||||
 | 
					      if (action.toastId === undefined) {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          ...state,
 | 
				
			||||||
 | 
					          toasts: [],
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        ...state,
 | 
				
			||||||
 | 
					        toasts: state.toasts.filter((t) => t.id !== action.toastId),
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const listeners: Array<(state: State) => void> = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let memoryState: State = { toasts: [] }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function dispatch(action: Action) {
 | 
				
			||||||
 | 
					  memoryState = reducer(memoryState, action)
 | 
				
			||||||
 | 
					  listeners.forEach((listener) => {
 | 
				
			||||||
 | 
					    listener(memoryState)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Toast = Omit<ToasterToast, "id">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function toast({ ...props }: Toast) {
 | 
				
			||||||
 | 
					  const id = genId()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const update = (props: ToasterToast) =>
 | 
				
			||||||
 | 
					    dispatch({
 | 
				
			||||||
 | 
					      type: "UPDATE_TOAST",
 | 
				
			||||||
 | 
					      toast: { ...props, id },
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  dispatch({
 | 
				
			||||||
 | 
					    type: "ADD_TOAST",
 | 
				
			||||||
 | 
					    toast: {
 | 
				
			||||||
 | 
					      ...props,
 | 
				
			||||||
 | 
					      id,
 | 
				
			||||||
 | 
					      open: true,
 | 
				
			||||||
 | 
					      onOpenChange: (open) => {
 | 
				
			||||||
 | 
					        if (!open) dismiss()
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    id: id,
 | 
				
			||||||
 | 
					    dismiss,
 | 
				
			||||||
 | 
					    update,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function useToast() {
 | 
				
			||||||
 | 
					  const [state, setState] = React.useState<State>(memoryState)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  React.useEffect(() => {
 | 
				
			||||||
 | 
					    listeners.push(setState)
 | 
				
			||||||
 | 
					    return () => {
 | 
				
			||||||
 | 
					      const index = listeners.indexOf(setState)
 | 
				
			||||||
 | 
					      if (index > -1) {
 | 
				
			||||||
 | 
					        listeners.splice(index, 1)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [state])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    ...state,
 | 
				
			||||||
 | 
					    toast,
 | 
				
			||||||
 | 
					    dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { useToast, toast }
 | 
				
			||||||
							
								
								
									
										6
									
								
								src/lib/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/lib/utils.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					import { type ClassValue, clsx } from "clsx"
 | 
				
			||||||
 | 
					import { twMerge } from "tailwind-merge"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function cn(...inputs: ClassValue[]) {
 | 
				
			||||||
 | 
					  return twMerge(clsx(inputs))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,3 +1,76 @@
 | 
				
			|||||||
@tailwind base;
 | 
					@tailwind base;
 | 
				
			||||||
@tailwind components;
 | 
					  @tailwind components;
 | 
				
			||||||
@tailwind utilities;
 | 
					  @tailwind utilities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @layer base {
 | 
				
			||||||
 | 
					    :root {
 | 
				
			||||||
 | 
					      --background: 0 0% 100%;
 | 
				
			||||||
 | 
					      --foreground: 222.2 84% 4.9%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --card: 0 0% 100%;
 | 
				
			||||||
 | 
					      --card-foreground: 222.2 84% 4.9%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --popover: 0 0% 100%;
 | 
				
			||||||
 | 
					      --popover-foreground: 222.2 84% 4.9%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --primary: 222.2 47.4% 11.2%;
 | 
				
			||||||
 | 
					      --primary-foreground: 210 40% 98%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --secondary: 210 40% 96.1%;
 | 
				
			||||||
 | 
					      --secondary-foreground: 222.2 47.4% 11.2%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --muted: 210 40% 96.1%;
 | 
				
			||||||
 | 
					      --muted-foreground: 215.4 16.3% 46.9%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --accent: 210 40% 96.1%;
 | 
				
			||||||
 | 
					      --accent-foreground: 222.2 47.4% 11.2%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --destructive: 0 84.2% 60.2%;
 | 
				
			||||||
 | 
					      --destructive-foreground: 210 40% 98%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --border: 214.3 31.8% 91.4%;
 | 
				
			||||||
 | 
					      --input: 214.3 31.8% 91.4%;
 | 
				
			||||||
 | 
					      --ring: 222.2 84% 4.9%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --radius: 0.5rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .dark {
 | 
				
			||||||
 | 
					      --background: 222.2 84% 4.9%;
 | 
				
			||||||
 | 
					      --foreground: 210 40% 98%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --card: 222.2 84% 4.9%;
 | 
				
			||||||
 | 
					      --card-foreground: 210 40% 98%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --popover: 222.2 84% 4.9%;
 | 
				
			||||||
 | 
					      --popover-foreground: 210 40% 98%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --primary: 210 40% 98%;
 | 
				
			||||||
 | 
					      --primary-foreground: 222.2 47.4% 11.2%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --secondary: 217.2 32.6% 17.5%;
 | 
				
			||||||
 | 
					      --secondary-foreground: 210 40% 98%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --muted: 217.2 32.6% 17.5%;
 | 
				
			||||||
 | 
					      --muted-foreground: 215 20.2% 65.1%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --accent: 217.2 32.6% 17.5%;
 | 
				
			||||||
 | 
					      --accent-foreground: 210 40% 98%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --destructive: 0 62.8% 30.6%;
 | 
				
			||||||
 | 
					      --destructive-foreground: 210 40% 98%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      --border: 217.2 32.6% 17.5%;
 | 
				
			||||||
 | 
					      --input: 217.2 32.6% 17.5%;
 | 
				
			||||||
 | 
					      --ring: 212.7 26.8% 83.9%;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @layer base {
 | 
				
			||||||
 | 
					    * {
 | 
				
			||||||
 | 
					      @apply border-border;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    body {
 | 
				
			||||||
 | 
					      @apply bg-background text-foreground;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
@ -1,14 +1,80 @@
 | 
				
			|||||||
import { type Config } from "tailwindcss";
 | 
					import type { Config } from "tailwindcss"
 | 
				
			||||||
import { fontFamily } from "tailwindcss/defaultTheme";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					const config = {
 | 
				
			||||||
  content: ["./src/**/*.tsx"],
 | 
					  darkMode: ["class"],
 | 
				
			||||||
 | 
					  content: [
 | 
				
			||||||
 | 
					    './pages/**/*.{ts,tsx}',
 | 
				
			||||||
 | 
					    './components/**/*.{ts,tsx}',
 | 
				
			||||||
 | 
					    './app/**/*.{ts,tsx}',
 | 
				
			||||||
 | 
					    './src/**/*.{ts,tsx}',
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					  prefix: "",
 | 
				
			||||||
  theme: {
 | 
					  theme: {
 | 
				
			||||||
 | 
					    container: {
 | 
				
			||||||
 | 
					      center: true,
 | 
				
			||||||
 | 
					      padding: "2rem",
 | 
				
			||||||
 | 
					      screens: {
 | 
				
			||||||
 | 
					        "2xl": "1400px",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    extend: {
 | 
					    extend: {
 | 
				
			||||||
      fontFamily: {
 | 
					      colors: {
 | 
				
			||||||
        sans: ["var(--font-geist-sans)", ...fontFamily.sans],
 | 
					        border: "hsl(var(--border))",
 | 
				
			||||||
 | 
					        input: "hsl(var(--input))",
 | 
				
			||||||
 | 
					        ring: "hsl(var(--ring))",
 | 
				
			||||||
 | 
					        background: "hsl(var(--background))",
 | 
				
			||||||
 | 
					        foreground: "hsl(var(--foreground))",
 | 
				
			||||||
 | 
					        primary: {
 | 
				
			||||||
 | 
					          DEFAULT: "hsl(var(--primary))",
 | 
				
			||||||
 | 
					          foreground: "hsl(var(--primary-foreground))",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        secondary: {
 | 
				
			||||||
 | 
					          DEFAULT: "hsl(var(--secondary))",
 | 
				
			||||||
 | 
					          foreground: "hsl(var(--secondary-foreground))",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        destructive: {
 | 
				
			||||||
 | 
					          DEFAULT: "hsl(var(--destructive))",
 | 
				
			||||||
 | 
					          foreground: "hsl(var(--destructive-foreground))",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        muted: {
 | 
				
			||||||
 | 
					          DEFAULT: "hsl(var(--muted))",
 | 
				
			||||||
 | 
					          foreground: "hsl(var(--muted-foreground))",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        accent: {
 | 
				
			||||||
 | 
					          DEFAULT: "hsl(var(--accent))",
 | 
				
			||||||
 | 
					          foreground: "hsl(var(--accent-foreground))",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        popover: {
 | 
				
			||||||
 | 
					          DEFAULT: "hsl(var(--popover))",
 | 
				
			||||||
 | 
					          foreground: "hsl(var(--popover-foreground))",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        card: {
 | 
				
			||||||
 | 
					          DEFAULT: "hsl(var(--card))",
 | 
				
			||||||
 | 
					          foreground: "hsl(var(--card-foreground))",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      borderRadius: {
 | 
				
			||||||
 | 
					        lg: "var(--radius)",
 | 
				
			||||||
 | 
					        md: "calc(var(--radius) - 2px)",
 | 
				
			||||||
 | 
					        sm: "calc(var(--radius) - 4px)",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      keyframes: {
 | 
				
			||||||
 | 
					        "accordion-down": {
 | 
				
			||||||
 | 
					          from: { height: "0" },
 | 
				
			||||||
 | 
					          to: { height: "var(--radix-accordion-content-height)" },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "accordion-up": {
 | 
				
			||||||
 | 
					          from: { height: "var(--radix-accordion-content-height)" },
 | 
				
			||||||
 | 
					          to: { height: "0" },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      animation: {
 | 
				
			||||||
 | 
					        "accordion-down": "accordion-down 0.2s ease-out",
 | 
				
			||||||
 | 
					        "accordion-up": "accordion-up 0.2s ease-out",
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  plugins: [],
 | 
					  plugins: [require("tailwindcss-animate")],
 | 
				
			||||||
} satisfies Config;
 | 
					} satisfies Config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default config
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user