Files
standardnotes-app-web/packages/web/src/javascripts/Components/Panes/ResponsivePaneProvider.tsx

145 lines
4.2 KiB
TypeScript

import { useAndroidBackHandler } from '@/NativeMobileWeb/useAndroidBackHandler'
import {
useEffect,
ReactNode,
useMemo,
createContext,
useCallback,
useContext,
memo,
useRef,
useLayoutEffect,
MutableRefObject,
} from 'react'
import { AppPaneId } from './AppPaneMetadata'
import { PaneController } from '../../Controllers/PaneController/PaneController'
import { observer } from 'mobx-react-lite'
type ResponsivePaneData = {
selectedPane: AppPaneId
toggleListPane: () => void
toggleNavigationPane: () => void
isListPaneCollapsed: boolean
isNavigationPaneCollapsed: boolean
panes: PaneController['panes']
toggleAppPane: (paneId: AppPaneId) => void
presentPane: PaneController['presentPane']
popToPane: PaneController['popToPane']
dismissLastPane: PaneController['dismissLastPane']
replacePanes: PaneController['replacePanes']
removePane: PaneController['removePane']
insertPaneAtIndex: PaneController['insertPaneAtIndex']
setPaneLayout: PaneController['setPaneLayout']
focusModeEnabled: PaneController['focusModeEnabled']
}
const ResponsivePaneContext = createContext<ResponsivePaneData | undefined>(undefined)
export const useResponsiveAppPane = () => {
const value = useContext(ResponsivePaneContext)
if (!value) {
throw new Error('Component must be a child of <ResponsivePaneProvider />')
}
return value
}
type ChildrenProps = {
children: ReactNode
}
type ProviderProps = {
paneController: PaneController
} & ChildrenProps
function useStateRef<State>(state: State): MutableRefObject<State> {
const ref = useRef<State>(state)
useLayoutEffect(() => {
ref.current = state
}, [state])
return ref
}
const MemoizedChildren = memo(({ children }: ChildrenProps) => <>{children}</>)
const ResponsivePaneProvider = ({ paneController, children }: ProviderProps) => {
const currentSelectedPane = paneController.currentPane
const currentSelectedPaneRef = useStateRef<AppPaneId>(currentSelectedPane)
const toggleAppPane = useCallback(
(paneId: AppPaneId) => {
paneController.presentPane(paneId)
},
[paneController],
)
const addAndroidBackHandler = useAndroidBackHandler()
useEffect(() => {
const removeListener = addAndroidBackHandler(() => {
if (
currentSelectedPaneRef.current === AppPaneId.Editor ||
currentSelectedPaneRef.current === AppPaneId.Navigation
) {
toggleAppPane(AppPaneId.Items)
return true
} else {
return false
}
})
return () => {
if (removeListener) {
removeListener()
}
}
}, [addAndroidBackHandler, currentSelectedPaneRef, toggleAppPane])
const contextValue = useMemo(
(): ResponsivePaneData => ({
selectedPane: currentSelectedPane,
toggleAppPane,
presentPane: paneController.presentPane,
isListPaneCollapsed: paneController.isListPaneCollapsed,
isNavigationPaneCollapsed: paneController.isNavigationPaneCollapsed,
toggleListPane: paneController.toggleListPane,
toggleNavigationPane: paneController.toggleNavigationPane,
panes: paneController.panes,
popToPane: paneController.popToPane,
dismissLastPane: paneController.dismissLastPane,
replacePanes: paneController.replacePanes,
removePane: paneController.removePane,
insertPaneAtIndex: paneController.insertPaneAtIndex,
setPaneLayout: paneController.setPaneLayout,
focusModeEnabled: paneController.focusModeEnabled,
}),
[
currentSelectedPane,
toggleAppPane,
paneController.panes,
paneController.isListPaneCollapsed,
paneController.isNavigationPaneCollapsed,
paneController.toggleListPane,
paneController.toggleNavigationPane,
paneController.presentPane,
paneController.popToPane,
paneController.dismissLastPane,
paneController.replacePanes,
paneController.removePane,
paneController.insertPaneAtIndex,
paneController.setPaneLayout,
paneController.focusModeEnabled,
],
)
return (
<ResponsivePaneContext.Provider value={contextValue}>
<MemoizedChildren children={children} />
</ResponsivePaneContext.Provider>
)
}
export default observer(ResponsivePaneProvider)