mirror of
https://github.com/ClipFusion-org/clipfusion.git
synced 2025-08-03 14:45:09 +00:00
sidebar collapse button now automatically aligns with the search bar
This commit is contained in:
parent
2b7713085d
commit
ddac9e31bd
@ -33,6 +33,7 @@ import SidebarTriggerAdjustable from "@/components/sidebar-trigger-adjustable";
|
||||
import ScrollFadingTitle from "@/components/scroll-fading-title";
|
||||
import AscendingCard from "@/components/ascending-card";
|
||||
import Link from "next/link";
|
||||
import StickyTopContainer from "@/components/sticky-top-container";
|
||||
|
||||
type SortingType = "byCreationDate"
|
||||
| "byEditDate"
|
||||
@ -503,7 +504,7 @@ export default function Home(): ReactNode {
|
||||
{projects && <Label className="text-muted-foreground text-sm">(Found {projects.length} projects)</Label>}
|
||||
</ScrollFadingTitle>
|
||||
</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">
|
||||
<StickyTopContainer>
|
||||
<SidebarTriggerAdjustable>
|
||||
<div className={cn("flex flex-row gap-2 items-center w-full", !isMobile && "justify-between")}>
|
||||
<Dialog>
|
||||
@ -573,7 +574,7 @@ export default function Home(): ReactNode {
|
||||
<Grid2X2CheckIcon /> {isMobile ? "Select" : "Select Projects"}
|
||||
</Toggle>
|
||||
</div>
|
||||
</div>
|
||||
</StickyTopContainer>
|
||||
<div className={cn("grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 mt-2")}>
|
||||
{filteredProjects && filteredProjects.map((project) => <ProjectContainer key={project.uuid} project={project} />)}
|
||||
</div>
|
||||
|
@ -25,14 +25,14 @@ export default function Settings(): ReactNode {
|
||||
<h2 className="font-bold break-keep text-xl sm:text-2xl md:text-3xl lg:text-4xl leading-none">Settings</h2>
|
||||
</ScrollFadingTitle>
|
||||
</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">
|
||||
<div className="flex flex-col sticky top-safe bg-background gap-2 pb-2 pt-4 p-5 w-[100% + 5 * var(--spacing)] z-10 -mx-5">
|
||||
<SidebarTriggerAdjustable adjustWidth={!isMobile ? 0 : 12} className="flex items-center justify-center">
|
||||
<WideContainer>
|
||||
<Search />
|
||||
</WideContainer>
|
||||
</SidebarTriggerAdjustable>
|
||||
</div>
|
||||
<div className="flex flex-col justify-center items-center">
|
||||
<div className="flex flex-col justify-start items-center h-screen">
|
||||
<WideContainer>
|
||||
<div className="flex flex-col gap-2 mt-2 justify-center">
|
||||
<Link href="/settings/storage">
|
||||
|
@ -4,12 +4,15 @@ import AscendingCard from "@/components/ascending-card";
|
||||
import ScrollFadingTitle from "@/components/scroll-fading-title";
|
||||
import Search from "@/components/search";
|
||||
import SidebarTriggerAdjustable from "@/components/sidebar-trigger-adjustable";
|
||||
import StaticBackButton from "@/components/static-back-button";
|
||||
import StaticSidebarTrigger from "@/components/static-sidebar-trigger";
|
||||
import StickyTopContainer from "@/components/sticky-top-container";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useSidebar } from "@/components/ui/sidebar";
|
||||
import WideContainer from "@/components/wide-container";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ArrowLeftIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
@ -28,34 +31,31 @@ function PersistentStorageControl({
|
||||
|
||||
export default function Storage() {
|
||||
const [status, setStatus] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
const isMobile = useIsMobile();
|
||||
const iPadAirBreakpoint = useIsMobile(820);
|
||||
const backButtonAdjust = (!isMobile && iPadAirBreakpoint) ? "pl-16" : "";
|
||||
|
||||
useEffect(() => {
|
||||
setStatus(localStorage.getItem("persistence-status"));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="p-5 w-full">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<StaticSidebarTrigger>
|
||||
<Button className="fixed size-7 ml-10 z-40 transition-colors" variant="ghost" size="icon" onClick={() => router.back()}>
|
||||
<ArrowLeftIcon />
|
||||
</Button>
|
||||
</StaticSidebarTrigger>
|
||||
<ScrollFadingTitle className="flex flex-row items-center">
|
||||
<div className="ml-10" />
|
||||
<div className="p-5 w-full h-full">
|
||||
<div className="flex flex-row items-center justify-start gap-2">
|
||||
<StaticSidebarTrigger/>
|
||||
<StaticBackButton/>
|
||||
<ScrollFadingTitle>
|
||||
<h2 className="font-bold break-keep text-xl sm:text-2xl md:text-3xl lg:text-4xl leading-none">Storage</h2>
|
||||
</ScrollFadingTitle>
|
||||
</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 adjustWidth={!isMobile ? 0 : 22} className="flex items-center justify-center">
|
||||
<StickyTopContainer>
|
||||
<SidebarTriggerAdjustable adjustWidth={isMobile ? 22 : 0} className={`flex items-center justify-center ${backButtonAdjust}`}>
|
||||
<WideContainer>
|
||||
<Search />
|
||||
</WideContainer>
|
||||
</SidebarTriggerAdjustable>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-start gap-1 md:lg:gap-2 mt-2">
|
||||
</StickyTopContainer>
|
||||
<div className={cn("flex flex-col items-center justify-start gap-1 md:lg:gap-2 mt-2 h-screen", backButtonAdjust)}>
|
||||
<WideContainer>
|
||||
<AscendingCard className="flex flex-row justify-between items-center p-4">
|
||||
<div className="flex flex-row gap-2 ">
|
||||
|
@ -10,7 +10,6 @@ const lerp = (a: number, b: number, t: number) => (
|
||||
a * t + b * (1 - t)
|
||||
);
|
||||
|
||||
|
||||
export const SidebarTriggerAdjustable = (props: ComponentProps<"div"> & {
|
||||
adjustWidth?: number | `${number}`
|
||||
}) => {
|
||||
@ -28,7 +27,6 @@ export const SidebarTriggerAdjustable = (props: ComponentProps<"div"> & {
|
||||
Math.max(0, Math.min(1, scrollPos / (window.innerHeight / 20))));
|
||||
|
||||
ref.current.style.transform = `translateX(calc(var(--spacing) * ${adjustWidth * slideAmount}))`;
|
||||
ref.current.style.marginTop = `calc(var(--spacing) * ${(isMobile ? 1 : 3) * slideAmount})`;
|
||||
ref.current.style.width = `calc(100% - var(--spacing) * ${lerp(0, adjustWidth, 1 - slideAmount)})`;
|
||||
};
|
||||
|
||||
|
66
src/components/static-back-button/StaticBackButton.tsx
Normal file
66
src/components/static-back-button/StaticBackButton.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
"use client";
|
||||
import { ReactNode, useEffect, useRef } from "react";
|
||||
import { SidebarTrigger } from "../ui/sidebar";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import { Button } from "../ui/button";
|
||||
import { ArrowLeftIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
const easeSlide = (x: number) => (
|
||||
1 - Math.pow(1 - x, 3)
|
||||
);
|
||||
|
||||
const lerp = (a: number, b: number, t: number) => (
|
||||
a * t + b * (1 - t)
|
||||
);
|
||||
|
||||
export const StaticBackButton = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const router = useRouter();
|
||||
const adjustHeight = isMobile ? -1 : 1;
|
||||
const ref = useRef<HTMLButtonElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let lastKnownScrollPosition = 0;
|
||||
let ticking = false;
|
||||
|
||||
const updateAdjustable = (scrollPos: number) => {
|
||||
if (!ref.current) return;
|
||||
const slideAmount = easeSlide(
|
||||
Math.max(0, Math.min(1, scrollPos / (window.innerHeight / 20))));
|
||||
|
||||
ref.current.style.transform = `translateY(calc(var(--spacing) * ${adjustHeight * -slideAmount}))`;
|
||||
};
|
||||
|
||||
const handleScroll = () => {
|
||||
lastKnownScrollPosition = window.scrollY;
|
||||
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(() => {
|
||||
updateAdjustable(lastKnownScrollPosition);
|
||||
ticking = false;
|
||||
});
|
||||
|
||||
ticking = true;
|
||||
}
|
||||
}
|
||||
|
||||
handleScroll();
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}, [isMobile, ref]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-10 h-full" />
|
||||
<div className="absolute top-0 left-0 pl-6 pt-4 md:p-6 overscroll-auto">
|
||||
<Button ref={ref} className="fixed size-7 ml-10 z-40 transition-colors" variant="ghost" size="icon" onClick={() => router.back()} tabIndex={1}>
|
||||
<ArrowLeftIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
3
src/components/static-back-button/index.ts
Normal file
3
src/components/static-back-button/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { StaticBackButton } from "./StaticBackButton";
|
||||
|
||||
export default StaticBackButton;
|
@ -1,19 +1,60 @@
|
||||
"use client";
|
||||
import { ReactNode } from "react";
|
||||
import { ReactNode, useEffect, useRef } from "react";
|
||||
import { SidebarTrigger } from "../ui/sidebar";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
|
||||
const easeSlide = (x: number) => (
|
||||
1 - Math.pow(1 - x, 3)
|
||||
);
|
||||
|
||||
const lerp = (a: number, b: number, t: number) => (
|
||||
a * t + b * (1 - t)
|
||||
);
|
||||
|
||||
export const StaticSidebarTrigger = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const adjustHeight = isMobile ? -1 : 1;
|
||||
const ref = useRef<HTMLButtonElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let lastKnownScrollPosition = 0;
|
||||
let ticking = false;
|
||||
|
||||
const updateAdjustable = (scrollPos: number) => {
|
||||
if (!ref.current) return;
|
||||
const slideAmount = easeSlide(
|
||||
Math.max(0, Math.min(1, scrollPos / (window.innerHeight / 20))));
|
||||
|
||||
ref.current.style.transform = `translateY(calc(var(--spacing) * ${adjustHeight * -slideAmount}))`;
|
||||
};
|
||||
|
||||
const handleScroll = () => {
|
||||
lastKnownScrollPosition = window.scrollY;
|
||||
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(() => {
|
||||
updateAdjustable(lastKnownScrollPosition);
|
||||
ticking = false;
|
||||
});
|
||||
|
||||
ticking = true;
|
||||
}
|
||||
}
|
||||
|
||||
handleScroll();
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}, [isMobile, ref]);
|
||||
|
||||
export const StaticSidebarTrigger = ({
|
||||
children
|
||||
}: {
|
||||
children?: ReactNode
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="ml-10"/>
|
||||
<div className="w-10 h-full" />
|
||||
<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`} size="lg" tabIndex={0}/>
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
<SidebarTrigger ref={ref} className={`fixed mr-2 z-40 transition-colors`} size="lg" tabIndex={0} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
@ -0,0 +1,6 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ComponentProps } from "react";
|
||||
|
||||
export const StickyTopContainer = (props: ComponentProps<"div">) => (
|
||||
<div {...props} className={cn("flex flex-col sticky top-safe bg-background gap-2 pb-2 pt-4 px-5 w-[100% + 5 * var(--spacing)] z-10 -mx-5", props.className)}/>
|
||||
)
|
3
src/components/sticky-top-container/index.ts
Normal file
3
src/components/sticky-top-container/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { StickyTopContainer } from "./StickyTopContainer";
|
||||
|
||||
export default StickyTopContainer;
|
Loading…
Reference in New Issue
Block a user