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
This commit is contained in:
2026-09-06 10:30:39 -04:00
parent 45dd783964
commit 0919295843
11 changed files with 195 additions and 250 deletions
+20 -19
View File
@@ -1,6 +1,6 @@
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import agent from '../api/agent' import agent from '../api/agent'
import { useParams } from 'react-router' import { useParams } from 'react-router-dom'
import { import {
Box, Box,
Button, Button,
@@ -12,10 +12,9 @@ import {
Spacer, Spacer,
Stack, Stack,
Text, Text,
useColorModeValue,
useDisclosure,
useMediaQuery,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import {useColorModeValue} from './ui/color-mode'
import {useDisclosure, useMediaQuery} from '@chakra-ui/react'
import ImageSlider from './detailsComponents/ImageSlider' import ImageSlider from './detailsComponents/ImageSlider'
import Summary from './detailsComponents/Summary' import Summary from './detailsComponents/Summary'
import Features from './detailsComponents/Features' import Features from './detailsComponents/Features'
@@ -26,7 +25,7 @@ import { loadingState } from '../stateManagement/loadingState'
import { ImmutableObject, useHookstate } from '@hookstate/core' import { ImmutableObject, useHookstate } from '@hookstate/core'
import LoadingModal from './LoadingModal' import LoadingModal from './LoadingModal'
import SteamAndDeckLogo from './helperComponents/SteamAndDeckLogo' import SteamAndDeckLogo from './helperComponents/SteamAndDeckLogo'
import { ExternalLinkIcon } from '@chakra-ui/icons' import { LuExternalLink } from 'react-icons/lu'
import { Data } from '../models/game' import { Data } from '../models/game'
import SummaryModal from './detailsComponents/SummaryModal' import SummaryModal from './detailsComponents/SummaryModal'
import SystemRequirementsModal from './detailsComponents/SystemRequirementsModal' import SystemRequirementsModal from './detailsComponents/SystemRequirementsModal'
@@ -38,14 +37,14 @@ const GameDetails = () => {
const loaded = loadedState.get() const loaded = loadedState.get()
const { id } = useParams<{ id: string }>() const { id } = useParams<{ id: string }>()
const bg = useColorModeValue('white', 'gray.700') const bg = useColorModeValue('white', 'gray.700')
const { isOpen: isSummaryOpen, onOpen: onSummaryOpen, onClose: onSummaryClose } = useDisclosure() const { open: isSummaryOpen, onOpen: onSummaryOpen, onClose: onSummaryClose } = useDisclosure()
const { isOpen: isSystemOpen, onOpen: onSystemOpen, onClose: onSystemClose } = useDisclosure() const { open: isSystemOpen, onOpen: onSystemOpen, onClose: onSystemClose } = useDisclosure()
const [isSmallerThan768] = useMediaQuery('(max-width: 768px)') const [isSmallerThan768] = useMediaQuery(['(max-width: 768px)'])
useEffect(() => { useEffect(() => {
const getGame = async () => { const getGame = async () => {
loadedState.set(false) loadedState.set(false)
return await agent.Games.details(id) return await agent.Games.details(id!)
} }
getGame().then((result) => { getGame().then((result) => {
gameState.set(result) gameState.set(result)
@@ -69,7 +68,7 @@ const GameDetails = () => {
<SystemRequirementsModal isOpen={isSystemOpen} onClose={onSystemClose} game={game} /> <SystemRequirementsModal isOpen={isSystemOpen} onClose={onSystemClose} game={game} />
: ''} : ''}
<Container maxW="container.xl" mt="5"> <Container maxW="1280px" mt="5">
<Flex direction={{ base: 'column', md: 'row' }}> <Flex direction={{ base: 'column', md: 'row' }}>
<Box p={2} alignSelf={{ <Box p={2} alignSelf={{
base: 'center', base: 'center',
@@ -78,25 +77,27 @@ const GameDetails = () => {
<Heading as="h1">{game.title}</Heading> <Heading as="h1">{game.title}</Heading>
</Box> </Box>
<Spacer /> <Spacer />
<Stack spacing={[1, 5]} direction={'row'} alignSelf={{ <Stack gap={[1, 5]} direction={'row'} alignSelf={{
base: 'center', base: 'center',
md: 'end' md: 'end'
}} pb="3"> }} pb="3">
{game.accessedBy[0].store.includes('Steam') && game.steamId.length > 0 && {game.accessedBy[0].store.includes('Steam') && game.steamId.length > 0 &&
(<ChakraLink href={`steam://run/${game.steamId}`}> (<ChakraLink href={`steam://run/${game.steamId}`}>
<Button variant={'outline'} colorScheme="blue"> <Button variant={'outline'} colorPalette="blue">
<Flex> <Flex>
<SteamAndDeckLogo size={'1.5em'} /> <SteamAndDeckLogo size={'1.5em'} />
<Text mt={'1'} ml={'2'}> <Text mt={'1'} ml={'2'}>
Open with Steam <ExternalLinkIcon pb={'1'} /> Open with Steam <LuExternalLink style={{display: 'inline'}} />
</Text> </Text>
</Flex> </Flex>
</Button> </Button>
</ChakraLink>)} </ChakraLink>)}
<Button as={Link} to={`/games/${game._id}/edit`} colorScheme="blue"> <Button asChild colorPalette="blue">
<Text mt={'1'}> <Link to={`/games/${game._id}/edit`}>
Edit <Text mt={'1'}>
</Text> Edit
</Text>
</Link>
</Button> </Button>
</Stack> </Stack>
</Flex> </Flex>
@@ -122,7 +123,7 @@ const GameDetails = () => {
<Box bg={bg} p="5" mt="3" rounded="md"> <Box bg={bg} p="5" mt="3" rounded="md">
{isSmallerThan768 ? {isSmallerThan768 ?
<Flex width="100%"> <Flex width="100%">
<Button colorScheme='blue' size='lg' width='100%' onMouseDown={onSummaryOpen}> <Button colorPalette='blue' size='lg' width='100%' onMouseDown={onSummaryOpen}>
About This Game About This Game
</Button> </Button>
<Spacer /> <Spacer />
@@ -138,7 +139,7 @@ const GameDetails = () => {
<Box bg={bg} mt="3" p="5" rounded="md"> <Box bg={bg} mt="3" p="5" rounded="md">
{isSmallerThan768 ? {isSmallerThan768 ?
<Flex width="100%"> <Flex width="100%">
<Button colorScheme='blue' size='lg' width='100%' onMouseDown={onSystemOpen}> <Button colorPalette='blue' size='lg' width='100%' onMouseDown={onSystemOpen}>
System Requirements System Requirements
</Button> </Button>
<Spacer /> <Spacer />
@@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { Data } from '../../models/game' 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 GameRating from "../detailsComponents/GameRating";
import SteamAndDeckLogo from "../helperComponents/SteamAndDeckLogo"; 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 const gameLink = game.accessedBy[0].store.includes('Steam') && game.steamId.length > 0 ? game.steamId : game._id
return ( return (
<> <>
<LinkBox <Box
as={Link} asChild
to={`/games/${gameLink}`}
onMouseOver={onToggle}
onMouseOut={onToggle}
> >
<LinkOverlay> <Link
to={`/games/${gameLink}`}
onMouseOver={onToggle}
onMouseOut={onToggle}
>
<Image borderTopRadius="md" src={game.frontImage} alt={game.title} /> <Image borderTopRadius="md" src={game.frontImage} alt={game.title} />
</LinkOverlay> <Heading m={2} size="md" my="2">
<Heading m={2} size="md" my="2"> {game.steamId !== '' ? `${game.steamId}: ${game.title}` : `${game.title}`}
{game.steamId !== '' ? `${game.steamId}: ${game.title}` : `${game.title}`} </Heading>
</Heading> <Text ml={2}>Play Status: {playState}</Text>
<Text ml={2}>Play Status: {playState}</Text> </Link>
</LinkBox> </Box>
<Flex m={2}> <Flex m={2}>
<GameRating game={game} size={'1em'} /> <GameRating game={game} size={'1em'} />
<Spacer /> <Spacer />
@@ -1,9 +1,8 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Data } from "../../models/game"; import { Data } from "../../models/game";
import { ContextMenu } from 'chakra-ui-contextmenu'; import { Menu, Portal } from '@chakra-ui/react';
import { MenuItem, MenuList } from '@chakra-ui/menu';
import GameThumbnail from "./GameThumbnail"; import GameThumbnail from "./GameThumbnail";
import { Box, MenuGroup, useDisclosure } from "@chakra-ui/react"; import { Box, useDisclosure } from "@chakra-ui/react";
import GameThumbnailSlide from "./GameThumbnailSlide"; import GameThumbnailSlide from "./GameThumbnailSlide";
import { playStatusValues } from "../../componentUtils/SelectValues"; import { playStatusValues } from "../../componentUtils/SelectValues";
import agent from "../../api/agent"; import agent from "../../api/agent";
@@ -14,7 +13,7 @@ interface Props {
} }
const GameThumbnailContextMenu = ({ game }: Props) => { const GameThumbnailContextMenu = ({ game }: Props) => {
const { isOpen, onToggle } = useDisclosure() const { open: isOpen, onToggle } = useDisclosure()
const [playState, setPlayState] = useState(game.accessedBy[0].playStatus) const [playState, setPlayState] = useState(game.accessedBy[0].playStatus)
const gameLink = game.steamId.length > 1 ? game.steamId : game._id 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)'); let mql = window.matchMedia('(max-width: 768px)');
return ( return (
<ContextMenu<HTMLDivElement> key={game._id} renderMenu={() => ( <Menu.Root key={game._id}>
<MenuList> <Menu.ContextTrigger asChild>
<MenuItem><Link to={`/games/${gameLink}`} target="_blank" rel="noopener noreferrer">Open in New Tab</Link></MenuItem>
<MenuGroup title={'Change PlayStatus'}>
{playStatusValues.map(value => <MenuItem
onClick={() => rightClick(value.label, value.value)}
>{value.label}</MenuItem>)}
</MenuGroup>
</MenuList>
)}>
{ref => <>
<Box <Box
ref={ref} borderWidth={'1px'} borderStyle={'solid'}
border={'1px'}
borderColor={'gray.500'} borderColor={'gray.500'}
maxW={'sm'} maxW={'sm'}
rounded={'md'} rounded={'md'}
@@ -58,10 +47,28 @@ const GameThumbnailContextMenu = ({ game }: Props) => {
> >
<GameThumbnail onToggle={onToggle} game={game} playState={playState} /> <GameThumbnail onToggle={onToggle} game={game} playState={playState} />
</Box> </Box>
{(!mql.matches) && <GameThumbnailSlide isOpen={isOpen} game={game} />} </Menu.ContextTrigger>
</> <Portal>
} <Menu.Positioner>
</ContextMenu> <Menu.Content>
<Menu.Item value="open-new-tab" asChild>
<Link to={`/games/${gameLink}`} target="_blank" rel="noopener noreferrer">Open in New Tab</Link>
</Menu.Item>
<Menu.ItemGroup>
<Menu.ItemGroupLabel>Change PlayStatus</Menu.ItemGroupLabel>
{playStatusValues.map((value, idx) => (
<Menu.Item
key={idx}
value={value.value}
onSelect={() => rightClick(value.label, value.value)}
>{value.label}</Menu.Item>
))}
</Menu.ItemGroup>
</Menu.Content>
</Menu.Positioner>
</Portal>
{(!mql.matches) && <GameThumbnailSlide isOpen={isOpen} game={game} />}
</Menu.Root>
); );
}; };
@@ -1,5 +1,5 @@
import React from 'react'; 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 {Data} from "../../models/game";
import ImageSlider from "../detailsComponents/ImageSlider"; import ImageSlider from "../detailsComponents/ImageSlider";
@@ -10,17 +10,25 @@ interface Props {
const GameThumbnailSlide = ({isOpen, game}: Props) => { const GameThumbnailSlide = ({isOpen, game}: Props) => {
return ( return (
<Slide direction="bottom" in={isOpen} unmountOnExit={true} style={{zIndex: 10}}> <Presence
present={isOpen}
position="fixed"
bottom="0"
insetX="0"
animationName={{ _open: "slide-from-bottom-full", _closed: "slide-to-bottom-full" }}
animationDuration="moderate"
style={{zIndex: 10}}
>
<Box <Box
p="40px" p="40px"
color="white" color="white"
mt="4" mt="4"
bgGradient={'linear(to-br, #314755, #26a0da)'} bgImage={'linear-gradient(to bottom right, #314755, #26a0da)'}
borderTopRadius="md" borderTopRadius="md"
shadow="md" shadow="md"
> >
<Container maxW="container.xl"> <Container maxW="1280px">
<HStack spacing='2rem'> <HStack gap='2rem'>
<ImageSlider game={game} width={'250px'}/> <ImageSlider game={game} width={'250px'}/>
<Box> <Box>
<Heading>{game.title}</Heading> <Heading>{game.title}</Heading>
@@ -29,7 +37,7 @@ const GameThumbnailSlide = ({isOpen, game}: Props) => {
</HStack> </HStack>
</Container> </Container>
</Box> </Box>
</Slide> </Presence>
); );
}; };
+39 -113
View File
@@ -1,5 +1,5 @@
import React, { useState as reactUseState } from 'react' 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 { Data } from '../../models/game'
import { gameDashboardState } from '../../stateManagement/gameState' import { gameDashboardState } from '../../stateManagement/gameState'
import { ImmutableObject, useHookstate } from '@hookstate/core' import { ImmutableObject, useHookstate } from '@hookstate/core'
@@ -36,48 +36,40 @@ export default function Features({ game }: Props) {
return history('/games') return history('/games')
} }
const badge = (label: string) => (
<Box>
<Badge colorPalette="blue" variant="outline" rounded="md" pl="2" pr="2">
<Text pt="1" fontSize="sm">
{label}
</Text>
</Badge>
</Box>
)
return ( return (
<Box mb="3"> <Box mb="3">
<Heading as="h3" size="lg">Features and Information</Heading> <Heading as="h3" size="lg">Features and Information</Heading>
<Flex direction="column" m="2"> <Flex direction="column" m="2">
<Box> {badge('Rating:')}
<Badge colorScheme="blue" variant="outline" rounded="md" pl="2" pr="2">
<Text pt="1" fontSize="sm">
Rating:
</Text>
</Badge>
</Box>
<Box mt="1.5" ml="5" mr="2"> <Box mt="1.5" ml="5" mr="2">
<GameRating game={game} size={'1.5em'} /> <GameRating game={game} size={'1.5em'} />
</Box> </Box>
</Flex> </Flex>
<Divider orientation="horizontal" /> <Separator borderColor="gray.200" orientation="horizontal" />
{game.accessedBy[0].store.includes('Steam') && game.steamId.length > 0 && {game.accessedBy[0].store.includes('Steam') && game.steamId.length > 0 &&
(<> (<>
<Flex direction="column" m="2"> <Flex direction="column" m="2">
<Box> {badge('Steam ID:')}
<Badge colorScheme="blue" variant="outline" rounded="md" pl="2" pr="2">
<Text pt="1" fontSize="sm">
Steam ID:
</Text>
</Badge>
</Box>
<Box mt="1.5" ml="5" mr="2"> <Box mt="1.5" ml="5" mr="2">
<Link pt="1" href={`steam://run/${game.steamId}`}>{game.steamId}</Link> <Link pt="1" href={`steam://run/${game.steamId}`}>{game.steamId}</Link>
</Box> </Box>
</Flex> </Flex>
<Divider orientation="horizontal" /> <Separator borderColor="gray.200" orientation="horizontal" />
</>)} </>)}
{game.series.length > 1 && {game.series.length > 1 &&
(<> (<>
<Flex direction="column" m="2"> <Flex direction="column" m="2">
<Box> {badge('Series:')}
<Badge colorScheme="blue" variant="outline" rounded="md" pl="2" pr="2">
<Text pt="1" fontSize="sm">
Series:
</Text>
</Badge>
</Box>
<Box mt="1.5" ml="5" mr="2"> <Box mt="1.5" ml="5" mr="2">
<Link onMouseDown={() => { <Link onMouseDown={() => {
gamesState.filters.series.set(game.series) gamesState.filters.series.set(game.series)
@@ -85,16 +77,10 @@ export default function Features({ game }: Props) {
}} pt="1">{game.series}</Link> }} pt="1">{game.series}</Link>
</Box> </Box>
</Flex> </Flex>
<Divider orientation="horizontal" /> <Separator borderColor="gray.200" orientation="horizontal" />
</>)} </>)}
<Flex direction="column" m="2"> <Flex direction="column" m="2">
<Box> {badge('Play Status:')}
<Badge colorScheme="blue" variant="outline" rounded="md" pl="2" pr="2">
<Text pt="1" fontSize="sm">
Play Status:
</Text>
</Badge>
</Box>
<Box mt="1.5" ml="5" mr="2"> <Box mt="1.5" ml="5" mr="2">
<Box display={'flex'}> <Box display={'flex'}>
{editMode ? {editMode ?
@@ -120,15 +106,9 @@ export default function Features({ game }: Props) {
</Box> </Box>
</Box> </Box>
</Flex> </Flex>
<Divider orientation="horizontal" /> <Separator borderColor="gray.200" orientation="horizontal" />
<Flex direction="column" m="2"> <Flex direction="column" m="2">
<Box> {badge('Genre:')}
<Badge colorScheme="blue" variant="outline" rounded="md" pl="2" pr="2">
<Text pt="1" fontSize="sm">
Genre:
</Text>
</Badge>
</Box>
<Box mt={'2'} ml={'4'}> <Box mt={'2'} ml={'4'}>
{game.genre.map((genre, index) => [ {game.genre.map((genre, index) => [
index > 0 && ", ", index > 0 && ", ",
@@ -141,15 +121,9 @@ export default function Features({ game }: Props) {
])} ])}
</Box> </Box>
</Flex> </Flex>
<Divider orientation="horizontal" /> <Separator borderColor="gray.200" orientation="horizontal" />
<Flex direction="column" m="2"> <Flex direction="column" m="2">
<Box> {badge('Operating Systems:')}
<Badge colorScheme="blue" variant="outline" rounded="md" pl="2" pr="2">
<Text pt="1" fontSize="sm">
Operating Systems:
</Text>
</Badge>
</Box>
<Box mt="1.5" ml="5" mr="2"> <Box mt="1.5" ml="5" mr="2">
<Text pt="1"> <Text pt="1">
{Object.keys(game.os).filter(os => { {Object.keys(game.os).filter(os => {
@@ -175,15 +149,9 @@ export default function Features({ game }: Props) {
</Text> </Text>
</Box> </Box>
</Flex> </Flex>
<Divider orientation="horizontal" /> <Separator borderColor="gray.200" orientation="horizontal" />
<Flex direction="column" m="2"> <Flex direction="column" m="2">
<Box> {badge('Store:')}
<Badge colorScheme="blue" variant="outline" rounded="md" pl="2" pr="2">
<Text pt="1" fontSize="sm">
Store:
</Text>
</Badge>
</Box>
<Box mt="1.5" ml="5" mr="2"> <Box mt="1.5" ml="5" mr="2">
{game.accessedBy[0].store.map((store, index) => [ {game.accessedBy[0].store.map((store, index) => [
index > 0 && ", ", index > 0 && ", ",
@@ -196,15 +164,9 @@ export default function Features({ game }: Props) {
])} ])}
</Box> </Box>
</Flex> </Flex>
<Divider orientation="horizontal" /> <Separator borderColor="gray.200" orientation="horizontal" />
<Flex direction="column" m="2"> <Flex direction="column" m="2">
<Box> {badge('Developer:')}
<Badge colorScheme="blue" variant="outline" rounded="md" pl="2" pr="2">
<Text pt="1" fontSize="sm">
Developer:
</Text>
</Badge>
</Box>
<Box mt="1.5" ml="5" mr="2"> <Box mt="1.5" ml="5" mr="2">
{game.developer.map((developer, index) => [ {game.developer.map((developer, index) => [
index > 0 && ", ", index > 0 && ", ",
@@ -217,15 +179,9 @@ export default function Features({ game }: Props) {
])} ])}
</Box> </Box>
</Flex> </Flex>
<Divider orientation="horizontal" /> <Separator borderColor="gray.200" orientation="horizontal" />
<Flex direction="column" m="2"> <Flex direction="column" m="2">
<Box> {badge('Publisher:')}
<Badge colorScheme="blue" variant="outline" rounded="md" pl="2" pr="2">
<Text pt="1" fontSize="sm">
Publisher:
</Text>
</Badge>
</Box>
<Box mt="1.5" ml="5" mr="2"> <Box mt="1.5" ml="5" mr="2">
{game.publisher.map((publisher, index) => [ {game.publisher.map((publisher, index) => [
index > 0 && ", ", index > 0 && ", ",
@@ -238,15 +194,9 @@ export default function Features({ game }: Props) {
])} ])}
</Box> </Box>
</Flex> </Flex>
<Divider orientation="horizontal" /> <Separator borderColor="gray.200" orientation="horizontal" />
<Flex direction="column" m="2"> <Flex direction="column" m="2">
<Box> {badge('Controller Support:')}
<Badge colorScheme="blue" variant="outline" rounded="md" pl="2" pr="2">
<Text pt="1" fontSize="sm">
Controller Support:
</Text>
</Badge>
</Box>
<Box mt="1.5" ml="5" mr="2"> <Box mt="1.5" ml="5" mr="2">
<Link onMouseDown={() => { <Link onMouseDown={() => {
gamesState.filters.controller.set(game.controller) gamesState.filters.controller.set(game.controller)
@@ -254,28 +204,16 @@ export default function Features({ game }: Props) {
}} pt="1">{game.controller}</Link> }} pt="1">{game.controller}</Link>
</Box> </Box>
</Flex> </Flex>
<Divider orientation="horizontal" /> <Separator borderColor="gray.200" orientation="horizontal" />
<Flex direction="column" m="2"> <Flex direction="column" m="2">
<Box> {badge('Release Date:')}
<Badge colorScheme="blue" variant="outline" rounded="md" pl="2" pr="2">
<Text pt="1" fontSize="sm">
Release Date:
</Text>
</Badge>
</Box>
<Box mt="1.5" ml="5" mr="2"> <Box mt="1.5" ml="5" mr="2">
<Text pt="1">{game.releaseDate}</Text> <Text pt="1">{game.releaseDate}</Text>
</Box> </Box>
</Flex> </Flex>
<Divider orientation="horizontal" /> <Separator borderColor="gray.200" orientation="horizontal" />
<Flex direction="column" m="2"> <Flex direction="column" m="2">
<Box> {badge('Soundtrack:')}
<Badge colorScheme="blue" variant="outline" rounded="md" pl="2" pr="2">
<Text pt="1" fontSize="sm">
Soundtrack:
</Text>
</Badge>
</Box>
<Box mt="1.5" ml="5" mr="2"> <Box mt="1.5" ml="5" mr="2">
<Link onMouseDown={() => { <Link onMouseDown={() => {
gamesState.filters.soundtrack.set(game.accessedBy[0].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}</Link> }} pt="1">{game.accessedBy[0].soundtrack}</Link>
</Box> </Box>
</Flex> </Flex>
<Divider orientation="horizontal" /> <Separator borderColor="gray.200" orientation="horizontal" />
<Flex direction="column" m="2"> <Flex direction="column" m="2">
<Box> {badge('Does it work with Intel?:')}
<Badge colorScheme="blue" variant="outline" rounded="md" pl="2" pr="2">
<Text pt="1" fontSize="sm">
Does it work with Intel?:
</Text>
</Badge>
</Box>
<Box mt="1.5" ml="5" mr="2"> <Box mt="1.5" ml="5" mr="2">
<Link onMouseDown={() => { <Link onMouseDown={() => {
gamesState.filters.intel.set(game.intel) gamesState.filters.intel.set(game.intel)
@@ -299,15 +231,9 @@ export default function Features({ game }: Props) {
}} pt="1">{game.intel}</Link> }} pt="1">{game.intel}</Link>
</Box> </Box>
</Flex> </Flex>
<Divider orientation="horizontal" /> <Separator borderColor="gray.200" orientation="horizontal" />
<Flex direction="column" m="2"> <Flex direction="column" m="2">
<Box> {badge('Does it work with WINE?:')}
<Badge colorScheme="blue" variant="outline" rounded="md" pl="2" pr="2">
<Text pt="1" fontSize="sm">
Does it work with WINE?:
</Text>
</Badge>
</Box>
<Box mt="1.5" ml="5" mr="2"> <Box mt="1.5" ml="5" mr="2">
<Link onMouseDown={() => { <Link onMouseDown={() => {
gamesState.filters.wine.set(game.wine) gamesState.filters.wine.set(game.wine)
@@ -315,7 +241,7 @@ export default function Features({ game }: Props) {
}} pt="1">{game.wine}</Link> }} pt="1">{game.wine}</Link>
</Box> </Box>
</Flex> </Flex>
<Divider orientation="horizontal" /> <Separator borderColor="gray.200" orientation="horizontal" />
</Box> </Box>
) )
} }
@@ -2,11 +2,12 @@ import React from 'react';
import { Data } from '../../models/game' import { Data } from '../../models/game'
import agent from "../../api/agent"; import agent from "../../api/agent";
import {playStatusValues} from "../../componentUtils/SelectValues"; import {playStatusValues} from "../../componentUtils/SelectValues";
import {Select} from "chakra-react-select"; import {Select} from "../FixedSelect";
import {ImmutableObject} from "@hookstate/core";
interface Props { interface Props {
game: Data game: Data | ImmutableObject<Data>
playState: string playState: string
setPlayState: any setPlayState: any
} }
@@ -33,14 +34,14 @@ const GamePlayStatus = ({game, playState, setPlayState}: Props) => {
value: playState, value: playState,
label: playState, label: playState,
}} }}
onChange={(e) => { onChange={(e: any) => {
if(e === null) return if(e === null) return
handleClick(e.value, e.label) handleClick(e.value, e.label)
}} }}
size={'sm'} size={'sm'}
options={playStatusValues} options={playStatusValues}
chakraStyles={{ chakraStyles={{
container: (provided) => ({ container: (provided: any) => ({
...provided, ...provided,
width: "150px" width: "150px"
}) })
@@ -5,10 +5,11 @@ import agent from "../../api/agent";
import {Flex, Link} from "@chakra-ui/react"; import {Flex, Link} from "@chakra-ui/react";
import {IoGameController, IoGameControllerOutline} from "react-icons/io5"; import {IoGameController, IoGameControllerOutline} from "react-icons/io5";
import {BiReset} from "react-icons/bi"; import {BiReset} from "react-icons/bi";
import {ImmutableObject} from "@hookstate/core";
interface Props { interface Props {
game: Data game: Data | ImmutableObject<Data>
size: string size: string
} }
+3 -3
View File
@@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import { Data } from '../../models/game' import { Data } from '../../models/game'
import { Interweave } from 'interweave' 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' import { ImmutableObject } from '@hookstate/core'
interface Props { interface Props {
@@ -15,7 +15,7 @@ export default function Summary({ game }: Props) {
{reviews.length >= 1 && ( {reviews.length >= 1 && (
<> <>
<Heading as="h1">Reviews</Heading> <Heading as="h1">Reviews</Heading>
<Divider m="3" /> <Separator borderColor="gray.200" m="3" />
<Interweave <Interweave
content={reviews} content={reviews}
allowAttributes allowAttributes
@@ -24,7 +24,7 @@ export default function Summary({ game }: Props) {
</> </>
)} )}
<Heading mt={reviews.length >= 1 ? '10' : ''} as="h1">About This Game</Heading> <Heading mt={reviews.length >= 1 ? '10' : ''} as="h1">About This Game</Heading>
<Divider m="3" /> <Separator borderColor="gray.200" m="3" />
<Interweave <Interweave
content={summary} content={summary}
allowAttributes allowAttributes
@@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import { Button, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react' import { Button, Dialog } from '@chakra-ui/react'
import {DialogBody, DialogCloseTrigger, DialogContent, DialogFooter, DialogRoot} from '../ui/dialog'
import Summary from './Summary' import Summary from './Summary'
import { ImmutableObject } from '@hookstate/core' import { ImmutableObject } from '@hookstate/core'
import { Data } from '../../models/game' import { Data } from '../../models/game'
@@ -14,20 +15,19 @@ interface Props {
const SummaryModal = ({ isOpen, onClose, game }: Props) => { const SummaryModal = ({ isOpen, onClose, game }: Props) => {
return ( return (
<Modal isOpen={isOpen} onClose={onClose}> <DialogRoot open={isOpen} onOpenChange={(e) => { if (!e.open) onClose() }}>
<ModalOverlay /> <DialogContent maxW={'90%'}>
<ModalContent maxW={'90%'}> <DialogCloseTrigger />
<ModalCloseButton /> <DialogBody>
<ModalBody>
<Summary game={game} /> <Summary game={game} />
</ModalBody> </DialogBody>
<ModalFooter> <DialogFooter>
<Button colorScheme='blue' onMouseDown={onClose}> <Button colorPalette='blue' onMouseDown={onClose}>
Close Close
</Button> </Button>
</ModalFooter> </DialogFooter>
</ModalContent> </DialogContent>
</Modal> </DialogRoot>
) )
} }
@@ -1,5 +1,5 @@
import React from 'react' 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 { Data } from '../../models/game'
import { Interweave } from 'interweave' import { Interweave } from 'interweave'
import { ImmutableObject } from '@hookstate/core' import { ImmutableObject } from '@hookstate/core'
@@ -20,51 +20,51 @@ export default function SystemRequirements({ game }: Props) {
return ( return (
<Box> <Box>
<Heading as='h3' size='lg'>System Requirements</Heading> <Heading as='h3' size='lg'>System Requirements</Heading>
<Tabs isLazy mt='3'> <Tabs.Root lazyMount mt='3' defaultValue="windows">
<TabList> <Tabs.List>
<Tab>Windows</Tab> <Tabs.Trigger value="windows">Windows</Tabs.Trigger>
<Tab isDisabled={macMin}>Mac</Tab> <Tabs.Trigger value="mac" disabled={macMin}>Mac</Tabs.Trigger>
<Tab isDisabled={linMin}>Linux</Tab> <Tabs.Trigger value="linux" disabled={linMin}>Linux</Tabs.Trigger>
</TabList> </Tabs.List>
<TabPanels> <Tabs.ContentGroup>
<TabPanel> <Tabs.Content value="windows">
<Tabs isLazy> <Tabs.Root lazyMount defaultValue="win-min">
<TabList> <Tabs.List>
<Tab>Minimum</Tab> <Tabs.Trigger value="win-min">Minimum</Tabs.Trigger>
<Tab isDisabled={winRec}>Recommended</Tab> <Tabs.Trigger value="win-rec" disabled={winRec}>Recommended</Tabs.Trigger>
</TabList> </Tabs.List>
<TabPanels> <Tabs.ContentGroup>
<TabPanel>{<Interweave content={windows.minimum} />}</TabPanel> <Tabs.Content value="win-min">{<Interweave content={windows.minimum} />}</Tabs.Content>
<TabPanel>{<Interweave content={windows.recommended} />}</TabPanel> <Tabs.Content value="win-rec">{<Interweave content={windows.recommended} />}</Tabs.Content>
</TabPanels> </Tabs.ContentGroup>
</Tabs> </Tabs.Root>
</TabPanel> </Tabs.Content>
<TabPanel> <Tabs.Content value="mac">
<Tabs isLazy> <Tabs.Root lazyMount defaultValue="mac-min">
<TabList> <Tabs.List>
<Tab isDisabled={macMin}>Minimum</Tab> <Tabs.Trigger value="mac-min" disabled={macMin}>Minimum</Tabs.Trigger>
<Tab isDisabled={macRec}>Recommended</Tab> <Tabs.Trigger value="mac-rec" disabled={macRec}>Recommended</Tabs.Trigger>
</TabList> </Tabs.List>
<TabPanels> <Tabs.ContentGroup>
<TabPanel>{<Interweave content={mac.minimum} />}</TabPanel> <Tabs.Content value="mac-min">{<Interweave content={mac.minimum} />}</Tabs.Content>
<TabPanel>{<Interweave content={mac.recommended} />}</TabPanel> <Tabs.Content value="mac-rec">{<Interweave content={mac.recommended} />}</Tabs.Content>
</TabPanels> </Tabs.ContentGroup>
</Tabs> </Tabs.Root>
</TabPanel> </Tabs.Content>
<TabPanel> <Tabs.Content value="linux">
<Tabs isLazy> <Tabs.Root lazyMount defaultValue="lin-min">
<TabList> <Tabs.List>
<Tab isDisabled={linMin}>Minimum</Tab> <Tabs.Trigger value="lin-min" disabled={linMin}>Minimum</Tabs.Trigger>
<Tab isDisabled={linRec}>Recommended</Tab> <Tabs.Trigger value="lin-rec" disabled={linRec}>Recommended</Tabs.Trigger>
</TabList> </Tabs.List>
<TabPanels> <Tabs.ContentGroup>
<TabPanel>{<Interweave content={linux.minimum} />}</TabPanel> <Tabs.Content value="lin-min">{<Interweave content={linux.minimum} />}</Tabs.Content>
<TabPanel>{<Interweave content={linux.recommended} />}</TabPanel> <Tabs.Content value="lin-rec">{<Interweave content={linux.recommended} />}</Tabs.Content>
</TabPanels> </Tabs.ContentGroup>
</Tabs> </Tabs.Root>
</TabPanel> </Tabs.Content>
</TabPanels> </Tabs.ContentGroup>
</Tabs> </Tabs.Root>
</Box> </Box>
) )
} }
@@ -1,5 +1,6 @@
import React from 'react' 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 { ImmutableObject } from '@hookstate/core'
import { Data } from '../../models/game' import { Data } from '../../models/game'
import SystemRequirements from './SystemRequirementsMenu' import SystemRequirements from './SystemRequirementsMenu'
@@ -14,20 +15,19 @@ interface Props {
const SummaryModal = ({ isOpen, onClose, game }: Props) => { const SummaryModal = ({ isOpen, onClose, game }: Props) => {
return ( return (
<Modal isOpen={isOpen} onClose={onClose}> <DialogRoot open={isOpen} onOpenChange={(e) => { if (!e.open) onClose() }}>
<ModalOverlay /> <DialogContent maxW={'90%'}>
<ModalContent maxW={'90%'}> <DialogCloseTrigger />
<ModalCloseButton /> <DialogBody>
<ModalBody>
<SystemRequirements game={game} /> <SystemRequirements game={game} />
</ModalBody> </DialogBody>
<ModalFooter> <DialogFooter>
<Button colorScheme='blue' onMouseDown={onClose}> <Button colorPalette='blue' onMouseDown={onClose}>
Close Close
</Button> </Button>
</ModalFooter> </DialogFooter>
</ModalContent> </DialogContent>
</Modal> </DialogRoot>
) )
} }