webapp/src/components/Nav/Header.tsx

238 lines
8.4 KiB
TypeScript
Raw Normal View History

2022-09-15 18:18:07 +00:00
import { For, Show, createSignal, createMemo, createEffect, onMount, onCleanup } from 'solid-js'
2022-09-09 11:53:35 +00:00
import Notifications from './Notifications'
2022-09-22 09:37:49 +00:00
import { Icon } from './Icon'
2022-09-09 11:53:35 +00:00
import { Modal } from './Modal'
2022-10-25 16:25:42 +00:00
import { AuthModal } from './AuthModal'
2022-09-09 11:53:35 +00:00
import { t } from '../../utils/intl'
2022-10-25 15:36:32 +00:00
import { useModalStore, showModal, useWarningsStore } from '../../stores/ui'
2022-09-30 14:22:33 +00:00
import { useAuthStore } from '../../stores/auth'
2022-09-22 09:37:49 +00:00
import { handleClientRouteLinkClick, router, Routes, useRouter } from '../../stores/router'
2022-09-28 21:08:14 +00:00
import styles from './Header.module.scss'
2022-09-22 09:37:49 +00:00
import { getPagePath } from '@nanostores/router'
import { getLogger } from '../../utils/logger'
2022-09-28 21:08:14 +00:00
import { clsx } from 'clsx'
2022-10-25 15:36:32 +00:00
import { SharePopup } from '../Article/SharePopup'
import { ProfilePopup } from './ProfilePopup'
import Userpic from '../Author/Userpic'
import type { Author } from '../../graphql/types.gen'
2022-09-09 11:53:35 +00:00
2022-09-22 09:37:49 +00:00
const log = getLogger('header')
const resources: { name: string; route: keyof Routes }[] = [
{ name: t('zine'), route: 'home' },
{ name: t('feed'), route: 'feed' },
{ name: t('topics'), route: 'topics' }
2022-09-09 11:53:35 +00:00
]
2022-09-22 09:37:49 +00:00
type Props = {
title?: string
2022-09-23 21:29:32 +00:00
isHeaderFixed?: boolean
2022-09-22 09:37:49 +00:00
}
export const Header = (props: Props) => {
2022-09-09 11:53:35 +00:00
// signals
2022-09-15 18:18:07 +00:00
const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false)
2022-09-15 19:41:05 +00:00
const [getIsScrolled, setIsScrolled] = createSignal(false)
2022-09-09 11:53:35 +00:00
const [fixed, setFixed] = createSignal(false)
const [visibleWarnings, setVisibleWarnings] = createSignal(false)
2022-10-25 15:36:32 +00:00
const [isSharePopupVisible, setIsSharePopupVisible] = createSignal(false)
const [isProfilePopupVisible, setIsProfilePopupVisible] = createSignal(false)
2022-09-09 11:53:35 +00:00
// stores
2022-10-25 16:25:42 +00:00
const { warnings } = useWarningsStore()
2022-09-30 14:22:33 +00:00
const { session } = useAuthStore()
2022-10-25 16:25:42 +00:00
const { modal } = useModalStore()
2022-09-22 09:37:49 +00:00
2022-10-25 16:25:42 +00:00
const { page } = useRouter()
2022-09-22 09:37:49 +00:00
2022-09-09 11:53:35 +00:00
// methods
const toggleWarnings = () => setVisibleWarnings(!visibleWarnings())
2022-10-25 15:36:32 +00:00
const toggleFixed = () => setFixed((oldFixed) => !oldFixed)
2022-09-09 11:53:35 +00:00
// effects
let windowScrollTop = 0
2022-09-09 11:53:35 +00:00
createEffect(() => {
2022-11-04 13:58:42 +00:00
const mainContent = document.querySelector('.main-content') as HTMLDivElement
if (fixed() || modal() !== null) {
windowScrollTop = window.scrollY
mainContent.style.marginTop = `-${windowScrollTop}px`
}
2022-10-25 16:25:42 +00:00
document.body.classList.toggle('fixed', fixed() || modal() !== null)
2022-10-25 21:17:35 +00:00
document.body.classList.toggle(styles.fixed, fixed() && !modal())
if (!fixed() && !modal()) {
mainContent.style.marginTop = ''
window.scrollTo(0, windowScrollTop)
}
2022-10-25 15:36:32 +00:00
})
2022-09-09 11:53:35 +00:00
// derived
const authorized = createMemo(() => session()?.user?.slug)
2022-09-14 11:28:43 +00:00
2022-10-25 16:25:42 +00:00
const handleBellIconClick = (event: Event) => {
event.preventDefault()
2022-09-14 11:28:43 +00:00
if (!authorized()) {
2022-09-22 09:37:49 +00:00
showModal('auth')
2022-09-14 11:28:43 +00:00
return
}
toggleWarnings()
}
2022-09-14 21:07:47 +00:00
onMount(() => {
2022-09-15 18:18:07 +00:00
let scrollTop = window.scrollY
2022-09-14 21:07:47 +00:00
2022-09-15 18:18:07 +00:00
const handleScroll = () => {
setIsScrollingBottom(window.scrollY > scrollTop)
2022-09-15 19:41:05 +00:00
setIsScrolled(window.scrollY > 0)
2022-09-15 18:18:07 +00:00
scrollTop = window.scrollY
}
2022-09-14 21:07:47 +00:00
2022-09-15 18:18:07 +00:00
window.addEventListener('scroll', handleScroll, { passive: true })
onCleanup(() => {
window.removeEventListener('scroll', handleScroll)
})
})
2022-09-14 21:07:47 +00:00
2022-09-09 11:53:35 +00:00
return (
2022-09-15 18:18:07 +00:00
<header
2022-09-28 21:08:14 +00:00
class={styles.mainHeader}
2022-09-15 18:18:07 +00:00
classList={{
2022-09-28 21:08:14 +00:00
[styles.headerFixed]: props.isHeaderFixed,
2022-09-29 20:10:00 +00:00
[styles.headerScrolledTop]: !getIsScrollingBottom() && getIsScrolled(),
[styles.headerScrolledBottom]:
(getIsScrollingBottom() && getIsScrolled() && !isProfilePopupVisible()) || isSharePopupVisible(),
2022-10-04 10:48:11 +00:00
[styles.headerWithTitle]: Boolean(props.title)
2022-09-15 18:18:07 +00:00
}}
>
2022-09-09 11:53:35 +00:00
<Modal name="auth">
<AuthModal />
</Modal>
2022-10-17 20:53:04 +00:00
<div class={clsx(styles.mainHeaderInner, 'wide-container')}>
2022-09-28 21:08:14 +00:00
<nav class={clsx(styles.headerInner, 'row')} classList={{ fixed: fixed() }}>
<div class={clsx(styles.mainLogo, 'col-auto')}>
2022-09-22 09:37:49 +00:00
<a href={getPagePath(router, 'home')} onClick={handleClientRouteLinkClick}>
2022-09-09 11:53:35 +00:00
<img src="/logo.svg" alt={t('Discours')} />
</a>
</div>
2022-10-03 21:31:11 +00:00
<div class={clsx(styles.mainNavigationWrapper, 'col')}>
2022-10-03 21:44:21 +00:00
<Show when={props.title}>
<div class={styles.articleHeader}>{props.title}</div>
</Show>
2022-09-15 19:41:05 +00:00
2022-09-29 10:08:22 +00:00
<ul
class={clsx(styles.mainNavigation, 'col text-xl inline-flex')}
classList={{ fixed: fixed() }}
>
2022-09-15 19:41:05 +00:00
<For each={resources}>
2022-09-22 09:37:49 +00:00
{(r) => (
2022-10-25 16:25:42 +00:00
<li classList={{ [styles.selected]: r.route === page().route }}>
2022-09-22 09:37:49 +00:00
<a href={getPagePath(router, r.route, null)} onClick={handleClientRouteLinkClick}>
2022-09-15 19:41:05 +00:00
{r.name}
</a>
</li>
)}
</For>
2022-09-28 21:08:14 +00:00
<li class={styles.headerSearch}>
2022-09-18 15:31:48 +00:00
<a href="#">
2022-09-29 19:16:17 +00:00
<Icon name="search" class={styles.icon} iconClassName={styles.searchIcon} />
2022-09-18 15:31:48 +00:00
{t('Search')}
</a>
</li>
2022-09-15 19:41:05 +00:00
</ul>
</div>
2022-09-29 10:08:22 +00:00
<div class={styles.usernav}>
<div class={clsx(styles.userControl, styles.userControl, 'col')}>
<div class={clsx(styles.userControlItem, styles.userControlItemWritePost)}>
2022-11-01 19:27:43 +00:00
<a href="/create" onClick={handleClientRouteLinkClick}>
<span class={styles.textLabel}>{t('Create post')}</span>
<Icon name="pencil" class={styles.icon} />
</a>
</div>
<div class={styles.userControlItem}>
2022-10-25 16:25:42 +00:00
<a href="#" onClick={handleBellIconClick}>
2022-09-09 11:53:35 +00:00
<div>
2022-10-25 16:25:42 +00:00
<Icon name="bell-white" counter={authorized() ? warnings().length : 1} />
2022-09-09 11:53:35 +00:00
</div>
</a>
</div>
<Show when={visibleWarnings()}>
<div class={clsx(styles.userControlItem, 'notifications')}>
2022-09-09 11:53:35 +00:00
<Notifications />
</div>
</Show>
<Show
when={authorized()}
fallback={
<div class={clsx(styles.userControlItem, 'loginbtn')}>
2022-10-25 16:25:42 +00:00
<a href="?modal=auth&mode=login" onClick={handleClientRouteLinkClick}>
2022-10-03 21:31:11 +00:00
<Icon name="user-anonymous" />
2022-09-09 11:53:35 +00:00
</a>
</div>
}
>
<div class={clsx(styles.userControlItem, styles.userControlItemInbox)}>
<a href="/inbox">
{/*FIXME: replace with route*/}
<div classList={{ entered: page().path === '/inbox' }}>
<Icon name="inbox-white" counter={session()?.news?.unread || 0} />
</div>
</a>
</div>
<ProfilePopup
onVisibilityChange={(isVisible) => {
setIsProfilePopupVisible(isVisible)
}}
containerCssClass={styles.control}
trigger={
<div class={styles.userControlItem}>
<button class={styles.button}>
<div classList={{ entered: page().path === `/${session().user?.slug}` }}>
<Userpic user={session().user as Author} class={styles.userpic} />
</div>
</button>
</div>
}
/>
2022-09-09 11:53:35 +00:00
</Show>
</div>
2022-10-03 21:44:21 +00:00
<Show when={props.title}>
<div class={styles.articleControls}>
2022-10-25 15:36:32 +00:00
<SharePopup
onVisibilityChange={(isVisible) => {
setIsSharePopupVisible(isVisible)
}}
containerCssClass={styles.control}
trigger={<Icon name="share-outline" class={styles.icon} />}
/>
<a href="#comments" class={styles.control}>
2022-10-03 21:44:21 +00:00
<Icon name="comments-outline" class={styles.icon} />
</a>
2022-10-25 15:36:32 +00:00
<a href="#" class={styles.control} onClick={(event) => event.preventDefault()}>
<Icon name="pencil-outline" class={styles.icon} />
</a>
<a href="#" class={styles.control} onClick={(event) => event.preventDefault()}>
<Icon name="bookmark" class={styles.icon} />
</a>
2022-10-03 21:44:21 +00:00
</div>
</Show>
2022-09-09 11:53:35 +00:00
</div>
2022-09-29 10:08:22 +00:00
<div class={styles.burgerContainer}>
<div class={styles.burger} classList={{ fixed: fixed() }} onClick={toggleFixed}>
2022-09-09 11:53:35 +00:00
<div />
</div>
</div>
</nav>
</div>
</header>
)
}