mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-07 08:26:15 +00:00
animation with framer motion
This commit is contained in:
parent
c307a0537c
commit
bed6d65ffa
6 changed files with 271 additions and 97 deletions
|
|
@ -5,6 +5,7 @@ import { Textarea2 } from "./ui/textarea";
|
|||
import { ArrowRight } from "lucide-react";
|
||||
import { MemoryDrawer } from "./MemoryDrawer";
|
||||
import useViewport from "@/hooks/useViewport";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
|
||||
const [value, setValue] = useState("");
|
||||
|
|
@ -24,10 +25,13 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<main
|
||||
<motion.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"
|
||||
className="flex h-screen max-h-screen w-full flex-col items-end justify-center gap-5 px-5 pb-[20vh] pt-5 transition-[padding] delay-200 duration-200 md:items-center md:px-72 [&[data-sidebar-open='true']]:pl-[calc(2.5rem+30vw)] [&[data-sidebar-open='true']]:pr-10 [&[data-sidebar-open='true']]:delay-0 "
|
||||
>
|
||||
<h1 className="text-rgray-11 text-center text-3xl">
|
||||
Ask your Second brain
|
||||
</h1>
|
||||
<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"
|
||||
|
|
@ -51,6 +55,6 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
|
|||
</div>
|
||||
</Textarea2>
|
||||
{width <= 768 && <MemoryDrawer />}
|
||||
</main>
|
||||
</motion.main>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import {
|
|||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { SpaceIcon } from "@/assets/Memories";
|
||||
import { AnimatePresence, LayoutGroup, motion } from "framer-motion";
|
||||
|
||||
const spaces = [
|
||||
{
|
||||
|
|
@ -33,6 +34,118 @@ const spaces = [
|
|||
value: "3",
|
||||
label: "Cool Libraries",
|
||||
},
|
||||
{
|
||||
value: "4",
|
||||
label: "Cool People",
|
||||
},
|
||||
{
|
||||
value: "5",
|
||||
label: "Cool Projects",
|
||||
},
|
||||
{
|
||||
value: "6",
|
||||
label: "Cool Tools",
|
||||
},
|
||||
{
|
||||
value: "7",
|
||||
label: "Cool Websites",
|
||||
},
|
||||
{
|
||||
value: "8",
|
||||
label: "Cool Books",
|
||||
},
|
||||
{
|
||||
value: "9",
|
||||
label: "Cool Videos",
|
||||
},
|
||||
{
|
||||
value: "10",
|
||||
label: "Cool Podcasts",
|
||||
},
|
||||
{
|
||||
value: "11",
|
||||
label: "Cool Articles",
|
||||
},
|
||||
{
|
||||
value: "12",
|
||||
label: "Cool Blogs",
|
||||
},
|
||||
{
|
||||
value: "13",
|
||||
label: "Cool News",
|
||||
},
|
||||
{
|
||||
value: "14",
|
||||
label: "Cool Forums",
|
||||
},
|
||||
{
|
||||
value: "15",
|
||||
label: "Cool Communities",
|
||||
},
|
||||
{
|
||||
value: "16",
|
||||
label: "Cool Events",
|
||||
},
|
||||
{
|
||||
value: "17",
|
||||
label: "Cool Jobs",
|
||||
},
|
||||
{
|
||||
value: "18",
|
||||
label: "Cool Companies",
|
||||
},
|
||||
{
|
||||
value: "19",
|
||||
label: "Cool Startups",
|
||||
},
|
||||
{
|
||||
value: "20",
|
||||
label: "Cool Investors",
|
||||
},
|
||||
{
|
||||
value: "21",
|
||||
label: "Cool Funds",
|
||||
},
|
||||
{
|
||||
value: "22",
|
||||
label: "Cool Incubators",
|
||||
},
|
||||
{
|
||||
value: "23",
|
||||
label: "Cool Accelerators",
|
||||
},
|
||||
{
|
||||
value: "24",
|
||||
label: "Cool Hackathons",
|
||||
},
|
||||
{
|
||||
value: "25",
|
||||
label: "Cool Conferences",
|
||||
},
|
||||
{
|
||||
value: "26",
|
||||
label: "Cool Workshops",
|
||||
},
|
||||
{
|
||||
value: "27",
|
||||
label: "Cool Seminars",
|
||||
},
|
||||
{
|
||||
value: "28",
|
||||
label: "Cool Webinars",
|
||||
},
|
||||
{
|
||||
value: "29",
|
||||
label: "Cool Courses",
|
||||
},
|
||||
{
|
||||
value: "30",
|
||||
label: "Cool Bootcamps",
|
||||
},
|
||||
{
|
||||
value: "31",
|
||||
label: "Cool Certifications",
|
||||
},
|
||||
];
|
||||
|
||||
export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
|
||||
|
|
@ -41,69 +154,96 @@ export function FilterCombobox({ className, ...props }: Props) {
|
|||
const [open, setOpen] = React.useState(false);
|
||||
const [values, setValues] = React.useState<string[]>([]);
|
||||
|
||||
const sortedSpaces = spaces.sort(({ value: a }, { value: b }) =>
|
||||
values.includes(a) && !values.includes(b)
|
||||
? -1
|
||||
: values.includes(b) && !values.includes(a)
|
||||
? 1
|
||||
: 0,
|
||||
);
|
||||
|
||||
console.log(sortedSpaces, values);
|
||||
|
||||
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>
|
||||
<AnimatePresence mode="popLayout">
|
||||
<LayoutGroup>
|
||||
<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 asChild>
|
||||
<motion.div layoutScroll>
|
||||
<CommandEmpty>Nothing found</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{sortedSpaces.map((space) => (
|
||||
<CommandItem
|
||||
key={space.value}
|
||||
value={space.value}
|
||||
onSelect={(val) => {
|
||||
setValues((prev) =>
|
||||
prev.includes(val)
|
||||
? prev.filter((v) => v !== val)
|
||||
: [...prev, val],
|
||||
);
|
||||
}}
|
||||
asChild
|
||||
>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1, transition: { delay: 0.05 } }}
|
||||
transition={{ duration: 0.15 }}
|
||||
layout
|
||||
layoutId={`space-combobox-${space.value}`}
|
||||
className="text-rgray-11"
|
||||
>
|
||||
<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",
|
||||
)}
|
||||
/>
|
||||
</motion.div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</motion.div>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</LayoutGroup>
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ export function MemoriesBar() {
|
|||
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">
|
||||
<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 pb-4 text-center font-normal ring-transparent transition focus-visible:outline-none focus-visible:ring-2">
|
||||
{content.length > 2 ? (
|
||||
<MemoryWithImages3
|
||||
className="h-24 w-24"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { MemoryIcon } from "../../assets/Memories";
|
|||
import { Trash2, User2 } from "lucide-react";
|
||||
import React, { useState } from "react";
|
||||
import { MemoriesBar } from "./MemoriesBar";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
export type MenuItem = {
|
||||
icon: React.ReactNode | React.ReactNode[];
|
||||
|
|
@ -47,30 +48,34 @@ export default function Sidebar({
|
|||
|
||||
return (
|
||||
<>
|
||||
<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 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="mt-auto" />
|
||||
{menuItemsBottom.map((item, index) => (
|
||||
<MenuItem
|
||||
key={index}
|
||||
item={item}
|
||||
selectedItem={selectedItem}
|
||||
setSelectedItem={setSelectedItem}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{selectedItem && (
|
||||
<SubSidebar>
|
||||
<Subbar />
|
||||
</SubSidebar>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
{selectedItem && (
|
||||
<SubSidebar>
|
||||
<Subbar />
|
||||
</SubSidebar>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -87,7 +92,7 @@ const MenuItem = ({
|
|||
<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"
|
||||
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"
|
||||
>
|
||||
{icon}
|
||||
<span className="">{label}</span>
|
||||
|
|
@ -96,8 +101,30 @@ const MenuItem = ({
|
|||
|
||||
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>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: "-100%" }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
x: "-100%",
|
||||
transition: { delay: 0.2 },
|
||||
}}
|
||||
transition={{
|
||||
duration: 0.2,
|
||||
}}
|
||||
className="bg-rgray-3 border-r-rgray-6 absolute left-[100%] top-0 z-[10] hidden h-screen w-[30vw] items-start justify-center overflow-x-hidden border-r font-light md:flex"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0, transition: { delay: 0 } }}
|
||||
transition={{
|
||||
delay: 0.2,
|
||||
}}
|
||||
className="z-[10] flex h-full w-full min-w-full flex-col items-center opacity-0"
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const Command = React.forwardRef<
|
|||
<CommandPrimitive
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"bg-rgray-3 text-rgray-11 flex h-full w-full flex-col overflow-hidden rounded-md",
|
||||
"bg-rgray-3 text-rgray-11 flex h-full w-full flex-col overflow-hidden rounded-md focus-visible:outline-none [&>[cmdk-list-sizer]]:max-h-[250px] [&>[cmdk-list-sizer]]:overflow-y-scroll",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -120,7 +120,7 @@ const CommandItem = React.forwardRef<
|
|||
<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",
|
||||
"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 opacity-70 outline-none data-[disabled='true']:pointer-events-none data-[disabled='true']:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import { useState, useEffect } from "react";
|
||||
|
||||
function getViewport() {
|
||||
const { innerWidth: width, innerHeight: height } = window;
|
||||
const { innerWidth: width, innerHeight: height } = window ?? {
|
||||
innerWidth: 0,
|
||||
innerHeight: 0,
|
||||
};
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue