refactor(nav): migrate header, dashboard chrome and discovery UI to v3

- Header/MobileBar: MenuRoot/Positioner/Content + Portal + Tooltip
  shims, ghost icon buttons, 2rem padded user/settings menus, hover
  token gray.700; MobileBar drawer API (open/onOpenChange/finalFocusEl)
- FilterBar/ActiveFilters: DrawerRoot/CloseTrigger, Divider -> Separator,
  Tag shim, FixedSelect with typed handlers and width-preserving
  chakraStyles, blue active-filter pills untouched
- Pagination: NumberInputRoot/Field + NativeSelect shims, first/last
  arrows back to double chevrons (LuChevronsLeft/Right), single chevrons
  kept for prev/next
- Search/SearchModal, Home, NoGames, LoadingModal, DashboardLoadingModal:
  Dialog/Toaster-era props, gap/align/textAlign renames; App.test target
  content updated accordingly
This commit is contained in:
2026-09-06 10:30:21 -04:00
parent 7bdf42ac07
commit 45dd783964
15 changed files with 325 additions and 326 deletions
+12 -14
View File
@@ -1,7 +1,8 @@
import React from 'react' import React from 'react'
import { useHookstate } from '@hookstate/core' import { useHookstate } from '@hookstate/core'
import { gameDashboardState } from '../stateManagement/gameState' import { gameDashboardState } from '../stateManagement/gameState'
import { Flex, Stack, HStack, Link, Spacer, Tag, TagCloseButton, TagLabel } from '@chakra-ui/react' import { Flex, Stack, HStack, Link, Spacer } from '@chakra-ui/react'
import {Tag} from './ui/tag'
import getWithFilters from '../componentUtils/getWithFilters' import getWithFilters from '../componentUtils/getWithFilters'
import { import {
loadingState, loadingState,
@@ -53,7 +54,7 @@ const ActiveFilters = () => {
}} direction={{ }} direction={{
base: 'column', base: 'column',
md: 'row' md: 'row'
}} spacing={4}> }} gap={4}>
{searchParams.length >= 1 && ( {searchParams.length >= 1 && (
<> <>
<h2>Search: </h2> <h2>Search: </h2>
@@ -61,11 +62,8 @@ const ActiveFilters = () => {
size={'md'} size={'md'}
borderRadius="full" borderRadius="full"
variant="solid" variant="solid"
colorScheme="blue" colorPalette="blue"
> onClose={async () => {
<TagLabel><Link
onMouseDown={() => setSearchModalOpen.set(true)}>{searchParams}</Link></TagLabel>
<TagCloseButton onMouseDown={async () => {
const newSearchParams = "" const newSearchParams = ""
ChangeActiveFilterHeader(filters, newSearchParams, setShowFiltersModuleState) ChangeActiveFilterHeader(filters, newSearchParams, setShowFiltersModuleState)
searchComplete.set(false) searchComplete.set(false)
@@ -80,7 +78,9 @@ const ActiveFilters = () => {
useRatingState, useRatingState,
useSteamRatingState, useSteamRatingState,
) )
}} /> }}
><Link
onMouseDown={() => setSearchModalOpen.set(true)}>{searchParams}</Link>
</Tag> </Tag>
</> </>
)} )}
@@ -99,22 +99,20 @@ const ActiveFilters = () => {
<> <>
<HStack key={num}> <HStack key={num}>
<h3 style={{ textTransform: 'capitalize' }}>{filter}</h3> <h3 style={{ textTransform: 'capitalize' }}>{filter}</h3>
<HStack spacing={4}> <HStack gap={4}>
{filters[filter as keyof typeof filters].split(',').map((item: string, index) => ( {filters[filter as keyof typeof filters].split(',').map((item: string, index) => (
<Tag <Tag
size={'md'} size={'md'}
key={index} key={index}
borderRadius="full" borderRadius="full"
variant="solid" variant="solid"
colorScheme="blue" colorPalette="blue"
> onClose={() => removeFilters(filter, item)}
<TagLabel>{ >{
item.includes('lte') item.includes('lte')
? item.replace('lte', '<= ') ? item.replace('lte', '<= ')
: item.includes('gte') ? item.replace('gte', '>= ') : item.includes('gte') ? item.replace('gte', '>= ')
: item} : item}
</TagLabel>
<TagCloseButton onMouseDown={() => removeFilters(filter, item)} />
</Tag> </Tag>
))} ))}
</HStack> </HStack>
+54 -60
View File
@@ -1,17 +1,12 @@
import React, {MutableRefObject, useEffect} from 'react' import React, {MutableRefObject, useEffect} from 'react'
import { import {
Center, Center,
Divider, Separator,
Drawer, Flex,
DrawerBody,
DrawerCloseButton,
DrawerContent,
DrawerFooter,
DrawerHeader,
DrawerOverlay, Flex,
Heading, Spacer, Heading, Spacer,
Text, Text,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import {DrawerBody, DrawerCloseTrigger, DrawerContent, DrawerFooter, DrawerHeader, DrawerRoot} from './ui/drawer'
import AdminMode from './authComponents/AdminMode' import AdminMode from './authComponents/AdminMode'
import {StateMethods, useHookstate} from '@hookstate/core' import {StateMethods, useHookstate} from '@hookstate/core'
import {tagState} from '../stateManagement/tagState' import {tagState} from '../stateManagement/tagState'
@@ -25,7 +20,7 @@ import {
} from '../componentUtils/SelectValues' } from '../componentUtils/SelectValues'
import {retrieveTags} from '../componentUtils/retrieveTags' import {retrieveTags} from '../componentUtils/retrieveTags'
import {gameDashboardState} from '../stateManagement/gameState' import {gameDashboardState} from '../stateManagement/gameState'
import {Select} from 'chakra-react-select' import {Select} from './FixedSelect'
import getWithFilters from '../componentUtils/getWithFilters' import getWithFilters from '../componentUtils/getWithFilters'
import {loadingState, showFiltersModuleState} from '../stateManagement/loadingState' import {loadingState, showFiltersModuleState} from '../stateManagement/loadingState'
import {adminMode} from '../stateManagement/userState' import {adminMode} from '../stateManagement/userState'
@@ -99,26 +94,25 @@ function FilterBar({filterIsOpen, filterOnClose, btnRef}: Props) {
} }
return ( return (
<Drawer <DrawerRoot
isOpen={filterIsOpen} open={filterIsOpen}
placement="right" placement="end"
onClose={filterOnClose} onOpenChange={(e) => { if (!e.open) filterOnClose() }}
finalFocusRef={btnRef} finalFocusEl={() => btnRef.current as unknown as HTMLElement}
> >
<DrawerOverlay/>
<DrawerContent> <DrawerContent>
<DrawerCloseButton/> <DrawerCloseTrigger/>
<DrawerHeader> <DrawerHeader>
<Heading as={'h3'}>Filters</Heading> <Heading as={'h3'}>Filters</Heading>
</DrawerHeader> </DrawerHeader>
<DrawerBody> <DrawerBody>
<AdminMode/> <AdminMode/>
<Divider my={'3'}/> <Separator borderColor="gray.200" my={'3'}/>
<Center> <Center>
<ResetFilterButton/> <ResetFilterButton/>
</Center> </Center>
<Divider my={'3'}/> <Separator borderColor="gray.200" my={'3'}/>
<Text mb={'1'} fontSize={'xl'}>Series</Text> <Text mb={'1'} fontSize={'xl'}>Series</Text>
<Select <Select
size={'sm'} size={'sm'}
@@ -126,13 +120,13 @@ function FilterBar({filterIsOpen, filterOnClose, btnRef}: Props) {
isClearable isClearable
isMulti isMulti
name="series" name="series"
onChange={(event) => { onChange={(event: any) => {
const value = event.map(a => a.value).join(',') const value = event.map((a: any) => a.value).join(',')
changeFilters("series", value) changeFilters("series", value)
}} }}
options={tags.series} options={tags.series}
/> />
<Divider my={'3'}/> <Separator borderColor="gray.200" my={'3'}/>
<Flex mb={'1'}> <Flex mb={'1'}>
<Text mb={'1'} fontSize={'xl'}>Rating</Text> <Text mb={'1'} fontSize={'xl'}>Rating</Text>
<Spacer /> <Spacer />
@@ -140,13 +134,13 @@ function FilterBar({filterIsOpen, filterOnClose, btnRef}: Props) {
size={'sm'} size={'sm'}
defaultValue={ratingToSelectDefaultValue(useRatingState)} defaultValue={ratingToSelectDefaultValue(useRatingState)}
name="rating" name="rating"
onChange={(event) => { onChange={(event: any) => {
if (event === null) return if (event === null) return
setRatingState.set(event.value) setRatingState.set(event.value)
}} }}
options={ratingStateValues} options={ratingStateValues}
chakraStyles={{ chakraStyles={{
container: (provided) => ({ container: (provided: any) => ({
...provided, ...provided,
width: "75px" width: "75px"
}) })
@@ -158,13 +152,13 @@ function FilterBar({filterIsOpen, filterOnClose, btnRef}: Props) {
defaultValue={filterToSelectDefaultValue("rating")} defaultValue={filterToSelectDefaultValue("rating")}
isClearable isClearable
name="rating" name="rating"
onChange={(event) => { onChange={(event: any) => {
if(event === null) return changeFilters("rating", "") if(event === null) return changeFilters("rating", "")
changeFilters("rating", event.value) changeFilters("rating", event.value)
}} }}
options={ratingValues} options={ratingValues}
/> />
<Divider my={'1'}/> <Separator borderColor="gray.200" my={'1'}/>
<Flex mb={'1'}> <Flex mb={'1'}>
<Text mb={'1'} fontSize={'xl'}>Steam Rating</Text> <Text mb={'1'} fontSize={'xl'}>Steam Rating</Text>
<Spacer /> <Spacer />
@@ -172,13 +166,13 @@ function FilterBar({filterIsOpen, filterOnClose, btnRef}: Props) {
size={'sm'} size={'sm'}
defaultValue={ratingToSelectDefaultValue(useSteamRatingState)} defaultValue={ratingToSelectDefaultValue(useSteamRatingState)}
name="steamRating" name="steamRating"
onChange={(event) => { onChange={(event: any) => {
if (event === null) return if (event === null) return
setSteamRatingState.set(event.value) setSteamRatingState.set(event.value)
}} }}
options={ratingStateValues} options={ratingStateValues}
chakraStyles={{ chakraStyles={{
container: (provided) => ({ container: (provided: any) => ({
...provided, ...provided,
width: "75px" width: "75px"
}) })
@@ -190,13 +184,13 @@ function FilterBar({filterIsOpen, filterOnClose, btnRef}: Props) {
defaultValue={filterToSelectDefaultValue("steamRating")} defaultValue={filterToSelectDefaultValue("steamRating")}
isClearable isClearable
name="steamRating" name="steamRating"
onChange={(event) => { onChange={(event: any) => {
if(event === null) return changeFilters("steamRating", "") if(event === null) return changeFilters("steamRating", "")
changeFilters("steamRating", event.value) changeFilters("steamRating", event.value)
}} }}
options={ratingValues} options={ratingValues}
/> />
<Divider my={'1'}/> <Separator borderColor="gray.200" my={'1'}/>
<Text mb={'1'} fontSize={'xl'}>Play Status</Text> <Text mb={'1'} fontSize={'xl'}>Play Status</Text>
<Select <Select
size={'sm'} size={'sm'}
@@ -204,13 +198,13 @@ function FilterBar({filterIsOpen, filterOnClose, btnRef}: Props) {
isClearable isClearable
isMulti isMulti
name="playStatus" name="playStatus"
onChange={(event) => { onChange={(event: any) => {
const value = event.map(a => a.value).join(',') const value = event.map((a: any) => a.value).join(',')
changeFilters("playStatus", value) changeFilters("playStatus", value)
}} }}
options={playStatusValues} options={playStatusValues}
/> />
<Divider my={'1'}/> <Separator borderColor="gray.200" my={'1'}/>
<Text mb={'1'} fontSize={'xl'}>Genres</Text> <Text mb={'1'} fontSize={'xl'}>Genres</Text>
<Select <Select
size={'sm'} size={'sm'}
@@ -218,13 +212,13 @@ function FilterBar({filterIsOpen, filterOnClose, btnRef}: Props) {
isClearable isClearable
isMulti isMulti
name="genre" name="genre"
onChange={(event) => { onChange={(event: any) => {
const value = event.map(a => a.value).join(',') const value = event.map((a: any) => a.value).join(',')
changeFilters("genre", value) changeFilters("genre", value)
}} }}
options={tags.genres} options={tags.genres}
/> />
<Divider my={'1'}/> <Separator borderColor="gray.200" my={'1'}/>
<Text mb={'1'} fontSize={'xl'}>Operating System</Text> <Text mb={'1'} fontSize={'xl'}>Operating System</Text>
<Select <Select
size={'sm'} size={'sm'}
@@ -232,13 +226,13 @@ function FilterBar({filterIsOpen, filterOnClose, btnRef}: Props) {
isClearable isClearable
isMulti isMulti
name="os" name="os"
onChange={(event) => { onChange={(event: any) => {
const value = event.map(a => a.value).join(',') const value = event.map((a: any) => a.value).join(',')
changeFilters("os", value) changeFilters("os", value)
}} }}
options={osValues} options={osValues}
/> />
<Divider my={'1'}/> <Separator borderColor="gray.200" my={'1'}/>
<Text mb={'1'} fontSize={'xl'}>Store</Text> <Text mb={'1'} fontSize={'xl'}>Store</Text>
<Select <Select
size={'sm'} size={'sm'}
@@ -246,13 +240,13 @@ function FilterBar({filterIsOpen, filterOnClose, btnRef}: Props) {
isClearable isClearable
isMulti isMulti
name="store" name="store"
onChange={(event) => { onChange={(event: any) => {
const value = event.map(a => a.value).join(',') const value = event.map((a: any) => a.value).join(',')
changeFilters("store", value) changeFilters("store", value)
}} }}
options={tags.store} options={tags.store}
/> />
<Divider my={'1'}/> <Separator borderColor="gray.200" my={'1'}/>
<Text mb={'1'} fontSize={'xl'}>Controller Support</Text> <Text mb={'1'} fontSize={'xl'}>Controller Support</Text>
<Select <Select
size={'sm'} size={'sm'}
@@ -260,13 +254,13 @@ function FilterBar({filterIsOpen, filterOnClose, btnRef}: Props) {
isClearable isClearable
isMulti isMulti
name="controller" name="controller"
onChange={(event) => { onChange={(event: any) => {
const value = event.map(a => a.value).join(',') const value = event.map((a: any) => a.value).join(',')
changeFilters("controller", value) changeFilters("controller", value)
}} }}
options={controllerValues} options={controllerValues}
/> />
<Divider my={'1'}/> <Separator borderColor="gray.200" my={'1'}/>
<Text mb={'1'} fontSize={'xl'}>Developer</Text> <Text mb={'1'} fontSize={'xl'}>Developer</Text>
<Select <Select
size={'sm'} size={'sm'}
@@ -274,13 +268,13 @@ function FilterBar({filterIsOpen, filterOnClose, btnRef}: Props) {
isClearable isClearable
isMulti isMulti
name="developer" name="developer"
onChange={(event) => { onChange={(event: any) => {
const value = event.map(a => a.value).join(',') const value = event.map((a: any) => a.value).join(',')
changeFilters("developer", value) changeFilters("developer", value)
}} }}
options={tags.developer} options={tags.developer}
/> />
<Divider my={'1'}/> <Separator borderColor="gray.200" my={'1'}/>
<Text mb={'1'} fontSize={'xl'}>Publisher</Text> <Text mb={'1'} fontSize={'xl'}>Publisher</Text>
<Select <Select
size={'sm'} size={'sm'}
@@ -288,13 +282,13 @@ function FilterBar({filterIsOpen, filterOnClose, btnRef}: Props) {
isClearable isClearable
isMulti isMulti
name="publisher" name="publisher"
onChange={(event) => { onChange={(event: any) => {
const value = event.map(a => a.value).join(',') const value = event.map((a: any) => a.value).join(',')
changeFilters("publisher", value) changeFilters("publisher", value)
}} }}
options={tags.publisher} options={tags.publisher}
/> />
<Divider my={'1'}/> <Separator borderColor="gray.200" my={'1'}/>
<Text mb={'1'} fontSize={'xl'}>Do you own the Soundtrack?</Text> <Text mb={'1'} fontSize={'xl'}>Do you own the Soundtrack?</Text>
<Select <Select
size={'sm'} size={'sm'}
@@ -302,13 +296,13 @@ function FilterBar({filterIsOpen, filterOnClose, btnRef}: Props) {
isClearable isClearable
isMulti isMulti
name="soundtrack" name="soundtrack"
onChange={(event) => { onChange={(event: any) => {
const value = event.map(a => a.value).join(',') const value = event.map((a: any) => a.value).join(',')
changeFilters("soundtrack", value) changeFilters("soundtrack", value)
}} }}
options={yesNo} options={yesNo}
/> />
<Divider my={'1'}/> <Separator borderColor="gray.200" my={'1'}/>
<Text mb={'1'} fontSize={'xl'}>Does in work on Intel?</Text> <Text mb={'1'} fontSize={'xl'}>Does in work on Intel?</Text>
<Select <Select
size={'sm'} size={'sm'}
@@ -316,13 +310,13 @@ function FilterBar({filterIsOpen, filterOnClose, btnRef}: Props) {
isClearable isClearable
isMulti isMulti
name="intel" name="intel"
onChange={(event) => { onChange={(event: any) => {
const value = event.map(a => a.value).join(',') const value = event.map((a: any) => a.value).join(',')
changeFilters("intel", value) changeFilters("intel", value)
}} }}
options={wineIntelValues} options={wineIntelValues}
/> />
<Divider my={'1'}/> <Separator borderColor="gray.200" my={'1'}/>
<Text mb={'1'} fontSize={'xl'}>Does is work with Wine?</Text> <Text mb={'1'} fontSize={'xl'}>Does is work with Wine?</Text>
<Select <Select
size={'sm'} size={'sm'}
@@ -330,20 +324,20 @@ function FilterBar({filterIsOpen, filterOnClose, btnRef}: Props) {
isClearable isClearable
isMulti isMulti
name="wine" name="wine"
onChange={(event) => { onChange={(event: any) => {
const value = event.map(a => a.value).join(',') const value = event.map((a: any) => a.value).join(',')
changeFilters("wine", value) changeFilters("wine", value)
}} }}
options={wineIntelValues} options={wineIntelValues}
/> />
<Divider my={'1'}/> <Separator borderColor="gray.200" my={'1'}/>
</DrawerBody> </DrawerBody>
<DrawerFooter> <DrawerFooter>
</DrawerFooter> </DrawerFooter>
</DrawerContent> </DrawerContent>
</Drawer> </DrawerRoot>
) )
} }
+1 -1
View File
@@ -6,7 +6,7 @@ export default function Footer() {
return ( return (
<Box as='footer' role='contentinfo' mx='auto' maxW='7xl' py='12' px={{ base: '4', md: '8' }}> <Box as='footer' role='contentinfo' mx='auto' maxW='7xl' py='12' px={{ base: '4', md: '8' }}>
<Stack> <Stack>
<Stack direction="row" spacing="4" align="center" justify="space-between"> <Stack direction="row" gap="4" align="center" justify="space-between">
</Stack> </Stack>
+13 -11
View File
@@ -4,7 +4,7 @@ import React, { useEffect } from 'react'
import { gameDashboardState } from '../stateManagement/gameState' import { gameDashboardState } from '../stateManagement/gameState'
import { loadingState } from '../stateManagement/loadingState' import { loadingState } from '../stateManagement/loadingState'
import { useHookstate } from '@hookstate/core' import { useHookstate } from '@hookstate/core'
import { Box, Center, Container, Divider, Heading, SimpleGrid, VStack } from '@chakra-ui/react' import { Box, Center, Container, Separator, Heading, SimpleGrid, VStack } from '@chakra-ui/react'
import Pagination from './Pagination' import Pagination from './Pagination'
import { NavLink } from 'react-router-dom' import { NavLink } from 'react-router-dom'
import { adminMode } from '../stateManagement/userState' import { adminMode } from '../stateManagement/userState'
@@ -33,34 +33,36 @@ const GameDashboard = () => {
return ( return (
<> <>
<Container maxW="container.xl" mt="7"> <Container maxW="1280px" mt="7">
<Pagination /> <Pagination />
<ActiveFilters /> <ActiveFilters />
<Divider /> <Separator borderColor="gray.200" />
{!loaded ? <DashboardLoadingModal /> : games.count.totalGames === 0 ? {!loaded ? <DashboardLoadingModal /> : games.count.totalGames === 0 ?
<Center h={'80vh'}> <Center h={'80vh'}>
<VStack spacing={10}> <VStack gap={10}>
<Heading as={'h1'} size={'3xl'}>No Results Found...</Heading> <Heading as={'h1'} size={'3xl'}>No Results Found...</Heading>
<Divider /> <Separator borderColor="gray.200" />
{games.searchParams !== '' ? {games.searchParams !== '' ?
<Box mt='10'> <Box mt='10'>
<ResetFilterButton text='Clear Search and Filters' size='lg' variant='outline' colorScheme='blue' /> <ResetFilterButton text='Clear Search and Filters' size='lg' variant='outline' colorPalette='blue' />
</Box> </Box>
: :
<Box <Box
rounded={'md'} rounded={'md'}
as={NavLink} asChild
border={'1px'} borderWidth={'1px'} borderStyle={'solid'}
p={'3'} p={'3'}
fontSize={'2xl'} fontSize={'2xl'}
to="/games/create"> >
<NavLink to="/games/create">
Create a Game Create a Game
</NavLink>
</Box> </Box>
} }
</VStack> </VStack>
</Center> </Center>
: :
<SimpleGrid columns={{ base: 1, sm: 2, md: 3 }} spacing={10} style={{ marginTop: '3rem' }}> <SimpleGrid columns={{ base: 1, sm: 2, md: 3 }} gap={10} style={{ marginTop: '3rem' }}>
{games.data.map((game) => ( {games.data.map((game) => (
<GameThumbnailContextMenu <GameThumbnailContextMenu
key={game._id} key={game._id}
@@ -68,7 +70,7 @@ const GameDashboard = () => {
game={game} /> game={game} />
))} ))}
</SimpleGrid>} </SimpleGrid>}
<Divider my={'1rem'} /> <Separator borderColor="gray.200" my={'1rem'} />
<Pagination /> <Pagination />
</Container> </Container>
</> </>
+62 -64
View File
@@ -9,27 +9,23 @@ import {
Link, Link,
HStack, HStack,
Stack, Stack,
useColorModeValue,
Menu, Menu,
MenuButton, Portal,
Avatar,
MenuList,
MenuItem,
MenuDivider,
Icon, Icon,
useColorMode,
Text, Text,
Center, Center,
VStack, VStack,
Tooltip, Separator,
Divider,
Heading, Heading,
Kbd, Kbd,
Spacer, Spacer,
useDisclosure, useDisclosure,
useMediaQuery, useMediaQuery,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import { AddIcon, HamburgerIcon, Search2Icon, SettingsIcon } from '@chakra-ui/icons' import {useColorMode, useColorModeValue} from './ui/color-mode'
import {Tooltip} from './ui/tooltip'
import {Avatar} from './ui/avatar'
import { LuPlus, LuMenu, LuSearch, LuSettings } from 'react-icons/lu'
import SearchModal from './SearchModal' import SearchModal from './SearchModal'
import { openAndCloseSearchDialogue } from '../stateManagement/loadingState' import { openAndCloseSearchDialogue } from '../stateManagement/loadingState'
import AdminMode from './authComponents/AdminMode' import AdminMode from './authComponents/AdminMode'
@@ -49,10 +45,10 @@ export default function NavBar() {
const bgLink = useColorModeValue('gray.200', 'gray.700') const bgLink = useColorModeValue('gray.200', 'gray.700')
const location = useLocation() const location = useLocation()
const history = useNavigate() const history = useNavigate()
const { isOpen: mobileIsOpen, onOpen: mobileOnOpen, onClose: mobileOnClose } = useDisclosure() const { open: mobileIsOpen, onOpen: mobileOnOpen, onClose: mobileOnClose } = useDisclosure()
const { isOpen: filterIsOpen, onOpen: filterOnOpen, onClose: filterOnClose } = useDisclosure() const { open: filterIsOpen, onOpen: filterOnOpen, onClose: filterOnClose } = useDisclosure()
const btnRef = React.useRef(null) const btnRef = React.useRef(null)
const [isSmallerThan768] = useMediaQuery('(max-width: 768px)') const [isSmallerThan768] = useMediaQuery(['(max-width: 768px)'])
const handleKeyPress = useCallback((event: any) => { const handleKeyPress = useCallback((event: any) => {
if (event.ctrlKey === true && event.key === ',') { if (event.ctrlKey === true && event.key === ',') {
@@ -95,13 +91,13 @@ export default function NavBar() {
position={loadedPreferences.get().stickyNav ? "fixed" : "relative"} position={loadedPreferences.get().stickyNav ? "fixed" : "relative"}
justifyContent={'space-between'} justifyContent={'space-between'}
> >
<HStack spacing={8} alignItems={'center'}> <HStack gap={8} alignItems={'center'}>
<Box as={RouteLink} to="/"> <Box asChild>
Game Database <RouteLink to="/">Game Database</RouteLink>
</Box> </Box>
<HStack <HStack
as={'nav'} as={'nav'}
spacing={4} gap={4}
display={{ base: 'none', md: 'flex' }} display={{ base: 'none', md: 'flex' }}
> >
<Link <Link
@@ -112,9 +108,8 @@ export default function NavBar() {
textDecoration: 'none', textDecoration: 'none',
bg: bgLink, bg: bgLink,
}} }}
as={RouteLink} asChild>
to="/games"> <RouteLink to="/games">Games</RouteLink>
Games
</Link> </Link>
<Link <Link
px={2} px={2}
@@ -124,9 +119,8 @@ export default function NavBar() {
textDecoration: 'none', textDecoration: 'none',
bg: bgLink, bg: bgLink,
}} }}
as={RouteLink} asChild>
to="/games/create"> <RouteLink to="/games/create">Create Game</RouteLink>
Create Game
</Link> </Link>
</HStack> </HStack>
</HStack> </HStack>
@@ -135,7 +129,7 @@ export default function NavBar() {
{isSmallerThan768 ? {isSmallerThan768 ?
<> <>
<Button ref={btnRef} onMouseDown={mobileIsOpen ? mobileOnClose : mobileOnOpen}> <Button ref={btnRef} onMouseDown={mobileIsOpen ? mobileOnClose : mobileOnOpen}>
<HamburgerIcon/> <LuMenu/>
</Button> </Button>
<MobileBar mobileIsOpen={mobileIsOpen} mobileOnClose={mobileOnClose} btnRef={btnRef} bgLink={bgLink} <MobileBar mobileIsOpen={mobileIsOpen} mobileOnClose={mobileOnClose} btnRef={btnRef} bgLink={bgLink}
user={user} colorMode={colorMode} toggleColorMode={toggleColorMode}/> user={user} colorMode={colorMode} toggleColorMode={toggleColorMode}/>
@@ -144,7 +138,7 @@ export default function NavBar() {
<Stack direction={'row'}> <Stack direction={'row'}>
{ user.role === 'admin' && <AdminMode/>} { user.role === 'admin' && <AdminMode/>}
<Tooltip hasArrow mr={'2'} label={'Filters'} aria-label={'Filters'}> <Tooltip showArrow content={'Filters'}>
<Button <Button
rounded={'full'} rounded={'full'}
bg={'transparent'} bg={'transparent'}
@@ -164,7 +158,7 @@ export default function NavBar() {
</Tooltip> </Tooltip>
<FilterBar filterIsOpen={filterIsOpen} filterOnClose={filterOnClose} btnRef={btnRef} filterLoadingState={filterLoadingState} /> <FilterBar filterIsOpen={filterIsOpen} filterOnClose={filterOnClose} btnRef={btnRef} filterLoadingState={filterLoadingState} />
<Tooltip hasArrow mr={'2'} label={'Search: ctrl+s'} aria-label={'Search'}> <Tooltip showArrow content={'Search: ctrl+s'}>
<Button <Button
rounded={'full'} rounded={'full'}
onMouseDown={() => { onMouseDown={() => {
@@ -173,64 +167,66 @@ export default function NavBar() {
bg={'transparent'} bg={'transparent'}
cursor={'pointer'} cursor={'pointer'}
minW={0}> minW={0}>
<Search2Icon/> <LuSearch/>
</Button> </Button>
</Tooltip> </Tooltip>
<Menu> <Menu.Root>
<Tooltip hasArrow mr={'2'} label={'Settings'} aria-label={'Settings'}> <Menu.Trigger asChild>
<MenuButton <Button
as={Button}
rounded={'full'} rounded={'full'}
variant={'transparent'} variant={'ghost'}
cursor={'pointer'} cursor={'pointer'}
minW={0}> minW={0}>
<SettingsIcon/> <Tooltip showArrow content={'Settings'}>
</MenuButton> <LuSettings/>
</Tooltip> </Tooltip>
<MenuList alignItems={'center'}> </Button>
<VStack> </Menu.Trigger>
{/*<Button onMouseDown={toggleColorMode} w={'70%'}>*/} <Portal>
{/* {colorMode === 'light' ? <MoonIcon/> : <SunIcon/>}*/} <Menu.Positioner>
{/* {colorMode === 'light' ?*/} <Menu.Content p={8}>
{/* <Text mt={'1'} ml={'2'}>Dark</Text> :*/} <VStack alignItems={'center'}>
{/* <Text mt={'1'} ml={'2'}>Light</Text>}*/}
{/*</Button>*/}
<Button <Button
variant={'solid'} variant={'solid'}
colorScheme={'blue'} colorPalette={'blue'}
size={'md'} size={'md'}
leftIcon={<AddIcon/>} asChild
as={RouteLink}
w={'70%'} w={'70%'}
to="/games/create"> >
<Text mt="1">Create Game</Text> <RouteLink to="/games/create"><LuPlus/> <Text mt="1">Create Game</Text></RouteLink>
</Button> </Button>
<Divider/> <Separator/>
<Heading as={'h3'} size={'md'}>Keyboard Shortcuts</Heading> <Heading as={'h3'} size={'md'}>Keyboard Shortcuts</Heading>
<HStack><Text fontSize={'sm'}>Search:</Text><span><Kbd>ctrl</Kbd> + <Kbd>,</Kbd></span></HStack> <HStack><Text fontSize={'sm'}>Search:</Text><span><Kbd>ctrl</Kbd> + <Kbd>,</Kbd></span></HStack>
<HStack><Text fontSize={'sm'}>Games Grid:</Text><span><Kbd>ctrl</Kbd> + <Kbd>.</Kbd></span></HStack> <HStack><Text fontSize={'sm'}>Games Grid:</Text><span><Kbd>ctrl</Kbd> + <Kbd>.</Kbd></span></HStack>
<HStack><Text fontSize={'sm'}>Create <HStack><Text fontSize={'sm'}>Create
Game:</Text><span><Kbd>ctrl</Kbd> + <Kbd>/</Kbd></span></HStack> Game:</Text><span><Kbd>ctrl</Kbd> + <Kbd>/</Kbd></span></HStack>
</VStack> </VStack>
</MenuList> </Menu.Content>
</Menu> </Menu.Positioner>
<Menu> </Portal>
<Tooltip hasArrow mr={'2'} label={'User Info and Logout'} aria-label={'User Info'}> </Menu.Root>
<MenuButton <Menu.Root>
as={Button} <Menu.Trigger asChild>
<Button
rounded={'full'} rounded={'full'}
variant={'transparent'} variant={'ghost'}
cursor={'pointer'} cursor={'pointer'}
minW={0}> minW={0}>
<Avatar size={'sm'} src={user.avatar}/> <Tooltip showArrow content={'User Info and Logout'}>
</MenuButton> <Avatar size={'sm'} src={user.avatar} name={user.displayName} />
</Tooltip> </Tooltip>
<MenuList alignItems={'center'}> </Button>
</Menu.Trigger>
<Portal>
<Menu.Positioner>
<Menu.Content p={8}>
<br/> <br/>
<Center> <Center>
<Avatar <Avatar
size={'2xl'} size={'2xl'}
src={user.avatar} src={user.avatar}
name={user.displayName}
/> />
</Center> </Center>
<br/> <br/>
@@ -238,12 +234,14 @@ export default function NavBar() {
<p>{user.displayName}</p> <p>{user.displayName}</p>
</Center> </Center>
<br/> <br/>
<MenuDivider/> <Menu.Separator/>
<MenuItem onClick={() => history('/profile')}>Preferences</MenuItem> <Menu.Item value="preferences" onSelect={() => history('/profile')}>Preferences</Menu.Item>
<MenuItem onClick={() => logout()}><Icon as={MdOutlinePowerSettingsNew} mr={'2'}/> <Text <Menu.Item value="logout" onSelect={() => logout()}><Icon as={MdOutlinePowerSettingsNew} mr={'2'}/> <Text
mt={'1'}>Logout</Text></MenuItem> mt={'1'}>Logout</Text></Menu.Item>
</MenuList> </Menu.Content>
</Menu> </Menu.Positioner>
</Portal>
</Menu.Root>
</Stack> </Stack>
} }
</Flex> </Flex>
+4 -4
View File
@@ -4,13 +4,13 @@ import { VStack, Image, Button, Heading, Flex, Center } from '@chakra-ui/react'
const Home = () => { const Home = () => {
return ( return (
<Flex align={'center'} bgGradient={'linear(to-br, #314755, #26a0da)'} height={'100vh'}> <Flex align={'center'} bgImage={'linear-gradient(to bottom right, #314755, #26a0da)'} height={'100vh'}>
<Center w={'100%'}> <Center w={'100%'}>
<VStack spacing={4} align={'center'} margin={'auto'}> <VStack gap={4} align={'center'} margin={'auto'}>
<Image src="/assets/Logo.svg" alt="Games Database Logo"/> <Image src="/assets/Logo.svg" alt="Games Database Logo"/>
<Heading as="h2" fontSize={'1.7em'} fontWeight={'normal'}>Welcome to your Games Database</Heading> <Heading as="h2" fontSize={'1.7em'} fontWeight={'normal'}>Welcome to your Games Database</Heading>
<Button as={Link} to="/login" size='lg' variant='outline' fontSize='1.25rem'> <Button asChild size='lg' variant='outline' fontSize='1.25rem'>
Login <Link to="/login">Login</Link>
</Button> </Button>
</VStack> </VStack>
</Center> </Center>
+7 -2
View File
@@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import { Center, CircularProgress } from '@chakra-ui/react' import { Center, ProgressCircle } from '@chakra-ui/react'
export default function LoadingModal() { export default function LoadingModal() {
@@ -14,7 +14,12 @@ export default function LoadingModal() {
bottom={'0'} bottom={'0'}
left={'0'} left={'0'}
> >
<CircularProgress isIndeterminate color="blue.500"/> <ProgressCircle.Root value={null} size="lg">
<ProgressCircle.Circle>
<ProgressCircle.Track />
<ProgressCircle.Range stroke="blue.500" />
</ProgressCircle.Circle>
</ProgressCircle.Root>
</Center> </Center>
</> </>
) )
+31 -43
View File
@@ -1,19 +1,18 @@
import React, { MutableRefObject } from 'react' import React, { MutableRefObject } from 'react'
import { import {
Avatar, Button, Center, ColorMode, Button, Center,
Divider, Drawer, Separator,
DrawerBody, Drawer,
DrawerCloseButton, Icon,
DrawerContent, DrawerFooter,
DrawerHeader,
DrawerOverlay, Icon,
Image, Link, Image, Link,
Stack, Text, Stack, Text,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import {DrawerBody, DrawerCloseTrigger, DrawerContent, DrawerFooter, DrawerHeader, DrawerRoot} from './ui/drawer'
import {Avatar} from './ui/avatar'
import AdminMode from './authComponents/AdminMode' import AdminMode from './authComponents/AdminMode'
import { NavLink } from 'react-router-dom' import { NavLink } from 'react-router-dom'
import Search from './Search' import Search from './Search'
import { AddIcon } from '@chakra-ui/icons' import { LuPlus } from 'react-icons/lu'
import { logout } from '../stateManagement/userState' import { logout } from '../stateManagement/userState'
import User from '../models/user' import User from '../models/user'
import { AiOutlineLogout } from 'react-icons/ai' import { AiOutlineLogout } from 'react-icons/ai'
@@ -21,12 +20,13 @@ import {useHookstate} from "@hookstate/core";
import {searchCompletedState} from "../stateManagement/loadingState"; import {searchCompletedState} from "../stateManagement/loadingState";
import {gameDashboardState} from "../stateManagement/gameState"; import {gameDashboardState} from "../stateManagement/gameState";
import ResetFilterButton from "./helperComponents/ResetFilterButton"; import ResetFilterButton from "./helperComponents/ResetFilterButton";
import type { ColorMode } from './ui/color-mode'
interface Props { interface Props {
mobileIsOpen: boolean mobileIsOpen: boolean
mobileOnClose: () => void mobileOnClose: () => void
btnRef: MutableRefObject<null> btnRef: MutableRefObject<null>
bgLink: "gray.200" | "gray.700" bgLink: string
user: User user: User
colorMode: ColorMode colorMode: ColorMode
toggleColorMode: () => void toggleColorMode: () => void
@@ -37,15 +37,14 @@ function MobileBar({ mobileIsOpen, mobileOnClose, btnRef, bgLink, user }: Props)
const gamesState = useHookstate(gameDashboardState) const gamesState = useHookstate(gameDashboardState)
const games = gamesState.get() const games = gamesState.get()
return ( return (
<Drawer <DrawerRoot
isOpen={mobileIsOpen} open={mobileIsOpen}
placement="right" placement="end"
onClose={mobileOnClose} onOpenChange={(e) => { if (!e.open) mobileOnClose() }}
finalFocusRef={btnRef} finalFocusEl={() => btnRef.current as unknown as HTMLElement}
> >
<DrawerOverlay/>
<DrawerContent> <DrawerContent>
<DrawerCloseButton/> <DrawerCloseTrigger/>
<DrawerHeader> <DrawerHeader>
<Image maxW="15rem" src="/assets/Logo.svg" alt="Games Database Logo"/> <Image maxW="15rem" src="/assets/Logo.svg" alt="Games Database Logo"/>
</DrawerHeader> </DrawerHeader>
@@ -54,7 +53,7 @@ function MobileBar({ mobileIsOpen, mobileOnClose, btnRef, bgLink, user }: Props)
<AdminMode /> <AdminMode />
<Stack <Stack
as={'nav'} as={'nav'}
spacing={4} gap={4}
> >
<Link <Link
px={2} px={2}
@@ -64,9 +63,8 @@ function MobileBar({ mobileIsOpen, mobileOnClose, btnRef, bgLink, user }: Props)
textDecoration: 'none', textDecoration: 'none',
bg: bgLink, bg: bgLink,
}} }}
as={NavLink} asChild>
to="/games"> <NavLink to="/games"><Text textAlign={'right'}>Games</Text></NavLink>
<Text align={'right'}>Games</Text>
</Link> </Link>
<Link <Link
px={2} px={2}
@@ -76,15 +74,14 @@ function MobileBar({ mobileIsOpen, mobileOnClose, btnRef, bgLink, user }: Props)
textDecoration: 'none', textDecoration: 'none',
bg: bgLink, bg: bgLink,
}} }}
as={NavLink} asChild>
to="/games/create"> <NavLink to="/games/create"><Text textAlign={'right'}>Create Game</Text></NavLink>
<Text align={'right'}>Create Game</Text>
</Link> </Link>
<Divider/> <Separator/>
<Search/> <Search/>
{searchCompleted.get() && <Text>Games Found: {games.count.totalGames}</Text>} {searchCompleted.get() && <Text>Games Found: {games.count.totalGames}</Text>}
<Divider/> <Separator/>
<Center><Avatar mr={'2'} size={'sm'} src={user.avatar}/><Text mt={1}>{user.displayName}</Text></Center> <Center><Avatar mr={'2'} size={'sm'} src={user.avatar} name={user.displayName}/><Text mt={1}>{user.displayName}</Text></Center>
<Link <Link
px={2} px={2}
py={1} py={1}
@@ -93,18 +90,10 @@ function MobileBar({ mobileIsOpen, mobileOnClose, btnRef, bgLink, user }: Props)
textDecoration: 'none', textDecoration: 'none',
bg: bgLink, bg: bgLink,
}} }}
as={NavLink} asChild>
to="/profile"> <NavLink to="/profile"><Text textAlign={'right'}>Preferences</Text></NavLink>
<Text align={'right'}>Preferences</Text>
</Link> </Link>
<Divider/> <Separator/>
{/*<Button onMouseDown={toggleColorMode} ml={'auto'}>*/}
{/* {colorMode === 'light' ? <MoonIcon/> : <SunIcon/>}*/}
{/* {colorMode === 'light' ?*/}
{/* <Text mt={'1'} ml={'2'}>Dark</Text> :*/}
{/* <Text mt={'1'} ml={'2'}>Light</Text>}*/}
{/*</Button>*/}
{/*<Divider/>*/}
<ResetFilterButton /> <ResetFilterButton />
</Stack> </Stack>
@@ -114,20 +103,19 @@ function MobileBar({ mobileIsOpen, mobileOnClose, btnRef, bgLink, user }: Props)
<DrawerFooter> <DrawerFooter>
<Button <Button
variant={'solid'} variant={'solid'}
colorScheme={'blue'} colorPalette={'blue'}
size={'md'} size={'md'}
leftIcon={<AddIcon/>} asChild
as={NavLink}
mr={3} mr={3}
to="/games/create"> >
<Text mt="1">Create Game</Text> <NavLink to="/games/create"><LuPlus/> <Text mt="1">Create Game</Text></NavLink>
</Button> </Button>
<Button onMouseDown={() => logout()}><Icon as={AiOutlineLogout} mr={'2'}/> <Text <Button onMouseDown={() => logout()}><Icon as={AiOutlineLogout} mr={'2'}/> <Text
mt={'1'}>Logout</Text></Button> mt={'1'}>Logout</Text></Button>
</DrawerFooter> </DrawerFooter>
</DrawerContent> </DrawerContent>
</Drawer> </DrawerRoot>
) )
} }
+4 -4
View File
@@ -4,12 +4,12 @@ import { Center, Flex, Heading, Button, VStack } from '@chakra-ui/react'
export default function NoGames() { export default function NoGames() {
return ( return (
<Flex align={'center'} bgGradient={'linear(to-br, #314755, #26a0da)'} height={'100vh'}> <Flex align={'center'} bgImage={'linear-gradient(to bottom right, #314755, #26a0da)'} height={'100vh'}>
<Center w={'100%'}> <Center w={'100%'}>
<VStack spacing={4} align={'center'} margin={'auto'}> <VStack gap={4} align={'center'} margin={'auto'}>
{<Heading as="h2">You have not added any games yet.</Heading>} {<Heading as="h2">You have not added any games yet.</Heading>}
<Button as={Link} to='/games/create' size='lg' variant='outline' fontSize='1.25rem'> <Button asChild size='lg' variant='outline' fontSize='1.25rem'>
Click here to add some! <Link to='/games/create'>Click here to add some!</Link>
</Button> </Button>
</VStack> </VStack>
</Center> </Center>
+50 -42
View File
@@ -4,24 +4,21 @@ import {
Grid, Grid,
GridItem, GridItem,
IconButton, IconButton,
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput,
NumberInputField,
NumberInputStepper,
Select,
Text, Text,
Tooltip, Portal,
useColorModeValue,
useMediaQuery,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import { ArrowLeftIcon, ArrowRightIcon, ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons' import {NumberInputRoot, NumberInputField} from './ui/number-input'
import { NativeSelect } from '@chakra-ui/react'
import { LuChevronLeft, LuChevronRight, LuChevronsLeft, LuChevronsRight } from 'react-icons/lu'
import {Tooltip} from './ui/tooltip'
import { useHookstate } from '@hookstate/core' import { useHookstate } from '@hookstate/core'
import { gameDashboardState } from '../stateManagement/gameState' import { gameDashboardState } from '../stateManagement/gameState'
import { loadingState } from '../stateManagement/loadingState' import { loadingState } from '../stateManagement/loadingState'
import { adminMode } from '../stateManagement/userState' import { adminMode } from '../stateManagement/userState'
import getWithFilters from '../componentUtils/getWithFilters' import getWithFilters from '../componentUtils/getWithFilters'
import { ratingState, steamRatingState } from "../stateManagement/ratingState"; import { ratingState, steamRatingState } from "../stateManagement/ratingState";
import {useColorModeValue} from "./ui/color-mode";
import {useMediaQuery} from "@chakra-ui/react";
export default function Pagination() { export default function Pagination() {
const gamesState = useHookstate(gameDashboardState) const gamesState = useHookstate(gameDashboardState)
@@ -34,7 +31,7 @@ export default function Pagination() {
const bg = useColorModeValue('gray.50', 'transparent') const bg = useColorModeValue('gray.50', 'transparent')
const useRatingState = useHookstate(ratingState).get() const useRatingState = useHookstate(ratingState).get()
const useSteamRatingState = useHookstate(steamRatingState).get() const useSteamRatingState = useHookstate(steamRatingState).get()
const [isSmallerThan768] = useMediaQuery('(max-width: 768px)') const [isSmallerThan768] = useMediaQuery(['(max-width: 768px)'])
const setPageSize = async (limit: number) => await getWithFilters( const setPageSize = async (limit: number) => await getWithFilters(
gamesState, gamesState,
@@ -99,22 +96,24 @@ export default function Pagination() {
}}> }}>
<GridItem area={"backArrows"}> <GridItem area={"backArrows"}>
<Flex> <Flex>
<Tooltip label="First Page"> <Tooltip content="First Page">
<IconButton <IconButton
aria-label={'First Page'} aria-label={'First Page'}
onMouseDown={() => gotoPage(0)} onMouseDown={() => gotoPage(0)}
isDisabled={pageIndex <= 1} disabled={pageIndex <= 1}
icon={<ArrowLeftIcon h={3} w={3} />}
mr={4} mr={4}
/> >
<LuChevronsLeft />
</IconButton>
</Tooltip> </Tooltip>
<Tooltip label="Previous Page"> <Tooltip content="Previous Page">
<IconButton <IconButton
aria-label={'Previous Page'} aria-label={'Previous Page'}
onMouseDown={previousPage} onMouseDown={previousPage}
isDisabled={pageIndex <= 1} disabled={pageIndex <= 1}
icon={<ChevronLeftIcon h={6} w={6} />} >
/> <LuChevronLeft />
</IconButton>
</Tooltip> </Tooltip>
</Flex> </Flex>
</GridItem> </GridItem>
@@ -133,39 +132,44 @@ export default function Pagination() {
{!isSmallerThan768 && {!isSmallerThan768 &&
<GridItem area={"goToPage"}> <GridItem area={"goToPage"}>
<Text mb={'1'}>Go to Page:</Text> <Text mb={'1'}>Go to Page:</Text>
<NumberInput <NumberInputRoot
bg={bg} bg={bg}
rounded={'md'} rounded={'md'}
w={{ base: 20, md: 28 }} w={{ base: 20, md: 28 }}
min={1} min={1}
max={pageCount} max={pageCount}
value={pageIndex} value={String(pageIndex)}
onChange={(value) => { onValueChange={(e) => {
const page = value ? Number(value) : 1 const page = e.value ? Number(e.value) : 1
gotoPage(page) gotoPage(page)
}} }}
> >
<NumberInputField /> <NumberInputField
<NumberInputStepper> borderWidth="1px"
<NumberIncrementStepper /> borderStyle="solid"
<NumberDecrementStepper /> borderColor={{ base: "gray.500", _invalid: "red.500" }}
</NumberInputStepper> />
</NumberInput> </NumberInputRoot>
</GridItem> </GridItem>
} }
<GridItem area={"countPerPage"}> <GridItem area={"countPerPage"}>
<Text mb={'1'}>Count Per Page:</Text> <Text mb={'1'}>Count Per Page:</Text>
<Select <NativeSelect.Root
ml={{ ml={{
base: '4', base: '4',
md: '0' md: '0'
}} }}
bg={bg}
rounded={'md'}
w={{ base: 20, md: 32 }} w={{ base: 20, md: 32 }}
>
<NativeSelect.Field
rounded={'md'}
borderWidth={'1px'}
borderStyle={'solid'}
borderColor={'gray.500'}
bg={bg}
value={pageSize} value={pageSize}
onChange={(e) => { onChange={(e: any) => {
setPageSize(Number(e.target.value)) setPageSize(Number(e.target.value))
}} }}
> >
@@ -174,29 +178,33 @@ export default function Pagination() {
{pageSize} {pageSize}
</option> </option>
))} ))}
</Select> </NativeSelect.Field>
<NativeSelect.Indicator />
</NativeSelect.Root>
</GridItem> </GridItem>
<GridItem area={'gamesFound'}> <GridItem area={'gamesFound'}>
<Text>Games Found: {games.count.totalGames}</Text> <Text>Games Found: {games.count.totalGames}</Text>
</GridItem> </GridItem>
<GridItem area={"forwardArrows"}> <GridItem area={"forwardArrows"}>
<Flex justifySelf={'end'}> <Flex justifySelf={'end'}>
<Tooltip label="Next Page"> <Tooltip content="Next Page">
<IconButton <IconButton
aria-label={'Next Page'} aria-label={'Next Page'}
onMouseDown={nextPage} onMouseDown={nextPage}
isDisabled={pageIndex >= pageCount} disabled={pageIndex >= pageCount}
icon={<ChevronRightIcon h={6} w={6} />} >
/> <LuChevronRight />
</IconButton>
</Tooltip> </Tooltip>
<Tooltip label="Last Page"> <Tooltip content="Last Page">
<IconButton <IconButton
aria-label={'Last Page'} aria-label={'Last Page'}
onMouseDown={() => gotoPage(pageCount)} onMouseDown={() => gotoPage(pageCount)}
isDisabled={pageIndex >= pageCount} disabled={pageIndex >= pageCount}
icon={<ArrowRightIcon h={3} w={3} />}
ml={4} ml={4}
/> >
<LuChevronsRight />
</IconButton>
</Tooltip> </Tooltip>
</Flex> </Flex>
</GridItem> </GridItem>
+13 -13
View File
@@ -1,6 +1,7 @@
import React, { FormEvent } from 'react' import React, { FormEvent } from 'react'
import { Button, FormControl, Input, InputGroup, InputRightElement, useColorModeValue } from '@chakra-ui/react' import { Button, Input, Group } from '@chakra-ui/react'
import { SearchIcon } from '@chakra-ui/icons' import { Field } from './ui/field'
import { LuSearch } from 'react-icons/lu'
import { GameList } from '../models/game' import { GameList } from '../models/game'
import { useHookstate } from '@hookstate/core' import { useHookstate } from '@hookstate/core'
import { gameDashboardState } from '../stateManagement/gameState' import { gameDashboardState } from '../stateManagement/gameState'
@@ -14,6 +15,7 @@ import { adminMode } from '../stateManagement/userState'
import getWithFilters from '../componentUtils/getWithFilters' import getWithFilters from '../componentUtils/getWithFilters'
import {ChangeActiveFilterHeader} from "../componentUtils/changeActiveFilterHeader"; import {ChangeActiveFilterHeader} from "../componentUtils/changeActiveFilterHeader";
import {ratingState, steamRatingState} from "../stateManagement/ratingState"; import {ratingState, steamRatingState} from "../stateManagement/ratingState";
import {useColorModeValue} from "./ui/color-mode";
export default function Search() { export default function Search() {
const gamesState = useHookstate(gameDashboardState) const gamesState = useHookstate(gameDashboardState)
@@ -54,16 +56,16 @@ export default function Search() {
return ( return (
<> <>
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<FormControl mb="3"> <Field mb="3">
<InputGroup> <Group attached w="full">
<Input <Input
autoFocus autoFocus
id="searchParams" id="searchParams"
name="searchParams" name="searchParams"
bg={bg} bg={bg}
type="text" type="text"
border="1px" borderWidth="1px" borderStyle="solid"
borderColor="gray.500" borderColor={{ base: "gray.500", _invalid: "red.500" }}
rounded="md" rounded="md"
placeholder="Search..." placeholder="Search..."
value={games.searchParams} value={games.searchParams}
@@ -71,17 +73,15 @@ export default function Search() {
handleInputChange('searchParams', e.target.value) handleInputChange('searchParams', e.target.value)
}} }}
/> />
<InputRightElement>
<Button <Button
isLoading={submitLoading} loading={submitLoading}
type="submit" type="submit"
colorScheme="blue" colorPalette="blue"
> >
<SearchIcon/> <LuSearch/>
</Button> </Button>
</InputRightElement> </Group>
</InputGroup> </Field>
</FormControl>
</form> </form>
</> </>
) )
+10 -12
View File
@@ -1,8 +1,6 @@
import React from 'react' import React from 'react'
import { import { Dialog } from '@chakra-ui/react'
Modal, ModalBody, ModalCloseButton, ModalContent, import {DialogBody, DialogCloseTrigger, DialogContent, DialogHeader, DialogRoot} from './ui/dialog'
ModalHeader,
} from '@chakra-ui/react'
import { useHookstate } from '@hookstate/core' import { useHookstate } from '@hookstate/core'
import { openAndCloseSearchDialogue } from '../stateManagement/loadingState' import { openAndCloseSearchDialogue } from '../stateManagement/loadingState'
import Search from './Search' import Search from './Search'
@@ -16,14 +14,14 @@ export default function SearchModal() {
} }
return ( return (
<Modal isOpen={isOpen} onClose={handleClose}> <DialogRoot open={isOpen} onOpenChange={(e) => { if (!e.open) handleClose() }}>
<ModalContent> <DialogContent>
<ModalHeader>Search</ModalHeader> <DialogHeader>Search</DialogHeader>
<ModalCloseButton onMouseDown={() => handleClose()}/> <DialogCloseTrigger onClick={() => handleClose()}/>
<ModalBody> <DialogBody>
<Search /> <Search />
</ModalBody> </DialogBody>
</ModalContent> </DialogContent>
</Modal> </DialogRoot>
) )
} }
@@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import { Center, CircularProgress } from '@chakra-ui/react' import { Center, ProgressCircle } from '@chakra-ui/react'
export default function DashboardLoadingModal() { export default function DashboardLoadingModal() {
@@ -9,7 +9,12 @@ export default function DashboardLoadingModal() {
w="100%" w="100%"
mt={'1rem'} mt={'1rem'}
> >
<CircularProgress isIndeterminate color="blue.500"/> <ProgressCircle.Root value={null} size="md">
<ProgressCircle.Circle>
<ProgressCircle.Track />
<ProgressCircle.Range />
</ProgressCircle.Circle>
</ProgressCircle.Root>
</Center> </Center>
</> </>
) )
@@ -9,12 +9,12 @@ import { ratingState, steamRatingState } from "../../stateManagement/ratingState
interface Props { interface Props {
text?: string text?: string
size?: string size?: "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl"
variant?: string variant?: "solid" | "subtle" | "surface" | "outline" | "ghost" | "plain"
colorScheme?: string colorPalette?: string
} }
const ResetFilterButton = ({ text = "Reset Search and Filters", size = 'md', variant = 'solid', colorScheme = 'gray' }: Props) => { const ResetFilterButton = ({ text = "Reset Search and Filters", size = 'md', variant = 'solid', colorPalette = 'gray' }: Props) => {
const gameState = useHookstate(gameDashboardState) const gameState = useHookstate(gameDashboardState)
const games = gameState.get() const games = gameState.get()
const admin = useHookstate(adminMode).get() const admin = useHookstate(adminMode).get()
@@ -58,7 +58,7 @@ const ResetFilterButton = ({ text = "Reset Search and Filters", size = 'md', var
} }
return ( return (
<Button onMouseDown={resetFilters} size={size} variant={variant} colorScheme={colorScheme}> <Button onMouseDown={resetFilters} size={size} variant={variant} colorPalette={colorPalette}>
{text} {text}
</Button> </Button>
) )
@@ -9,16 +9,18 @@ const SteamAndDeckLogo = (props: ISteamAndDeckLogo) => {
return ( return (
<div> <div>
<Box <Box
as="svg" asChild
role="img"
width={props.size} width={props.size}
height={props.size} height={props.size}
_hover={{
fill: "blue.500",
}}
>
<svg
role="img"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1080 1080" viewBox="0 0 1080 1080"
fill="#1f2127" fill="#1f2127"
_hover={{
fill: "brand.accent",
}}
> >
<title>SteamAndDeckLogo</title> <title>SteamAndDeckLogo</title>
<path <path
@@ -97,6 +99,7 @@ const SteamAndDeckLogo = (props: ISteamAndDeckLogo) => {
<stop offset="1" stopColor="#00A6FF" stopOpacity="0.6"></stop> <stop offset="1" stopColor="#00A6FF" stopOpacity="0.6"></stop>
</linearGradient> </linearGradient>
</defs> </defs>
</svg>
</Box> </Box>
</div> </div>
); );