Merge pull request #30237 from overleaf/ae-workbench-popover

Add a popover to the AI assistant rail button

GitOrigin-RevId: 5bc141ba10ef0bc798ab79a847d73804d60353d6
This commit is contained in:
Mathias Jakobsen
2025-12-11 10:00:14 +00:00
committed by Copybot
parent b554b0cfcc
commit efe7cf0064
7 changed files with 36 additions and 11 deletions

View File

@@ -29,6 +29,7 @@ const VALID_KEYS = [
'new-editor-intro',
'new-editor-intro-2',
'old-editor-warning-tooltip',
'workbench-rail-popover',
]
async function completeTutorial(req, res, next) {

View File

@@ -1048,6 +1048,7 @@ module.exports = {
errorLogsComponents: [],
referenceIndices: [],
railEntries: [],
railPopovers: [],
},
moduleImportSequence: [

View File

@@ -2,7 +2,7 @@ import { DropdownMenu } from '@/shared/components/dropdown/dropdown-menu'
import { RailTabKey } from '../../contexts/rail-context'
import { RailElement } from '../../utils/rail-types'
import RailTab from './rail-tab'
import { shouldIncludeRailTab } from '../../utils/rail-utils'
import { shouldIncludeElement } from '../../utils/rail-utils'
export default function RailOverflowDropdown({
tabs,
@@ -16,7 +16,7 @@ export default function RailOverflowDropdown({
return (
<DropdownMenu className="ide-rail-overflow-dropdown">
{tabs
.filter(shouldIncludeRailTab)
.filter(shouldIncludeElement)
.map(({ icon, key, indicator, title, disabled }) => (
<RailTab
open={isOpen && selectedTab === key}

View File

@@ -6,7 +6,7 @@ import usePreviousValue from '@/shared/hooks/use-previous-value'
import { HistorySidebar } from '@/features/ide-react/components/history-sidebar'
import { Tab } from 'react-bootstrap'
import { RailElement } from '../../utils/rail-types'
import { shouldIncludeRailTab } from '../../utils/rail-utils'
import { shouldIncludeElement } from '../../utils/rail-utils'
export default function RailPanel({
isReviewPanelOpen,
@@ -53,7 +53,7 @@ export default function RailPanel({
>
<Tab.Content className="ide-rail-tab-content">
{railTabs
.filter(shouldIncludeRailTab)
.filter(shouldIncludeElement)
.map(({ key, component, mountOnFirstLoad }) => (
<Tab.Pane
eventKey={key}

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useRef } from 'react'
import { FC, RefObject, useCallback, useEffect, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { Nav, TabContainer } from 'react-bootstrap'
import { useLayoutContext } from '@/shared/context/layout-context'
@@ -30,7 +30,7 @@ import EditorTourRailTooltip from '../editor-tour/editor-tour-rail-tooltip'
import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
import EditorTourThemeTooltip from '../editor-tour/editor-tour-theme-tooltip'
import EditorTourSwitchBackTooltip from '../editor-tour/editor-tour-switch-back-tooltip'
import { shouldIncludeRailTab } from '../../utils/rail-utils'
import { shouldIncludeElement } from '../../utils/rail-utils'
const moduleRailEntries = (
importOverleafModules('railEntries') as {
@@ -38,6 +38,19 @@ const moduleRailEntries = (
path: string
}[]
).map(({ import: { default: element } }) => element)
const moduleRailPopovers = (
importOverleafModules('railPopovers') as {
import: {
default: {
key: string
Component: FC<{ ref: RefObject<HTMLAnchorElement> }>
ref: RefObject<HTMLAnchorElement>
hide: boolean | (() => boolean)
}
}
path: string
}[]
).map(({ import: { default: element } }) => element)
export const RailLayout = () => {
const { sendEvent } = useEditorAnalytics()
@@ -175,7 +188,7 @@ export const RailLayout = () => {
useEffect(() => {
const validTabKeys = railTabs
.filter(shouldIncludeRailTab)
.filter(shouldIncludeElement)
.map(tab => tab.key)
if (!validTabKeys.includes(selectedTab) && isOpen) {
// If the selected tab is no longer valid (e.g. due to permissions changes),
@@ -225,7 +238,7 @@ export const RailLayout = () => {
<Nav activeKey={selectedTab} className="ide-rail-tabs-nav">
<div className="ide-rail-tabs-wrapper" ref={tabWrapperRef}>
{tabsInRail
.filter(shouldIncludeRailTab)
.filter(shouldIncludeElement)
.map(({ icon, key, indicator, title, disabled, ref }) => (
<RailTab
open={isOpen && selectedTab === key}
@@ -254,6 +267,11 @@ export const RailLayout = () => {
<EditorTourRailTooltip target={fileTreeRef.current} />
<EditorTourThemeTooltip target={settingsRef.current} />
<EditorTourSwitchBackTooltip target={settingsRef.current} />
{moduleRailPopovers
.filter(shouldIncludeElement)
.map(({ key, Component, ref }) => (
<Component key={key} ref={ref} />
))}
<RailPanel
isReviewPanelOpen={isReviewPanelOpen}
isHistoryView={isHistoryView}

View File

@@ -15,6 +15,7 @@ const NewEditorTourContext = createContext<
stage: NewEditorTourStage
stageNumber: number
totalStages: number
isShowing: boolean
shouldShowTourStage: (tourStage: NewEditorTourStage) => boolean
startTour: () => void
goToNextStage: () => void
@@ -83,6 +84,7 @@ export const NewEditorTourProvider: FC<React.PropsWithChildren> = ({
goToNextStage,
finishTour,
dismissTour,
isShowing: showTour,
}),
[
stage,
@@ -93,6 +95,7 @@ export const NewEditorTourProvider: FC<React.PropsWithChildren> = ({
goToNextStage,
finishTour,
dismissTour,
showTour,
]
)

View File

@@ -1,6 +1,8 @@
import { RailElement } from './rail-types'
export function shouldIncludeRailTab({ hide }: RailElement): boolean {
export function shouldIncludeElement({
hide,
}: {
hide?: boolean | (() => boolean)
}): boolean {
if (typeof hide === 'function') {
return !hide()
}