| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- import { ReactElement, useCallback, useMemo, 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'
- import { useLayoutContext } from '@/shared/context/layout-context'
- import { FileTree } from '@/features/ide-react/components/file-tree'
- import { ErrorIndicator, ErrorPane } from './errors'
- type RailElement = {
- icon: AvailableUnfilledIcon
- key: string
- component: ReactElement
- indicator?: ReactElement
- }
- type RailActionLink = { key: string; icon: AvailableUnfilledIcon; href: string }
- type RailActionButton = {
- key: string
- icon: AvailableUnfilledIcon
- action: () => void
- }
- type RailAction = RailActionLink | RailActionButton
- 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: (
- <>
- {/* TODO: add panel for file outline */}
- <FileTree />
- </>
- ),
- },
- {
- 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: <ErrorPane />,
- indicator: <ErrorIndicator />,
- },
- ]
- export const RailLayout = () => {
- const [selectedTab, setSelectedTab] = useState<string | undefined>(
- RAIL_TABS[0]?.key
- )
- const { setLeftMenuShown } = useLayoutContext()
- const railActions: RailAction[] = useMemo(
- () => [
- { key: 'support', icon: 'help', href: '/learn' },
- {
- key: 'settings',
- icon: 'settings',
- action: () => setLeftMenuShown(true),
- },
- ],
- [setLeftMenuShown]
- )
- 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="d-flex flex-column ide-rail-tabs-nav"
- >
- {RAIL_TABS.map(({ icon, key, indicator }) => (
- <RailTab
- active={selectedTab === key}
- key={key}
- eventKey={key}
- icon={icon}
- indicator={indicator}
- />
- ))}
- <div className="flex-grow-1" />
- {railActions?.map(action => (
- <RailActionElement key={action.key} action={action} />
- ))}
- </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,
- indicator,
- }: {
- icon: AvailableUnfilledIcon
- eventKey: string
- active: boolean
- indicator?: ReactElement
- }) => {
- return (
- <NavLink eventKey={eventKey} className="ide-rail-tab-link">
- {active ? (
- <MaterialIcon className="ide-rail-tab-link-icon" type={icon} />
- ) : (
- <MaterialIcon className="ide-rail-tab-link-icon" type={icon} unfilled />
- )}
- {indicator}
- </NavLink>
- )
- }
- const RailActionElement = ({ action }: { action: RailAction }) => {
- const icon = (
- <MaterialIcon
- className="ide-rail-tab-link-icon"
- type={action.icon}
- unfilled
- />
- )
- const onActionClick = useCallback(() => {
- if ('action' in action) {
- action.action()
- }
- }, [action])
- if ('href' in action) {
- return (
- <a
- href={action.href}
- target="_blank"
- rel="noopener"
- className="ide-rail-tab-link"
- >
- {icon}
- </a>
- )
- } else {
- return (
- <button
- onClick={onActionClick}
- className="ide-rail-tab-link ide-rail-tab-button"
- type="button"
- >
- {icon}
- </button>
- )
- }
- }
|