rail-context.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import { sendSearchEvent } from '@/features/event-tracking/search-events'
  2. import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel'
  3. import useEventListener from '@/shared/hooks/use-event-listener'
  4. import { isMac } from '@/shared/utils/os'
  5. import {
  6. createContext,
  7. Dispatch,
  8. FC,
  9. SetStateAction,
  10. useCallback,
  11. useContext,
  12. useMemo,
  13. useRef,
  14. useState,
  15. } from 'react'
  16. import { ImperativePanelHandle } from 'react-resizable-panels'
  17. export type RailTabKey =
  18. | 'file-tree'
  19. | 'integrations'
  20. | 'review-panel'
  21. | 'chat'
  22. | 'errors'
  23. | 'full-project-search'
  24. export type RailModalKey = 'keyboard-shortcuts' | 'contact-us' | 'dictionary'
  25. const RailContext = createContext<
  26. | {
  27. selectedTab: RailTabKey
  28. isOpen: boolean
  29. setIsOpen: Dispatch<SetStateAction<boolean>>
  30. panelRef: React.RefObject<ImperativePanelHandle>
  31. togglePane: () => void
  32. handlePaneExpand: () => void
  33. handlePaneCollapse: () => void
  34. resizing: boolean
  35. setResizing: Dispatch<SetStateAction<boolean>>
  36. activeModal: RailModalKey | null
  37. setActiveModal: Dispatch<SetStateAction<RailModalKey | null>>
  38. openTab: (tab: RailTabKey) => void
  39. }
  40. | undefined
  41. >(undefined)
  42. export const RailProvider: FC<React.PropsWithChildren> = ({ children }) => {
  43. const [isOpen, setIsOpen] = useState(true)
  44. const [resizing, setResizing] = useState(false)
  45. const [activeModal, setActiveModalInternal] = useState<RailModalKey | null>(
  46. null
  47. )
  48. const setActiveModal: Dispatch<SetStateAction<RailModalKey | null>> =
  49. useCallback(modalKey => {
  50. setActiveModalInternal(modalKey)
  51. }, [])
  52. const panelRef = useRef<ImperativePanelHandle>(null)
  53. useCollapsiblePanel(isOpen, panelRef)
  54. const togglePane = useCallback(() => {
  55. setIsOpen(value => !value)
  56. }, [])
  57. const handlePaneExpand = useCallback(() => {
  58. setIsOpen(true)
  59. }, [])
  60. const handlePaneCollapse = useCallback(() => {
  61. setIsOpen(false)
  62. }, [])
  63. // NOTE: The file tree **MUST** be the first tab to be opened
  64. // since it is responsible for opening the initial document.
  65. const [selectedTab, setSelectedTab] = useState<RailTabKey>('file-tree')
  66. const openTab = useCallback(
  67. (tab: RailTabKey) => {
  68. setSelectedTab(tab)
  69. setIsOpen(true)
  70. },
  71. [setIsOpen, setSelectedTab]
  72. )
  73. useEventListener(
  74. 'ui.toggle-review-panel',
  75. useCallback(() => {
  76. if (isOpen && selectedTab === 'review-panel') {
  77. handlePaneCollapse()
  78. } else {
  79. openTab('review-panel')
  80. }
  81. }, [handlePaneCollapse, selectedTab, isOpen, openTab])
  82. )
  83. useEventListener(
  84. 'keydown',
  85. useCallback(
  86. (event: KeyboardEvent) => {
  87. if (
  88. (isMac ? event.metaKey : event.ctrlKey) &&
  89. event.shiftKey &&
  90. event.code === 'KeyF'
  91. ) {
  92. event.preventDefault()
  93. sendSearchEvent('search-open', {
  94. searchType: 'full-project',
  95. method: 'keyboard',
  96. })
  97. openTab('full-project-search')
  98. }
  99. },
  100. [openTab]
  101. )
  102. )
  103. const value = useMemo(
  104. () => ({
  105. selectedTab,
  106. isOpen,
  107. setIsOpen,
  108. panelRef,
  109. togglePane,
  110. handlePaneExpand,
  111. handlePaneCollapse,
  112. resizing,
  113. setResizing,
  114. activeModal,
  115. setActiveModal,
  116. openTab,
  117. }),
  118. [
  119. selectedTab,
  120. isOpen,
  121. setIsOpen,
  122. panelRef,
  123. togglePane,
  124. handlePaneExpand,
  125. handlePaneCollapse,
  126. resizing,
  127. setResizing,
  128. activeModal,
  129. setActiveModal,
  130. openTab,
  131. ]
  132. )
  133. return <RailContext.Provider value={value}>{children}</RailContext.Provider>
  134. }
  135. export const useRailContext = () => {
  136. const context = useContext(RailContext)
  137. if (!context) {
  138. throw new Error('useRailContext is only available inside RailProvider')
  139. }
  140. return context
  141. }