Merge pull request #198 from jedmund/hovercard-revamp
Revamp hovercards
This commit is contained in:
commit
12976eab34
14 changed files with 743 additions and 313 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -54,6 +54,7 @@ public/images/job*
|
||||||
public/images/awakening*
|
public/images/awakening*
|
||||||
public/images/ax*
|
public/images/ax*
|
||||||
public/images/accessory*
|
public/images/accessory*
|
||||||
|
public/images/mastery*
|
||||||
|
|
||||||
# Typescript v1 declaration files
|
# Typescript v1 declaration files
|
||||||
typings/
|
typings/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
.Character.HovercardContent {
|
||||||
|
.title .Image {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.Perpetuity {
|
||||||
|
position: absolute;
|
||||||
|
background-image: url('/icons/perpetuity/filled.svg');
|
||||||
|
background-size: $unit-3x $unit-3x;
|
||||||
|
z-index: 20;
|
||||||
|
top: $unit-half * -1;
|
||||||
|
right: $unit-3x;
|
||||||
|
width: $unit-3x;
|
||||||
|
height: $unit-3x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Mastery {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit-half;
|
||||||
|
|
||||||
|
.ExtendedMastery {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: $unit-half;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: $unit-3x;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: $bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Awakening {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit;
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: $unit-half;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: $unit-3x;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: $bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// .Footer {
|
||||||
|
// position: sticky;
|
||||||
|
// bottom: 0;
|
||||||
|
// left: 0;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,31 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
|
import jconv from 'jconv'
|
||||||
|
|
||||||
import * as HoverCard from '@radix-ui/react-hover-card'
|
import {
|
||||||
|
Hovercard,
|
||||||
|
HovercardContent,
|
||||||
|
HovercardTrigger,
|
||||||
|
} from '~components/Hovercard'
|
||||||
|
import Button from '~components/Button'
|
||||||
import WeaponLabelIcon from '~components/WeaponLabelIcon'
|
import WeaponLabelIcon from '~components/WeaponLabelIcon'
|
||||||
import UncapIndicator from '~components/UncapIndicator'
|
import UncapIndicator from '~components/UncapIndicator'
|
||||||
|
|
||||||
|
import {
|
||||||
|
overMastery,
|
||||||
|
aetherialMastery,
|
||||||
|
permanentMastery,
|
||||||
|
} from '~data/overMastery'
|
||||||
|
import { characterAwakening } from '~data/awakening'
|
||||||
|
import { ExtendedMastery } from '~types'
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
gridCharacter: GridCharacter
|
gridCharacter: GridCharacter
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
|
onTriggerClick: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
interface KeyNames {
|
interface KeyNames {
|
||||||
|
|
@ -43,10 +57,19 @@ const CharacterHovercard = (props: Props) => {
|
||||||
]
|
]
|
||||||
|
|
||||||
const tintElement = Element[props.gridCharacter.object.element]
|
const tintElement = Element[props.gridCharacter.object.element]
|
||||||
const wikiUrl = `https://gbf.wiki/${props.gridCharacter.object.name.en.replaceAll(
|
|
||||||
' ',
|
function goTo() {
|
||||||
'_'
|
const urlSafeName = props.gridCharacter.object.name.en.replaceAll(' ', '_')
|
||||||
)}`
|
const url = `https://gbf.wiki/${urlSafeName}`
|
||||||
|
|
||||||
|
window.open(url, '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
|
const perpetuity = () => {
|
||||||
|
if (props.gridCharacter && props.gridCharacter.perpetuity) {
|
||||||
|
return <i className="Perpetuity" />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function characterImage() {
|
function characterImage() {
|
||||||
let imgSrc = ''
|
let imgSrc = ''
|
||||||
|
|
@ -66,59 +89,194 @@ const CharacterHovercard = (props: Props) => {
|
||||||
return imgSrc
|
return imgSrc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function masteryElement(dictionary: ItemSkill[], mastery: ExtendedMastery) {
|
||||||
|
const canonicalMastery = dictionary.find(
|
||||||
|
(item) => item.id === mastery.modifier
|
||||||
|
)
|
||||||
|
|
||||||
|
if (canonicalMastery) {
|
||||||
|
return (
|
||||||
|
<li className="ExtendedMastery" key={canonicalMastery.id}>
|
||||||
|
<img
|
||||||
|
alt={canonicalMastery.name[locale]}
|
||||||
|
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/mastery/${canonicalMastery.slug}.png`}
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
<strong>{canonicalMastery.name[locale]}</strong>
|
||||||
|
{`+${mastery.strength}${canonicalMastery.suffix}`}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const overMasterySection = () => {
|
||||||
|
if (props.gridCharacter && props.gridCharacter.over_mastery) {
|
||||||
|
return (
|
||||||
|
<section className="Mastery">
|
||||||
|
<h5 className={tintElement}>
|
||||||
|
{t('modals.characters.subtitles.ring')}
|
||||||
|
</h5>
|
||||||
|
<ul>
|
||||||
|
{[...Array(4)].map((e, i) => {
|
||||||
|
const ringIndex = i + 1
|
||||||
|
const ringStat: ExtendedMastery =
|
||||||
|
props.gridCharacter.over_mastery[i]
|
||||||
|
if (ringStat && ringStat.modifier && ringStat.modifier > 0) {
|
||||||
|
if (ringIndex === 1 || ringIndex === 2) {
|
||||||
|
return masteryElement(overMastery.a, ringStat)
|
||||||
|
} else if (ringIndex === 3) {
|
||||||
|
return masteryElement(overMastery.b, ringStat)
|
||||||
|
} else {
|
||||||
|
return masteryElement(overMastery.c, ringStat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const aetherialMasterySection = () => {
|
||||||
|
if (
|
||||||
|
props.gridCharacter &&
|
||||||
|
props.gridCharacter.aetherial_mastery &&
|
||||||
|
props.gridCharacter.aetherial_mastery.modifier > 0
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<section className="Mastery">
|
||||||
|
<h5 className={tintElement}>
|
||||||
|
{t('modals.characters.subtitles.earring')}
|
||||||
|
</h5>
|
||||||
|
<ul>
|
||||||
|
{masteryElement(
|
||||||
|
aetherialMastery,
|
||||||
|
props.gridCharacter.aetherial_mastery
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const permanentMasterySection = () => {
|
||||||
|
if (props.gridCharacter && props.gridCharacter.perpetuity) {
|
||||||
|
return (
|
||||||
|
<section className="Mastery">
|
||||||
|
<h5 className={tintElement}>
|
||||||
|
{t('modals.characters.subtitles.permanent')}
|
||||||
|
</h5>
|
||||||
|
<ul>
|
||||||
|
{[...Array(4)].map((e, i) => {
|
||||||
|
return masteryElement(permanentMastery, {
|
||||||
|
modifier: i + 1,
|
||||||
|
strength: permanentMastery[i].maxValue,
|
||||||
|
})
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const awakeningSection = () => {
|
||||||
|
const gridAwakening = props.gridCharacter.awakening
|
||||||
|
const awakening = characterAwakening.find(
|
||||||
|
(awakening) => awakening.id === gridAwakening?.type
|
||||||
|
)
|
||||||
|
|
||||||
|
if (gridAwakening && awakening) {
|
||||||
|
return (
|
||||||
|
<section className="Awakening">
|
||||||
|
<h5 className={tintElement}>
|
||||||
|
{t('modals.characters.subtitles.awakening')}
|
||||||
|
</h5>
|
||||||
|
<div>
|
||||||
|
{gridAwakening.type > 1 ? (
|
||||||
|
<img
|
||||||
|
alt={awakening.name[locale]}
|
||||||
|
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/awakening/character_${gridAwakening.type}.jpg`}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
<span>
|
||||||
|
<strong>{`${awakening.name[locale]}`}</strong>
|
||||||
|
{`Lv${gridAwakening.level}`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const wikiButton = (
|
||||||
|
<Button
|
||||||
|
className={tintElement}
|
||||||
|
text={t('buttons.wiki')}
|
||||||
|
onClick={goTo}
|
||||||
|
contained={true}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HoverCard.Root>
|
<Hovercard openDelay={350}>
|
||||||
<HoverCard.Trigger>{props.children}</HoverCard.Trigger>
|
<HovercardTrigger asChild onClick={props.onTriggerClick}>
|
||||||
<HoverCard.Portal>
|
{props.children}
|
||||||
<HoverCard.Content className="Weapon Hovercard">
|
</HovercardTrigger>
|
||||||
<div className="top">
|
<HovercardContent className="Character">
|
||||||
<div className="title">
|
<div className="top">
|
||||||
<h4>{props.gridCharacter.object.name[locale]}</h4>
|
<div className="title">
|
||||||
|
<h4>{props.gridCharacter.object.name[locale]}</h4>
|
||||||
|
<div className="Image">
|
||||||
|
{perpetuity()}
|
||||||
<img
|
<img
|
||||||
alt={props.gridCharacter.object.name[locale]}
|
alt={props.gridCharacter.object.name[locale]}
|
||||||
src={characterImage()}
|
src={characterImage()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="subInfo">
|
</div>
|
||||||
<div className="icons">
|
<div className="subInfo">
|
||||||
<WeaponLabelIcon
|
<div className="icons">
|
||||||
labelType={Element[props.gridCharacter.object.element]}
|
<WeaponLabelIcon
|
||||||
/>
|
labelType={Element[props.gridCharacter.object.element]}
|
||||||
|
/>
|
||||||
|
<WeaponLabelIcon
|
||||||
|
labelType={
|
||||||
|
Proficiency[
|
||||||
|
props.gridCharacter.object.proficiency.proficiency1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{props.gridCharacter.object.proficiency.proficiency2 ? (
|
||||||
<WeaponLabelIcon
|
<WeaponLabelIcon
|
||||||
labelType={
|
labelType={
|
||||||
Proficiency[
|
Proficiency[
|
||||||
props.gridCharacter.object.proficiency.proficiency1
|
props.gridCharacter.object.proficiency.proficiency2
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{props.gridCharacter.object.proficiency.proficiency2 ? (
|
) : (
|
||||||
<WeaponLabelIcon
|
''
|
||||||
labelType={
|
)}
|
||||||
Proficiency[
|
|
||||||
props.gridCharacter.object.proficiency.proficiency2
|
|
||||||
]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<UncapIndicator
|
|
||||||
type="character"
|
|
||||||
ulb={props.gridCharacter.object.uncap.ulb || false}
|
|
||||||
flb={props.gridCharacter.object.uncap.flb || false}
|
|
||||||
special={false}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<UncapIndicator
|
||||||
|
type="character"
|
||||||
|
ulb={props.gridCharacter.object.uncap.ulb || false}
|
||||||
|
flb={props.gridCharacter.object.uncap.flb || false}
|
||||||
|
transcendenceStage={props.gridCharacter.transcendence_step}
|
||||||
|
special={props.gridCharacter.object.special}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">
|
{wikiButton}
|
||||||
{t('buttons.wiki')}
|
{awakeningSection()}
|
||||||
</a>
|
{overMasterySection()}
|
||||||
<HoverCard.Arrow />
|
{aetherialMasterySection()}
|
||||||
</HoverCard.Content>
|
{permanentMasterySection()}
|
||||||
</HoverCard.Portal>
|
</HovercardContent>
|
||||||
</HoverCard.Root>
|
</Hovercard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ const CharacterUnit = ({
|
||||||
setDetailsModalOpen(true)
|
setDetailsModalOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function openSearchModal(event: MouseEvent<HTMLDivElement>) {
|
function openSearchModal() {
|
||||||
if (editable) setSearchModalOpen(true)
|
if (editable) setSearchModalOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,33 +286,50 @@ const CharacterUnit = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const image = (
|
const image = () => {
|
||||||
<div
|
let image = (
|
||||||
className="CharacterImage"
|
|
||||||
onClick={openSearchModal}
|
|
||||||
tabIndex={gridCharacter ? gridCharacter.position * 7 : 0}
|
|
||||||
>
|
|
||||||
<img
|
<img
|
||||||
alt={character?.name[locale]}
|
alt={character?.name[locale]}
|
||||||
className="grid_image"
|
className="grid_image"
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
/>
|
/>
|
||||||
{editable ? (
|
)
|
||||||
<span className="icon">
|
|
||||||
<PlusIcon />
|
const content = (
|
||||||
</span>
|
<div
|
||||||
) : (
|
className="CharacterImage"
|
||||||
''
|
tabIndex={gridCharacter ? gridCharacter.position * 7 : 0}
|
||||||
)}
|
onClick={openSearchModal}
|
||||||
</div>
|
>
|
||||||
)
|
{image}
|
||||||
|
{editable ? (
|
||||||
|
<span className="icon">
|
||||||
|
<PlusIcon />
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return gridCharacter ? (
|
||||||
|
<CharacterHovercard
|
||||||
|
gridCharacter={gridCharacter}
|
||||||
|
onTriggerClick={openSearchModal}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</CharacterHovercard>
|
||||||
|
) : (
|
||||||
|
content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const unitContent = (
|
const unitContent = (
|
||||||
<>
|
<>
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
{contextMenu()}
|
{contextMenu()}
|
||||||
{perpetuity()}
|
{perpetuity()}
|
||||||
{image}
|
{image()}
|
||||||
{gridCharacter && character ? (
|
{gridCharacter && character ? (
|
||||||
<UncapIndicator
|
<UncapIndicator
|
||||||
type="character"
|
type="character"
|
||||||
|
|
@ -335,13 +352,7 @@ const CharacterUnit = ({
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
const unitContentWithHovercard = (
|
return unitContent
|
||||||
<CharacterHovercard gridCharacter={gridCharacter!}>
|
|
||||||
{unitContent}
|
|
||||||
</CharacterHovercard>
|
|
||||||
)
|
|
||||||
|
|
||||||
return gridCharacter && !editable ? unitContentWithHovercard : unitContent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CharacterUnit
|
export default CharacterUnit
|
||||||
|
|
|
||||||
91
components/Hovercard/index.scss
Normal file
91
components/Hovercard/index.scss
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
.HovercardContent {
|
||||||
|
animation: scaleIn $duration-zoom ease-out;
|
||||||
|
transform-origin: var(--radix-hover-card-content-transform-origin);
|
||||||
|
background: var(--dialog-bg);
|
||||||
|
border-radius: $card-corner;
|
||||||
|
color: var(--text-primary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit-2x;
|
||||||
|
max-height: 30vh;
|
||||||
|
overflow-y: scroll;
|
||||||
|
padding: $unit-2x;
|
||||||
|
width: 300px;
|
||||||
|
|
||||||
|
.top {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: calc($unit / 2);
|
||||||
|
|
||||||
|
.title {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: $unit * 2;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: $font-medium;
|
||||||
|
line-height: 1.2;
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: auto;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.subInfo {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: $unit * 2;
|
||||||
|
|
||||||
|
.icons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-grow: 1;
|
||||||
|
gap: $unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
h5 {
|
||||||
|
font-size: $font-small;
|
||||||
|
font-weight: $medium;
|
||||||
|
opacity: 0.7;
|
||||||
|
|
||||||
|
&.wind {
|
||||||
|
color: $wind-bg-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.fire {
|
||||||
|
color: $fire-bg-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.water {
|
||||||
|
color: $water-bg-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.earth {
|
||||||
|
color: $earth-bg-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
color: $dark-bg-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.light {
|
||||||
|
color: $light-bg-20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Button {
|
||||||
|
display: block;
|
||||||
|
padding: $unit * 1.5;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
components/Hovercard/index.tsx
Normal file
31
components/Hovercard/index.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import React, { PropsWithChildren } from 'react'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
import * as HoverCardPrimitive from '@radix-ui/react-hover-card'
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
|
interface Props extends HoverCardPrimitive.HoverCardContentProps {}
|
||||||
|
|
||||||
|
export const Hovercard = HoverCardPrimitive.Root
|
||||||
|
export const HovercardTrigger = HoverCardPrimitive.Trigger
|
||||||
|
|
||||||
|
export const HovercardContent = ({
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: PropsWithChildren<Props>) => {
|
||||||
|
const classes = classNames(props.className, {
|
||||||
|
HovercardContent: true,
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<HoverCardPrimitive.Portal>
|
||||||
|
<HoverCardPrimitive.Content
|
||||||
|
{...props}
|
||||||
|
className={classes}
|
||||||
|
sideOffset={4}
|
||||||
|
collisionPadding={{ top: 16, left: 16, right: 16, bottom: 16 }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</HoverCardPrimitive.Content>
|
||||||
|
</HoverCardPrimitive.Portal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,12 @@ import React from 'react'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
import * as HoverCard from '@radix-ui/react-hover-card'
|
import {
|
||||||
|
Hovercard,
|
||||||
|
HovercardContent,
|
||||||
|
HovercardTrigger,
|
||||||
|
} from '~components/Hovercard'
|
||||||
|
import Button from '~components/Button'
|
||||||
import WeaponLabelIcon from '~components/WeaponLabelIcon'
|
import WeaponLabelIcon from '~components/WeaponLabelIcon'
|
||||||
import UncapIndicator from '~components/UncapIndicator'
|
import UncapIndicator from '~components/UncapIndicator'
|
||||||
|
|
||||||
|
|
@ -12,6 +16,7 @@ import './index.scss'
|
||||||
interface Props {
|
interface Props {
|
||||||
gridSummon: GridSummon
|
gridSummon: GridSummon
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
|
onTriggerClick: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SummonHovercard = (props: Props) => {
|
const SummonHovercard = (props: Props) => {
|
||||||
|
|
@ -23,10 +28,13 @@ const SummonHovercard = (props: Props) => {
|
||||||
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
|
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
|
||||||
|
|
||||||
const tintElement = Element[props.gridSummon.object.element]
|
const tintElement = Element[props.gridSummon.object.element]
|
||||||
const wikiUrl = `https://gbf.wiki/${props.gridSummon.object.name.en.replaceAll(
|
|
||||||
' ',
|
function goTo() {
|
||||||
'_'
|
const urlSafeName = props.gridSummon.object.name.en.replaceAll(' ', '_')
|
||||||
)}`
|
const url = `https://gbf.wiki/${urlSafeName}`
|
||||||
|
|
||||||
|
window.open(url, '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
function summonImage() {
|
function summonImage() {
|
||||||
let imgSrc = ''
|
let imgSrc = ''
|
||||||
|
|
@ -59,40 +67,46 @@ const SummonHovercard = (props: Props) => {
|
||||||
return imgSrc
|
return imgSrc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const wikiButton = (
|
||||||
|
<Button
|
||||||
|
className={tintElement}
|
||||||
|
text={t('buttons.wiki')}
|
||||||
|
onClick={goTo}
|
||||||
|
contained={true}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HoverCard.Root>
|
<Hovercard openDelay={350}>
|
||||||
<HoverCard.Trigger>{props.children}</HoverCard.Trigger>
|
<HovercardTrigger asChild onClick={props.onTriggerClick}>
|
||||||
<HoverCard.Portal>
|
{props.children}
|
||||||
<HoverCard.Content className="Weapon Hovercard">
|
</HovercardTrigger>
|
||||||
<div className="top">
|
<HovercardContent className="Summon">
|
||||||
<div className="title">
|
<div className="top">
|
||||||
<h4>{props.gridSummon.object.name[locale]}</h4>
|
<div className="title">
|
||||||
<img
|
<h4>{props.gridSummon.object.name[locale]}</h4>
|
||||||
alt={props.gridSummon.object.name[locale]}
|
<img
|
||||||
src={summonImage()}
|
alt={props.gridSummon.object.name[locale]}
|
||||||
/>
|
src={summonImage()}
|
||||||
</div>
|
/>
|
||||||
<div className="subInfo">
|
|
||||||
<div className="icons">
|
|
||||||
<WeaponLabelIcon
|
|
||||||
labelType={Element[props.gridSummon.object.element]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<UncapIndicator
|
|
||||||
type="summon"
|
|
||||||
ulb={props.gridSummon.object.uncap.ulb || false}
|
|
||||||
flb={props.gridSummon.object.uncap.flb || false}
|
|
||||||
special={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">
|
<div className="subInfo">
|
||||||
{t('buttons.wiki')}
|
<div className="icons">
|
||||||
</a>
|
<WeaponLabelIcon
|
||||||
<HoverCard.Arrow />
|
labelType={Element[props.gridSummon.object.element]}
|
||||||
</HoverCard.Content>
|
/>
|
||||||
</HoverCard.Portal>
|
</div>
|
||||||
</HoverCard.Root>
|
<UncapIndicator
|
||||||
|
type="summon"
|
||||||
|
ulb={props.gridSummon.object.uncap.ulb || false}
|
||||||
|
flb={props.gridSummon.object.uncap.flb || false}
|
||||||
|
special={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{wikiButton}
|
||||||
|
</HovercardContent>
|
||||||
|
</Hovercard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ const SummonUnit = ({
|
||||||
})
|
})
|
||||||
|
|
||||||
// Methods: Open layer
|
// Methods: Open layer
|
||||||
function openSearchModal(event: MouseEvent<HTMLDivElement>) {
|
function openSearchModal() {
|
||||||
if (editable) setSearchModalOpen(true)
|
if (editable) setSearchModalOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,8 +223,8 @@ const SummonUnit = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methods: Core element rendering
|
// Methods: Core element rendering
|
||||||
const image = (
|
const image = () => {
|
||||||
<div className="SummonImage" onClick={openSearchModal}>
|
let image = (
|
||||||
<img
|
<img
|
||||||
alt={summon?.name[locale]}
|
alt={summon?.name[locale]}
|
||||||
className={classNames({
|
className={classNames({
|
||||||
|
|
@ -233,21 +233,35 @@ const SummonUnit = ({
|
||||||
})}
|
})}
|
||||||
src={imageUrl !== '' ? imageUrl : placeholderImageUrl()}
|
src={imageUrl !== '' ? imageUrl : placeholderImageUrl()}
|
||||||
/>
|
/>
|
||||||
{editable ? (
|
)
|
||||||
<span className="icon">
|
|
||||||
<PlusIcon />
|
const content = (
|
||||||
</span>
|
<div className="SummonImage" onClick={openSearchModal}>
|
||||||
) : (
|
{image}
|
||||||
''
|
{editable ? (
|
||||||
)}
|
<span className="icon">
|
||||||
</div>
|
<PlusIcon />
|
||||||
)
|
</span>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return gridSummon ? (
|
||||||
|
<SummonHovercard gridSummon={gridSummon} onTriggerClick={openSearchModal}>
|
||||||
|
{content}
|
||||||
|
</SummonHovercard>
|
||||||
|
) : (
|
||||||
|
content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const unitContent = (
|
const unitContent = (
|
||||||
<>
|
<>
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
{contextMenu()}
|
{contextMenu()}
|
||||||
{image}
|
{image()}
|
||||||
{gridSummon ? (
|
{gridSummon ? (
|
||||||
<UncapIndicator
|
<UncapIndicator
|
||||||
type="summon"
|
type="summon"
|
||||||
|
|
@ -271,11 +285,7 @@ const SummonUnit = ({
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
const unitContentWithHovercard = (
|
return unitContent
|
||||||
<SummonHovercard gridSummon={gridSummon!}>{unitContent}</SummonHovercard>
|
|
||||||
)
|
|
||||||
|
|
||||||
return gridSummon && !editable ? unitContentWithHovercard : unitContent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SummonUnit
|
export default SummonUnit
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
.Weapon.Hovercard {
|
.Weapon.HovercardContent {
|
||||||
.skills {
|
.skills {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding-right: $unit * 2;
|
padding-right: $unit-2x;
|
||||||
|
|
||||||
.axSkill {
|
.axSkill {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -36,6 +36,26 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
font-size: $normal;
|
font-size: $normal;
|
||||||
gap: calc($unit / 2);
|
gap: $unit-half;
|
||||||
|
}
|
||||||
|
|
||||||
|
.awakening {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit-half;
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: $unit-half;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: $unit-4x;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: $bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,24 @@ import React from 'react'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
import * as HoverCard from '@radix-ui/react-hover-card'
|
import {
|
||||||
|
Hovercard,
|
||||||
|
HovercardContent,
|
||||||
|
HovercardTrigger,
|
||||||
|
} from '~components/Hovercard'
|
||||||
|
import Button from '~components/Button'
|
||||||
import WeaponLabelIcon from '~components/WeaponLabelIcon'
|
import WeaponLabelIcon from '~components/WeaponLabelIcon'
|
||||||
import UncapIndicator from '~components/UncapIndicator'
|
import UncapIndicator from '~components/UncapIndicator'
|
||||||
|
|
||||||
import ax from '~data/ax'
|
import ax from '~data/ax'
|
||||||
|
import { weaponAwakening } from '~data/awakening'
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
gridWeapon: GridWeapon
|
gridWeapon: GridWeapon
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
|
onTriggerClick: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
interface KeyNames {
|
interface KeyNames {
|
||||||
|
|
@ -26,10 +32,11 @@ interface KeyNames {
|
||||||
|
|
||||||
const WeaponHovercard = (props: Props) => {
|
const WeaponHovercard = (props: Props) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { t } = useTranslation('common')
|
|
||||||
const locale =
|
const locale =
|
||||||
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||||
|
|
||||||
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
|
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
|
||||||
const Proficiency = [
|
const Proficiency = [
|
||||||
'none',
|
'none',
|
||||||
|
|
@ -67,11 +74,19 @@ const WeaponHovercard = (props: Props) => {
|
||||||
props.gridWeapon.object.element == 0 && props.gridWeapon.element
|
props.gridWeapon.object.element == 0 && props.gridWeapon.element
|
||||||
? Element[props.gridWeapon.element]
|
? Element[props.gridWeapon.element]
|
||||||
: Element[props.gridWeapon.object.element]
|
: Element[props.gridWeapon.object.element]
|
||||||
|
|
||||||
const wikiUrl = `https://gbf.wiki/${props.gridWeapon.object.name.en.replaceAll(
|
const wikiUrl = `https://gbf.wiki/${props.gridWeapon.object.name.en.replaceAll(
|
||||||
' ',
|
' ',
|
||||||
'_'
|
'_'
|
||||||
)}`
|
)}`
|
||||||
|
|
||||||
|
function goTo() {
|
||||||
|
const urlSafeName = props.gridWeapon.object.name.en.replaceAll(' ', '_')
|
||||||
|
const url = `https://gbf.wiki/${urlSafeName}`
|
||||||
|
|
||||||
|
window.open(url, '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
const hovercardSide = () => {
|
const hovercardSide = () => {
|
||||||
if (props.gridWeapon.position == -1) return 'right'
|
if (props.gridWeapon.position == -1) return 'right'
|
||||||
else if ([6, 7, 8, 9, 10, 11].includes(props.gridWeapon.position))
|
else if ([6, 7, 8, 9, 10, 11].includes(props.gridWeapon.position))
|
||||||
|
|
@ -129,6 +144,33 @@ const WeaponHovercard = (props: Props) => {
|
||||||
return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`
|
return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const awakeningSection = () => {
|
||||||
|
const gridAwakening = props.gridWeapon.awakening
|
||||||
|
const awakening = weaponAwakening.find(
|
||||||
|
(awakening) => awakening.id === gridAwakening?.type
|
||||||
|
)
|
||||||
|
|
||||||
|
if (gridAwakening && awakening) {
|
||||||
|
return (
|
||||||
|
<section className="awakening">
|
||||||
|
<h5 className={tintElement}>
|
||||||
|
{t('modals.weapon.subtitles.awakening')}
|
||||||
|
</h5>
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
alt={awakening.name[locale]}
|
||||||
|
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/awakening/weapon_${gridAwakening.type}.png`}
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
<strong>{`${awakening.name[locale]}`}</strong>
|
||||||
|
{`Lv${gridAwakening.level}`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const keysSection = (
|
const keysSection = (
|
||||||
<section className="weaponKeys">
|
<section className="weaponKeys">
|
||||||
{WeaponKeyNames[props.gridWeapon.object.series] ? (
|
{WeaponKeyNames[props.gridWeapon.object.series] ? (
|
||||||
|
|
@ -188,65 +230,71 @@ const WeaponHovercard = (props: Props) => {
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
const wikiButton = (
|
||||||
<HoverCard.Root>
|
<Button
|
||||||
<HoverCard.Trigger>{props.children}</HoverCard.Trigger>
|
className={tintElement}
|
||||||
<HoverCard.Portal>
|
text={t('buttons.wiki')}
|
||||||
<HoverCard.Content className="Weapon Hovercard" side={hovercardSide()}>
|
onClick={goTo}
|
||||||
<div className="top">
|
contained={true}
|
||||||
<div className="title">
|
/>
|
||||||
<h4>{props.gridWeapon.object.name[locale]}</h4>
|
)
|
||||||
<img
|
|
||||||
alt={props.gridWeapon.object.name[locale]}
|
|
||||||
src={weaponImage()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="subInfo">
|
|
||||||
<div className="icons">
|
|
||||||
{props.gridWeapon.object.element !== 0 ||
|
|
||||||
(props.gridWeapon.object.element === 0 &&
|
|
||||||
props.gridWeapon.element != null) ? (
|
|
||||||
<WeaponLabelIcon
|
|
||||||
labelType={
|
|
||||||
props.gridWeapon.object.element === 0 &&
|
|
||||||
props.gridWeapon.element !== 0
|
|
||||||
? Element[props.gridWeapon.element]
|
|
||||||
: Element[props.gridWeapon.object.element]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
|
||||||
<WeaponLabelIcon
|
|
||||||
labelType={Proficiency[props.gridWeapon.object.proficiency]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<UncapIndicator
|
|
||||||
type="weapon"
|
|
||||||
ulb={props.gridWeapon.object.uncap.ulb || false}
|
|
||||||
flb={props.gridWeapon.object.uncap.flb || false}
|
|
||||||
special={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{props.gridWeapon.object.ax &&
|
return (
|
||||||
props.gridWeapon.ax &&
|
<Hovercard openDelay={350}>
|
||||||
props.gridWeapon.ax[0].modifier &&
|
<HovercardTrigger asChild onClick={props.onTriggerClick}>
|
||||||
props.gridWeapon.ax[0].strength
|
{props.children}
|
||||||
? axSection
|
</HovercardTrigger>
|
||||||
: ''}
|
<HovercardContent className="Weapon" side={hovercardSide()}>
|
||||||
{props.gridWeapon.weapon_keys &&
|
<div className="top">
|
||||||
props.gridWeapon.weapon_keys.length > 0
|
<div className="title">
|
||||||
? keysSection
|
<h4>{props.gridWeapon.object.name[locale]}</h4>
|
||||||
: ''}
|
<img
|
||||||
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">
|
alt={props.gridWeapon.object.name[locale]}
|
||||||
{t('buttons.wiki')}
|
src={weaponImage()}
|
||||||
</a>
|
/>
|
||||||
<HoverCard.Arrow />
|
</div>
|
||||||
</HoverCard.Content>
|
<div className="subInfo">
|
||||||
</HoverCard.Portal>
|
<div className="icons">
|
||||||
</HoverCard.Root>
|
{props.gridWeapon.object.element !== 0 ||
|
||||||
|
(props.gridWeapon.object.element === 0 &&
|
||||||
|
props.gridWeapon.element != null) ? (
|
||||||
|
<WeaponLabelIcon
|
||||||
|
labelType={
|
||||||
|
props.gridWeapon.object.element === 0 &&
|
||||||
|
props.gridWeapon.element !== 0
|
||||||
|
? Element[props.gridWeapon.element]
|
||||||
|
: Element[props.gridWeapon.object.element]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
<WeaponLabelIcon
|
||||||
|
labelType={Proficiency[props.gridWeapon.object.proficiency]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<UncapIndicator
|
||||||
|
type="weapon"
|
||||||
|
ulb={props.gridWeapon.object.uncap.ulb || false}
|
||||||
|
flb={props.gridWeapon.object.uncap.flb || false}
|
||||||
|
special={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{props.gridWeapon.object.ax &&
|
||||||
|
props.gridWeapon.ax &&
|
||||||
|
props.gridWeapon.ax[0].modifier &&
|
||||||
|
props.gridWeapon.ax[0].strength
|
||||||
|
? axSection
|
||||||
|
: ''}
|
||||||
|
{awakeningSection()}
|
||||||
|
{props.gridWeapon.weapon_keys && props.gridWeapon.weapon_keys.length > 0
|
||||||
|
? keysSection
|
||||||
|
: ''}
|
||||||
|
{wikiButton}
|
||||||
|
</HovercardContent>
|
||||||
|
</Hovercard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -189,6 +189,7 @@ const WeaponModal = ({
|
||||||
if (gridWeapon.mainhand) appState.grid.weapons.mainWeapon = gridWeapon
|
if (gridWeapon.mainhand) appState.grid.weapons.mainWeapon = gridWeapon
|
||||||
else appState.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon
|
else appState.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon
|
||||||
|
|
||||||
|
if (onOpenChange) onOpenChange(false)
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ const WeaponUnit = ({
|
||||||
setDetailsModalOpen(true)
|
setDetailsModalOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function openSearchModal(event: MouseEvent<HTMLDivElement>) {
|
function openSearchModal() {
|
||||||
if (editable) setSearchModalOpen(true)
|
if (editable) setSearchModalOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -509,17 +509,8 @@ const WeaponUnit = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methods: Core element rendering
|
// Methods: Core element rendering
|
||||||
const image = (
|
const image = () => {
|
||||||
<div className="WeaponImage" onClick={openSearchModal}>
|
const image = (
|
||||||
<div className="Modifiers">
|
|
||||||
{awakeningImage()}
|
|
||||||
<div className="Skills">
|
|
||||||
{axImages()}
|
|
||||||
{telumaImages()}
|
|
||||||
{opusImages()}
|
|
||||||
{ultimaImages()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<img
|
<img
|
||||||
alt={weapon?.name[locale]}
|
alt={weapon?.name[locale]}
|
||||||
className={classNames({
|
className={classNames({
|
||||||
|
|
@ -528,21 +519,44 @@ const WeaponUnit = ({
|
||||||
})}
|
})}
|
||||||
src={imageUrl !== '' ? imageUrl : placeholderImageUrl()}
|
src={imageUrl !== '' ? imageUrl : placeholderImageUrl()}
|
||||||
/>
|
/>
|
||||||
{editable ? (
|
)
|
||||||
<span className="icon">
|
|
||||||
<PlusIcon />
|
const content = (
|
||||||
</span>
|
<div className="WeaponImage" onClick={openSearchModal}>
|
||||||
) : (
|
<div className="Modifiers">
|
||||||
''
|
{awakeningImage()}
|
||||||
)}
|
<div className="Skills">
|
||||||
</div>
|
{axImages()}
|
||||||
)
|
{telumaImages()}
|
||||||
|
{opusImages()}
|
||||||
|
{ultimaImages()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{image}
|
||||||
|
{editable ? (
|
||||||
|
<span className="icon">
|
||||||
|
<PlusIcon />
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return gridWeapon ? (
|
||||||
|
<WeaponHovercard gridWeapon={gridWeapon} onTriggerClick={openSearchModal}>
|
||||||
|
{content}
|
||||||
|
</WeaponHovercard>
|
||||||
|
) : (
|
||||||
|
content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const unitContent = (
|
const unitContent = (
|
||||||
<>
|
<>
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
{contextMenu()}
|
{contextMenu()}
|
||||||
{image}
|
{image()}
|
||||||
{gridWeapon && weapon ? (
|
{gridWeapon && weapon ? (
|
||||||
<UncapIndicator
|
<UncapIndicator
|
||||||
type="weapon"
|
type="weapon"
|
||||||
|
|
@ -562,11 +576,7 @@ const WeaponUnit = ({
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
const unitContentWithHovercard = (
|
return unitContent
|
||||||
<WeaponHovercard gridWeapon={gridWeapon!}>{unitContent}</WeaponHovercard>
|
|
||||||
)
|
|
||||||
|
|
||||||
return gridWeapon && !editable ? unitContentWithHovercard : unitContent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WeaponUnit
|
export default WeaponUnit
|
||||||
|
|
|
||||||
|
|
@ -330,3 +330,54 @@ export const aetherialMastery: ItemSkill[] = [
|
||||||
fractional: false,
|
fractional: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const permanentMastery: ItemSkill[] = [
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'Extended Mastery Star Cap',
|
||||||
|
ja: 'LB強化回数上限',
|
||||||
|
},
|
||||||
|
id: 1,
|
||||||
|
slug: 'star-cap',
|
||||||
|
minValue: 10,
|
||||||
|
maxValue: 10,
|
||||||
|
suffix: '',
|
||||||
|
fractional: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'ATK',
|
||||||
|
ja: '攻撃',
|
||||||
|
},
|
||||||
|
id: 2,
|
||||||
|
slug: 'atk',
|
||||||
|
minValue: 10,
|
||||||
|
maxValue: 10,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'HP',
|
||||||
|
ja: 'HP',
|
||||||
|
},
|
||||||
|
id: 3,
|
||||||
|
slug: 'hp',
|
||||||
|
minValue: 10,
|
||||||
|
maxValue: 10,
|
||||||
|
suffix: '',
|
||||||
|
fractional: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
en: 'DMG Cap',
|
||||||
|
ja: 'ダメージ上限',
|
||||||
|
},
|
||||||
|
id: 4,
|
||||||
|
slug: 'dmg-cap',
|
||||||
|
minValue: 5,
|
||||||
|
maxValue: 5,
|
||||||
|
suffix: '%',
|
||||||
|
fractional: false,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -200,98 +200,6 @@ select {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Hovercard {
|
|
||||||
background: #222;
|
|
||||||
border-radius: $unit;
|
|
||||||
color: $grey-100;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $unit * 2;
|
|
||||||
padding: $unit * 2;
|
|
||||||
width: 300px;
|
|
||||||
|
|
||||||
.top {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: calc($unit / 2);
|
|
||||||
|
|
||||||
.title {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: $unit * 2;
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
flex-grow: 1;
|
|
||||||
font-size: $font-medium;
|
|
||||||
line-height: 1.2;
|
|
||||||
min-width: 140px;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
height: auto;
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.subInfo {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: $unit * 2;
|
|
||||||
|
|
||||||
.icons {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-grow: 1;
|
|
||||||
gap: $unit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.UncapIndicator {
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
|
||||||
h5 {
|
|
||||||
font-size: $font-small;
|
|
||||||
font-weight: $medium;
|
|
||||||
opacity: 0.7;
|
|
||||||
|
|
||||||
&.wind {
|
|
||||||
color: $wind-bg-20;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.fire {
|
|
||||||
color: $fire-bg-20;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.water {
|
|
||||||
color: $water-bg-20;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.earth {
|
|
||||||
color: $earth-bg-20;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.dark {
|
|
||||||
color: $dark-bg-10;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.light {
|
|
||||||
color: $light-bg-20;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a.Button {
|
|
||||||
display: block;
|
|
||||||
padding: $unit * 1.5;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Teams,
|
#Teams,
|
||||||
#Profile {
|
#Profile {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -447,17 +355,25 @@ i.tag {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
70% {
|
65% {
|
||||||
opacity: 0.8;
|
opacity: 0.65;
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
70% {
|
||||||
|
opacity: 0.7;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
opacity: 0.75;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
80% {
|
80% {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
transform: scale(1);
|
transform: scale(1.02);
|
||||||
}
|
}
|
||||||
90% {
|
90% {
|
||||||
opacity: 0.8;
|
opacity: 0.9;
|
||||||
transform: scale(0.95);
|
transform: scale(0.96);
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue