started implmeneting filter
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { Button } from "@repo/ui/src/shadcn/button";
|
||||
import { Button } from "@repo/ui/shadcn/button";
|
||||
import React from "react";
|
||||
import { signIn } from "../helpers/server/auth";
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import Image from "next/image";
|
|||
import Link from "next/link";
|
||||
import Logo from "@/public/logo.svg";
|
||||
import { signIn } from "@/app/helpers/server/auth";
|
||||
import { Google } from "@repo/ui/src/components/icons";
|
||||
import { Google } from "@repo/ui/components/icons";
|
||||
|
||||
export const runtime = "edge";
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ async function Signin() {
|
|||
action={async () => {
|
||||
"use server";
|
||||
await signIn("google", {
|
||||
redirectTo: "/home",
|
||||
redirectTo: "/home?firstTime=true",
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { FormEvent, useState } from "react";
|
||||
import formSubmitAction from "./formSubmitAction";
|
||||
import { useToast } from "@repo/ui/src/shadcn/use-toast";
|
||||
import { useToast } from "@repo/ui/shadcn/use-toast";
|
||||
|
||||
function EmailInput() {
|
||||
const [email, setEmail] = useState("");
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import Image from "next/image";
|
||||
import { X } from "@repo/ui/src/components/icons";
|
||||
import { X } from "@repo/ui/components/icons";
|
||||
|
||||
import { features } from "./FeatureContent";
|
||||
import { CardClick } from "@repo/ui/src/components/cardClick";
|
||||
import { CardClick } from "@repo/ui/components/cardClick";
|
||||
|
||||
export default function Features() {
|
||||
const [tab, setTab] = useState<number>(0);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
import React from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Twitter } from "@repo/ui/src/components/icons";
|
||||
import { Twitter } from "@repo/ui/components/icons";
|
||||
import EmailInput from "./EmailInput";
|
||||
import LinkArrow from "./linkArrow";
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
Notion,
|
||||
Reddit,
|
||||
Twitter,
|
||||
} from "@repo/ui/src/components/icons";
|
||||
} from "@repo/ui/components/icons";
|
||||
import Image from "next/image";
|
||||
|
||||
const icons = [
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import RotatingIcons from "./RotatingIcons";
|
|||
import Hero from "./Hero";
|
||||
import Navbar from "./Navbar";
|
||||
import Cta from "./Cta";
|
||||
import { Toaster } from "@repo/ui/src/shadcn/toaster";
|
||||
import { Toaster } from "@repo/ui/shadcn/toaster";
|
||||
import Features from "./Features";
|
||||
import Footer from "./footer";
|
||||
import { auth } from "../helpers/server/auth";
|
||||
|
|
|
|||
7
apps/web/app/chat/page.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import React from "react";
|
||||
|
||||
function Page() {
|
||||
return <div>Page</div>;
|
||||
}
|
||||
|
||||
export default Page;
|
||||
|
|
@ -4,7 +4,7 @@ import dynamic from "next/dynamic";
|
|||
|
||||
// Don't SSR the toggle since the value on the server will be different than the client
|
||||
export const getThemeToggler = () =>
|
||||
dynamic(() => import("@repo/ui/src/shadcn/theme-toggle"), {
|
||||
dynamic(() => import("@repo/ui/shadcn/theme-toggle"), {
|
||||
ssr: false,
|
||||
// Make sure to code a placeholder so the UI doesn't jump when the component loads
|
||||
loading: () => <div className="w-6 h-6" />,
|
||||
|
|
|
|||
10
apps/web/app/helpers/lib/searchParams.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import {
|
||||
createSearchParamsCache,
|
||||
parseAsInteger,
|
||||
parseAsString,
|
||||
parseAsBoolean,
|
||||
} from "nuqs/server";
|
||||
|
||||
export const homeSearchParamsCache = createSearchParamsCache({
|
||||
firstTime: parseAsBoolean.withDefault(false),
|
||||
});
|
||||
7
apps/web/app/home/actions.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
"use server";
|
||||
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export async function navigate(q: string) {
|
||||
redirect(`/chat?q=${q}`);
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import React from "react";
|
|||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import Logo from "../../public/logo.svg";
|
||||
import { AddIcon, ChatIcon } from "@repo/ui/src/icons";
|
||||
import { AddIcon, ChatIcon } from "@repo/ui/icons";
|
||||
|
||||
function Header() {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { MemoriesIcon, ExploreIcon, HistoryIcon } from "@repo/ui/src/icons";
|
||||
import { MemoriesIcon, ExploreIcon, HistoryIcon } from "@repo/ui/icons";
|
||||
|
||||
function Menu() {
|
||||
const menuItems = [
|
||||
|
|
|
|||
|
|
@ -2,8 +2,15 @@ import React from "react";
|
|||
import Menu from "./menu";
|
||||
import Header from "./header";
|
||||
import QueryInput from "./queryinput";
|
||||
import { homeSearchParamsCache } from "@/app/helpers/lib/searchParams";
|
||||
|
||||
function Page({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Record<string, string | string[] | undefined>;
|
||||
}) {
|
||||
const { firstTime } = homeSearchParamsCache.parse(searchParams);
|
||||
|
||||
function Page() {
|
||||
return (
|
||||
<main className="h-screen flex flex-col p-4 relative">
|
||||
<Menu />
|
||||
|
|
@ -12,7 +19,7 @@ function Page() {
|
|||
|
||||
<div className="max-w-3xl flex mx-auto w-full flex-col">
|
||||
{/* all content goes here */}
|
||||
<div className=""></div>
|
||||
{/* <div className="">hi {firstTime ? 'first time' : ''}</div> */}
|
||||
|
||||
<div className="w-full h-96">
|
||||
<QueryInput />
|
||||
|
|
|
|||
|
|
@ -1,35 +1,83 @@
|
|||
import { ArrowRightIcon, MemoriesIcon, SelectIcon } from "@repo/ui/src/icons";
|
||||
"use client";
|
||||
|
||||
import { ArrowRightIcon, MemoriesIcon, SelectIcon } from "@repo/ui/icons";
|
||||
import Image from "next/image";
|
||||
import React from "react";
|
||||
import Divider from "@repo/ui/src/shadcn/divider";
|
||||
import Divider from "@repo/ui/shadcn/divider";
|
||||
import { redirect } from "next/navigation";
|
||||
import { navigate } from "./actions";
|
||||
import { FilterSpaces } from "@repo/ui/components/filterSpaces";
|
||||
|
||||
function QueryInput() {
|
||||
const [q, setQ] = React.useState("");
|
||||
|
||||
const parseQ = React.useCallback(() => {
|
||||
const newQ = q.replace(/\n/g, "\\n");
|
||||
return newQ;
|
||||
}, [q]);
|
||||
|
||||
const [selectedSpaces, setSelectedSpaces] = React.useState<number[]>([]);
|
||||
|
||||
return (
|
||||
<div className="bg-secondary rounded-[24px] w-full mt-40">
|
||||
{/* input and action button */}
|
||||
<div className="flex gap-4 p-3">
|
||||
<form action={async () => navigate(parseQ())} className="flex gap-4 p-3">
|
||||
<textarea
|
||||
name="query-input"
|
||||
name="q"
|
||||
cols={30}
|
||||
rows={4}
|
||||
className="bg-transparent pt-2.5 text-base text-[#989EA4] focus:text-foreground duration-200 tracking-[3%] outline-none resize-none w-full p-4"
|
||||
placeholder="Ask your second brain..."
|
||||
></textarea>
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
if (!e.shiftKey) navigate(parseQ());
|
||||
}
|
||||
}}
|
||||
onChange={(e) => setQ(e.target.value)}
|
||||
value={q}
|
||||
/>
|
||||
|
||||
<button className="h-12 w-12 rounded-[14px] bg-[#21303D] all-center shrink-0 hover:brightness-125 duration-200 outline-none focus:outline focus:outline-primary active:scale-90">
|
||||
<button
|
||||
type="submit"
|
||||
className="h-12 w-12 rounded-[14px] bg-[#21303D] all-center shrink-0 hover:brightness-125 duration-200 outline-none focus:outline focus:outline-primary active:scale-90"
|
||||
>
|
||||
<Image src={ArrowRightIcon} alt="Right arrow icon" />
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* suggestions */}
|
||||
{/* selected sources */}
|
||||
<div className="flex items-center gap-6 p-2">
|
||||
<button className="bg-[#2B3237] h-9 p-2 px-3 flex items-center gap-2 rounded-full">
|
||||
{/* <button className="bg-[#2B3237] h-9 p-2 px-3 flex items-center gap-2 rounded-full">
|
||||
<Image src={MemoriesIcon} alt="Memories icon" className="w-5" />
|
||||
<span className="pr-3">Filters</span>
|
||||
<Image src={SelectIcon} alt="Select icon" className="w-4" />
|
||||
</button>
|
||||
</button> */}
|
||||
<FilterSpaces
|
||||
name="Filters"
|
||||
selectedSpaces={selectedSpaces}
|
||||
setSelectedSpaces={setSelectedSpaces}
|
||||
// side="top"
|
||||
// align="start"
|
||||
// className="mr-auto bg-[#252525] md:hidden"
|
||||
spaces={[
|
||||
{
|
||||
name: "Nvidia",
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
name: "Open-source",
|
||||
id: 3,
|
||||
},
|
||||
{
|
||||
name: "Artificial Intelligence",
|
||||
id: 4,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="flex gap-6 brightness-75">
|
||||
<p>Nvidia</p>
|
||||
<p>Open-source</p>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import "./globals.css";
|
||||
// import "./globals.css";
|
||||
import "@repo/tailwind-config/globals.css";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Button } from "@repo/ui/src/shadcn/button";
|
||||
import { Button } from "@repo/ui/shadcn/button";
|
||||
import { auth, signIn, signOut } from "../helpers/server/auth";
|
||||
import { db } from "../helpers/server/db";
|
||||
import { sql } from "drizzle-orm";
|
||||
|
|
@ -97,7 +97,9 @@ export default async function Page() {
|
|||
await signOut();
|
||||
}}
|
||||
>
|
||||
<Button className="mt-4">Sign out</Button>
|
||||
<Button variant={"destructive"} className="mt-4">
|
||||
Sign out
|
||||
</Button>
|
||||
</form>
|
||||
</>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -1,315 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import Image from "next/image";
|
||||
import { Cross2Icon, UploadIcon } from "@radix-ui/react-icons";
|
||||
import Dropzone, {
|
||||
type DropzoneProps,
|
||||
type FileRejection,
|
||||
} from "react-dropzone";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { cn, formatBytes } from "@repo/ui/lib/utils";
|
||||
import { useControllableState } from "@repo/ui/hooks/use-controllable-state";
|
||||
import { Button } from "@repo/ui/src/button";
|
||||
import { Progress } from "@repo/ui/src/progress";
|
||||
import { ScrollArea } from "@repo/ui/src/scroll-area";
|
||||
|
||||
interface FileUploaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
* Value of the uploader.
|
||||
* @type File[]
|
||||
* @default undefined
|
||||
* @example value={files}
|
||||
*/
|
||||
value?: File[];
|
||||
|
||||
/**
|
||||
* Function to be called when the value changes.
|
||||
* @type React.Dispatch<React.SetStateAction<File[]>>
|
||||
* @default undefined
|
||||
* @example onValueChange={(files) => setFiles(files)}
|
||||
*/
|
||||
onValueChange?: React.Dispatch<React.SetStateAction<File[]>>;
|
||||
|
||||
/**
|
||||
* Function to be called when files are uploaded.
|
||||
* @type (files: File[]) => Promise<void>
|
||||
* @default undefined
|
||||
* @example onUpload={(files) => uploadFiles(files)}
|
||||
*/
|
||||
onUpload?: (files: File[]) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Progress of the uploaded files.
|
||||
* @type Record<string, number> | undefined
|
||||
* @default undefined
|
||||
* @example progresses={{ "file1.png": 50 }}
|
||||
*/
|
||||
progresses?: Record<string, number>;
|
||||
|
||||
/**
|
||||
* Accepted file types for the uploader.
|
||||
* @type { [key: string]: string[]}
|
||||
* @default
|
||||
* ```ts
|
||||
* { "image/*": [] }
|
||||
* ```
|
||||
* @example accept={["image/png", "image/jpeg"]}
|
||||
*/
|
||||
accept?: DropzoneProps["accept"];
|
||||
|
||||
/**
|
||||
* Maximum file size for the uploader.
|
||||
* @type number | undefined
|
||||
* @default 1024 * 1024 * 2 // 2MB
|
||||
* @example maxSize={1024 * 1024 * 2} // 2MB
|
||||
*/
|
||||
maxSize?: DropzoneProps["maxSize"];
|
||||
|
||||
/**
|
||||
* Maximum number of files for the uploader.
|
||||
* @type number | undefined
|
||||
* @default 1
|
||||
* @example maxFiles={5}
|
||||
*/
|
||||
maxFiles?: DropzoneProps["maxFiles"];
|
||||
|
||||
/**
|
||||
* Whether the uploader should accept multiple files.
|
||||
* @type boolean
|
||||
* @default false
|
||||
* @example multiple
|
||||
*/
|
||||
multiple?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the uploader is disabled.
|
||||
* @type boolean
|
||||
* @default false
|
||||
* @example disabled
|
||||
*/
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function FileUploader(props: FileUploaderProps) {
|
||||
const {
|
||||
value: valueProp,
|
||||
onValueChange,
|
||||
onUpload,
|
||||
progresses,
|
||||
accept = { "image/*": [] },
|
||||
maxSize = 1024 * 1024 * 2,
|
||||
maxFiles = 1,
|
||||
multiple = false,
|
||||
disabled = false,
|
||||
className,
|
||||
...dropzoneProps
|
||||
} = props;
|
||||
|
||||
const [files, setFiles] = useControllableState({
|
||||
prop: valueProp,
|
||||
onChange: onValueChange,
|
||||
});
|
||||
|
||||
const onDrop = React.useCallback(
|
||||
(acceptedFiles: File[], rejectedFiles: FileRejection[]) => {
|
||||
if (!multiple && maxFiles === 1 && acceptedFiles.length > 1) {
|
||||
toast.error("Cannot upload more than 1 file at a time");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((files?.length ?? 0) + acceptedFiles.length > maxFiles) {
|
||||
toast.error(`Cannot upload more than ${maxFiles} files`);
|
||||
return;
|
||||
}
|
||||
|
||||
const newFiles = acceptedFiles.map((file) =>
|
||||
Object.assign(file, {
|
||||
preview: URL.createObjectURL(file),
|
||||
}),
|
||||
);
|
||||
|
||||
const updatedFiles = files ? [...files, ...newFiles] : newFiles;
|
||||
|
||||
setFiles(updatedFiles);
|
||||
|
||||
if (rejectedFiles.length > 0) {
|
||||
rejectedFiles.forEach(({ file }) => {
|
||||
toast.error(`File ${file.name} was rejected`);
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
onUpload &&
|
||||
updatedFiles.length > 0 &&
|
||||
updatedFiles.length <= maxFiles
|
||||
) {
|
||||
const target =
|
||||
updatedFiles.length > 0 ? `${updatedFiles.length} files` : `file`;
|
||||
|
||||
toast.promise(onUpload(updatedFiles), {
|
||||
loading: `Uploading ${target}...`,
|
||||
success: () => {
|
||||
setFiles([]);
|
||||
return `${target} uploaded`;
|
||||
},
|
||||
error: `Failed to upload ${target}`,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
[files, maxFiles, multiple, onUpload, setFiles],
|
||||
);
|
||||
|
||||
function onRemove(index: number) {
|
||||
if (!files) return;
|
||||
const newFiles = files.filter((_, i) => i !== index);
|
||||
setFiles(newFiles);
|
||||
onValueChange?.(newFiles);
|
||||
}
|
||||
|
||||
// Revoke preview url when component unmounts
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
if (!files) return;
|
||||
files.forEach((file) => {
|
||||
if (isFileWithPreview(file)) {
|
||||
URL.revokeObjectURL(file.preview);
|
||||
}
|
||||
});
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const isDisabled = disabled || (files?.length ?? 0) >= maxFiles;
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col gap-6 overflow-hidden">
|
||||
<Dropzone
|
||||
onDrop={onDrop}
|
||||
accept={accept}
|
||||
maxSize={maxSize}
|
||||
maxFiles={maxFiles}
|
||||
multiple={maxFiles > 1 || multiple}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{({ getRootProps, getInputProps, isDragActive }) => (
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className={cn(
|
||||
"group relative grid h-52 w-full cursor-pointer place-items-center rounded-lg border-2 border-dashed border-muted-foreground/25 px-5 py-2.5 text-center transition hover:bg-muted/25",
|
||||
"ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
isDragActive && "border-muted-foreground/50",
|
||||
isDisabled && "pointer-events-none opacity-60",
|
||||
className,
|
||||
)}
|
||||
{...dropzoneProps}
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
{isDragActive ? (
|
||||
<div className="flex flex-col items-center justify-center gap-4 sm:px-5">
|
||||
<div className="rounded-full border border-dashed p-3">
|
||||
<UploadIcon
|
||||
className="size-7 text-muted-foreground"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<p className="font-medium text-muted-foreground">
|
||||
Drop the files here
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center gap-4 sm:px-5">
|
||||
<div className="rounded-full border border-dashed p-3">
|
||||
<UploadIcon
|
||||
className="size-7 text-muted-foreground"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-px">
|
||||
<p className="font-medium text-muted-foreground">
|
||||
Drag {`'n'`} drop files here, or click to select files
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground/70">
|
||||
You can upload
|
||||
{maxFiles > 1
|
||||
? ` ${maxFiles === Infinity ? "multiple" : maxFiles}
|
||||
files (up to ${formatBytes(maxSize)} each)`
|
||||
: ` a file with ${formatBytes(maxSize)}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Dropzone>
|
||||
{files?.length ? (
|
||||
<ScrollArea className="h-fit w-full px-3">
|
||||
<div className="max-h-48 space-y-4">
|
||||
{files?.map((file, index) => (
|
||||
<FileCard
|
||||
key={index}
|
||||
file={file}
|
||||
onRemove={() => onRemove(index)}
|
||||
progress={progresses?.[file.name]}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface FileCardProps {
|
||||
file: File;
|
||||
onRemove: () => void;
|
||||
progress?: number;
|
||||
}
|
||||
|
||||
function FileCard({ file, progress, onRemove }: FileCardProps) {
|
||||
return (
|
||||
<div className="relative flex items-center space-x-4">
|
||||
<div className="flex flex-1 space-x-4">
|
||||
{isFileWithPreview(file) ? (
|
||||
<Image
|
||||
src={file.preview}
|
||||
alt={file.name}
|
||||
width={48}
|
||||
height={48}
|
||||
loading="lazy"
|
||||
className="aspect-square shrink-0 rounded-md object-cover"
|
||||
/>
|
||||
) : null}
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<div className="space-y-px">
|
||||
<p className="line-clamp-1 text-sm font-medium text-foreground/80">
|
||||
{file.name}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatBytes(file.size)}
|
||||
</p>
|
||||
</div>
|
||||
{progress ? <Progress value={progress} /> : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="size-7"
|
||||
onClick={onRemove}
|
||||
>
|
||||
<Cross2Icon className="size-4 " aria-hidden="true" />
|
||||
<span className="sr-only">Remove file</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function isFileWithPreview(file: File): file is File & { preview: string } {
|
||||
return "preview" in file && typeof file.preview === "string";
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getErrorMessage } from "../helpers/lib/handle-errors";
|
||||
import { Button } from "@repo/ui/src/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@repo/ui/src/form";
|
||||
import { FileUploader } from "./file-uploader";
|
||||
|
||||
import { UploadedFilesCard } from "./uploaded-files-card";
|
||||
import { useUploadFile } from "@repo/ui/hooks/use-upload-file";
|
||||
|
||||
const schema = z.object({
|
||||
images: z.array(z.instanceof(File)),
|
||||
});
|
||||
|
||||
type Schema = z.infer<typeof schema>;
|
||||
|
||||
export default function ReactHookFormDemo() {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const { uploadFiles, uploadedFiles, isUploading } = useUploadFile(
|
||||
"imageUploader",
|
||||
{ defaultUploadedFiles: [] },
|
||||
);
|
||||
const form = useForm<Schema>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
images: [],
|
||||
},
|
||||
});
|
||||
|
||||
function onSubmit(input: Schema) {
|
||||
setLoading(true);
|
||||
|
||||
toast.promise(uploadFiles(input.images), {
|
||||
loading: "Uploading images...",
|
||||
success: () => {
|
||||
form.reset();
|
||||
setLoading(false);
|
||||
return "Images uploaded";
|
||||
},
|
||||
error: (err) => {
|
||||
setLoading(false);
|
||||
return getErrorMessage(err);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full min-h-screen items-center justify-center">
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="flex max-w-2xl flex-col gap-6 w-full"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="images"
|
||||
render={({ field }) => (
|
||||
<div className="space-y-6">
|
||||
<FormItem className="w-full">
|
||||
<FormLabel>Images</FormLabel>
|
||||
<FormControl>
|
||||
<FileUploader
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
maxFiles={4}
|
||||
maxSize={4 * 1024 * 1024}
|
||||
onUpload={uploadFiles}
|
||||
disabled={isUploading}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
{uploadedFiles.length > 0 ? (
|
||||
<UploadedFilesCard uploadedFiles={uploadedFiles} />
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Button className="w-fit" disabled={loading}>
|
||||
Save
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
import Image from "next/image";
|
||||
// import type { UploadedFile } from '@repo/shared-types';
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@repo/ui/src/shadcn/card";
|
||||
import { ScrollArea, ScrollBar } from "@repo/ui/src/shadcn/scroll-area";
|
||||
|
||||
interface UploadedFilesCardProps {
|
||||
uploadedFiles: any[];
|
||||
}
|
||||
|
||||
import { ImageIcon } from "@radix-ui/react-icons";
|
||||
|
||||
import { cn } from "@repo/ui/lib/utils";
|
||||
|
||||
interface EmptyCardProps extends React.ComponentPropsWithoutRef<typeof Card> {
|
||||
title: string;
|
||||
description?: string;
|
||||
action?: React.ReactNode;
|
||||
icon?: React.ComponentType<{ className?: string }>;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function EmptyCard({
|
||||
title,
|
||||
description,
|
||||
icon: Icon = ImageIcon,
|
||||
action,
|
||||
className,
|
||||
...props
|
||||
}: EmptyCardProps) {
|
||||
return (
|
||||
<Card
|
||||
className={cn(
|
||||
"flex w-full flex-col items-center justify-center space-y-6 bg-transparent p-16",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="mr-4 shrink-0 rounded-full border border-dashed p-4">
|
||||
<Icon className="size-8 text-muted-foreground" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-1.5 text-center">
|
||||
<CardTitle>{title}</CardTitle>
|
||||
{description ? <CardDescription>{description}</CardDescription> : null}
|
||||
</div>
|
||||
{action ? action : null}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export function UploadedFilesCard({ uploadedFiles }: UploadedFilesCardProps) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Uploaded files</CardTitle>
|
||||
<CardDescription>View the uploaded files here</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{uploadedFiles.length > 0 ? (
|
||||
<ScrollArea className="pb-4">
|
||||
<div className="flex w-max space-x-2.5">
|
||||
{uploadedFiles.map((file) => (
|
||||
<div key={file.key} className="relative aspect-video w-64">
|
||||
<Image
|
||||
src={file.url}
|
||||
alt={file.name}
|
||||
fill
|
||||
sizes="(min-width: 640px) 640px, 100vw"
|
||||
loading="lazy"
|
||||
className="rounded-md object-cover"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<ScrollBar orientation="horizontal" />
|
||||
</ScrollArea>
|
||||
) : (
|
||||
<EmptyCard
|
||||
title="No files uploaded"
|
||||
description="Upload some files to see them here"
|
||||
className="w-full"
|
||||
/>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
@ -14,9 +14,13 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@million/lint": "^1.0.0-rc.11",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@repo/ui": "*",
|
||||
"cmdk": "^1.0.0",
|
||||
"million": "^3.1.6",
|
||||
"next": "^14.1.1",
|
||||
"nuqs": "^1.17.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
|
|
@ -32,5 +36,10 @@
|
|||
"@types/react-dom": "^18.2.19",
|
||||
"eslint": "^8.57.0",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"esbuild",
|
||||
"workerd",
|
||||
"xycolors"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
"next.config.mjs",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
".next/types/**/*.ts",
|
||||
"../../packages/ui/"
|
||||
],
|
||||
"exclude": ["node_modules/"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
"@cloudflare/next-on-pages": "1",
|
||||
"@cloudflare/workers-types": "^4.20240512.0",
|
||||
"@repo/eslint-config": "*",
|
||||
"@repo/shared-types": "*",
|
||||
"@repo/tailwind-config": "*",
|
||||
"@repo/typescript-config": "*",
|
||||
"@repo/ui": "*",
|
||||
|
|
|
|||
|
|
@ -2,10 +2,14 @@
|
|||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
--foreground: rgba(179, 188, 197, 1);
|
||||
--foreground-menu: rgba(106, 115, 125, 1);
|
||||
--background: rgba(23, 27, 31, 1);
|
||||
--secondary: rgba(31, 36, 40, 1);
|
||||
--primary: rgba(54, 157, 253, 1);
|
||||
--border: rgba(51, 57, 67, 1);
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 0 0% 3.9%;
|
||||
|
|
@ -13,10 +17,8 @@
|
|||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 0 0% 3.9%;
|
||||
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
--secondary: 0 0% 96.1%;
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
|
||||
--muted: 0 0% 96.1%;
|
||||
|
|
@ -28,49 +30,37 @@
|
|||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
--ring: 0 0% 3.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 0 0% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
|
||||
--card: 0 0% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
|
||||
--popover: 0 0% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
|
||||
--secondary: 0 0% 14.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
|
||||
--accent: 0 0% 14.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--border: 0 0% 14.9%;
|
||||
--input: 0 0% 14.9%;
|
||||
--ring: 0 0% 83.1%;
|
||||
}
|
||||
body {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
.all-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
||||
|
||||
.gradient-background {
|
||||
background: linear-gradient(
|
||||
150deg,
|
||||
rgba(255, 255, 255, 0.1) 0%,
|
||||
rgba(255, 255, 255, 0)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,11 +23,12 @@ const config = {
|
|||
foreground: "var(--foreground)",
|
||||
"foreground-menu": "var(--foreground-menu)",
|
||||
background: "var(--background)",
|
||||
secondary: "var(--secondary)",
|
||||
primary: "var(--primary)",
|
||||
border: "var(--border)",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
primary: "var(--primary)",
|
||||
secondary: "var(--secondary)",
|
||||
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
|
|
|
|||
|
|
@ -1,78 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { cn } from "@repo/ui/lib/utils";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import React from "react";
|
||||
|
||||
export const CardClick = ({
|
||||
tab,
|
||||
handleClickIndex,
|
||||
items,
|
||||
}: {
|
||||
tab: number;
|
||||
handleClickIndex: (tab: number) => void;
|
||||
items: {
|
||||
title: string;
|
||||
description: string;
|
||||
svg: React.ReactNode;
|
||||
}[];
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn("flex flex-col")}>
|
||||
{items.map((item, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="group relative block h-full w-full cursor-pointer rounded-2xl p-2 transition-all hover:border"
|
||||
onMouseDown={() => handleClickIndex(idx)}
|
||||
>
|
||||
<AnimatePresence>
|
||||
{tab === idx && (
|
||||
<motion.span
|
||||
className="absolute inset-0 -z-[1] block h-full w-full rounded-3xl [background:linear-gradient(#2E2E32,#2E2E32),linear-gradient(120deg,theme(colors.zinc.700),theme(colors.zinc.700/0),theme(colors.zinc.700))]"
|
||||
layoutId="hoverBackground"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
transition: { duration: 0.15 },
|
||||
}}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
transition: { duration: 0.15, delay: 0.2 },
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<Card
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
svg={item.svg}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Card = ({
|
||||
title,
|
||||
description,
|
||||
svg,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
svg: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center rounded-2xl border border-transparent px-6 py-4 text-left`}
|
||||
>
|
||||
{svg}
|
||||
<div>
|
||||
<div className="font-inter-tight mb-1 text-lg font-semibold text-zinc-200">
|
||||
{title}
|
||||
</div>
|
||||
<div className="text-zinc-500">{description}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,304 +0,0 @@
|
|||
import type { SVGProps } from "react";
|
||||
export const Github = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
viewBox="0 0 256 250"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
{...props}
|
||||
>
|
||||
<path d="M128.001 0C57.317 0 0 57.307 0 128.001c0 56.554 36.676 104.535 87.535 121.46 6.397 1.185 8.746-2.777 8.746-6.158 0-3.052-.12-13.135-.174-23.83-35.61 7.742-43.124-15.103-43.124-15.103-5.823-14.795-14.213-18.73-14.213-18.73-11.613-7.944.876-7.78.876-7.78 12.853.902 19.621 13.19 19.621 13.19 11.417 19.568 29.945 13.911 37.249 10.64 1.149-8.272 4.466-13.92 8.127-17.116-28.431-3.236-58.318-14.212-58.318-63.258 0-13.975 5-25.394 13.188-34.358-1.329-3.224-5.71-16.242 1.24-33.874 0 0 10.749-3.44 35.21 13.121 10.21-2.836 21.16-4.258 32.038-4.307 10.878.049 21.837 1.47 32.066 4.307 24.431-16.56 35.165-13.12 35.165-13.12 6.967 17.63 2.584 30.65 1.255 33.873 8.207 8.964 13.173 20.383 13.173 34.358 0 49.163-29.944 59.988-58.447 63.157 4.591 3.972 8.682 11.762 8.682 23.704 0 17.126-.148 30.91-.148 35.126 0 3.407 2.304 7.398 8.792 6.14C219.37 232.5 256 184.537 256 128.002 256 57.307 198.691 0 128.001 0Zm-80.06 182.34c-.282.636-1.283.827-2.194.39-.929-.417-1.45-1.284-1.15-1.922.276-.655 1.279-.838 2.205-.399.93.418 1.46 1.293 1.139 1.931Zm6.296 5.618c-.61.566-1.804.303-2.614-.591-.837-.892-.994-2.086-.375-2.66.63-.566 1.787-.301 2.626.591.838.903 1 2.088.363 2.66Zm4.32 7.188c-.785.545-2.067.034-2.86-1.104-.784-1.138-.784-2.503.017-3.05.795-.547 2.058-.055 2.861 1.075.782 1.157.782 2.522-.019 3.08Zm7.304 8.325c-.701.774-2.196.566-3.29-.49-1.119-1.032-1.43-2.496-.726-3.27.71-.776 2.213-.558 3.315.49 1.11 1.03 1.45 2.505.701 3.27Zm9.442 2.81c-.31 1.003-1.75 1.459-3.199 1.033-1.448-.439-2.395-1.613-2.103-2.626.301-1.01 1.747-1.484 3.207-1.028 1.446.436 2.396 1.602 2.095 2.622Zm10.744 1.193c.036 1.055-1.193 1.93-2.715 1.95-1.53.034-2.769-.82-2.786-1.86 0-1.065 1.202-1.932 2.733-1.958 1.522-.03 2.768.818 2.768 1.868Zm10.555-.405c.182 1.03-.875 2.088-2.387 2.37-1.485.271-2.861-.365-3.05-1.386-.184-1.056.893-2.114 2.376-2.387 1.514-.263 2.868.356 3.061 1.403Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Twitter = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
viewBox="0 0 256 209"
|
||||
width="1em"
|
||||
height="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M256 25.45c-9.42 4.177-19.542 7-30.166 8.27 10.845-6.5 19.172-16.793 23.093-29.057a105.183 105.183 0 0 1-33.351 12.745C205.995 7.201 192.346.822 177.239.822c-29.006 0-52.523 23.516-52.523 52.52 0 4.117.465 8.125 1.36 11.97-43.65-2.191-82.35-23.1-108.255-54.876-4.52 7.757-7.11 16.78-7.11 26.404 0 18.222 9.273 34.297 23.365 43.716a52.312 52.312 0 0 1-23.79-6.57c-.003.22-.003.44-.003.661 0 25.447 18.104 46.675 42.13 51.5a52.592 52.592 0 0 1-23.718.9c6.683 20.866 26.08 36.05 49.062 36.475-17.975 14.086-40.622 22.483-65.228 22.483-4.24 0-8.42-.249-12.529-.734 23.243 14.902 50.85 23.597 80.51 23.597 96.607 0 149.434-80.031 149.434-149.435 0-2.278-.05-4.543-.152-6.795A106.748 106.748 0 0 0 256 25.45"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Medium = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
width="52"
|
||||
height="52"
|
||||
viewBox="0 0 52 52"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M17.332 13C20.7798 13 24.0864 14.3696 26.5244 16.8076C28.9624 19.2456 30.332 22.5522 30.332 26C30.332 29.4478 28.9624 32.7544 26.5244 35.1924C24.0864 37.6304 20.7798 39 17.332 39C13.8842 39 10.5776 37.6304 8.13964 35.1924C5.70167 32.7544 4.33203 29.4478 4.33203 26C4.33203 22.5522 5.70167 19.2456 8.13964 16.8076C10.5776 14.3696 13.8842 13 17.332 13ZM36.832 15.1667C40.082 15.1667 42.2487 20.0178 42.2487 26C42.2487 31.9822 40.082 36.8333 36.832 36.8333C33.582 36.8333 31.4154 31.9822 31.4154 26C31.4154 20.0178 33.582 15.1667 36.832 15.1667ZM45.4987 16.25C46.322 16.25 47.0414 18.0418 47.4054 21.1163L47.5072 22.0762L47.5484 22.5853L47.6134 23.6557L47.635 24.2168L47.661 25.389L47.6654 26L47.661 26.611L47.635 27.7832L47.6134 28.3465L47.5484 29.4147L47.505 29.9238L47.4075 30.8837C47.0414 33.9603 46.3242 35.75 45.4987 35.75C44.6754 35.75 43.956 33.9582 43.592 30.8837L43.4902 29.9238C43.4753 29.7542 43.4616 29.5845 43.449 29.4147L43.384 28.3443C43.3756 28.1573 43.3684 27.9703 43.3624 27.7832L43.3364 26.611V25.389L43.3624 24.2168L43.384 23.6535L43.449 22.5853L43.4924 22.0762L43.5899 21.1163C43.956 18.0397 44.6732 16.25 45.4987 16.25Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Reddit = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
className="_1O4jTk-dZ-VIxsCuYB6OR8"
|
||||
viewBox="0 0 216 216"
|
||||
width="1em"
|
||||
height="1em"
|
||||
{...props}
|
||||
>
|
||||
<defs>
|
||||
<radialGradient
|
||||
id="snoo-radial-gragient"
|
||||
cx={169.75}
|
||||
cy={92.19}
|
||||
r={50.98}
|
||||
fx={169.75}
|
||||
fy={92.19}
|
||||
gradientTransform="matrix(1 0 0 .87 0 11.64)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset={0} stopColor="#feffff" />
|
||||
<stop offset={0.4} stopColor="#feffff" />
|
||||
<stop offset={0.51} stopColor="#f9fcfc" />
|
||||
<stop offset={0.62} stopColor="#edf3f5" />
|
||||
<stop offset={0.7} stopColor="#dee9ec" />
|
||||
<stop offset={0.72} stopColor="#d8e4e8" />
|
||||
<stop offset={0.76} stopColor="#ccd8df" />
|
||||
<stop offset={0.8} stopColor="#c8d5dd" />
|
||||
<stop offset={0.83} stopColor="#ccd6de" />
|
||||
<stop offset={0.85} stopColor="#d8dbe2" />
|
||||
<stop offset={0.88} stopColor="#ede3e9" />
|
||||
<stop offset={0.9} stopColor="#ffebef" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
xlinkHref="#snoo-radial-gragient"
|
||||
id="snoo-radial-gragient-2"
|
||||
cx={47.31}
|
||||
r={50.98}
|
||||
fx={47.31}
|
||||
/>
|
||||
<radialGradient
|
||||
xlinkHref="#snoo-radial-gragient"
|
||||
id="snoo-radial-gragient-3"
|
||||
cx={109.61}
|
||||
cy={85.59}
|
||||
r={153.78}
|
||||
fx={109.61}
|
||||
fy={85.59}
|
||||
gradientTransform="matrix(1 0 0 .7 0 25.56)"
|
||||
/>
|
||||
<radialGradient
|
||||
id="snoo-radial-gragient-4"
|
||||
cx={-6.01}
|
||||
cy={64.68}
|
||||
r={12.85}
|
||||
fx={-6.01}
|
||||
fy={64.68}
|
||||
gradientTransform="matrix(1.07 0 0 1.55 81.08 27.26)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset={0} stopColor="#f60" />
|
||||
<stop offset={0.5} stopColor="#ff4500" />
|
||||
<stop offset={0.7} stopColor="#fc4301" />
|
||||
<stop offset={0.82} stopColor="#f43f07" />
|
||||
<stop offset={0.92} stopColor="#e53812" />
|
||||
<stop offset={1} stopColor="#d4301f" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
xlinkHref="#snoo-radial-gragient-4"
|
||||
id="snoo-radial-gragient-5"
|
||||
cx={-73.55}
|
||||
cy={64.68}
|
||||
r={12.85}
|
||||
fx={-73.55}
|
||||
fy={64.68}
|
||||
gradientTransform="matrix(-1.07 0 0 1.55 62.87 27.26)"
|
||||
/>
|
||||
<radialGradient
|
||||
id="snoo-radial-gragient-6"
|
||||
cx={107.93}
|
||||
cy={166.96}
|
||||
r={45.3}
|
||||
fx={107.93}
|
||||
fy={166.96}
|
||||
gradientTransform="matrix(1 0 0 .66 0 57.4)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset={0} stopColor="#172e35" />
|
||||
<stop offset={0.29} stopColor="#0e1c21" />
|
||||
<stop offset={0.73} stopColor="#030708" />
|
||||
<stop offset={1} />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
xlinkHref="#snoo-radial-gragient"
|
||||
id="snoo-radial-gragient-7"
|
||||
cx={147.88}
|
||||
cy={32.94}
|
||||
r={39.77}
|
||||
fx={147.88}
|
||||
fy={32.94}
|
||||
gradientTransform="matrix(1 0 0 .98 0 .54)"
|
||||
/>
|
||||
<radialGradient
|
||||
id="snoo-radial-gragient-8"
|
||||
cx={131.31}
|
||||
cy={73.08}
|
||||
r={32.6}
|
||||
fx={131.31}
|
||||
fy={73.08}
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset={0.48} stopColor="#7a9299" />
|
||||
<stop offset={0.67} stopColor="#172e35" />
|
||||
<stop offset={0.75} />
|
||||
<stop offset={0.82} stopColor="#172e35" />
|
||||
</radialGradient>
|
||||
<style>
|
||||
{"\n .snoo-cls-11{strokeWidth:0;fill:#ffc49c}\n "}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
fill="#ff4500"
|
||||
strokeWidth={0}
|
||||
d="M108 0C48.35 0 0 48.35 0 108c0 29.82 12.09 56.82 31.63 76.37l-20.57 20.57C6.98 209.02 9.87 216 15.64 216H108c59.65 0 108-48.35 108-108S167.65 0 108 0Z"
|
||||
/>
|
||||
<circle
|
||||
cx={169.22}
|
||||
cy={106.98}
|
||||
r={25.22}
|
||||
fill="url(#snoo-radial-gragient)"
|
||||
strokeWidth={0}
|
||||
/>
|
||||
<circle
|
||||
cx={46.78}
|
||||
cy={106.98}
|
||||
r={25.22}
|
||||
fill="url(#snoo-radial-gragient-2)"
|
||||
strokeWidth={0}
|
||||
/>
|
||||
<ellipse
|
||||
cx={108.06}
|
||||
cy={128.64}
|
||||
fill="url(#snoo-radial-gragient-3)"
|
||||
strokeWidth={0}
|
||||
rx={72}
|
||||
ry={54}
|
||||
/>
|
||||
<path
|
||||
fill="url(#snoo-radial-gragient-4)"
|
||||
strokeWidth={0}
|
||||
d="M86.78 123.48c-.42 9.08-6.49 12.38-13.56 12.38s-12.46-4.93-12.04-14.01c.42-9.08 6.49-15.02 13.56-15.02s12.46 7.58 12.04 16.66Z"
|
||||
/>
|
||||
<path
|
||||
fill="url(#snoo-radial-gragient-5)"
|
||||
strokeWidth={0}
|
||||
d="M129.35 123.48c.42 9.08 6.49 12.38 13.56 12.38s12.46-4.93 12.04-14.01c-.42-9.08-6.49-15.02-13.56-15.02s-12.46 7.58-12.04 16.66Z"
|
||||
/>
|
||||
<ellipse
|
||||
cx={79.63}
|
||||
cy={116.37}
|
||||
className="snoo-cls-11"
|
||||
rx={2.8}
|
||||
ry={3.05}
|
||||
/>
|
||||
<ellipse
|
||||
cx={146.21}
|
||||
cy={116.37}
|
||||
className="snoo-cls-11"
|
||||
rx={2.8}
|
||||
ry={3.05}
|
||||
/>
|
||||
<path
|
||||
fill="url(#snoo-radial-gragient-6)"
|
||||
strokeWidth={0}
|
||||
d="M108.06 142.92c-8.76 0-17.16.43-24.92 1.22-1.33.13-2.17 1.51-1.65 2.74 4.35 10.39 14.61 17.69 26.57 17.69s22.23-7.3 26.57-17.69c.52-1.23-.33-2.61-1.65-2.74-7.77-.79-16.16-1.22-24.92-1.22Z"
|
||||
/>
|
||||
<circle
|
||||
cx={147.49}
|
||||
cy={49.43}
|
||||
r={17.87}
|
||||
fill="url(#snoo-radial-gragient-7)"
|
||||
strokeWidth={0}
|
||||
/>
|
||||
<path
|
||||
fill="url(#snoo-radial-gragient-8)"
|
||||
strokeWidth={0}
|
||||
d="M107.8 76.92c-2.14 0-3.87-.89-3.87-2.27 0-16.01 13.03-29.04 29.04-29.04 2.14 0 3.87 1.73 3.87 3.87s-1.73 3.87-3.87 3.87c-11.74 0-21.29 9.55-21.29 21.29 0 1.38-1.73 2.27-3.87 2.27Z"
|
||||
/>
|
||||
<path
|
||||
fill="#842123"
|
||||
strokeWidth={0}
|
||||
d="M62.82 122.65c.39-8.56 6.08-14.16 12.69-14.16 6.26 0 11.1 6.39 11.28 14.33.17-8.88-5.13-15.99-12.05-15.99s-13.14 6.05-13.56 15.2c-.42 9.15 4.97 13.83 12.04 13.83h.52c-6.44-.16-11.3-4.79-10.91-13.2Zm90.48 0c-.39-8.56-6.08-14.16-12.69-14.16-6.26 0-11.1 6.39-11.28 14.33-.17-8.88 5.13-15.99 12.05-15.99 7.07 0 13.14 6.05 13.56 15.2.42 9.15-4.97 13.83-12.04 13.83h-.52c6.44-.16 11.3-4.79 10.91-13.2Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Notion = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
viewBox="0 0 256 268"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="#FFF"
|
||||
d="M16.092 11.538 164.09.608c18.179-1.56 22.85-.508 34.28 7.801l47.243 33.282C253.406 47.414 256 48.975 256 55.207v182.527c0 11.439-4.155 18.205-18.696 19.24L65.44 267.378c-10.913.517-16.11-1.043-21.825-8.327L8.826 213.814C2.586 205.487 0 199.254 0 191.97V29.726c0-9.352 4.155-17.153 16.092-18.188Z"
|
||||
/>
|
||||
<path d="M164.09.608 16.092 11.538C4.155 12.573 0 20.374 0 29.726v162.245c0 7.284 2.585 13.516 8.826 21.843l34.789 45.237c5.715 7.284 10.912 8.844 21.825 8.327l171.864-10.404c14.532-1.035 18.696-7.801 18.696-19.24V55.207c0-5.911-2.336-7.614-9.21-12.66l-1.185-.856L198.37 8.409C186.94.1 182.27-.952 164.09.608ZM69.327 52.22c-14.033.945-17.216 1.159-25.186-5.323L23.876 30.778c-2.06-2.086-1.026-4.69 4.163-5.207l142.274-10.395c11.947-1.043 18.17 3.12 22.842 6.758l24.401 17.68c1.043.525 3.638 3.637.517 3.637L71.146 52.095l-1.819.125Zm-16.36 183.954V81.222c0-6.767 2.077-9.887 8.3-10.413L230.02 60.93c5.724-.517 8.31 3.12 8.31 9.879v153.917c0 6.767-1.044 12.49-10.387 13.008l-161.487 9.361c-9.343.517-13.489-2.594-13.489-10.921ZM212.377 89.53c1.034 4.681 0 9.362-4.681 9.897l-7.783 1.542v114.404c-6.758 3.637-12.981 5.715-18.18 5.715-8.308 0-10.386-2.604-16.609-10.396l-50.898-80.079v77.476l16.1 3.646s0 9.362-12.989 9.362l-35.814 2.077c-1.043-2.086 0-7.284 3.63-8.318l9.351-2.595V109.823l-12.98-1.052c-1.044-4.68 1.55-11.439 8.826-11.965l38.426-2.585 52.958 81.113v-71.76l-13.498-1.552c-1.043-5.733 3.111-9.896 8.3-10.404l35.84-2.087Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const X = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="none"
|
||||
viewBox="0 0 1200 1227"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M714.163 519.284 1160.89 0h-105.86L667.137 450.887 357.328 0H0l468.492 681.821L0 1226.37h105.866l409.625-476.152 327.181 476.152H1200L714.137 519.284h.026ZM569.165 687.828l-47.468-67.894-377.686-540.24h162.604l304.797 435.991 47.468 67.894 396.2 566.721H892.476L569.165 687.854v-.026Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Google = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 256 262"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"
|
||||
fill="#ffffff"
|
||||
/>
|
||||
<path
|
||||
d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"
|
||||
fill="#ffffff"
|
||||
/>
|
||||
<path
|
||||
d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782"
|
||||
fill="#ffffff"
|
||||
/>
|
||||
<path
|
||||
d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"
|
||||
fill="#ffffff"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
/**
|
||||
* @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-callback-ref/src/useCallbackRef.tsx
|
||||
*/
|
||||
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
|
||||
/**
|
||||
* A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a
|
||||
* prop or avoid re-executing effects when passed as a dependency
|
||||
*/
|
||||
function useCallbackRef<T extends (...args: never[]) => unknown>(
|
||||
callback: T | undefined,
|
||||
): T {
|
||||
const callbackRef = useRef(callback);
|
||||
|
||||
useEffect(() => {
|
||||
callbackRef.current = callback;
|
||||
});
|
||||
|
||||
// https://github.com/facebook/react/issues/19240
|
||||
return useMemo(() => ((...args) => callbackRef.current?.(...args)) as T, []);
|
||||
}
|
||||
|
||||
export { useCallbackRef };
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
import * as React from "react";
|
||||
|
||||
import { useCallbackRef } from "@repo/ui/src/hooks/use-callback-ref.js";
|
||||
|
||||
/**
|
||||
* @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-controllable-state/src/useControllableState.tsx
|
||||
*/
|
||||
|
||||
type UseControllableStateParams<T> = {
|
||||
prop?: T | undefined;
|
||||
defaultProp?: T | undefined;
|
||||
onChange?: (state: T) => void;
|
||||
};
|
||||
|
||||
type SetStateFn<T> = (prevState?: T) => T;
|
||||
|
||||
function useControllableState<T>({
|
||||
prop,
|
||||
defaultProp,
|
||||
onChange = () => {},
|
||||
}: UseControllableStateParams<T>) {
|
||||
const [uncontrolledProp, setUncontrolledProp] = useUncontrolledState({
|
||||
defaultProp,
|
||||
onChange,
|
||||
});
|
||||
const isControlled = prop !== undefined;
|
||||
const value = isControlled ? prop : uncontrolledProp;
|
||||
const handleChange = useCallbackRef(onChange);
|
||||
|
||||
const setValue: React.Dispatch<React.SetStateAction<T | undefined>> =
|
||||
React.useCallback(
|
||||
(nextValue) => {
|
||||
if (isControlled) {
|
||||
const setter = nextValue as SetStateFn<T>;
|
||||
const value =
|
||||
typeof nextValue === "function" ? setter(prop) : nextValue;
|
||||
if (value !== prop) handleChange(value as T);
|
||||
} else {
|
||||
setUncontrolledProp(nextValue);
|
||||
}
|
||||
},
|
||||
[isControlled, prop, setUncontrolledProp, handleChange],
|
||||
);
|
||||
|
||||
return [value, setValue] as const;
|
||||
}
|
||||
|
||||
function useUncontrolledState<T>({
|
||||
defaultProp,
|
||||
onChange,
|
||||
}: Omit<UseControllableStateParams<T>, "prop">) {
|
||||
const uncontrolledState = React.useState<T | undefined>(defaultProp);
|
||||
const [value] = uncontrolledState;
|
||||
const prevValueRef = React.useRef(value);
|
||||
const handleChange = useCallbackRef(onChange);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (prevValueRef.current !== value) {
|
||||
handleChange(value as T);
|
||||
prevValueRef.current = value;
|
||||
}
|
||||
}, [value, prevValueRef, handleChange]);
|
||||
|
||||
return uncontrolledState;
|
||||
}
|
||||
|
||||
export { useControllableState };
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 4.25V16.75M16.25 10.5H3.75" stroke="#C2C7CB" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 228 B |
|
|
@ -1,3 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.25 16.25L17.5 10M17.5 10L11.25 3.75M17.5 10H2.5" stroke="#369DFD" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 249 B |
|
|
@ -1,3 +0,0 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.1875 10C7.1875 10.0829 7.15458 10.1624 7.09597 10.221C7.03737 10.2796 6.95788 10.3125 6.875 10.3125C6.79212 10.3125 6.71263 10.2796 6.65403 10.221C6.59542 10.1624 6.5625 10.0829 6.5625 10C6.5625 9.91712 6.59542 9.83763 6.65403 9.77903C6.71263 9.72042 6.79212 9.6875 6.875 9.6875C6.95788 9.6875 7.03737 9.72042 7.09597 9.77903C7.15458 9.83763 7.1875 9.91712 7.1875 10ZM7.1875 10H6.875M10.3125 10C10.3125 10.0829 10.2796 10.1624 10.221 10.221C10.1624 10.2796 10.0829 10.3125 10 10.3125C9.91712 10.3125 9.83763 10.2796 9.77903 10.221C9.72042 10.1624 9.6875 10.0829 9.6875 10C9.6875 9.91712 9.72042 9.83763 9.77903 9.77903C9.83763 9.72042 9.91712 9.6875 10 9.6875C10.0829 9.6875 10.1624 9.72042 10.221 9.77903C10.2796 9.83763 10.3125 9.91712 10.3125 10ZM10.3125 10H10M13.4375 10C13.4375 10.0829 13.4046 10.1624 13.346 10.221C13.2874 10.2796 13.2079 10.3125 13.125 10.3125C13.0421 10.3125 12.9626 10.2796 12.904 10.221C12.8454 10.1624 12.8125 10.0829 12.8125 10C12.8125 9.91712 12.8454 9.83763 12.904 9.77903C12.9626 9.72042 13.0421 9.6875 13.125 9.6875C13.2079 9.6875 13.2874 9.72042 13.346 9.77903C13.4046 9.83763 13.4375 9.91712 13.4375 10ZM13.4375 10H13.125M17.5 10C17.5 13.7967 14.1417 16.875 10 16.875C9.28099 16.8759 8.56503 16.7814 7.87083 16.5942C6.8923 17.2824 5.6986 17.5951 4.50833 17.475C4.376 17.4622 4.24422 17.4442 4.11333 17.4208C4.52406 16.9368 4.80456 16.356 4.92833 15.7333C5.00333 15.3525 4.8175 14.9825 4.53917 14.7117C3.275 13.4817 2.5 11.8242 2.5 10C2.5 6.20333 5.85833 3.125 10 3.125C14.1417 3.125 17.5 6.20333 17.5 10Z" stroke="#989EA4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
|
|
@ -1,3 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.5 17.5L14 14L17.5 6.5L10 10L6.5 17.5ZM12 13C11.7167 13 11.4793 12.904 11.288 12.712C11.0967 12.52 11.0007 12.2827 11 12C10.9993 11.7173 11.0953 11.48 11.288 11.288C11.4807 11.096 11.718 11 12 11C12.282 11 12.5197 11.096 12.713 11.288C12.9063 11.48 13.002 11.7173 13 12C12.998 12.2827 12.902 12.5203 12.712 12.713C12.522 12.9057 12.2847 13.0013 12 13ZM12 22C10.6167 22 9.31667 21.7373 8.1 21.212C6.88334 20.6867 5.825 19.9743 4.925 19.075C4.025 18.1757 3.31267 17.1173 2.788 15.9C2.26333 14.6827 2.00067 13.3827 2 12C1.99933 10.6173 2.262 9.31733 2.788 8.1C3.314 6.88267 4.02633 5.82433 4.925 4.925C5.82367 4.02567 6.882 3.31333 8.1 2.788C9.318 2.26267 10.618 2 12 2C13.382 2 14.682 2.26267 15.9 2.788C17.118 3.31333 18.1763 4.02567 19.075 4.925C19.9737 5.82433 20.6863 6.88267 21.213 8.1C21.7397 9.31733 22.002 10.6173 22 12C21.998 13.3827 21.7353 14.6827 21.212 15.9C20.6887 17.1173 19.9763 18.1757 19.075 19.075C18.1737 19.9743 17.1153 20.687 15.9 21.213C14.6847 21.739 13.3847 22.0013 12 22Z" fill="#6A737D"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
|
|
@ -1,3 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2.25C6.615 2.25 2.25 6.615 2.25 12C2.25 17.385 6.615 21.75 12 21.75C17.385 21.75 21.75 17.385 21.75 12C21.75 6.615 17.385 2.25 12 2.25ZM12.75 6C12.75 5.80109 12.671 5.61032 12.5303 5.46967C12.3897 5.32902 12.1989 5.25 12 5.25C11.8011 5.25 11.6103 5.32902 11.4697 5.46967C11.329 5.61032 11.25 5.80109 11.25 6V12C11.25 12.414 11.586 12.75 12 12.75H16.5C16.6989 12.75 16.8897 12.671 17.0303 12.5303C17.171 12.3897 17.25 12.1989 17.25 12C17.25 11.8011 17.171 11.6103 17.0303 11.4697C16.8897 11.329 16.6989 11.25 16.5 11.25H12.75V6Z" fill="#6A737D"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 701 B |
|
|
@ -1,17 +0,0 @@
|
|||
import AddIcon from "./add.svg";
|
||||
import ChatIcon from "./chat.svg";
|
||||
import HistoryIcon from "./history.svg";
|
||||
import ExploreIcon from "./explore.svg";
|
||||
import MemoriesIcon from "./memories.svg";
|
||||
import ArrowRightIcon from "./arrowRight.svg";
|
||||
import SelectIcon from "./select.svg";
|
||||
|
||||
export {
|
||||
AddIcon,
|
||||
ChatIcon,
|
||||
HistoryIcon,
|
||||
ExploreIcon,
|
||||
MemoriesIcon,
|
||||
ArrowRightIcon,
|
||||
SelectIcon,
|
||||
};
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.566 4.657C5.95195 4.55235 6.35011 4.49955 6.75 4.5H17.25C17.66 4.5 18.056 4.555 18.433 4.657C18.1837 4.15898 17.8006 3.7402 17.3268 3.44754C16.8529 3.15489 16.3069 2.99993 15.75 3H8.25C7.69288 2.99974 7.1467 3.15462 6.67265 3.44728C6.1986 3.73994 5.8154 4.15883 5.566 4.657ZM2.25 12C2.25 11.2044 2.56607 10.4413 3.12868 9.87868C3.69129 9.31607 4.45435 9 5.25 9H18.75C19.5456 9 20.3087 9.31607 20.8713 9.87868C21.4339 10.4413 21.75 11.2044 21.75 12V18C21.75 18.7956 21.4339 19.5587 20.8713 20.1213C20.3087 20.6839 19.5456 21 18.75 21H5.25C4.45435 21 3.69129 20.6839 3.12868 20.1213C2.56607 19.5587 2.25 18.7956 2.25 18V12ZM5.25 7.5C4.84 7.5 4.444 7.555 4.066 7.657C4.3154 7.15883 4.6986 6.73994 5.17265 6.44728C5.6467 6.15462 6.19288 5.99974 6.75 6H17.25C17.8069 5.99993 18.3529 6.15489 18.8268 6.44754C19.3006 6.7402 19.6837 7.15898 19.933 7.657C19.5474 7.55244 19.1496 7.49964 18.75 7.5H5.25Z" fill="#6A737D"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1 KiB |
|
|
@ -1,4 +0,0 @@
|
|||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.25 11.25L9 15L12.75 11.25" stroke="#858B92" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.25 6.75L9 3L12.75 6.75" stroke="#858B92" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 347 B |
|
|
@ -1,56 +0,0 @@
|
|||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@repo/ui/lib/utils";
|
||||
import React from "react";
|
||||
|
||||
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 };
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
import * as React from "react";
|
||||
|
||||
import { cn } from "@repo/ui/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,
|
||||
};
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { cn } from "@repo/ui/lib/utils";
|
||||
|
||||
function Divider({ className }: { className?: string }) {
|
||||
return <div className={cn("bg-[#2D343A] h-[1px] w-full", className)}></div>;
|
||||
}
|
||||
|
||||
export default Divider;
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
import * as React from "react";
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import {
|
||||
Controller,
|
||||
ControllerProps,
|
||||
FieldPath,
|
||||
FieldValues,
|
||||
FormProvider,
|
||||
useFormContext,
|
||||
} from "react-hook-form";
|
||||
|
||||
import { cn } from "@repo/ui/lib/utils";
|
||||
import { Label } from "@repo/ui/src/shadcn/label";
|
||||
|
||||
const Form = FormProvider;
|
||||
|
||||
type FormFieldContextValue<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = {
|
||||
name: TName;
|
||||
};
|
||||
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||
{} as FormFieldContextValue,
|
||||
);
|
||||
|
||||
const FormField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>({
|
||||
...props
|
||||
}: ControllerProps<TFieldValues, TName>) => {
|
||||
return (
|
||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||
<Controller {...props} />
|
||||
</FormFieldContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const useFormField = () => {
|
||||
const fieldContext = React.useContext(FormFieldContext);
|
||||
const itemContext = React.useContext(FormItemContext);
|
||||
const { getFieldState, formState } = useFormContext();
|
||||
|
||||
const fieldState = getFieldState(fieldContext.name, formState);
|
||||
|
||||
if (!fieldContext) {
|
||||
throw new Error("useFormField should be used within <FormField>");
|
||||
}
|
||||
|
||||
const { id } = itemContext;
|
||||
|
||||
return {
|
||||
id,
|
||||
name: fieldContext.name,
|
||||
formItemId: `${id}-form-item`,
|
||||
formDescriptionId: `${id}-form-item-description`,
|
||||
formMessageId: `${id}-form-item-message`,
|
||||
...fieldState,
|
||||
};
|
||||
};
|
||||
|
||||
type FormItemContextValue = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||
{} as FormItemContextValue,
|
||||
);
|
||||
|
||||
const FormItem = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const id = React.useId();
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||
</FormItemContext.Provider>
|
||||
);
|
||||
});
|
||||
FormItem.displayName = "FormItem";
|
||||
|
||||
const FormLabel = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { error, formItemId } = useFormField();
|
||||
|
||||
return (
|
||||
<Label
|
||||
ref={ref}
|
||||
className={cn(error && "text-destructive", className)}
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
FormLabel.displayName = "FormLabel";
|
||||
|
||||
const FormControl = React.forwardRef<
|
||||
React.ElementRef<typeof Slot>,
|
||||
React.ComponentPropsWithoutRef<typeof Slot>
|
||||
>(({ ...props }, ref) => {
|
||||
const { error, formItemId, formDescriptionId, formMessageId } =
|
||||
useFormField();
|
||||
|
||||
return (
|
||||
<Slot
|
||||
ref={ref}
|
||||
id={formItemId}
|
||||
aria-describedby={
|
||||
!error
|
||||
? `${formDescriptionId}`
|
||||
: `${formDescriptionId} ${formMessageId}`
|
||||
}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
FormControl.displayName = "FormControl";
|
||||
|
||||
const FormDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { formDescriptionId } = useFormField();
|
||||
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formDescriptionId}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
FormDescription.displayName = "FormDescription";
|
||||
|
||||
const FormMessage = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { error, formMessageId } = useFormField();
|
||||
const body = error ? String(error?.message) : children;
|
||||
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formMessageId}
|
||||
className={cn("text-sm font-medium text-destructive", className)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
</p>
|
||||
);
|
||||
});
|
||||
FormMessage.displayName = "FormMessage";
|
||||
|
||||
export {
|
||||
useFormField,
|
||||
Form,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
FormField,
|
||||
};
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@repo/ui/lib/utils";
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
);
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||
VariantProps<typeof labelVariants>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(labelVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Label.displayName = LabelPrimitive.Root.displayName;
|
||||
|
||||
export { Label };
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as ProgressPrimitive from "@radix-ui/react-progress";
|
||||
|
||||
import { cn } from "@repo/ui/lib/utils";
|
||||
|
||||
const Progress = React.forwardRef<
|
||||
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
||||
>(({ className, value, ...props }, ref) => (
|
||||
<ProgressPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ProgressPrimitive.Indicator
|
||||
className="h-full w-full flex-1 bg-primary transition-all"
|
||||
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||
/>
|
||||
</ProgressPrimitive.Root>
|
||||
));
|
||||
Progress.displayName = ProgressPrimitive.Root.displayName;
|
||||
|
||||
export { Progress };
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
||||
|
||||
import { cn } from "@repo/ui/lib/utils";
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
));
|
||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
|
||||
|
||||
const ScrollBar = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
>(({ className, orientation = "vertical", ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
ref={ref}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"flex touch-none select-none transition-colors",
|
||||
orientation === "vertical" &&
|
||||
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||
orientation === "horizontal" &&
|
||||
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
));
|
||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
|
||||
|
||||
export { ScrollArea, ScrollBar };
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { useTheme } from "next-app-theme/use-theme";
|
||||
import { Sun, Moon } from "lucide-react";
|
||||
|
||||
export default function ThemeToggle() {
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
const icon = theme === "dark" ? <Sun /> : <Moon />;
|
||||
|
||||
return <button onClick={toggleTheme}>{icon}</button>;
|
||||
}
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
"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 "@repo/ui/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,
|
||||
};
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
Toast,
|
||||
ToastClose,
|
||||
ToastDescription,
|
||||
ToastProvider,
|
||||
ToastTitle,
|
||||
ToastViewport,
|
||||
} from "@repo/ui/src/shadcn/toast";
|
||||
import { useToast } from "@repo/ui/src/shadcn/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>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
"use client";
|
||||
|
||||
// Inspired by react-hot-toast library
|
||||
import * as React from "react";
|
||||
|
||||
import type { ToastActionElement, ToastProps } from "packages/ui/src/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 };
|
||||
4
packages/ui/src/ui.d.ts
vendored
|
|
@ -1,4 +0,0 @@
|
|||
declare module "*.svg" {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
|
|
@ -4,5 +4,5 @@
|
|||
"outDir": "dist"
|
||||
},
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"include": ["src"]
|
||||
"include": ["shadcn", "icons", "hooks", "components"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"compilerOptions": {
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src", "turbo"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"include": ["shadcn", "icons", "hooks", "components"]
|
||||
}
|
||||
|
|
|
|||