Merge pull request #35735 from overleaf/dp-mobile-projects-dropdown

Update mobile projects dropdown to merge top and submenu views

GitOrigin-RevId: 33650de2018b2536101edb7071526b65393e90d2
This commit is contained in:
David
2026-09-17 11:38:01 +00:00
committed by Copybot
parent f81a7b2ff2
commit 691452580b
5 changed files with 76 additions and 84 deletions
@@ -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": "",
@@ -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 (
<>
<li role="none">
<OLDropdownItem href="/library" active={activePage === 'library'}>
{t('library')}
</OLDropdownItem>
</li>
<li role="none">
{onProjectsClick ? (
<OLDropdownItem
as="button"
tabIndex={-1}
trailingIcon="chevron_right"
onClick={e => {
e.stopPropagation()
onProjectsClick()
}}
>
{t('projects')}
</OLDropdownItem>
) : (
<OLDropdownItem href="/project" active={activePage === 'projects'}>
{t('projects')}
</OLDropdownItem>
)}
</li>
</>
)
}
export default MobilePageSwitcherItems
@@ -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 (
<ProjectsFilterMenu filter={filter} activePage="projects">
<ProjectsFilterMenu filter={filter} activePage={activePage}>
{isActive => (
<OLDropdownItem
as="button"
@@ -51,10 +59,12 @@ export function Item({ filter, text, leadingIcon, onClick }: ItemProps) {
)
}
function ProjectsDropdown() {
function NavigationDropdown({ activePage }: { activePage: ActivePage }) {
const { t } = useTranslation()
const [title, setTitle] = useState(() => 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<Record<Filter, string>>({
@@ -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 = (
<>
<li role="none">
<Item filter="all" text={t('all_projects')} />
<Item
filter="all"
text={t('all_projects')}
activePage={activePage}
leadingIcon={isLibraryEnabled && <Folder size={20} />}
/>
</li>
<li role="none">
<Item filter="owned" text={t('your_projects')} />
<Item
filter="owned"
text={t('your_projects')}
activePage={activePage}
leadingIcon={isLibraryEnabled && <OLDropdownItem.EmptyLeadingIcon />}
/>
</li>
<li role="none">
<Item filter="shared" text={t('shared_with_you')} />
<Item
filter="shared"
text={t('shared_with_you')}
activePage={activePage}
leadingIcon={isLibraryEnabled && <OLDropdownItem.EmptyLeadingIcon />}
/>
</li>
<li role="none">
<Item filter="archived" text={t('archived_projects')} />
<Item
filter="archived"
text={t('archived_projects')}
activePage={activePage}
leadingIcon={isLibraryEnabled && <OLDropdownItem.EmptyLeadingIcon />}
/>
</li>
{!isLibraryEnabled && (
<li role="none">
<Item filter="trashed" text={t('trashed_projects')} />
<Item
filter="trashed"
text={t('trashed_projects')}
activePage={activePage}
/>
</li>
)}
</>
@@ -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')
}
>
<span className="text-truncate" aria-hidden>
{title}
@@ -144,28 +185,15 @@ function ProjectsDropdown() {
}
>
{!isLibraryEnabled && submenuItems}
{isLibraryEnabled && view === 'submenu' && (
{isLibraryEnabled && view === 'top' && (
<>
<li role="none">
<OLDropdownItem
as="button"
tabIndex={-1}
leadingIcon={<MaterialIcon type="chevron_left" />}
aria-label={t('back')}
onClick={e => {
e.stopPropagation()
setView('top')
}}
>
{t('projects')}
</OLDropdownItem>
</li>
{filterItems}
<li role="none">
<OLDropdownItem
as="button"
tabIndex={-1}
trailingIcon="chevron_right"
leadingIcon={<OLDropdownItem.EmptyLeadingIcon />}
onClick={e => {
e.stopPropagation()
setView('tags')
@@ -174,13 +202,24 @@ function ProjectsDropdown() {
{t('tags')}
</OLDropdownItem>
</li>
<OLDropdownDivider />
<li role="none">
<Item
filter="trashed"
text={t('trash')}
leadingIcon={<Trash size={20} />}
activePage={activePage}
/>
</li>
<li role="none">
<OLDropdownItem
active={activePage === 'library'}
href="/library"
leadingIcon={<BookBookmark size={20} />}
>
{t('library')}
</OLDropdownItem>
</li>
</>
)}
{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() {
<TagsList />
</>
)}
{isLibraryEnabled && view === 'top' && (
<MobilePageSwitcherItems
activePage="projects"
onProjectsClick={() => setView('submenu')}
/>
)}
</OLDropdownMenu>
</OLDropdown>
)
}
export default ProjectsDropdown
export default NavigationDropdown
@@ -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')}
>
<ProjectsDropdown />
<NavigationDropdown activePage="projects" />
<SortByDropdown />
</div>
</div>
+1
View File
@@ -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 sales</0> 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",