Update PartyHeader and DropdownMenuItem

* Remove extraneous states and hooks from PartyHeader
* Only show PartyDropdown if we are looking at an existing party
* Add destructive prop for DropdownMenuItem
* Remove extraneous classes from PartyDropdown
* Localize dropdown contents
This commit is contained in:
Justin Edmund 2023-06-30 13:55:01 -07:00
parent 9a16574948
commit 0e10ac5a48
6 changed files with 69 additions and 114 deletions

View file

@ -50,7 +50,7 @@
} }
} }
& .destructive { &.destructive {
color: $error; color: $error;
&:hover { &:hover {

View file

@ -3,7 +3,13 @@ import classNames from 'classnames'
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
import styles from './index.module.scss' import styles from './index.module.scss'
interface Props extends DropdownMenuPrimitive.DropdownMenuItemProps {} interface Props extends DropdownMenuPrimitive.DropdownMenuItemProps {
destructive?: boolean
}
const defaultProps = {
destructive: false,
}
export const DropdownMenuItem = React.forwardRef<HTMLDivElement, Props>( export const DropdownMenuItem = React.forwardRef<HTMLDivElement, Props>(
function dropdownMenuItem( function dropdownMenuItem(
@ -13,6 +19,7 @@ export const DropdownMenuItem = React.forwardRef<HTMLDivElement, Props>(
const classes = classNames(props.className, { const classes = classNames(props.className, {
[styles.menuItem]: true, [styles.menuItem]: true,
[styles.language]: props.className?.includes('language'), [styles.language]: props.className?.includes('language'),
[styles.destructive]: props.destructive,
}) })
return ( return (
@ -27,4 +34,6 @@ export const DropdownMenuItem = React.forwardRef<HTMLDivElement, Props>(
} }
) )
DropdownMenuItem.defaultProps = defaultProps
export default DropdownMenuItem export default DropdownMenuItem

View file

@ -1,9 +1,8 @@
// Libraries // Libraries
import React, { useEffect, useState } from 'react' import React, { useState } from 'react'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { subscribe, useSnapshot } from 'valtio' import { useSnapshot } from 'valtio'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import classNames from 'classnames'
// Dependencies: Common // Dependencies: Common
import Button from '~components/common/Button' import Button from '~components/common/Button'
@ -11,9 +10,9 @@ import {
DropdownMenu, DropdownMenu,
DropdownMenuTrigger, DropdownMenuTrigger,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
} from '~components/common/DropdownMenuContent' } from '~components/common/DropdownMenuContent'
import DropdownMenuGroup from '~components/common/DropdownMenuGroup'
import DropdownMenuItem from '~components/common/DropdownMenuItem'
// Dependencies: Toasts // Dependencies: Toasts
import RemixedToast from '~components/toasts/RemixedToast' import RemixedToast from '~components/toasts/RemixedToast'
@ -24,12 +23,7 @@ import DeleteTeamAlert from '~components/dialogs/DeleteTeamAlert'
import RemixTeamAlert from '~components/dialogs/RemixTeamAlert' import RemixTeamAlert from '~components/dialogs/RemixTeamAlert'
// Dependencies: Utils // Dependencies: Utils
import api from '~utils/api'
import { accountState } from '~utils/accountState'
import { appState } from '~utils/appState' import { appState } from '~utils/appState'
import { getLocalId } from '~utils/localId'
import { retrieveLocaleCookies } from '~utils/retrieveCookies'
import { setEditKey, storeEditKey } from '~utils/userToken'
// Dependencies: Icons // Dependencies: Icons
import EllipsisIcon from '~public/icons/Ellipsis.svg' import EllipsisIcon from '~public/icons/Ellipsis.svg'
@ -51,9 +45,6 @@ const PartyDropdown = ({
// Router // Router
const router = useRouter() const router = useRouter()
const locale =
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
const localeData = retrieveLocaleCookies()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
@ -63,23 +54,9 @@ const PartyDropdown = ({
const [copyToastOpen, setCopyToastOpen] = useState(false) const [copyToastOpen, setCopyToastOpen] = useState(false)
const [remixToastOpen, setRemixToastOpen] = useState(false) const [remixToastOpen, setRemixToastOpen] = useState(false)
const [name, setName] = useState('')
const [originalName, setOriginalName] = useState('')
// Snapshots // Snapshots
const { account } = useSnapshot(accountState)
const { party: partySnapshot } = useSnapshot(appState) const { party: partySnapshot } = useSnapshot(appState)
// Subscribe to app state to listen for party name and
// unsubscribe when component is unmounted
const unsubscribe = subscribe(appState, () => {
const newName =
appState.party && appState.party.name ? appState.party.name : ''
setName(newName)
})
useEffect(() => () => unsubscribe(), [])
// Methods: Event handlers (Buttons) // Methods: Event handlers (Buttons)
function handleButtonClicked() { function handleButtonClicked() {
setOpen(!open) setOpen(!open)
@ -145,39 +122,37 @@ const PartyDropdown = ({
remixTeamCallback() remixTeamCallback()
} }
const editableItems = () => { const items = (
return (
<> <>
<DropdownMenuGroup className="MenuGroup"> <DropdownMenuGroup>
<DropdownMenuItem className="MenuItem" onClick={copyToClipboard}> <DropdownMenuItem onClick={copyToClipboard}>
<span>Copy link to team</span> <span>{t('dropdown.party.copy')}</span>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem className="MenuItem" onClick={openRemixTeamAlert}> <DropdownMenuItem onClick={openRemixTeamAlert}>
<span>Remix team</span> <span>{t('dropdown.party.remix')}</span>
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>
<DropdownMenuGroup className="MenuGroup"> <DropdownMenuGroup>
<DropdownMenuItem className="MenuItem" onClick={openDeleteTeamAlert}> <DropdownMenuItem destructive={true} onClick={openDeleteTeamAlert}>
<span className="destructive">Delete team</span> <span>{t('dropdown.party.delete')}</span>
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>
</> </>
) )
}
return ( return (
<> <>
<div id="DropdownWrapper"> <div className="dropdownWrapper">
<DropdownMenu open={open} onOpenChange={handleOpenChange}> <DropdownMenu open={open} onOpenChange={handleOpenChange}>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button
leftAccessoryIcon={<EllipsisIcon />} active={open}
className={classNames({ Active: open })}
blended={true} blended={true}
leftAccessoryIcon={<EllipsisIcon />}
onClick={handleButtonClicked} onClick={handleButtonClicked}
/> />
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent>{editableItems()}</DropdownMenuContent> <DropdownMenuContent>{items}</DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</div> </div>

View file

@ -17,6 +17,9 @@ import { accountState } from '~utils/accountState'
import { appState, initialAppState } from '~utils/appState' import { appState, initialAppState } from '~utils/appState'
import { formatTimeAgo } from '~utils/timeAgo' import { formatTimeAgo } from '~utils/timeAgo'
import RemixTeamAlert from '~components/dialogs/RemixTeamAlert'
import RemixedToast from '~components/toasts/RemixedToast'
import EditIcon from '~public/icons/Edit.svg' import EditIcon from '~public/icons/Edit.svg'
import RemixIcon from '~public/icons/Remix.svg' import RemixIcon from '~public/icons/Remix.svg'
import SaveIcon from '~public/icons/Save.svg' import SaveIcon from '~public/icons/Save.svg'
@ -24,9 +27,6 @@ import SaveIcon from '~public/icons/Save.svg'
import type { DetailsObject } from 'types' import type { DetailsObject } from 'types'
import styles from './index.module.scss' import styles from './index.module.scss'
import RemixTeamAlert from '~components/dialogs/RemixTeamAlert'
import RemixedToast from '~components/toasts/RemixedToast'
import { set } from 'local-storage'
// Props // Props
interface Props { interface Props {
@ -45,23 +45,15 @@ const PartyHeader = (props: Props) => {
const router = useRouter() const router = useRouter()
const locale = router.locale || 'en' const locale = router.locale || 'en'
const isNewParty =
router.asPath === '/' || router.asPath.split('/')[1] === 'new'
const { party: partySnapshot } = useSnapshot(appState) const { party: partySnapshot } = useSnapshot(appState)
// State: Component // State: Component
const [remixAlertOpen, setRemixAlertOpen] = useState(false) const [remixAlertOpen, setRemixAlertOpen] = useState(false)
const [remixToastOpen, setRemixToastOpen] = useState(false) const [remixToastOpen, setRemixToastOpen] = useState(false)
// State: Data
const [name, setName] = useState('')
const [chargeAttack, setChargeAttack] = useState(true)
const [fullAuto, setFullAuto] = useState(false)
const [autoGuard, setAutoGuard] = useState(false)
const [autoSummon, setAutoSummon] = useState(false)
const [buttonCount, setButtonCount] = useState<number | undefined>(undefined)
const [chainCount, setChainCount] = useState<number | undefined>(undefined)
const [turnCount, setTurnCount] = useState<number | undefined>(undefined)
const [clearTime, setClearTime] = useState(0)
const userClass = classNames({ const userClass = classNames({
[styles.user]: true, [styles.user]: true,
[styles.empty]: !party.user, [styles.empty]: !party.user,
@ -76,39 +68,6 @@ const PartyHeader = (props: Props) => {
light: party && party.element == 6, light: party && party.element == 6,
}) })
useEffect(() => {
if (props.party) {
setName(props.party.name)
setAutoGuard(props.party.auto_guard)
setFullAuto(props.party.full_auto)
setAutoSummon(props.party.auto_summon)
setChargeAttack(props.party.charge_attack)
setClearTime(props.party.clear_time)
if (props.party.turn_count) setTurnCount(props.party.turn_count)
if (props.party.button_count) setButtonCount(props.party.button_count)
if (props.party.chain_count) setChainCount(props.party.chain_count)
}
}, [props.party])
// Subscribe to router changes and reset state
// if the new route is a new team
useEffect(() => {
router.events.on('routeChangeStart', (url, { shallow }) => {
if (url === '/new' || url === '/') {
const party = initialAppState.party
setName(party.name ? party.name : '')
setAutoGuard(party.autoGuard)
setFullAuto(party.fullAuto)
setChargeAttack(party.chargeAttack)
setClearTime(party.clearTime)
setTurnCount(party.turnCount)
setButtonCount(party.buttonCount)
setChainCount(party.chainCount)
}
})
}, [])
// Actions: Favorites // Actions: Favorites
function toggleFavorite() { function toggleFavorite() {
if (appState.party.favorited) unsaveFavorite() if (appState.party.favorited) unsaveFavorite()
@ -255,7 +214,7 @@ const PartyHeader = (props: Props) => {
) )
const autoGuardToken = ( const autoGuardToken = (
<Token active className="autoGuard"> <Token active={party.autoGuard} className="autoGuard">
{`${t('party.details.labels.auto_guard')} ${ {`${t('party.details.labels.auto_guard')} ${
party.autoGuard ? 'On' : 'Off' party.autoGuard ? 'On' : 'Off'
}`} }`}
@ -326,8 +285,8 @@ const PartyHeader = (props: Props) => {
{fullAutoToken} {fullAutoToken}
{autoSummonToken} {autoSummonToken}
{autoGuardToken} {autoGuardToken}
{party.turnCount ? turnCountToken : ''} {party.turnCount && turnCountToken}
{party.clearTime > 0 ? clearTimeToken() : ''} {party.clearTime > 0 && clearTimeToken()}
{buttonChainToken()} {buttonChainToken()}
</> </>
) )
@ -374,7 +333,7 @@ const PartyHeader = (props: Props) => {
<h1 className={party.name ? '' : 'empty'}> <h1 className={party.name ? '' : 'empty'}>
{party.name ? party.name : t('no_title')} {party.name ? party.name : t('no_title')}
</h1> </h1>
{party.remix && party.sourceParty ? ( {party.remix && party.sourceParty && (
<Tooltip content={t('tooltips.source')}> <Tooltip content={t('tooltips.source')}>
<Button <Button
blended={true} blended={true}
@ -384,22 +343,18 @@ const PartyHeader = (props: Props) => {
onClick={() => goTo(party.sourceParty?.shortcode)} onClick={() => goTo(party.sourceParty?.shortcode)}
/> />
</Tooltip> </Tooltip>
) : (
''
)} )}
</div> </div>
<div className={styles.attribution}> <div className={styles.attribution}>
{renderUserBlock()} {renderUserBlock()}
{appState.party.raid ? linkedRaidBlock(appState.party.raid) : ''} {appState.party.raid && linkedRaidBlock(appState.party.raid)}
{party.created_at != '' ? ( {party.created_at != '' && (
<time <time
className={styles.lastUpdated} className={styles.lastUpdated}
dateTime={new Date(party.created_at).toString()} dateTime={new Date(party.created_at).toString()}
> >
{formatTimeAgo(new Date(party.created_at), locale)} {formatTimeAgo(new Date(party.created_at), locale)}
</time> </time>
) : (
''
)} )}
</div> </div>
</div> </div>
@ -414,11 +369,13 @@ const PartyHeader = (props: Props) => {
text={t('buttons.show_info')} text={t('buttons.show_info')}
/> />
</EditPartyModal> </EditPartyModal>
{!isNewParty && (
<PartyDropdown <PartyDropdown
editable={props.editable} editable={props.editable}
deleteTeamCallback={props.deleteCallback} deleteTeamCallback={props.deleteCallback}
remixTeamCallback={props.remixCallback} remixTeamCallback={props.remixCallback}
/> />
)}
</div> </div>
) : ( ) : (
<div className={styles.right}> <div className={styles.right}>

View file

@ -58,6 +58,13 @@
"remove": "Remove from grid", "remove": "Remove from grid",
"remove_job_skill": "Remove class skill" "remove_job_skill": "Remove class skill"
}, },
"dropdown": {
"party": {
"copy": "Copy link to team",
"delete": "Delete team",
"remix": "Remix team"
}
},
"elements": { "elements": {
"null": "Null", "null": "Null",
"wind": "Wind", "wind": "Wind",

View file

@ -58,6 +58,13 @@
"remove": "編成から削除", "remove": "編成から削除",
"remove_job_skill": "ジョブスキルを削除" "remove_job_skill": "ジョブスキルを削除"
}, },
"dropdown": {
"party": {
"copy": "編成のリンクをコピー",
"delete": "編成を削除",
"remix": "編成をリミックス"
}
},
"elements": { "elements": {
"null": "無", "null": "無",
"wind": "風", "wind": "風",