From 0919295843b4aa0c4d2522dde9fdcbec93a44beb Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sun, 6 Sep 2026 10:30:39 -0400 Subject: [PATCH] refactor(details): migrate game details, thumbnails and menus to v3 - GameDetails/context menus: chakra context-menu dep replaced with MenuRoot shims, hover lift kept, FixedSelect play-status control, Link/Text/Separator prop renames - Features/Summary/SystemRequirements: Jodit + modal flows moved onto Dialog shims, v3 Button/Tag/Link tokens, carousel slide kept as-is - Preserves legacy dark card/menu surfaces via gray.950 -> v2 gray.700 mapping in the theme --- src/components/GameDetails.tsx | 39 ++--- .../dashboardComponents/GameThumbnail.tsx | 27 ++-- .../GameThumbnailContextMenu.tsx | 49 +++--- .../GameThumbnailSlide.tsx | 20 ++- src/components/detailsComponents/Features.tsx | 152 +++++------------- .../detailsComponents/GamePlayStatus.tsx | 9 +- .../detailsComponents/GameRating.tsx | 3 +- src/components/detailsComponents/Summary.tsx | 6 +- .../detailsComponents/SummaryModal.tsx | 24 +-- .../SystemRequirementsMenu.tsx | 92 +++++------ .../SystemRequirementsModal.tsx | 24 +-- 11 files changed, 195 insertions(+), 250 deletions(-) diff --git a/src/components/GameDetails.tsx b/src/components/GameDetails.tsx index 8d1f37b..a4bc5ac 100644 --- a/src/components/GameDetails.tsx +++ b/src/components/GameDetails.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react' import agent from '../api/agent' -import { useParams } from 'react-router' +import { useParams } from 'react-router-dom' import { Box, Button, @@ -12,10 +12,9 @@ import { Spacer, Stack, Text, - useColorModeValue, - useDisclosure, - useMediaQuery, } from '@chakra-ui/react' +import {useColorModeValue} from './ui/color-mode' +import {useDisclosure, useMediaQuery} from '@chakra-ui/react' import ImageSlider from './detailsComponents/ImageSlider' import Summary from './detailsComponents/Summary' import Features from './detailsComponents/Features' @@ -26,7 +25,7 @@ import { loadingState } from '../stateManagement/loadingState' import { ImmutableObject, useHookstate } from '@hookstate/core' import LoadingModal from './LoadingModal' import SteamAndDeckLogo from './helperComponents/SteamAndDeckLogo' -import { ExternalLinkIcon } from '@chakra-ui/icons' +import { LuExternalLink } from 'react-icons/lu' import { Data } from '../models/game' import SummaryModal from './detailsComponents/SummaryModal' import SystemRequirementsModal from './detailsComponents/SystemRequirementsModal' @@ -38,14 +37,14 @@ const GameDetails = () => { const loaded = loadedState.get() const { id } = useParams<{ id: string }>() const bg = useColorModeValue('white', 'gray.700') - const { isOpen: isSummaryOpen, onOpen: onSummaryOpen, onClose: onSummaryClose } = useDisclosure() - const { isOpen: isSystemOpen, onOpen: onSystemOpen, onClose: onSystemClose } = useDisclosure() - const [isSmallerThan768] = useMediaQuery('(max-width: 768px)') + const { open: isSummaryOpen, onOpen: onSummaryOpen, onClose: onSummaryClose } = useDisclosure() + const { open: isSystemOpen, onOpen: onSystemOpen, onClose: onSystemClose } = useDisclosure() + const [isSmallerThan768] = useMediaQuery(['(max-width: 768px)']) useEffect(() => { const getGame = async () => { loadedState.set(false) - return await agent.Games.details(id) + return await agent.Games.details(id!) } getGame().then((result) => { gameState.set(result) @@ -69,7 +68,7 @@ const GameDetails = () => { : ''} - + { {game.title} - {game.accessedBy[0].store.includes('Steam') && game.steamId.length > 0 && ( - )} - @@ -122,7 +123,7 @@ const GameDetails = () => { {isSmallerThan768 ? - @@ -138,7 +139,7 @@ const GameDetails = () => { {isSmallerThan768 ? - diff --git a/src/components/dashboardComponents/GameThumbnail.tsx b/src/components/dashboardComponents/GameThumbnail.tsx index 58fc6bd..26a9994 100644 --- a/src/components/dashboardComponents/GameThumbnail.tsx +++ b/src/components/dashboardComponents/GameThumbnail.tsx @@ -1,7 +1,7 @@ import React from 'react' import { Link } from 'react-router-dom' import { Data } from '../../models/game' -import { Link as ChakraLink, Flex, Heading, Image, LinkBox, LinkOverlay, Spacer, Text } from '@chakra-ui/react' +import { Link as ChakraLink, Flex, Heading, Image, Spacer, Text, Box } from '@chakra-ui/react' import GameRating from "../detailsComponents/GameRating"; import SteamAndDeckLogo from "../helperComponents/SteamAndDeckLogo"; @@ -16,20 +16,21 @@ export default function GameThumbnail({ onToggle, game, playState }: Props) { const gameLink = game.accessedBy[0].store.includes('Steam') && game.steamId.length > 0 ? game.steamId : game._id return ( <> - - + {game.title} - - - {game.steamId !== '' ? `${game.steamId}: ${game.title}` : `${game.title}`} - - Play Status: {playState} - + + {game.steamId !== '' ? `${game.steamId}: ${game.title}` : `${game.title}`} + + Play Status: {playState} + + diff --git a/src/components/dashboardComponents/GameThumbnailContextMenu.tsx b/src/components/dashboardComponents/GameThumbnailContextMenu.tsx index 97c5701..618e060 100644 --- a/src/components/dashboardComponents/GameThumbnailContextMenu.tsx +++ b/src/components/dashboardComponents/GameThumbnailContextMenu.tsx @@ -1,9 +1,8 @@ import React, { useState } from 'react'; import { Data } from "../../models/game"; -import { ContextMenu } from 'chakra-ui-contextmenu'; -import { MenuItem, MenuList } from '@chakra-ui/menu'; +import { Menu, Portal } from '@chakra-ui/react'; import GameThumbnail from "./GameThumbnail"; -import { Box, MenuGroup, useDisclosure } from "@chakra-ui/react"; +import { Box, useDisclosure } from "@chakra-ui/react"; import GameThumbnailSlide from "./GameThumbnailSlide"; import { playStatusValues } from "../../componentUtils/SelectValues"; import agent from "../../api/agent"; @@ -14,7 +13,7 @@ interface Props { } const GameThumbnailContextMenu = ({ game }: Props) => { - const { isOpen, onToggle } = useDisclosure() + const { open: isOpen, onToggle } = useDisclosure() const [playState, setPlayState] = useState(game.accessedBy[0].playStatus) const gameLink = game.steamId.length > 1 ? game.steamId : game._id @@ -34,20 +33,10 @@ const GameThumbnailContextMenu = ({ game }: Props) => { let mql = window.matchMedia('(max-width: 768px)'); return ( - key={game._id} renderMenu={() => ( - - Open in New Tab - - {playStatusValues.map(value => rightClick(value.label, value.value)} - >{value.label})} - - - )}> - {ref => <> + + { > - {(!mql.matches) && } - - } - + + + + + + Open in New Tab + + + Change PlayStatus + {playStatusValues.map((value, idx) => ( + rightClick(value.label, value.value)} + >{value.label} + ))} + + + + + {(!mql.matches) && } + ); }; diff --git a/src/components/dashboardComponents/GameThumbnailSlide.tsx b/src/components/dashboardComponents/GameThumbnailSlide.tsx index f3160ba..1c97387 100644 --- a/src/components/dashboardComponents/GameThumbnailSlide.tsx +++ b/src/components/dashboardComponents/GameThumbnailSlide.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {Box, Container, Heading, HStack, Slide} from "@chakra-ui/react"; +import {Box, Container, Heading, HStack, Presence} from "@chakra-ui/react"; import {Data} from "../../models/game"; import ImageSlider from "../detailsComponents/ImageSlider"; @@ -10,17 +10,25 @@ interface Props { const GameThumbnailSlide = ({isOpen, game}: Props) => { return ( - + - - + + {game.title} @@ -29,7 +37,7 @@ const GameThumbnailSlide = ({isOpen, game}: Props) => { - + ); }; diff --git a/src/components/detailsComponents/Features.tsx b/src/components/detailsComponents/Features.tsx index 04d218a..33a4c82 100644 --- a/src/components/detailsComponents/Features.tsx +++ b/src/components/detailsComponents/Features.tsx @@ -1,5 +1,5 @@ import React, { useState as reactUseState } from 'react' -import { Badge, Box, Divider, Flex, Heading, Link, Text } from '@chakra-ui/react' +import { Badge, Box, Separator, Flex, Heading, Link, Text } from '@chakra-ui/react' import { Data } from '../../models/game' import { gameDashboardState } from '../../stateManagement/gameState' import { ImmutableObject, useHookstate } from '@hookstate/core' @@ -36,48 +36,40 @@ export default function Features({ game }: Props) { return history('/games') } + const badge = (label: string) => ( + + + + {label} + + + + ) + return ( Features and Information - - - - Rating: - - - + {badge('Rating:')} - + {game.accessedBy[0].store.includes('Steam') && game.steamId.length > 0 && (<> - - - - Steam ID: - - - + {badge('Steam ID:')} {game.steamId} - + )} {game.series.length > 1 && (<> - - - - Series: - - - + {badge('Series:')} { gamesState.filters.series.set(game.series) @@ -85,16 +77,10 @@ export default function Features({ game }: Props) { }} pt="1">{game.series} - + )} - - - - Play Status: - - - + {badge('Play Status:')} {editMode ? @@ -120,15 +106,9 @@ export default function Features({ game }: Props) { - + - - - - Genre: - - - + {badge('Genre:')} {game.genre.map((genre, index) => [ index > 0 && ", ", @@ -141,15 +121,9 @@ export default function Features({ game }: Props) { ])} - + - - - - Operating Systems: - - - + {badge('Operating Systems:')} {Object.keys(game.os).filter(os => { @@ -175,15 +149,9 @@ export default function Features({ game }: Props) { - + - - - - Store: - - - + {badge('Store:')} {game.accessedBy[0].store.map((store, index) => [ index > 0 && ", ", @@ -196,15 +164,9 @@ export default function Features({ game }: Props) { ])} - + - - - - Developer: - - - + {badge('Developer:')} {game.developer.map((developer, index) => [ index > 0 && ", ", @@ -217,15 +179,9 @@ export default function Features({ game }: Props) { ])} - + - - - - Publisher: - - - + {badge('Publisher:')} {game.publisher.map((publisher, index) => [ index > 0 && ", ", @@ -238,15 +194,9 @@ export default function Features({ game }: Props) { ])} - + - - - - Controller Support: - - - + {badge('Controller Support:')} { gamesState.filters.controller.set(game.controller) @@ -254,28 +204,16 @@ export default function Features({ game }: Props) { }} pt="1">{game.controller} - + - - - - Release Date: - - - + {badge('Release Date:')} {game.releaseDate} - + - - - - Soundtrack: - - - + {badge('Soundtrack:')} { gamesState.filters.soundtrack.set(game.accessedBy[0].soundtrack) @@ -283,15 +221,9 @@ export default function Features({ game }: Props) { }} pt="1">{game.accessedBy[0].soundtrack} - + - - - - Does it work with Intel?: - - - + {badge('Does it work with Intel?:')} { gamesState.filters.intel.set(game.intel) @@ -299,15 +231,9 @@ export default function Features({ game }: Props) { }} pt="1">{game.intel} - + - - - - Does it work with WINE?: - - - + {badge('Does it work with WINE?:')} { gamesState.filters.wine.set(game.wine) @@ -315,7 +241,7 @@ export default function Features({ game }: Props) { }} pt="1">{game.wine} - + ) } diff --git a/src/components/detailsComponents/GamePlayStatus.tsx b/src/components/detailsComponents/GamePlayStatus.tsx index 5b51395..6eabd95 100644 --- a/src/components/detailsComponents/GamePlayStatus.tsx +++ b/src/components/detailsComponents/GamePlayStatus.tsx @@ -2,11 +2,12 @@ import React from 'react'; import { Data } from '../../models/game' import agent from "../../api/agent"; import {playStatusValues} from "../../componentUtils/SelectValues"; -import {Select} from "chakra-react-select"; +import {Select} from "../FixedSelect"; +import {ImmutableObject} from "@hookstate/core"; interface Props { - game: Data + game: Data | ImmutableObject playState: string setPlayState: any } @@ -33,14 +34,14 @@ const GamePlayStatus = ({game, playState, setPlayState}: Props) => { value: playState, label: playState, }} - onChange={(e) => { + onChange={(e: any) => { if(e === null) return handleClick(e.value, e.label) }} size={'sm'} options={playStatusValues} chakraStyles={{ - container: (provided) => ({ + container: (provided: any) => ({ ...provided, width: "150px" }) diff --git a/src/components/detailsComponents/GameRating.tsx b/src/components/detailsComponents/GameRating.tsx index 6b94010..ad959a6 100644 --- a/src/components/detailsComponents/GameRating.tsx +++ b/src/components/detailsComponents/GameRating.tsx @@ -5,10 +5,11 @@ import agent from "../../api/agent"; import {Flex, Link} from "@chakra-ui/react"; import {IoGameController, IoGameControllerOutline} from "react-icons/io5"; import {BiReset} from "react-icons/bi"; +import {ImmutableObject} from "@hookstate/core"; interface Props { - game: Data + game: Data | ImmutableObject size: string } diff --git a/src/components/detailsComponents/Summary.tsx b/src/components/detailsComponents/Summary.tsx index 420c529..985d2be 100644 --- a/src/components/detailsComponents/Summary.tsx +++ b/src/components/detailsComponents/Summary.tsx @@ -1,7 +1,7 @@ import React from 'react' import { Data } from '../../models/game' import { Interweave } from 'interweave' -import { Divider, Box, Heading } from '@chakra-ui/react' +import { Separator, Box, Heading } from '@chakra-ui/react' import { ImmutableObject } from '@hookstate/core' interface Props { @@ -15,7 +15,7 @@ export default function Summary({ game }: Props) { {reviews.length >= 1 && ( <> Reviews - + )} = 1 ? '10' : ''} as="h1">About This Game - + { return ( - - - - - + { if (!e.open) onClose() }}> + + + - - - - - - + + + ) } diff --git a/src/components/detailsComponents/SystemRequirementsMenu.tsx b/src/components/detailsComponents/SystemRequirementsMenu.tsx index e750089..d05f30a 100644 --- a/src/components/detailsComponents/SystemRequirementsMenu.tsx +++ b/src/components/detailsComponents/SystemRequirementsMenu.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { TabList, Tab, TabPanel, TabPanels, Tabs, Heading, Box } from '@chakra-ui/react' +import { Tabs, Heading, Box } from '@chakra-ui/react' import { Data } from '../../models/game' import { Interweave } from 'interweave' import { ImmutableObject } from '@hookstate/core' @@ -20,51 +20,51 @@ export default function SystemRequirements({ game }: Props) { return ( System Requirements - - - Windows - Mac - Linux - - - - - - Minimum - Recommended - - - {} - {} - - - - - - - Minimum - Recommended - - - {} - {} - - - - - - - Minimum - Recommended - - - {} - {} - - - - - + + + Windows + Mac + Linux + + + + + + Minimum + Recommended + + + {} + {} + + + + + + + Minimum + Recommended + + + {} + {} + + + + + + + Minimum + Recommended + + + {} + {} + + + + + ) } diff --git a/src/components/detailsComponents/SystemRequirementsModal.tsx b/src/components/detailsComponents/SystemRequirementsModal.tsx index 38de6f5..45944fd 100644 --- a/src/components/detailsComponents/SystemRequirementsModal.tsx +++ b/src/components/detailsComponents/SystemRequirementsModal.tsx @@ -1,5 +1,6 @@ import React from 'react' -import { Button, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react' +import { Button } from '@chakra-ui/react' +import {DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogRoot} from '../ui/dialog' import { ImmutableObject } from '@hookstate/core' import { Data } from '../../models/game' import SystemRequirements from './SystemRequirementsMenu' @@ -14,20 +15,19 @@ interface Props { const SummaryModal = ({ isOpen, onClose, game }: Props) => { return ( - - - - - + { if (!e.open) onClose() }}> + + + - - - - - - + + + ) }