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"
 | 
			
		||||
  },
 | 
			
		||||
  "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",
 | 
			
		||||
    "@vercel/postgres": "^0.8.0",
 | 
			
		||||
    "class-variance-authority": "^0.7.0",
 | 
			
		||||
    "clsx": "^2.1.1",
 | 
			
		||||
    "drizzle-orm": "^0.29.4",
 | 
			
		||||
    "geist": "^1.3.0",
 | 
			
		||||
    "lucide-react": "^0.379.0",
 | 
			
		||||
    "next": "^15.0.0-rc.0",
 | 
			
		||||
    "postgres": "^3.4.3",
 | 
			
		||||
    "react": "^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"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,8 @@
 | 
			
		||||
import FormCard from "./FormCard";
 | 
			
		||||
import FormCard from "./_components/FormCard";
 | 
			
		||||
 | 
			
		||||
export default function App() {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="container flex w-full flex-col justify-center">
 | 
			
		||||
      <h1>Yes hello</h1>
 | 
			
		||||
      <FormCard />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
@ -1,5 +1,14 @@
 | 
			
		||||
"use client";
 | 
			
		||||
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 { getProducers } from "~/server/actions/allProducers";
 | 
			
		||||
 | 
			
		||||
@ -35,19 +44,33 @@ export default function CreateWine() {
 | 
			
		||||
    loadProducers();
 | 
			
		||||
  }, []);
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="container flex flex-col gap-4">
 | 
			
		||||
      <form action={formAction}>
 | 
			
		||||
        <input className="border-2" type="text" name="name" />
 | 
			
		||||
        <select name="producer">
 | 
			
		||||
          {producers.map((producer, i) => (
 | 
			
		||||
            <option key={i} value={producer.id}>
 | 
			
		||||
              {producer.name}
 | 
			
		||||
            </option>
 | 
			
		||||
          ))}
 | 
			
		||||
        </select>
 | 
			
		||||
        <button type="submit">Add wine</button>
 | 
			
		||||
    <div className="flex w-full flex-col">
 | 
			
		||||
      <form action={formAction} className="flex flex-col gap-2">
 | 
			
		||||
        <Input
 | 
			
		||||
          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) => (
 | 
			
		||||
              <SelectItem key={i} value={producer.id}>
 | 
			
		||||
                {producer.name}
 | 
			
		||||
              </SelectItem>
 | 
			
		||||
            ))}
 | 
			
		||||
          </SelectContent>
 | 
			
		||||
        </Select>
 | 
			
		||||
        <Button type="submit">Add wine</Button>
 | 
			
		||||
      </form>
 | 
			
		||||
      {JSON.stringify(formState)}
 | 
			
		||||
      {formState.message === "success" ? (
 | 
			
		||||
        <span className="text-green-400">Wine succesfully submitted</span>
 | 
			
		||||
      ) : (
 | 
			
		||||
        ""
 | 
			
		||||
      )}
 | 
			
		||||
    </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";
 | 
			
		||||
 | 
			
		||||
export default function FormCard() {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="mx-auto p-4 shadow-md">
 | 
			
		||||
      <h1>Add a new wine</h1>
 | 
			
		||||
      <CreateWine />
 | 
			
		||||
    </div>
 | 
			
		||||
    <Card>
 | 
			
		||||
      <CardHeader>
 | 
			
		||||
        <CardTitle>Add wine</CardTitle>
 | 
			
		||||
        <CardDescription>All fields are required so get to it!</CardDescription>
 | 
			
		||||
      </CardHeader>
 | 
			
		||||
      <CardContent>
 | 
			
		||||
        <CreateWine />
 | 
			
		||||
      </CardContent>
 | 
			
		||||
      <CardFooter>
 | 
			
		||||
        <p>Card Footer</p>
 | 
			
		||||
      </CardFooter>
 | 
			
		||||
    </Card>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,14 @@
 | 
			
		||||
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 = {
 | 
			
		||||
  title: "Create T3 App",
 | 
			
		||||
@ -14,8 +22,16 @@ export default function RootLayout({
 | 
			
		||||
  children: React.ReactNode;
 | 
			
		||||
}) {
 | 
			
		||||
  return (
 | 
			
		||||
    <html lang="en" className={`${GeistSans.variable}`}>
 | 
			
		||||
      <body>{children}</body>
 | 
			
		||||
    <html lang="en">
 | 
			
		||||
      <body
 | 
			
		||||
        className={cn(
 | 
			
		||||
          "min-h-screen bg-background font-sans antialiased",
 | 
			
		||||
          fontSans.variable,
 | 
			
		||||
        )}
 | 
			
		||||
      >
 | 
			
		||||
        <Filtermenu />
 | 
			
		||||
        {children}
 | 
			
		||||
      </body>
 | 
			
		||||
    </html>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import App from "./_components/App";
 | 
			
		||||
import App from "./App";
 | 
			
		||||
 | 
			
		||||
export default async function HomePage() {
 | 
			
		||||
  return (
 | 
			
		||||
    <main>
 | 
			
		||||
    <main className="w-full">
 | 
			
		||||
      <App />
 | 
			
		||||
    </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 components;
 | 
			
		||||
@tailwind utilities;
 | 
			
		||||
  @tailwind components;
 | 
			
		||||
  @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 { fontFamily } from "tailwindcss/defaultTheme";
 | 
			
		||||
import type { Config } from "tailwindcss"
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  content: ["./src/**/*.tsx"],
 | 
			
		||||
const config = {
 | 
			
		||||
  darkMode: ["class"],
 | 
			
		||||
  content: [
 | 
			
		||||
    './pages/**/*.{ts,tsx}',
 | 
			
		||||
    './components/**/*.{ts,tsx}',
 | 
			
		||||
    './app/**/*.{ts,tsx}',
 | 
			
		||||
    './src/**/*.{ts,tsx}',
 | 
			
		||||
	],
 | 
			
		||||
  prefix: "",
 | 
			
		||||
  theme: {
 | 
			
		||||
    container: {
 | 
			
		||||
      center: true,
 | 
			
		||||
      padding: "2rem",
 | 
			
		||||
      screens: {
 | 
			
		||||
        "2xl": "1400px",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    extend: {
 | 
			
		||||
      fontFamily: {
 | 
			
		||||
        sans: ["var(--font-geist-sans)", ...fontFamily.sans],
 | 
			
		||||
      colors: {
 | 
			
		||||
        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: [],
 | 
			
		||||
} satisfies Config;
 | 
			
		||||
  plugins: [require("tailwindcss-animate")],
 | 
			
		||||
} satisfies Config
 | 
			
		||||
 | 
			
		||||
export default config
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user