From 2d368c32cc103c04c79be9f7522a794c253a851f Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 19 Jun 2023 02:35:26 -0700 Subject: [PATCH] Implement removing job skills We added a (...) button next to each editable job skill that opens a context menu that will allow the user to remove the job skill. An alert is presented to make sure the user is sure before proceeding. As part of this change, some minor restyling of JobSkillItem was necessary --- components/character/CharacterGrid/index.tsx | 18 +++ components/job/JobSection/index.scss | 5 +- components/job/JobSection/index.tsx | 16 ++- components/job/JobSkillItem/index.scss | 94 +++++++++----- components/job/JobSkillItem/index.tsx | 127 ++++++++++++++++--- 5 files changed, 206 insertions(+), 54 deletions(-) diff --git a/components/character/CharacterGrid/index.tsx b/components/character/CharacterGrid/index.tsx index 958d374e..048ba655 100644 --- a/components/character/CharacterGrid/index.tsx +++ b/components/character/CharacterGrid/index.tsx @@ -259,6 +259,23 @@ const CharacterGrid = (props: Props) => { } } + function removeJobSkill(position: number) { + if (party.id && props.editable) { + api + .removeJobSkill({ partyId: party.id, position: position }) + .then((response) => { + // Update the current skills + const newSkills = response.data.job_skills + setJobSkills(newSkills) + appState.party.jobSkills = newSkills + }) + .catch((error) => { + const data = error.response.data + console.log(data) + }) + } + } + async function saveAccessory(accessory: JobAccessory) { const payload = { party: { @@ -506,6 +523,7 @@ const CharacterGrid = (props: Props) => { editable={props.editable} saveJob={saveJob} saveSkill={saveJobSkill} + removeSkill={removeJobSkill} saveAccessory={saveAccessory} /> void saveSkill: (skill: JobSkill, position: number) => void + removeSkill: (position: number) => void saveAccessory: (accessory: JobAccessory) => void } @@ -48,6 +50,12 @@ const JobSection = (props: Props) => { // Refs const selectRef = React.createRef() + // Classes + const skillContainerClasses = classNames({ + JobSkills: true, + editable: props.editable, + }) + useEffect(() => { // Set current job based on ID setJob(props.job) @@ -126,9 +134,11 @@ const JobSection = (props: Props) => { return ( ) } @@ -173,10 +183,6 @@ const JobSection = (props: Props) => { ) - function jobLabel() { - return job ? filledJobLabel : emptyJobLabel - } - // Render: JSX components return (
@@ -209,7 +215,7 @@ const JobSection = (props: Props) => { )} -
    +
      {[...Array(numSkills)].map((e, i) => (
    • {canEditSkill(skills[i]) diff --git a/components/job/JobSkillItem/index.scss b/components/job/JobSkillItem/index.scss index 9a15bb62..df2fa67b 100644 --- a/components/job/JobSkillItem/index.scss +++ b/components/job/JobSkillItem/index.scss @@ -1,47 +1,81 @@ +.JobSkills { + &.editable .JobSkill { + .Info { + padding: $unit-half * 1.5; + + & > img, + & > div.placeholder { + width: $unit-4x; + height: $unit-4x; + } + } + } +} + .JobSkill { display: flex; - gap: $unit; - align-items: center; + align-items: stretch; + justify-content: space-between; + + &.editable .Info:hover { + background-color: var(--button-bg-hover); + } &.editable:hover { cursor: pointer; - & > img.editable, - & > div.placeholder.editable { - border: $hover-stroke; - box-shadow: $hover-shadow; - cursor: pointer; - transform: $scale-tall; - } + .Info { + & > img.editable, + & > div.placeholder.editable { + border: $hover-stroke; + box-shadow: $hover-shadow; + cursor: pointer; + transform: $scale-tall; + } - & p.placeholder { - color: var(--text-tertiary-hover); - } + & p.placeholder { + color: var(--text-tertiary-hover); + } - & svg { - fill: var(--icon-secondary-hover); + & svg { + fill: var(--icon-secondary-hover); + } } } - & > img, - & > div.placeholder { - background: var(--card-bg); - border-radius: calc($unit / 2); - border: 1px solid rgba(0, 0, 0, 0); - width: $unit * 5; - height: $unit * 5; - } - - & > div.placeholder { - display: flex; + .Info { align-items: center; - justify-content: center; + border-radius: $input-corner; + display: flex; + flex-grow: 1; + gap: $unit; - & > svg { - fill: var(--icon-secondary); - width: $unit * 2; - height: $unit * 2; + & > img, + & > div.placeholder { + background: var(--card-bg); + border-radius: calc($unit / 2); + border: 1px solid rgba(0, 0, 0, 0); + width: $unit-5x; + height: $unit-5x; } + + & > div.placeholder { + display: flex; + align-items: center; + justify-content: center; + + & > svg { + fill: var(--icon-secondary); + width: $unit-2x; + height: $unit-2x; + } + } + } + + & > .Button { + justify-content: center; + max-width: $unit-6x; + height: auto; } p { diff --git a/components/job/JobSkillItem/index.tsx b/components/job/JobSkillItem/index.tsx index bac8f729..a1ded0d3 100644 --- a/components/job/JobSkillItem/index.tsx +++ b/components/job/JobSkillItem/index.tsx @@ -1,21 +1,43 @@ -import React from 'react' +import React, { useState } from 'react' import { useRouter } from 'next/router' -import { useTranslation } from 'next-i18next' - +import { Trans, useTranslation } from 'next-i18next' import classNames from 'classnames' -import PlusIcon from '~public/icons/Add.svg' +import Alert from '~components/common/Alert' +import Button from '~components/common/Button' +import { + ContextMenu, + ContextMenuTrigger, + ContextMenuContent, +} from '~components/common/ContextMenu' +import ContextMenuItem from '~components/common/ContextMenuItem' + +import EllipsisIcon from '~public/icons/Ellipsis.svg' +import PlusIcon from '~public/icons/Add.svg' import './index.scss' // Props interface Props extends React.ComponentPropsWithoutRef<'div'> { skill?: JobSkill + position: number editable: boolean hasJob: boolean + removeJobSkill: (position: number) => void } const JobSkillItem = React.forwardRef( - function useJobSkillItem({ ...props }, forwardedRef) { + function useJobSkillItem( + { + skill, + position, + editable, + hasJob, + removeJobSkill: sendJobSkillToRemove, + ...props + }, + forwardedRef + ) { + // Set up translation const router = useRouter() const { t } = useTranslation('common') const locale = @@ -23,31 +45,55 @@ const JobSkillItem = React.forwardRef( ? router.locale : 'en' + // States: Component + const [alertOpen, setAlertOpen] = useState(false) + const [contextMenuOpen, setContextMenuOpen] = useState(false) + + // Classes const classes = classNames({ JobSkill: true, - editable: props.editable, + editable: editable, }) const imageClasses = classNames({ - placeholder: !props.skill, - editable: props.editable && props.hasJob, + placeholder: !skill, + editable: editable && hasJob, }) + const buttonClasses = classNames({ + Clicked: contextMenuOpen, + }) + + // Methods: Data mutation + function removeJobSkill() { + if (skill) sendJobSkillToRemove(position) + setAlertOpen(false) + } + + // Methods: Context menu + function handleButtonClicked() { + setContextMenuOpen(!contextMenuOpen) + } + + function handleContextMenuOpenChange(open: boolean) { + if (!open) setContextMenuOpen(false) + } + const skillImage = () => { let jsx: React.ReactNode - if (props.skill) { + if (skill) { jsx = ( {props.skill.name[locale]} ) } else { jsx = (
      - {props.editable && props.hasJob ? : ''} + {editable && hasJob ? : ''}
      ) } @@ -58,9 +104,9 @@ const JobSkillItem = React.forwardRef( const label = () => { let jsx: React.ReactNode - if (props.skill) { - jsx =

      {props.skill.name[locale]}

      - } else if (props.editable && props.hasJob) { + if (skill) { + jsx =

      {skill.name[locale]}

      + } else if (editable && hasJob) { jsx =

      {t('job_skills.state.selectable')}

      } else { jsx =

      {t('job_skills.state.no_skill')}

      @@ -69,10 +115,55 @@ const JobSkillItem = React.forwardRef( return jsx } + const removeAlert = () => { + return ( + setAlertOpen(false)} + cancelActionText={t('buttons.cancel')} + message={ + + Are you sure you want to remove{' '} + {{ job_skill: skill?.name[locale] }} from your + team? + + } + /> + ) + } + + const contextMenu = () => { + return ( + <> + + +