From a52979d9122cd23de4192cfd61fc532db0e5763a Mon Sep 17 00:00:00 2001 From: Hachi-R Date: Sun, 19 Jan 2025 19:33:37 -0300 Subject: [PATCH] refactor: migrate game details styles from VE to SCSS + BEM --- .../cloud-sync-files-modal.scss | 41 +++ .../cloud-sync-files-modal.tsx | 18 +- .../cloud-sync-modal/cloud-sync-modal.scss | 113 ++++++++ .../cloud-sync-modal/cloud-sync-modal.tsx | 84 +++--- .../description-header.scss | 17 ++ .../description-header/description-header.tsx | 8 +- .../gallery-slider/gallery-slider.scss | 131 +++++++++ .../gallery-slider/gallery-slider.tsx | 41 +-- .../game-details/game-details-content.tsx | 44 ++- .../game-details/game-details-skeleton.tsx | 49 ++-- .../src/pages/game-details/game-details.scss | 270 ++++++++++++++++++ .../src/pages/game-details/game-details.tsx | 15 +- .../game-details/hero/hero-panel-actions.scss | 18 ++ .../game-details/hero/hero-panel-actions.tsx | 21 +- .../pages/game-details/hero/hero-panel.scss | 66 +++++ .../pages/game-details/hero/hero-panel.tsx | 44 ++- .../modals/download-settings-modal.scss | 47 +++ .../modals/download-settings-modal.tsx | 33 +-- .../modals/game-options-modal.scss | 24 ++ .../modals/game-options-modal.tsx | 7 +- .../modals/remove-from-library-modal.scss | 11 + .../modals/remove-from-library-modal.tsx | 4 +- .../game-details/modals/repacks-modal.scss | 28 ++ .../game-details/modals/repacks-modal.tsx | 13 +- .../modals/reset-achievements-modal.scss | 11 + .../modals/reset-achievements-modal.tsx | 5 +- .../sidebar-section/sidebar-section.scss | 40 +++ .../sidebar-section/sidebar-section.tsx | 17 +- .../sidebar/how-long-to-beat-section.tsx | 16 +- .../pages/game-details/sidebar/sidebar.scss | 174 +++++++++++ .../pages/game-details/sidebar/sidebar.tsx | 51 ++-- 31 files changed, 1197 insertions(+), 264 deletions(-) create mode 100644 src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.scss create mode 100644 src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.scss create mode 100644 src/renderer/src/pages/game-details/description-header/description-header.scss create mode 100644 src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss create mode 100644 src/renderer/src/pages/game-details/game-details.scss create mode 100644 src/renderer/src/pages/game-details/hero/hero-panel-actions.scss create mode 100644 src/renderer/src/pages/game-details/hero/hero-panel.scss create mode 100644 src/renderer/src/pages/game-details/modals/download-settings-modal.scss create mode 100644 src/renderer/src/pages/game-details/modals/game-options-modal.scss create mode 100644 src/renderer/src/pages/game-details/modals/remove-from-library-modal.scss create mode 100644 src/renderer/src/pages/game-details/modals/repacks-modal.scss create mode 100644 src/renderer/src/pages/game-details/modals/reset-achievements-modal.scss create mode 100644 src/renderer/src/pages/game-details/sidebar-section/sidebar-section.scss create mode 100644 src/renderer/src/pages/game-details/sidebar/sidebar.scss diff --git a/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.scss b/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.scss new file mode 100644 index 00000000..9dac15e6 --- /dev/null +++ b/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.scss @@ -0,0 +1,41 @@ +@use "../../../scss/globals.scss"; + +.cloud-sync-files-modal { + &__mapping-methods { + display: grid; + gap: globals.$spacing-unit; + grid-template-columns: repeat(2, 1fr); + } + + &__file-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: globals.$spacing-unit; + margin-top: calc(globals.$spacing-unit * 2); + } + + &__file-item { + flex: 1; + color: globals.$muted-color; + text-decoration: underline; + display: flex; + cursor: pointer; + } + + &__container { + display: flex; + flex-direction: column; + gap: globals.$spacing-unit; + } + + &__mapping-label { + margin-bottom: globals.$spacing-unit; + } + + &__custom-path { + margin-top: calc(globals.$spacing-unit * 2); + } +} diff --git a/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx b/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx index 6fd277e7..031d179e 100644 --- a/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx +++ b/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx @@ -4,7 +4,7 @@ import { cloudSyncContext, gameDetailsContext } from "@renderer/context"; import { useTranslation } from "react-i18next"; import { CheckCircleFillIcon, FileDirectoryIcon } from "@primer/octicons-react"; -import * as styles from "./cloud-sync-files-modal.css"; +import "./cloud-sync-files-modal.scss"; import { formatBytes } from "@shared"; import { useToast } from "@renderer/hooks"; import { useForm } from "react-hook-form"; @@ -96,10 +96,12 @@ export function CloudSyncFilesModal({ description={t("manage_files_description")} onClose={onClose} > -
- {t("mapping_method_label")} +
+ + {t("mapping_method_label")} + -
+
{Object.values(FileMappingMethod).map((mappingMethod) => (
-
+
{selectedFileMappingMethod === FileMappingMethod.Automatic ? (

{t("files_automatically_mapped")}

) : ( @@ -142,11 +144,11 @@ export function CloudSyncFilesModal({ /> )} -
    +
      {files.map((file) => ( -
    • +
-
-
-

{t("backups")}

- - {artifacts.length} / {backupsPerGameLimit} - -
+ {uploadingBackup && ( + + )} + +
+

{t("backups")}

+ + {artifacts.length} / {backupsPerGameLimit} +
{artifacts.length > 0 ? ( -
    +
      {artifacts.map((artifact) => ( -
    • -
      -
      +
    • +
      +

      {t("backup_from", { date: format(artifact.createdAt, "dd/MM/yyyy"), @@ -230,29 +218,33 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { {formatBytes(artifact.artifactLengthInBytes)}

      - + {artifact.hostname} - + {artifact.downloadOptionTitle ?? t("no_download_option_info")} - + {format(artifact.createdAt, "dd/MM/yyyy HH:mm:ss")}
      -
      +
      -
      +
      {previews.map((media, i) => ( diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index bd138e81..ce00216d 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -7,7 +7,6 @@ import { DescriptionHeader } from "./description-header/description-header"; import { GallerySlider } from "./gallery-slider/gallery-slider"; import { Sidebar } from "./sidebar/sidebar"; -import * as styles from "./game-details.css"; import { useTranslation } from "react-i18next"; import { cloudSyncContext, gameDetailsContext } from "@renderer/context"; import { AuthPage, steamUrlBuilder } from "@shared"; @@ -15,7 +14,9 @@ import { AuthPage, steamUrlBuilder } from "@shared"; import cloudIconAnimated from "@renderer/assets/icons/cloud-animated.gif"; import { useUserDetails } from "@renderer/hooks"; import { useSubscription } from "@renderer/hooks/use-subscription"; +import "./game-details.scss"; +const HERO_HEIGHT = 300; const HERO_ANIMATION_THRESHOLD = 25; export function GameDetailsContent() { @@ -80,7 +81,7 @@ export function GameDetailsContent() { }, [objectId]); const onScroll: React.UIEventHandler = (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( @@ -118,10 +119,12 @@ export function GameDetailsContent() { }, [getGameArtifacts]); return ( -
      +
      {game?.title} @@ -129,47 +132,38 @@ export function GameDetailsContent() {
      -
      +
      -
      +
      {game?.title} -
      -
      +
      {Array.from({ length: 6 }).map((_, index) => ( ))} diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss new file mode 100644 index 00000000..899d654a --- /dev/null +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -0,0 +1,270 @@ +@use "../../scss/globals.scss"; + +$hero-height: 300px; + +@keyframes slide-in { + 0% { + transform: translateY(calc(40px + globals.$spacing-unit * 2)); + opacity: 0; + } + + 100% { + transform: translateY(0); + opacity: 1; + } +} + +.game-details { + &__wrapper { + display: flex; + flex-direction: column; + overflow: hidden; + width: 100%; + height: 100%; + transition: all ease 0.3s; + + &--blurred { + filter: blur(20px); + } + } + + &__hero { + width: 100%; + height: $hero-height; + min-height: $hero-height; + display: flex; + flex-direction: column; + position: relative; + transition: all ease 0.2s; + + @media (min-width: 1250px) { + height: 350px; + min-height: 350px; + } + } + + &__hero-content { + padding: calc(globals.$spacing-unit * 2); + height: 100%; + width: 100%; + display: flex; + justify-content: space-between; + align-items: flex-end; + } + + &__hero-logo-backdrop { + width: 100%; + height: 100%; + background: linear-gradient(0deg, rgba(0, 0, 0, 0.3) 60%, transparent 100%); + position: absolute; + display: flex; + flex-direction: column; + justify-content: space-between; + } + + &__hero-image { + width: 100%; + height: $hero-height; + min-height: $hero-height; + object-fit: cover; + object-position: top; + transition: all ease 0.2s; + position: absolute; + z-index: 0; + + @media (min-width: 1250px) { + object-position: center; + height: 350px; + min-height: 350px; + } + } + + &__game-logo { + width: 300px; + align-self: flex-end; + } + + &__hero-image-skeleton { + height: 300px; + + @media (min-width: 1250px) { + height: 350px; + } + } + + &__container { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + overflow: auto; + z-index: 1; + } + + &__description-container { + display: flex; + width: 100%; + flex: 1; + background: linear-gradient( + 0deg, + globals.$background-color 50%, + globals.$dark-background-color 100% + ); + } + + &__description-content { + width: 100%; + height: 100%; + } + + &__description { + user-select: text; + line-height: 22px; + font-size: globals.$body-font-size; + padding: calc(globals.$spacing-unit * 3) calc(globals.$spacing-unit * 2); + width: 100%; + margin-left: auto; + margin-right: auto; + + @media (min-width: 1280px) { + width: 60%; + } + + img { + border-radius: 5px; + margin-top: globals.$spacing-unit; + margin-bottom: calc(globals.$spacing-unit * 3); + display: block; + width: 100%; + height: auto; + object-fit: cover; + } + + a { + color: globals.$body-color; + } + + .bb_tag { + margin-top: calc(globals.$spacing-unit * 2); + margin-bottom: calc(globals.$spacing-unit * 2); + } + } + + &__description-skeleton { + display: flex; + flex-direction: column; + gap: globals.$spacing-unit; + padding: calc(globals.$spacing-unit * 3) calc(globals.$spacing-unit * 2); + width: 100%; + margin-left: auto; + margin-right: auto; + + @media (min-width: 1280px) { + width: 60%; + line-height: 22px; + } + } + + &__randomizer-button { + animation: slide-in 0.2s; + position: fixed; + bottom: calc(globals.$spacing-unit * 3); + right: calc(9px + globals.$spacing-unit * 2); + box-shadow: rgba(255, 255, 255, 0.1) 0px 0px 10px 1px; + border: solid 2px globals.$border-color; + z-index: 1; + background-color: globals.$background-color; + + &:hover { + background-color: globals.$background-color; + box-shadow: rgba(255, 255, 255, 0.1) 0px 0px 15px 5px; + opacity: 1; + } + + &:active { + transform: scale(0.98); + } + + &:disabled { + box-shadow: none; + transform: none; + opacity: 0.8; + background-color: globals.$background-color; + } + } + + &__hero-panel-skeleton { + width: 100%; + padding: calc(globals.$spacing-unit * 2); + display: flex; + align-items: center; + background-color: globals.$background-color; + height: 72px; + border-bottom: solid 1px globals.$border-color; + } + + &__cloud-sync-button { + padding: calc(globals.$spacing-unit * 1.5) calc(globals.$spacing-unit * 2); + background-color: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(20px); + border-radius: 8px; + transition: all ease 0.2s; + cursor: pointer; + min-height: 40px; + display: flex; + align-items: center; + justify-content: center; + gap: globals.$spacing-unit; + color: globals.$muted-color; + font-size: globals.$small-font-size; + border: solid 1px globals.$border-color; + box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.8); + animation: slide-in 0.3s cubic-bezier(0.33, 1, 0.68, 1); + + &:active { + opacity: 0.9; + } + + &:disabled { + opacity: globals.$disabled-opacity; + cursor: not-allowed; + } + + &:hover { + background-color: rgba(0, 0, 0, 0.5); + } + } + + &__stars-icon-container { + width: 16px; + height: 16px; + position: relative; + } + + &__stars-icon { + width: 70px; + position: absolute; + top: -28px; + left: -27px; + } + + &__cloud-icon-container { + width: 20px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + position: relative; + } + + &__cloud-icon { + width: 26px; + position: absolute; + top: -3px; + } + + &__hero-backdrop { + flex: 1; + transition: opacity 0.2s ease; + } +} diff --git a/src/renderer/src/pages/game-details/game-details.tsx b/src/renderer/src/pages/game-details/game-details.tsx index 4fbcc855..d43ecff9 100644 --- a/src/renderer/src/pages/game-details/game-details.tsx +++ b/src/renderer/src/pages/game-details/game-details.tsx @@ -11,7 +11,6 @@ import starsIconAnimated from "@renderer/assets/icons/stars-animated.gif"; import { useTranslation } from "react-i18next"; import { SkeletonTheme } from "react-loading-skeleton"; import { GameDetailsSkeleton } from "./game-details-skeleton"; -import * as styles from "./game-details.css"; import { vars } from "@renderer/theme.css"; @@ -27,6 +26,7 @@ import { GameOptionsModal, RepacksModal } from "./modals"; import { Downloader, getDownloadersForUri } from "@shared"; import { CloudSyncModal } from "./cloud-sync-modal/cloud-sync-modal"; import { CloudSyncFilesModal } from "./cloud-sync-files-modal/cloud-sync-files-modal"; +import "./game-details.scss"; export default function GameDetails() { const [randomGame, setRandomGame] = useState(null); @@ -185,23 +185,16 @@ export default function GameDetails() { {fromRandomizer && ( @@ -109,7 +108,7 @@ export function HeroPanelActions() { onClick={closeGame} theme="outline" disabled={deleting} - className={styles.heroPanelAction} + className="hero-panel-actions__action" > {t("close")} @@ -122,7 +121,7 @@ export function HeroPanelActions() { onClick={openGame} theme="outline" disabled={deleting || isGameRunning} - className={styles.heroPanelAction} + className="hero-panel-actions__action" > {t("play")} @@ -135,7 +134,7 @@ export function HeroPanelActions() { onClick={() => setShowRepacksModal(true)} theme="outline" disabled={isGameDownloading || !repacks.length} - className={styles.heroPanelAction} + className="hero-panel-actions__action" > {t("download")} @@ -154,16 +153,14 @@ export function HeroPanelActions() { if (game) { return ( -
      +
      {gameActionButton()} - -
      - +
      @@ -178,13 +169,7 @@ export function DownloadSettingsModal({
      -
      +
      {t("no_write_permission")} @@ -212,7 +197,7 @@ export function DownloadSettingsModal({ } /> -

      +

      diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.scss b/src/renderer/src/pages/game-details/modals/game-options-modal.scss new file mode 100644 index 00000000..0a3c0246 --- /dev/null +++ b/src/renderer/src/pages/game-details/modals/game-options-modal.scss @@ -0,0 +1,24 @@ +@use "../../../scss/globals.scss"; + +.game-options-modal { + &__container { + display: flex; + gap: calc(globals.$spacing-unit * 2); + flex-direction: column; + } + + &__header { + display: flex; + flex-direction: column; + gap: globals.$spacing-unit; + } + + &__header-description { + font-weight: 400; + } + + &__row { + display: flex; + gap: globals.$spacing-unit; + } +} diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx index b06de28a..2a826c04 100644 --- a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx @@ -10,6 +10,7 @@ import { RemoveGameFromLibraryModal } from "./remove-from-library-modal"; import { ResetAchievementsModal } from "./reset-achievements-modal"; import { FileDirectoryIcon, FileIcon } from "@primer/octicons-react"; import { debounce } from "lodash-es"; +import "./game-options-modal.scss"; export interface GameOptionsModalProps { visible: boolean; @@ -199,10 +200,10 @@ export function GameOptionsModal({ onClose={onClose} large={true} > -

      -
      +
      +

      {t("executable_section_title")}

      -

      +

      {t("executable_section_description")}

      diff --git a/src/renderer/src/pages/game-details/modals/remove-from-library-modal.scss b/src/renderer/src/pages/game-details/modals/remove-from-library-modal.scss new file mode 100644 index 00000000..9c390d67 --- /dev/null +++ b/src/renderer/src/pages/game-details/modals/remove-from-library-modal.scss @@ -0,0 +1,11 @@ +@use "../../../scss/globals.scss"; + +.remove-from-library-modal { + &__actions { + display: flex; + width: 100%; + justify-content: flex-end; + align-items: center; + gap: globals.$spacing-unit; + } +} diff --git a/src/renderer/src/pages/game-details/modals/remove-from-library-modal.tsx b/src/renderer/src/pages/game-details/modals/remove-from-library-modal.tsx index 39789872..85cea8cd 100644 --- a/src/renderer/src/pages/game-details/modals/remove-from-library-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/remove-from-library-modal.tsx @@ -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; @@ -30,7 +30,7 @@ export function RemoveGameFromLibraryModal({ description={t("remove_from_library_description", { game: game.title })} onClose={onClose} > -
      +
      diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.scss b/src/renderer/src/pages/game-details/modals/repacks-modal.scss new file mode 100644 index 00000000..a575b291 --- /dev/null +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.scss @@ -0,0 +1,28 @@ +@use "../../../scss/globals.scss"; + +.repacks-modal { + &__repacks { + display: flex; + gap: globals.$spacing-unit; + flex-direction: column; + } + + &__repack-button { + display: flex; + text-align: left; + flex-direction: column; + align-items: flex-start; + gap: globals.$spacing-unit; + color: globals.$body-color; + padding: calc(globals.$spacing-unit * 2); + } + + &__repack-title { + color: globals.$muted-color; + word-break: break-word; + } + + &__repack-info { + font-size: globals.$small-font-size; + } +} diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx index 635c7f99..0dac3bd0 100644 --- a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx @@ -4,14 +4,13 @@ import { useTranslation } from "react-i18next"; import { Badge, Button, Modal, TextField } from "@renderer/components"; import type { GameRepack } from "@types"; -import * as styles from "./repacks-modal.css"; - import { SPACING_UNIT } from "@renderer/theme.css"; import { DownloadSettingsModal } from "./download-settings-modal"; import { gameDetailsContext } from "@renderer/context"; import { Downloader } from "@shared"; import { orderBy } from "lodash-es"; import { useDate } from "@renderer/hooks"; +import "./repacks-modal.scss"; export interface RepacksModalProps { visible: boolean; @@ -90,7 +89,7 @@ export function RepacksModal({
      -
      +
      {filteredRepacks.map((repack) => { const isLastDownloadedOption = checkIfLastDownloadedOption(repack); @@ -99,17 +98,15 @@ export function RepacksModal({ key={repack.id} theme="dark" onClick={() => handleRepackClick(repack)} - className={styles.repackButton} + className="repacks-modal__repack-button" > -

      - {repack.title} -

      +

      {repack.title}

      {isLastDownloadedOption && ( {t("last_downloaded_option")} )} -

      +

      {repack.fileSize} - {repack.repacker} -{" "} {repack.uploadDate ? formatDate(repack.uploadDate!) : ""}

      diff --git a/src/renderer/src/pages/game-details/modals/reset-achievements-modal.scss b/src/renderer/src/pages/game-details/modals/reset-achievements-modal.scss new file mode 100644 index 00000000..b41be41d --- /dev/null +++ b/src/renderer/src/pages/game-details/modals/reset-achievements-modal.scss @@ -0,0 +1,11 @@ +@use "../../../scss/globals.scss"; + +.reset-achievements-modal { + &__actions { + display: flex; + width: 100%; + justify-content: flex-end; + align-items: center; + gap: globals.$spacing-unit; + } +} diff --git a/src/renderer/src/pages/game-details/modals/reset-achievements-modal.tsx b/src/renderer/src/pages/game-details/modals/reset-achievements-modal.tsx index 642d32ba..fc71e2d0 100644 --- a/src/renderer/src/pages/game-details/modals/reset-achievements-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/reset-achievements-modal.tsx @@ -1,7 +1,8 @@ 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 "./reset-achievements-modal.scss"; + type ResetAchievementsModalProps = Readonly<{ visible: boolean; game: Game; @@ -34,7 +35,7 @@ export function ResetAchievementsModal({ game: game.title, })} > -
      +
      diff --git a/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.scss b/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.scss new file mode 100644 index 00000000..5ea421c3 --- /dev/null +++ b/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.scss @@ -0,0 +1,40 @@ +@use "../../../scss/globals.scss"; + +.sidebar-section { + &__button { + height: 72px; + padding: calc(globals.$spacing-unit * 2) calc(globals.$spacing-unit * 2); + display: flex; + align-items: center; + background-color: globals.$background-color; + color: globals.$muted-color; + width: 100%; + cursor: pointer; + transition: all ease 0.2s; + gap: globals.$spacing-unit; + font-size: globals.$body-font-size; + font-weight: bold; + + &:hover { + background-color: rgba(255, 255, 255, 0.05); + } + + &:active { + opacity: globals.$active-opacity; + } + } + + &__chevron { + transition: transform ease 0.2s; + + &--open { + transform: rotate(180deg); + } + } + + &__content { + overflow: hidden; + transition: max-height 0.4s cubic-bezier(0, 1, 0, 1); + position: relative; + } +} diff --git a/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.tsx b/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.tsx index e24f677b..42697fe3 100644 --- a/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.tsx +++ b/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.tsx @@ -1,7 +1,6 @@ import { ChevronDownIcon } from "@primer/octicons-react"; import { useEffect, useRef, useState } from "react"; - -import * as styles from "./sidebar-section.css"; +import "./sidebar-section.scss"; export interface SidebarSectionProps { title: string; @@ -22,23 +21,25 @@ export function SidebarSection({ title, children }: SidebarSectionProps) { }, [isOpen, children, height]); return ( -
      +
      {children} diff --git a/src/renderer/src/pages/game-details/sidebar/how-long-to-beat-section.tsx b/src/renderer/src/pages/game-details/sidebar/how-long-to-beat-section.tsx index d63879f5..138826ff 100644 --- a/src/renderer/src/pages/game-details/sidebar/how-long-to-beat-section.tsx +++ b/src/renderer/src/pages/game-details/sidebar/how-long-to-beat-section.tsx @@ -2,9 +2,8 @@ import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; import { useTranslation } from "react-i18next"; import type { HowLongToBeatCategory } from "@types"; import { vars } from "@renderer/theme.css"; - -import * as styles from "./sidebar.css"; import { SidebarSection } from "../sidebar-section/sidebar-section"; +import "./sidebar.scss"; const durationTranslation: Record = { Hours: "hours", @@ -32,15 +31,12 @@ export function HowLongToBeatSection({ return ( -
        +
          {howLongToBeatData ? howLongToBeatData.map((category) => ( -
        • +
        • -

          +

          {getDuration(category.duration)}

          @@ -62,7 +58,7 @@ export function HowLongToBeatSection({ : Array.from({ length: 4 }).map((_, index) => ( ))}
        diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.scss b/src/renderer/src/pages/game-details/sidebar/sidebar.scss new file mode 100644 index 00000000..15bc74c3 --- /dev/null +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.scss @@ -0,0 +1,174 @@ +@use "../../../scss/globals.scss"; + +.content-sidebar { + border-left: solid 1px globals.$border-color; + background-color: globals.$dark-background-color; + width: 100%; + height: 100%; + + @media (min-width: 1024px) { + max-width: 300px; + width: 100%; + } + + @media (min-width: 1280px) { + width: 100%; + max-width: 400px; + } +} + +.requirement { + &__button-container { + width: 100%; + display: flex; + } + + &__button { + border: solid 1px globals.$border-color; + border-left: none; + border-right: none; + border-radius: 0; + width: 100%; + } + + &__details { + padding: calc(globals.$spacing-unit * 2); + line-height: 22px; + font-size: globals.$body-font-size; + + a { + display: flex; + color: globals.$body-color; + } + } + + &__details-skeleton { + display: flex; + flex-direction: column; + gap: globals.$spacing-unit; + padding: calc(globals.$spacing-unit * 2); + font-size: globals.$body-font-size; + } +} + +.how-long-to-beat { + &__categories-list { + margin: 0; + padding: calc(globals.$spacing-unit * 2); + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 2); + } + + &__category { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit / 2); + background: linear-gradient( + 90deg, + transparent 20%, + rgb(255 255 255 / 2%) 100% + ); + border-radius: 4px; + padding: globals.$spacing-unit calc(globals.$spacing-unit * 2); + border: solid 1px globals.$border-color; + } + + &__category-label { + color: globals.$muted-color; + } + + &__category-skeleton { + border: solid 1px globals.$border-color; + border-radius: 4px; + height: 76px; + } +} + +.stats { + &__section { + display: flex; + gap: calc(globals.$spacing-unit * 2); + padding: calc(globals.$spacing-unit * 2); + justify-content: space-between; + transition: max-height ease 0.5s; + overflow: hidden; + + @media (min-width: 1024px) { + flex-direction: column; + } + + @media (min-width: 1280px) { + flex-direction: row; + } + } + + &__category-title { + font-size: globals.$small-font-size; + font-weight: bold; + display: flex; + align-items: center; + gap: globals.$spacing-unit; + } + + &__category { + display: flex; + flex-direction: row; + gap: calc(globals.$spacing-unit / 2); + justify-content: space-between; + align-items: center; + } +} + +.list { + list-style: none; + margin: 0; + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 2); + padding: calc(globals.$spacing-unit * 2); + + &__item { + display: flex; + cursor: pointer; + transition: all ease 0.1s; + color: globals.$muted-color; + width: 100%; + overflow: hidden; + border-radius: 4px; + padding: globals.$spacing-unit; + gap: calc(globals.$spacing-unit * 2); + align-items: center; + text-align: left; + + &:hover { + background-color: rgba(255, 255, 255, 0.15); + text-decoration: none; + } + } + + &__item-image { + width: 54px; + height: 54px; + border-radius: 4px; + object-fit: cover; + + &--locked { + filter: grayscale(100%); + } + } +} + +.subscription-required-button { + text-decoration: none; + display: flex; + justify-content: center; + width: 100%; + gap: calc(globals.$spacing-unit / 2); + color: globals.$warning-color; + cursor: pointer; + + &:hover { + text-decoration: underline; + } +} diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index 7787b22a..27c3ee47 100644 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -7,7 +7,6 @@ import type { import { useTranslation } from "react-i18next"; import { Button, Link } from "@renderer/components"; -import * as styles from "./sidebar.css"; import { gameDetailsContext } from "@renderer/context"; import { useDate, useFormat, useUserDetails } from "@renderer/hooks"; import { @@ -20,8 +19,8 @@ import { HowLongToBeatSection } from "./how-long-to-beat-section"; import { howLongToBeatEntriesTable } from "@renderer/dexie"; import { SidebarSection } from "../sidebar-section/sidebar-section"; import { buildGameAchievementPath } from "@renderer/helpers"; -import { SPACING_UNIT } from "@renderer/theme.css"; import { useSubscription } from "@renderer/hooks/use-subscription"; +import "./sidebar.scss"; const fakeAchievements: UserAchievement[] = [ { @@ -64,7 +63,6 @@ export function Sidebar() { }>({ isLoading: true, data: null }); const { userDetails, hasActiveSubscription } = useUserDetails(); - const [activeRequirement, setActiveRequirement] = useState("minimum"); @@ -72,10 +70,8 @@ export function Sidebar() { useContext(gameDetailsContext); const { showHydraCloudModal } = useSubscription(); - const { t } = useTranslation("game_details"); const { formatDateTime } = useDate(); - const { numberFormatter } = useFormat(); useEffect(() => { @@ -118,7 +114,7 @@ export function Sidebar() { }, [objectId, shop, gameTitle]); return ( -