From 691452580b71d197e45c02c5c2741a73c7ac0acc Mon Sep 17 00:00:00 2001
From: David <33458145+davidmcpowell@users.noreply.github.com>
Date: Mon, 27 Jul 2026 09:54:28 +0100
Subject: [PATCH] Merge pull request #35735 from
overleaf/dp-mobile-projects-dropdown
Update mobile projects dropdown to merge top and submenu views
GitOrigin-RevId: 33650de2018b2536101edb7071526b65393e90d2
---
.../web/frontend/extracted-translations.json | 1 +
.../dropdown/mobile-page-switcher-items.tsx | 43 -------
...s-dropdown.tsx => navigation-dropdown.tsx} | 111 ++++++++++++------
.../components/project-list-ds-nav.tsx | 4 +-
services/web/locales/en.json | 1 +
5 files changed, 76 insertions(+), 84 deletions(-)
delete mode 100644 services/web/frontend/js/features/project-list/components/dropdown/mobile-page-switcher-items.tsx
rename services/web/frontend/js/features/project-list/components/dropdown/{projects-dropdown.tsx => navigation-dropdown.tsx} (68%)
diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index 38b4ecb0ee..89330243bc 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/services/web/frontend/extracted-translations.json
@@ -1278,6 +1278,7 @@
"name": "",
"name_usage_explanation": "",
"navigation": "",
+ "navigation_menu": "",
"need_anything_contact_us_at": "",
"need_contact_group_admin_to_make_changes": "",
"need_make_changes": "",
diff --git a/services/web/frontend/js/features/project-list/components/dropdown/mobile-page-switcher-items.tsx b/services/web/frontend/js/features/project-list/components/dropdown/mobile-page-switcher-items.tsx
deleted file mode 100644
index 3c03ce43db..0000000000
--- a/services/web/frontend/js/features/project-list/components/dropdown/mobile-page-switcher-items.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { useTranslation } from 'react-i18next'
-import { OLDropdownItem } from '@/shared/components/ol/ol-dropdown-menu'
-import { ActivePage } from '../../util/navigation-state'
-
-type Props = {
- activePage: ActivePage
- onProjectsClick?: () => void
-}
-
-function MobilePageSwitcherItems({ activePage, onProjectsClick }: Props) {
- const { t } = useTranslation()
-
- return (
- <>
-
-
- {t('library')}
-
-
-
- {onProjectsClick ? (
- {
- e.stopPropagation()
- onProjectsClick()
- }}
- >
- {t('projects')}
-
- ) : (
-
- {t('projects')}
-
- )}
-
- >
- )
-}
-
-export default MobilePageSwitcherItems
diff --git a/services/web/frontend/js/features/project-list/components/dropdown/projects-dropdown.tsx b/services/web/frontend/js/features/project-list/components/dropdown/navigation-dropdown.tsx
similarity index 68%
rename from services/web/frontend/js/features/project-list/components/dropdown/projects-dropdown.tsx
rename to services/web/frontend/js/features/project-list/components/dropdown/navigation-dropdown.tsx
index 82eb424433..cadd1c3d39 100644
--- a/services/web/frontend/js/features/project-list/components/dropdown/projects-dropdown.tsx
+++ b/services/web/frontend/js/features/project-list/components/dropdown/navigation-dropdown.tsx
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
-import { Trash } from '@phosphor-icons/react'
+import { BookBookmark, Trash, Folder } from '@phosphor-icons/react'
import {
Filter,
UNCATEGORIZED_KEY,
@@ -8,6 +8,7 @@ import {
} from '../../context/project-list-context'
import {
OLDropdown,
+ OLDropdownDivider,
OLDropdownHeader,
OLDropdownItem,
OLDropdownMenu,
@@ -17,16 +18,23 @@ import MaterialIcon from '@/shared/components/material-icon'
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
import ProjectsFilterMenu from '../projects-filter-menu'
import TagsList from '../tags-list'
-import MobilePageSwitcherItems from './mobile-page-switcher-items'
+import { ActivePage } from '../../util/navigation-state'
type ItemProps = {
filter: Filter
text: string
+ activePage: ActivePage
leadingIcon?: React.ReactNode
onClick?: () => void
}
-export function Item({ filter, text, leadingIcon, onClick }: ItemProps) {
+export function Item({
+ filter,
+ text,
+ leadingIcon,
+ onClick,
+ activePage,
+}: ItemProps) {
const { selectFilter } = useProjectListContext()
const handleClick = () => {
selectFilter(filter)
@@ -34,7 +42,7 @@ export function Item({ filter, text, leadingIcon, onClick }: ItemProps) {
}
return (
-
+
{isActive => (
t('all_projects'))
- const [view, setView] = useState<'top' | 'submenu' | 'tags'>('submenu')
+ const [title, setTitle] = useState(() =>
+ activePage === 'library' ? t('library') : t('all_projects')
+ )
+ const [view, setView] = useState<'top' | 'tags'>('top')
const { filter, selectedTagId, tags } = useProjectListContext()
const isLibraryEnabled = isSplitTestEnabled('overleaf-library')
const filterTranslations = useRef>({
@@ -66,6 +76,11 @@ function ProjectsDropdown() {
})
useEffect(() => {
+ if (activePage === 'library') {
+ setTitle(t('library'))
+ return
+ }
+
if (selectedTagId === undefined) {
setTitle(filterTranslations.current[filter])
}
@@ -79,25 +94,49 @@ function ProjectsDropdown() {
setTitle(tag.name ?? '')
}
}
- }, [filter, tags, selectedTagId, t])
+ }, [filter, tags, selectedTagId, t, activePage])
const filterItems = (
<>
-
+ }
+ />
-
+ }
+ />
-
+ }
+ />
-
+ }
+ />
{!isLibraryEnabled && (
-
+
)}
>
@@ -119,9 +158,9 @@ function ProjectsDropdown() {
isLibraryEnabled
? show => {
if (show) {
- setView(selectedTagId !== undefined ? 'tags' : 'submenu')
+ setView(selectedTagId !== undefined ? 'tags' : 'top')
} else {
- setView('submenu')
+ setView('top')
}
}
: undefined
@@ -131,7 +170,9 @@ function ProjectsDropdown() {
id="projects-types-dropdown-toggle-btn"
className="ps-0 mb-0 btn-transparent h3"
size="lg"
- aria-label={t('filter_projects')}
+ aria-label={
+ isLibraryEnabled ? t('navigation_menu') : t('filter_projects')
+ }
>
{title}
@@ -144,28 +185,15 @@ function ProjectsDropdown() {
}
>
{!isLibraryEnabled && submenuItems}
- {isLibraryEnabled && view === 'submenu' && (
+ {isLibraryEnabled && view === 'top' && (
<>
-
- }
- aria-label={t('back')}
- onClick={e => {
- e.stopPropagation()
- setView('top')
- }}
- >
- {t('projects')}
-
-
{filterItems}
}
onClick={e => {
e.stopPropagation()
setView('tags')
@@ -174,13 +202,24 @@ function ProjectsDropdown() {
{t('tags')}
+
}
+ activePage={activePage}
/>
+
+ }
+ >
+ {t('library')}
+
+
>
)}
{isLibraryEnabled && view === 'tags' && (
@@ -193,7 +232,7 @@ function ProjectsDropdown() {
aria-label={t('back')}
onClick={e => {
e.stopPropagation()
- setView('submenu')
+ setView('top')
}}
>
{t('tags')}
@@ -202,15 +241,9 @@ function ProjectsDropdown() {
>
)}
- {isLibraryEnabled && view === 'top' && (
- setView('submenu')}
- />
- )}
)
}
-export default ProjectsDropdown
+export default NavigationDropdown
diff --git a/services/web/frontend/js/features/project-list/components/project-list-ds-nav.tsx b/services/web/frontend/js/features/project-list/components/project-list-ds-nav.tsx
index 6584663b9d..1db53fe0d1 100644
--- a/services/web/frontend/js/features/project-list/components/project-list-ds-nav.tsx
+++ b/services/web/frontend/js/features/project-list/components/project-list-ds-nav.tsx
@@ -5,7 +5,7 @@ import NewProjectButton from './new-project-button'
import ProjectListTable from './table/project-list-table'
import UserNotifications from './notifications/user-notifications'
import SearchForm from './search-form'
-import ProjectsDropdown from './dropdown/projects-dropdown'
+import NavigationDropdown from './dropdown/navigation-dropdown'
import SortByDropdown from './dropdown/sort-by-dropdown'
import ProjectTools from './table/project-tools/project-tools'
import ProjectListTitle from './title/project-list-title'
@@ -151,7 +151,7 @@ export function ProjectListDsNav() {
className="projects-toolbar"
aria-label={t('projects')}
>
-
+
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index 815c5ca5ad..5f1daea7b0 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -1663,6 +1663,7 @@
"name_usage_explanation": "Your name will be displayed to your collaborators (so they know who they’re working with).",
"native": "Native",
"navigation": "Navigation",
+ "navigation_menu": "Navigation menu",
"nearly_activated": "You’re one step away from activating your __appName__ account!",
"need_20_plus_users_discount": "20+ users? <0>Contact sales0> to get the best discounts.",
"need_anything_contact_us_at": "If there is anything you ever need please feel free to contact us directly at",