Fix hovercards

* Extracted header into HovercardHeader component
This commit is contained in:
Justin Edmund 2023-06-30 12:21:54 -07:00
parent 37e0525243
commit 7baa9dddd9
9 changed files with 367 additions and 311 deletions

View file

@ -0,0 +1,57 @@
.root {
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;
}
.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;
}
}
}
.subInfo {
align-items: center;
display: flex;
flex-direction: row;
gap: $unit * 2;
.icons {
display: flex;
flex-direction: row;
flex-grow: 1;
gap: $unit;
}
.UncapIndicator {
min-width: 100px;
}
}
}

View file

@ -0,0 +1,150 @@
import { useRouter } from 'next/router'
import UncapIndicator from '~components/uncap/UncapIndicator'
import WeaponLabelIcon from '~components/weapon/WeaponLabelIcon'
import styles from './index.module.scss'
interface Props {
gridObject: GridCharacter | GridSummon | GridWeapon
object: Character | Summon | Weapon
type: 'character' | 'summon' | 'weapon'
}
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
const Proficiency = [
'none',
'sword',
'dagger',
'axe',
'spear',
'bow',
'staff',
'fist',
'harp',
'gun',
'katana',
]
const HovercardHeader = ({ gridObject, object, type, ...props }: Props) => {
const router = useRouter()
const locale =
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
const overlay = () => {
if (type === 'character') {
const gridCharacter = gridObject as GridCharacter
if (gridCharacter.perpetuity) return <i className={styles.perpetuity} />
} else if (type === 'summon') {
const gridSummon = gridObject as GridSummon
if (gridSummon.quick_summon) return <i className={styles.quickSummon} />
}
}
const characterImage = () => {
const gridCharacter = gridObject as GridCharacter
const character = object as Character
// Change the image based on the uncap level
let suffix = '01'
if (gridCharacter.uncap_level == 6) suffix = '04'
else if (gridCharacter.uncap_level == 5) suffix = '03'
else if (gridCharacter.uncap_level > 2) suffix = '02'
return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_${suffix}.jpg`
}
const summonImage = () => {
const summon = object as Summon
const gridSummon = gridObject as GridSummon
const upgradedSummons = [
'2040094000',
'2040100000',
'2040080000',
'2040098000',
'2040090000',
'2040084000',
'2040003000',
'2040056000',
]
let suffix = ''
if (
upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 &&
gridSummon.uncap_level == 5
) {
suffix = '_02'
} else if (
gridSummon.object.uncap.xlb &&
gridSummon.transcendence_step > 0
) {
suffix = '_03'
}
// Generate the correct source for the summon
return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}${suffix}.jpg`
}
const weaponImage = () => {
const gridWeapon = gridObject as GridWeapon
const weapon = object as Weapon
if (gridWeapon.object.element == 0 && gridWeapon.element)
return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}_${gridWeapon.element}.jpg`
else
return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`
}
const image = () => {
switch (type) {
case 'character':
return characterImage()
case 'summon':
return summonImage()
case 'weapon':
return weaponImage()
}
}
return (
<header className={styles.root}>
<div className={styles.title}>
<h4>{object.name[locale]}</h4>
<div className={styles.image}>
{overlay()}
<img alt={object.name[locale]} src={image()} />
</div>
</div>
<div className={styles.subInfo}>
<div className={styles.icons}>
<WeaponLabelIcon labelType={Element[object.element]} />
{'proficiency' in object && Array.isArray(object.proficiency) && (
<WeaponLabelIcon labelType={Proficiency[object.proficiency[0]]} />
)}
{'proficiency' in object && !Array.isArray(object.proficiency) && (
<WeaponLabelIcon labelType={Proficiency[object.proficiency]} />
)}
{'proficiency' in object &&
Array.isArray(object.proficiency) &&
object.proficiency.length > 1 && (
<WeaponLabelIcon labelType={Proficiency[object.proficiency[1]]} />
)}
</div>
<UncapIndicator
type={type}
ulb={object.uncap.ulb || false}
flb={object.uncap.flb || false}
transcendenceStage={
'transcendence_step' in gridObject
? gridObject.transcendence_step
: 0
}
special={'special' in object ? object.special : false}
/>
</div>
</header>
)
}
export default HovercardHeader

View file

