feat: initial implementation of responsive app panes (#1198)

This commit is contained in:
Aman Harwara
2022-07-04 21:20:28 +05:30
committed by GitHub
parent 38725daeb9
commit 21ea2ec7a1
20 changed files with 336 additions and 186 deletions

View File

@@ -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',
}

View File

@@ -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

View File

@@ -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