feat(mobile/tab_switcher): scroll to active tab

This commit is contained in:
Elian Doran 2026-01-31 18:25:01 +02:00
parent 740b1093d7
commit 2a38af5db6
No known key found for this signature in database

View File

@ -2,10 +2,12 @@ import "./TabSwitcher.css";
import clsx from "clsx";
import { createPortal } from "preact/compat";
import { useCallback, useState } from "preact/hooks";
import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks";
import appContext from "../../components/app_context";
import NoteContext from "../../components/note_context";
import { getHue, parseColor } from "../../services/css_class_manager";
import froca from "../../services/froca";
import { t } from "../../services/i18n";
import { NoteContent } from "../collections/legacy/ListOrGridView";
import { LaunchBarActionButton } from "../launch_bar/launch_bar_widgets";
@ -32,6 +34,7 @@ function TabBarModal({ shown, setShown }: {
shown: boolean;
setShown: (newValue: boolean) => void;
}) {
const [ fullyShown, setFullyShown ] = useState(false);
const selectTab = useCallback((noteContextToActivate: NoteContext) => {
appContext.tabManager.activateNoteContext(noteContextToActivate.ntxId);
setShown(false);
@ -43,18 +46,33 @@ function TabBarModal({ shown, setShown }: {
size="xl"
title="Tabs"
show={shown}
onHidden={() => setShown(false)}
onShown={() => setFullyShown(true)}
onHidden={() => {
setShown(false);
setFullyShown(false);
}}
>
<TabBarModelContent selectTab={selectTab} />
<TabBarModelContent selectTab={selectTab} shown={fullyShown} />
</Modal>
);
}
function TabBarModelContent({ selectTab }: {
function TabBarModelContent({ selectTab, shown }: {
shown: boolean;
selectTab: (noteContextToActivate: NoteContext) => void;
}) {
const mainNoteContexts = useMainNoteContexts();
const activeNoteContext = useActiveNoteContext();
const tabRefs = useRef<Record<string, HTMLDivElement | null>>({});
// Scroll to active tab.
useEffect(() => {
if (!shown || !activeNoteContext?.ntxId) return;
const correspondingEl = tabRefs.current[activeNoteContext.ntxId];
requestAnimationFrame(() => {
correspondingEl?.scrollIntoView();
});
}, [ activeNoteContext, shown ]);
return (
<div className="tabs">
@ -64,13 +82,15 @@ function TabBarModelContent({ selectTab }: {
noteContext={noteContext}
activeNtxId={activeNoteContext.ntxId}
selectTab={selectTab}
containerRef={el => (tabRefs.current[noteContext.ntxId ?? ""] = el)}
/>
))}
</div>
);
}
function Tab({ noteContext, selectTab, activeNtxId }: {
function Tab({ noteContext, containerRef, selectTab, activeNtxId }: {
containerRef: (el: HTMLDivElement | null) => void;
noteContext: NoteContext;
selectTab: (noteContextToActivate: NoteContext) => void;
activeNtxId: string | null | undefined;
@ -78,15 +98,21 @@ function Tab({ noteContext, selectTab, activeNtxId }: {
const { note } = noteContext;
const iconClass = useNoteIcon(note);
const colorClass = note?.getColorClass() || '';
const workspaceTabBackgroundColorHue = getWorkspaceTabBackgroundColorHue(noteContext);
return (
<div
class={clsx("tab-card", colorClass, {
active: noteContext.ntxId === activeNtxId
ref={containerRef}
class={clsx("tab-card", {
active: noteContext.ntxId === activeNtxId,
"with-hue": workspaceTabBackgroundColorHue !== undefined
})}
onClick={() => selectTab(noteContext)}
style={{
"--bg-hue": workspaceTabBackgroundColorHue
}}
>
<header>
<header className={colorClass}>
<Icon icon={iconClass} />
<span className="title">{noteContext.note?.title ?? t("tab_row.new_tab")}</span>
</header>
@ -102,6 +128,23 @@ function Tab({ noteContext, selectTab, activeNtxId }: {
);
}
function getWorkspaceTabBackgroundColorHue(noteContext: NoteContext) {
if (!noteContext.hoistedNoteId) return;
const hoistedNote = froca.getNoteFromCache(noteContext.hoistedNoteId);
if (!hoistedNote) return;
const workspaceTabBackgroundColor = hoistedNote.getWorkspaceTabBackgroundColor();
if (!workspaceTabBackgroundColor) return;
try {
const parsedColor = parseColor(workspaceTabBackgroundColor);
if (!parsedColor) return;
return getHue(parsedColor);
} catch (e) {
// Colors are non-critical, simply ignore.
}
}
function useMainNoteContexts() {
const [ noteContexts, setNoteContexts ] = useState(appContext.tabManager.getMainNoteContexts());