diff --git a/bun.lockb b/bun.lockb
index e615322..6c5f890 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index b225db5..9b01bf8 100644
--- a/package.json
+++ b/package.json
@@ -3,31 +3,27 @@
"version": "0.1.0",
"private": true,
"dependencies": {
- "@chakra-ui/icons": "^2.1.1",
- "@chakra-ui/react": "^2.8.2",
- "@emotion/react": "^11.13.3",
- "@emotion/styled": "^11.13.0",
- "@hookstate/core": "^4.0.1",
+ "@chakra-ui/react": "^3.37.0",
+ "@emotion/react": "^11.14.0",
+ "@hookstate/core": "^4.0.2",
"@hookstate/localstored": "^4.0.2",
- "axios": "1.7.5",
+ "axios": "^1.20.0",
"bson-objectid": "^2.0.4",
- "chakra-react-select": "^4.9.1",
- "chakra-ui-contextmenu": "^1.0.5",
- "framer-motion": "^11.3.30",
- "interweave": "^13.1.0",
- "jodit-react": "^4.1.2",
- "js-base64": "^3.7.7",
- "react": "^18.3.1",
- "react-device-detect": "^2.2.3",
- "react-dom": "^18.3.1",
- "react-hook-form": "^7.53.0",
- "react-icons": "^5.3.0",
+ "chakra-react-select": "^6.1.3",
+ "interweave": "^13.1.1",
+ "jodit-react": "^5.5.3",
+ "js-base64": "^3.9.3",
+ "next-themes": "^0.4.6",
+ "react": "^19.2.8",
+ "react-dom": "^19.2.8",
+ "react-hook-form": "^7.87.0",
+ "react-icons": "^5.7.0",
"react-rating": "^2.0.5",
"react-responsive-carousel": "^3.2.23",
- "react-router-dom": "^6.26.1",
- "react-select": "^5.8.0",
- "react-toastify": "^10.0.5",
- "web-vitals": "^4.2.3"
+ "react-router-dom": "^7.18.3",
+ "react-select": "^5.10.2",
+ "react-toastify": "^11.1.0",
+ "web-vitals": "^6.2.1"
},
"scripts": {
"start": "bunx --bun vite",
@@ -42,8 +38,8 @@
]
},
"resolutions": {
- "@types/react": "18.2.21",
- "@types/react-dom": "18.2.7"
+ "@types/react": "19.2.18",
+ "@types/react-dom": "19.2.7"
},
"browserslist": {
"production": [
@@ -58,18 +54,18 @@
]
},
"devDependencies": {
- "@chakra-ui/cli": "^2.4.1",
- "@hookstate/devtools": "^4.0.1",
- "@testing-library/jest-dom": "^6.5.0",
- "@testing-library/react": "^16.0.0",
- "@testing-library/user-event": "^14.5.2",
- "@types/jest": "^29.5.12",
- "@types/node": "^22.5.1",
- "@types/react": "^18.2.21",
- "@types/react-dom": "^18.2.7",
+ "@chakra-ui/cli": "^3.37.0",
+ "@hookstate/devtools": "^4.0.3",
+ "@testing-library/jest-dom": "^7.0.1",
+ "@testing-library/react": "^16.3.3",
+ "@testing-library/user-event": "^14.6.7",
+ "@types/jest": "^30.0.0",
+ "@types/node": "^26.4.1",
+ "@types/react": "^19.2.18",
+ "@types/react-dom": "^19.2.7",
"@types/react-table": "^7.7.20",
- "@vitejs/plugin-react": "^4.3.1",
- "typescript": "^5.5.4",
- "vite": "^5.4.2"
+ "@vitejs/plugin-react": "^6.1.1",
+ "typescript": "^5.9.3",
+ "vite": "^8.2.2"
}
}
diff --git a/src/App.test.tsx b/src/App.test.tsx
index 2a68616..c05057a 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -1,9 +1,17 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
-import App from './App';
+import { MemoryRouter } from 'react-router-dom';
+import { Provider } from './components/ui/provider';
+import Home from './components/Home';
-test('renders learn react link', () => {
- render();
- const linkElement = screen.getByText(/learn react/i);
- expect(linkElement).toBeInTheDocument();
+test('renders welcome message', () => {
+ render(
+
+
+
+
+
+ );
+ const heading = screen.getByText(/Welcome to your Games Database/i);
+ expect(heading).toBeInTheDocument();
});
diff --git a/src/App.tsx b/src/App.tsx
index 45e72cb..d93755b 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -23,7 +23,8 @@ import ForgotPassword from './components/authComponents/ForgotPassword'
import ResetPassword from './components/authComponents/ResetPassword'
import UserProfile from './components/userComponents/UserProfile'
import EditUser from './components/userComponents/EditUser'
-import {Box, useColorMode} from "@chakra-ui/react";
+import {Box} from "@chakra-ui/react";
+import {useColorMode} from "./components/ui/color-mode";
import globalRouter from "./api/globalRouter";
function App() {
@@ -44,8 +45,10 @@ function App() {
if (!appLoad)
useEffect(() => {
- if(loadedPreferences.get().theme !== colorMode) {
+ const preferredTheme = loadedPreferences.get().theme
+ if(preferredTheme && preferredTheme !== colorMode) {
localStorage.removeItem('chakra-ui-color-mode')
+ localStorage.removeItem('theme')
toggleColorMode()
}
}, [loadedPreferences.get().theme]);
diff --git a/src/components/ActiveFilters.tsx b/src/components/ActiveFilters.tsx
index 0c2720f..032dc68 100644
--- a/src/components/ActiveFilters.tsx
+++ b/src/components/ActiveFilters.tsx
@@ -1,7 +1,8 @@
import React from 'react'
import { useHookstate } from '@hookstate/core'
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 {
loadingState,
@@ -53,7 +54,7 @@ const ActiveFilters = () => {
}} direction={{
base: 'column',
md: 'row'
- }} spacing={4}>
+ }} gap={4}>
{searchParams.length >= 1 && (
<>
Search:
@@ -61,11 +62,8 @@ const ActiveFilters = () => {
size={'md'}
borderRadius="full"
variant="solid"
- colorScheme="blue"
- >
- setSearchModalOpen.set(true)}>{searchParams}
- {
+ colorPalette="blue"
+ onClose={async () => {
const newSearchParams = ""
ChangeActiveFilterHeader(filters, newSearchParams, setShowFiltersModuleState)
searchComplete.set(false)
@@ -80,7 +78,9 @@ const ActiveFilters = () => {
useRatingState,
useSteamRatingState,
)
- }} />
+ }}
+ > setSearchModalOpen.set(true)}>{searchParams}
>
)}
@@ -99,22 +99,20 @@ const ActiveFilters = () => {
<>
{filter}
-
+
{filters[filter as keyof typeof filters].split(',').map((item: string, index) => (
- {
+ colorPalette="blue"
+ onClose={() => removeFilters(filter, item)}
+ >{
item.includes('lte')
? item.replace('lte', '<= ')
: item.includes('gte') ? item.replace('gte', '>= ')
: item}
-
- removeFilters(filter, item)} />
))}
diff --git a/src/components/DeleteDialogue.tsx b/src/components/DeleteDialogue.tsx
index 1cafb17..712e002 100644
--- a/src/components/DeleteDialogue.tsx
+++ b/src/components/DeleteDialogue.tsx
@@ -3,13 +3,9 @@ import agent from '../api/agent'
import { useNavigate } from 'react-router-dom'
import { Data } from '../models/game'
import {
- AlertDialog,
- AlertDialogBody,
- AlertDialogContent, AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogOverlay,
- Button, Icon, Text
+ Button, Text
} from '@chakra-ui/react'
+import {DialogBody, DialogContent, DialogFooter, DialogHeader, DialogRoot} from './ui/dialog'
type Props = {
id: string
@@ -44,34 +40,31 @@ export default function DeleteDialogue({
}
}
- const cancelRef = useRef(null)
+ const cancelRef = useRef(null)
return (
-
-
-
-
- Delete?
-
- Are you sure you would like to delete {game.title}?
-
-
-
-
-
-
-
-
+ { if (!e.open) handleClose() }} role="alertdialog" initialFocusEl={() => cancelRef.current}>
+
+ Delete?
+
+ Are you sure you would like to delete {game.title}?
+
+
+
+
+
+
+
)
}
diff --git a/src/components/FilterBar.tsx b/src/components/FilterBar.tsx
index ea36a8f..ccc794d 100644
--- a/src/components/FilterBar.tsx
+++ b/src/components/FilterBar.tsx
@@ -1,17 +1,12 @@
import React, {MutableRefObject, useEffect} from 'react'
import {
Center,
- Divider,
- Drawer,
- DrawerBody,
- DrawerCloseButton,
- DrawerContent,
- DrawerFooter,
- DrawerHeader,
- DrawerOverlay, Flex,
+ Separator,
+ Flex,
Heading, Spacer,
Text,
} from '@chakra-ui/react'
+import {DrawerBody, DrawerCloseTrigger, DrawerContent, DrawerFooter, DrawerHeader, DrawerRoot} from './ui/drawer'
import AdminMode from './authComponents/AdminMode'
import {StateMethods, useHookstate} from '@hookstate/core'
import {tagState} from '../stateManagement/tagState'
@@ -25,7 +20,7 @@ import {
} from '../componentUtils/SelectValues'
import {retrieveTags} from '../componentUtils/retrieveTags'
import {gameDashboardState} from '../stateManagement/gameState'
-import {Select} from 'chakra-react-select'
+import {Select} from './FixedSelect'
import getWithFilters from '../componentUtils/getWithFilters'
import {loadingState, showFiltersModuleState} from '../stateManagement/loadingState'
import {adminMode} from '../stateManagement/userState'
@@ -99,26 +94,25 @@ function FilterBar({filterIsOpen, filterOnClose, btnRef}: Props) {
}
return (
- { if (!e.open) filterOnClose() }}
+ finalFocusEl={() => btnRef.current as unknown as HTMLElement}
>
-
-
+
Filters
-
+
-
+
Series
-
+
)
}
-export default FilterBar
\ No newline at end of file
+export default FilterBar
diff --git a/src/components/FixedSelect.tsx b/src/components/FixedSelect.tsx
new file mode 100644
index 0000000..317e893
--- /dev/null
+++ b/src/components/FixedSelect.tsx
@@ -0,0 +1,140 @@
+import React from 'react'
+import { Select as V6Select, CreatableSelect as V6Creatable } from 'chakra-react-select'
+import { useColorModeValue } from './ui/color-mode'
+
+// Shared styling that restores how these selects looked with Chakra v2 /
+// chakra-react-select v4 (verified against the v4.9.1 source + old screenshots):
+//
+// - v4's control border resolved to the outer wrapper's gray.500 edge via
+// `borderColor: inherit` (outer Box sets gray.500). v6 paints its own light
+// edge (gray.100 in dark mode) on top, producing a double white+gray border.
+// Fix universally: single gray.500 border on the control itself, transparent
+// bg so wrapper bg shows through, no shadow, no padding so the chevron zone
+// sits flush (v4 had padding:0 + overflow:hidden).
+// - v4's chevron sat on a grey input-addon zone (gray.100 / whiteAlpha.300),
+// flush right with rounded right corners matching the control.
+// - v4's indicator separator was a subtle Divider, effectively invisible in
+// the old screenshots; v6 hides it by default, so keep it hidden instead of
+// rendering a prominent white line.
+// - v4 multi-value pills defaulted to gray subtle tags (dark gray pill in dark
+// mode), not bright blue. Defaults restored here; ActiveFilters tags pass
+// blue explicitly and are unaffected.
+function V6IndicatorSeparator() {
+ return null
+}
+
+function useSharedChakraStyles(extra: any) {
+ const zoneBg = useColorModeValue('gray.100', 'whiteAlpha.300')
+ const pillBg = useColorModeValue('gray.100', 'whiteAlpha.300')
+ const pillColor = useColorModeValue('gray.800', 'gray.100')
+ const optionHoverBg = useColorModeValue('gray.200', 'whiteAlpha.300')
+ return {
+ control: (provided: any) => ({
+ ...provided,
+ borderColor: 'gray.500',
+ borderWidth: '1px',
+ borderStyle: 'solid',
+ background: 'transparent',
+ boxShadow: 'none',
+ padding: 0,
+ overflow: 'hidden',
+ }),
+ valueContainer: (provided: any, state: any) => ({
+ ...provided,
+ paddingTop: '2px',
+ paddingBottom: '2px',
+ paddingLeft: state?.selectProps?.size === 'sm' ? '12px' : '16px',
+ paddingRight: '8px',
+ }),
+ indicatorsContainer: (provided: any) => ({
+ ...provided,
+ padding: 0,
+ margin: 0,
+ }),
+ dropdownIndicator: (provided: any) => ({
+ ...provided,
+ background: zoneBg,
+ alignSelf: 'stretch',
+ height: '100%',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ margin: 0,
+ paddingLeft: '8px',
+ paddingRight: '8px',
+ borderWidth: 0,
+ borderRadius: 0,
+ borderTopRightRadius: 'md',
+ borderBottomRightRadius: 'md',
+ }),
+ option: (provided: any, state: any) => {
+ const size = state?.selectProps?.size === 'sm' ? 'sm' : state?.selectProps?.size === 'lg' ? 'lg' : 'md'
+ const v4 = {
+ sm: { fontSize: '14px', paddingLeft: '12.8px', paddingRight: '12.8px', paddingTop: '4.8px', paddingBottom: '4.8px' },
+ md: { fontSize: '16px', paddingLeft: '12.8px', paddingRight: '12.8px', paddingTop: '6.4px', paddingBottom: '6.4px' },
+ lg: { fontSize: '18px', paddingLeft: '16px', paddingRight: '16px', paddingTop: '8px', paddingBottom: '8px' },
+ }[size]
+ return {
+ ...provided,
+ fontSize: v4.fontSize,
+ paddingLeft: v4.paddingLeft,
+ paddingRight: v4.paddingRight,
+ paddingTop: v4.paddingTop,
+ paddingBottom: v4.paddingBottom,
+ // v3 hover uses bg.emphasized/60 — near-invisible on dark surfaces.
+ // Brighten unselected hover in both modes; keep selected blue.
+ _highlighted: state?.isSelected
+ ? provided._highlighted
+ : { bg: optionHoverBg },
+ }
+ },
+ multiValue: (provided: any) => ({
+ ...provided,
+ background: pillBg,
+ color: pillColor,
+ borderRadius: 'md',
+ margin: '2px',
+ }),
+ multiValueLabel: (provided: any) => ({
+ ...provided,
+ color: pillColor,
+ }),
+ multiValueRemove: (provided: any) => ({
+ ...provided,
+ color: pillColor,
+ }),
+ ...extra,
+ }
+}
+
+function mergeComponents(extra: any) {
+ return { IndicatorSeparator: V6IndicatorSeparator, ...extra }
+}
+
+export function Select(props: any) {
+ const { chakraStyles, components, tagColorPalette = 'gray', tagVariant = 'subtle', ...rest } = props
+ const mergedStyles = useSharedChakraStyles(chakraStyles)
+ return (
+
+ )
+}
+
+export function CreatableSelect(props: any) {
+ const { chakraStyles, components, tagColorPalette = 'gray', tagVariant = 'subtle', ...rest } = props
+ const mergedStyles = useSharedChakraStyles(chakraStyles)
+ return (
+
+ )
+}
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx
index 2091fc0..fce4181 100644
--- a/src/components/Footer.tsx
+++ b/src/components/Footer.tsx
@@ -6,7 +6,7 @@ export default function Footer() {
return (
-
+
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 &&
(
- )}
-
-
- Edit
-
+
+
+
+ Edit
+
+
@@ -122,7 +123,7 @@ const GameDetails = () => {
{isSmallerThan768 ?
-
+
About This Game
@@ -138,7 +139,7 @@ const GameDetails = () => {
{isSmallerThan768 ?
-
+
System Requirements
diff --git a/src/components/GameForm.tsx b/src/components/GameForm.tsx
index 7acf969..f0562ee 100644
--- a/src/components/GameForm.tsx
+++ b/src/components/GameForm.tsx
@@ -21,17 +21,16 @@ import {
Box,
Button,
Container,
- FormControl,
- FormLabel,
+ Flex,
Heading,
- InputGroup,
Spacer,
Stack,
- Switch,
Text,
- useColorModeValue,
} from '@chakra-ui/react'
-import {CreatableSelect,} from 'chakra-react-select'
+import {Field} from './ui/field'
+import {Switch} from './ui/switch'
+import {useColorModeValue} from './ui/color-mode'
+import {CreatableSelect} from './FixedSelect'
import {adminMode} from '../stateManagement/userState'
import {encode} from 'js-base64'
import SingleFieldInput from './formFields/SingleFieldInput'
@@ -314,63 +313,64 @@ const GameForm = () => {
return (
<>
-
+
{id ? Editing {game.title} :
Add a Game to the Database}
{(gameId === '' && !game.scrape) || (gameId.length >= 1 && admin) ? (
) : ''}
<>
-
- Series
+
+ rounded="md"
+ w="full">
{
+ onChange={(values: any) => {
if (values === null) values = {value: '', label: ''}
if (values) {
handleInputChange('series', values.value)
@@ -380,20 +380,18 @@ const GameForm = () => {
value={{value: game.series, label: game.series}}
/>
-
+
>
{(gameId === '' && !game.scrape) || (gameId.length >= 1 && admin) ? (
<>
-
- Screenshots
+
+ rounded="md"
+ w="full">
{
value={screenshots[0]?.value === '' ? [] : screenshots}
/>
-
+
>
) : ''}
{(gameId === '' && !game.scrape) || (gameId.length >= 1) ?
@@ -453,14 +451,14 @@ const GameForm = () => {
>
{(gameId === '' && !game.scrape) || (gameId.length >= 1 && admin) ? (
- Operating Systems
+ Operating Systems
-
+
{
>
{(gameId === '' && !game.scrape) || (gameId.length >= 1 && admin) ? (
<>
- System Requirements
+ System Requirements
- Windows
+ Windows
{
bg={bg}
handleSystemRequirements={handleSystemRequirements}
rows={3}/>
- Mac OSX
+ Mac OSX
{
field={game.systemRequirements.mac.recommended} bg={bg}
handleSystemRequirements={handleSystemRequirements}
rows={3}/>
- Linux
+ Linux
{
>
) : ''}
-
+
Save
@@ -579,7 +577,7 @@ const GameForm = () => {
<>
{
openState.set(true)
}}
diff --git a/src/components/GamesDashboard.tsx b/src/components/GamesDashboard.tsx
index 280b6f8..24539ba 100644
--- a/src/components/GamesDashboard.tsx
+++ b/src/components/GamesDashboard.tsx
@@ -4,7 +4,7 @@ import React, { useEffect } from 'react'
import { gameDashboardState } from '../stateManagement/gameState'
import { loadingState } from '../stateManagement/loadingState'
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 { NavLink } from 'react-router-dom'
import { adminMode } from '../stateManagement/userState'
@@ -33,34 +33,36 @@ const GameDashboard = () => {
return (
<>
-
+
-
+
{!loaded ? : games.count.totalGames === 0 ?
-
+
No Results Found...
-
+
{games.searchParams !== '' ?
-
+
:
+ >
+
Create a Game
+
}
:
-
+
{games.data.map((game) => (
{
game={game} />
))}
}
-
+
>
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
index bc99a00..43c2b64 100644
--- a/src/components/Header.tsx
+++ b/src/components/Header.tsx
@@ -9,27 +9,23 @@ import {
Link,
HStack,
Stack,
- useColorModeValue,
Menu,
- MenuButton,
- Avatar,
- MenuList,
- MenuItem,
- MenuDivider,
+ Portal,
Icon,
- useColorMode,
Text,
Center,
VStack,
- Tooltip,
- Divider,
+ Separator,
Heading,
Kbd,
Spacer,
useDisclosure,
useMediaQuery,
} 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 { openAndCloseSearchDialogue } from '../stateManagement/loadingState'
import AdminMode from './authComponents/AdminMode'
@@ -49,10 +45,10 @@ export default function NavBar() {
const bgLink = useColorModeValue('gray.200', 'gray.700')
const location = useLocation()
const history = useNavigate()
- const { isOpen: mobileIsOpen, onOpen: mobileOnOpen, onClose: mobileOnClose } = useDisclosure()
- const { isOpen: filterIsOpen, onOpen: filterOnOpen, onClose: filterOnClose } = useDisclosure()
+ const { open: mobileIsOpen, onOpen: mobileOnOpen, onClose: mobileOnClose } = useDisclosure()
+ const { open: filterIsOpen, onOpen: filterOnOpen, onClose: filterOnClose } = useDisclosure()
const btnRef = React.useRef(null)
- const [isSmallerThan768] = useMediaQuery('(max-width: 768px)')
+ const [isSmallerThan768] = useMediaQuery(['(max-width: 768px)'])
const handleKeyPress = useCallback((event: any) => {
if (event.ctrlKey === true && event.key === ',') {
@@ -95,13 +91,13 @@ export default function NavBar() {
position={loadedPreferences.get().stickyNav ? "fixed" : "relative"}
justifyContent={'space-between'}
>
-
-
- Game Database
+
+
+ Game Database
- Games
+ asChild>
+ Games
- Create Game
+ asChild>
+ Create Game
@@ -135,7 +129,7 @@ export default function NavBar() {
{isSmallerThan768 ?
<>
-
+
@@ -144,7 +138,7 @@ export default function NavBar() {
{ user.role === 'admin' && }
-
+
-
+
{
@@ -173,77 +167,81 @@ export default function NavBar() {
bg={'transparent'}
cursor={'pointer'}
minW={0}>
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+ Create Game
+
+
+ Keyboard Shortcuts
+ Search:ctrl + ,
+ Games Grid:ctrl + .
+ Create
+ Game:ctrl + /
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {user.displayName}
+
+
+
+ history('/profile')}>Preferences
+ logout()}> Logout
+
+
+
+
}
diff --git a/src/components/Home.tsx b/src/components/Home.tsx
index 2400aae..161f02f 100644
--- a/src/components/Home.tsx
+++ b/src/components/Home.tsx
@@ -4,13 +4,13 @@ import { VStack, Image, Button, Heading, Flex, Center } from '@chakra-ui/react'
const Home = () => {
return (
-
+
-
+
Welcome to your Games Database
-
- Login
+
+ Login
diff --git a/src/components/LoadingModal.tsx b/src/components/LoadingModal.tsx
index 5303349..9afe522 100644
--- a/src/components/LoadingModal.tsx
+++ b/src/components/LoadingModal.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import { Center, CircularProgress } from '@chakra-ui/react'
+import { Center, ProgressCircle } from '@chakra-ui/react'
export default function LoadingModal() {
@@ -14,7 +14,12 @@ export default function LoadingModal() {
bottom={'0'}
left={'0'}
>
-
+
+
+
+
+
+
>
)
diff --git a/src/components/MobileBar.tsx b/src/components/MobileBar.tsx
index 246a897..dbcf620 100644
--- a/src/components/MobileBar.tsx
+++ b/src/components/MobileBar.tsx
@@ -1,19 +1,18 @@
import React, { MutableRefObject } from 'react'
import {
- Avatar, Button, Center, ColorMode,
- Divider, Drawer,
- DrawerBody,
- DrawerCloseButton,
- DrawerContent, DrawerFooter,
- DrawerHeader,
- DrawerOverlay, Icon,
+ Button, Center,
+ Separator,
+ Drawer,
+ Icon,
Image, Link,
Stack, Text,
} 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 { NavLink } from 'react-router-dom'
import Search from './Search'
-import { AddIcon } from '@chakra-ui/icons'
+import { LuPlus } from 'react-icons/lu'
import { logout } from '../stateManagement/userState'
import User from '../models/user'
import { AiOutlineLogout } from 'react-icons/ai'
@@ -21,12 +20,13 @@ import {useHookstate} from "@hookstate/core";
import {searchCompletedState} from "../stateManagement/loadingState";
import {gameDashboardState} from "../stateManagement/gameState";
import ResetFilterButton from "./helperComponents/ResetFilterButton";
+import type { ColorMode } from './ui/color-mode'
interface Props {
mobileIsOpen: boolean
mobileOnClose: () => void
btnRef: MutableRefObject
- bgLink: "gray.200" | "gray.700"
+ bgLink: string
user: User
colorMode: ColorMode
toggleColorMode: () => void
@@ -37,15 +37,14 @@ function MobileBar({ mobileIsOpen, mobileOnClose, btnRef, bgLink, user }: Props)
const gamesState = useHookstate(gameDashboardState)
const games = gamesState.get()
return (
- { if (!e.open) mobileOnClose() }}
+ finalFocusEl={() => btnRef.current as unknown as HTMLElement}
>
-
-
+
@@ -54,7 +53,7 @@ function MobileBar({ mobileIsOpen, mobileOnClose, btnRef, bgLink, user }: Props)
- Games
+ asChild>
+ Games
- Create Game
+ asChild>
+ Create Game
-
+
{searchCompleted.get() && Games Found: {games.count.totalGames}}
-
- {user.displayName}
+
+ {user.displayName}
- Preferences
+ asChild>
+ Preferences
-
- {/**/}
- {/* {colorMode === 'light' ? : }*/}
- {/* {colorMode === 'light' ?*/}
- {/* Dark :*/}
- {/* Light}*/}
- {/**/}
- {/**/}
+
@@ -114,21 +103,20 @@ function MobileBar({ mobileIsOpen, mobileOnClose, btnRef, bgLink, user }: Props)
}
- as={NavLink}
+ asChild
mr={3}
- to="/games/create">
- Create Game
+ >
+ Create Game
logout()}> Logout
-
+
)
}
-export default MobileBar
\ No newline at end of file
+export default MobileBar
diff --git a/src/components/NoGames.tsx b/src/components/NoGames.tsx
index 1ee327d..3d83049 100644
--- a/src/components/NoGames.tsx
+++ b/src/components/NoGames.tsx
@@ -4,12 +4,12 @@ import { Center, Flex, Heading, Button, VStack } from '@chakra-ui/react'
export default function NoGames() {
return (
-
+
-
+
{You have not added any games yet.}
-
- Click here to add some!
+
+ Click here to add some!
diff --git a/src/components/Pagination.tsx b/src/components/Pagination.tsx
index 40f8a56..a87928e 100644
--- a/src/components/Pagination.tsx
+++ b/src/components/Pagination.tsx
@@ -4,24 +4,21 @@ import {
Grid,
GridItem,
IconButton,
- NumberDecrementStepper,
- NumberIncrementStepper,
- NumberInput,
- NumberInputField,
- NumberInputStepper,
- Select,
Text,
- Tooltip,
- useColorModeValue,
- useMediaQuery,
+ Portal,
} 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 { gameDashboardState } from '../stateManagement/gameState'
import { loadingState } from '../stateManagement/loadingState'
import { adminMode } from '../stateManagement/userState'
import getWithFilters from '../componentUtils/getWithFilters'
import { ratingState, steamRatingState } from "../stateManagement/ratingState";
+import {useColorModeValue} from "./ui/color-mode";
+import {useMediaQuery} from "@chakra-ui/react";
export default function Pagination() {
const gamesState = useHookstate(gameDashboardState)
@@ -34,7 +31,7 @@ export default function Pagination() {
const bg = useColorModeValue('gray.50', 'transparent')
const useRatingState = useHookstate(ratingState).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(
gamesState,
@@ -99,22 +96,24 @@ export default function Pagination() {
}}>
-
+
gotoPage(0)}
- isDisabled={pageIndex <= 1}
- icon={}
+ disabled={pageIndex <= 1}
mr={4}
- />
+ >
+
+
-
+
}
- />
+ disabled={pageIndex <= 1}
+ >
+
+
@@ -133,70 +132,79 @@ export default function Pagination() {
{!isSmallerThan768 &&
Go to Page:
- {
- const page = value ? Number(value) : 1
+ value={String(pageIndex)}
+ onValueChange={(e) => {
+ const page = e.value ? Number(e.value) : 1
gotoPage(page)
}}
>
-
-
-
-
-
-
+
+
}
Count Per Page:
- {
- setPageSize(Number(e.target.value))
- }}
>
- {[10, 20, 30, 40, 50].map((pageSize) => (
-
- ))}
-
+ {
+ setPageSize(Number(e.target.value))
+ }}
+ >
+ {[10, 20, 30, 40, 50].map((pageSize) => (
+
+ ))}
+
+
+
Games Found: {games.count.totalGames}
-
+
= pageCount}
- icon={}
- />
+ disabled={pageIndex >= pageCount}
+ >
+
+
-
+
gotoPage(pageCount)}
- isDisabled={pageIndex >= pageCount}
- icon={}
+ disabled={pageIndex >= pageCount}
ml={4}
- />
+ >
+
+
diff --git a/src/components/Search.tsx b/src/components/Search.tsx
index e6ce6dd..d051828 100644
--- a/src/components/Search.tsx
+++ b/src/components/Search.tsx
@@ -1,6 +1,7 @@
import React, { FormEvent } from 'react'
-import { Button, FormControl, Input, InputGroup, InputRightElement, useColorModeValue } from '@chakra-ui/react'
-import { SearchIcon } from '@chakra-ui/icons'
+import { Button, Input, Group } from '@chakra-ui/react'
+import { Field } from './ui/field'
+import { LuSearch } from 'react-icons/lu'
import { GameList } from '../models/game'
import { useHookstate } from '@hookstate/core'
import { gameDashboardState } from '../stateManagement/gameState'
@@ -14,6 +15,7 @@ import { adminMode } from '../stateManagement/userState'
import getWithFilters from '../componentUtils/getWithFilters'
import {ChangeActiveFilterHeader} from "../componentUtils/changeActiveFilterHeader";
import {ratingState, steamRatingState} from "../stateManagement/ratingState";
+import {useColorModeValue} from "./ui/color-mode";
export default function Search() {
const gamesState = useHookstate(gameDashboardState)
@@ -54,16 +56,16 @@ export default function Search() {
return (
<>
>
)
-}
\ No newline at end of file
+}
diff --git a/src/components/SearchModal.tsx b/src/components/SearchModal.tsx
index c124d13..4d9eb11 100644
--- a/src/components/SearchModal.tsx
+++ b/src/components/SearchModal.tsx
@@ -1,8 +1,6 @@
import React from 'react'
-import {
- Modal, ModalBody, ModalCloseButton, ModalContent,
- ModalHeader,
-} from '@chakra-ui/react'
+import { Dialog } from '@chakra-ui/react'
+import {DialogBody, DialogCloseTrigger, DialogContent, DialogHeader, DialogRoot} from './ui/dialog'
import { useHookstate } from '@hookstate/core'
import { openAndCloseSearchDialogue } from '../stateManagement/loadingState'
import Search from './Search'
@@ -16,14 +14,14 @@ export default function SearchModal() {
}
return (
-
-
- Search
- handleClose()}/>
-
+ { if (!e.open) handleClose() }}>
+
+ Search
+ handleClose()}/>
+
-
-
-
+
+
+
)
}
diff --git a/src/components/authComponents/AdminMode.tsx b/src/components/authComponents/AdminMode.tsx
index 51122a2..26bc7dc 100644
--- a/src/components/authComponents/AdminMode.tsx
+++ b/src/components/authComponents/AdminMode.tsx
@@ -1,5 +1,6 @@
import React from 'react'
-import { Checkbox, Text } from '@chakra-ui/react'
+import { Text } from '@chakra-ui/react'
+import {Checkbox} from '../ui/checkbox'
import { adminModeToggle, loggedInUser, adminMode } from '../../stateManagement/userState'
import { useHookstate } from '@hookstate/core'
@@ -8,8 +9,8 @@ export default function AdminMode() {
const admin = useHookstate(adminMode).get()
return (
- adminModeToggle()} isChecked={admin} isDisabled={!(user.role === 'admin')}>
+ adminModeToggle()} checked={admin} disabled={!(user.role === 'admin')}>
Administrator Mode
)
-}
\ No newline at end of file
+}
diff --git a/src/components/authComponents/ForgotPassword.tsx b/src/components/authComponents/ForgotPassword.tsx
index 4348f7f..adb9ad6 100644
--- a/src/components/authComponents/ForgotPassword.tsx
+++ b/src/components/authComponents/ForgotPassword.tsx
@@ -3,11 +3,11 @@ import { useForm } from 'react-hook-form'
import {
Container,
Button,
- FormControl,
- FormLabel,
Input,
- useColorModeValue, FormErrorMessage, useToast,
} from '@chakra-ui/react'
+import {Field} from '../ui/field'
+import {useColorModeValue} from '../ui/color-mode'
+import {toaster} from '../ui/toaster'
import agent from '../../api/agent'
import { IForgotPassword } from '../../models/user'
@@ -18,7 +18,6 @@ export default function ForgotPassword() {
handleSubmit,
formState: { errors, isSubmitting, isDirty },
} = useForm()
- const toast = useToast()
interface Message {
success: boolean
@@ -27,27 +26,25 @@ export default function ForgotPassword() {
const onSubmit = handleSubmit(async (values) => {
const message = await agent.Account.forgot(values as IForgotPassword) as Message
- toast({
+ toaster.create({
title: 'Email Sent',
- status: 'success',
+ type: 'success',
description: message.data,
- isClosable: true,
+ closable: true,
duration: 10000,
- position: 'top'
})
})
return (
diff --git a/src/components/dashboardComponents/GameThumbnailContextMenu.tsx b/src/components/dashboardComponents/GameThumbnailContextMenu.tsx
index 97c5701..c61845d 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, onOpen, onClose } = 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={() => (
-
-
-
- {playStatusValues.map(value => )}
-
-
- )}>
- {ref => <>
+
+
{
transitionDuration={'1'}
transitionTimingFunction={'ease-in-out'}
_hover={{ boxShadow: '2xl', transform: 'translateY(-3px)' }}
+ onMouseEnter={onOpen}
+ onMouseLeave={onClose}
>
-
+
- {(!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() }}>
+
+
+
-
-
-
+
+
+
Close
-
-
-
+
+
+
)
}
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() }}>
+
+
+
-
-
-
+
+
+
Close
-
-
-
+
+
+
)
}
diff --git a/src/components/formFields/CheckboxInput.tsx b/src/components/formFields/CheckboxInput.tsx
index 4449eb4..ca81329 100644
--- a/src/components/formFields/CheckboxInput.tsx
+++ b/src/components/formFields/CheckboxInput.tsx
@@ -1,5 +1,6 @@
import React from 'react'
-import { Checkbox, Text } from '@chakra-ui/react'
+import { Text } from '@chakra-ui/react'
+import {Checkbox} from '../ui/checkbox'
interface Props {
label: string
@@ -12,15 +13,16 @@ interface Props {
const CheckboxInput = ({ label, textField, checked, defaultIsChecked, handleOSChange }: Props) => {
return (
{
+ checked={checked}
+ onCheckedChange={() => {
handleOSChange(textField)
}}
name={textField}
- spacing="1rem"
+ gap="1rem"
>{label}
)
}
-export default CheckboxInput
\ No newline at end of file
+export default CheckboxInput
diff --git a/src/components/formFields/CreatableSelectInput.tsx b/src/components/formFields/CreatableSelectInput.tsx
index 78f457e..d3219c6 100644
--- a/src/components/formFields/CreatableSelectInput.tsx
+++ b/src/components/formFields/CreatableSelectInput.tsx
@@ -1,6 +1,7 @@
import React from 'react'
-import { Box, FormControl, FormLabel } from '@chakra-ui/react'
-import { CreatableSelect } from 'chakra-react-select'
+import { Box } from '@chakra-ui/react'
+import { Field } from '../ui/field'
+import { CreatableSelect } from '../FixedSelect'
interface Props {
label: string
@@ -13,13 +14,12 @@ interface Props {
const CreatableSelectInput = ({ label, textField, value, bg, handleInputChange, options}: Props) => {
return (
-
- {label}
+
+ rounded="md"
+ w="full"
+ >
-
+
)
}
-export default CreatableSelectInput
\ No newline at end of file
+export default CreatableSelectInput
diff --git a/src/components/formFields/SelectInput.tsx b/src/components/formFields/SelectInput.tsx
index 1cd5507..db10fd4 100644
--- a/src/components/formFields/SelectInput.tsx
+++ b/src/components/formFields/SelectInput.tsx
@@ -1,6 +1,7 @@
import React from 'react'
-import {Box, FormControl, FormLabel} from '@chakra-ui/react'
-import {Select} from 'chakra-react-select'
+import {Box} from '@chakra-ui/react'
+import { Field } from '../ui/field'
+import {Select} from '../FixedSelect'
interface Props {
label: string
@@ -14,18 +15,15 @@ interface Props {
const SelectInput = ({label, textField, field, bg, handleAccessedBy, options, width = ''}: Props) => {
return (
-
- {label}
+
{
+ onChange={(values: any) => {
if (values) handleAccessedBy(textField, values.value)
}}
options={options}
@@ -35,8 +33,8 @@ const SelectInput = ({label, textField, field, bg, handleAccessedBy, options, wi
}}
/>
-
+
)
}
-export default SelectInput
\ No newline at end of file
+export default SelectInput
diff --git a/src/components/formFields/SingleFieldInput.tsx b/src/components/formFields/SingleFieldInput.tsx
index 5c31eff..b9fead2 100644
--- a/src/components/formFields/SingleFieldInput.tsx
+++ b/src/components/formFields/SingleFieldInput.tsx
@@ -1,5 +1,6 @@
import React from 'react'
-import { FormControl, FormLabel, Input } from '@chakra-ui/react'
+import { Input } from '@chakra-ui/react'
+import { Field } from '../ui/field'
import { Data } from '../../models/game'
interface Props {
@@ -14,15 +15,14 @@ interface Props {
const SingleFieldInput = ({ label, textField, field, bg, handleInputChange, required }: Props) => {
return (
-
- {label}
+
-
+
)
}
-export default SingleFieldInput
\ No newline at end of file
+export default SingleFieldInput
diff --git a/src/components/formFields/SingleFieldNumericInput.tsx b/src/components/formFields/SingleFieldNumericInput.tsx
index 4348e1c..03a5d8f 100644
--- a/src/components/formFields/SingleFieldNumericInput.tsx
+++ b/src/components/formFields/SingleFieldNumericInput.tsx
@@ -1,39 +1,63 @@
-import React from 'react'
-import { FormControl, FormLabel, Input } from '@chakra-ui/react'
-import { Data } from '../../models/game'
+import React from "react";
+import { Input } from "@chakra-ui/react";
+import { Field } from "../ui/field";
+import { Data } from "../../models/game";
interface Props {
- label: string
- textField: string
- field: string
- bg: string
- handleInputChange: (name: string, value: Data[G]) => void
- required: boolean
+ label: string;
+ textField: string;
+ field: string;
+ bg: string;
+ handleInputChange: (
+ name: string,
+ value: Data[G],
+ ) => void;
+ required: boolean;
}
-const SingleFieldNumericInput = ({ label, textField, field, bg, handleInputChange, required }: Props) => {
+const SingleFieldNumericInput = ({
+ label,
+ textField,
+ field,
+ bg,
+ handleInputChange,
+ required,
+}: Props) => {
+ const isValidSteamValue = (v: string) => {
+ const s = v.trim();
+ if (!s) return false;
+ if (/^\d+$/.test(s)) return true;
+ return s.includes("store.steampowered.com");
+ };
+
+ const showError = field.trim() !== "" && !isValidSteamValue(field);
return (
-
- {label}
+
{
- handleInputChange(textField, e.target.value)
+ handleInputChange(textField, e.target.value);
}}
/>
-
- )
-}
+
+ );
+};
-export default SingleFieldNumericInput
\ No newline at end of file
+export default SingleFieldNumericInput;
diff --git a/src/components/formFields/SystemRequirementsTextareaInput.tsx b/src/components/formFields/SystemRequirementsTextareaInput.tsx
index 27130b8..987f339 100644
--- a/src/components/formFields/SystemRequirementsTextareaInput.tsx
+++ b/src/components/formFields/SystemRequirementsTextareaInput.tsx
@@ -1,5 +1,5 @@
import React, {useMemo, useRef} from 'react'
-import {FormControl, FormLabel} from '@chakra-ui/react'
+import {Field} from '../ui/field'
import JoditEditor from "jodit-react";
interface Props {
@@ -24,18 +24,17 @@ const SystemRequirementsTextareaInput = ({label, textField, field, handleSystemR
);
return (
-
- {label}
+
{
+ onChange={(value: string) => {
handleSystemRequirements(arrText[1], arrText[2], value)
}}
/>
-
+
)
}
-export default SystemRequirementsTextareaInput
\ No newline at end of file
+export default SystemRequirementsTextareaInput
diff --git a/src/components/formFields/TextareaInput.tsx b/src/components/formFields/TextareaInput.tsx
index d350f6f..1dbfc51 100644
--- a/src/components/formFields/TextareaInput.tsx
+++ b/src/components/formFields/TextareaInput.tsx
@@ -1,5 +1,5 @@
import React, {useMemo, useRef} from 'react'
-import { FormControl, FormLabel } from '@chakra-ui/react'
+import { Field } from '../ui/field'
import { Data } from '../../models/game'
import JoditEditor from "jodit-react";
@@ -25,18 +25,17 @@ const TextareaInput = ({ label, textField, field, handleInputChange }: Props) =>
return (
-
- {label}
+
{
+ onChange={(value: string) => {
handleInputChange(textField, value)
}}
/>
-
+
)
}
-export default TextareaInput
\ No newline at end of file
+export default TextareaInput
diff --git a/src/components/helperComponents/ResetFilterButton.tsx b/src/components/helperComponents/ResetFilterButton.tsx
index f1bcd2c..7fd1151 100644
--- a/src/components/helperComponents/ResetFilterButton.tsx
+++ b/src/components/helperComponents/ResetFilterButton.tsx
@@ -9,12 +9,12 @@ import { ratingState, steamRatingState } from "../../stateManagement/ratingState
interface Props {
text?: string
- size?: string
- variant?: string
- colorScheme?: string
+ size?: "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl"
+ variant?: "solid" | "subtle" | "surface" | "outline" | "ghost" | "plain"
+ 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 games = gameState.get()
const admin = useHookstate(adminMode).get()
@@ -58,7 +58,7 @@ const ResetFilterButton = ({ text = "Reset Search and Filters", size = 'md', var
}
return (
-
+
{text}
)
diff --git a/src/components/helperComponents/SteamAndDeckLogo.tsx b/src/components/helperComponents/SteamAndDeckLogo.tsx
index 602216d..7dc11ad 100644
--- a/src/components/helperComponents/SteamAndDeckLogo.tsx
+++ b/src/components/helperComponents/SteamAndDeckLogo.tsx
@@ -9,17 +9,19 @@ const SteamAndDeckLogo = (props: ISteamAndDeckLogo) => {
return (
);
diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx
new file mode 100644
index 0000000..0afc8ef
--- /dev/null
+++ b/src/components/ui/alert.tsx
@@ -0,0 +1,29 @@
+import { Alert as ChakraAlert } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface AlertProps extends Omit {
+ startElement?: React.ReactNode
+ endElement?: React.ReactNode
+ title?: React.ReactNode
+ icon?: React.ReactElement
+}
+
+export const Alert = React.forwardRef(
+ function Alert(props, ref) {
+ const { title, children, icon, startElement, endElement, ...rest } = props
+ return (
+
+ {startElement || {icon}}
+ {children ? (
+
+ {title}
+ {children}
+
+ ) : (
+ {title}
+ )}
+ {endElement}
+
+ )
+ },
+)
diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx
new file mode 100644
index 0000000..0f1d3a3
--- /dev/null
+++ b/src/components/ui/avatar.tsx
@@ -0,0 +1,34 @@
+import {
+ Avatar as ChakraAvatar,
+ AvatarGroup as ChakraAvatarGroup,
+} from "@chakra-ui/react"
+import * as React from "react"
+
+type ImageProps = React.ImgHTMLAttributes
+
+export interface AvatarProps extends ChakraAvatar.RootProps {
+ name?: string
+ src?: string
+ srcSet?: string
+ loading?: ImageProps["loading"]
+ icon?: React.ReactElement
+ fallback?: React.ReactNode
+}
+
+export const Avatar = React.forwardRef(
+ function Avatar(props, ref) {
+ const { name, src, srcSet, loading, icon, fallback, children, ...rest } =
+ props
+ return (
+
+
+ {icon || fallback}
+
+
+ {children}
+
+ )
+ },
+)
+
+export const AvatarGroup = ChakraAvatarGroup
diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx
new file mode 100644
index 0000000..246e2f0
--- /dev/null
+++ b/src/components/ui/checkbox.tsx
@@ -0,0 +1,25 @@
+import { Checkbox as ChakraCheckbox } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface CheckboxProps extends ChakraCheckbox.RootProps {
+ icon?: React.ReactNode
+ inputProps?: React.InputHTMLAttributes
+ rootRef?: React.RefObject
+}
+
+export const Checkbox = React.forwardRef(
+ function Checkbox(props, ref) {
+ const { icon, children, inputProps, rootRef, ...rest } = props
+ return (
+
+
+
+ {icon || }
+
+ {children != null && (
+ {children}
+ )}
+
+ )
+ },
+)
diff --git a/src/components/ui/close-button.tsx b/src/components/ui/close-button.tsx
new file mode 100644
index 0000000..94af488
--- /dev/null
+++ b/src/components/ui/close-button.tsx
@@ -0,0 +1,17 @@
+import type { ButtonProps } from "@chakra-ui/react"
+import { IconButton as ChakraIconButton } from "@chakra-ui/react"
+import * as React from "react"
+import { LuX } from "react-icons/lu"
+
+export type CloseButtonProps = ButtonProps
+
+export const CloseButton = React.forwardRef<
+ HTMLButtonElement,
+ CloseButtonProps
+>(function CloseButton(props, ref) {
+ return (
+
+ {props.children ?? }
+
+ )
+})
diff --git a/src/components/ui/color-mode.tsx b/src/components/ui/color-mode.tsx
new file mode 100644
index 0000000..3ad064a
--- /dev/null
+++ b/src/components/ui/color-mode.tsx
@@ -0,0 +1,108 @@
+"use client"
+
+import type { IconButtonProps, SpanProps } from "@chakra-ui/react"
+import { ClientOnly, IconButton, Skeleton, Span } from "@chakra-ui/react"
+import { ThemeProvider, useTheme } from "next-themes"
+import type { ThemeProviderProps } from "next-themes"
+import * as React from "react"
+import { LuMoon, LuSun } from "react-icons/lu"
+
+export interface ColorModeProviderProps extends ThemeProviderProps {}
+
+export function ColorModeProvider(props: ColorModeProviderProps) {
+ return (
+
+ )
+}
+
+export type ColorMode = "light" | "dark"
+
+export interface UseColorModeReturn {
+ colorMode: ColorMode
+ setColorMode: (colorMode: ColorMode) => void
+ toggleColorMode: () => void
+}
+
+export function useColorMode(): UseColorModeReturn {
+ const { resolvedTheme, setTheme, forcedTheme } = useTheme()
+ const colorMode = forcedTheme || resolvedTheme
+ const toggleColorMode = () => {
+ setTheme(resolvedTheme === "dark" ? "light" : "dark")
+ }
+ return {
+ colorMode: colorMode as ColorMode,
+ setColorMode: setTheme,
+ toggleColorMode,
+ }
+}
+
+export function useColorModeValue(light: T, dark: T) {
+ const { colorMode } = useColorMode()
+ return colorMode === "dark" ? dark : light
+}
+
+export function ColorModeIcon() {
+ const { colorMode } = useColorMode()
+ return colorMode === "dark" ? :
+}
+
+interface ColorModeButtonProps extends Omit {}
+
+export const ColorModeButton = React.forwardRef<
+ HTMLButtonElement,
+ ColorModeButtonProps
+>(function ColorModeButton(props, ref) {
+ const { toggleColorMode } = useColorMode()
+ return (
+ }>
+
+
+
+
+ )
+})
+
+export const LightMode = React.forwardRef(
+ function LightMode(props, ref) {
+ return (
+
+ )
+ },
+)
+
+export const DarkMode = React.forwardRef(
+ function DarkMode(props, ref) {
+ return (
+
+ )
+ },
+)
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
new file mode 100644
index 0000000..44ff61a
--- /dev/null
+++ b/src/components/ui/dialog.tsx
@@ -0,0 +1,62 @@
+import { Dialog as ChakraDialog, Portal } from "@chakra-ui/react"
+import { CloseButton } from "./close-button"
+import * as React from "react"
+
+interface DialogContentProps extends ChakraDialog.ContentProps {
+ portalled?: boolean
+ portalRef?: React.RefObject
+ backdrop?: boolean
+}
+
+export const DialogContent = React.forwardRef<
+ HTMLDivElement,
+ DialogContentProps
+>(function DialogContent(props, ref) {
+ const {
+ children,
+ portalled = true,
+ portalRef,
+ backdrop = true,
+ ...rest
+ } = props
+
+ return (
+
+ {backdrop && }
+
+
+ {children}
+
+
+
+ )
+})
+
+export const DialogCloseTrigger = React.forwardRef<
+ HTMLButtonElement,
+ ChakraDialog.CloseTriggerProps
+>(function DialogCloseTrigger(props, ref) {
+ return (
+
+
+ {props.children}
+
+
+ )
+})
+
+export const DialogRoot = ChakraDialog.Root
+export const DialogFooter = ChakraDialog.Footer
+export const DialogHeader = ChakraDialog.Header
+export const DialogBody = ChakraDialog.Body
+export const DialogBackdrop = ChakraDialog.Backdrop
+export const DialogTitle = ChakraDialog.Title
+export const DialogDescription = ChakraDialog.Description
+export const DialogTrigger = ChakraDialog.Trigger
+export const DialogActionTrigger = ChakraDialog.ActionTrigger
diff --git a/src/components/ui/drawer.tsx b/src/components/ui/drawer.tsx
new file mode 100644
index 0000000..4b189b5
--- /dev/null
+++ b/src/components/ui/drawer.tsx
@@ -0,0 +1,61 @@
+import { Drawer as ChakraDrawer, Portal } from "@chakra-ui/react"
+import { CloseButton } from "./close-button"
+import * as React from "react"
+
+interface DrawerContentProps extends ChakraDrawer.ContentProps {
+ portalled?: boolean
+ portalRef?: React.RefObject
+ offset?: ChakraDrawer.ContentProps["padding"]
+ backdrop?: boolean
+}
+
+export const DrawerContent = React.forwardRef<
+ HTMLDivElement,
+ DrawerContentProps
+>(function DrawerContent(props, ref) {
+ const {
+ children,
+ portalled = true,
+ portalRef,
+ offset,
+ backdrop = true,
+ ...rest
+ } = props
+ return (
+
+ {backdrop && }
+
+
+ {children}
+
+
+
+ )
+})
+
+export const DrawerCloseTrigger = React.forwardRef<
+ HTMLButtonElement,
+ ChakraDrawer.CloseTriggerProps
+>(function DrawerCloseTrigger(props, ref) {
+ return (
+
+
+
+ )
+})
+
+export const DrawerTrigger = ChakraDrawer.Trigger
+export const DrawerRoot = ChakraDrawer.Root
+export const DrawerFooter = ChakraDrawer.Footer
+export const DrawerHeader = ChakraDrawer.Header
+export const DrawerBody = ChakraDrawer.Body
+export const DrawerBackdrop = ChakraDrawer.Backdrop
+export const DrawerDescription = ChakraDrawer.Description
+export const DrawerTitle = ChakraDrawer.Title
+export const DrawerActionTrigger = ChakraDrawer.ActionTrigger
diff --git a/src/components/ui/field.tsx b/src/components/ui/field.tsx
new file mode 100644
index 0000000..4866269
--- /dev/null
+++ b/src/components/ui/field.tsx
@@ -0,0 +1,33 @@
+import { Field as ChakraField } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface FieldProps extends Omit {
+ label?: React.ReactNode
+ helperText?: React.ReactNode
+ errorText?: React.ReactNode
+ optionalText?: React.ReactNode
+}
+
+export const Field = React.forwardRef(
+ function Field(props, ref) {
+ const { label, children, helperText, errorText, optionalText, ...rest } =
+ props
+ return (
+
+ {label && (
+
+ {label}
+
+
+ )}
+ {children}
+ {helperText && (
+ {helperText}
+ )}
+ {errorText && (
+ {errorText}
+ )}
+
+ )
+ },
+)
diff --git a/src/components/ui/menu.tsx b/src/components/ui/menu.tsx
new file mode 100644
index 0000000..79ceb07
--- /dev/null
+++ b/src/components/ui/menu.tsx
@@ -0,0 +1,112 @@
+"use client"
+
+import { AbsoluteCenter, Menu as ChakraMenu, Portal } from "@chakra-ui/react"
+import * as React from "react"
+import { LuCheck, LuChevronRight } from "react-icons/lu"
+
+interface MenuContentProps extends ChakraMenu.ContentProps {
+ portalled?: boolean
+ portalRef?: React.RefObject
+}
+
+export const MenuContent = React.forwardRef(
+ function MenuContent(props, ref) {
+ const { portalled = true, portalRef, ...rest } = props
+ return (
+
+
+
+
+
+ )
+ },
+)
+
+export const MenuArrow = React.forwardRef<
+ HTMLDivElement,
+ ChakraMenu.ArrowProps
+>(function MenuArrow(props, ref) {
+ return (
+
+
+
+ )
+})
+
+export const MenuCheckboxItem = React.forwardRef<
+ HTMLDivElement,
+ ChakraMenu.CheckboxItemProps
+>(function MenuCheckboxItem(props, ref) {
+ return (
+
+
+
+
+
+
+ {props.children}
+
+ )
+})
+
+export const MenuRadioItem = React.forwardRef<
+ HTMLDivElement,
+ ChakraMenu.RadioItemProps
+>(function MenuRadioItem(props, ref) {
+ const { children, ...rest } = props
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+})
+
+export const MenuItemGroup = React.forwardRef<
+ HTMLDivElement,
+ ChakraMenu.ItemGroupProps
+>(function MenuItemGroup(props, ref) {
+ const { title, children, ...rest } = props
+ return (
+
+ {title && (
+
+ {title}
+
+ )}
+ {children}
+
+ )
+})
+
+export interface MenuTriggerItemProps extends ChakraMenu.ItemProps {
+ startIcon?: React.ReactNode
+}
+
+export const MenuTriggerItem = React.forwardRef<
+ HTMLDivElement,
+ MenuTriggerItemProps
+>(function MenuTriggerItem(props, ref) {
+ const { startIcon, children, ...rest } = props
+ return (
+
+ {startIcon}
+ {children}
+
+
+ )
+})
+
+export const MenuRadioItemGroup = ChakraMenu.RadioItemGroup
+export const MenuContextTrigger = ChakraMenu.ContextTrigger
+export const MenuRoot = ChakraMenu.Root
+export const MenuSeparator = ChakraMenu.Separator
+
+export const MenuItem = ChakraMenu.Item
+export const MenuItemText = ChakraMenu.ItemText
+export const MenuItemCommand = ChakraMenu.ItemCommand
+export const MenuTrigger = ChakraMenu.Trigger
diff --git a/src/components/ui/number-input.tsx b/src/components/ui/number-input.tsx
new file mode 100644
index 0000000..0948535
--- /dev/null
+++ b/src/components/ui/number-input.tsx
@@ -0,0 +1,28 @@
+import { NumberInput as ChakraNumberInput } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface NumberInputProps extends ChakraNumberInput.RootProps {}
+
+export const NumberInputRoot = React.forwardRef<
+ HTMLDivElement,
+ NumberInputProps
+>(function NumberInput(props, ref) {
+ const { children, ...rest } = props
+ return (
+
+ {children}
+
+
+
+
+
+ )
+})
+
+export const NumberInputField = ChakraNumberInput.Input
+export const NumberInputScrubber = ChakraNumberInput.Scrubber
+export const NumberInputLabel = ChakraNumberInput.Label
diff --git a/src/components/ui/provider.tsx b/src/components/ui/provider.tsx
new file mode 100644
index 0000000..ec44b3f
--- /dev/null
+++ b/src/components/ui/provider.tsx
@@ -0,0 +1,16 @@
+"use client"
+
+import { ChakraProvider } from "@chakra-ui/react"
+import {
+ ColorModeProvider,
+ type ColorModeProviderProps,
+} from "./color-mode"
+import system from "../../styles/theme"
+
+export function Provider(props: ColorModeProviderProps) {
+ return (
+
+
+
+ )
+}
diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx
new file mode 100644
index 0000000..e3690c0
--- /dev/null
+++ b/src/components/ui/switch.tsx
@@ -0,0 +1,39 @@
+import { Switch as ChakraSwitch } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface SwitchProps extends ChakraSwitch.RootProps {
+ inputProps?: React.InputHTMLAttributes
+ rootRef?: React.RefObject
+ trackLabel?: { on: React.ReactNode; off: React.ReactNode }
+ thumbLabel?: { on: React.ReactNode; off: React.ReactNode }
+}
+
+export const Switch = React.forwardRef(
+ function Switch(props, ref) {
+ const { inputProps, children, rootRef, trackLabel, thumbLabel, ...rest } =
+ props
+
+ return (
+
+
+
+
+ {thumbLabel && (
+
+ {thumbLabel?.on}
+
+ )}
+
+ {trackLabel && (
+
+ {trackLabel.on}
+
+ )}
+
+ {children != null && (
+ {children}
+ )}
+
+ )
+ },
+)
diff --git a/src/components/ui/tag.tsx b/src/components/ui/tag.tsx
new file mode 100644
index 0000000..df02291
--- /dev/null
+++ b/src/components/ui/tag.tsx
@@ -0,0 +1,42 @@
+import { Tag as ChakraTag } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface TagProps extends ChakraTag.RootProps {
+ startElement?: React.ReactNode
+ endElement?: React.ReactNode
+ onClose?: VoidFunction
+ closable?: boolean
+}
+
+export const Tag = React.forwardRef(
+ function Tag(props, ref) {
+ const {
+ startElement,
+ endElement,
+ onClose,
+ closable = !!onClose,
+ children,
+ size,
+ ...rest
+ } = props
+ // v2 tag text per size (v3 renders one step smaller)
+ const labelTextStyle = size === 'lg' ? 'md' : size === 'sm' ? 'xs' : 'sm'
+
+ return (
+
+ {startElement && (
+ {startElement}
+ )}
+ {children}
+ {endElement && (
+ {endElement}
+ )}
+ {closable && (
+
+
+
+ )}
+
+ )
+ },
+)
diff --git a/src/components/ui/toaster.tsx b/src/components/ui/toaster.tsx
new file mode 100644
index 0000000..5d70a35
--- /dev/null
+++ b/src/components/ui/toaster.tsx
@@ -0,0 +1,43 @@
+"use client"
+
+import {
+ Toaster as ChakraToaster,
+ Portal,
+ Spinner,
+ Stack,
+ Toast,
+ createToaster,
+} from "@chakra-ui/react"
+
+export const toaster = createToaster({
+ placement: "bottom-end",
+ pauseOnPageIdle: true,
+})
+
+export const Toaster = () => {
+ return (
+
+
+ {(toast) => (
+
+ {toast.type === "loading" ? (
+
+ ) : (
+
+ )}
+
+ {toast.title && {toast.title}}
+ {toast.description && (
+ {toast.description}
+ )}
+
+ {toast.action && (
+ {toast.action.label}
+ )}
+ {toast.closable && }
+
+ )}
+
+
+ )
+}
diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx
new file mode 100644
index 0000000..0129778
--- /dev/null
+++ b/src/components/ui/tooltip.tsx
@@ -0,0 +1,46 @@
+import { Tooltip as ChakraTooltip, Portal } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface TooltipProps extends ChakraTooltip.RootProps {
+ showArrow?: boolean
+ portalled?: boolean
+ portalRef?: React.RefObject
+ content: React.ReactNode
+ contentProps?: ChakraTooltip.ContentProps
+ disabled?: boolean
+}
+
+export const Tooltip = React.forwardRef(
+ function Tooltip(props, ref) {
+ const {
+ showArrow,
+ children,
+ disabled,
+ portalled = true,
+ content,
+ contentProps,
+ portalRef,
+ ...rest
+ } = props
+
+ if (disabled) return children
+
+ return (
+
+ {children}
+
+
+
+ {showArrow && (
+
+
+
+ )}
+ {content}
+
+
+
+
+ )
+ },
+)
diff --git a/src/components/userComponents/CreateUser.tsx b/src/components/userComponents/CreateUser.tsx
index 8e3cc58..88ea2fe 100644
--- a/src/components/userComponents/CreateUser.tsx
+++ b/src/components/userComponents/CreateUser.tsx
@@ -3,15 +3,13 @@ import { useForm } from 'react-hook-form'
import { UserFormValues } from '../../models/user'
import { createUser } from '../../stateManagement/userState'
import {
- Alert,
- AlertDescription,
- AlertIcon,
Button,
Container,
- FormControl, FormErrorMessage,
- FormLabel,
- Input, useColorModeValue,
+ Input,
} from '@chakra-ui/react'
+import {Field} from '../ui/field'
+import {Alert} from '../ui/alert'
+import {useColorModeValue} from '../ui/color-mode'
export default function CreateUser() {
const {
@@ -44,59 +42,52 @@ export default function CreateUser() {
return (