@ -1,20 +1,5 @@
.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 {
.content {
.mastery {
display: flex;
flex-direction: column;
gap: $unit;
@ -24,7 +9,7 @@
flex-direction: column;
gap: $unit-half;
.ExtendedMastery {
.extendedMastery {
align-items: center;
display: flex;
gap: $unit-half;
@ -40,7 +25,7 @@
}
}
.Awakening {
.awakening {
display: flex;
flex-direction: column;
gap: $unit;

View file

@ -19,6 +19,7 @@ import {
import { ExtendedMastery } from '~types'
import styles from './index.module.scss'
import HovercardHeader from '~components/HovercardHeader'
interface Props {
gridCharacter: GridCharacter
@ -33,20 +34,6 @@ const CharacterHovercard = (props: Props) => {
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
const Proficiency = [
'none',
'sword',
'dagger',
'axe',
'spear',
'bow',
'staff',
'fist',
'harp',
'gun',
'katana',
]
const tintElement = Element[props.gridCharacter.object.element]
function goTo() {
@ -56,30 +43,6 @@ const CharacterHovercard = (props: Props) => {
window.open(url, '_blank')
}
const perpetuity = () => {
if (props.gridCharacter && props.gridCharacter.perpetuity) {
return <i className="Perpetuity" />
}
}
function characterImage() {
let imgSrc = ''
if (props.gridCharacter) {
const character = props.gridCharacter.object
// Change the image based on the uncap level
let suffix = '01'
if (props.gridCharacter.uncap_level == 6) suffix = '04'
else if (props.gridCharacter.uncap_level == 5) suffix = '03'
else if (props.gridCharacter.uncap_level > 2) suffix = '02'
imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_${suffix}.jpg`
}
return imgSrc
}
function masteryElement(dictionary: ItemSkill[], mastery: ExtendedMastery) {
const canonicalMastery = dictionary.find(
(item) => item.id === mastery.modifier
@ -87,7 +50,7 @@ const CharacterHovercard = (props: Props) => {
if (canonicalMastery) {
return (
<li className="ExtendedMastery" key={canonicalMastery.id}>
<li className={styles.extendedMastery} key={canonicalMastery.id}>
<img
alt={canonicalMastery.name[locale]}
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/mastery/${canonicalMastery.slug}.png`}
@ -104,7 +67,7 @@ const CharacterHovercard = (props: Props) => {
const overMasterySection = () => {
if (props.gridCharacter && props.gridCharacter.over_mastery) {
return (
<section className="Mastery">
<section className={styles.mastery}>
<h5 className={tintElement}>
{t('modals.characters.subtitles.ring')}
</h5>
@ -136,7 +99,7 @@ const CharacterHovercard = (props: Props) => {
props.gridCharacter.aetherial_mastery.modifier > 0
) {
return (
<section className="Mastery">
<section className={styles.mastery}>
<h5 className={tintElement}>
{t('modals.characters.subtitles.earring')}
</h5>
@ -154,7 +117,7 @@ const CharacterHovercard = (props: Props) => {
const permanentMasterySection = () => {
if (props.gridCharacter && props.gridCharacter.perpetuity) {
return (
<section className="Mastery">
<section className={styles.mastery}>
<h5 className={tintElement}>
{t('modals.characters.subtitles.permanent')}
</h5>
@ -176,7 +139,7 @@ const CharacterHovercard = (props: Props) => {
if (gridAwakening) {
return (
<section className="Awakening">
<section className={styles.awakening}>
<h5 className={tintElement}>
{t('modals.characters.subtitles.awakening')}
</h5>
@ -200,7 +163,7 @@ const CharacterHovercard = (props: Props) => {
className={tintElement}
text={t('buttons.wiki')}
onClick={goTo}
contained={true}
bound={true}
/>
)
@ -209,51 +172,12 @@ const CharacterHovercard = (props: Props) => {
<HovercardTrigger asChild onClick={props.onTriggerClick}>
{props.children}
</HovercardTrigger>
<HovercardContent className="Character" side="top">
<div className="top">
<div className="title">
<h4>{props.gridCharacter.object.name[locale]}</h4>
<div className="Image">
{perpetuity()}
<img
alt={props.gridCharacter.object.name[locale]}
src={characterImage()}
/>
</div>
</div>
<div className="subInfo">
<div className="icons">
<WeaponLabelIcon
labelType={Element[props.gridCharacter.object.element]}
/>
<WeaponLabelIcon
labelType={
Proficiency[
props.gridCharacter.object.proficiency.proficiency1
]
}
/>
{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}
transcendenceStage={props.gridCharacter.transcendence_step}
special={props.gridCharacter.object.special}
/>
</div>
</div>
<HovercardContent className={styles.content} side="top">
<HovercardHeader
gridObject={props.gridCharacter}
object={props.gridCharacter.object}
type="character"
/>
{wikiButton}
{awakeningSection()}
{overMasterySection()}

View file

@ -1,12 +1,9 @@
div[data-radix-popper-content-wrapper] {
z-index: 10 !important;
}
.HovercardContent {
.hovercard {
animation: scaleIn $duration-zoom ease-out;
transform-origin: var(--radix-hover-card-content-transform-origin);
background: var(--dialog-bg);
border-radius: $card-corner;
border: 1px solid rgba(0, 0, 0, 0.1);
color: var(--text-primary);
display: flex;
flex-direction: column;
@ -16,77 +13,47 @@ div[data-radix-popper-content-wrapper] {
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;
}
.UncapIndicator {
min-width: 100px;
}
}
}
section {
h5 {
font-size: $font-small;
font-weight: $medium;
opacity: 0.7;
&.wind {
color: $wind-bg-20;
@keyframes scaleIn {
0% {
opacity: 0;
transform: scale(0);
}
&.fire {
color: $fire-bg-20;
20% {
opacity: 0.2;
transform: scale(0.4);
}
&.water {
color: $water-bg-20;
40% {
opacity: 0.4;
transform: scale(0.8);
}
&.earth {
color: $earth-bg-20;
60% {
opacity: 0.6;
transform: scale(1);
}
&.dark {
color: $dark-bg-10;
65% {
opacity: 0.65;
transform: scale(1.1);
}
&.light {
color: $light-bg-20;
70% {
opacity: 0.7;
transform: scale(1);
}
75% {
opacity: 0.75;
transform: scale(0.98);
}
80% {
opacity: 0.8;
transform: scale(1.02);
}
90% {
opacity: 0.9;
transform: scale(0.96);
}
100% {
opacity: 1;
transform: scale(1);
}
}
}

View file

@ -14,7 +14,7 @@ export const HovercardContent = ({
...props
}: PropsWithChildren<Props>) => {
const classes = classNames(props.className, {
HovercardContent: true,
[styles.hovercard]: true,
})
return (
<HoverCardPrimitive.Portal>

View file

@ -8,8 +8,7 @@ import {
HovercardTrigger,
} from '~components/common/Hovercard'
import Button from '~components/common/Button'
import WeaponLabelIcon from '~components/weapon/WeaponLabelIcon'
import UncapIndicator from '~components/uncap/UncapIndicator'
import HovercardHeader from '~components/HovercardHeader'
import styles from './index.module.scss'
@ -79,7 +78,7 @@ const SummonHovercard = (props: Props) => {
className={tintElement}
text={t('buttons.wiki')}
onClick={goTo}
contained={true}
bound={true}
/>
)
@ -88,31 +87,12 @@ const SummonHovercard = (props: Props) => {
<HovercardTrigger asChild onClick={props.onTriggerClick}>
{props.children}
</HovercardTrigger>
<HovercardContent className="Summon" side={props.side}>
<div className="top">
<div className="title">
<h4>{props.gridSummon.object.name[locale]}</h4>
<img
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}
xlb={props.gridSummon.object.uncap.xlb || false}
transcendenceStage={props.gridSummon.transcendence_step}
special={false}
/>
</div>
</div>
<HovercardContent side={props.side}>
<HovercardHeader
gridObject={props.gridSummon}
object={props.gridSummon.object}
type="summon"
/>
{wikiButton}
</HovercardContent>
</Hovercard>

View file

@ -1,34 +1,64 @@
.Weapon.HovercardContent {
.content {
section {
display: flex;
flex-direction: column;
gap: $unit-half;
}
.awakening {
display: flex;
flex-direction: column;
gap: $unit-half;
}
.skill {
align-items: center;
display: flex;
gap: $unit-half;
img {
width: $unit-4x;
}
strong {
font-weight: $bold;
}
&.axSkill .axImageWrapper {
display: flex;
align-items: center;
justify-content: center;
width: $unit-4x;
height: $unit-4x;
}
&.axSkill.secondary .axImageWrapper {
img {
width: $unit-3x;
height: $unit-3x;
}
}
}
.skills {
display: flex;
flex-direction: row;
flex-direction: column;
justify-content: space-between;
padding-right: $unit-2x;
.axSkill {
align-items: center;
display: flex;
flex-direction: row;
&.secondary {
gap: $unit * 1.5;
&.primary img {
height: 64px;
width: 64px;
img {
height: 24px;
width: 24px;
}
}
&.secondary {
gap: $unit * 1.5;
img {
height: 36px;
width: 36px;
}
}
span {
font-size: $font-small;
font-weight: $medium;
text-align: center;
}
span {
font-size: $font-small;
font-weight: $medium;
text-align: center;
}
}
@ -38,24 +68,4 @@
font-size: $normal;
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;
}
}
}
}

View file

@ -1,15 +1,15 @@
import React from 'react'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'
import classNames from 'classnames'
import {
Hovercard,
HovercardContent,
HovercardTrigger,
} from '~components/common/Hovercard'
import HovercardHeader from '~components/HovercardHeader'
import Button from '~components/common/Button'
import WeaponLabelIcon from '~components/weapon/WeaponLabelIcon'
import UncapIndicator from '~components/uncap/UncapIndicator'
import ax from '~data/ax'
@ -148,11 +148,11 @@ const WeaponHovercard = (props: Props) => {
if (gridAwakening) {
return (
<section className="awakening">
<section className={styles.awakening}>
<h5 className={tintElement}>
{t('modals.weapon.subtitles.awakening')}
</h5>
<div>
<div className={styles.skill}>
<img
alt={gridAwakening.type.name[locale]}
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/awakening/${gridAwakening.type.slug}.png`}
@ -168,7 +168,7 @@ const WeaponHovercard = (props: Props) => {
}
const keysSection = (
<section className="weaponKeys">
<section className={styles.weaponKeys}>
{WeaponKeyNames[props.gridWeapon.object.series] ? (
<h5 className={tintElement}>
{WeaponKeyNames[props.gridWeapon.object.series][locale]}
@ -182,7 +182,7 @@ const WeaponHovercard = (props: Props) => {
? Array.from(Array(props.gridWeapon.weapon_keys.length)).map((x, i) => {
return (
<div
className="weaponKey"
className={styles.weaponKey}
key={props.gridWeapon.weapon_keys![i].id}
>
<span>{props.gridWeapon.weapon_keys![i].name[locale]}</span>
@ -194,29 +194,44 @@ const WeaponHovercard = (props: Props) => {
)
const axSection = (
<section className="axSkills">
<section className={styles.axSkills}>
<h5 className={tintElement}>{t('modals.weapon.subtitles.ax_skills')}</h5>
<div className="skills">
<div className="primary axSkill">
<img
alt="AX1"
src={`/icons/ax/primary_${
props.gridWeapon.ax ? props.gridWeapon.ax[0].modifier : ''
}.png`}
/>
<div className={styles.skills}>
<div
className={classNames({
[styles.axSkill]: true,
[styles.skill]: true,
})}
>
<div className={styles.axImageWrapper}>
<img
alt="AX1"
src={`/icons/ax/primary_${
props.gridWeapon.ax ? props.gridWeapon.ax[0].modifier : ''
}.png`}
/>
</div>
<span>{createPrimaryAxSkillString()}</span>
</div>
{props.gridWeapon.ax &&
props.gridWeapon.ax[1].modifier &&
props.gridWeapon.ax[1].strength ? (
<div className="secondary axSkill">
<img
alt="AX2"
src={`/icons/ax/secondary_${
props.gridWeapon.ax ? props.gridWeapon.ax[1].modifier : ''
}.png`}
/>
<div
className={classNames({
[styles.secondary]: true,
[styles.axSkill]: true,
[styles.skill]: true,
})}
>
<div className={styles.axImageWrapper}>
<img
alt="AX2"
src={`/icons/ax/secondary_${
props.gridWeapon.ax ? props.gridWeapon.ax[1].modifier : ''
}.png`}
/>
</div>
<span>{createSecondaryAxSkillString()}</span>
</div>
) : (
@ -231,7 +246,7 @@ const WeaponHovercard = (props: Props) => {
className={tintElement}
text={t('buttons.wiki')}
onClick={goTo}
contained={true}
bound={true}
/>
)
@ -240,44 +255,12 @@ const WeaponHovercard = (props: Props) => {
<HovercardTrigger asChild onClick={props.onTriggerClick}>
{props.children}
</HovercardTrigger>
<HovercardContent className="Weapon" side={hovercardSide()}>
<div className="top">
<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>
<HovercardContent className={styles.content} side={hovercardSide()}>
<HovercardHeader
gridObject={props.gridWeapon}
object={props.gridWeapon.object}
type="weapon"
/>
{props.gridWeapon.object.ax &&
props.gridWeapon.ax &&
props.gridWeapon.ax[0].modifier &&