import { sendSearchEvent } from '@/features/event-tracking/search-events' import useEventListener from '@/shared/hooks/use-event-listener' import usePersistedState from '@/shared/hooks/use-persisted-state' import { isMac } from '@/shared/utils/os' import { createContext, Dispatch, FC, SetStateAction, useCallback, useContext, useLayoutEffect, useMemo, useRef, useState, } from 'react' import { ImperativePanelHandle } from 'react-resizable-panels' export type RailTabKey = | 'file-tree' | 'integrations' | 'review-panel' | 'chat' | 'errors' | 'full-project-search' export type RailModalKey = 'keyboard-shortcuts' | 'contact-us' | 'dictionary' const RailContext = createContext< | { selectedTab: RailTabKey isOpen: boolean setIsOpen: Dispatch> panelRef: React.RefObject togglePane: () => void handlePaneExpand: () => void handlePaneCollapse: () => void resizing: boolean setResizing: Dispatch> activeModal: RailModalKey | null setActiveModal: Dispatch> openTab: (tab: RailTabKey) => void } | undefined >(undefined) export const RailProvider: FC = ({ children }) => { const [isOpen, setIsOpen] = usePersistedState('rail-is-open', true) const [resizing, setResizing] = useState(false) const [activeModal, setActiveModalInternal] = useState( null ) const setActiveModal: Dispatch> = useCallback(modalKey => { setActiveModalInternal(modalKey) }, []) const panelRef = useRef(null) const togglePane = useCallback(() => { setIsOpen(value => !value) }, [setIsOpen]) const handlePaneExpand = useCallback(() => { setIsOpen(true) }, [setIsOpen]) const handlePaneCollapse = useCallback(() => { setIsOpen(false) }, [setIsOpen]) const [selectedTab, setSelectedTab] = usePersistedState( 'selected-rail-tab', 'file-tree' ) // Keep the panel collapse/expanded state in sync with isOpen and selectedTab useLayoutEffect(() => { const panelHandle = panelRef.current if (panelHandle) { if (isOpen) { panelHandle.expand() } else { panelHandle.collapse() } } }, [isOpen, selectedTab]) const openTab = useCallback( (tab: RailTabKey) => { setSelectedTab(tab) setIsOpen(true) }, [setIsOpen, setSelectedTab] ) useEventListener( 'ui.toggle-review-panel', useCallback(() => { if (isOpen && selectedTab === 'review-panel') { handlePaneCollapse() } else { openTab('review-panel') } }, [handlePaneCollapse, selectedTab, isOpen, openTab]) ) useEventListener( 'keydown', useCallback( (event: KeyboardEvent) => { if ( (isMac ? event.metaKey : event.ctrlKey) && event.shiftKey && event.code === 'KeyF' ) { event.preventDefault() sendSearchEvent('search-open', { searchType: 'full-project', method: 'keyboard', }) openTab('full-project-search') } }, [openTab] ) ) const value = useMemo( () => ({ selectedTab, isOpen, setIsOpen, panelRef, togglePane, handlePaneExpand, handlePaneCollapse, resizing, setResizing, activeModal, setActiveModal, openTab, }), [ selectedTab, isOpen, setIsOpen, panelRef, togglePane, handlePaneExpand, handlePaneCollapse, resizing, setResizing, activeModal, setActiveModal, openTab, ] ) return {children} } export const useRailContext = () => { const context = useContext(RailContext) if (!context) { throw new Error('useRailContext is only available inside RailProvider') } return context }