diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index ad684dd166..1187aedbab 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/services/web/frontend/extracted-translations.json
@@ -847,6 +847,7 @@
"keep_personal_projects_separate": "",
"keep_your_account_safe_add_another_email": "",
"keybindings": "",
+ "keyboard_shortcuts": "",
"knowledge_base": "",
"labels_help_you_to_easily_reference_your_figures": "",
"labels_help_you_to_reference_your_tables": "",
diff --git a/services/web/frontend/js/features/ide-redesign/components/help/contact-us.tsx b/services/web/frontend/js/features/ide-redesign/components/help/contact-us.tsx
new file mode 100644
index 0000000000..cadb741aef
--- /dev/null
+++ b/services/web/frontend/js/features/ide-redesign/components/help/contact-us.tsx
@@ -0,0 +1,16 @@
+import { FC, useCallback } from 'react'
+import ContactUsModal from '../../../../../../modules/support/frontend/js/components/contact-us-modal'
+import { useRailContext } from '../../contexts/rail-context'
+import getMeta from '@/utils/meta'
+
+export const RailHelpContactUsModal: FC<{ show: boolean }> = ({ show }) => {
+ const { setActiveModal } = useRailContext()
+ const handleHide = useCallback(() => setActiveModal(null), [setActiveModal])
+ const showSupport = getMeta('ol-showSupport')
+ if (!showSupport) {
+ return null
+ }
+ return (
+
+ )
+}
diff --git a/services/web/frontend/js/features/ide-redesign/components/help/keyboard-shortcuts.tsx b/services/web/frontend/js/features/ide-redesign/components/help/keyboard-shortcuts.tsx
new file mode 100644
index 0000000000..dd764c46dc
--- /dev/null
+++ b/services/web/frontend/js/features/ide-redesign/components/help/keyboard-shortcuts.tsx
@@ -0,0 +1,19 @@
+import { FC } from 'react'
+import { useProjectContext } from '@/shared/context/project-context'
+import HotkeysModal from '@/features/hotkeys-modal/components/hotkeys-modal'
+import { isMac } from '@/shared/utils/os'
+import { useRailContext } from '../../contexts/rail-context'
+
+export const RailHelpShowHotkeysModal: FC<{ show: boolean }> = ({ show }) => {
+ const { features } = useProjectContext()
+ const { setActiveModal } = useRailContext()
+
+ return (
+ setActiveModal(null)}
+ isMac={isMac}
+ trackChangesVisible={features?.trackChangesVisible}
+ />
+ )
+}
diff --git a/services/web/frontend/js/features/ide-redesign/components/rail.tsx b/services/web/frontend/js/features/ide-redesign/components/rail.tsx
index 9fba7b352f..6e00e71738 100644
--- a/services/web/frontend/js/features/ide-redesign/components/rail.tsx
+++ b/services/web/frontend/js/features/ide-redesign/components/rail.tsx
@@ -6,7 +6,11 @@ import MaterialIcon, {
import { Panel } from 'react-resizable-panels'
import { useLayoutContext } from '@/shared/context/layout-context'
import { ErrorIndicator, ErrorPane } from './errors'
-import { RailTabKey, useRailContext } from '../contexts/rail-context'
+import {
+ RailModalKey,
+ RailTabKey,
+ useRailContext,
+} from '../contexts/rail-context'
import FileTreeOutlinePanel from './file-tree-outline-panel'
import { ChatIndicator, ChatPane } from './chat/chat'
import getMeta from '@/utils/meta'
@@ -16,6 +20,15 @@ import { useTranslation } from 'react-i18next'
import classNames from 'classnames'
import IntegrationsPanel from './integrations-panel/integrations-panel'
import OLButton from '@/features/ui/components/ol/ol-button'
+import {
+ Dropdown,
+ DropdownDivider,
+ DropdownItem,
+ DropdownMenu,
+ DropdownToggle,
+} from '@/features/ui/components/bootstrap-5/dropdown-menu'
+import { RailHelpShowHotkeysModal } from './help/keyboard-shortcuts'
+import { RailHelpContactUsModal } from './help/contact-us'
type RailElement = {
icon: AvailableUnfilledIcon
@@ -25,13 +38,17 @@ type RailElement = {
hide?: boolean
}
-type RailActionLink = { key: string; icon: AvailableUnfilledIcon; href: string }
type RailActionButton = {
key: string
icon: AvailableUnfilledIcon
action: () => void
}
-type RailAction = RailActionLink | RailActionButton
+type RailDropdown = {
+ key: string
+ icon: AvailableUnfilledIcon
+ dropdown: ReactElement
+}
+type RailAction = RailDropdown | RailActionButton
const RAIL_TABS: RailElement[] = [
{
@@ -64,9 +81,24 @@ const RAIL_TABS: RailElement[] = [
},
]
+const RAIL_MODALS: {
+ key: RailModalKey
+ modalComponentFunction: FC<{ show: boolean }>
+}[] = [
+ {
+ key: 'keyboard-shortcuts',
+ modalComponentFunction: RailHelpShowHotkeysModal,
+ },
+ {
+ key: 'contact-us',
+ modalComponentFunction: RailHelpContactUsModal,
+ },
+]
+
export const RailLayout = () => {
const { t } = useTranslation()
const {
+ activeModal,
selectedTab,
setSelectedTab,
isOpen,
@@ -77,11 +109,14 @@ export const RailLayout = () => {
togglePane,
setResizing,
} = useRailContext()
-
const { setLeftMenuShown } = useLayoutContext()
const railActions: RailAction[] = useMemo(
() => [
- { key: 'support', icon: 'help', href: '/learn' },
+ {
+ key: 'support',
+ icon: 'help',
+ dropdown: ,
+ },
{
key: 'settings',
icon: 'settings',
@@ -96,6 +131,12 @@ export const RailLayout = () => {
if (key === selectedTab) {
togglePane()
} else {
+ // HACK: Apparently the onSelect event is triggered with href attributes
+ // from DropdownItems
+ if (!RAIL_TABS.some(tab => !tab.hide && tab.key === key)) {
+ // Attempting to open a non-existent tab
+ return
+ }
// Change the selected tab and make sure it's open
setSelectedTab((key ?? 'file-tree') as RailTabKey)
setIsOpen(true)
@@ -168,6 +209,9 @@ export const RailLayout = () => {
tooltipWhenClosed={t('tooltip_show_panel')}
/>
+ {RAIL_MODALS.map(({ key, modalComponentFunction: Component }) => (
+
+ ))}
)
}
@@ -214,16 +258,18 @@ const RailActionElement = ({ action }: { action: RailAction }) => {
}
}, [action])
- if ('href' in action) {
+ if ('dropdown' in action) {
return (
-
- {icon}
-
+
+
+ {icon}
+
+ {action.dropdown}
+
)
} else {
return (
@@ -253,3 +299,44 @@ export const RailPanelHeader: FC<{ title: string }> = ({ title }) => {
)
}
+
+const RailHelpDropdown = () => {
+ const showSupport = getMeta('ol-showSupport')
+ const { t } = useTranslation()
+ const { setActiveModal } = useRailContext()
+ const openKeyboardShortcutsModal = useCallback(() => {
+ setActiveModal('keyboard-shortcuts')
+ }, [setActiveModal])
+ const openContactUsModal = useCallback(() => {
+ setActiveModal('contact-us')
+ }, [setActiveModal])
+ return (
+
+
+ {t('keyboard_shortcuts')}
+
+
+ {t('documentation')}
+
+
+ {showSupport && (
+
+ {t('contact_us')}
+
+ )}
+
+ {t('give_feedback')}
+
+
+ )
+}
diff --git a/services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx b/services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx
index 15e0df035c..fe622991ed 100644
--- a/services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx
+++ b/services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx
@@ -19,6 +19,8 @@ export type RailTabKey =
| 'chat'
| 'errors'
+export type RailModalKey = 'keyboard-shortcuts' | 'contact-us'
+
const RailContext = createContext<
| {
selectedTab: RailTabKey
@@ -31,6 +33,8 @@ const RailContext = createContext<
handlePaneCollapse: () => void
resizing: boolean
setResizing: Dispatch>
+ activeModal: RailModalKey | null
+ setActiveModal: Dispatch>
}
| undefined
>(undefined)
@@ -38,6 +42,14 @@ const RailContext = createContext<
export const RailProvider: FC = ({ children }) => {
const [isOpen, setIsOpen] = useState(true)
const [resizing, setResizing] = useState(false)
+ const [activeModal, setActiveModalInternal] = useState(
+ null
+ )
+ const setActiveModal: Dispatch> =
+ useCallback(modalKey => {
+ setActiveModalInternal(modalKey)
+ }, [])
+
const panelRef = useRef(null)
useCollapsiblePanel(isOpen, panelRef)
@@ -69,6 +81,8 @@ export const RailProvider: FC = ({ children }) => {
handlePaneCollapse,
resizing,
setResizing,
+ activeModal,
+ setActiveModal,
}),
[
selectedTab,
@@ -81,6 +95,8 @@ export const RailProvider: FC = ({ children }) => {
handlePaneCollapse,
resizing,
setResizing,
+ activeModal,
+ setActiveModal,
]
)
diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss
index 69a7e5390a..8410ae853a 100644
--- a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss
+++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss
@@ -133,3 +133,11 @@
gap: var(--spacing-02);
width: 40px;
}
+
+.ide-rail-tab-dropdown {
+ border: 0;
+
+ &.dropdown-toggle::after {
+ display: none;
+ }
+}
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index 8b615beeb0..19216795cc 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -1110,6 +1110,7 @@
"keep_your_account_safe_add_another_email": "Keep your account safe and make sure you don’t lose access to it by adding another email address.",
"keep_your_email_updated": "Keep your email updated so that you don’t lose access to your account and data.",
"keybindings": "Keybindings",
+ "keyboard_shortcuts": "Keyboard shortcuts",
"knowledge_base": "knowledge base",
"ko": "Korean",
"labels_help_you_to_easily_reference_your_figures": "Labels help you to easily reference your figures throughout your document. To reference a figure within the text, reference the label using the <0>\\ref{...}0> command. This makes it easy to reference figures without needing to manually remember the figure numbering. <1>Learn more1>",