refactor sidebar; add to mobile navbar

This commit is contained in:
mantikoros 2022-09-17 18:25:53 -05:00
parent 191ec9535c
commit 438c12da57
3 changed files with 161 additions and 143 deletions

View File

@ -0,0 +1,23 @@
import { DotsHorizontalIcon } from '@heroicons/react/outline'
export function MoreButton() {
return <SidebarButton text={'More'} icon={DotsHorizontalIcon} />
}
function SidebarButton(props: {
text: string
icon: React.ComponentType<{ className?: string }>
children?: React.ReactNode
}) {
const { text, children } = props
return (
<a className="group flex items-center rounded-md px-3 py-2 text-sm font-medium text-gray-600 hover:cursor-pointer hover:bg-gray-100">
<props.icon
className="-ml-1 mr-3 h-6 w-6 flex-shrink-0 text-gray-400 group-hover:text-gray-500"
aria-hidden="true"
/>
<span className="truncate">{text}</span>
{children}
</a>
)
}

View File

@ -0,0 +1,63 @@
import React from 'react'
import clsx from 'clsx'
import Link from 'next/link'
import { trackCallback } from 'web/lib/service/analytics'
export type Item = {
name: string
trackingEventName?: string
href?: string
key?: string
icon?: React.ComponentType<{ className?: string }>
}
export function SidebarItem(props: {
item: Item
currentPage: string
onClick?: (key: string) => void
}) {
const { item, currentPage, onClick } = props
const isCurrentPage =
item.href != null ? item.href === currentPage : item.key === currentPage
const sidebarItem = (
<a
onClick={trackCallback('sidebar: ' + item.name)}
className={clsx(
isCurrentPage
? 'bg-gray-200 text-gray-900'
: 'text-gray-600 hover:bg-gray-100',
'group flex items-center rounded-md px-3 py-2 text-sm font-medium'
)}
aria-current={item.href == currentPage ? 'page' : undefined}
>
{item.icon && (
<item.icon
className={clsx(
isCurrentPage
? 'text-gray-500'
: 'text-gray-400 group-hover:text-gray-500',
'-ml-1 mr-3 h-6 w-6 flex-shrink-0'
)}
aria-hidden="true"
/>
)}
<span className="truncate">{item.name}</span>
</a>
)
if (item.href) {
return (
<Link href={item.href} key={item.name}>
{sidebarItem}
</Link>
)
} else {
return onClick ? (
<button onClick={() => onClick(item.key ?? '#')}>{sidebarItem}</button>
) : (
<> </>
)
}
}

View File

