From ea1fc5f74e129b5d87d9eeda4e021d3bf164cee0 Mon Sep 17 00:00:00 2001 From: Tim Down <158919+timdown@users.noreply.github.com> Date: Mon, 2 Oct 2023 10:35:02 +0100 Subject: [PATCH] React IDE page shell (#14988) * React IDE page shell * Set the maximum height of the symbol palette to 336px * Tidy export * Remove unnecessary destructuring * Update comment * Optimize toggle Co-authored-by: Alf Eaton * Change snap-to-collapse threshold to 5% * Synchronize left column width between history and editor views and remove duplication in ide-page * Replace resizer dots with SVG * Rermove unnecessary import and comment the remaining ones * Use block prepend to avoid duplication * Improve vertical content divider styling * Implement fixed width during container resize on left column * Change IDE page file extension * Refactor fixed-size panel into a hook and use for chat panel --------- Co-authored-by: Alf Eaton GitOrigin-RevId: aa881e48a2838a192b6f8f9e16e561f5cd706bd3 --- package-lock.json | 19 ++++ .../src/Features/Project/ProjectController.js | 7 +- .../web/app/views/project/editor/meta.pug | 1 + services/web/app/views/project/ide-react.pug | 21 +++++ .../web/frontend/extracted-translations.json | 5 ++ .../ide-react/components/ide-root.tsx | 22 +++++ .../ide-react/components/layout/ide-page.tsx | 11 +++ .../layout/layout-with-placeholders.tsx | 49 ++++++++++ .../components/layout/main-layout.tsx | 66 ++++++++++++++ .../layout/placeholder/placeholder-chat.tsx | 7 ++ .../placeholder-editor-and-pdf.tsx | 90 +++++++++++++++++++ .../placeholder-editor-main-content.tsx | 38 ++++++++ .../placeholder-editor-sidebar.tsx | 26 ++++++ .../layout/placeholder/placeholder-header.tsx | 42 +++++++++ .../placeholder/placeholder-history.tsx | 38 ++++++++ .../layout/two-column-main-content.tsx | 80 +++++++++++++++++ .../resize/horizontal-resize-handle.tsx | 19 ++++ .../components/resize/horizontal-toggler.tsx | 48 ++++++++++ .../resize/vertical-resize-handle.tsx | 13 +++ .../ide-react/hooks/use-collapsible-panel.ts | 19 ++++ .../ide-react/hooks/use-fixed-size-column.ts | 66 ++++++++++++++ services/web/frontend/js/pages/ide.jsx | 13 +++ .../stories/ide-page/layout.stories.tsx | 27 ++++++ .../web/frontend/stylesheets/app/editor.less | 1 + .../stylesheets/app/editor/ide-react.less | 90 +++++++++++++++++++ services/web/locales/en.json | 1 + services/web/package.json | 1 + 27 files changed, 819 insertions(+), 1 deletion(-) create mode 100644 services/web/app/views/project/ide-react.pug create mode 100644 services/web/frontend/js/features/ide-react/components/ide-root.tsx create mode 100644 services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx create mode 100644 services/web/frontend/js/features/ide-react/components/layout/layout-with-placeholders.tsx create mode 100644 services/web/frontend/js/features/ide-react/components/layout/main-layout.tsx create mode 100644 services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-chat.tsx create mode 100644 services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-editor-and-pdf.tsx create mode 100644 services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-editor-main-content.tsx create mode 100644 services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-editor-sidebar.tsx create mode 100644 services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-header.tsx create mode 100644 services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-history.tsx create mode 100644 services/web/frontend/js/features/ide-react/components/layout/two-column-main-content.tsx create mode 100644 services/web/frontend/js/features/ide-react/components/resize/horizontal-resize-handle.tsx create mode 100644 services/web/frontend/js/features/ide-react/components/resize/horizontal-toggler.tsx create mode 100644 services/web/frontend/js/features/ide-react/components/resize/vertical-resize-handle.tsx create mode 100644 services/web/frontend/js/features/ide-react/hooks/use-collapsible-panel.ts create mode 100644 services/web/frontend/js/features/ide-react/hooks/use-fixed-size-column.ts create mode 100644 services/web/frontend/js/pages/ide.jsx create mode 100644 services/web/frontend/stories/ide-page/layout.stories.tsx create mode 100644 services/web/frontend/stylesheets/app/editor/ide-react.less diff --git a/package-lock.json b/package-lock.json index d9d86baf71..a845afb2aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39625,6 +39625,16 @@ "node": ">=0.10.0" } }, + "node_modules/react-resizable-panels": { + "version": "0.0.55", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-0.0.55.tgz", + "integrity": "sha512-J/LTFzUEjJiqwSjVh8gjUXkQDA8MRPjARASfn++d2+KOgA+9UcRYUfE3QBJixer2vkk+ffQ4cq3QzWzzHgqYpQ==", + "dev": true, + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-resize-detector": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-7.1.2.tgz", @@ -48067,6 +48077,7 @@ "react-i18next": "^11.18.6", "react-linkify": "^1.0.0-alpha", "react-refresh": "^0.14.0", + "react-resizable-panels": "^0.0.55", "react2angular": "^4.0.6", "react2angular-shared-context": "^1.1.0", "requirejs": "^2.3.6", @@ -56670,6 +56681,7 @@ "react-i18next": "^11.18.6", "react-linkify": "^1.0.0-alpha", "react-refresh": "^0.14.0", + "react-resizable-panels": "^0.0.55", "react2angular": "^4.0.6", "react2angular-shared-context": "^1.1.0", "recurly": "^4.0.0", @@ -81001,6 +81013,13 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==" }, + "react-resizable-panels": { + "version": "0.0.55", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-0.0.55.tgz", + "integrity": "sha512-J/LTFzUEjJiqwSjVh8gjUXkQDA8MRPjARASfn++d2+KOgA+9UcRYUfE3QBJixer2vkk+ffQ4cq3QzWzzHgqYpQ==", + "dev": true, + "requires": {} + }, "react-resize-detector": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-7.1.2.tgz", diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index 65165f3780..4c3531654e 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -873,9 +873,14 @@ const ProjectController = { !Features.hasFeature('saas') || req.query?.personal_access_token === 'true' + const idePageReact = req.query?.['ide-page'] === 'react' + const template = detachRole === 'detached' - ? 'project/editor_detached' + ? // TODO: Create React version of detached page + 'project/editor_detached' + : idePageReact + ? 'project/ide-react' : 'project/editor' res.render(template, { diff --git a/services/web/app/views/project/editor/meta.pug b/services/web/app/views/project/editor/meta.pug index cb976e3fbe..9eb19aee12 100644 --- a/services/web/app/views/project/editor/meta.pug +++ b/services/web/app/views/project/editor/meta.pug @@ -41,6 +41,7 @@ meta(name="ol-hasTrackChangesFeature", data-type="boolean" content=hasTrackChang meta(name="ol-mathJax3Path" content=mathJax3Path) meta(name="ol-completedTutorials", data-type="json" content=user.completedTutorials) meta(name="ol-projectTags" data-type="json" content=projectTags) +meta(name="ol-idePageReact", data-type="boolean" content=idePageReact) - var fileActionI18n = ['edited', 'renamed', 'created', 'deleted'].reduce((acc, i) => {acc[i] = translate('file_action_' + i); return acc}, {}) meta(name="ol-fileActionI18n" data-type="json" content=fileActionI18n) diff --git a/services/web/app/views/project/ide-react.pug b/services/web/app/views/project/ide-react.pug new file mode 100644 index 0000000000..d1f6aa675c --- /dev/null +++ b/services/web/app/views/project/ide-react.pug @@ -0,0 +1,21 @@ +extends ../layout + +block vars + - var suppressNavbar = true + - var suppressFooter = true + - var suppressSkipToContent = true + - metadata.robotsNoindexNofollow = true + +block entrypointVar + - entrypoint = 'pages/ide' + +block content + main#ide-root + +block append meta + include ./editor/meta + +block prepend foot-scripts + each file in (useOpenTelemetry ? entrypointScripts("tracing") : []) + script(type="text/javascript", nonce=scriptNonce, src=file) + script(type="text/javascript", nonce=scriptNonce, src=(wsUrl || '/socket.io') + '/socket.io.js') diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 6e146d10ea..a98a8cb883 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -926,6 +926,7 @@ "resend_group_invite": "", "resend_managed_user_invite": "", "resending_confirmation_email": "", + "resize": "", "resolve": "", "resolved_comments": "", "restore_file": "", @@ -1178,6 +1179,10 @@ "toolbar_table_insert_table_lowercase": "", "toolbar_toggle_symbol_palette": "", "toolbar_undo": "", + "tooltip_hide_filetree": "", + "tooltip_hide_pdf": "", + "tooltip_show_filetree": "", + "tooltip_show_pdf": "", "total_per_month": "", "total_per_year": "", "total_with_subtotal_and_tax": "", diff --git a/services/web/frontend/js/features/ide-react/components/ide-root.tsx b/services/web/frontend/js/features/ide-react/components/ide-root.tsx new file mode 100644 index 0000000000..8619fb8601 --- /dev/null +++ b/services/web/frontend/js/features/ide-react/components/ide-root.tsx @@ -0,0 +1,22 @@ +import useWaitForI18n from '@/shared/hooks/use-wait-for-i18n' +import { GenericErrorBoundaryFallback } from '@/shared/components/generic-error-boundary-fallback' +import withErrorBoundary from '@/infrastructure/error-boundary' +import IdePage from '@/features/ide-react/components/layout/ide-page' + +function IdeRoot() { + // Check that we haven't inadvertently loaded Angular + // TODO: Remove this before rolling out this component to any users + if (typeof window.angular !== 'undefined') { + throw new Error('Angular detected. This page must not load Angular.') + } + + const { isReady } = useWaitForI18n() + + if (!isReady) { + return null + } + + return +} + +export default withErrorBoundary(IdeRoot, GenericErrorBoundaryFallback) diff --git a/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx b/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx new file mode 100644 index 0000000000..c9d24bf061 --- /dev/null +++ b/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx @@ -0,0 +1,11 @@ +import LayoutWithPlaceholders from '@/features/ide-react/components/layout/layout-with-placeholders' + +// This is filled with placeholder content while the real content is migrated +// away from Angular +export default function IdePage() { + return ( +
+ +
+ ) +} diff --git a/services/web/frontend/js/features/ide-react/components/layout/layout-with-placeholders.tsx b/services/web/frontend/js/features/ide-react/components/layout/layout-with-placeholders.tsx new file mode 100644 index 0000000000..f3f2d175f8 --- /dev/null +++ b/services/web/frontend/js/features/ide-react/components/layout/layout-with-placeholders.tsx @@ -0,0 +1,49 @@ +import { useState } from 'react' +import PlaceholderHeader from '@/features/ide-react/components/layout/placeholder/placeholder-header' +import PlaceholderChat from '@/features/ide-react/components/layout/placeholder/placeholder-chat' +import PlaceholderHistory from '@/features/ide-react/components/layout/placeholder/placeholder-history' +import PlaceholderEditorMainContent from '@/features/ide-react/components/layout/placeholder/placeholder-editor-main-content' +import MainLayout from '@/features/ide-react/components/layout/main-layout' + +export default function LayoutWithPlaceholders({ + shouldPersistLayout, +}: { + shouldPersistLayout: boolean +}) { + const [chatIsOpen, setChatIsOpen] = useState(false) + const [historyIsOpen, setHistoryIsOpen] = useState(false) + const [leftColumnDefaultSize, setLeftColumnDefaultSize] = useState(20) + + const headerContent = ( + + ) + const chatContent = + const mainContent = historyIsOpen ? ( + + ) : ( + + ) + + return ( + + ) +} diff --git a/services/web/frontend/js/features/ide-react/components/layout/main-layout.tsx b/services/web/frontend/js/features/ide-react/components/layout/main-layout.tsx new file mode 100644 index 0000000000..045d0f27cf --- /dev/null +++ b/services/web/frontend/js/features/ide-react/components/layout/main-layout.tsx @@ -0,0 +1,66 @@ +import { Panel, PanelGroup } from 'react-resizable-panels' +import { ReactNode } from 'react' +import { HorizontalResizeHandle } from '../resize/horizontal-resize-handle' +import useFixedSizeColumn from '@/features/ide-react/hooks/use-fixed-size-column' +import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel' + +const CHAT_DEFAULT_SIZE = 20 + +type PageProps = { + headerContent: ReactNode + chatContent: ReactNode + mainContent: ReactNode + chatIsOpen: boolean + shouldPersistLayout: boolean +} + +// The main area below the header is split into two: the main content and chat. +// The reason for not splitting the left column containing the file tree and +// outline here is that the history view has its own file tree, so it is more +// convenient to replace the whole of the main content when in history view. +export default function MainLayout({ + headerContent, + chatContent, + mainContent, + chatIsOpen, + shouldPersistLayout, +}: PageProps) { + const { fixedPanelRef: chatPanelRef, handleLayout } = useFixedSizeColumn( + CHAT_DEFAULT_SIZE, + chatIsOpen + ) + + useCollapsiblePanel(chatIsOpen, chatPanelRef) + + return ( +
+ {headerContent} +
+ + + {mainContent} + + {chatIsOpen ? ( + <> + + + {chatContent} + + + ) : null} + +
+
+ ) +} diff --git a/services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-chat.tsx b/services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-chat.tsx new file mode 100644 index 0000000000..edb7f7a461 --- /dev/null +++ b/services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-chat.tsx @@ -0,0 +1,7 @@ +import React from 'react' + +export default function PlaceholderChat() { + return ( + + ) +} diff --git a/services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-editor-and-pdf.tsx b/services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-editor-and-pdf.tsx new file mode 100644 index 0000000000..00c4ef36ae --- /dev/null +++ b/services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-editor-and-pdf.tsx @@ -0,0 +1,90 @@ +import React, { useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { + ImperativePanelHandle, + Panel, + PanelGroup, +} from 'react-resizable-panels' +import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/horizontal-resize-handle' +import { HorizontalToggler } from '@/features/ide-react/components/resize/horizontal-toggler' +import { VerticalResizeHandle } from '@/features/ide-react/components/resize/vertical-resize-handle' +import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel' + +type PlaceholderEditorAndPdfProps = { + shouldPersistLayout?: boolean +} + +export default function PlaceholderEditorAndPdf({ + shouldPersistLayout = false, +}: PlaceholderEditorAndPdfProps) { + const { t } = useTranslation() + const [pdfIsOpen, setPdfIsOpen] = useState(false) + const [symbolPaletteIsOpen, setSymbolPaletteIsOpen] = useState(false) + + const pdfPanelRef = useRef(null) + useCollapsiblePanel(pdfIsOpen, pdfPanelRef) + + return ( + + + + + Editor placeholder +
+ +
+ {symbolPaletteIsOpen ? ( + <> + + +
+ Symbol palette placeholder +
+
+ + ) : null} +
+
+ + setPdfIsOpen(pdfIsOpen)} + tooltipWhenOpen={t('tooltip_hide_pdf')} + tooltipWhenClosed={t('tooltip_show_pdf')} + /> + + setPdfIsOpen(!collapsed)} + > + PDF placeholder + +
+ ) +} diff --git a/services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-editor-main-content.tsx b/services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-editor-main-content.tsx new file mode 100644 index 0000000000..0e1d39beec --- /dev/null +++ b/services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-editor-main-content.tsx @@ -0,0 +1,38 @@ +import React, { useState } from 'react' +import TwoColumnMainContent from '@/features/ide-react/components/layout/two-column-main-content' +import PlaceholderEditorAndPdf from '@/features/ide-react/components/layout/placeholder/placeholder-editor-and-pdf' +import PlaceholderEditorSidebar from '@/features/ide-react/components/layout/placeholder/placeholder-editor-sidebar' + +type PlaceholderEditorMainContentProps = { + shouldPersistLayout: boolean + leftColumnDefaultSize: number + setLeftColumnDefaultSize: React.Dispatch> +} + +export default function PlaceholderEditorMainContent({ + shouldPersistLayout, + leftColumnDefaultSize, + setLeftColumnDefaultSize, +}: PlaceholderEditorMainContentProps) { + const [leftColumnIsOpen, setLeftColumnIsOpen] = useState(true) + + const leftColumnContent = ( + + ) + const rightColumnContent = ( + + ) + + return ( + + ) +} diff --git a/services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-editor-sidebar.tsx b/services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-editor-sidebar.tsx new file mode 100644 index 0000000000..c6f91c6691 --- /dev/null +++ b/services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-editor-sidebar.tsx @@ -0,0 +1,26 @@ +import React from 'react' +import { Panel, PanelGroup } from 'react-resizable-panels' +import { VerticalResizeHandle } from '@/features/ide-react/components/resize/vertical-resize-handle' + +type PlaceholderHeaderProps = { + shouldPersistLayout: boolean +} + +export default function PlaceholderEditorSidebar({ + shouldPersistLayout, +}: PlaceholderHeaderProps) { + return ( + + ) +} diff --git a/services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-header.tsx b/services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-header.tsx new file mode 100644 index 0000000000..88ca322af0 --- /dev/null +++ b/services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-header.tsx @@ -0,0 +1,42 @@ +import React from 'react' +import ChatToggleButton from '@/features/editor-navigation-toolbar/components/chat-toggle-button' +import HistoryToggleButton from '@/features/editor-navigation-toolbar/components/history-toggle-button' + +type PlaceholderHeaderProps = { + chatIsOpen: boolean + setChatIsOpen: (chatIsOpen: boolean) => void + historyIsOpen: boolean + setHistoryIsOpen: (chatIsOpen: boolean) => void +} + +export default function PlaceholderHeader({ + chatIsOpen, + setChatIsOpen, + historyIsOpen, + setHistoryIsOpen, +}: PlaceholderHeaderProps) { + function toggleChatOpen() { + setChatIsOpen(!chatIsOpen) + } + + function toggleHistoryOpen() { + setHistoryIsOpen(!historyIsOpen) + } + + return ( +
+
Header placeholder
+
+ + +
+
+ ) +} diff --git a/services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-history.tsx b/services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-history.tsx new file mode 100644 index 0000000000..ff6f3ba4e6 --- /dev/null +++ b/services/web/frontend/js/features/ide-react/components/layout/placeholder/placeholder-history.tsx @@ -0,0 +1,38 @@ +import React, { useState } from 'react' +import TwoColumnMainContent from '@/features/ide-react/components/layout/two-column-main-content' + +type PlaceholderHistoryProps = { + shouldPersistLayout: boolean + leftColumnDefaultSize: number + setLeftColumnDefaultSize: React.Dispatch> +} + +export default function PlaceholderHistory({ + shouldPersistLayout, + leftColumnDefaultSize, + setLeftColumnDefaultSize, +}: PlaceholderHistoryProps) { + const [leftColumnIsOpen, setLeftColumnIsOpen] = useState(true) + + const leftColumnContent = ( + + ) + const rightColumnContent = ( +
History document diff viewer and versions list placeholder
+ ) + + return ( + + ) +} diff --git a/services/web/frontend/js/features/ide-react/components/layout/two-column-main-content.tsx b/services/web/frontend/js/features/ide-react/components/layout/two-column-main-content.tsx new file mode 100644 index 0000000000..8deb584dd0 --- /dev/null +++ b/services/web/frontend/js/features/ide-react/components/layout/two-column-main-content.tsx @@ -0,0 +1,80 @@ +import React, { ReactNode, useEffect } from 'react' +import { Panel, PanelGroup } from 'react-resizable-panels' +import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/horizontal-resize-handle' +import { useTranslation } from 'react-i18next' +import { HorizontalToggler } from '@/features/ide-react/components/resize/horizontal-toggler' +import useFixedSizeColumn from '@/features/ide-react/hooks/use-fixed-size-column' +import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel' + +type TwoColumnMainContentProps = { + leftColumnId: string + leftColumnContent: ReactNode + leftColumnDefaultSize: number + setLeftColumnDefaultSize: React.Dispatch> + rightColumnContent: ReactNode + leftColumnIsOpen: boolean + setLeftColumnIsOpen: ( + leftColumnIsOpen: TwoColumnMainContentProps['leftColumnIsOpen'] + ) => void + shouldPersistLayout?: boolean +} + +export default function TwoColumnMainContent({ + leftColumnId, + leftColumnContent, + leftColumnDefaultSize, + setLeftColumnDefaultSize, + rightColumnContent, + leftColumnIsOpen, + setLeftColumnIsOpen, + shouldPersistLayout = false, +}: TwoColumnMainContentProps) { + const { t } = useTranslation() + + const { + fixedPanelRef: leftColumnPanelRef, + fixedPanelWidthRef: leftColumnWidthRef, + handleLayout, + } = useFixedSizeColumn(leftColumnDefaultSize, leftColumnIsOpen) + + useCollapsiblePanel(leftColumnIsOpen, leftColumnPanelRef) + + // Update the left column default size on unmount rather than doing it on + // every resize, which causes ResizeObserver errors + useEffect(() => { + if (leftColumnWidthRef.current) { + setLeftColumnDefaultSize(leftColumnWidthRef.current.size) + } + }, [leftColumnWidthRef, setLeftColumnDefaultSize]) + + return ( + + setLeftColumnIsOpen(!collapsed)} + > + {leftColumnIsOpen ? leftColumnContent : null} + + + setLeftColumnIsOpen(isOpen)} + tooltipWhenOpen={t('tooltip_hide_filetree')} + tooltipWhenClosed={t('tooltip_show_filetree')} + /> + + {rightColumnContent} + + ) +} diff --git a/services/web/frontend/js/features/ide-react/components/resize/horizontal-resize-handle.tsx b/services/web/frontend/js/features/ide-react/components/resize/horizontal-resize-handle.tsx new file mode 100644 index 0000000000..776fdefc56 --- /dev/null +++ b/services/web/frontend/js/features/ide-react/components/resize/horizontal-resize-handle.tsx @@ -0,0 +1,19 @@ +import { PanelResizeHandle } from 'react-resizable-panels' +import { FC } from 'react' +import { useTranslation } from 'react-i18next' +import { PanelResizeHandleProps } from 'react-resizable-panels/dist/declarations/src/PanelResizeHandle' + +export const HorizontalResizeHandle: FC = ({ + children, + ...props +}) => { + const { t } = useTranslation() + + return ( + +
+ {children} +
+
+ ) +} diff --git a/services/web/frontend/js/features/ide-react/components/resize/horizontal-toggler.tsx b/services/web/frontend/js/features/ide-react/components/resize/horizontal-toggler.tsx new file mode 100644 index 0000000000..0ca790f1f5 --- /dev/null +++ b/services/web/frontend/js/features/ide-react/components/resize/horizontal-toggler.tsx @@ -0,0 +1,48 @@ +import classNames from 'classnames' +import Tooltip from '@/shared/components/tooltip' + +type HorizontalTogglerType = 'west' | 'east' + +type HorizontalTogglerProps = { + id: string + isOpen: boolean + setIsOpen: (isClosed: boolean) => void + togglerType: HorizontalTogglerType + tooltipWhenOpen: string + tooltipWhenClosed: string +} + +export function HorizontalToggler({ + id, + isOpen, + setIsOpen, + togglerType, + tooltipWhenOpen, + tooltipWhenClosed, +}: HorizontalTogglerProps) { + const description = isOpen ? tooltipWhenOpen : tooltipWhenClosed + + return ( + +