aboutsummaryrefslogtreecommitdiff
path: root/apps/web/src/components
diff options
context:
space:
mode:
authorDhravya <[email protected]>2024-04-03 00:24:05 -0700
committerDhravya <[email protected]>2024-04-03 00:24:05 -0700
commit20febf888b6ca42fba462390f723573807aef502 (patch)
treea7e157c07d3868a4e42135a91aeb8606781251db /apps/web/src/components
parentupdate: catch up with main (diff)
parentresponsiveness (diff)
downloadsupermemory-20febf888b6ca42fba462390f723573807aef502.tar.xz
supermemory-20febf888b6ca42fba462390f723573807aef502.zip
uptodate with main
Diffstat (limited to 'apps/web/src/components')
-rw-r--r--apps/web/src/components/Main.tsx56
-rw-r--r--apps/web/src/components/MemoryDrawer.tsx36
-rw-r--r--apps/web/src/components/Sidebar/CategoryItem.tsx307
-rw-r--r--apps/web/src/components/Sidebar/FilterCombobox.tsx109
-rw-r--r--apps/web/src/components/Sidebar/MemoriesBar.tsx147
-rw-r--r--apps/web/src/components/Sidebar/PagesItem.tsx4
-rw-r--r--apps/web/src/components/Sidebar/index.tsx135
-rw-r--r--apps/web/src/components/ui/command.tsx155
-rw-r--r--apps/web/src/components/ui/dialog.tsx122
-rw-r--r--apps/web/src/components/ui/drawer.tsx14
-rw-r--r--apps/web/src/components/ui/input.tsx34
-rw-r--r--apps/web/src/components/ui/textarea.tsx35
12 files changed, 1104 insertions, 50 deletions
diff --git a/apps/web/src/components/Main.tsx b/apps/web/src/components/Main.tsx
new file mode 100644
index 00000000..b6ad3787
--- /dev/null
+++ b/apps/web/src/components/Main.tsx
@@ -0,0 +1,56 @@
+"use client";
+import { useEffect, useRef, useState } from "react";
+import { FilterCombobox } from "./Sidebar/FilterCombobox";
+import { Textarea2 } from "./ui/textarea";
+import { ArrowRight } from "lucide-react";
+import { MemoryDrawer } from "./MemoryDrawer";
+import useViewport from "@/hooks/useViewport";
+
+export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
+ const [value, setValue] = useState("");
+ const { width } = useViewport();
+
+ const textArea = useRef<HTMLTextAreaElement>(null);
+
+ useEffect(() => {
+ function onResize() {
+ if (!textArea.current || !window.visualViewport) return;
+
+ const visualViewportHeight = window.visualViewport.height;
+ }
+
+ window.visualViewport?.addEventListener("resize", onResize);
+ return () => window.visualViewport?.removeEventListener("resize", onResize);
+ }, []);
+
+ return (
+ <main
+ data-sidebar-open={sidebarOpen}
+ className="flex h-screen max-h-screen w-full items-end justify-center px-5 pb-[20vh] pt-5 md:items-center md:px-60 md:[&[data-sidebar-open='true']]:px-20"
+ >
+ <Textarea2
+ ref={textArea}
+ className="h-max max-h-[30em] min-h-[3em] resize-y flex-row items-start justify-center overflow-auto py-5 md:h-[20vh] md:resize-none md:flex-col md:items-center md:justify-center md:p-2 md:pb-2 md:pt-2"
+ textAreaProps={{
+ placeholder: "Ask your SuperMemory...",
+ className:
+ "h-auto overflow-auto md:h-full md:resize-none text-lg py-0 px-2 md:py-0 md:p-5 resize-y text-rgray-11 w-full min-h-[1em]",
+ value,
+ autoFocus: true,
+ onChange: (e) => setValue(e.target.value),
+ }}
+ >
+ <div className="text-rgray-11/70 flex h-full w-fit items-center justify-center pl-0 md:w-full md:p-2">
+ <FilterCombobox className="hidden md:flex" />
+ <button
+ disabled={value.trim().length < 1}
+ className="text-rgray-11/70 bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-4 mt-auto flex items-center justify-center rounded-full p-2 ring-2 ring-transparent focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:ml-auto md:mt-0"
+ >
+ <ArrowRight className="h-5 w-5" />
+ </button>
+ </div>
+ </Textarea2>
+ {width <= 768 && <MemoryDrawer />}
+ </main>
+ );
+}
diff --git a/apps/web/src/components/MemoryDrawer.tsx b/apps/web/src/components/MemoryDrawer.tsx
new file mode 100644
index 00000000..f9d7d6c4
--- /dev/null
+++ b/apps/web/src/components/MemoryDrawer.tsx
@@ -0,0 +1,36 @@
+import { useState } from "react";
+import { Drawer, DrawerContent, DrawerOverlay } from "./ui/drawer";
+import { MemoryIcon } from "@/assets/Memories";
+
+export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
+
+export function MemoryDrawer({ className, ...props }: Props) {
+ const [activeSnapPoint, setActiveSnapPoint] = useState<
+ number | null | string
+ >(0.1);
+
+ return (
+ <Drawer
+ snapPoints={[0.1, 0.9]}
+ activeSnapPoint={activeSnapPoint}
+ shouldScaleBackground={false}
+ setActiveSnapPoint={setActiveSnapPoint}
+ open={true}
+ dismissible={false}
+ modal={false}
+ >
+ <DrawerContent
+ overlay={false}
+ className="border-rgray-6 h-full w-screen border-2 pt-4 focus-visible:outline-none"
+ handle={false}
+ >
+ <div className="bg-rgray-4 border-rgray-6 text-rgray-11 absolute left-1/2 top-0 flex w-fit -translate-x-1/2 -translate-y-1/2 items-center justify-center gap-2 rounded-md border-2 px-3 py-2">
+ <MemoryIcon className="h-7 w-7" />
+ Memories
+ </div>
+ Hello
+ </DrawerContent>
+ <DrawerOverlay className="relative bg-transparent" />
+ </Drawer>
+ );
+}
diff --git a/apps/web/src/components/Sidebar/CategoryItem.tsx b/apps/web/src/components/Sidebar/CategoryItem.tsx
new file mode 100644
index 00000000..c2e72ba5
--- /dev/null
+++ b/apps/web/src/components/Sidebar/CategoryItem.tsx
@@ -0,0 +1,307 @@
+"use client";
+import { cleanUrl } from "@/lib/utils";
+import { StoredContent } from "@/server/db/schema";
+import {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+} from "../ui/dropdown-menu";
+import { Label } from "../ui/label";
+import {
+ ArrowUpRight,
+ MoreHorizontal,
+ Tags,
+ ChevronDown,
+ Edit3,
+ Trash2,
+ Save,
+ ChevronRight,
+ Plus,
+ Minus,
+} from "lucide-react";
+import { useState } from "react";
+import {
+ Drawer,
+ DrawerContent,
+ DrawerHeader,
+ DrawerTitle,
+ DrawerDescription,
+ DrawerFooter,
+ DrawerClose,
+} from "../ui/drawer";
+import { Input } from "../ui/input";
+import { Textarea } from "../ui/textarea";
+import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
+import {
+ AnimatePresence,
+ motion,
+ Reorder,
+ useMotionValue,
+} from "framer-motion";
+
+const pages: StoredContent[] = [
+ {
+ id: 1,
+ content: "",
+ title: "Visual Studio Code",
+ url: "https://code.visualstudio.com",
+ description: "",
+ image: "https://code.visualstudio.com/favicon.ico",
+ baseUrl: "https://code.visualstudio.com",
+ savedAt: new Date(),
+ space: ""
+ },
+ {
+ id: 2,
+ content: "",
+ title: "yxshv/vscode: An unofficial remake of vscode's landing page",
+ url: "https://github.com/yxshv/vscode",
+ description: "",
+ image: "https://github.com/favicon.ico",
+ baseUrl: "https://github.com",
+ savedAt: new Date(),
+ space: ""
+ },
+ {
+ id: 3,
+ content: "",
+ title: "yxshv/vscode: An unofficial remake of vscode's landing page",
+ url: "https://github.com/yxshv/vscode",
+ description: "",
+ image: "https://github.com/favicon.ico",
+ baseUrl: "https://github.com",
+ savedAt: new Date(),
+ space: ""
+ },
+ {
+ id: 4,
+ content: "",
+ title: "yxshv/vscode: An unofficial remake of vscode's landing page",
+ url: "https://github.com/yxshv/vscode",
+ description: "",
+ image: "https://github.com/favicon.ico",
+ baseUrl: "https://github.com",
+ savedAt: new Date(),
+ space: ""
+ },
+ {
+ id: 5,
+ content: "",
+ title: "yxshv/vscode: An unofficial remake of vscode's landing page",
+ url: "https://github.com/yxshv/vscode",
+ description: "",
+ image: "https://github.com/favicon.ico",
+ baseUrl: "https://github.com",
+ savedAt: new Date(),
+ space: ""
+ },
+ {
+ id: 6,
+ content: "",
+ title: "yxshv/vscode: An unofficial remake of vscode's landing page",
+ url: "https://github.com/yxshv/vscode",
+ description: "",
+ image: "https://github.com/favicon.ico",
+ baseUrl: "https://github.com",
+ savedAt: new Date(),
+ space: ""
+ },
+ {
+ id: 7,
+ content: "",
+ title: "yxshv/vscode: An unofficial remake of vscode's landing page",
+ url: "https://github.com/yxshv/vscode",
+ description: "",
+ image: "https://github.com/favicon.ico",
+ baseUrl: "https://github.com",
+ savedAt: new Date(),
+ space: ""
+ },
+ {
+ id: 8,
+ content: "",
+ title: "yxshv/vscode: An unofficial remake of vscode's landing page",
+ url: "https://github.com/yxshv/vscode",
+ description: "",
+ image: "https://github.com/favicon.ico",
+ baseUrl: "https://github.com",
+ savedAt: new Date(),
+ space: ""
+ },
+ {
+ id: 9,
+ content: "",
+ title: "yxshv/vscode: An unofficial remake of vscode's landing page",
+ url: "https://github.com/yxshv/vscode",
+ description: "",
+ image: "https://github.com/favicon.ico",
+ baseUrl: "https://github.com",
+ savedAt: new Date(),
+ space: ""
+ },
+];
+export const CategoryItem: React.FC<{ item: StoredContent }> = ({ item }) => {
+ const [isExpanded, setIsExpanded] = useState(false);
+ const [isEditDrawerOpen, setIsEditDrawerOpen] = useState(false);
+
+ const [items, setItems] = useState<StoredContent[]>(pages);
+
+ return (
+ <>
+ <div className="hover:bg-rgray-5 has-[button:focus]:bg-rgray-5 flex w-full items-center rounded-full py-1 pl-3 pr-2 transition [&:hover>button>div>[data-down-icon]]:scale-125 [&:hover>button>div>[data-down-icon]]:opacity-100 [&:hover>button>div>[data-down-icon]]:delay-150 [&:hover>button>div>[data-tags-icon]]:scale-75 [&:hover>button>div>[data-tags-icon]]:opacity-0 [&:hover>button>div>[data-tags-icon]]:delay-0 [&:hover>button]:opacity-100">
+ <button
+ onClick={() => setIsExpanded((prev) => !prev)}
+ className="flex w-full items-center gap-2 focus-visible:outline-none"
+ >
+ <div className="relative h-5 min-w-5">
+ <Tags
+ data-tags-icon
+ className="z-1 h-5 w-5 transition-[transform,opacity] delay-150 duration-150"
+ strokeWidth={1.5}
+ />
+ <ChevronDown
+ data-down-icon
+ className={`absolute left-1/2 top-1/2 z-[2] h-4 w-4 min-w-4 -translate-x-1/2 -translate-y-1/2 scale-75 opacity-0 transition-[transform,opacity] duration-150 ${isExpanded ? "rotate-180" : "rotate-0"}`}
+ strokeWidth={1.5}
+ />
+ </div>
+
+ <span className="w-full truncate text-nowrap text-left">
+ {item.title ?? "Untitled website"}
+ </span>
+ </button>
+ <Drawer
+ shouldScaleBackground
+ open={isEditDrawerOpen}
+ onOpenChange={setIsEditDrawerOpen}
+ >
+ <DrawerContent className="pb-10 lg:px-[25vw]">
+ <DrawerHeader className="relative mt-10 px-0">
+ <DrawerTitle className=" flex w-full justify-between">
+ Edit Page Details
+ </DrawerTitle>
+ <DrawerDescription>Change the page details</DrawerDescription>
+ <a
+ target="_blank"
+ href={item.url}
+ className="text-rgray-11/90 bg-rgray-3 text-md absolute right-0 top-0 flex w-min translate-y-1/2 items-center justify-center gap-1 rounded-full px-5 py-1"
+ >
+ <img src={item.image ?? "/brain.png"} className="h-4 w-4" />
+ {cleanUrl(item.url)}
+ </a>
+ </DrawerHeader>
+
+ <div className="mt-5">
+ <Label>Title</Label>
+ <Input
+ className=""
+ required
+ value={item.title ?? ""}
+ placeholder={item.title ?? "Enter the title for the page"}
+ />
+ </div>
+ <div className="mt-5">
+ <Label>Additional Context</Label>
+ <Textarea
+ className=""
+ value={item.content ?? ""}
+ placeholder={"Enter additional context for this page"}
+ />
+ </div>
+ <DrawerFooter className="flex flex-row-reverse items-center justify-end px-0 pt-5">
+ <DrawerClose className="flex items-center justify-center rounded-md px-3 py-2 ring-2 ring-transparent transition hover:bg-blue-100 hover:text-blue-400 focus-visible:bg-blue-100 focus-visible:text-blue-400 focus-visible:outline-none focus-visible:ring-blue-200 dark:hover:bg-blue-100/10 dark:focus-visible:bg-blue-100/10 dark:focus-visible:ring-blue-200/30">
+ <Save className="mr-2 h-4 w-4 " strokeWidth={1.5} />
+ Save
+ </DrawerClose>
+ <DrawerClose className="hover:bg-rgray-3 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 flex items-center justify-center rounded-md px-3 py-2 ring-2 ring-transparent transition focus-visible:outline-none">
+ Cancel
+ </DrawerClose>
+ <DrawerClose className="mr-auto flex items-center justify-center rounded-md bg-red-100 px-3 py-2 text-red-400 ring-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-red-200 dark:bg-red-100/10 dark:focus-visible:ring-red-200/30">
+ <Trash2 className="mr-2 h-4 w-4 " strokeWidth={1.5} />
+ Delete
+ </DrawerClose>
+ </DrawerFooter>
+ </DrawerContent>
+ </Drawer>
+ </div>
+ <AnimatePresence>
+ {isExpanded && (
+ <Reorder.Group
+ axis="y"
+ values={items}
+ onReorder={setItems}
+ as="div"
+ initial={{ height: 0 }}
+ animate={{ height: "auto" }}
+ exit={{
+ height: 0,
+ transition: {},
+ }}
+ layoutScroll
+ className="flex max-h-32 w-full flex-col items-center overflow-y-auto pl-7"
+ >
+ <AnimatePresence>
+ {items.map((item, i) => (
+ <CategoryPage
+ key={item.id}
+ index={i}
+ item={item}
+ onRemove={() =>
+ setItems((prev) => prev.filter((_, index) => i !== index))
+ }
+ />
+ ))}
+ </AnimatePresence>
+ </Reorder.Group>
+ )}
+ </AnimatePresence>
+ </>
+ );
+};
+
+export const CategoryPage: React.FC<{
+ item: StoredContent;
+ index: number;
+ onRemove?: () => void;
+}> = ({ item, onRemove, index }) => {
+ return (
+ <Reorder.Item
+ value={item}
+ as="div"
+ key={index}
+ exit={{ opacity: 0, scale: 0.8 }}
+ dragListener={false}
+ className="hover:bg-rgray-5 has-[a:focus]:bg-rgray-5 flex w-full items-center rounded-full py-1 pl-3 pr-2 transition [&:hover>a>div>[data-icon]]:scale-125 [&:hover>a>div>[data-icon]]:opacity-100 [&:hover>a>div>[data-icon]]:delay-150 [&:hover>a>div>img]:scale-75 [&:hover>a>div>img]:opacity-0 [&:hover>a>div>img]:delay-0 [&:hover>button]:opacity-100"
+ >
+ <a
+ href={item.url}
+ target="_blank"
+ className="flex w-[90%] items-center gap-2 focus-visible:outline-none"
+ >
+ <div className="relative h-4 min-w-4">
+ <img
+ src={item.image ?? "/brain.png"}
+ alt={item.title ?? "Untitiled website"}
+ className="z-1 h-4 w-4 transition-[transform,opacity] delay-150 duration-150"
+ />
+ <ArrowUpRight
+ data-icon
+ className="absolute left-1/2 top-1/2 z-[2] h-4 w-4 min-w-4 -translate-x-1/2 -translate-y-1/2 scale-75 opacity-0 transition-[transform,opacity] duration-150"
+ strokeWidth={1.5}
+ />
+ </div>
+
+ <span className="w-full truncate text-nowrap">
+ {item.title ?? "Untitled website"}
+ </span>
+ </a>
+ <button
+ onClick={() => onRemove?.()}
+ className="ml-auto w-4 min-w-4 rounded-[0.15rem] opacity-0 focus-visible:opacity-100 focus-visible:outline-none"
+ >
+ <Minus className="h-4 w-4 min-w-4" />
+ </button>
+ </Reorder.Item>
+ );
+};
diff --git a/apps/web/src/components/Sidebar/FilterCombobox.tsx b/apps/web/src/components/Sidebar/FilterCombobox.tsx
new file mode 100644
index 00000000..ade54711
--- /dev/null
+++ b/apps/web/src/components/Sidebar/FilterCombobox.tsx
@@ -0,0 +1,109 @@
+"use client";
+
+import * as React from "react";
+import { Check, ChevronsUpDown } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/components/ui/command";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import { SpaceIcon } from "@/assets/Memories";
+
+const spaces = [
+ {
+ value: "1",
+ label: "Cool Tech",
+ },
+ {
+ value: "2",
+ label: "Cool Courses",
+ },
+ {
+ value: "3",
+ label: "Cool Libraries",
+ },
+];
+
+export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
+
+export function FilterCombobox({ className, ...props }: Props) {
+ const [open, setOpen] = React.useState(false);
+ const [values, setValues] = React.useState<string[]>([]);
+
+ return (
+ <Popover open={open} onOpenChange={setOpen}>
+ <PopoverTrigger asChild>
+ <button
+ data-state-on={open}
+ className={cn(
+ "text-rgray-11/70 on:bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-3 relative flex items-center justify-center gap-1 rounded-md px-3 py-1.5 ring-2 ring-transparent focus-visible:outline-none",
+ className,
+ )}
+ {...props}
+ >
+ <SpaceIcon className="mr-1 h-5 w-5" />
+ Filter
+ <ChevronsUpDown className="h-4 w-4" />
+ <div
+ data-state-on={values.length > 0}
+ className="on:flex text-rgray-11 border-rgray-6 bg-rgray-2 absolute left-0 top-0 hidden aspect-[1] h-4 w-4 -translate-x-1/3 -translate-y-1/3 items-center justify-center rounded-full border text-center text-[9px]"
+ >
+ {values.length}
+ </div>
+ </button>
+ </PopoverTrigger>
+ <PopoverContent className="w-[200px] p-0">
+ <Command
+ filter={(val, search) =>
+ spaces
+ .find((s) => s.value === val)
+ ?.label.toLowerCase()
+ .includes(search.toLowerCase().trim())
+ ? 1
+ : 0
+ }
+ >
+ <CommandInput placeholder="Filter spaces..." />
+ <CommandList>
+ <CommandEmpty>Nothing found</CommandEmpty>
+ {/* bug: doesn't work on clicking with mouse only keyboard, weird */}
+ <CommandGroup>
+ {spaces.map((space) => (
+ <CommandItem
+ key={space.value}
+ value={space.value}
+ onSelect={(val) => {
+ setValues((prev) =>
+ prev.includes(val)
+ ? prev.filter((v) => v !== val)
+ : [...prev, val],
+ );
+ }}
+ >
+ <SpaceIcon className="mr-2 h-4 w-4" />
+ {space.label}
+ {values.includes(space.value)}
+ <Check
+ data-state-on={values.includes(space.value)}
+ className={cn("on:opacity-100 ml-auto h-4 w-4 opacity-0")}
+ />
+ </CommandItem>
+ ))}
+ </CommandGroup>
+ </CommandList>
+ </Command>
+ </PopoverContent>
+ </Popover>
+ );
+}
diff --git a/apps/web/src/components/Sidebar/MemoriesBar.tsx b/apps/web/src/components/Sidebar/MemoriesBar.tsx
new file mode 100644
index 00000000..72ee6d42
--- /dev/null
+++ b/apps/web/src/components/Sidebar/MemoriesBar.tsx
@@ -0,0 +1,147 @@
+import {
+ MemoryWithImage,
+ MemoryWithImages3,
+ MemoryWithImages2,
+} from '@/assets/MemoryWithImages';
+import { type Space } from '../../../types/memory';
+import { InputWithIcon } from '../ui/input';
+import { Search } from 'lucide-react';
+
+export function MemoriesBar() {
+ const spaces: Space[] = [
+ {
+ id: 1,
+ title: 'Cool Tech',
+ description: 'Really cool mind blowing tech',
+ content: [
+ {
+ id: 1,
+ title: 'Perplexity',
+ description: 'A good ui',
+ content: '',
+ image: 'https://perplexity.ai/favicon.ico',
+ url: 'https://perplexity.ai',
+ savedAt: new Date(),
+ baseUrl: 'https://perplexity.ai',
+ space: 'Cool tech',
+ },
+ {
+ id: 2,
+ title: 'Pi.ai',
+ description: 'A good ui',
+ content: '',
+ image: 'https://pi.ai/pi-logo-192.png?v=2',
+ url: 'https://pi.ai',
+ savedAt: new Date(),
+ baseUrl: 'https://pi.ai',
+ space: 'Cool tech',
+ },
+ {
+ id: 3,
+ title: 'Visual Studio Code',
+ description: 'A good ui',
+ content: '',
+ image: 'https://code.visualstudio.com/favicon.ico',
+ url: 'https://code.visualstudio.com',
+ savedAt: new Date(),
+ baseUrl: 'https://code.visualstudio.com',
+ space: 'Cool tech',
+ },
+ ],
+ },
+ {
+ id: 2,
+ title: 'Cool Courses',
+ description: 'Amazng',
+ content: [
+ {
+ id: 1,
+ title: 'Animation on the web',
+ description: 'A good ui',
+ content: '',
+ image: 'https://animations.dev/favicon.ico',
+ url: 'https://animations.dev',
+ savedAt: new Date(),
+ baseUrl: 'https://animations.dev',
+ space: 'Cool courses',
+ },
+ {
+ id: 2,
+ title: 'Tailwind Course',
+ description: 'A good ui',
+ content: '',
+ image:
+ 'https://tailwindcss.com/_next/static/media/tailwindcss-mark.3c5441fc7a190fb1800d4a5c7f07ba4b1345a9c8.svg',
+ url: 'https://tailwindcss.com',
+ savedAt: new Date(),
+ baseUrl: 'https://tailwindcss.com',
+ space: 'Cool courses',
+ },
+ ],
+ },
+ {
+ id: 3,
+ title: 'Cool Libraries',
+ description: 'Really cool mind blowing tech',
+ content: [
+ {
+ id: 1,
+ title: 'Perplexity',
+ description: 'A good ui',
+ content: '',
+ image: 'https://yashverma.me/logo.jpg',
+ url: 'https://perplexity.ai',
+ savedAt: new Date(),
+ baseUrl: 'https://perplexity.ai',
+ space: 'Cool libraries',
+ },
+ ],
+ },
+ ];
+
+ return (
+ <div className="text-rgray-11 flex w-full flex-col items-start py-8 text-left">
+ <div className="w-full px-8">
+ <h1 className="w-full text-2xl">Your Memories</h1>
+ <InputWithIcon
+ placeholder="Search"
+ icon={<Search className="text-rgray-11 h-5 w-5 opacity-50" />}
+ className="bg-rgray-4 mt-2 w-full"
+ />
+ </div>
+ <div className="grid w-full grid-flow-row grid-cols-3 gap-1 px-2 py-5">
+ {spaces.map((space) => (
+ <Space key={space.id} {...space} />
+ ))}
+ </div>
+ </div>
+ );
+}
+
+export function Space({ title, description, content, id }: Space) {
+ console.log(title, content.map((c) => c.image).reverse());
+ return (
+ <button className="hover:bg-rgray-2 focus-visible:bg-rgray-2 focus-visible:ring-rgray-7 flex flex-col items-center justify-center rounded-md p-2 text-center ring-transparent transition focus-visible:outline-none focus-visible:ring-2">
+ {content.length > 2 ? (
+ <MemoryWithImages3
+ className="h-24 w-24"
+ id={id.toString()}
+ images={content.map((c) => c.image).reverse() as string[]}
+ />
+ ) : content.length === 1 ? (
+ <MemoryWithImage
+ className="h-24 w-24"
+ id={id.toString()}
+ image={content[0].image!}
+ />
+ ) : (
+ <MemoryWithImages2
+ className="h-24 w-24"
+ id={id.toString()}
+ images={content.map((c) => c.image).reverse() as string[]}
+ />
+ )}
+ <span>{title}</span>
+ </button>
+ );
+}
diff --git a/apps/web/src/components/Sidebar/PagesItem.tsx b/apps/web/src/components/Sidebar/PagesItem.tsx
index ce762ae5..fea8bf33 100644
--- a/apps/web/src/components/Sidebar/PagesItem.tsx
+++ b/apps/web/src/components/Sidebar/PagesItem.tsx
@@ -36,7 +36,7 @@ export const PageItem: React.FC<{ item: StoredContent }> = ({ item }) => {
const [isEditDrawerOpen, setIsEditDrawerOpen] = useState(false);
return (
- <div className="hover:bg-rgray-5 focus-within:bg-rgray-5 flex w-full items-center rounded-full py-1 pl-3 pr-2 transition [&:hover>a>div>[data-upright-icon]]:scale-125 [&:hover>a>div>[data-upright-icon]]:opacity-100 [&:hover>a>div>[data-upright-icon]]:delay-150 [&:hover>a>div>img]:scale-75 [&:hover>a>div>img]:opacity-0 [&:hover>a>div>img]:delay-0 [&:hover>button]:opacity-100">
+ <div className="hover:bg-rgray-5 has-[a:focus]:bg-rgray-5 flex w-full items-center rounded-full py-1 pl-3 pr-2 transition [&:hover>a>div>[data-icon]]:scale-125 [&:hover>a>div>[data-icon]]:opacity-100 [&:hover>a>div>[data-icon]]:delay-150 [&:hover>a>div>img]:scale-75 [&:hover>a>div>img]:opacity-0 [&:hover>a>div>img]:delay-0 [&:hover>button]:opacity-100">
<a
href={item.url}
target="_blank"
@@ -49,7 +49,7 @@ export const PageItem: React.FC<{ item: StoredContent }> = ({ item }) => {
className="z-1 h-4 w-4 transition-[transform,opacity] delay-150 duration-150"
/>
<ArrowUpRight
- data-upright-icon
+ data-icon
className="absolute left-1/2 top-1/2 z-[2] h-4 w-4 min-w-4 -translate-x-1/2 -translate-y-1/2 scale-75 opacity-0 transition-[transform,opacity] duration-150"
strokeWidth={1.5}
/>
diff --git a/apps/web/src/components/Sidebar/index.tsx b/apps/web/src/components/Sidebar/index.tsx
index 80d4beb5..1680000b 100644
--- a/apps/web/src/components/Sidebar/index.tsx
+++ b/apps/web/src/components/Sidebar/index.tsx
@@ -1,46 +1,103 @@
-"use server";
+"use client";
import { StoredContent } from "@/server/db/schema";
-import { AddNewPagePopover, PageItem } from "./PagesItem";
-
-export default async function Sidebar() {
- const pages: StoredContent[] = [
- {
- id: 1,
- content: "",
- title: "Visual Studio Code",
- url: "https://code.visualstudio.com",
- description: "",
- image: "https://code.visualstudio.com/favicon.ico",
- baseUrl: "https://code.visualstudio.com",
- savedAt: new Date(),
- },
- {
- id: 2,
- content: "",
- title: "yxshv/vscode: An unofficial remake of vscode's landing page",
- url: "https://github.com/yxshv/vscode",
- description: "",
- image: "https://github.com/favicon.ico",
- baseUrl: "https://github.com",
- savedAt: new Date(),
- },
- ];
+import { MemoryIcon } from "../../assets/Memories";
+import { Trash2, User2 } from "lucide-react";
+import React, { useState } from "react";
+import { MemoriesBar } from "./MemoriesBar";
+
+export type MenuItem = {
+ icon: React.ReactNode | React.ReactNode[];
+ label: string;
+ content?: React.FC;
+};
+
+const menuItemsTop: Array<MenuItem> = [
+ {
+ icon: <MemoryIcon className="h-10 w-10" />,
+ label: "Memories",
+ content: MemoriesBar,
+ },
+];
+
+const menuItemsBottom: Array<MenuItem> = [
+ {
+ icon: <Trash2 strokeWidth={1.3} className="h-6 w-6" />,
+ label: "Trash",
+ },
+ {
+ icon: <User2 strokeWidth={1.3} className="h-6 w-6" />,
+ label: "Profile",
+ },
+];
+
+export default function Sidebar({
+ onSelectChange,
+}: {
+ onSelectChange?: (selectedItem: string | null) => void;
+}) {
+ const menuItems = [...menuItemsTop, ...menuItemsBottom];
+ const [selectedItem, setSelectedItem] = useState<string | null>(null);
+
+ React.useEffect(() => {
+ onSelectChange?.(selectedItem);
+ }, [selectedItem]);
+
+ const Subbar =
+ menuItems.find((i) => i.label === selectedItem)?.content ?? (() => <></>);
return (
- <aside className="bg-rgray-3 flex h-screen w-[25%] flex-col items-start justify-between py-5 pb-[50vh] font-light">
- <div className="flex items-center justify-center gap-1 px-5 text-xl font-normal">
- <img src="/brain.png" alt="logo" className="h-10 w-10" />
- SuperMemory
- </div>
- <div className="flex w-full flex-col items-start justify-center p-2">
- <h1 className="mb-1 flex w-full items-center justify-center px-3 font-normal">
- Pages
- <AddNewPagePopover />
- </h1>
- {pages.map((item) => (
- <PageItem key={item.id} item={item} />
+ <>
+ <div className="bg-rgray-2 border-r-rgray-6 hidden h-screen max-h-screen w-max flex-col items-center border-r px-2 py-5 text-sm font-light md:flex">
+ {menuItemsTop.map((item, index) => (
+ <MenuItem
+ key={index}
+ item={item}
+ selectedItem={selectedItem}
+ setSelectedItem={setSelectedItem}
+ />
+ ))}
+ <div className="mt-auto" />
+ {menuItemsBottom.map((item, index) => (
+ <MenuItem
+ key={index}
+ item={item}
+ selectedItem={selectedItem}
+ setSelectedItem={setSelectedItem}
+ />
))}
</div>
- </aside>
+ {selectedItem && (
+ <SubSidebar>
+ <Subbar />
+ </SubSidebar>
+ )}
+ </>
+ );
+}
+
+const MenuItem = ({
+ item: { icon, label },
+ selectedItem,
+ setSelectedItem,
+}: {
+ item: MenuItem;
+ selectedItem: string | null;
+ setSelectedItem: React.Dispatch<React.SetStateAction<string | null>>;
+}) => (
+ <button
+ data-state-on={selectedItem === label}
+ onClick={() => setSelectedItem((prev) => (prev === label ? null : label))}
+ className="on:opacity-100 on:bg-rgray-4 focus-visible:ring-rgray-7 flex w-full flex-col items-center justify-center rounded-md px-3 py-3 opacity-80 ring-2 ring-transparent transition hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-none"
+ >
+ {icon}
+ <span className="">{label}</span>
+ </button>
+);
+
+export function SubSidebar({ children }: { children?: React.ReactNode }) {
+ return (
+ <div className="bg-rgray-3 border-r-rgray-6 hidden h-screen w-[50vw] flex-col items-center border-r font-light md:flex">
+ {children}
+ </div>
);
}
diff --git a/apps/web/src/components/ui/command.tsx b/apps/web/src/components/ui/command.tsx
new file mode 100644
index 00000000..54070776
--- /dev/null
+++ b/apps/web/src/components/ui/command.tsx
@@ -0,0 +1,155 @@
+"use client";
+
+import * as React from "react";
+import { type DialogProps } from "@radix-ui/react-dialog";
+import { Command as CommandPrimitive } from "cmdk";
+import { Search } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+import { Dialog, DialogContent } from "@/components/ui/dialog";
+
+const Command = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive>
+>(({ className, ...props }, ref) => (
+ <CommandPrimitive
+ ref={ref}
+ className={cn(
+ "bg-rgray-3 text-rgray-11 flex h-full w-full flex-col overflow-hidden rounded-md",
+ className,
+ )}
+ {...props}
+ />
+));
+Command.displayName = CommandPrimitive.displayName;
+
+interface CommandDialogProps extends DialogProps {}
+
+const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
+ return (
+ <Dialog {...props}>
+ <DialogContent className="overflow-hidden p-0 shadow-lg">
+ <Command className="[&_[cmdk-group-heading]]:text-rgray-11 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
+ {children}
+ </Command>
+ </DialogContent>
+ </Dialog>
+ );
+};
+
+const CommandInput = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive.Input>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
+>(({ className, ...props }, ref) => (
+ <div
+ className="border-rgray-6 flex items-center border-b px-3"
+ cmdk-input-wrapper=""
+ >
+ <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
+ <CommandPrimitive.Input
+ ref={ref}
+ className={cn(
+ "placeholder:text-rgray-11/50 flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50",
+ className,
+ )}
+ {...props}
+ />
+ </div>
+));
+
+CommandInput.displayName = CommandPrimitive.Input.displayName;
+
+const CommandList = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive.List>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
+>(({ className, ...props }, ref) => (
+ <CommandPrimitive.List
+ ref={ref}
+ className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
+ {...props}
+ />
+));
+
+CommandList.displayName = CommandPrimitive.List.displayName;
+
+const CommandEmpty = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive.Empty>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
+>((props, ref) => (
+ <CommandPrimitive.Empty
+ ref={ref}
+ className="py-6 text-center text-sm"
+ {...props}
+ />
+));
+
+CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
+
+const CommandGroup = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive.Group>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
+>(({ className, ...props }, ref) => (
+ <CommandPrimitive.Group
+ ref={ref}
+ className={cn(
+ "text-rgray-12 [&_[cmdk-group-heading]]:text-rgray-11 overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
+ className,
+ )}
+ {...props}
+ />
+));
+
+CommandGroup.displayName = CommandPrimitive.Group.displayName;
+
+const CommandSeparator = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive.Separator>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+ <CommandPrimitive.Separator
+ ref={ref}
+ className={cn("bg-rgray-3 -mx-1 h-px", className)}
+ {...props}
+ />
+));
+CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
+
+const CommandItem = React.forwardRef<
+ React.ElementRef<typeof CommandPrimitive.Item>,
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
+>(({ className, ...props }, ref) => (
+ <CommandPrimitive.Item
+ ref={ref}
+ className={cn(
+ "aria-selected:bg-rgray-5 aria-selected:text-rgray-12 relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+ className,
+ )}
+ {...props}
+ />
+));
+
+CommandItem.displayName = CommandPrimitive.Item.displayName;
+
+const CommandShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLSpanElement>) => {
+ return (
+ <span
+ className={cn("text-gray-11 ml-auto text-xs tracking-widest", className)}
+ {...props}
+ />
+ );
+};
+CommandShortcut.displayName = "CommandShortcut";
+
+export {
+ Command,
+ CommandDialog,
+ CommandInput,
+ CommandList,
+ CommandEmpty,
+ CommandGroup,
+ CommandItem,
+ CommandShortcut,
+ CommandSeparator,
+};
diff --git a/apps/web/src/components/ui/dialog.tsx b/apps/web/src/components/ui/dialog.tsx
new file mode 100644
index 00000000..ec19b41a
--- /dev/null
+++ b/apps/web/src/components/ui/dialog.tsx
@@ -0,0 +1,122 @@
+"use client";
+
+import * as React from "react";
+import * as DialogPrimitive from "@radix-ui/react-dialog";
+import { X } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+const Dialog = DialogPrimitive.Root;
+
+const DialogTrigger = DialogPrimitive.Trigger;
+
+const DialogPortal = DialogPrimitive.Portal;
+
+const DialogClose = DialogPrimitive.Close;
+
+const DialogOverlay = React.forwardRef<
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
+>(({ className, ...props }, ref) => (
+ <DialogPrimitive.Overlay
+ ref={ref}
+ className={cn(
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80",
+ className,
+ )}
+ {...props}
+ />
+));
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
+
+const DialogContent = React.forwardRef<
+ React.ElementRef<typeof DialogPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
+>(({ className, children, ...props }, ref) => (
+ <DialogPortal>
+ <DialogOverlay />
+ <DialogPrimitive.Content
+ ref={ref}
+ className={cn(
+ "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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] border-rgray-6 bg-rgray-3 fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg",
+ className,
+ )}
+ {...props}
+ >
+ {children}
+ <DialogPrimitive.Close className="ring-offset-rgray-2 focus:ring-rgray-7 data-[state=open]:bg-rgray-3 data-[state=open]:text-rgray-11 absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none">
+ <X className="h-4 w-4" />
+ <span className="sr-only">Close</span>
+ </DialogPrimitive.Close>
+ </DialogPrimitive.Content>
+ </DialogPortal>
+));
+DialogContent.displayName = DialogPrimitive.Content.displayName;
+
+const DialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+ <div
+ className={cn(
+ "flex flex-col space-y-1.5 text-center sm:text-left",
+ className,
+ )}
+ {...props}
+ />
+);
+DialogHeader.displayName = "DialogHeader";
+
+const DialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+ <div
+ className={cn(
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
+ className,
+ )}
+ {...props}
+ />
+);
+DialogFooter.displayName = "DialogFooter";
+
+const DialogTitle = React.forwardRef<
+ React.ElementRef<typeof DialogPrimitive.Title>,
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
+>(({ className, ...props }, ref) => (
+ <DialogPrimitive.Title
+ ref={ref}
+ className={cn(
+ "text-lg font-semibold leading-none tracking-tight",
+ className,
+ )}
+ {...props}
+ />
+));
+DialogTitle.displayName = DialogPrimitive.Title.displayName;
+
+const DialogDescription = React.forwardRef<
+ React.ElementRef<typeof DialogPrimitive.Description>,
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
+>(({ className, ...props }, ref) => (
+ <DialogPrimitive.Description
+ ref={ref}
+ className={cn("text-rgray-11 text-sm", className)}
+ {...props}
+ />
+));
+DialogDescription.displayName = DialogPrimitive.Description.displayName;
+
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogClose,
+ DialogTrigger,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+};
diff --git a/apps/web/src/components/ui/drawer.tsx b/apps/web/src/components/ui/drawer.tsx
index 705ca01c..28e8dbdf 100644
--- a/apps/web/src/components/ui/drawer.tsx
+++ b/apps/web/src/components/ui/drawer.tsx
@@ -28,6 +28,7 @@ const DrawerOverlay = React.forwardRef<
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Overlay
ref={ref}
+ data-drawer-overlay
className={cn("fixed inset-0 z-50 bg-black/80", className)}
{...props}
/>
@@ -36,10 +37,13 @@ DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
const DrawerContent = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Content>,
- React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
->(({ className, children, ...props }, ref) => (
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> & {
+ overlay?: boolean;
+ handle?: boolean;
+ }
+>(({ className, children, overlay = true, handle = true, ...props }, ref) => (
<DrawerPortal>
- <DrawerOverlay />
+ {overlay && <DrawerOverlay />}
<DrawerPrimitive.Content
ref={ref}
className={cn(
@@ -48,7 +52,9 @@ const DrawerContent = React.forwardRef<
)}
{...props}
>
- <div className="bg-rgray-4 mx-auto mt-4 h-2 w-[100px] rounded-full " />
+ {handle && (
+ <div className="bg-rgray-4 mx-auto mt-4 h-2 w-[100px] rounded-full " />
+ )}
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
diff --git a/apps/web/src/components/ui/input.tsx b/apps/web/src/components/ui/input.tsx
index 8a7a9340..cee654a8 100644
--- a/apps/web/src/components/ui/input.tsx
+++ b/apps/web/src/components/ui/input.tsx
@@ -11,7 +11,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
<input
type={type}
className={cn(
- "border-rgray-6 text-rgray-12 ring-offset-rgray-2 placeholder:text-rgray-11 focus-visible:ring-rgray-7 flex h-10 w-full rounded-md border bg-transparent px-3 py-2 text-sm transition file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ",
+ "border-rgray-6 text-rgray-11 placeholder:text-rgray-11 focus-visible:ring-rgray-7 flex h-10 w-full rounded-md border bg-transparent px-3 py-2 text-sm transition file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-50 ",
className,
)}
ref={ref}
@@ -20,6 +20,34 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
);
},
);
-Input.displayName = "Input";
-export { Input };
+export interface InputWithIconProps
+ extends React.InputHTMLAttributes<HTMLInputElement> {
+ icon: React.ReactNode;
+}
+
+const InputWithIcon = React.forwardRef<HTMLInputElement, InputWithIconProps>(
+ ({ className, type, icon, ...props }, ref) => {
+ return (
+ <div
+ className={cn(
+ "border-rgray-6 text-rgray-11 focus-within:ring-rgray-7 flex h-10 w-full items-center justify-center gap-2 rounded-md border bg-transparent px-3 py-2 text-sm transition focus-within:outline-none focus-within:ring-2 ",
+ className,
+ )}
+ >
+ {icon}
+ <input
+ type={type}
+ className={
+ "placeholder:text-rgray-11/50 w-full bg-transparent file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
+ }
+ ref={ref}
+ {...props}
+ />
+ </div>
+ );
+ },
+);
+InputWithIcon.displayName = "Input";
+
+export { Input, InputWithIcon };
diff --git a/apps/web/src/components/ui/textarea.tsx b/apps/web/src/components/ui/textarea.tsx
index 68d8e79e..10a7ed75 100644
--- a/apps/web/src/components/ui/textarea.tsx
+++ b/apps/web/src/components/ui/textarea.tsx
@@ -10,7 +10,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
return (
<textarea
className={cn(
- "border-rgray-6 ring-offset-rgray-2 placeholder:text-rgray-11 focus-visible:ring-rgray-7 flex min-h-[80px] w-full rounded-md border bg-transparent px-3 py-2 text-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
+ "border-rgray-6 text-rgray-11 placeholder:text-rgray-11/70 focus-within:ring-rgray-7 flex min-h-[80px] w-full rounded-md border bg-transparent px-3 py-2 text-sm font-normal transition focus-within:outline-none focus-within:ring-2 disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
ref={ref}
@@ -21,4 +21,35 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
);
Textarea.displayName = "Textarea";
-export { Textarea };
+export interface Textarea2Props extends React.HTMLAttributes<HTMLDivElement> {
+ textAreaProps?: TextareaProps;
+}
+
+const Textarea2 = React.forwardRef<HTMLTextAreaElement, Textarea2Props>(
+ ({ className, children, textAreaProps: _textAreaProps, ...props }, ref) => {
+ const { className: textAreaClassName, ...textAreaProps } =
+ _textAreaProps || {};
+ return (
+ <div
+ className={cn(
+ "border-rgray-6 text-rgray-11 focus-within:ring-rgray-7 flex h-auto min-h-[80px] w-full flex-col items-start justify-center rounded-md border bg-transparent px-3 py-2 text-sm transition focus-within:outline-none focus-within:ring-2",
+ className,
+ )}
+ {...props}
+ >
+ <textarea
+ className={cn(
+ "placeholder:text-rgray-11/70 h-full w-full resize-none bg-transparent focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
+ textAreaClassName,
+ )}
+ ref={ref}
+ {...textAreaProps}
+ />
+ {children}
+ </div>
+ );
+ },
+);
+Textarea2.displayName = "Textarea2";
+
+export { Textarea, Textarea2 };