mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 16:56:22 +02:00
Initial commit
This commit is contained in:
commit
55332d1ddb
168 changed files with 18456 additions and 0 deletions
|
|
@ -0,0 +1,82 @@
|
|||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
export default function LinkComponent(...props: any) {
|
||||
const activeLink = props[0].activeLink;
|
||||
const collapsed = props[0].collapsed;
|
||||
const animationDuration = props[0].animationDuration;
|
||||
|
||||
return (
|
||||
<Link href={props[0].href}>
|
||||
{collapsed ? (
|
||||
<TooltipProvider delayDuration={150}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className={`flex flex-row gap-6 rounded-md py-3 font-normal ${
|
||||
collapsed ? "justify-center" : "items-center"
|
||||
} ${activeLink ? "bg-border text-foreground" : ""}`}
|
||||
>
|
||||
<div className={collapsed ? "p-0" : "pl-4"}>
|
||||
{props[0].icon}
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
layout
|
||||
animate={{
|
||||
x: collapsed ? -20 : 0,
|
||||
y: collapsed ? 0 : 0,
|
||||
opacity: collapsed ? 0 : 1,
|
||||
width: collapsed ? 0 : "auto",
|
||||
display: collapsed ? "none" : "block",
|
||||
}}
|
||||
transition={{ duration: animationDuration }}
|
||||
className="text-sm"
|
||||
>
|
||||
{props[0].label}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>{props[0].label}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : (
|
||||
<div
|
||||
className={`flex flex-row gap-6 rounded-md py-3 font-normal ${
|
||||
collapsed ? "justify-center" : "items-center"
|
||||
} ${activeLink ? "bg-border text-foreground" : ""}`}
|
||||
>
|
||||
<div className={collapsed ? "p-0" : "pl-4"}>{props[0].icon}</div>
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
layout
|
||||
animate={{
|
||||
x: collapsed ? -20 : 0,
|
||||
y: collapsed ? 0 : 0,
|
||||
opacity: collapsed ? 0 : 1,
|
||||
width: collapsed ? 0 : "auto",
|
||||
display: collapsed ? "none" : "block",
|
||||
}}
|
||||
transition={{ duration: animationDuration }}
|
||||
className="text-sm"
|
||||
>
|
||||
{props[0].label}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
import { FC } from "react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import LinkComponent from "./NavLink";
|
||||
import { navConfig } from "@/lib/config/";
|
||||
|
||||
type NavLinksProps = {
|
||||
collapsed: boolean;
|
||||
animationDuration: number;
|
||||
};
|
||||
|
||||
const NavLinks: FC<NavLinksProps> = ({ collapsed, animationDuration }) => {
|
||||
const pathName = usePathname();
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col justify-between">
|
||||
<div id="topNavLinks">
|
||||
{navConfig.navLinks.map((link, index) => {
|
||||
if (link.navLocation === "top") {
|
||||
const activeLink = pathName === link.href;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="px-5 text-muted-foreground transition-all duration-300 hover:text-foreground"
|
||||
>
|
||||
<LinkComponent
|
||||
activeLink={activeLink}
|
||||
href={link.href}
|
||||
label={link.label}
|
||||
icon={link.icon}
|
||||
animationDuration={animationDuration}
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div id="btmNavLinks">
|
||||
{navConfig.navLinks.map((link, index) => {
|
||||
if (link.navLocation === "bottom") {
|
||||
const activeLink = pathName === link.href;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="px-5 text-muted-foreground transition-all duration-300 hover:text-foreground"
|
||||
>
|
||||
<LinkComponent
|
||||
activeLink={activeLink}
|
||||
href={link.href}
|
||||
label={link.label}
|
||||
icon={link.icon}
|
||||
animationDuration={animationDuration}
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavLinks;
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
"use client";
|
||||
import { useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { Icons } from "@/components/icons";
|
||||
import { motion } from "framer-motion";
|
||||
import NavLinks from "./NavLinks";
|
||||
|
||||
const Sidebar = () => {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const animationDuration = 0.4;
|
||||
const sideBarWidth = "250px";
|
||||
|
||||
// Load collapsed state from localStorage on component mount
|
||||
useEffect(() => {
|
||||
const collapsedState = localStorage.getItem("sidebarCollapsed");
|
||||
if (collapsedState !== null) {
|
||||
setCollapsed(collapsedState === "true" ? true : false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleClose = () => {
|
||||
localStorage.setItem("sidebarCollapsed", (!collapsed).toString());
|
||||
setCollapsed(!collapsed);
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
layout
|
||||
initial={{ width: collapsed ? "88px" : sideBarWidth }}
|
||||
animate={{
|
||||
minWidth: collapsed ? "88px" : sideBarWidth,
|
||||
width: collapsed ? "88px" : sideBarWidth,
|
||||
}}
|
||||
transition={{ duration: animationDuration }}
|
||||
id="sidebar"
|
||||
className={`flex h-full flex-col justify-between gap-8 border-r border-border`}
|
||||
>
|
||||
<div
|
||||
className={`flex h-20 items-center justify-between border-b border-border ${
|
||||
collapsed ? "px-8" : "px-4"
|
||||
} `}
|
||||
>
|
||||
<Link href="/">
|
||||
<motion.div
|
||||
layout
|
||||
animate={{
|
||||
x: collapsed ? -100 : 0,
|
||||
y: collapsed ? 0 : 0,
|
||||
opacity: collapsed ? 0 : 1,
|
||||
width: collapsed ? 0 : "auto",
|
||||
display: collapsed ? "none" : "block",
|
||||
}}
|
||||
transition={{ duration: animationDuration }}
|
||||
>
|
||||
<div className="flex flex-row items-center gap-1 font-semibold text-sm text-foreground">
|
||||
<span>
|
||||
<Icons.logo className="h-5" />
|
||||
</span>
|
||||
Next-Fast-Turbo
|
||||
</div>
|
||||
</motion.div>
|
||||
</Link>
|
||||
|
||||
{collapsed ? (
|
||||
<Icons.panelLeftOpen
|
||||
className="h-6 w-6 cursor-pointer text-muted-foreground transition-all hover:text-foreground hover:duration-300"
|
||||
onClick={handleClose}
|
||||
/>
|
||||
) : (
|
||||
<Icons.panelLeftClose
|
||||
className="h-6 w-6 cursor-pointer text-muted-foreground transition-all hover:text-foreground hover:duration-300"
|
||||
onClick={handleClose}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 border-border pb-8">
|
||||
<NavLinks collapsed={collapsed} animationDuration={animationDuration} />
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
"use client";
|
||||
import { useRef } from "react";
|
||||
import { useState } from "react";
|
||||
import { Squash as Hamburger } from "hamburger-react";
|
||||
import { useClickAway } from "react-use";
|
||||
import { navConfig } from "@/lib/config/";
|
||||
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
const SidebarMobile = () => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const ref = useRef(null);
|
||||
|
||||
useClickAway(ref, () => setOpen(false));
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<Hamburger toggled={isOpen} size={20} toggle={setOpen} />
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="fixed left-0 w-[85%] py-12 h-screen px-8 bg-background"
|
||||
>
|
||||
<ul className="grid min-h-72 content-evenly">
|
||||
{navConfig.navLinks.map((link, index) => {
|
||||
return (
|
||||
<motion.li
|
||||
initial={{ scale: 0, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 260,
|
||||
damping: 20,
|
||||
delay: 0.1 + index / 10,
|
||||
}}
|
||||
key={link.pageTitle}
|
||||
className="w-full"
|
||||
>
|
||||
<Link href={link.href}>{link.pageTitle}</Link>
|
||||
<Separator className="my-2" />
|
||||
</motion.li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidebarMobile;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export { default as Sidebar } from "./Sidebar";
|
||||
export { default as SidebarMobile } from "./SidebarMobile";
|
||||
Loading…
Add table
Add a link
Reference in a new issue