sidebar switcher now always stays on screen

This commit is contained in:
corgifist 2025-07-27 18:39:15 +03:00
parent f881b2d7bf
commit 6ad064c9c1
7 changed files with 104 additions and 41 deletions

View File

@ -48,7 +48,7 @@ export default async function RootLayout({
<ThemeProvider> <ThemeProvider>
<SidebarProvider> <SidebarProvider>
<Dashboard/> <Dashboard/>
<main className="w-full"> <main className="w-full relative">
<PersistenceProvider> <PersistenceProvider>
{children} {children}
</PersistenceProvider> </PersistenceProvider>

View File

@ -30,6 +30,8 @@ import { Tooltip, TooltipContent } from "@/components/ui/tooltip";
import { TooltipTrigger } from "@radix-ui/react-tooltip"; import { TooltipTrigger } from "@radix-ui/react-tooltip";
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"; import { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import StaticSidebarTrigger from "@/components/static-sidebar-trigger";
import SidebarTriggerAdjustable from "@/components/sidebar-trigger-adjustable";
type SortingType = "byCreationDate" type SortingType = "byCreationDate"
| "byEditDate" | "byEditDate"
@ -331,7 +333,7 @@ const ProjectDropdown = ({
); );
}; };
const ProjectDescription = ({ project }: { project: Project}): ReactNode => { const ProjectDescription = ({ project }: { project: Project }): ReactNode => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
if (!project.description) return <></>; if (!project.description) return <></>;
@ -348,7 +350,7 @@ const ProjectDescription = ({ project }: { project: Project}): ReactNode => {
<SheetTitle>{project.title} Description</SheetTitle> <SheetTitle>{project.title} Description</SheetTitle>
<SheetDescription>Additional Information About the Project</SheetDescription> <SheetDescription>Additional Information About the Project</SheetDescription>
</SheetHeader> </SheetHeader>
<Separator/> <Separator />
<div className="p-2"> <div className="p-2">
{project.description} {project.description}
</div> </div>
@ -417,7 +419,7 @@ const ProjectContainer = ({
return ( return (
<AspectRatio data-selectable="true" ratio={16 / 9} onClick={handleClick}> <AspectRatio data-selectable="true" ratio={16 / 9} onClick={handleClick}>
<Card className="relative rounded-lg shadow-md w-full h-full overflow-hidden hover:scale-[101%] hover:drop-shadow-xl duration-100" data-selectable="true"> <Card className="relative rounded-lg shadow-md w-full h-full overflow-hidden hover:scale-[101%] hover:drop-shadow-xl duration-100" data-selectable="true">
<div className="absolute bottom-0 left-0 w-full h-full bg-gradient-to-t from-white dark:from-black to-transparent opacity-50" data-selectable="true"/> <div className="absolute bottom-0 left-0 w-full h-full bg-gradient-to-t from-white dark:from-black to-transparent opacity-50" data-selectable="true" />
<div className="absolute bottom-0 left-0 p-2 w-full flex flex-row justify-between items-center" data-selectable="true"> <div className="absolute bottom-0 left-0 p-2 w-full flex flex-row justify-between items-center" data-selectable="true">
<div data-selectable="true"> <div data-selectable="true">
<h3 className="text-sm sm:text-sm md:text-md lg:text-lg font-semibold line-clamp-1" data-selectable="true">{project.title}</h3> <h3 className="text-sm sm:text-sm md:text-md lg:text-lg font-semibold line-clamp-1" data-selectable="true">{project.title}</h3>
@ -425,7 +427,7 @@ const ProjectContainer = ({
{project.editDate && <p className="text-sm text-secondary-foreground" data-selectable="true">Last Edit Date: {date.toLocaleDateString()}, {date.toLocaleTimeString()}</p>} {project.editDate && <p className="text-sm text-secondary-foreground" data-selectable="true">Last Edit Date: {date.toLocaleDateString()}, {date.toLocaleTimeString()}</p>}
</div> </div>
<div className="flex flex-col lg:xl:flex-row items-center gap-1" data-selectable="true"> <div className="flex flex-col lg:xl:flex-row items-center gap-1" data-selectable="true">
{!selecting && <ProjectDescription project={project}/>} {!selecting && <ProjectDescription project={project} />}
<ProjectDropdown selected={selected} setSelected={setSelected} project={project} /> <ProjectDropdown selected={selected} setSelected={setSelected} project={project} />
</div> </div>
</div> </div>
@ -488,44 +490,46 @@ export default function Home(): ReactNode {
<SelectContext.Provider value={context}> <SelectContext.Provider value={context}>
<div className="p-5"> <div className="p-5">
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<SidebarTrigger size="lg" /> <StaticSidebarTrigger />
<h2 className="font-bold break-keep text-xl sm:text-2xl md:text-3xl lg:text-4xl leading-none">Project Library</h2> <h2 className="font-bold break-keep text-xl sm:text-2xl md:text-3xl lg:text-4xl leading-none">Project Library</h2>
{projects && <Label className="text-muted-foreground text-sm">(Found {projects.length} projects)</Label>} {projects && <Label className="text-muted-foreground text-sm">(Found {projects.length} projects)</Label>}
</div> </div>
<div className="flex flex-col sticky top-safe bg-background gap-2 mt-3 pb-2 pt-2 p-5 w-[100% + 5 * var(--spacing)] z-10 -mx-5"> <div className="flex flex-col sticky top-safe bg-background gap-2 mt-2 pb-2 pt-2 p-5 w-[100% + 5 * var(--spacing)] z-10 -mx-5">
<div className={cn("flex flex-row gap-2 items-center w-full", !isMobile && "justify-between")}> <SidebarTriggerAdjustable>
<Dialog> <div className={cn("flex flex-row gap-2 items-center w-full", !isMobile && "justify-between")}>
<DialogTrigger asChild> <Dialog>
<Button> <DialogTrigger asChild>
<PlusIcon /> {!isMobile && "New Project"} <Button>
</Button> <PlusIcon /> {!isMobile && "New Project"}
</DialogTrigger> </Button>
<DialogContent> </DialogTrigger>
<DialogHeader> <DialogContent>
<DialogTitle> <DialogHeader>
Create New Project <DialogTitle>
</DialogTitle> Create New Project
<DialogDescription> </DialogTitle>
Fill in the information about your project. You can change it at any time later. <DialogDescription>
</DialogDescription> Fill in the information about your project. You can change it at any time later.
</DialogHeader> </DialogDescription>
<Form {...newProjectForm}> </DialogHeader>
<form onSubmit={newProjectForm.handleSubmit(newProjectSubmit)} className="grid gap-3"> <Form {...newProjectForm}>
<ProjectInfoForm form={newProjectForm} /> <form onSubmit={newProjectForm.handleSubmit(newProjectSubmit)} className="grid gap-3">
<DialogFooter> <ProjectInfoForm form={newProjectForm} />
<DialogClose asChild> <DialogFooter>
<Button variant="outline">Cancel</Button> <DialogClose asChild>
</DialogClose> <Button variant="outline">Cancel</Button>
<DialogClose asChild> </DialogClose>
<Button type="submit">Create</Button> <DialogClose asChild>
</DialogClose> <Button type="submit">Create</Button>
</DialogFooter> </DialogClose>
</form> </DialogFooter>
</Form> </form>
</DialogContent> </Form>
</Dialog> </DialogContent>
<Search placeholder="Search Projects" value={search} onChange={(e) => setSearch(e.target.value)} className={isMobile ? "w-full" : "w-60"} /> </Dialog>
</div> <Search placeholder="Search Projects" value={search} onChange={(e) => setSearch(e.target.value)} className={isMobile ? "w-full" : "w-60"} />
</div>
</SidebarTriggerAdjustable>
<div className="flex flex-row justify-between items-center w-full"> <div className="flex flex-row justify-between items-center w-full">
<div className="flex flex-row items-center gap-1"> <div className="flex flex-row items-center gap-1">
<DropdownMenu> <DropdownMenu>

View File

@ -6,6 +6,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
import { InfoIcon } from "lucide-react"; import { InfoIcon } from "lucide-react";
import { ReactNode, useEffect, useState } from "react"; import { ReactNode, useEffect, useState } from "react";
import { usePersistenceContext } from "../persistence-provider"; import { usePersistenceContext } from "../persistence-provider";
import StaticSidebarTrigger from "@/components/static-sidebar-trigger";
function PersistentStorageControl({ function PersistentStorageControl({
status status
@ -29,7 +30,7 @@ export default function Settings(): ReactNode {
return ( return (
<div className="p-5 w-full"> <div className="p-5 w-full">
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<SidebarTrigger/> <StaticSidebarTrigger/>
<h2 className="font-bold break-keep text-xl sm:text-2xl md:text-3xl lg:text-4xl leading-none">Settings</h2> <h2 className="font-bold break-keep text-xl sm:text-2xl md:text-3xl lg:text-4xl leading-none">Settings</h2>
</div> </div>
<div className="flex flex-col gap-1 md:lg:gap-2 mt-2 md:mt-4 lg:mt-5"> <div className="flex flex-col gap-1 md:lg:gap-2 mt-2 md:mt-4 lg:mt-5">

View File

@ -0,0 +1,37 @@
"use client";
import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/lib/utils";
import { ComponentProps, useEffect, useState } from "react";
const easeSlide = (x: number) => (
1 - Math.pow(1 - x, 2)
);
export const SidebarTriggerAdjustable = (props: ComponentProps<"div">) => {
const [scrollY, setScrollY] = useState(0);
const [windowHeight, setWindowHeight] = useState(1);
const isMobile = useIsMobile();
useEffect(() => {
const handleScroll = () => setScrollY(window.scrollY);
const handleResize = () => setWindowHeight(window.innerHeight);
setWindowHeight(window.innerHeight);
window.addEventListener('scroll', handleScroll);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('scroll', handleScroll);
window.removeEventListener('resize', handleResize);
};
}, []);
let slideAmount = Math.max(0, Math.min(1, scrollY / (windowHeight / 20)));
slideAmount = easeSlide(slideAmount);
return <div {...props} style={{
paddingLeft: `calc(var(--spacing) * 12 * ${slideAmount})`,
paddingTop: `calc(var(--spacing) * ${isMobile ? 1 : 3} * ${slideAmount})`
}}></div>;
}

View File

@ -0,0 +1,3 @@
import { SidebarTriggerAdjustable } from "./SidebarTriggerAdjustable";
export default SidebarTriggerAdjustable;

View File

@ -0,0 +1,15 @@
"use client";
import { useEffect, useState } from "react";
import { SidebarTrigger, useSidebar } from "../ui/sidebar";
export const StaticSidebarTrigger = () => {
return (
<>
<div className="ml-10"/>
<div className="absolute top-0 left-0 pl-6 pt-4 md:p-6 overscroll-auto">
<SidebarTrigger className={`fixed mr-2 z-40 transition-colors will-change-[transform, scroll-position]`} size="lg" tabIndex={0}/>
</div>
</>
);
};

View File

@ -0,0 +1,3 @@
import { StaticSidebarTrigger } from "./StaticSidebarTrigger";
export default StaticSidebarTrigger;