mirror of
https://github.com/ClipFusion-org/clipfusion.git
synced 2025-08-03 16:55:08 +00:00
sidebar switcher now always stays on screen
This commit is contained in:
parent
f881b2d7bf
commit
6ad064c9c1
@ -48,7 +48,7 @@ export default async function RootLayout({
|
||||
<ThemeProvider>
|
||||
<SidebarProvider>
|
||||
<Dashboard/>
|
||||
<main className="w-full">
|
||||
<main className="w-full relative">
|
||||
<PersistenceProvider>
|
||||
{children}
|
||||
</PersistenceProvider>
|
||||
|
@ -30,6 +30,8 @@ import { Tooltip, TooltipContent } from "@/components/ui/tooltip";
|
||||
import { TooltipTrigger } from "@radix-ui/react-tooltip";
|
||||
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import StaticSidebarTrigger from "@/components/static-sidebar-trigger";
|
||||
import SidebarTriggerAdjustable from "@/components/sidebar-trigger-adjustable";
|
||||
|
||||
type SortingType = "byCreationDate"
|
||||
| "byEditDate"
|
||||
@ -331,7 +333,7 @@ const ProjectDropdown = ({
|
||||
);
|
||||
};
|
||||
|
||||
const ProjectDescription = ({ project }: { project: Project}): ReactNode => {
|
||||
const ProjectDescription = ({ project }: { project: Project }): ReactNode => {
|
||||
const isMobile = useIsMobile();
|
||||
if (!project.description) return <></>;
|
||||
|
||||
@ -348,7 +350,7 @@ const ProjectDescription = ({ project }: { project: Project}): ReactNode => {
|
||||
<SheetTitle>{project.title} Description</SheetTitle>
|
||||
<SheetDescription>Additional Information About the Project</SheetDescription>
|
||||
</SheetHeader>
|
||||
<Separator/>
|
||||
<Separator />
|
||||
<div className="p-2">
|
||||
{project.description}
|
||||
</div>
|
||||
@ -417,7 +419,7 @@ const ProjectContainer = ({
|
||||
return (
|
||||
<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">
|
||||
<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 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>
|
||||
@ -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>}
|
||||
</div>
|
||||
<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} />
|
||||
</div>
|
||||
</div>
|
||||
@ -488,44 +490,46 @@ export default function Home(): ReactNode {
|
||||
<SelectContext.Provider value={context}>
|
||||
<div className="p-5">
|
||||
<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>
|
||||
{projects && <Label className="text-muted-foreground text-sm">(Found {projects.length} projects)</Label>}
|
||||
</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={cn("flex flex-row gap-2 items-center w-full", !isMobile && "justify-between")}>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button>
|
||||
<PlusIcon /> {!isMobile && "New Project"}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Create New Project
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Fill in the information about your project. You can change it at any time later.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Form {...newProjectForm}>
|
||||
<form onSubmit={newProjectForm.handleSubmit(newProjectSubmit)} className="grid gap-3">
|
||||
<ProjectInfoForm form={newProjectForm} />
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">Cancel</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button type="submit">Create</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Search placeholder="Search Projects" value={search} onChange={(e) => setSearch(e.target.value)} className={isMobile ? "w-full" : "w-60"} />
|
||||
</div>
|
||||
<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">
|
||||
<SidebarTriggerAdjustable>
|
||||
<div className={cn("flex flex-row gap-2 items-center w-full", !isMobile && "justify-between")}>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button>
|
||||
<PlusIcon /> {!isMobile && "New Project"}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Create New Project
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Fill in the information about your project. You can change it at any time later.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Form {...newProjectForm}>
|
||||
<form onSubmit={newProjectForm.handleSubmit(newProjectSubmit)} className="grid gap-3">
|
||||
<ProjectInfoForm form={newProjectForm} />
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">Cancel</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button type="submit">Create</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<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 items-center gap-1">
|
||||
<DropdownMenu>
|
||||
|
@ -6,6 +6,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
|
||||
import { InfoIcon } from "lucide-react";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
import { usePersistenceContext } from "../persistence-provider";
|
||||
import StaticSidebarTrigger from "@/components/static-sidebar-trigger";
|
||||
|
||||
function PersistentStorageControl({
|
||||
status
|
||||
@ -29,7 +30,7 @@ export default function Settings(): ReactNode {
|
||||
return (
|
||||
<div className="p-5 w-full">
|
||||
<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>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 md:lg:gap-2 mt-2 md:mt-4 lg:mt-5">
|
||||
|
@ -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>;
|
||||
}
|
3
src/components/sidebar-trigger-adjustable/index.ts
Normal file
3
src/components/sidebar-trigger-adjustable/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { SidebarTriggerAdjustable } from "./SidebarTriggerAdjustable";
|
||||
|
||||
export default SidebarTriggerAdjustable;
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
3
src/components/static-sidebar-trigger/index.ts
Normal file
3
src/components/static-sidebar-trigger/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { StaticSidebarTrigger } from "./StaticSidebarTrigger";
|
||||
|
||||
export default StaticSidebarTrigger;
|
Loading…
Reference in New Issue
Block a user