Files
omnivore/packages/web/components/patterns/PrimaryHeader.tsx
Jackson Harper eb8cb3854c Do all scroll watching on the main window
This moves all scrolling from child divs to the main window. This
improves our keyboard handling, as focus will be given to the
body element, not the child div when navigating by keyboard
commands, so arrow keys and space bar will work after navigating
to the reader with the keyboard commands.
2022-06-17 15:59:51 -07:00

251 lines
6.8 KiB
TypeScript

import { Box, HStack } from '../elements/LayoutPrimitives'
import { OmnivoreNameLogo } from './../elements/images/OmnivoreNameLogo'
import { DropdownMenu, HeaderDropdownAction } from './../patterns/DropdownMenu'
import { darkenTheme, lightenTheme, updateTheme } from '../../lib/themeUpdater'
import { AvatarDropdown } from './../elements/AvatarDropdown'
import { ThemeId } from './../tokens/stitches.config'
import { useCallback, useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { useKeyboardShortcuts } from '../../lib/keyboardShortcuts/useKeyboardShortcuts'
import { primaryCommands } from '../../lib/keyboardShortcuts/navigationShortcuts'
import { UserBasicData } from '../../lib/networking/queries/useGetViewerQuery'
import { setupAnalytics } from '../../lib/analytics'
type HeaderProps = {
user?: UserBasicData
userInitials: string
hideHeader?: boolean
profileImageURL?: string
isTransparent: boolean
toolbarControl?: JSX.Element
alwaysDisplayToolbar?: boolean
setShowLogoutConfirmation: (showShareModal: boolean) => void
setShowKeyboardCommandsModal: (showShareModal: boolean) => void
}
export function PrimaryHeader(props: HeaderProps): JSX.Element {
const router = useRouter()
const [isScrolled, setIsScrolled] = useState(false)
useKeyboardShortcuts(
primaryCommands((action) => {
switch (action) {
case 'themeDarker':
darkenTheme()
break
case 'themeLighter':
lightenTheme()
break
case 'toggleShortcutHelpModalDisplay':
props.setShowKeyboardCommandsModal(true)
break
}
})
)
/*
useRegisterActions([
{
id: 'lightTheme',
section: 'Preferences',
name: 'Change theme (lighter) ',
shortcut: ['v', 'l'],
keywords: 'light theme',
perform: () => lightenTheme(),
},
{
id: 'darkTheme',
section: 'Preferences',
name: 'Change theme (darker) ',
shortcut: ['v', 'd'],
keywords: 'dark theme',
perform: () => darkenTheme(),
},
])
*/
const initAnalytics = useCallback(() => {
setupAnalytics(props.user)
}, [props.user])
useEffect(() => {
initAnalytics()
window.addEventListener('load', initAnalytics)
return () => {
window.removeEventListener('load', initAnalytics)
}
}, [initAnalytics])
function headerDropdownActionHandler(action: HeaderDropdownAction): void {
switch (action) {
case 'apply-darker-theme':
updateTheme(ThemeId.Darker)
break
case 'apply-dark-theme':
updateTheme(ThemeId.Dark)
break
case 'apply-lighter-theme':
updateTheme(ThemeId.Lighter)
break
case 'apply-light-theme':
updateTheme(ThemeId.Light)
break
case 'increaseFontSize':
document.dispatchEvent(new Event('increaseFontSize'))
break
case 'decreaseFontSize':
document.dispatchEvent(new Event('decreaseFontSize'))
break
case 'navigate-to-install':
router.push('/settings/installation')
break
case 'navigate-to-emails':
router.push('/settings/emails')
break
case 'navigate-to-labels':
router.push('/settings/labels')
break
case 'navigate-to-profile':
if (props.user) {
router.push(`/${props.user.profile.username}`)
}
break
case 'navigate-to-subscriptions':
router.push('/settings/subscriptions')
break
case 'logout':
props.setShowLogoutConfirmation(true)
break
default:
break
}
}
return (
<>
<NavHeader
{...props}
isVisible={true}
username={props.user?.profile.username}
actionHandler={headerDropdownActionHandler}
isDisplayingShadow={isScrolled}
toolbarControl={props.toolbarControl}
alwaysDisplayToolbar={props.alwaysDisplayToolbar}
/>
</>
)
}
// Separating out component so we can extract to design system
type NavHeaderProps = {
username?: string
userInitials: string
actionHandler: (action: HeaderDropdownAction) => void
profileImageURL?: string
isDisplayingShadow?: boolean
isVisible?: boolean
isTransparent: boolean
toolbarControl?: JSX.Element
alwaysDisplayToolbar?: boolean
}
function NavHeader(props: NavHeaderProps): JSX.Element {
const router = useRouter()
const currentPath = decodeURI(router.asPath)
return (
<nav>
<HStack
alignment="center"
distribution="between"
css={{
zIndex: 5,
width: '100%',
boxShadow: props.isDisplayingShadow ? '$panelShadow' : 'unset',
p: '0px $3 0px $3',
height: '48px',
position: 'fixed',
bg: props.isTransparent ? 'transparent' : '$grayBase',
'@smDown': {
p: '0px 18px 0px 16px',
},
'@lgDown': {
bg: '$grayBase',
},
}}
>
<HStack alignment="center" distribution="start">
<Box
css={{
display: 'flex',
alignItems: 'center',
paddingRight: '10px',
}}
>
<OmnivoreNameLogo href={props.username ? '/home' : '/login'} />
</Box>
<NavLinks currentPath={currentPath} isLoggedIn={!!props.username} />
</HStack>
{props.toolbarControl && (
<HStack
distribution="end"
alignment="center"
css={{
height: '100%',
width: '100%',
mr: '16px',
display: props.alwaysDisplayToolbar ? 'flex' : 'none',
'@lgDown': {
display: 'flex',
},
}}
>
{props.toolbarControl}
</HStack>
)}
{props.username && (
<HStack
alignment="center"
css={{ display: 'flex', alignItems: 'center' }}
>
<DropdownMenu
username={props.username}
triggerElement={
<AvatarDropdown
userInitials={props.userInitials}
profileImageURL={props.profileImageURL}
/>
}
actionHandler={props.actionHandler}
/>
</HStack>
)}
</HStack>
</nav>
)
}
type UserNavLinksProps = {
isLoggedIn: boolean
currentPath: string
}
function NavLinks(props: UserNavLinksProps): JSX.Element {
return (
<>
{/* <HeaderNavLink
isActive={props.currentPath.startsWith('/home')}
href={props.isLoggedIn ? '/home' : '/login'}
text="Home"
/> */}
{/* <HeaderNavLink
isActive={props.currentPath == '/discover'}
href="/discover"
text="Discover"
/> */}
</>
)
}