Merge pull request #34162 from overleaf/mj-tabs-close-to-right

[web] Context menu updates for tabs

GitOrigin-RevId: 09dbecaf119237adfb9b779a46bf76103c12de8e
This commit is contained in:
Mathias Jakobsen
2026-06-08 08:06:22 +00:00
committed by Copybot
parent 20f03d75fe
commit ddfff06ef8
8 changed files with 208 additions and 24 deletions
@@ -311,8 +311,8 @@
"clone_with_git": "",
"close": "",
"close_dialog": "",
"close_others": "",
"close_tab": "",
"close_other_tabs": "",
"close_tabs_to_the_right": "",
"clsi_maintenance": "",
"clsi_unavailable": "",
"code": "",
@@ -2014,6 +2014,7 @@
"syntax_checks": "",
"syntax_validation": "",
"tab_connecting": "",
"tab_settings": "",
"table": "",
"tabs_open_in_preview_mode_until_you_interact_with_them": "",
"tag_color": "",
@@ -48,6 +48,7 @@ const TabsContext = React.createContext<
closeTab: (id: string) => void
closeOtherTabs: (id: string) => void
makeTabPermanent: (id: string) => void
closeToRight: (id: string) => void
moveTab: (
sourceTabId: string,
targetTabId: string,
@@ -174,6 +175,32 @@ export const TabsProvider: FC<React.PropsWithChildren> = ({ children }) => {
[openEntity, openTab, setOpenTabs]
)
const closeToRight = useCallback(
async (id: string) => {
const tabIndex = openTabs.findIndex(tab => tab.id === id)
if (tabIndex === -1) {
debugConsole.warn(
'Attempting to close to right on tab that is not open'
)
return
}
const openTabIndex = openTabs.findIndex(
tab => tab.id === openEntity?.entity._id
)
if (openTabIndex === -1 || openTabIndex > tabIndex) {
await openTab(id)
}
setOpenTabs(current => {
const currentIndex = current.findIndex(tab => tab.id === id)
if (currentIndex === -1) {
return current
}
return current.slice(0, currentIndex + 1)
})
},
[setOpenTabs, openTabs, openEntity, openTab]
)
const moveTab = useCallback(
(sourceTabId: string, targetTabId: string, position: 'left' | 'right') => {
debugConsole.log({ sourceTabId, targetTabId, position })
@@ -269,6 +296,7 @@ export const TabsProvider: FC<React.PropsWithChildren> = ({ children }) => {
makeTabPermanent,
contextMenuTarget,
setContextMenuTarget,
closeToRight,
}),
[
tabs,
@@ -279,6 +307,7 @@ export const TabsProvider: FC<React.PropsWithChildren> = ({ children }) => {
makeTabPermanent,
contextMenuTarget,
setContextMenuTarget,
closeToRight,
]
)
@@ -24,6 +24,10 @@ export default function useFocusOnSetting() {
const settingToFocusElt: HTMLElement | null =
settingElt?.querySelector('input, select, button') ?? settingElt
settingElt?.classList.add('ide-setting-highlighted')
setTimeout(() => {
settingElt?.classList.remove('ide-setting-highlighted')
}, 2000)
setActiveTab(newActiveTab)
setEltToScrollTo({ tab: newActiveTab, element: settingToFocusElt })
@@ -231,7 +231,7 @@ export const Tab = memo(function Tab({
<button
onClick={onCloseClick}
className="editor-file-tab-close-action"
aria-label={t('close_tab')}
aria-label={t('close')}
>
<MaterialIcon type="close" />
</button>
@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom'
import { useTranslation } from 'react-i18next'
import {
Dropdown,
DropdownDivider,
DropdownItem,
DropdownMenu,
} from '@/shared/components/dropdown/dropdown-menu'
@@ -14,6 +15,7 @@ export function TabsContextMenu() {
tabs,
closeTab,
closeOtherTabs,
closeToRight,
contextMenuTarget,
setContextMenuTarget,
} = useTabsContext()
@@ -89,7 +91,7 @@ export function TabsContextMenu() {
close()
}}
>
{t('close_tab')}
{t('close')}
</DropdownItem>
<DropdownItem
disabled={tabs.length <= 1}
@@ -99,7 +101,36 @@ export function TabsContextMenu() {
close()
}}
>
{t('close_others')}
{t('close_other_tabs')}
</DropdownItem>
<DropdownItem
disabled={tabs[tabs.length - 1]?.id === contextMenuTarget.tabId}
as="button"
onClick={() => {
closeToRight(contextMenuTarget.tabId)
close()
}}
>
{t('close_tabs_to_the_right')}
</DropdownItem>
<DropdownDivider />
<DropdownItem
as="button"
onClick={() => {
window.dispatchEvent(
new CustomEvent('ui.toggle-left-menu', { detail: true })
)
// focus the tab setting
window.dispatchEvent(
new CustomEvent('ui.focus-setting', {
detail: 'previewTabs',
})
)
close()
}}
leadingIcon="settings"
>
{t('tab_settings')}
</DropdownItem>
</DropdownMenu>
</Dropdown>
@@ -10,7 +10,7 @@
max-height: 75%;
height: 500px;
width: 100%;
padding: 0 var(--spacing-06);
padding: 0 var(--spacing-04);
overflow: auto;
}
@@ -65,6 +65,7 @@
line-height: var(--line-height-01);
padding-bottom: var(--spacing-06);
border-bottom: 1px solid var(--border-divider);
margin: 0 var(--spacing-04);
}
.ide-setting {
@@ -72,6 +73,9 @@
justify-content: space-between;
align-items: center;
min-height: 48px;
padding: 0 var(--spacing-02);
margin: 0 var(--spacing-02);
border-radius: var(--border-radius-base);
}
.ide-dropdown-setting {
@@ -96,7 +100,7 @@
min-width: 160px;
display: flex;
justify-content: flex-end;
margin-left: var(--spacing-06);
margin-left: var(--spacing-05);
}
.ide-radio-setting-options {
@@ -116,3 +120,7 @@
align-items: center;
gap: var(--spacing-05);
}
.ide-setting-highlighted {
box-shadow: 0 1px 10px 0 rgb(102 175 233 / 80%);
}