From d66b3b93fe11d480f39bf1287795285c441503d9 Mon Sep 17 00:00:00 2001 From: Cole Bemis Date: Thu, 12 Dec 2024 23:58:07 -0800 Subject: [PATCH] Conditionally show page title --- src/components/app-layout.tsx | 40 +++++----------------------------- src/components/sidebar.tsx | 41 ++++------------------------------- src/hooks/is-scrolled.ts | 39 +++++++++++++++++++++++++++++++++ src/routes/notes_.$.tsx | 20 ++++++++++++----- 4 files changed, 62 insertions(+), 78 deletions(-) create mode 100644 src/hooks/is-scrolled.ts diff --git a/src/components/app-layout.tsx b/src/components/app-layout.tsx index 952dd9d0..ab98d5c0 100644 --- a/src/components/app-layout.tsx +++ b/src/components/app-layout.tsx @@ -1,5 +1,4 @@ import { useAtomValue } from "jotai" -import { useEffect, useRef, useState } from "react" import { LoadingIcon16 } from "../components/icons" import { RepoForm } from "../components/repo-form" import { @@ -10,10 +9,11 @@ import { isSignedOutAtom, sidebarAtom, } from "../global-state" +import { useIsScrolled } from "../hooks/is-scrolled" import { cx } from "../utils/cx" import { AppHeader, AppHeaderProps } from "./app-header" -import { Sidebar } from "./sidebar" import { NavBar } from "./nav-bar" +import { Sidebar } from "./sidebar" type AppLayoutProps = AppHeaderProps & { className?: string @@ -35,33 +35,7 @@ export function AppLayout({ const githubRepo = useAtomValue(githubRepoAtom) const sidebar = useAtomValue(sidebarAtom) - const [isScrolled, setIsScrolled] = useState(false) - const scrollContainerRef = useRef(null) - // Reference to an invisible element at the top of the content - // Used to detect when content is scrolled - const topSentinelRef = useRef(null) - - useEffect(() => { - const sentinel = topSentinelRef.current - if (!sentinel) return - - // Create an IntersectionObserver that watches when our sentinel element - // enters or leaves the viewport - const observer = new IntersectionObserver( - ([entry]) => { - // When sentinel is visible (intersecting), we're at the top (not scrolled) - // When it's not visible, we've scrolled down - setIsScrolled(!entry.isIntersecting) - }, - { - // Only trigger when sentinel is fully visible/hidden - threshold: 1.0, - }, - ) - - observer.observe(sentinel) - return () => observer.disconnect() - }, []) + const { isScrolled, topSentinelProps } = useIsScrolled() return (
@@ -76,12 +50,8 @@ export function AppLayout({ actions={isRepoCloned || isSignedOut || disableGuard ? actions : undefined} className={cx("border-b", isScrolled ? "border-border-secondary" : "border-transparent")} /> -
- {/* Invisible sentinel element that helps detect scroll position */} -
+
+
{isRepoNotCloned && !disableGuard ? (
diff --git a/src/components/sidebar.tsx b/src/components/sidebar.tsx index 992f2bc1..0324e3bc 100644 --- a/src/components/sidebar.tsx +++ b/src/components/sidebar.tsx @@ -1,6 +1,6 @@ import { useSetAtom } from "jotai" -import { useEffect, useRef, useState } from "react" import { sidebarAtom } from "../global-state" +import { useIsScrolled } from "../hooks/is-scrolled" import { cx } from "../utils/cx" import { IconButton } from "./icon-button" import { SidebarFillIcon16 } from "./icons" @@ -9,34 +9,7 @@ import { NavItems } from "./nav-items" export function Sidebar() { const setSidebar = useSetAtom(sidebarAtom) - // const { isScrolled, scrollContainerRef, topSentinelRef } = useIsScrolled() - const [isScrolled, setIsScrolled] = useState(false) - const scrollContainerRef = useRef(null) - // Reference to an invisible element at the top of the content - // Used to detect when content is scrolled - const topSentinelRef = useRef(null) - - useEffect(() => { - const sentinel = topSentinelRef.current - if (!sentinel) return - - // Create an IntersectionObserver that watches when our sentinel element - // enters or leaves the viewport - const observer = new IntersectionObserver( - ([entry]) => { - // When sentinel is visible (intersecting), we're at the top (not scrolled) - // When it's not visible, we've scrolled down - setIsScrolled(!entry.isIntersecting) - }, - { - // Only trigger when sentinel is fully visible/hidden - threshold: 1.0, - }, - ) - - observer.observe(sentinel) - return () => observer.disconnect() - }, []) + const { isScrolled, topSentinelProps } = useIsScrolled() return (
@@ -55,14 +28,8 @@ export function Sidebar() {
-
-
+
+
diff --git a/src/hooks/is-scrolled.ts b/src/hooks/is-scrolled.ts new file mode 100644 index 00000000..8068fba7 --- /dev/null +++ b/src/hooks/is-scrolled.ts @@ -0,0 +1,39 @@ +import { useState, useRef, useEffect, useMemo } from "react" + +export function useIsScrolled() { + const [isScrolled, setIsScrolled] = useState(false) + // Reference to an invisible element at the top of the content + // Used to detect when content is scrolled + const topSentinelRef = useRef(null) + + useEffect(() => { + const sentinel = topSentinelRef.current + if (!sentinel) return + + // Create an IntersectionObserver that watches when our sentinel element + // enters or leaves the viewport + const observer = new IntersectionObserver( + ([entry]) => { + // When sentinel is visible (intersecting), we're at the top (not scrolled) + // When it's not visible, we've scrolled down + setIsScrolled(!entry.isIntersecting) + }, + { + // Only trigger when sentinel is fully visible/hidden + threshold: 1.0, + }, + ) + + observer.observe(sentinel) + return () => observer.disconnect() + }, []) + + const topSentinelProps = useMemo(() => { + return { + ref: topSentinelRef, + className: "pointer-events-none absolute inset-x-0 top-0 h-[1px]", + } + }, []) + + return { isScrolled, topSentinelProps } +} diff --git a/src/routes/notes_.$.tsx b/src/routes/notes_.$.tsx index e0fc523b..84e8d7d4 100644 --- a/src/routes/notes_.$.tsx +++ b/src/routes/notes_.$.tsx @@ -25,7 +25,6 @@ import { FullwidthIcon16, MoreIcon16, NoteIcon16, - PinFillIcon12, PinFillIcon16, PinIcon16, ShareIcon16, @@ -49,6 +48,7 @@ import { widthAtom, } from "../global-state" import { useEditorSettings } from "../hooks/editor-settings" +import { useIsScrolled } from "../hooks/is-scrolled" import { useDeleteNote, useNoteById, useSaveNote } from "../hooks/note" import { useSearchNotes } from "../hooks/search" import { GitHubRepository, Note, NoteId, Template } from "../schema" @@ -312,18 +312,25 @@ function NotePage() { }) }, [getHandleSave, getEditorValue, getSwitchToReading, getIsDirty]) + const { isScrolled, topSentinelProps } = useIsScrolled() + + const shouldShowPageTitle = + parsedNote.displayName !== "Untitled note" && (isScrolled || !parsedNote.title) + return ( - {parsedNote.pinned ? : null} - - - + {/* {parsedNote.pinned ? : null} */} + {shouldShowPageTitle ? ( + + + + ) : null} {isDirty ? : null} } - icon={} + icon={shouldShowPageTitle ? : null} actions={
{!note || isDirty ? ( @@ -505,6 +512,7 @@ function NotePage() { } }} > +