feat: achievements page adjustments

This commit is contained in:
Zamitto 2024-10-11 16:01:15 -03:00
parent 0d909d6eeb
commit 7e25741657
2 changed files with 148 additions and 69 deletions

View file

@ -2,20 +2,36 @@ import { SPACING_UNIT, vars } from "../../theme.css";
import { style } from "@vanilla-extract/css";
import { recipe } from "@vanilla-extract/recipes";
export const container = style({
width: "100%",
export const HERO_HEIGHT = 300;
export const wrapper = style({
display: "flex",
flexDirection: "column",
gap: `${SPACING_UNIT * 2}px`,
overflow: "hidden",
width: "100%",
height: "100%",
transition: "all ease 0.3s",
});
export const header = style({
display: "flex",
height: `${HERO_HEIGHT}px`,
minHeight: `${HERO_HEIGHT}px`,
gap: `${SPACING_UNIT}px`,
flexDirection: "column",
position: "relative",
transition: "all ease 0.2s",
"@media": {
"(min-width: 1250px)": {
height: "350px",
minHeight: "350px",
},
},
});
export const headerImage = style({
position: "absolute",
inset: "0",
borderRadius: "4px",
objectFit: "cover",
cursor: "pointer",
@ -23,6 +39,45 @@ export const headerImage = style({
transition: "all ease 0.2s",
});
export const gameLogo = style({
padding: `${SPACING_UNIT * 2}px`,
width: "300px",
});
export const container = style({
width: "100%",
height: "100%",
display: "flex",
flexDirection: "column",
overflow: "auto",
zIndex: "1",
});
export const panel = recipe({
base: {
width: "100%",
height: "100px",
minHeight: "100px",
padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 2}px`,
backgroundColor: vars.color.darkBackground,
display: "flex",
flexDirection: "column",
transition: "all ease 0.2s",
borderBottom: `solid 1px ${vars.color.border}`,
position: "sticky",
overflow: "hidden",
top: "0",
zIndex: "1",
},
variants: {
stuck: {
true: {
boxShadow: "0px 0px 15px 0px rgba(0, 0, 0, 0.8)",
},
},
},
});
export const list = style({
listStyle: "none",
margin: "0",
@ -30,6 +85,7 @@ export const list = style({
flexDirection: "column",
gap: `${SPACING_UNIT * 2}px`,
padding: `${SPACING_UNIT * 2}px`,
backgroundColor: vars.color.background,
});
export const listItem = style({

View file

@ -2,16 +2,15 @@ import { setHeaderTitle } from "@renderer/features";
import { useAppDispatch, useDate } from "@renderer/hooks";
import { steamUrlBuilder } from "@shared";
import type { GameShop, UserAchievement } from "@types";
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate, useSearchParams } from "react-router-dom";
import { useSearchParams } from "react-router-dom";
import * as styles from "./achievements.css";
import {
buildGameDetailsPath,
formatDownloadProgress,
} from "@renderer/helpers";
import { formatDownloadProgress } from "@renderer/helpers";
import { TrophyIcon } from "@primer/octicons-react";
import { SPACING_UNIT, vars } from "@renderer/theme.css";
import { vars } from "@renderer/theme.css";
const HERO_ANIMATION_THRESHOLD = 25;
export function Achievement() {
const [searchParams] = useSearchParams();
@ -21,10 +20,14 @@ export function Achievement() {
const userId = searchParams.get("userId");
const displayName = searchParams.get("displayName");
const heroRef = useRef<HTMLDivElement | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
const [isHeaderStuck, setIsHeaderStuck] = useState(false);
const [backdropOpactiy, setBackdropOpacity] = useState(1);
const { t } = useTranslation("achievement");
const { format } = useDate();
const navigate = useNavigate();
const dispatch = useAppDispatch();
@ -42,18 +45,30 @@ export function Achievement() {
useEffect(() => {
if (title) {
dispatch(
setHeaderTitle(
displayName
? t("user_achievements", {
displayName,
})
: t("your_achievements")
)
);
dispatch(setHeaderTitle(title));
}
}, [dispatch, title]);
const onScroll: React.UIEventHandler<HTMLElement> = (event) => {
const heroHeight = heroRef.current?.clientHeight ?? styles.HERO_HEIGHT;
const scrollY = (event.target as HTMLDivElement).scrollTop;
const opacity = Math.max(
0,
1 - scrollY / (heroHeight - HERO_ANIMATION_THRESHOLD)
);
if (scrollY >= heroHeight && !isHeaderStuck) {
setIsHeaderStuck(true);
}
if (scrollY <= heroHeight && isHeaderStuck) {
setIsHeaderStuck(false);
}
setBackdropOpacity(opacity);
};
if (!objectId || !shop || !title) return null;
const unlockedAchievementCount = achievements.filter(
@ -62,40 +77,48 @@ export function Achievement() {
const totalAchievementCount = achievements.length;
const handleClickGame = () => {
navigate(
buildGameDetailsPath({
shop: shop as GameShop,
objectId,
title,
})
);
};
return (
<div className={styles.container}>
<div className={styles.header}>
<button onClick={handleClickGame}>
<img
src={steamUrlBuilder.libraryHero(objectId)}
alt={title}
className={styles.headerImage}
<div className={styles.wrapper}>
<img
src={steamUrlBuilder.libraryHero(objectId)}
alt={title}
className={styles.headerImage}
/>
<section
ref={containerRef}
onScroll={onScroll}
className={styles.container}
>
<div className={styles.header}>
<div
style={{
flex: 1,
opacity: Math.min(1, 1 - backdropOpactiy),
}}
/>
</button>
<div
style={{
width: "100%",
display: "flex",
flexDirection: "column",
padding: `0 ${SPACING_UNIT * 2}px`,
}}
>
<h1>{title}</h1>
<img
src={steamUrlBuilder.logo(objectId!)}
className={styles.gameLogo}
alt={title}
/>
</div>
<div className={styles.panel({ stuck: isHeaderStuck })}>
<h1 style={{ fontSize: "1.2em", marginBottom: "8px" }}>
{displayName
? t("user_achievements", {
displayName,
})
: t("your_achievements")}
</h1>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: 8,
width: "100%",
color: vars.color.muted,
}}
>
@ -124,29 +147,29 @@ export function Achievement() {
className={styles.achievementsProgressBar}
/>
</div>
</div>
<ul className={styles.list}>
{achievements.map((achievement, index) => (
<li key={index} className={styles.listItem}>
<img
className={styles.listItemImage({
unlocked: achievement.unlocked,
})}
src={achievement.icon}
alt={achievement.displayName}
loading="lazy"
/>
<div>
<p>{achievement.displayName}</p>
<p>{achievement.description}</p>
<small>
{achievement.unlockTime && format(achievement.unlockTime)}
</small>
</div>
</li>
))}
</ul>
<ul className={styles.list}>
{achievements.map((achievement, index) => (
<li key={index} className={styles.listItem}>
<img
className={styles.listItemImage({
unlocked: achievement.unlocked,
})}
src={achievement.icon}
alt={achievement.displayName}
loading="lazy"
/>
<div>
<p>{achievement.displayName}</p>
<p>{achievement.description}</p>
<small>
{achievement.unlockTime && format(achievement.unlockTime)}
</small>
</div>
</li>
))}
</ul>
</section>
</div>
);
}