Merge pull request #24708 from overleaf/mj-rail-element-tooltips

[web] Add tooltips to rail buttons

GitOrigin-RevId: 1b75c48f30b593e9e725bcb9658c4c15c9fe6a66
This commit is contained in:
Mathias Jakobsen
2025-04-10 08:06:01 +00:00
committed by Copybot
parent c183176fd3
commit 6207c853ef
3 changed files with 114 additions and 67 deletions
@@ -521,6 +521,7 @@
"error": "", "error": "",
"error_assist": "", "error_assist": "",
"error_assist_to_help_fixing_latex_errors": "", "error_assist_to_help_fixing_latex_errors": "",
"error_log": "",
"error_opening_document": "", "error_opening_document": "",
"error_opening_document_detail": "", "error_opening_document_detail": "",
"error_performing_request": "", "error_performing_request": "",
@@ -1394,6 +1395,7 @@
"reverse_x_sort_order": "", "reverse_x_sort_order": "",
"revert_pending_plan_change": "", "revert_pending_plan_change": "",
"review": "", "review": "",
"review_panel": "",
"review_panel_comments_and_track_changes": "", "review_panel_comments_and_track_changes": "",
"review_your_peers_work": "", "review_your_peers_work": "",
"reviewer": "", "reviewer": "",
@@ -31,58 +31,31 @@ import { RailHelpShowHotkeysModal } from './help/keyboard-shortcuts'
import { RailHelpContactUsModal } from './help/contact-us' import { RailHelpContactUsModal } from './help/contact-us'
import { HistorySidebar } from '@/features/ide-react/components/history-sidebar' import { HistorySidebar } from '@/features/ide-react/components/history-sidebar'
import DictionarySettingsModal from './settings/editor-settings/dictionary-settings-modal' import DictionarySettingsModal from './settings/editor-settings/dictionary-settings-modal'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
type RailElement = { type RailElement = {
icon: AvailableUnfilledIcon icon: AvailableUnfilledIcon
key: RailTabKey key: RailTabKey
component: ReactElement component: ReactElement
indicator?: ReactElement indicator?: ReactElement
title: string
hide?: boolean hide?: boolean
} }
type RailActionButton = { type RailActionButton = {
key: string key: string
icon: AvailableUnfilledIcon icon: AvailableUnfilledIcon
title: string
action: () => void action: () => void
} }
type RailDropdown = { type RailDropdown = {
key: string key: string
icon: AvailableUnfilledIcon icon: AvailableUnfilledIcon
title: string
dropdown: ReactElement dropdown: ReactElement
} }
type RailAction = RailDropdown | RailActionButton type RailAction = RailDropdown | RailActionButton
const RAIL_TABS: RailElement[] = [
{
key: 'file-tree',
icon: 'description',
component: <FileTreeOutlinePanel />,
},
{
key: 'integrations',
icon: 'integration_instructions',
component: <IntegrationsPanel />,
},
{
key: 'review-panel',
icon: 'rate_review',
component: <>Review panel</>,
},
{
key: 'chat',
icon: 'forum',
component: <ChatPane />,
indicator: <ChatIndicator />,
hide: !getMeta('ol-chatEnabled'),
},
{
key: 'errors',
icon: 'report',
component: <ErrorPane />,
indicator: <ErrorIndicator />,
},
]
const RAIL_MODALS: { const RAIL_MODALS: {
key: RailModalKey key: RailModalKey
modalComponentFunction: FC<{ show: boolean }> modalComponentFunction: FC<{ show: boolean }>
@@ -120,20 +93,61 @@ export const RailLayout = () => {
const isHistoryView = view === 'history' const isHistoryView = view === 'history'
const railTabs: RailElement[] = useMemo(
() => [
{
key: 'file-tree',
icon: 'description',
title: t('file_tree'),
component: <FileTreeOutlinePanel />,
},
{
key: 'integrations',
icon: 'integration_instructions',
title: t('integrations'),
component: <IntegrationsPanel />,
},
{
key: 'review-panel',
icon: 'rate_review',
title: t('review_panel'),
component: <>Review panel</>,
},
{
key: 'chat',
icon: 'forum',
component: <ChatPane />,
indicator: <ChatIndicator />,
title: t('chat'),
hide: !getMeta('ol-chatEnabled'),
},
{
key: 'errors',
icon: 'report',
title: t('error_log'),
component: <ErrorPane />,
indicator: <ErrorIndicator />,
},
],
[t]
)
const railActions: RailAction[] = useMemo( const railActions: RailAction[] = useMemo(
() => [ () => [
{ {
key: 'support', key: 'support',
icon: 'help', icon: 'help',
title: t('help'),
dropdown: <RailHelpDropdown />, dropdown: <RailHelpDropdown />,
}, },
{ {
key: 'settings', key: 'settings',
icon: 'settings', icon: 'settings',
title: t('settings'),
action: () => setLeftMenuShown(true), action: () => setLeftMenuShown(true),
}, },
], ],
[setLeftMenuShown] [setLeftMenuShown, t]
) )
const onTabSelect = useCallback( const onTabSelect = useCallback(
@@ -143,7 +157,7 @@ export const RailLayout = () => {
} else { } else {
// HACK: Apparently the onSelect event is triggered with href attributes // HACK: Apparently the onSelect event is triggered with href attributes
// from DropdownItems // from DropdownItems
if (!RAIL_TABS.some(tab => !tab.hide && tab.key === key)) { if (!railTabs.some(tab => !tab.hide && tab.key === key)) {
// Attempting to open a non-existent tab // Attempting to open a non-existent tab
return return
} }
@@ -152,7 +166,7 @@ export const RailLayout = () => {
setIsOpen(true) setIsOpen(true)
} }
}, },
[setSelectedTab, selectedTab, setIsOpen, togglePane] [setSelectedTab, selectedTab, setIsOpen, togglePane, railTabs]
) )
return ( return (
@@ -166,17 +180,18 @@ export const RailLayout = () => {
> >
<div className={classNames('ide-rail', { hidden: isHistoryView })}> <div className={classNames('ide-rail', { hidden: isHistoryView })}>
<Nav activeKey={selectedTab} className="ide-rail-tabs-nav"> <Nav activeKey={selectedTab} className="ide-rail-tabs-nav">
{RAIL_TABS.filter(({ hide }) => !hide).map( {railTabs
({ icon, key, indicator }) => ( .filter(({ hide }) => !hide)
.map(({ icon, key, indicator, title }) => (
<RailTab <RailTab
open={isOpen && selectedTab === key} open={isOpen && selectedTab === key}
key={key} key={key}
eventKey={key} eventKey={key}
icon={icon} icon={icon}
indicator={indicator} indicator={indicator}
title={title}
/> />
) ))}
)}
<div className="flex-grow-1" /> <div className="flex-grow-1" />
{railActions?.map(action => ( {railActions?.map(action => (
<RailActionElement key={action.key} action={action} /> <RailActionElement key={action.key} action={action} />
@@ -199,11 +214,13 @@ export const RailLayout = () => {
className={classNames('ide-rail-content', { hidden: isHistoryView })} className={classNames('ide-rail-content', { hidden: isHistoryView })}
> >
<Tab.Content> <Tab.Content>
{RAIL_TABS.filter(({ hide }) => !hide).map(({ key, component }) => ( {railTabs
<Tab.Pane eventKey={key} key={key}> .filter(({ hide }) => !hide)
{component} .map(({ key, component }) => (
</Tab.Pane> <Tab.Pane eventKey={key} key={key}>
))} {component}
</Tab.Pane>
))}
</Tab.Content> </Tab.Content>
</div> </div>
</Panel> </Panel>
@@ -234,26 +251,38 @@ const RailTab = ({
eventKey, eventKey,
open, open,
indicator, indicator,
title,
}: { }: {
icon: AvailableUnfilledIcon icon: AvailableUnfilledIcon
eventKey: string eventKey: string
open: boolean open: boolean
indicator?: ReactElement indicator?: ReactElement
title: string
}) => { }) => {
return ( return (
<NavLink <OLTooltip
eventKey={eventKey} id={`rail-tab-tooltip-${eventKey}`}
className={classNames('ide-rail-tab-link', { description={title}
'open-rail': open, overlayProps={{ delay: 0, placement: 'right' }}
})}
> >
{open ? ( <NavLink
<MaterialIcon className="ide-rail-tab-link-icon" type={icon} /> eventKey={eventKey}
) : ( className={classNames('ide-rail-tab-link', {
<MaterialIcon className="ide-rail-tab-link-icon" type={icon} unfilled /> 'open-rail': open,
)} })}
{indicator} >
</NavLink> {open ? (
<MaterialIcon className="ide-rail-tab-link-icon" type={icon} />
) : (
<MaterialIcon
className="ide-rail-tab-link-icon"
type={icon}
unfilled
/>
)}
{indicator}
</NavLink>
</OLTooltip>
) )
} }
@@ -274,25 +303,39 @@ const RailActionElement = ({ action }: { action: RailAction }) => {
if ('dropdown' in action) { if ('dropdown' in action) {
return ( return (
<Dropdown align="end" drop="end"> <Dropdown align="end" drop="end">
<DropdownToggle <OLTooltip
id="rail-help-dropdown-btn" id={`rail-dropdown-tooltip-${action.key}`}
className="ide-rail-tab-link ide-rail-tab-button ide-rail-tab-dropdown" description={action.title}
as="button" overlayProps={{ delay: 0, placement: 'right' }}
> >
{icon} <span>
</DropdownToggle> <DropdownToggle
id="rail-help-dropdown-btn"
className="ide-rail-tab-link ide-rail-tab-button ide-rail-tab-dropdown"
as="button"
>
{icon}
</DropdownToggle>
</span>
</OLTooltip>
{action.dropdown} {action.dropdown}
</Dropdown> </Dropdown>
) )
} else { } else {
return ( return (
<button <OLTooltip
onClick={onActionClick} id={`rail-tab-tooltip-${action.key}`}
className="ide-rail-tab-link ide-rail-tab-button" description={action.title}
type="button" overlayProps={{ delay: 0, placement: 'right' }}
> >
{icon} <button
</button> onClick={onActionClick}
className="ide-rail-tab-link ide-rail-tab-button"
type="button"
>
{icon}
</button>
</OLTooltip>
) )
} }
} }
+2
View File
@@ -673,6 +673,7 @@
"error": "Error", "error": "Error",
"error_assist": "Error Assist", "error_assist": "Error Assist",
"error_assist_to_help_fixing_latex_errors": "<0>Error Assist</0> for help fixing LaTeX errors", "error_assist_to_help_fixing_latex_errors": "<0>Error Assist</0> for help fixing LaTeX errors",
"error_log": "Error log",
"error_opening_document": "Error opening document", "error_opening_document": "Error opening document",
"error_opening_document_detail": "Sorry, something went wrong opening this document. Please try again.", "error_opening_document_detail": "Sorry, something went wrong opening this document. Please try again.",
"error_performing_request": "An error has occurred while performing your request.", "error_performing_request": "An error has occurred while performing your request.",
@@ -1849,6 +1850,7 @@
"reverse_x_sort_order": "Reverse __x__ sort order", "reverse_x_sort_order": "Reverse __x__ sort order",
"revert_pending_plan_change": "Revert scheduled plan change", "revert_pending_plan_change": "Revert scheduled plan change",
"review": "Review", "review": "Review",
"review_panel": "Review panel",
"review_panel_comments_and_track_changes": "Review panel – Comments & track changes", "review_panel_comments_and_track_changes": "Review panel – Comments & track changes",
"review_your_peers_work": "Review your peers’ work", "review_your_peers_work": "Review your peers’ work",
"reviewer": "Reviewer", "reviewer": "Reviewer",