feat: initial implementation of responsive app panes (#1198)
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
import { IconType } from '@standardnotes/snjs'
|
||||
|
||||
export enum AppPaneId {
|
||||
Navigation = 'NavigationColumn',
|
||||
Items = 'ItemsColumn',
|
||||
Editor = 'EditorColumn',
|
||||
}
|
||||
|
||||
export const AppPaneTitles = {
|
||||
[AppPaneId.Navigation]: 'Navigation',
|
||||
[AppPaneId.Items]: 'Notes & Files',
|
||||
[AppPaneId.Editor]: 'Editor',
|
||||
}
|
||||
|
||||
export const AppPaneIcons: Record<AppPaneId, IconType> = {
|
||||
[AppPaneId.Navigation]: 'hashtag',
|
||||
[AppPaneId.Items]: 'notes',
|
||||
[AppPaneId.Editor]: 'plain-text',
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useMemo, ReactNode } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { AppPaneIcons, AppPaneId, AppPaneTitles } from './AppPaneMetadata'
|
||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||
import { useResponsiveAppPane } from './ResponsivePaneProvider'
|
||||
|
||||
type Props = {
|
||||
children: ReactNode
|
||||
contentClassName?: string
|
||||
contentElementId?: string
|
||||
paneId: AppPaneId
|
||||
}
|
||||
|
||||
const ResponsivePaneContent = ({ children, contentClassName, contentElementId, paneId }: Props) => {
|
||||
const { selectedPane, toggleAppPane: togglePane } = useResponsiveAppPane()
|
||||
|
||||
const isSelectedPane = useMemo(() => selectedPane === paneId, [paneId, selectedPane])
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className={classNames(
|
||||
'flex w-full items-center justify-between border-b border-solid border-border px-4 py-2 focus:shadow-none focus:outline-none md:hidden',
|
||||
isSelectedPane ? 'bg-contrast' : 'bg-default',
|
||||
)}
|
||||
onClick={() => togglePane(paneId)}
|
||||
>
|
||||
<div className="flex items-center gap-2 font-semibold">
|
||||
<Icon type={AppPaneIcons[paneId]} />
|
||||
<span>{AppPaneTitles[paneId]}</span>
|
||||
</div>
|
||||
<Icon type="chevron-down" />
|
||||
</button>
|
||||
<div
|
||||
id={contentElementId}
|
||||
className={classNames('content', isSelectedPane ? 'h-full' : 'hidden flex-col md:flex', contentClassName)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ResponsivePaneContent
|
||||
@@ -0,0 +1,64 @@
|
||||
import { ElementIds } from '@/Constants/ElementIDs'
|
||||
import { useEffect, ReactNode, useMemo, createContext, useCallback, useContext, useState } from 'react'
|
||||
import { AppPaneId } from './AppPaneMetadata'
|
||||
|
||||
type ResponsivePaneData = {
|
||||
selectedPane: AppPaneId
|
||||
toggleAppPane: (paneId: AppPaneId) => void
|
||||
}
|
||||
|
||||
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 Props = {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const ResponsivePaneProvider = ({ children }: Props) => {
|
||||
const [currentSelectedPane, setCurrentSelectedPane] = useState<AppPaneId>(AppPaneId.Editor)
|
||||
const [previousSelectedPane, setPreviousSelectedPane] = useState<AppPaneId>(AppPaneId.Editor)
|
||||
|
||||
const toggleAppPane = useCallback(
|
||||
(paneId: AppPaneId) => {
|
||||
if (paneId === currentSelectedPane) {
|
||||
setCurrentSelectedPane(previousSelectedPane ? previousSelectedPane : AppPaneId.Editor)
|
||||
setPreviousSelectedPane(paneId)
|
||||
} else {
|
||||
setPreviousSelectedPane(currentSelectedPane)
|
||||
setCurrentSelectedPane(paneId)
|
||||
}
|
||||
},
|
||||
[currentSelectedPane, previousSelectedPane],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (previousSelectedPane) {
|
||||
const previousPaneElement = document.getElementById(ElementIds[previousSelectedPane])
|
||||
previousPaneElement?.classList.remove('selected')
|
||||
}
|
||||
|
||||
const currentPaneElement = document.getElementById(ElementIds[currentSelectedPane])
|
||||
currentPaneElement?.classList.add('selected')
|
||||
}, [currentSelectedPane, previousSelectedPane])
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
selectedPane: currentSelectedPane,
|
||||
toggleAppPane,
|
||||
}),
|
||||
[currentSelectedPane, toggleAppPane],
|
||||
)
|
||||
|
||||
return <ResponsivePaneContext.Provider value={contextValue}>{children}</ResponsivePaneContext.Provider>
|
||||
}
|
||||
|
||||
export default ResponsivePaneProvider
|
||||
Reference in New Issue
Block a user