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 { style } from "@vanilla-extract/css";
import { recipe } from "@vanilla-extract/recipes"; import { recipe } from "@vanilla-extract/recipes";
export const container = style({ export const HERO_HEIGHT = 300;
width: "100%",
export const wrapper = style({
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
gap: `${SPACING_UNIT * 2}px`, overflow: "hidden",
width: "100%",
height: "100%",
transition: "all ease 0.3s",
}); });
export const header = style({ export const header = style({
display: "flex", display: "flex",
height: `${HERO_HEIGHT}px`,
minHeight: `${HERO_HEIGHT}px`,
gap: `${SPACING_UNIT}px`, gap: `${SPACING_UNIT}px`,
flexDirection: "column", flexDirection: "column",
position: "relative",
transition: "all ease 0.2s",
"@media": {
"(min-width: 1250px)": {
height: "350px",
minHeight: "350px",
},
},
}); });
export const headerImage = style({ export const headerImage = style({
position: "absolute",
inset: "0",
borderRadius: "4px", borderRadius: "4px",
objectFit: "cover", objectFit: "cover",
cursor: "pointer", cursor: "pointer",
@ -23,6 +39,45 @@ export const headerImage = style({
transition: "all ease 0.2s", 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({ export const list = style({
listStyle: "none", listStyle: "none",
margin: "0", margin: "0",
@ -30,6 +85,7 @@ export const list = style({
flexDirection: "column", flexDirection: "column",
gap: `${SPACING_UNIT * 2}px`, gap: `${SPACING_UNIT * 2}px`,
padding: `${SPACING_UNIT * 2}px`, padding: `${SPACING_UNIT * 2}px`,
backgroundColor: vars.color.background,
}); });
export const listItem = style({ export const listItem = style({

View file

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