Implement new header design (so far)

This commit is contained in:
Justin Edmund 2023-01-27 20:30:39 -08:00
parent ceb87a8016
commit fa1bf57964
5 changed files with 278 additions and 223 deletions

View file

@ -286,9 +286,9 @@ const AccountModal = (props: Props) => {
return ( return (
<Dialog open={open} onOpenChange={openChange}> <Dialog open={open} onOpenChange={openChange}>
<DialogTrigger asChild> <DialogTrigger asChild>
<li className="MenuItem"> <div className="MenuItem">
<span>{t('menu.settings')}</span> <span>{t('menu.settings')}</span>
</li> </div>
</DialogTrigger> </DialogTrigger>
<DialogContent <DialogContent
className="Account" className="Account"

View file

@ -138,6 +138,10 @@
display: flex; display: flex;
&.Arrow {
margin-top: $unit-half;
}
svg { svg {
fill: var(--button-text); fill: var(--button-text);
height: $dimension; height: $dimension;

View file

@ -5,11 +5,18 @@
justify-content: space-between; justify-content: space-between;
width: 100%; width: 100%;
#Right > div { section {
display: flex; display: flex;
gap: $unit; gap: $unit;
} }
img {
$diameter: 32px;
border-radius: calc($diameter / 2);
height: $diameter;
width: $diameter;
}
#DropdownWrapper { #DropdownWrapper {
display: inline-block; display: inline-block;
padding-bottom: $unit; padding-bottom: $unit;
@ -20,7 +27,7 @@
} }
&:hover { &:hover {
padding-right: $unit-4x; // padding-right: $unit-4x;
.Button { .Button {
background: var(--button-bg-hover); background: var(--button-bg-hover);

View file

@ -3,12 +3,13 @@ import { useSnapshot } from 'valtio'
import { deleteCookie } from 'cookies-next' import { deleteCookie } from 'cookies-next'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import classNames from 'classnames'
import clonedeep from 'lodash.clonedeep' import clonedeep from 'lodash.clonedeep'
import api from '~utils/api' import api from '~utils/api'
import { accountState, initialAccountState } from '~utils/accountState' import { accountState, initialAccountState } from '~utils/accountState'
import { appState, initialAppState } from '~utils/appState' import { appState, initialAppState } from '~utils/appState'
import capitalizeFirstLetter from '~utils/capitalizeFirstLetter'
import Button from '~components/Button' import Button from '~components/Button'
import HeaderMenu from '~components/HeaderMenu' import HeaderMenu from '~components/HeaderMenu'
@ -16,10 +17,22 @@ import HeaderMenu from '~components/HeaderMenu'
import AddIcon from '~public/icons/Add.svg' import AddIcon from '~public/icons/Add.svg'
import LinkIcon from '~public/icons/Link.svg' import LinkIcon from '~public/icons/Link.svg'
import MenuIcon from '~public/icons/Menu.svg' import MenuIcon from '~public/icons/Menu.svg'
import ArrowIcon from '~public/icons/Arrow.svg'
import SaveIcon from '~public/icons/Save.svg' import SaveIcon from '~public/icons/Save.svg'
import classNames from 'classnames'
import './index.scss' import './index.scss'
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
} from '~components/DropdownMenuContent'
import Link from 'next/link'
import LoginModal from '~components/LoginModal'
import SignupModal from '~components/SignupModal'
import AccountModal from '~components/AccountModal'
const Header = () => { const Header = () => {
// Localization // Localization
@ -30,19 +43,40 @@ const Header = () => {
// State management // State management
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [leftMenuOpen, setLeftMenuOpen] = useState(false)
const [rightMenuOpen, setRightMenuOpen] = useState(false)
// Snapshots // Snapshots
const { account } = useSnapshot(accountState) const { account } = useSnapshot(accountState)
const { party } = useSnapshot(appState) const { party } = useSnapshot(appState)
function menuButtonClicked() { function handleLeftMenuButtonClicked() {
setOpen(!open) setLeftMenuOpen(!leftMenuOpen)
}
function handleRightMenuButtonClicked() {
setRightMenuOpen(!rightMenuOpen)
} }
function onClickOutsideMenu() { function onClickOutsideMenu() {
setOpen(false) setOpen(false)
} }
function handleLeftMenuOpenChange(open: boolean) {
setLeftMenuOpen(open)
}
function handleRightMenuOpenChange(open: boolean) {
setRightMenuOpen(open)
}
function closeLeftMenu() {
setLeftMenuOpen(false)
}
function closeRightMenu() {
setRightMenuOpen(false)
}
function copyToClipboard() { function copyToClipboard() {
const el = document.createElement('input') const el = document.createElement('input')
el.value = window.location.href el.value = window.location.href
@ -69,6 +103,10 @@ const Header = () => {
} }
function logout() { function logout() {
// Close menu
closeRightMenu()
// Delete cookies
deleteCookie('account') deleteCookie('account')
deleteCookie('user') deleteCookie('user')
@ -107,7 +145,7 @@ const Header = () => {
if (router.route === '/p/[party]') if (router.route === '/p/[party]')
return ( return (
<Button <Button
accessoryIcon={<LinkIcon className="stroke" />} leftAccessoryIcon={<LinkIcon className="stroke" />}
blended={true} blended={true}
text={t('buttons.copy')} text={t('buttons.copy')}
onClick={copyToClipboard} onClick={copyToClipboard}
@ -115,24 +153,39 @@ const Header = () => {
) )
} }
const leftNav = () => { const title = () => {
let title = ''
let hasAccessory = false
const path = router.asPath.split('/')[1]
console.log(router.asPath.split('/'))
if (path === 'p') {
hasAccessory = true
if (appState.party && appState.party.name) {
title = appState.party.name
} else {
title = t('no_title')
}
} else if (['weapons', 'summons', 'characters', 'new', ''].includes(path)) {
title = t('new_party')
} else if (
['about', 'updates', 'roadmap', 'saved', 'teams'].includes(path)
) {
title = capitalizeFirstLetter(path)
} else {
title = path
}
return ( return (
<div id="DropdownWrapper"> <Button
<Button blended={true}
accessoryIcon={<MenuIcon />} rightAccessoryIcon={
className={classNames({ Active: open })} path === 'p' && hasAccessory ? (
blended={true} <LinkIcon className="stroke" />
text={t('buttons.menu')} ) : undefined
onClick={menuButtonClicked} }
/> text={title}
<HeaderMenu />
authenticated={account.authorized}
open={open}
username={account.user?.username}
onClickOutside={onClickOutsideMenu}
logout={logout}
/>
</div>
) )
} }
@ -140,7 +193,7 @@ const Header = () => {
if (party.favorited) if (party.favorited)
return ( return (
<Button <Button
accessoryIcon={<SaveIcon />} leftAccessoryIcon={<SaveIcon />}
blended={true} blended={true}
text="Saved" text="Saved"
onClick={toggleFavorite} onClick={toggleFavorite}
@ -149,7 +202,7 @@ const Header = () => {
else else
return ( return (
<Button <Button
accessoryIcon={<SaveIcon />} leftAccessoryIcon={<SaveIcon />}
blended={true} blended={true}
text="Save" text="Save"
onClick={toggleFavorite} onClick={toggleFavorite}
@ -157,9 +210,56 @@ const Header = () => {
) )
} }
const rightNav = () => { const image = () => {
let image
const user = accountState.account.user
if (accountState.account.authorized && user) {
image = (
<img
alt={user.username}
className={`profile ${user.element}`}
srcSet={`/profile/${user.picture}.png,
/profile/${user.picture}@2x.png 2x`}
src={`/profile/${user.picture}.png`}
/>
)
} else {
image = <img alt={t('no_user')} className="profile placeholder" />
}
return image
}
const left = () => {
return ( return (
<div> <section>
<div id="DropdownWrapper">
<DropdownMenu
open={leftMenuOpen}
onOpenChange={handleLeftMenuOpenChange}
>
<DropdownMenuTrigger asChild>
<Button
leftAccessoryIcon={<MenuIcon />}
className={classNames({ Active: leftMenuOpen })}
blended={true}
onClick={handleLeftMenuButtonClicked}
/>
</DropdownMenuTrigger>
<DropdownMenuContent className="Left">
{leftMenuItems()}
</DropdownMenuContent>
</DropdownMenu>
</div>
{title()}
</section>
)
}
const right = () => {
return (
<section>
{router.route === '/p/[party]' && {router.route === '/p/[party]' &&
account.user && account.user &&
(!party.user || party.user.id !== account.user.id) (!party.user || party.user.id !== account.user.id)
@ -168,20 +268,151 @@ const Header = () => {
{copyButton()} {copyButton()}
<Button {/* <Button
accessoryIcon={<AddIcon className="Add" />} leftAccessoryIcon={<AddIcon className="Add" />}
blended={true} blended={true}
text={t('buttons.new')} text={t('buttons.new')}
onClick={newParty} onClick={newParty}
/> /> */}
</div>
<DropdownMenu
open={rightMenuOpen}
onOpenChange={handleRightMenuOpenChange}
>
<DropdownMenuTrigger asChild>
<Button
className={classNames({ Active: rightMenuOpen })}
leftAccessoryIcon={image()}
rightAccessoryIcon={<ArrowIcon />}
rightAccessoryClassName="Arrow"
onClick={handleRightMenuButtonClicked}
blended={true}
/>
</DropdownMenuTrigger>
<DropdownMenuContent className="Right">
{rightMenuItems()}
</DropdownMenuContent>
</DropdownMenu>
</section>
) )
} }
const leftMenuItems = () => {
return (
<>
{accountState.account.authorized && accountState.account.user ? (
<>
<DropdownMenuGroup className="MenuGroup">
<DropdownMenuItem className="MenuItem" onClick={closeRightMenu}>
<Link
href={`/${accountState.account.user.username}` || ''}
passHref
>
Your profile
</Link>
</DropdownMenuItem>
<DropdownMenuItem className="MenuItem" onClick={closeLeftMenu}>
<Link href={`/saved` || ''}>{t('menu.saved')}</Link>
</DropdownMenuItem>
</DropdownMenuGroup>
</>
) : (
''
)}
<DropdownMenuGroup className="MenuGroup">
<DropdownMenuItem className="MenuItem" onClick={closeLeftMenu}>
<Link href="/teams">{t('menu.teams')}</Link>
</DropdownMenuItem>
<DropdownMenuItem className="MenuItem">
<div>
<span>{t('menu.guides')}</span>
<i className="tag">{t('coming_soon')}</i>
</div>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuGroup className="MenuGroup">
<DropdownMenuItem className="MenuItem" onClick={closeLeftMenu}>
<a href="/about" target="_blank">
{t('about.segmented_control.about')}
</a>
</DropdownMenuItem>
<DropdownMenuItem className="MenuItem" onClick={closeLeftMenu}>
<a href="/updates" target="_blank">
{t('about.segmented_control.updates')}
</a>
</DropdownMenuItem>
<DropdownMenuItem className="MenuItem" onClick={closeLeftMenu}>
<a href="/roadmap" target="_blank">
{t('about.segmented_control.roadmap')}
</a>
</DropdownMenuItem>
</DropdownMenuGroup>
</>
)
}
const rightMenuItems = () => {
let items
const account = accountState.account
if (account.authorized && account.user) {
items = (
<>
<DropdownMenuGroup className="MenuGroup">
<DropdownMenuItem className="MenuItem" onClick={closeRightMenu}>
<Link href={`/new` || ''} passHref>
New party
</Link>
</DropdownMenuItem>
<DropdownMenuItem className="MenuItem" onClick={closeRightMenu}>
<Link href={`/${account.user.username}` || ''} passHref>
Your profile
</Link>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup className="MenuGroup">
<DropdownMenuItem
className="MenuItem"
onClick={closeRightMenu}
asChild
>
<AccountModal
username={account.user.username}
picture={account.user.picture}
gender={account.user.gender}
language={account.user.language}
theme={account.user.theme}
/>
</DropdownMenuItem>
<DropdownMenuItem className="MenuItem" onClick={logout}>
<span>{t('menu.logout')}</span>
</DropdownMenuItem>
</DropdownMenuGroup>
</>
)
} else {
items = (
<>
<DropdownMenuGroup className="MenuGroup">
<DropdownMenuItem className="MenuItem">New party</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup className="MenuGroup">
<LoginModal />
<SignupModal />
</DropdownMenuGroup>
</>
)
}
return items
}
return ( return (
<nav id="Header"> <nav id="Header">
<div id="Left">{leftNav()}</div> {left()}
<div id="Right">{rightNav()}</div> {right()}
</nav> </nav>
) )
} }

View file

@ -1,187 +0,0 @@
.Menu {
background: var(--menu-bg);
border-radius: 6px;
box-sizing: border-box;
display: none;
min-width: 220px;
position: absolute;
top: $unit-8x; // This shouldn't be hardcoded. How to calculate it?
// Also, add space that doesn't make the menu disappear if you move your mouse slowly
z-index: 10;
@include breakpoint(phone) {
left: $unit-2x;
right: $unit-2x;
}
}
.MenuItem {
color: var(--text-tertiary);
font-weight: $normal;
@include breakpoint(phone) {
cursor: pointer;
}
&:hover:not(.disabled) {
background: var(--menu-bg-item-hover);
color: var(--text-primary);
cursor: pointer;
a {
color: var(--text-primary);
&:hover {
text-decoration: none;
}
&:visited {
color: var(--text-primary);
}
}
@include breakpoint(phone) {
background: inherit;
color: inherit;
cursor: default;
a {
color: inherit;
}
}
}
&.profile > div {
padding: 6px 12px;
}
&.language {
align-items: center;
display: flex;
flex-direction: row;
gap: $unit;
padding-right: $unit;
span {
flex-grow: 1;
}
.Switch {
$height: 24px;
background: $grey-60;
border-radius: calc($height / 2);
border: none;
position: relative;
width: 44px;
height: $height;
&:hover {
cursor: pointer;
}
.Thumb {
$diameter: 18px;
background: $grey-100;
border-radius: calc($diameter / 2);
display: block;
height: $diameter;
width: $diameter;
position: absolute;
top: 3px;
left: 3px;
z-index: 3;
&:hover {
cursor: pointer;
}
&[data-state='checked'] {
background: $grey-100;
left: 23px;
}
}
.left,
.right {
color: $grey-100;
font-size: 10px;
font-weight: $bold;
position: absolute;
z-index: 2;
}
.left {
top: 6px;
left: 6px;
}
.right {
top: 6px;
right: 5px;
}
}
}
a {
color: $grey-50;
&:hover {
text-decoration: none;
}
&:visited {
color: $grey-50;
}
}
& > a,
& > span {
display: block;
padding: 12px 12px;
}
& > div {
align-items: center;
display: flex;
flex-direction: row;
padding: 10px 12px;
&:hover {
i.tag {
background: var(--tag-bg);
color: var(--tag-text);
}
}
span {
flex-grow: 1;
}
img {
$diameter: 32px;
border-radius: calc($diameter / 2);
height: $diameter;
width: $diameter;
}
}
}
.MenuGroup {
border-bottom: 1px solid var(--menu-separator);
&:first-child .MenuItem:first-child:hover {
border-top-left-radius: 6px;
border-top-right-radius: 6px;
}
&:last-child .MenuItem:last-child:hover {
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
}
&:last-child {
border-bottom: none;
}
}