Add TranscendenceStar and TranscendenceFragment

`TranscendenceStar` is a new star capable of displaying `TranscendenceFragment`s depending on the `GridCharacter` stage
This commit is contained in:
Justin Edmund 2023-01-22 14:40:21 -08:00
parent d618d44429
commit 9c757c2c5b
4 changed files with 314 additions and 0 deletions

View file

@ -0,0 +1,83 @@
.Fragment {
$degrees: 72deg;
$origWidth: 29px;
$origHeight: 54px;
$scaledWidth: 12px;
$scaledHeight: calc(($scaledWidth / $origWidth) * $origHeight);
$scale: 1.2;
@include hidpiImage(
'/icons/transcendence/interactive/interactive-piece',
png,
$scaledWidth,
$scaledHeight
);
position: absolute;
z-index: 32;
aspect-ratio: 29 / 54;
height: $scaledHeight;
width: $scaledWidth;
opacity: 0;
&:hover {
cursor: pointer;
}
&.Visible {
opacity: 1;
}
&.Stage1 {
top: 3px;
left: 18px;
// &:hover {
// transform: scale($scale, $scale) translateY(-2px);
// }
}
&.Stage2 {
top: 10px;
left: 27px;
transform: rotate($degrees);
// &:hover {
// transform: rotate($degrees) scale($scale, $scale) translateY(-2px);
// }
}
&.Stage3 {
top: 21px;
left: 24px;
transform: rotate($degrees * 2);
// &:hover {
// transform: rotate($degrees * 2) scale($scale, $scale) translateY(-1px);
// }
}
&.Stage4 {
top: 21px;
left: 12px;
transform: rotate($degrees * 3);
// &:hover {
// transform: rotate($degrees * 3) scale($scale, $scale) translateY(-1px);
// }
}
&.Stage5 {
top: 10px;
left: 8px;
transform: rotate($degrees * 4);
// &:hover {
// transform: rotate($degrees * 4) scale($scale, $scale) translateY(-1px);
// }
}
}

View file

@ -0,0 +1,49 @@
import React from 'react'
import classnames from 'classnames'
import './index.scss'
interface Props {
stage: number
interactive: boolean
visible: boolean
onClick?: (index: number) => void
onHover?: (index: number) => void
}
const TranscendenceFragment = ({
interactive,
stage,
visible,
onClick,
onHover,
}: Props) => {
const classes = classnames({
Fragment: true,
Visible: visible,
Stage1: stage === 1,
Stage2: stage === 2,
Stage3: stage === 3,
Stage4: stage === 4,
Stage5: stage === 5,
})
function handleClick() {
if (interactive && onClick) onClick(stage)
}
function handleHover() {
if (interactive && onHover) onHover(stage)
}
return (
<i className={classes} onClick={handleClick} onMouseOver={handleHover} />
)
}
TranscendenceFragment.defaultProps = {
interactive: false,
visible: false,
}
export default TranscendenceFragment

View file

@ -0,0 +1,79 @@
.TranscendenceStar {
position: relative;
&.Immutable {
pointer-events: none;
}
.Figure {
$size: 18px;
background-repeat: no-repeat;
background-size: 54px 54px;
display: block;
height: $size;
width: $size;
&.Interactive.Base {
$size: $unit-6x;
@include hidpiImage(
'/icons/transcendence/interactive/interactive-base',
png,
$size,
$size
);
height: $size;
width: $size;
&:hover {
cursor: pointer;
transform: none;
}
}
&:hover {
transform: scale(1.2);
}
&.Stage1 {
background-image: url('/icons/transcendence/1/step-1@3x.png');
&:hover {
background-image: url('/icons/transcendence/1/step-1-hover@3x.png');
}
}
&.Stage2 {
background-image: url('/icons/transcendence/2/step-2@3x.png');
&:hover {
background-image: url('/icons/transcendence/2/step-2-hover@3x.png');
}
}
&.Stage3 {
background-image: url('/icons/transcendence/3/step-3@3x.png');
&:hover {
background-image: url('/icons/transcendence/3/step-3-hover@3x.png');
}
}
&.Stage4 {
background-image: url('/icons/transcendence/4/step-4@3x.png');
&:hover {
background-image: url('/icons/transcendence/4/step-4-hover@3x.png');
}
}
&.Stage5 {
background-image: url('/icons/transcendence/5/step-5@3x.png');
&:hover {
background-image: url('/icons/transcendence/5/step-5-hover@3x.png');
}
}
}
}

View file

@ -0,0 +1,103 @@
import React, { useEffect, useState } from 'react'
import classnames from 'classnames'
import './index.scss'
import TranscendenceFragment from '~components/TranscendenceFragment'
interface Props {
className?: string
stage: number
editable: boolean
interactive: boolean
onClick?: () => void
onFragmentClick?: (newStage: number) => void
onFragmentHover?: (newStage: number) => void
}
const NUM_FRAGMENTS = 5
const TranscendenceStar = ({
className,
interactive,
stage,
editable,
onClick,
onFragmentClick,
onFragmentHover,
}: Props) => {
const [visibleStage, setVisibleStage] = useState(0)
const [currentStage, setCurrentStage] = useState(0)
// Classes
const starClasses = classnames({
TranscendenceStar: true,
Immutable: !editable,
})
const baseImageClasses = classnames(className, {
Figure: true,
})
useEffect(() => {
setVisibleStage(stage)
setCurrentStage(stage)
}, [stage])
function handleClick() {
if (onClick) {
onClick()
}
}
function handleFragmentClick(index: number) {
let newStage = index
if (index === currentStage) newStage = 0
setVisibleStage(newStage)
setCurrentStage(newStage)
if (onFragmentClick) onFragmentClick(newStage)
}
function handleFragmentHover(index: number) {
setVisibleStage(index)
if (onFragmentHover) onFragmentHover(index)
}
function handleMouseLeave() {
setVisibleStage(currentStage)
if (onFragmentHover) onFragmentHover(currentStage)
}
return (
<li
className={starClasses}
onClick={interactive ? handleClick : () => {}}
onMouseLeave={interactive ? handleMouseLeave : () => {}}
>
<div className="Fragments">
{[...Array(NUM_FRAGMENTS)].map((e, i) => {
const loopStage = i + 1
return (
<TranscendenceFragment
key={`fragment_${loopStage}`}
stage={loopStage}
visible={loopStage <= visibleStage}
interactive={interactive}
onClick={interactive ? handleFragmentClick : () => {}}
onHover={interactive ? handleFragmentHover : () => {}}
/>
)
})}
</div>
<i className={baseImageClasses} />
</li>
)
}
TranscendenceStar.defaultProps = {
stage: 0,
editable: false,
interactive: false,
}
export default TranscendenceStar