Merge pull request #23384 from overleaf/mj-ide-collapsible-rail
[web] Make panels collapsible in editor redesign GitOrigin-RevId: bc979e3b6028666d3e0aa751e341d838615c1aac
This commit is contained in:
@@ -1737,8 +1737,10 @@
|
||||
"toolbar_undo": "",
|
||||
"toolbar_undo_redo_actions": "",
|
||||
"tooltip_hide_filetree": "",
|
||||
"tooltip_hide_panel": "",
|
||||
"tooltip_hide_pdf": "",
|
||||
"tooltip_show_filetree": "",
|
||||
"tooltip_show_panel": "",
|
||||
"tooltip_show_pdf": "",
|
||||
"total_due_today": "",
|
||||
"total_per_month": "",
|
||||
|
||||
+10
-1
@@ -2,8 +2,11 @@ import { Panel, PanelGroup } from 'react-resizable-panels'
|
||||
import { FileTree } from '@/features/ide-react/components/file-tree'
|
||||
import { OutlineContainer } from '@/features/outline/components/outline-container'
|
||||
import { VerticalResizeHandle } from '@/features/ide-react/components/resize/vertical-resize-handle'
|
||||
import { useOutlinePane } from '@/features/ide-react/hooks/use-outline-pane'
|
||||
|
||||
function FileTreeOutlinePanel() {
|
||||
const { outlineEnabled, outlinePanelRef } = useOutlinePane()
|
||||
|
||||
return (
|
||||
<PanelGroup
|
||||
autoSaveId="ide-redesign-file-tree-outline"
|
||||
@@ -17,12 +20,18 @@ function FileTreeOutlinePanel() {
|
||||
>
|
||||
<FileTree />
|
||||
</Panel>
|
||||
<VerticalResizeHandle hitAreaMargins={{ coarse: 0, fine: 0 }} />
|
||||
<VerticalResizeHandle
|
||||
hitAreaMargins={{ coarse: 0, fine: 0 }}
|
||||
disabled={!outlineEnabled}
|
||||
/>
|
||||
<Panel
|
||||
defaultSize={50}
|
||||
maxSize={75}
|
||||
id="ide-redesign-file-outline"
|
||||
order={2}
|
||||
collapsible
|
||||
ref={outlinePanelRef}
|
||||
style={{ minHeight: 36 }} // keep the header visible
|
||||
>
|
||||
<OutlineContainer />
|
||||
</Panel>
|
||||
|
||||
@@ -5,8 +5,20 @@ import PdfPreview from '@/features/pdf-preview/components/pdf-preview'
|
||||
import { Editor } from './editor'
|
||||
import { RailLayout } from './rail'
|
||||
import { Toolbar } from './toolbar/toolbar'
|
||||
import { HorizontalToggler } from '@/features/ide-react/components/resize/horizontal-toggler'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { usePdfPane } from '../hooks/use-pdf-pane'
|
||||
|
||||
export default function MainLayout() {
|
||||
const {
|
||||
isOpen: isPdfOpen,
|
||||
setIsOpen: setIsPdfOpen,
|
||||
panelRef: pdfPanelRef,
|
||||
handlePaneCollapse: handlePdfPaneCollapse,
|
||||
handlePaneExpand: handlePdfPaneExpand,
|
||||
togglePane: togglePdfPane,
|
||||
} = usePdfPane()
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className="ide-redesign-main">
|
||||
<Toolbar />
|
||||
@@ -19,10 +31,6 @@ export default function MainLayout() {
|
||||
})}
|
||||
>
|
||||
<RailLayout />
|
||||
<HorizontalResizeHandle
|
||||
resizable
|
||||
hitAreaMargins={{ coarse: 0, fine: 0 }}
|
||||
/>
|
||||
<Panel id="ide-redesign-editor-panel" order={2}>
|
||||
<div className="ide-redesign-editor-container">
|
||||
<Editor />
|
||||
@@ -30,12 +38,26 @@ export default function MainLayout() {
|
||||
</Panel>
|
||||
<HorizontalResizeHandle
|
||||
resizable
|
||||
onDoubleClick={togglePdfPane}
|
||||
hitAreaMargins={{ coarse: 0, fine: 0 }}
|
||||
/>
|
||||
>
|
||||
<HorizontalToggler
|
||||
id="ide-redesign-pdf-panel"
|
||||
togglerType="east"
|
||||
isOpen={isPdfOpen}
|
||||
setIsOpen={setIsPdfOpen}
|
||||
tooltipWhenOpen={t('tooltip_hide_pdf')}
|
||||
tooltipWhenClosed={t('tooltip_show_pdf')}
|
||||
/>
|
||||
</HorizontalResizeHandle>
|
||||
<Panel
|
||||
collapsible
|
||||
className="ide-redesign-pdf-container"
|
||||
id="ide-redesign-pdf-panel"
|
||||
order={2}
|
||||
ref={pdfPanelRef}
|
||||
onExpand={handlePdfPaneExpand}
|
||||
onCollapse={handlePdfPaneCollapse}
|
||||
>
|
||||
<PdfPreview />
|
||||
</Panel>
|
||||
|
||||
@@ -10,6 +10,11 @@ import { RailTabKey, useRailTabContext } from '../contexts/rail-tab-context'
|
||||
import FileTreeOutlinePanel from './file-tree-outline-panel'
|
||||
import { ChatIndicator, ChatPane } from './chat'
|
||||
import getMeta from '@/utils/meta'
|
||||
import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/horizontal-resize-handle'
|
||||
import { HorizontalToggler } from '@/features/ide-react/components/resize/horizontal-toggler'
|
||||
import { useRail } from '../hooks/use-rail'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import classNames from 'classnames'
|
||||
|
||||
type RailElement = {
|
||||
icon: AvailableUnfilledIcon
|
||||
@@ -59,6 +64,15 @@ const RAIL_TABS: RailElement[] = [
|
||||
]
|
||||
|
||||
export const RailLayout = () => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
panelRef,
|
||||
handlePaneCollapse,
|
||||
handlePaneExpand,
|
||||
togglePane,
|
||||
} = useRail()
|
||||
const { selectedTab, setSelectedTab } = useRailTabContext()
|
||||
|
||||
const { setLeftMenuShown } = useLayoutContext()
|
||||
@@ -74,16 +88,26 @@ export const RailLayout = () => {
|
||||
[setLeftMenuShown]
|
||||
)
|
||||
|
||||
const onTabSelect = useCallback(
|
||||
(key: string | null) => {
|
||||
if (key === selectedTab) {
|
||||
togglePane()
|
||||
} else {
|
||||
// Change the selected tab and make sure it's open
|
||||
setSelectedTab((key ?? 'file-tree') as RailTabKey)
|
||||
setIsOpen(true)
|
||||
}
|
||||
},
|
||||
[setSelectedTab, selectedTab, setIsOpen, togglePane]
|
||||
)
|
||||
|
||||
return (
|
||||
<TabContainer
|
||||
mountOnEnter // Only render when necessary (so that we can lazy load tab content)
|
||||
unmountOnExit={false} // TODO: Should we unmount the tabs when they're not used?
|
||||
transition={false}
|
||||
activeKey={selectedTab}
|
||||
onSelect={useCallback(
|
||||
key => setSelectedTab(key ?? undefined),
|
||||
[setSelectedTab]
|
||||
)}
|
||||
onSelect={onTabSelect}
|
||||
id="ide-rail-tabs"
|
||||
>
|
||||
<div className="ide-rail">
|
||||
@@ -94,7 +118,7 @@ export const RailLayout = () => {
|
||||
{RAIL_TABS.filter(({ hide }) => !hide).map(
|
||||
({ icon, key, indicator }) => (
|
||||
<RailTab
|
||||
active={selectedTab === key}
|
||||
open={isOpen && selectedTab === key}
|
||||
key={key}
|
||||
eventKey={key}
|
||||
icon={icon}
|
||||
@@ -114,6 +138,10 @@ export const RailLayout = () => {
|
||||
defaultSize={15}
|
||||
minSize={5}
|
||||
maxSize={80}
|
||||
ref={panelRef}
|
||||
collapsible
|
||||
onCollapse={handlePaneCollapse}
|
||||
onExpand={handlePaneExpand}
|
||||
>
|
||||
<div className="ide-rail-content">
|
||||
<Tab.Content>
|
||||
@@ -125,6 +153,20 @@ export const RailLayout = () => {
|
||||
</Tab.Content>
|
||||
</div>
|
||||
</Panel>
|
||||
<HorizontalResizeHandle
|
||||
resizable
|
||||
hitAreaMargins={{ coarse: 0, fine: 0 }}
|
||||
onDoubleClick={togglePane}
|
||||
>
|
||||
<HorizontalToggler
|
||||
id="ide-redesign-sidebar-panel"
|
||||
togglerType="west"
|
||||
isOpen={isOpen}
|
||||
setIsOpen={setIsOpen}
|
||||
tooltipWhenOpen={t('tooltip_hide_panel')}
|
||||
tooltipWhenClosed={t('tooltip_show_panel')}
|
||||
/>
|
||||
</HorizontalResizeHandle>
|
||||
</TabContainer>
|
||||
)
|
||||
}
|
||||
@@ -132,17 +174,22 @@ export const RailLayout = () => {
|
||||
const RailTab = ({
|
||||
icon,
|
||||
eventKey,
|
||||
active,
|
||||
open,
|
||||
indicator,
|
||||
}: {
|
||||
icon: AvailableUnfilledIcon
|
||||
eventKey: string
|
||||
active: boolean
|
||||
open: boolean
|
||||
indicator?: ReactElement
|
||||
}) => {
|
||||
return (
|
||||
<NavLink eventKey={eventKey} className="ide-rail-tab-link">
|
||||
{active ? (
|
||||
<NavLink
|
||||
eventKey={eventKey}
|
||||
className={classNames('ide-rail-tab-link', {
|
||||
'open-rail': open,
|
||||
})}
|
||||
>
|
||||
{open ? (
|
||||
<MaterialIcon className="ide-rail-tab-link-icon" type={icon} />
|
||||
) : (
|
||||
<MaterialIcon className="ide-rail-tab-link-icon" type={icon} unfilled />
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { useRail } from './use-rail'
|
||||
|
||||
export const usePdfPane = () => {
|
||||
// FIXME: This is temporary, to avoid clashing with the existing usePdfPane
|
||||
// which uses the layout context. That's the correct approach.
|
||||
return useRail()
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel'
|
||||
import { ImperativePanelHandle } from 'react-resizable-panels'
|
||||
|
||||
export const useRail = () => {
|
||||
const [isOpen, setIsOpen] = useState(true)
|
||||
const [resizing, setResizing] = useState(false)
|
||||
const panelRef = useRef<ImperativePanelHandle>(null)
|
||||
useCollapsiblePanel(isOpen, panelRef)
|
||||
|
||||
const togglePane = useCallback(() => {
|
||||
setIsOpen(value => !value)
|
||||
}, [])
|
||||
|
||||
const handlePaneExpand = useCallback(() => {
|
||||
setIsOpen(true)
|
||||
}, [])
|
||||
|
||||
const handlePaneCollapse = useCallback(() => {
|
||||
setIsOpen(false)
|
||||
}, [])
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
panelRef,
|
||||
togglePane,
|
||||
handlePaneExpand,
|
||||
handlePaneCollapse,
|
||||
resizing,
|
||||
setResizing,
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
&.open-rail {
|
||||
color: var(--ide-rail-color);
|
||||
background-color: var(--ide-rail-link-active-background);
|
||||
|
||||
|
||||
@@ -2265,8 +2265,10 @@
|
||||
"toolbar_undo": "Undo",
|
||||
"toolbar_undo_redo_actions": "Undo/Redo actions",
|
||||
"tooltip_hide_filetree": "Click to hide the file tree",
|
||||
"tooltip_hide_panel": "Click to hide the panel",
|
||||
"tooltip_hide_pdf": "Click to hide the PDF",
|
||||
"tooltip_show_filetree": "Click to show the file tree",
|
||||
"tooltip_show_panel": "Click to show the panel",
|
||||
"tooltip_show_pdf": "Click to show the PDF",
|
||||
"top_pick": "Top pick",
|
||||
"total": "Total",
|
||||
|
||||
Reference in New Issue
Block a user