mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-05 08:06:19 +00:00
add custom Bin component
This commit is contained in:
parent
a23fd73a0b
commit
c22751ee68
7 changed files with 440 additions and 145 deletions
104
apps/web/src/assets/Bin.tsx
Normal file
104
apps/web/src/assets/Bin.tsx
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import { cn } from "@/lib/utils";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export const Bin: React.FC<React.HTMLAttributes<HTMLDivElement> & {}> = ({
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
const icon = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let timeout: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
const observer = new MutationObserver(function (mutations) {
|
||||
mutations.forEach(function (mutation) {
|
||||
if (
|
||||
mutation.type === "attributes" &&
|
||||
mutation.attributeName === "data-open" &&
|
||||
(mutation.oldValue === "false" || mutation.oldValue === null) &&
|
||||
icon.current?.dataset["open"] === "true"
|
||||
) {
|
||||
if (timeout) clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
icon.current!.dataset["open"] = "false";
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(icon.current!, {
|
||||
attributes: true, //configure it to listen to attribute changes
|
||||
});
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={icon}
|
||||
data-open="false"
|
||||
className={cn(
|
||||
"relative z-[100] flex w-full origin-bottom flex-col items-center justify-center transition-transform delay-500 duration-500 data-[open='true']:-translate-y-2 data-[open='true']:scale-150 data-[open='true']:delay-0 [&[data-open='true']>[data-lid]]:rotate-[150deg] [&[data-open='true']>[data-lid]]:delay-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<svg
|
||||
data-lid
|
||||
className="w-full origin-[90%_80%] transition-transform delay-500 duration-500 ease-in-out"
|
||||
viewBox="0 0 24 7"
|
||||
fill="none"
|
||||
strokeWidth={1}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M3 6H21"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M8 6V4C8 3 9 2 10 2H14C15 2 16 3 16 4V6"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<svg
|
||||
className="-mt-[1px] w-full"
|
||||
viewBox="0 0 24 19"
|
||||
fill="none"
|
||||
strokeWidth={1}
|
||||
data-trash-bin
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 1V15C19 16 18 17 17 17H7C6 17 5 16 5 15V1"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M10 6V12"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M14 6V12"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
import { svgId } from "@/lib/utils";
|
||||
|
||||
export const MemoryIcon: React.FC<React.SVGAttributes<SVGElement>> = (
|
||||
props,
|
||||
) => (
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
|
|||
|
||||
const textArea = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
console.log("main px", sidebarOpen);
|
||||
|
||||
useEffect(() => {
|
||||
function onResize() {
|
||||
if (!textArea.current || !window.visualViewport) return;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import {
|
||||
MemoryWithImage,
|
||||
MemoryWithImages3,
|
||||
|
|
@ -5,99 +6,125 @@ import {
|
|||
} from "@/assets/MemoryWithImages";
|
||||
import { type Space } from "../../../types/memory";
|
||||
import { InputWithIcon } from "../ui/input";
|
||||
import { MoreHorizontal, Search } from "lucide-react";
|
||||
import {
|
||||
ArrowUpRight,
|
||||
Edit3,
|
||||
MoreHorizontal,
|
||||
Search,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "../ui/dropdown-menu";
|
||||
import {
|
||||
animate,
|
||||
AnimatePresence,
|
||||
LayoutGroup,
|
||||
motion,
|
||||
useAnimate,
|
||||
Variant,
|
||||
} from "framer-motion";
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
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",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
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",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const [parent, enableAnimations] = useAutoAnimate();
|
||||
const [currentSpaces, setCurrentSpaces] = useState(spaces);
|
||||
|
||||
console.log("currentSpaces: ", currentSpaces);
|
||||
|
||||
return (
|
||||
<div className="text-rgray-11 flex w-full flex-col items-start py-8 text-left">
|
||||
|
|
@ -109,28 +136,129 @@ export function MemoriesBar() {
|
|||
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
|
||||
ref={parent}
|
||||
className="grid w-full grid-flow-row grid-cols-3 gap-1 px-2 py-5"
|
||||
>
|
||||
{currentSpaces.map((space) => (
|
||||
<SpaceItem
|
||||
onDelete={() =>
|
||||
setCurrentSpaces((prev) => prev.filter((s) => s.id !== space.id))
|
||||
}
|
||||
key={space.id}
|
||||
{...space}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Space({ title, description, content, id }: Space) {
|
||||
console.log(title, content.map((c) => c.image).reverse());
|
||||
const SpaceExitVariant: Variant = {
|
||||
opacity: 0,
|
||||
scale: 0,
|
||||
borderRadius: "50%",
|
||||
background: "var(--gray-1)",
|
||||
transition: {
|
||||
duration: 0.2,
|
||||
},
|
||||
};
|
||||
|
||||
export function SpaceItem({
|
||||
title,
|
||||
description,
|
||||
content,
|
||||
id,
|
||||
onDelete,
|
||||
}: Space & { onDelete: () => void }) {
|
||||
const [itemRef, animateItem] = useAnimate();
|
||||
|
||||
return (
|
||||
<div className="hover:bg-rgray-2 has-[[data-space-text]:focus-visible]:bg-rgray-2 has-[[data-space-text]:focus-visible]:ring-rgray-7 [&:has-[[data-space-text]:focus-visible]>[data-more-button]]:opacity-100 relative flex flex-col-reverse items-center justify-center rounded-md p-2 pb-4 text-center font-normal ring-transparent transition has-[[data-space-text]:focus-visible]:outline-none has-[[data-space-text]:focus-visible]:ring-2 [&:hover>[data-more-button]]:opacity-100">
|
||||
<motion.div
|
||||
ref={itemRef}
|
||||
className="hover:bg-rgray-2 has-[[data-space-text]:focus-visible]:bg-rgray-2 has-[[data-space-text]:focus-visible]:ring-rgray-7 [&:has-[[data-space-text]:focus-visible]>[data-more-button]]:opacity-100 relative flex flex-col-reverse items-center justify-center rounded-md p-2 pb-4 text-center font-normal ring-transparent transition has-[[data-space-text]:focus-visible]:outline-none has-[[data-space-text]:focus-visible]:ring-2 [&:hover>[data-more-button]]:opacity-100"
|
||||
>
|
||||
<button data-space-text className="focus-visible:outline-none">
|
||||
{title}
|
||||
</button>
|
||||
<button
|
||||
data-more-button
|
||||
className="hover:bg-rgray-3 focus-visible:bg-rgray-3 focus-visible:ring-rgray-7 absolute right-2 top-2 rounded-md p-1 opacity-0 ring-transparent transition focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2"
|
||||
>
|
||||
<MoreHorizontal className="text-rgray-11 h-5 w-5" />
|
||||
</button>
|
||||
<SpaceMoreButton
|
||||
onDelete={() => {
|
||||
if (!itemRef.current) return;
|
||||
const trash = document.querySelector("#trash")! as HTMLDivElement;
|
||||
const trashBin = document.querySelector("#trash-button")!;
|
||||
const trashRect = trashBin.getBoundingClientRect();
|
||||
const scopeRect = itemRef.current.getBoundingClientRect();
|
||||
const el = document.createElement("div");
|
||||
el.style.position = "fixed";
|
||||
el.style.top = "0";
|
||||
el.style.left = "0";
|
||||
el.style.width = "15px";
|
||||
el.style.height = "15px";
|
||||
el.style.backgroundColor = "var(--gray-7)";
|
||||
el.style.zIndex = "60";
|
||||
el.style.borderRadius = "50%";
|
||||
el.style.transform = "scale(5)";
|
||||
el.style.opacity = "0";
|
||||
trash.dataset["open"] = "true";
|
||||
const initial = {
|
||||
x: scopeRect.left + scopeRect.width / 2,
|
||||
y: scopeRect.top + scopeRect.height / 2,
|
||||
};
|
||||
const delta = {
|
||||
x:
|
||||
trashRect.left +
|
||||
trashRect.width / 2 -
|
||||
scopeRect.left +
|
||||
scopeRect.width / 2,
|
||||
y:
|
||||
trashRect.top +
|
||||
trashRect.height / 4 -
|
||||
scopeRect.top +
|
||||
scopeRect.height / 2,
|
||||
};
|
||||
const end = {
|
||||
x: trashRect.left + trashRect.width / 2,
|
||||
y: trashRect.top + trashRect.height / 4,
|
||||
};
|
||||
el.style.offsetPath = `path('M ${initial.x} ${initial.y} Q ${delta.x * 0.01} ${delta.y * 0.01} ${end.x} ${end.y}`;
|
||||
animateItem(itemRef.current, SpaceExitVariant, {
|
||||
duration: 0.2,
|
||||
}).then(() => {
|
||||
itemRef.current.style.scale = "0";
|
||||
onDelete();
|
||||
});
|
||||
document.body.appendChild(el);
|
||||
el.animate(
|
||||
{
|
||||
transform: ["scale(5)", "scale(1)"],
|
||||
opacity: [0, 0.3, 1],
|
||||
},
|
||||
{
|
||||
duration: 200,
|
||||
easing: "cubic-bezier(0.64, 0.57, 0.67, 1.53)",
|
||||
fill: "forwards",
|
||||
},
|
||||
);
|
||||
el.animate(
|
||||
{
|
||||
offsetDistance: ["0%", "100%"],
|
||||
},
|
||||
{
|
||||
duration: 2000,
|
||||
easing: "cubic-bezier(0.64, 0.57, 0.67, 1.53)",
|
||||
fill: "forwards",
|
||||
delay: 200,
|
||||
},
|
||||
).onfinish = () => {
|
||||
el.animate(
|
||||
{ transform: "scale(0)", opacity: 0 },
|
||||
{ duration: 200, fill: "forwards" },
|
||||
).onfinish = () => {
|
||||
el.remove();
|
||||
};
|
||||
};
|
||||
}}
|
||||
/>
|
||||
{content.length > 2 ? (
|
||||
<MemoryWithImages3
|
||||
className="h-24 w-24"
|
||||
|
|
@ -150,6 +278,45 @@ export function Space({ title, description, content, id }: Space) {
|
|||
images={content.map((c) => c.image).reverse() as string[]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SpaceMoreButton({ onDelete }: { onDelete?: () => void }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
data-more-button
|
||||
className="hover:bg-rgray-3 focus-visible:bg-rgray-3 focus-visible:ring-rgray-7 absolute right-2 top-2 rounded-md p-1 opacity-0 ring-transparent transition focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2"
|
||||
>
|
||||
<MoreHorizontal className="text-rgray-11 h-5 w-5" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem>
|
||||
<ArrowUpRight
|
||||
className="mr-2 h-4 w-4 scale-125"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
Open
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => {}}>
|
||||
<Edit3 className="mr-2 h-4 w-4" strokeWidth={1.5} />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={onDelete}
|
||||
className="focus:bg-red-100 focus:text-red-400 dark:focus:bg-red-100/10"
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" strokeWidth={1.5} />
|
||||
Move to Trash
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@
|
|||
import { StoredContent } from "@/server/db/schema";
|
||||
import { MemoryIcon } from "../../assets/Memories";
|
||||
import { Trash2, User2 } from "lucide-react";
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { MemoriesBar } from "./MemoriesBar";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { Bin } from "@/assets/Bin";
|
||||
|
||||
export type MenuItem = {
|
||||
icon: React.ReactNode | React.ReactNode[];
|
||||
|
|
@ -39,34 +40,46 @@ export default function Sidebar({
|
|||
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 ?? (() => <></>);
|
||||
|
||||
useEffect(() => {
|
||||
onSelectChange?.(selectedItem);
|
||||
}, [selectedItem]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative hidden h-screen max-h-screen w-max flex-col items-center text-sm font-light md:flex">
|
||||
<div className="bg-rgray-2 border-r-rgray-6 relative z-[10000] flex h-full w-full flex-col items-center justify-center border-r px-2 py-5 ">
|
||||
{menuItemsTop.map((item, index) => (
|
||||
<MenuItem
|
||||
key={index}
|
||||
item={item}
|
||||
selectedItem={selectedItem}
|
||||
setSelectedItem={setSelectedItem}
|
||||
/>
|
||||
))}
|
||||
<div className="bg-rgray-2 border-r-rgray-6 relative z-[50] flex h-full w-full flex-col items-center justify-center border-r px-2 py-5 ">
|
||||
<MenuItem
|
||||
item={{
|
||||
label: "Memories",
|
||||
icon: <MemoryIcon className="h-10 w-10" />,
|
||||
content: MemoriesBar,
|
||||
}}
|
||||
selectedItem={selectedItem}
|
||||
setSelectedItem={setSelectedItem}
|
||||
/>
|
||||
|
||||
<div className="mt-auto" />
|
||||
{menuItemsBottom.map((item, index) => (
|
||||
<MenuItem
|
||||
key={index}
|
||||
item={item}
|
||||
selectedItem={selectedItem}
|
||||
setSelectedItem={setSelectedItem}
|
||||
/>
|
||||
))}
|
||||
|
||||
<MenuItem
|
||||
item={{
|
||||
label: "Trash",
|
||||
icon: <Bin id="trash" className="z-[300] h-7 w-7" />,
|
||||
}}
|
||||
selectedItem={selectedItem}
|
||||
id='trash-button'
|
||||
setSelectedItem={setSelectedItem}
|
||||
/>
|
||||
<MenuItem
|
||||
item={{
|
||||
label: "Profile",
|
||||
icon: <User2 strokeWidth={1.3} className="h-7 w-7" />,
|
||||
}}
|
||||
selectedItem={selectedItem}
|
||||
setSelectedItem={setSelectedItem}
|
||||
/>
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{selectedItem && (
|
||||
|
|
@ -84,7 +97,8 @@ const MenuItem = ({
|
|||
item: { icon, label },
|
||||
selectedItem,
|
||||
setSelectedItem,
|
||||
}: {
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLButtonElement> & {
|
||||
item: MenuItem;
|
||||
selectedItem: string | null;
|
||||
setSelectedItem: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
|
|
@ -93,6 +107,7 @@ const MenuItem = ({
|
|||
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 relative z-[100] 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"
|
||||
{...props}
|
||||
>
|
||||
{icon}
|
||||
<span className="">{label}</span>
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ const DropdownMenuContent = React.forwardRef<
|
|||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in bg-rgray-3 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 border-rgray-6 text-rgray-11 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md",
|
||||
"data-[state=open]:animate-in bg-rgray-3 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 border-rgray-6 text-rgray-11 z-50 min-w-[9rem] overflow-hidden rounded-md border p-1 shadow-md",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -11,21 +11,30 @@ const PopoverTrigger = PopoverPrimitive.Trigger;
|
|||
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"border-rgray-6 bg-rgray-3 text-rgray-11 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-md outline-none",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
));
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & {
|
||||
animate?: boolean;
|
||||
}
|
||||
>(
|
||||
(
|
||||
{ className, align = "center", animate = true, sideOffset = 4, ...props },
|
||||
ref,
|
||||
) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"border-rgray-6 bg-rgray-3 text-rgray-11 z-50 w-72 rounded-md border p-4 shadow-md outline-none",
|
||||
animate &&
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
),
|
||||
);
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent };
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue