Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion components/editor/EditorBottomBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from "react";
import { useTranslations } from "next-intl";
import { ChevronDown, ChevronUp } from "lucide-react";

import { useIsPhone, useIsTouch, useViewportBottomInset } from "@src/lib/utils/hooks";
import { useIsPhone, useIsTouch, usePagePanLock, useViewportBottomInset } from "@src/lib/utils/hooks";
import { KEYBOARD_MIN_HEIGHT } from "@src/lib/editor/visible-band";
import { useActiveEditor } from "@src/lib/editor/use-active-editor";
import { useEditorFocused } from "@src/lib/editor/use-editor-focused";
Expand Down Expand Up @@ -120,10 +120,16 @@ const EditorBottomBar = () => {

const hasIslands = isTouch && !isPhone;

// A drag across the row must never pan the page: this bar is fixed chrome, so
// the pan would carry it off the screen (see usePagePanLock). The pill's own
// sideways scroll and its upward menus are exempted there.
const panLockRef = usePagePanLock<HTMLDivElement>();

if (!isTouch || !isWriting) return null;

return (
<div
ref={panLockRef}
className={styles.bar_row}
// Handed to CSS as a length rather than set as `bottom` outright, so
// the rule can floor it against the resting height — see .bar_row.
Expand Down
10 changes: 10 additions & 0 deletions components/editor/MobileFormatToolbar.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@
min-width: 0;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
/* Stop at this row's own ends: without it, a drag that runs past the last
* control chains out to the document, and since the bar is fixed chrome that
* pans it clean off the screen. The vertical half of the same gesture is
* refused outright by the row's pan lock (see usePagePanLock). */
overscroll-behavior: contain;
scrollbar-width: none;
}

Expand Down Expand Up @@ -259,6 +264,9 @@
min-width: 180px;
max-height: 260px;
overflow-y: auto;
/* Keep a flick through the list from chaining to the document once it hits
* either end — that pan takes the whole bar off screen (see .format_group). */
overscroll-behavior: contain;
padding: 6px;

background-color: var(--secondary);
Expand Down Expand Up @@ -312,6 +320,8 @@
max-width: min(280px, 100%);
max-height: 260px;
overflow-y: auto;
/* As with .element_menu — the suggestions must not chain their scroll out. */
overscroll-behavior: contain;
padding: 6px;

background-color: var(--secondary);
Expand Down
16 changes: 11 additions & 5 deletions components/navbar/HomeNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ import navbar from "./ProjectNavbar.module.css";
import navBtn from "@components/utils/NavbarIconButton.module.css";

interface HomeNavbarProps {
/** Phone only: opens the projects sidebar drawer (the burger lives here now). */
onOpenSidebar?: () => void;
/**
* Phone only: toggles the projects sidebar drawer (the burger lives here now).
* Both phone drawers sit below the navbar, so their opener stays tappable while
* they're open and has to close them again — see [ProjectPageContainer.module.css].
*/
onToggleSidebar?: () => void;
}

const HomeNavbar = ({ onOpenSidebar }: HomeNavbarProps) => {
const { openDashboard } = useContext(DashboardContext);
const HomeNavbar = ({ onToggleSidebar }: HomeNavbarProps) => {
const { isOpen, openDashboard, closeDashboard } = useContext(DashboardContext);
const { user, isLoading } = useCookieUser();
const isPhone = useIsPhone();
const tNav = useTranslations("navbar");
Expand All @@ -29,6 +33,8 @@ const HomeNavbar = ({ onOpenSidebar }: HomeNavbarProps) => {
// While the auth state is still loading, omit the tab arg so the modal
// opens on its current activeTab instead of guessing wrong.
const onOpen = () => {
// Second tap on the Settings icon closes the drawer it opened.
if (isOpen) return closeDashboard();
if (isLoading) openDashboard();
else openDashboard(user ? "Profile" : "Auth");
};
Expand All @@ -44,7 +50,7 @@ const HomeNavbar = ({ onOpenSidebar }: HomeNavbarProps) => {
<div className={navbar.mobile_left}>
<button
className={join(navBtn.button, navbar.mobile_icon, navbar.home_burger)}
onClick={onOpenSidebar}
onClick={onToggleSidebar}
aria-label={tNav("menu")}
>
<Menu size={18} />
Expand Down
14 changes: 13 additions & 1 deletion components/navbar/ProjectNavbarMobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { useViewContext } from "@src/context/ViewContext";
import { useActiveEditor } from "@src/lib/editor/use-active-editor";
import { getDictationLanguage, useDictation } from "@src/lib/editor/use-dictation";
import { uploadToCloudPopup } from "@src/lib/screenplay/popup";
import { usePagePanLock } from "@src/lib/utils/hooks";
import { join } from "@src/lib/utils/misc";

import { useProjectNavbar } from "./useProjectNavbar";
Expand Down Expand Up @@ -160,8 +161,19 @@ const ProjectNavbarMobile = () => {
setMobileEditMode(false);
};

// The bar is a fixed overlay on phone, so a drag that lands on it and pans the
// page takes the bar off the top of the screen with it — most visibly while
// editing, when the keyboard gives WKWebView's scroll view the room to move.
// Refuse those pans here (see usePagePanLock). Everything that scrolls — the
// burger drawer, the search panel, the tool sheets — is portaled to <body> and
// so sits outside this subtree entirely.
const panLockRef = usePagePanLock<HTMLElement>();

return (
<nav className={join(navbar.container, chromeHidden ? navbar.container_hidden : "")}>
<nav
ref={panLockRef}
className={join(navbar.container, chromeHidden ? navbar.container_hidden : "")}
>
<nav className={navbar.mobile_bar}>
{/* Left cluster: edit-mode controls (leave edit mode + undo/redo)
while editing; otherwise the back-to-projects arrow. */}
Expand Down
2 changes: 1 addition & 1 deletion src/app/projects/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function ProjectsPageContent() {
if (!projectId) {
return (
<>
<HomeNavbar onOpenSidebar={() => setSidebarOpen(true)} />
<HomeNavbar onToggleSidebar={() => setSidebarOpen((open) => !open)} />
<ProjectPageContainer sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} />
<DashboardModal />
</>
Expand Down
4 changes: 2 additions & 2 deletions src/context/ViewContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ interface ViewContextType {
/**
* Subscribe to be called synchronously *just before* `isEndlessScroll`
* flips — i.e. while the outgoing layout is still on screen and measurable.
* Editor panels use it to record which block sits at the top of their
* viewport, because the two modes render the same document at very
* Editor panels use it to record which block the reader is looking at (and
* how far into it), because the two modes render the same document at very
* different heights and a raw scrollTop would land somewhere else entirely
* (see DocumentEditorPanel's scroll anchoring). Returns an unsubscribe.
*/
Expand Down
Loading
Loading