@ -1,29 +1,93 @@
import React from 'react'
import { import {
HomeIcon, HomeIcon,
SearchIcon, SearchIcon,
BookOpenIcon, BookOpenIcon,
DotsHorizontalIcon,
CashIcon, CashIcon,
HeartIcon, HeartIcon,
ChatIcon, ChatIcon,
ChartBarIcon,
} from '@heroicons/react/outline' } from '@heroicons/react/outline'
import clsx from 'clsx' import clsx from 'clsx'
import Link from 'next/link'
import Router, { useRouter } from 'next/router' import Router, { useRouter } from 'next/router'
import { useUser } from 'web/hooks/use-user' import { useUser } from 'web/hooks/use-user'
import { firebaseLogout, User } from 'web/lib/firebase/users' import { firebaseLogout, User } from 'web/lib/firebase/users'
import { ManifoldLogo } from './manifold-logo' import { ManifoldLogo } from './manifold-logo'
import { MenuButton, MenuItem } from './menu' import { MenuButton, MenuItem } from './menu'
import { ProfileSummary } from './profile-menu' import { ProfileSummary } from './profile-menu'
import NotificationsIcon from 'web/components/notifications-icon' import NotificationsIcon from 'web/components/notifications-icon'
import React from 'react'
import { IS_PRIVATE_MANIFOLD } from 'common/envs/constants' import { IS_PRIVATE_MANIFOLD } from 'common/envs/constants'
import { CreateQuestionButton } from 'web/components/create-question-button' import { CreateQuestionButton } from 'web/components/create-question-button'
import { trackCallback, withTracking } from 'web/lib/service/analytics' import { withTracking } from 'web/lib/service/analytics'
import { CHALLENGES_ENABLED } from 'common/challenge' import { CHALLENGES_ENABLED } from 'common/challenge'
import { buildArray } from 'common/util/array' import { buildArray } from 'common/util/array'
import TrophyIcon from 'web/lib/icons/trophy-icon' import TrophyIcon from 'web/lib/icons/trophy-icon'
import { SignInButton } from '../sign-in-button' import { SignInButton } from '../sign-in-button'
import { SidebarItem } from './sidebar-item'
import { MoreButton } from './more-button'
export default function Sidebar(props: { className?: string }) {
const { className } = props
const router = useRouter()
const currentPage = router.pathname
const user = useUser()
const desktopNavOptions = !user
? signedOutDesktopNavigation
: getDesktopNavigation()
const mobileNavOptions = !user
? signedOutMobileNavigation
: signedInMobileNavigation
const createMarketButton = user && !user.isBannedFromPosting && (
<CreateQuestionButton />
)
return (
<nav
aria-label="Sidebar"
className={clsx('flex max-h-[100vh] flex-col', className)}
>
<ManifoldLogo className="py-6" twoLine />
{!user && <SignInButton className="mb-4" />}
{user && <ProfileSummary user={user} />}
{/* Mobile navigation */}
<div className="flex min-h-0 shrink flex-col gap-1 lg:hidden">
{mobileNavOptions.map((item) => (
<SidebarItem key={item.href} item={item} currentPage={currentPage} />
))}
{user && (
<MenuButton
menuItems={getMoreMobileNav()}
buttonContent={<MoreButton />}
/>
)}
{createMarketButton}
</div>
{/* Desktop navigation */}
<div className="hidden min-h-0 shrink flex-col items-stretch gap-1 lg:flex ">
{desktopNavOptions.map((item) => (
<SidebarItem key={item.href} item={item} currentPage={currentPage} />
))}
<MenuButton
menuItems={getMoreDesktopNavigation(user)}
buttonContent={<MoreButton />}
/>
{createMarketButton}
</div>
</nav>
)
}
const logout = async () => { const logout = async () => {
// log out, and then reload the page, in case SSR wants to boot them out // log out, and then reload the page, in case SSR wants to boot them out
@ -32,7 +96,7 @@ const logout = async () => {
await Router.replace(Router.asPath) await Router.replace(Router.asPath)
} }
function getNavigation() { function getDesktopNavigation() {
return [ return [
{ name: 'Home', href: '/home', icon: HomeIcon }, { name: 'Home', href: '/home', icon: HomeIcon },
{ name: 'Search', href: '/search', icon: SearchIcon }, { name: 'Search', href: '/search', icon: SearchIcon },
@ -51,10 +115,10 @@ function getNavigation() {
] ]
} }
function getMoreNavigation(user?: User | null) { function getMoreDesktopNavigation(user?: User | null) {
if (IS_PRIVATE_MANIFOLD) { if (IS_PRIVATE_MANIFOLD) {
return [ return [
{ name: 'Leaderboards', href: '/leaderboards' }, { name: 'Leaderboards', href: '/leaderboards', icon: ChartBarIcon },
{ {
name: 'Sign out', name: 'Sign out',
href: '#', href: '#',
@ -81,7 +145,6 @@ function getMoreNavigation(user?: User | null) {
// Signed in "More" // Signed in "More"
return buildArray( return buildArray(
{ name: 'Leaderboards', href: '/leaderboards' },
{ name: 'Groups', href: '/groups' }, { name: 'Groups', href: '/groups' },
CHALLENGES_ENABLED && { name: 'Challenges', href: '/challenges' }, CHALLENGES_ENABLED && { name: 'Challenges', href: '/challenges' },
[ [
@ -99,7 +162,7 @@ function getMoreNavigation(user?: User | null) {
) )
} }
const signedOutNavigation = [ const signedOutDesktopNavigation = [
{ name: 'Home', href: '/', icon: HomeIcon }, { name: 'Home', href: '/', icon: HomeIcon },
{ name: 'Explore', href: '/search', icon: SearchIcon }, { name: 'Explore', href: '/search', icon: SearchIcon },
{ {
@ -117,11 +180,14 @@ const signedOutMobileNavigation = [
}, },
{ name: 'Charity', href: '/charity', icon: HeartIcon }, { name: 'Charity', href: '/charity', icon: HeartIcon },
{ name: 'Tournaments', href: '/tournaments', icon: TrophyIcon }, { name: 'Tournaments', href: '/tournaments', icon: TrophyIcon },
{ name: 'Leaderboards', href: '/leaderboards', icon: ChartBarIcon },
{ name: 'Discord', href: 'https://discord.gg/eHQBNBqXuh', icon: ChatIcon }, { name: 'Discord', href: 'https://discord.gg/eHQBNBqXuh', icon: ChatIcon },
] ]
const signedInMobileNavigation = [ const signedInMobileNavigation = [
{ name: 'Search', href: '/search', icon: SearchIcon },
{ name: 'Tournaments', href: '/tournaments', icon: TrophyIcon }, { name: 'Tournaments', href: '/tournaments', icon: TrophyIcon },
{ name: 'Leaderboards', href: '/leaderboards', icon: ChartBarIcon },
...(IS_PRIVATE_MANIFOLD ...(IS_PRIVATE_MANIFOLD
? [] ? []
: [{ name: 'Get M$', href: '/add-funds', icon: CashIcon }]), : [{ name: 'Get M$', href: '/add-funds', icon: CashIcon }]),
@ -145,7 +211,6 @@ function getMoreMobileNav() {
[ [
{ name: 'Groups', href: '/groups' }, { name: 'Groups', href: '/groups' },
{ name: 'Referrals', href: '/referrals' }, { name: 'Referrals', href: '/referrals' },
{ name: 'Leaderboards', href: '/leaderboards' },
{ name: 'Charity', href: '/charity' }, { name: 'Charity', href: '/charity' },
{ name: 'Send M$', href: '/links' }, { name: 'Send M$', href: '/links' },
{ name: 'Discord', href: 'https://discord.gg/eHQBNBqXuh' }, { name: 'Discord', href: 'https://discord.gg/eHQBNBqXuh' },
@ -153,136 +218,3 @@ function getMoreMobileNav() {
signOut signOut
) )
} }
export type Item = {
name: string
trackingEventName?: string
href?: string
key?: string
icon?: React.ComponentType<{ className?: string }>
}
export function SidebarItem(props: {
item: Item
currentPage: string
onClick?: (key: string) => void
}) {
const { item, currentPage, onClick } = props
const isCurrentPage =
item.href != null ? item.href === currentPage : item.key === currentPage
const sidebarItem = (
<a
onClick={trackCallback('sidebar: ' + item.name)}
className={clsx(
isCurrentPage
? 'bg-gray-200 text-gray-900'
: 'text-gray-600 hover:bg-gray-100',
'group flex items-center rounded-md px-3 py-2 text-sm font-medium'
)}
aria-current={item.href == currentPage ? 'page' : undefined}
>
{item.icon && (
<item.icon
className={clsx(
isCurrentPage
? 'text-gray-500'
: 'text-gray-400 group-hover:text-gray-500',
'-ml-1 mr-3 h-6 w-6 flex-shrink-0'
)}
aria-hidden="true"
/>
)}
<span className="truncate">{item.name}</span>
</a>
)
if (item.href) {
return (
<Link href={item.href} key={item.name}>
{sidebarItem}
</Link>
)
} else {
return onClick ? (
<button onClick={() => onClick(item.key ?? '#')}>{sidebarItem}</button>
) : (
<> </>
)
}
}
function SidebarButton(props: {
text: string
icon: React.ComponentType<{ className?: string }>
children?: React.ReactNode
}) {
const { text, children } = props
return (
<a className="group flex items-center rounded-md px-3 py-2 text-sm font-medium text-gray-600 hover:cursor-pointer hover:bg-gray-100">
<props.icon
className="-ml-1 mr-3 h-6 w-6 flex-shrink-0 text-gray-400 group-hover:text-gray-500"
aria-hidden="true"
/>
<span className="truncate">{text}</span>
{children}
</a>
)
}
function MoreButton() {
return <SidebarButton text={'More'} icon={DotsHorizontalIcon} />
}
export default function Sidebar(props: { className?: string }) {
const { className } = props
const router = useRouter()
const currentPage = router.pathname
const user = useUser()
const navigationOptions = !user ? signedOutNavigation : getNavigation()
const mobileNavigationOptions = !user
? signedOutMobileNavigation
: signedInMobileNavigation
return (
<nav
aria-label="Sidebar"
className={clsx('flex max-h-[100vh] flex-col', className)}
>
<ManifoldLogo className="py-6" twoLine />
{!user && <SignInButton className="mb-4" />}
{user && <ProfileSummary user={user} />}
{/* Mobile navigation */}
<div className="flex min-h-0 shrink flex-col gap-1 lg:hidden">
{mobileNavigationOptions.map((item) => (
<SidebarItem key={item.href} item={item} currentPage={currentPage} />
))}
{user && (
<MenuButton
menuItems={getMoreMobileNav()}
buttonContent={<MoreButton />}
/>
)}
</div>
{/* Desktop navigation */}
<div className="hidden min-h-0 shrink flex-col items-stretch gap-1 lg:flex ">
{navigationOptions.map((item) => (
<SidebarItem key={item.href} item={item} currentPage={currentPage} />
))}
<MenuButton
menuItems={getMoreNavigation(user)}
buttonContent={<MoreButton />}
/>
{user && !user.isBannedFromPosting && <CreateQuestionButton />}
</div>
</nav>
)
}