From ddac9e31bddfb4f9f772e5f9568fe7245a868410 Mon Sep 17 00:00:00 2001 From: corgifist Date: Wed, 30 Jul 2025 01:57:32 +0300 Subject: [PATCH] sidebar collapse button now automatically aligns with the search bar --- src/app/(dashboard)/page.tsx | 5 +- src/app/(dashboard)/settings/page.tsx | 4 +- src/app/(dashboard)/settings/storage/page.tsx | 28 ++++---- .../SidebarTriggerAdjustable.tsx | 2 - .../static-back-button/StaticBackButton.tsx | 66 +++++++++++++++++++ src/components/static-back-button/index.ts | 3 + .../StaticSidebarTrigger.tsx | 63 ++++++++++++++---- .../StickyTopContainer.tsx | 6 ++ src/components/sticky-top-container/index.ts | 3 + 9 files changed, 149 insertions(+), 31 deletions(-) create mode 100644 src/components/static-back-button/StaticBackButton.tsx create mode 100644 src/components/static-back-button/index.ts create mode 100644 src/components/sticky-top-container/StickyTopContainer.tsx create mode 100644 src/components/sticky-top-container/index.ts diff --git a/src/app/(dashboard)/page.tsx b/src/app/(dashboard)/page.tsx index 59921c3..fd26cb7 100644 --- a/src/app/(dashboard)/page.tsx +++ b/src/app/(dashboard)/page.tsx @@ -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 && } -
+
@@ -573,7 +574,7 @@ export default function Home(): ReactNode { {isMobile ? "Select" : "Select Projects"}
-
+
{filteredProjects && filteredProjects.map((project) => )}
diff --git a/src/app/(dashboard)/settings/page.tsx b/src/app/(dashboard)/settings/page.tsx index 62afa1a..8ce38c8 100644 --- a/src/app/(dashboard)/settings/page.tsx +++ b/src/app/(dashboard)/settings/page.tsx @@ -25,14 +25,14 @@ export default function Settings(): ReactNode {

Settings

-
+
-
+
diff --git a/src/app/(dashboard)/settings/storage/page.tsx b/src/app/(dashboard)/settings/storage/page.tsx index 52bef26..e4429c9 100644 --- a/src/app/(dashboard)/settings/storage/page.tsx +++ b/src/app/(dashboard)/settings/storage/page.tsx @@ -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(null); - const router = useRouter(); const isMobile = useIsMobile(); + const iPadAirBreakpoint = useIsMobile(820); + const backButtonAdjust = (!isMobile && iPadAirBreakpoint) ? "pl-16" : ""; useEffect(() => { setStatus(localStorage.getItem("persistence-status")); }, []); return ( -
-
- - - - -
+
+
+ + +

Storage

-
- + + -
-
+ +
diff --git a/src/components/sidebar-trigger-adjustable/SidebarTriggerAdjustable.tsx b/src/components/sidebar-trigger-adjustable/SidebarTriggerAdjustable.tsx index e223fdb..8793c20 100644 --- a/src/components/sidebar-trigger-adjustable/SidebarTriggerAdjustable.tsx +++ b/src/components/sidebar-trigger-adjustable/SidebarTriggerAdjustable.tsx @@ -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)})`; }; diff --git a/src/components/static-back-button/StaticBackButton.tsx b/src/components/static-back-button/StaticBackButton.tsx new file mode 100644 index 0000000..65933e9 --- /dev/null +++ b/src/components/static-back-button/StaticBackButton.tsx @@ -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(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 ( + <> +
+
+ +
+ + ); +}; \ No newline at end of file diff --git a/src/components/static-back-button/index.ts b/src/components/static-back-button/index.ts new file mode 100644 index 0000000..8db2bb4 --- /dev/null +++ b/src/components/static-back-button/index.ts @@ -0,0 +1,3 @@ +import { StaticBackButton } from "./StaticBackButton"; + +export default StaticBackButton; \ No newline at end of file diff --git a/src/components/static-sidebar-trigger/StaticSidebarTrigger.tsx b/src/components/static-sidebar-trigger/StaticSidebarTrigger.tsx index 6babc2a..27ef1a7 100644 --- a/src/components/static-sidebar-trigger/StaticSidebarTrigger.tsx +++ b/src/components/static-sidebar-trigger/StaticSidebarTrigger.tsx @@ -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(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 ( <> -
+
- - {children} -
- + +
+ ); }; \ No newline at end of file diff --git a/src/components/sticky-top-container/StickyTopContainer.tsx b/src/components/sticky-top-container/StickyTopContainer.tsx new file mode 100644 index 0000000..b7c8371 --- /dev/null +++ b/src/components/sticky-top-container/StickyTopContainer.tsx @@ -0,0 +1,6 @@ +import { cn } from "@/lib/utils"; +import { ComponentProps } from "react"; + +export const StickyTopContainer = (props: ComponentProps<"div">) => ( +
+) \ No newline at end of file diff --git a/src/components/sticky-top-container/index.ts b/src/components/sticky-top-container/index.ts new file mode 100644 index 0000000..9701698 --- /dev/null +++ b/src/components/sticky-top-container/index.ts @@ -0,0 +1,3 @@ +import { StickyTopContainer } from "./StickyTopContainer"; + +export default StickyTopContainer; \ No newline at end of file