full migration to scss

This commit is contained in:
Nate 2025-01-17 19:43:41 -03:00
parent b0eb7c16cd
commit d038398750
45 changed files with 310 additions and 396 deletions

View File

@ -110,8 +110,8 @@ export function Modal({
<Backdrop isClosing={isClosing}>
<div
className={cn("modal", {
"modal__closing": isClosing,
"modal__large": large,
modal__closing: isClosing,
modal__large: large,
})}
role="dialog"
aria-labelledby={title}

View File

@ -171,8 +171,8 @@ export function Sidebar() {
<aside
ref={sidebarRef}
className={cn("sidebar", {
"sidebar__resizing": isResizing,
"sidebar__darwin": window.electron.platform === "darwin",
sidebar__resizing: isResizing,
sidebar__darwin: window.electron.platform === "darwin",
})}
style={{
width: sidebarWidth,

View File

@ -79,7 +79,7 @@ export function Toast({ visible, message, type, onClose }: ToastProps) {
return (
<div
className={cn("toast", {
"toast__closing": isClosing,
toast__closing: isClosing,
})}
>
<div className="toast__content">

View File

@ -5,7 +5,7 @@ import { EyeClosedIcon } from "@primer/octicons-react";
import HydraIcon from "@renderer/assets/icons/hydra.svg?react";
import { useSubscription } from "@renderer/hooks/use-subscription";
import "./achievements.scss";
import "../../scss/_variables.scss"
import "../../scss/_variables.scss";
interface AchievementListProps {
achievements: UserAchievement[];

View File

@ -4,7 +4,6 @@ import { UserAchievement } from "@types";
import { useSubscription } from "@renderer/hooks/use-subscription";
import { useUserDetails } from "@renderer/hooks";
import "./achievement-panel.sccs";
export interface AchievementPanelProps {

View File

@ -8,18 +8,18 @@ import {
formatDownloadProgress,
} from "@renderer/helpers";
import { LockIcon, PersonIcon, TrophyIcon } from "@primer/octicons-react";
import { SPACING_UNIT, vars } from "@renderer/theme.css";
import { gameDetailsContext } from "@renderer/context";
import type { ComparedAchievements } from "@types";
import { average } from "color.js";
import Color from "color";
import { Link } from "@renderer/components";
import { ComparedAchievementList } from "./compared-achievement-list";
import * as styles from "./achievements.css";
import { AchievementList } from "./achievement-list";
import { AchievementPanel } from "./achievement-panel";
import { ComparedAchievementPanel } from "./compared-achievement-panel";
import { useSubscription } from "@renderer/hooks/use-subscription";
import "./achievements.scss";
import "../../scss/_variables.scss";
interface UserInfo {
id: string;
@ -48,10 +48,10 @@ function AchievementSummary({ user, isComparison }: AchievementSummaryProps) {
user: Pick<UserInfo, "profileImageUrl" | "displayName">
) => {
return (
<div className={styles.profileAvatar}>
<div className="achievements__profile-avatar">
{user.profileImageUrl ? (
<img
className={styles.profileAvatar}
className="achievements__profile-avatar"
src={user.profileImageUrl}
alt={user.displayName}
/>
@ -64,97 +64,38 @@ function AchievementSummary({ user, isComparison }: AchievementSummaryProps) {
if (isComparison && userDetails?.id == user.id && !hasActiveSubscription) {
return (
<div
style={{
display: "flex",
gap: `${SPACING_UNIT * 2}px`,
alignItems: "center",
position: "relative",
padding: `${SPACING_UNIT}px`,
}}
>
<div
style={{
position: "absolute",
zIndex: 2,
inset: 0,
width: "100%",
height: "100%",
background: "rgba(0, 0, 0, 0.7)",
display: "flex",
alignItems: "center",
flexDirection: "row",
gap: `${SPACING_UNIT}px`,
borderRadius: "4px",
justifyContent: "center",
}}
>
<div className="achievements__summary achievements__summary--locked">
<div className="achievements__summary-overlay">
<LockIcon size={24} />
<h3>
<button
className={styles.subscriptionRequiredButton}
className="achievements__subscription-required-button"
onClick={() => showHydraCloudModal("achievements")}
>
{t("subscription_needed")}
</button>
</h3>
</div>
<div
style={{
display: "flex",
gap: `${SPACING_UNIT * 2}px`,
alignItems: "center",
height: "62px",
position: "relative",
filter: "blur(4px)",
}}
>
<div className="achievements__summary-content achievements__summary-content--blurred">
{getProfileImage(user)}
<h1 style={{ marginBottom: "8px" }}>{user.displayName}</h1>
<h1 className="achievements__summary-title">{user.displayName}</h1>
</div>
</div>
);
}
return (
<div
style={{
display: "flex",
gap: `${SPACING_UNIT * 2}px`,
alignItems: "center",
padding: `${SPACING_UNIT}px`,
}}
>
<div className="achievements__summary">
{getProfileImage(user)}
<div
style={{
display: "flex",
flexDirection: "column",
width: "100%",
}}
>
<h1 style={{ marginBottom: "8px" }}>{user.displayName}</h1>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: 8,
color: "#c0c1c7",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: 8,
}}
>
<div className="achievements__summary-details">
<h1 className="achievements__summary-title">{user.displayName}</h1>
<div className="achievements__summary-stats">
<div className="achievements__summary-count">
<TrophyIcon size={13} />
<span>
{user.unlockedAchievementCount} / {user.totalAchievementCount}
</span>
</div>
<span>
{formatDownloadProgress(
user.unlockedAchievementCount / user.totalAchievementCount
@ -164,7 +105,7 @@ function AchievementSummary({ user, isComparison }: AchievementSummaryProps) {
<progress
max={1}
value={user.unlockedAchievementCount / user.totalAchievementCount}
className={styles.achievementsProgressBar}
className="achievements__progress-bar"
/>
</div>
</div>
@ -201,9 +142,10 @@ export function AchievementsContent({
setGameColor(backgroundColor);
};
const HERO_HEIGHT = 150;
const onScroll: React.UIEventHandler<HTMLElement> = (event) => {
const heroHeight = heroRef.current?.clientHeight ?? styles.HERO_HEIGHT;
const heroHeight = heroRef.current?.clientHeight ?? HERO_HEIGHT;
const scrollY = (event.target as HTMLDivElement).scrollTop;
if (scrollY >= heroHeight && !isHeaderStuck) {
@ -219,10 +161,10 @@ export function AchievementsContent({
user: Pick<UserInfo, "profileImageUrl" | "displayName">
) => {
return (
<div className={styles.profileAvatarSmall}>
<div className="achievements__profile-avatar-small">
{user.profileImageUrl ? (
<img
className={styles.profileAvatarSmall}
className="achievements__profile-avatar-small"
src={user.profileImageUrl}
alt={user.displayName}
/>
@ -236,10 +178,10 @@ export function AchievementsContent({
if (!objectId || !shop || !gameTitle || !userDetails) return null;
return (
<div className={styles.wrapper}>
<div className="achievements__wrapper">
<img
src={steamUrlBuilder.libraryHero(objectId)}
style={{ display: "none" }}
className="achievements__hidden-image"
alt={gameTitle}
onLoad={handleHeroLoad}
/>
@ -247,38 +189,29 @@ export function AchievementsContent({
<section
ref={containerRef}
onScroll={onScroll}
className={styles.container}
className="achievements__container"
>
<div
className="achievements__gradient-background"
style={{
display: "flex",
flexDirection: "column",
background: `linear-gradient(0deg, #1c1c1c 0%, ${gameColor} 100%)`,
}}
>
<div ref={heroRef} className={styles.hero}>
<div className={styles.heroContent}>
<div ref={heroRef} className="achievements__hero">
<div className="achievements__hero-content">
<Link
to={buildGameDetailsPath({ shop, objectId, title: gameTitle })}
>
<img
src={steamUrlBuilder.logo(objectId)}
className={styles.gameLogo}
className="achievements__game-logo"
alt={gameTitle}
/>
</Link>
</div>
</div>
<div
style={{
display: "flex",
flexDirection: "column",
width: "100%",
gap: `${SPACING_UNIT}px`,
padding: `${SPACING_UNIT}px`,
}}
>
<div className="achievements__summary-container">
<AchievementSummary
user={{
...userDetails,
@ -298,24 +231,24 @@ export function AchievementsContent({
</div>
{otherUser && (
<div className={styles.tableHeader({ stuck: isHeaderStuck })}>
<div
style={{
display: "grid",
gridTemplateColumns: hasActiveSubscription
? "3fr 1fr 1fr"
: "3fr 2fr",
gap: `${SPACING_UNIT * 2}px`,
padding: `${SPACING_UNIT}px ${SPACING_UNIT * 3}px`,
}}
className={classNames("achievements__table-header", {
"achievements__table-header--stuck": isHeaderStuck,
})}
>
<div
className={classNames("achievements__grid-container", {
"achievements__grid-container--no-subscription":
!hasActiveSubscription,
})}
>
<div></div>
{hasActiveSubscription && (
<div style={{ display: "flex", justifyContent: "center" }}>
<div className="achievements__profile-center">
{getProfileImage({ ...userDetails })}
</div>
)}
<div style={{ display: "flex", justifyContent: "center" }}>
<div className="achievements__profile-center">
{getProfileImage(otherUser)}
</div>
</div>

View File

@ -76,10 +76,7 @@ export default function Achievements() {
(otherUserId && comparedAchievements === null);
return (
<SkeletonTheme
baseColor="#1c1c1c"
highlightColor="#444"
>
<SkeletonTheme baseColor="#1c1c1c" highlightColor="#444">
{showSkeleton ? (
<AchievementsSkeleton />
) : (

View File

@ -19,4 +19,10 @@
border: 1px solid $border-color;
align-self: flex-start;
}
&__header {
display: flex;
gap: calc(var(--spacing-unit) * 2);
justify-content: space-between;
}
}

View File

@ -9,8 +9,8 @@ import {
import { useEffect, useMemo, useRef, useState } from "react";
import "./catalogue.scss";
import "../../scss/_variables.scss";
import { SPACING_UNIT, vars } from "@renderer/theme.css";
import { downloadSourcesTable } from "@renderer/dexie";
import { FilterSection } from "./filter-section";
import { setFilters, setPage } from "@renderer/features";
@ -270,13 +270,7 @@ export default function Catalogue() {
</div>
</div>
<div
style={{
display: "flex",
gap: SPACING_UNIT * 2,
justifyContent: "space-between",
}}
>
<div className="catalogue__header">
<div
style={{
display: "flex",
@ -287,8 +281,8 @@ export default function Catalogue() {
>
{isLoading ? (
<SkeletonTheme
baseColor={vars.color.darkBackground}
highlightColor={vars.color.background}
baseColor="var(--dark-background-color)"
highlightColor="var(--background-color)"
>
{Array.from({ length: PAGE_SIZE }).map((_, i) => (
<Skeleton
@ -296,7 +290,7 @@ export default function Catalogue() {
style={{
height: 105,
borderRadius: 4,
border: `solid 1px ${vars.color.border}`,
border: `solid 1px var(--border-color)`,
}}
/>
))}

View File

@ -1,5 +1,5 @@
import { vars } from "@renderer/theme.css";
import { XIcon } from "@primer/octicons-react";
import "../../scss/_variables.scss";
interface FilterItemProps {
filter: string;
@ -13,11 +13,11 @@ export function FilterItem({ filter, orbColor, onRemove }: FilterItemProps) {
style={{
display: "flex",
alignItems: "center",
color: vars.color.body,
backgroundColor: vars.color.darkBackground,
color: "var(--body-color)",
backgroundColor: "var(--dark-background-color",
padding: "6px 12px",
borderRadius: 4,
border: `solid 1px ${vars.color.border}`,
border: `solid 1px var(--border-color)`,
fontSize: 12,
}}
>
@ -35,7 +35,7 @@ export function FilterItem({ filter, orbColor, onRemove }: FilterItemProps) {
type="button"
onClick={onRemove}
style={{
color: vars.color.body,
color: "var(--body-color)",
marginLeft: 4,
display: "flex",
alignItems: "center",

View File

@ -3,8 +3,8 @@ import { useFormat } from "@renderer/hooks";
import { useCallback, useMemo, useState } from "react";
import List from "rc-virtual-list";
import { vars } from "@renderer/theme.css";
import { useTranslation } from "react-i18next";
import "../../scss/_variables.scss";
export interface FilterSectionProps {
title: string;
@ -80,7 +80,7 @@ export function FilterSection({
fontSize: 12,
marginBottom: 12,
display: "block",
color: vars.color.body,
color: "var(--body-color)",
cursor: "pointer",
textDecoration: "underline",
}}

View File

@ -30,7 +30,6 @@ export function DeleteGameModal({
onClose={onClose}
>
<div className="delete-game-modal__actions-buttons-ctn">
<Button onClick={handleDeleteGame} theme="outline">
{t("delete")}
</Button>

View File

@ -12,7 +12,6 @@ import { Downloader, formatBytes, steamUrlBuilder } from "@shared";
import { DOWNLOADER_NAME } from "@renderer/constants";
import { useAppSelector, useDownload } from "@renderer/hooks";
import "./download-group.scss";
import { useTranslation } from "react-i18next";

View File

@ -18,8 +18,8 @@ import { useTranslation } from "react-i18next";
import { AxiosProgressEvent } from "axios";
import { formatDownloadProgress } from "@renderer/helpers";
import "./cloud-sync-modal.scss"
import "../../../scss/_variables.scss"
import "./cloud-sync-modal.scss";
import "../../../scss/_variables.scss";
export interface CloudSyncModalProps
extends Omit<ModalProps, "children" | "title"> {}

View File

@ -2,7 +2,7 @@ import { useContext, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { ChevronRightIcon, ChevronLeftIcon } from "@primer/octicons-react";
import "./gallery-slider.scss"
import "./gallery-slider.scss";
import { gameDetailsContext } from "@renderer/context";
const getButtonClasses = (visible: boolean, direction: "left" | "right") => {

View File

@ -31,7 +31,6 @@ export function GameDetailsContent() {
game,
gameColor,
setGameColor,
hasNSFWContentBlocked,
} = useContext(gameDetailsContext);
const { showHydraCloudModal } = useSubscription();
@ -78,9 +77,10 @@ export function GameDetailsContent() {
useEffect(() => {
setBackdropOpacity(1);
}, [objectId]);
const HERO_HEIGHT = 150;
const onScroll: React.UIEventHandler<HTMLElement> = (event) => {
const heroHeight = heroRef.current?.clientHeight ?? styles.HERO_HEIGHT;
const heroHeight = heroRef.current?.clientHeight ?? HERO_HEIGHT;
const scrollY = (event.target as HTMLDivElement).scrollTop;
const opacity = Math.max(

View File

@ -24,7 +24,6 @@ $hero-height: 300px;
&__blurred-content {
filter: blur(20px);
}
}

View File

@ -12,10 +12,8 @@ import { useTranslation } from "react-i18next";
import { SkeletonTheme } from "react-loading-skeleton";
import { GameDetailsSkeleton } from "./game-details-skeleton";
import "./game-details.scss";
import { GameDetailsContent } from "./game-details-content";
import {
CloudSyncContextConsumer,
@ -149,10 +147,7 @@ export default function GameDetails() {
)}
</CloudSyncContextConsumer>
<SkeletonTheme
baseColor="#1c1c1c"
highlightColor="#444"
>
<SkeletonTheme baseColor="#1c1c1c" highlightColor="#444">
{isLoading ? <GameDetailsSkeleton /> : <GameDetailsContent />}
<RepacksModal

View File

@ -8,7 +8,7 @@ import { Button } from "@renderer/components";
import { useDownload, useLibrary } from "@renderer/hooks";
import { useContext, useState } from "react";
import { useTranslation } from "react-i18next";
import "./hero-panel-actions.scss"
import "./hero-panel-actions.scss";
import { gameDetailsContext } from "@renderer/context";

View File

@ -1,6 +1,6 @@
import { useContext, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import "./hero-panel.scss"
import "./hero-panel.scss";
import { formatDownloadProgress } from "@renderer/helpers";
import { useDate, useDownload, useFormat } from "@renderer/hooks";
import { Link } from "@renderer/components";

View File

@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next";
import { useDate, useDownload } from "@renderer/hooks";
import { HeroPanelActions } from "./hero-panel-actions";
import "./hero-panel.scss"
import "./hero-panel.scss";
import { HeroPanelPlaytime } from "./hero-panel-playtime";
import { gameDetailsContext } from "@renderer/context";

View File

@ -9,7 +9,7 @@ import type { GameRepack } from "@types";
import { DOWNLOADER_NAME } from "@renderer/constants";
import { useAppSelector, useFeature, useToast } from "@renderer/hooks";
import "./download-settings-modal.scss";
import "../../../scss/_variables.scss"
import "../../../scss/_variables.scss";
export interface DownloadSettingsModalProps {
visible: boolean;

View File

@ -1,7 +1,7 @@
import { useTranslation } from "react-i18next";
import { Button, Modal } from "@renderer/components";
import * as styles from "./remove-from-library-modal.css";
import type { Game } from "@types";
import "./remove-from-library-modal.scss";
interface RemoveGameFromLibraryModalProps {
visible: boolean;

View File

@ -1,7 +1,7 @@
import { useTranslation } from "react-i18next";
import { Button, Modal } from "@renderer/components";
import type { Game } from "@types";
import "./remove-from-library-modal.scss"
import "./remove-from-library-modal.scss";
type ResetAchievementsModalProps = Readonly<{
visible: boolean;
game: Game;

View File

@ -3,8 +3,8 @@ import { useTranslation } from "react-i18next";
import type { HowLongToBeatCategory } from "@types";
import { SidebarSection } from "../sidebar-section/sidebar-section";
import "./sidebar.scss"
import "../../../scss/_variables.scss"
import "./sidebar.scss";
import "../../../scss/_variables.scss";
const durationTranslation: Record<string, string> = {
Hours: "hours",

View File

@ -131,7 +131,8 @@ return (
<div className="sidebar__list-item">
<img
className={classNames("sidebar__list-item-image", {
"sidebar__list-item-image--unlocked": achievement.unlocked,
"sidebar__list-item-image--unlocked":
achievement.unlocked,
"sidebar__list-item-image--blurred": true,
})}
src={achievement.icon}

View File

@ -1,7 +1,7 @@
import { LockIcon } from "@primer/octicons-react";
import { useTranslation } from "react-i18next";
import "./locked-profile.scss"
import "./locked-profile.scss";
export function LockedProfile() {
const { t } = useTranslation("user_profile");

View File

@ -259,8 +259,7 @@
justify-content: flex-end;
height: 100%;
width: 100%;
background:
linear-gradient(0deg, rgba(0,0,0 0.70) 20%, transparent 100%);
background: linear-gradient(0deg, rgba(0, 0, 0 0.7) 20%, transparent 100%);
padding: 8;
}
@ -272,7 +271,7 @@
display: flex;
align-items: center;
gap: 4;
padding: 4px,
padding: 4px;
}
&__game-card-stats-container {

View File

@ -87,9 +87,7 @@ export function ProfileContent() {
const shouldShowRightContent = hasGames || userProfile.friends.length > 0;
return (
<section
className="profile-content__container"
>
<section className="profile-content__container">
<div style={{ flex: 1 }}>
{!hasGames && (
<div className="profile-content__no-games">

View File

@ -63,7 +63,9 @@ export function RecentGamesBox() {
/>
<div className="profile-content__list-item-details">
<span className="profile-content__list-item-title">{game.title}</span>
<span className="profile-content__list-item-title">
{game.title}
</span>
<div className="profile-content__list-item-description">
<ClockIcon />

View File

@ -1,4 +1,4 @@
import "./profile-hero.scss"
import "./profile-hero.scss";
import { useCallback, useContext, useMemo, useState } from "react";
import { userProfileContext } from "@renderer/context";
@ -362,11 +362,7 @@ export function ProfileHero() {
background: backgroundImage ? backgroundImageLayer : heroBackground,
}}
>
<div
className="profile-hero__actions"
>
{profileActions}
</div>
<div className="profile-hero__actions">{profileActions}</div>
</div>
</section>
</>

View File

@ -1,7 +1,7 @@
import { ProfileContent } from "./profile-content/profile-content";
import { SkeletonTheme } from "react-loading-skeleton";
import "../../_theme.scss"
import "../../_theme.scss";
import "./profile.scss";
import { UserProfileContextProvider } from "@renderer/context";

View File

@ -75,9 +75,7 @@ export function ReportProfile() {
title={t("report_profile")}
clickOutsideToClose={false}
>
<form
className="report-profile__report-modal"
>
<form className="report-profile__report-modal">
<Controller
control={control}
name="reason"

View File

@ -5,7 +5,7 @@
display: flex;
flex-direction: column;
gap: #{$spacing-unit};
min-width: 500px,
min-width: 500px;
}
&__validation-result {

View File

@ -137,9 +137,7 @@ export function AddDownloadSourceModal({
description={t("add_download_source_description")}
onClose={onClose}
>
<div
className="download-source__add-source"
>
<div className="download-source__add-source">
<TextField
{...register("url")}
label={t("download_source_url")}
@ -159,12 +157,8 @@ export function AddDownloadSourceModal({
/>
{validationResult && (
<div
className="download-source__validation-result"
>
<div
className="download-source__input"
>
<div className="download-source__validation-result">
<div className="download-source__input">
<h4>{validationResult?.name}</h4>
<small>
{t("found_download_option", {

View File

@ -6,7 +6,7 @@ import { Button, CheckboxField, Link, TextField } from "@renderer/components";
import { useAppSelector, useToast } from "@renderer/hooks";
import { settingsContext } from "@renderer/context";
import "./settings-real-debrid.scss"
import "./settings-real-debrid.scss";
const REAL_DEBRID_API_TOKEN_URL = "https://real-debrid.com/apitoken";
@ -78,7 +78,9 @@ export function SettingsRealDebrid() {
return (
<form className="settings-real-debrid__form" onSubmit={handleFormSubmit}>
<p className="settings-real-debrid__description">{t("real_debrid_description")}</p>
<p className="settings-real-debrid__description">
{t("real_debrid_description")}
</p>
<CheckboxField
label={t("enable_real_debrid")}

View File

@ -21,10 +21,7 @@ export const HydraCloudModal = ({
return (
<Modal visible={visible} title={t("hydra_cloud")} onClose={onClose}>
<div
data-hydra-cloud-feature={feature}
className="hydra-cloud__on-close"
>
<div data-hydra-cloud-feature={feature} className="hydra-cloud__on-close">
{t("hydra_cloud_feature_found")}
<Button onClick={handleClickOpenCheckout}>{t("learn_more")}</Button>
</div>

View File

@ -105,7 +105,10 @@ export const UserFriendItem = (props: UserFriendItemProps) => {
if (type === "BLOCKED") {
return (
<div className="user-friend-modal__friend-list-container">
<div className="user-friend-modal__friend-list-button" style={{ cursor: "inherit" }}>
<div
className="user-friend-modal__friend-list-button"
style={{ cursor: "inherit" }}
>
<Avatar size={35} src={profileImageUrl} alt={displayName} />
<div

View File

@ -103,8 +103,11 @@ export const UserFriendModalList = ({
overflowY: "scroll",
}}
>
{!isLoading && friends.length === 0 &&
<p className="user-friend-modal__friend-list-display-name">{t("no_friends_added")}</p>}
{!isLoading && friends.length === 0 && (
<p className="user-friend-modal__friend-list-display-name">
{t("no_friends_added")}
</p>
)}
{friends.map((friend) => {
return (
<UserFriendItem

View File

@ -44,7 +44,7 @@
position: "absolute";
right: "8px";
display: "flex";
gap: #{$spacing-unit}
gap: #{$spacing-unit};
}
&__friend-request-item {