mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-09 03:37:45 +03:00
refactor: migrate achievement list styles from VE to SCSS + BEM
This commit is contained in:
parent
2d665b2266
commit
86d7ced0c0
@ -1,11 +1,10 @@
|
|||||||
import { useDate } from "@renderer/hooks";
|
import { useDate } from "@renderer/hooks";
|
||||||
import type { UserAchievement } from "@types";
|
import type { UserAchievement } from "@types";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import * as styles from "./achievements.css";
|
import "./achievements.scss";
|
||||||
import { EyeClosedIcon } from "@primer/octicons-react";
|
import { EyeClosedIcon } from "@primer/octicons-react";
|
||||||
import HydraIcon from "@renderer/assets/icons/hydra.svg?react";
|
import HydraIcon from "@renderer/assets/icons/hydra.svg?react";
|
||||||
import { useSubscription } from "@renderer/hooks/use-subscription";
|
import { useSubscription } from "@renderer/hooks/use-subscription";
|
||||||
import { vars } from "@renderer/theme.css";
|
|
||||||
|
|
||||||
interface AchievementListProps {
|
interface AchievementListProps {
|
||||||
achievements: UserAchievement[];
|
achievements: UserAchievement[];
|
||||||
@ -17,27 +16,21 @@ export function AchievementList({ achievements }: AchievementListProps) {
|
|||||||
const { formatDateTime } = useDate();
|
const { formatDateTime } = useDate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className={styles.list}>
|
<ul className="achievements__list">
|
||||||
{achievements.map((achievement) => (
|
{achievements.map((achievement) => (
|
||||||
<li
|
<li key={achievement.name} className="achievements__item">
|
||||||
key={achievement.name}
|
|
||||||
className={styles.listItem}
|
|
||||||
style={{ display: "flex" }}
|
|
||||||
>
|
|
||||||
<img
|
<img
|
||||||
className={styles.listItemImage({
|
className={`achievements__item-image ${!achievement.unlocked ? "achievements__item-image--locked" : ""}`}
|
||||||
unlocked: achievement.unlocked,
|
|
||||||
})}
|
|
||||||
src={achievement.icon}
|
src={achievement.icon}
|
||||||
alt={achievement.displayName}
|
alt={achievement.displayName}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div style={{ flex: 1 }}>
|
<div className="achievements__item-content">
|
||||||
<h4 style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
<h4 className="achievements__item-title">
|
||||||
{achievement.hidden && (
|
{achievement.hidden && (
|
||||||
<span
|
<span
|
||||||
style={{ display: "flex" }}
|
className="achievements__item-hidden-icon"
|
||||||
title={t("hidden_achievement_tooltip")}
|
title={t("hidden_achievement_tooltip")}
|
||||||
>
|
>
|
||||||
<EyeClosedIcon size={12} />
|
<EyeClosedIcon size={12} />
|
||||||
@ -47,41 +40,36 @@ export function AchievementList({ achievements }: AchievementListProps) {
|
|||||||
</h4>
|
</h4>
|
||||||
<p>{achievement.description}</p>
|
<p>{achievement.description}</p>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
|
||||||
|
<div className="achievements__item-meta">
|
||||||
{achievement.points != undefined ? (
|
{achievement.points != undefined ? (
|
||||||
<div
|
<div
|
||||||
style={{ display: "flex", alignItems: "center", gap: "4px" }}
|
className="achievements__item-points"
|
||||||
title={t("achievement_earn_points", {
|
title={t("achievement_earn_points", {
|
||||||
points: achievement.points,
|
points: achievement.points,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<HydraIcon width={20} height={20} />
|
<HydraIcon className="achievements__item-points-icon" />
|
||||||
<p style={{ fontSize: "1.1em" }}>{achievement.points}</p>
|
<p className="achievements__item-points-value">
|
||||||
|
{achievement.points}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
onClick={() => showHydraCloudModal("achievements")}
|
onClick={() => showHydraCloudModal("achievements")}
|
||||||
style={{
|
className="achievements__item-points achievements__item-points--locked"
|
||||||
display: "flex",
|
title={t("achievement_earn_points", { points: "???" })}
|
||||||
alignItems: "center",
|
|
||||||
gap: "4px",
|
|
||||||
cursor: "pointer",
|
|
||||||
color: vars.color.warning,
|
|
||||||
}}
|
|
||||||
title={t("achievement_earn_points", {
|
|
||||||
points: "???",
|
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
<HydraIcon width={20} height={20} />
|
<HydraIcon className="achievements__item-points-icon" />
|
||||||
<p style={{ fontSize: "1.1em" }}>???</p>
|
<p className="achievements__item-points-value">???</p>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{achievement.unlockTime != null && (
|
{achievement.unlockTime != null && (
|
||||||
<div
|
<div
|
||||||
|
className="achievements__item-unlock-time"
|
||||||
title={t("unlocked_at", {
|
title={t("unlocked_at", {
|
||||||
date: formatDateTime(achievement.unlockTime),
|
date: formatDateTime(achievement.unlockTime),
|
||||||
})}
|
})}
|
||||||
style={{ whiteSpace: "nowrap", gap: "4px", display: "flex" }}
|
|
||||||
>
|
>
|
||||||
<small>{formatDateTime(achievement.unlockTime)}</small>
|
<small>{formatDateTime(achievement.unlockTime)}</small>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import Skeleton from "react-loading-skeleton";
|
import Skeleton from "react-loading-skeleton";
|
||||||
import * as styles from "./achievements.css";
|
import "./achievements.scss";
|
||||||
|
|
||||||
export function AchievementsSkeleton() {
|
export function AchievementsSkeleton() {
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className="achievements__container">
|
||||||
<div className={styles.hero}>
|
<div className="achievements__hero">
|
||||||
<Skeleton className={styles.heroImageSkeleton} />
|
<Skeleton className="achievements__hero-image-skeleton" />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.heroPanelSkeleton}></div>
|
<div className="achievements__hero-panel-skeleton"></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
262
src/renderer/src/pages/achievements/achievements.scss
Normal file
262
src/renderer/src/pages/achievements/achievements.scss
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
@use "../../scss/globals.scss";
|
||||||
|
@use "sass:math";
|
||||||
|
|
||||||
|
$hero-height: 150px;
|
||||||
|
$logo-height: 100px;
|
||||||
|
$logo-max-width: 200px;
|
||||||
|
|
||||||
|
.achievements {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transition: all ease 0.3s;
|
||||||
|
|
||||||
|
&__hero {
|
||||||
|
width: 100%;
|
||||||
|
height: $hero-height;
|
||||||
|
min-height: $hero-height;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
transition: all ease 0.2s;
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
padding: globals.$spacing-unit * 2;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-logo-backdrop {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-image-skeleton {
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__game-logo {
|
||||||
|
width: $logo-max-width;
|
||||||
|
height: $logo-height;
|
||||||
|
object-fit: contain;
|
||||||
|
transition: all ease 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: auto;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__table-header {
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--color-dark-background);
|
||||||
|
transition: all ease 0.2s;
|
||||||
|
border-bottom: solid 1px var(--color-border);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
&--stuck {
|
||||||
|
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: globals.$spacing-unit * 2;
|
||||||
|
padding: globals.$spacing-unit * 2;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
display: flex;
|
||||||
|
transition: all ease 0.1s;
|
||||||
|
color: var(--color-muted);
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: globals.$spacing-unit globals.$spacing-unit;
|
||||||
|
gap: globals.$spacing-unit * 2;
|
||||||
|
align-items: center;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-image {
|
||||||
|
width: 54px;
|
||||||
|
height: 54px;
|
||||||
|
border-radius: 4px;
|
||||||
|
object-fit: cover;
|
||||||
|
|
||||||
|
&--locked {
|
||||||
|
filter: grayscale(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-hidden-icon {
|
||||||
|
display: flex;
|
||||||
|
color: var(--color-warning);
|
||||||
|
opacity: 0.8;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-eye-closed {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
color: globals.$warning-color;
|
||||||
|
scale: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-points {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
margin-right: 4px;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
&--locked {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-value {
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-unlock-time {
|
||||||
|
white-space: nowrap;
|
||||||
|
gap: 4px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-compared {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 3fr 1fr 1fr;
|
||||||
|
|
||||||
|
&--no-owner {
|
||||||
|
grid-template-columns: 3fr 2fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: globals.$spacing-unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-status {
|
||||||
|
display: flex;
|
||||||
|
padding: globals.$spacing-unit;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&--unlocked {
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: globals.$spacing-unit;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__progress-bar {
|
||||||
|
width: 100%;
|
||||||
|
height: 8px;
|
||||||
|
transition: all ease 0.2s;
|
||||||
|
|
||||||
|
&::-webkit-progress-bar {
|
||||||
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-progress-value {
|
||||||
|
background-color: var(--color-muted);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__profile-avatar {
|
||||||
|
height: 54px;
|
||||||
|
width: 54px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
position: relative;
|
||||||
|
object-fit: cover;
|
||||||
|
|
||||||
|
&--small {
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__subscription-button {
|
||||||
|
text-decoration: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
gap: math.div(globals.$spacing-unit, 2);
|
||||||
|
color: var(--color-body);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,11 @@
|
|||||||
import type { ComparedAchievements } from "@types";
|
import type { ComparedAchievements } from "@types";
|
||||||
import * as styles from "./achievements.css";
|
import "./achievements.scss";
|
||||||
import {
|
import {
|
||||||
CheckCircleIcon,
|
CheckCircleIcon,
|
||||||
EyeClosedIcon,
|
EyeClosedIcon,
|
||||||
LockIcon,
|
LockIcon,
|
||||||
} from "@primer/octicons-react";
|
} from "@primer/octicons-react";
|
||||||
import { useDate } from "@renderer/hooks";
|
import { useDate } from "@renderer/hooks";
|
||||||
import { SPACING_UNIT } from "@renderer/theme.css";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export interface ComparedAchievementListProps {
|
export interface ComparedAchievementListProps {
|
||||||
@ -20,39 +19,26 @@ export function ComparedAchievementList({
|
|||||||
const { formatDateTime } = useDate();
|
const { formatDateTime } = useDate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className={styles.list}>
|
<ul className="achievements__list">
|
||||||
{achievements.achievements.map((achievement, index) => (
|
{achievements.achievements.map((achievement, index) => (
|
||||||
<li
|
<li
|
||||||
key={index}
|
key={index}
|
||||||
className={styles.listItem}
|
className={`achievements__item achievements__item-compared ${
|
||||||
style={{
|
!achievement.ownerStat && "achievements__item-compared--no-owner"
|
||||||
display: "grid",
|
}`}
|
||||||
gridTemplateColumns: achievement.ownerStat
|
|
||||||
? "3fr 1fr 1fr"
|
|
||||||
: "3fr 2fr",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div
|
<div className="achievements__item-main">
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: `${SPACING_UNIT}px`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
<img
|
||||||
className={styles.listItemImage({
|
className="achievements__item-image"
|
||||||
unlocked: true,
|
|
||||||
})}
|
|
||||||
src={achievement.icon}
|
src={achievement.icon}
|
||||||
alt={achievement.displayName}
|
alt={achievement.displayName}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div className="achievements__item-content">
|
||||||
<h4 style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
<h4 className="achievements__item-title">
|
||||||
{achievement.hidden && (
|
{achievement.hidden && (
|
||||||
<span
|
<span
|
||||||
style={{ display: "flex" }}
|
className="achievements__item-hidden-icon"
|
||||||
title={t("hidden_achievement_tooltip")}
|
title={t("hidden_achievement_tooltip")}
|
||||||
>
|
>
|
||||||
<EyeClosedIcon size={12} />
|
<EyeClosedIcon size={12} />
|
||||||
@ -67,25 +53,13 @@ export function ComparedAchievementList({
|
|||||||
{achievement.ownerStat ? (
|
{achievement.ownerStat ? (
|
||||||
achievement.ownerStat.unlocked ? (
|
achievement.ownerStat.unlocked ? (
|
||||||
<div
|
<div
|
||||||
style={{
|
className="achievements__item-status achievements__item-status--unlocked"
|
||||||
whiteSpace: "nowrap",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
gap: `${SPACING_UNIT}px`,
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
title={formatDateTime(achievement.ownerStat.unlockTime!)}
|
title={formatDateTime(achievement.ownerStat.unlockTime!)}
|
||||||
>
|
>
|
||||||
<CheckCircleIcon />
|
<CheckCircleIcon />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div className="achievements__item-status">
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
padding: `${SPACING_UNIT}px`,
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<LockIcon />
|
<LockIcon />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -93,25 +67,13 @@ export function ComparedAchievementList({
|
|||||||
|
|
||||||
{achievement.targetStat.unlocked ? (
|
{achievement.targetStat.unlocked ? (
|
||||||
<div
|
<div
|
||||||
style={{
|
className="achievements__item-status achievements__item-status--unlocked"
|
||||||
whiteSpace: "nowrap",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
gap: `${SPACING_UNIT}px`,
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
title={formatDateTime(achievement.targetStat.unlockTime!)}
|
title={formatDateTime(achievement.targetStat.unlockTime!)}
|
||||||
>
|
>
|
||||||
<CheckCircleIcon />
|
<CheckCircleIcon />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div className="achievements__item-status">
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
padding: `${SPACING_UNIT}px`,
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<LockIcon />
|
<LockIcon />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
Loading…
Reference in New Issue
Block a user