Merge pull request #22787 from overleaf/mj-ide-rail

[web] Create rail tabbed layout

GitOrigin-RevId: be54a224087aad8e6e2762d9c26463e37aecd9aa
This commit is contained in:
Mathias Jakobsen
2025-01-17 09:04:45 +00:00
committed by Copybot
parent 539e96c62b
commit 849275c4b8
4 changed files with 168 additions and 14 deletions
@@ -3,15 +3,13 @@ import classNames from 'classnames'
import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/horizontal-resize-handle'
import PdfPreview from '@/features/pdf-preview/components/pdf-preview'
import { Editor } from './editor'
import { RailLayout } from './rail'
export default function MainLayout() {
return (
<div className="ide-redesign-main">
<div className="ide-skeleton-block">Toolbar</div>
<div className="ide-redesign-body">
<div className="ide-skeleton-block ide-redesign-full-height">
Left menu
</div>
<PanelGroup
autoSaveId="ide-redesign-outer-layout"
direction="horizontal"
@@ -19,17 +17,7 @@ export default function MainLayout() {
'ide-panel-group-resizing': false,
})}
>
<Panel
id="ide-redesign-sidebar-panel"
order={1}
defaultSize={15}
minSize={5}
maxSize={80}
>
<div className="ide-skeleton-block ide-redesign-full-height">
Side bar
</div>
</Panel>
<RailLayout />
<HorizontalResizeHandle
resizable
hitAreaMargins={{ coarse: 0, fine: 0 }}
@@ -0,0 +1,108 @@
import { ReactElement, useCallback, useState } from 'react'
import { Nav, NavLink, Tab, TabContainer } from 'react-bootstrap-5'
import MaterialIcon, {
AvailableUnfilledIcon,
} from '@/shared/components/material-icon'
import { Panel } from 'react-resizable-panels'
type RailElement = {
icon: AvailableUnfilledIcon
key: string
component: ReactElement
}
const RAIL_TABS: RailElement[] = [
// NOTE: The file tree **MUST** be the first (i.e. default) tab in the list
// since the file tree is responsible for opening the initial document.
{
key: 'file-tree',
icon: 'description',
component: <>File tree</>,
},
{
key: 'integrations',
icon: 'integration_instructions',
component: <>Integrations</>,
},
{
key: 'review-panel',
icon: 'rate_review',
component: <>Review panel</>,
},
{
key: 'chat',
icon: 'forum',
component: <>Chat</>,
},
{
key: 'errors',
icon: 'report',
component: <>Errors</>,
},
]
export const RailLayout = () => {
const [selectedTab, setSelectedTab] = useState<string | undefined>(
RAIL_TABS[0]?.key
)
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}
defaultActiveKey={selectedTab}
onSelect={useCallback(key => setSelectedTab(key ?? undefined), [])}
id="ide-rail-tabs"
>
<div className="ide-rail">
<Nav defaultActiveKey={RAIL_TABS[0]?.key} className="flex-column">
{RAIL_TABS.map(({ icon, key }) => (
<RailTab
active={selectedTab === key}
key={key}
eventKey={key}
icon={icon}
/>
))}
</Nav>
</div>
<Panel
id="ide-redesign-sidebar-panel"
order={1}
defaultSize={15}
minSize={5}
maxSize={80}
>
<div className="ide-rail-content">
<Tab.Content>
{RAIL_TABS.map(({ key, component }) => (
<Tab.Pane eventKey={key} key={key}>
{component}
</Tab.Pane>
))}
</Tab.Content>
</div>
</Panel>
</TabContainer>
)
}
const RailTab = ({
icon,
eventKey,
active,
}: {
icon: AvailableUnfilledIcon
eventKey: string
active: boolean
}) => {
return (
<NavLink eventKey={eventKey} className="ide-rail-tab-link">
<MaterialIcon
className="ide-rail-tab-link-icon"
type={icon}
unfilled={!active}
/>
</NavLink>
)
}
@@ -7,6 +7,7 @@
@import 'sidebar-v2-dash-pane';
@import 'editor/ide';
@import 'editor/ide-redesign';
@import 'editor/rail';
@import 'editor/toolbar';
@import 'editor/online-users';
@import 'editor/hotkeys';
@@ -0,0 +1,57 @@
:root {
--ide-rail-background: #fff;
--ide-rail-color: var(--neutral-90);
--ide-rail-link-background: #fff;
--ide-rail-link-active-background: var(--neutral-10);
--ide-rail-link-active-indicator-background: var(--neutral-90);
}
.ide-rail-tab-link {
border-radius: 12px;
display: block;
height: 48px;
width: 48px;
text-align: center;
padding: 0;
color: var(--ide-rail-color);
background-color: var(--ide-rail-link-background);
position: relative;
overflow-y: hidden;
.ide-rail-tab-link-icon {
line-height: 48px;
font-size: 20px;
}
&.active {
color: var(--ide-rail-color);
background-color: var(--ide-rail-link-active-background);
&::after {
$indicator-height: 3px;
border-radius: 12px;
content: '';
position: absolute;
bottom: -$indicator-height;
left: 25%;
box-sizing: border-box;
width: 50%;
height: $indicator-height * 2;
border: $indicator-height solid
var(--ide-rail-link-active-indicator-background);
background-color: var(--ide-rail-link-active-indicator-background);
}
}
}
.ide-rail {
height: 100%;
padding: var(--spacing-02);
background: var(--ide-rail-background);
}
.ide-rail-content {
height: 100%;
border: 1px solid var(--border-divider);
}