refactor(forms): port game form to Chakra v3 with v4-style selects
- New FixedSelect wrapper over chakra-react-select v6 restoring the v4 look everywhere: single gray.500 control border, flush addon chevron zone with rounded inner corners, hidden indicator separator, v4 option padding/type scale, gray subtle pills, brighter unselected hover - GameForm: FormControl/InputGroup -> Field/Flex, Button colorScheme/isLoading -> colorPalette/loading, Stack spacing -> gap, Container container.md -> 768px, Steam Scrape row rebuilt as nowrap Flex, select wrappers de-bordered (control owns the border) - formFields: Field/Checkbox/Switch shims in, required asterisk dropped to match legacy labels, Select/Creatable wrappers de-bordered, TS-typed select callbacks
This commit is contained in:
@@ -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 (
|
||||||
|
<V6Select
|
||||||
|
{...rest}
|
||||||
|
tagColorPalette={tagColorPalette}
|
||||||
|
tagVariant={tagVariant}
|
||||||
|
components={mergeComponents(components)}
|
||||||
|
chakraStyles={mergedStyles}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CreatableSelect(props: any) {
|
||||||
|
const { chakraStyles, components, tagColorPalette = 'gray', tagVariant = 'subtle', ...rest } = props
|
||||||
|
const mergedStyles = useSharedChakraStyles(chakraStyles)
|
||||||
|
return (
|
||||||
|
<V6Creatable
|
||||||
|
{...rest}
|
||||||
|
tagColorPalette={tagColorPalette}
|
||||||
|
tagVariant={tagVariant}
|
||||||
|
components={mergeComponents(components)}
|
||||||
|
chakraStyles={mergedStyles}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
+43
-45
@@ -21,17 +21,16 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Container,
|
Container,
|
||||||
FormControl,
|
Flex,
|
||||||
FormLabel,
|
|
||||||
Heading,
|
Heading,
|
||||||
InputGroup,
|
|
||||||
Spacer,
|
Spacer,
|
||||||
Stack,
|
Stack,
|
||||||
Switch,
|
|
||||||
Text,
|
Text,
|
||||||
useColorModeValue,
|
|
||||||
} from '@chakra-ui/react'
|
} 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 {adminMode} from '../stateManagement/userState'
|
||||||
import {encode} from 'js-base64'
|
import {encode} from 'js-base64'
|
||||||
import SingleFieldInput from './formFields/SingleFieldInput'
|
import SingleFieldInput from './formFields/SingleFieldInput'
|
||||||
@@ -314,63 +313,64 @@ const GameForm = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Container maxW="container.md" mt="10">
|
<Container maxW="768px" mt="10">
|
||||||
{id ? <Heading as="h1" size="xl">Editing {game.title}</Heading> :
|
{id ? <Heading as="h1" size="xl">Editing {game.title}</Heading> :
|
||||||
<Heading as="h1" size="xl">Add a Game to the Database</Heading>}
|
<Heading as="h1" size="xl">Add a Game to the Database</Heading>}
|
||||||
<form onSubmit={handleSubmit} style={{marginTop: '2rem'}}>
|
<form onSubmit={handleSubmit} style={{marginTop: '2rem'}}>
|
||||||
<Stack spacing={[1, 5]} direction={{base: 'column', sm: 'row'}} justify={'flex-end'}>
|
<Stack gap={[1, 5]} direction={{base: 'column', sm: 'row'}} justify={'flex-end'}>
|
||||||
<Button
|
<Button
|
||||||
isLoading={submitLoading}
|
loading={submitLoading}
|
||||||
colorScheme="blue"
|
colorPalette="blue"
|
||||||
type="submit"
|
type="submit"
|
||||||
data-name={"save"}
|
data-name={"save"}
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
isLoading={moveOn}
|
loading={moveOn}
|
||||||
colorScheme="green"
|
colorPalette="green"
|
||||||
type="submit"
|
type="submit"
|
||||||
data-name={"save-and-view"}
|
data-name={"save-and-view"}
|
||||||
>
|
>
|
||||||
Save and View
|
Save and View
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
<InputGroup>
|
<Flex gap={3} alignItems="center">
|
||||||
{!id || admin ? (
|
{!id || admin ? (
|
||||||
|
<Box flex="1">
|
||||||
<SingleFieldNumericInput label={'Steam ID'} textField={'steamId'} field={game.steamId} bg={bg}
|
<SingleFieldNumericInput label={'Steam ID'} textField={'steamId'} field={game.steamId} bg={bg}
|
||||||
handleInputChange={handleInputChange} required={game.scrape}/>
|
handleInputChange={handleInputChange} required={game.scrape}/>
|
||||||
|
</Box>
|
||||||
) : ''}
|
) : ''}
|
||||||
{!id && (
|
{!id && (
|
||||||
<FormControl display="flex" ml={'3'} alignItems="center">
|
<Flex alignItems="center" ml={'3'} flexShrink={0} gap={2}>
|
||||||
<FormLabel htmlFor="scrape" mb="0">Steam Scrape</FormLabel>
|
<Text whiteSpace="nowrap">Steam Scrape</Text>
|
||||||
<Switch
|
<Switch
|
||||||
isChecked={game.scrape}
|
colorPalette="blue"
|
||||||
onChange={() => {
|
checked={game.scrape}
|
||||||
|
onCheckedChange={() => {
|
||||||
handleToggle('scrape')
|
handleToggle('scrape')
|
||||||
}}
|
}}
|
||||||
name="scrape"
|
name="scrape"
|
||||||
id="scrape"
|
id="scrape"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</InputGroup>
|
</Flex>
|
||||||
{(gameId === '' && !game.scrape) || (gameId.length >= 1 && admin) ? (
|
{(gameId === '' && !game.scrape) || (gameId.length >= 1 && admin) ? (
|
||||||
<SingleFieldInput label={'Title'} textField={'title'} field={game.title} bg={bg}
|
<SingleFieldInput label={'Title'} textField={'title'} field={game.title} bg={bg}
|
||||||
handleInputChange={handleInputChange} required={false}/>
|
handleInputChange={handleInputChange} required={false}/>
|
||||||
) : ''}
|
) : ''}
|
||||||
<>
|
<>
|
||||||
<FormControl mb="3">
|
<Field mb="3" label="Series">
|
||||||
<FormLabel>Series</FormLabel>
|
|
||||||
<Box
|
<Box
|
||||||
bg={bg}
|
bg={bg}
|
||||||
border="1px"
|
rounded="md"
|
||||||
borderColor="gray.500"
|
w="full">
|
||||||
rounded="md">
|
|
||||||
<CreatableSelect
|
<CreatableSelect
|
||||||
name="series"
|
name="series"
|
||||||
isClearable
|
isClearable
|
||||||
onChange={(values) => {
|
onChange={(values: any) => {
|
||||||
if (values === null) values = {value: '', label: ''}
|
if (values === null) values = {value: '', label: ''}
|
||||||
if (values) {
|
if (values) {
|
||||||
handleInputChange('series', values.value)
|
handleInputChange('series', values.value)
|
||||||
@@ -380,20 +380,18 @@ const GameForm = () => {
|
|||||||
value={{value: game.series, label: game.series}}
|
value={{value: game.series, label: game.series}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</FormControl>
|
</Field>
|
||||||
</>
|
</>
|
||||||
{(gameId === '' && !game.scrape) || (gameId.length >= 1 && admin) ? (
|
{(gameId === '' && !game.scrape) || (gameId.length >= 1 && admin) ? (
|
||||||
<>
|
<>
|
||||||
<SingleFieldInput label={'Front Image'} textField={'frontImage'} field={game.frontImage}
|
<SingleFieldInput label={'Front Image'} textField={'frontImage'} field={game.frontImage}
|
||||||
bg={bg}
|
bg={bg}
|
||||||
handleInputChange={handleInputChange} required={false}/>
|
handleInputChange={handleInputChange} required={false}/>
|
||||||
<FormControl mb="3">
|
<Field mb="3" label="Screenshots">
|
||||||
<FormLabel>Screenshots</FormLabel>
|
|
||||||
<Box
|
<Box
|
||||||
bg={bg}
|
bg={bg}
|
||||||
border="1px"
|
rounded="md"
|
||||||
borderColor="gray.500"
|
w="full">
|
||||||
rounded="md">
|
|
||||||
<CreatableSelect
|
<CreatableSelect
|
||||||
name="screenshots"
|
name="screenshots"
|
||||||
isClearable
|
isClearable
|
||||||
@@ -433,7 +431,7 @@ const GameForm = () => {
|
|||||||
value={screenshots[0]?.value === '' ? [] : screenshots}
|
value={screenshots[0]?.value === '' ? [] : screenshots}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</FormControl>
|
</Field>
|
||||||
</>
|
</>
|
||||||
) : ''}
|
) : ''}
|
||||||
{(gameId === '' && !game.scrape) || (gameId.length >= 1) ?
|
{(gameId === '' && !game.scrape) || (gameId.length >= 1) ?
|
||||||
@@ -453,14 +451,14 @@ const GameForm = () => {
|
|||||||
</>
|
</>
|
||||||
{(gameId === '' && !game.scrape) || (gameId.length >= 1 && admin) ? (
|
{(gameId === '' && !game.scrape) || (gameId.length >= 1 && admin) ? (
|
||||||
<Box mt="5" mb="5">
|
<Box mt="5" mb="5">
|
||||||
<FormLabel>Operating Systems</FormLabel>
|
<Text fontWeight="medium" mb="2">Operating Systems</Text>
|
||||||
<Box
|
<Box
|
||||||
bg={bg}
|
bg={bg}
|
||||||
border="1px"
|
borderWidth="1px" borderStyle="solid"
|
||||||
borderColor="gray.500"
|
borderColor="gray.500"
|
||||||
rounded="md"
|
rounded="md"
|
||||||
p="2">
|
p="2">
|
||||||
<Stack spacing={[1, 5]} direction={['column', 'row']}>
|
<Stack gap={[1, 5]} direction={['column', 'row']}>
|
||||||
<CheckboxInput label={'Windows'} textField={'windows'} checked={game.os.windows}
|
<CheckboxInput label={'Windows'} textField={'windows'} checked={game.os.windows}
|
||||||
defaultIsChecked={true} handleOSChange={handleOSChange}/>
|
defaultIsChecked={true} handleOSChange={handleOSChange}/>
|
||||||
<CheckboxInput label={'Mac OSX'} textField={'mac'} checked={game.os.mac}
|
<CheckboxInput label={'Mac OSX'} textField={'mac'} checked={game.os.mac}
|
||||||
@@ -518,9 +516,9 @@ const GameForm = () => {
|
|||||||
</>
|
</>
|
||||||
{(gameId === '' && !game.scrape) || (gameId.length >= 1 && admin) ? (
|
{(gameId === '' && !game.scrape) || (gameId.length >= 1 && admin) ? (
|
||||||
<>
|
<>
|
||||||
<Text align="center" fontSize="xl" mt="10">System Requirements</Text>
|
<Text textAlign="center" fontSize="xl" mt="10">System Requirements</Text>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fontSize="lg" align="center">Windows</Text>
|
<Text fontSize="lg" textAlign="center">Windows</Text>
|
||||||
<SystemRequirementsTextareaInput label={'Windows Minimum'}
|
<SystemRequirementsTextareaInput label={'Windows Minimum'}
|
||||||
textField={'systemRequirements.windows.minimum'}
|
textField={'systemRequirements.windows.minimum'}
|
||||||
field={game.systemRequirements.windows.minimum} bg={bg}
|
field={game.systemRequirements.windows.minimum} bg={bg}
|
||||||
@@ -532,7 +530,7 @@ const GameForm = () => {
|
|||||||
bg={bg}
|
bg={bg}
|
||||||
handleSystemRequirements={handleSystemRequirements}
|
handleSystemRequirements={handleSystemRequirements}
|
||||||
rows={3}/>
|
rows={3}/>
|
||||||
<Text fontSize="lg" align="center">Mac OSX</Text>
|
<Text fontSize="lg" textAlign="center">Mac OSX</Text>
|
||||||
<SystemRequirementsTextareaInput label={'Mac OSX Minimum'}
|
<SystemRequirementsTextareaInput label={'Mac OSX Minimum'}
|
||||||
textField={'systemRequirements.mac.minimum'}
|
textField={'systemRequirements.mac.minimum'}
|
||||||
field={game.systemRequirements.mac.minimum} bg={bg}
|
field={game.systemRequirements.mac.minimum} bg={bg}
|
||||||
@@ -543,7 +541,7 @@ const GameForm = () => {
|
|||||||
field={game.systemRequirements.mac.recommended} bg={bg}
|
field={game.systemRequirements.mac.recommended} bg={bg}
|
||||||
handleSystemRequirements={handleSystemRequirements}
|
handleSystemRequirements={handleSystemRequirements}
|
||||||
rows={3}/>
|
rows={3}/>
|
||||||
<Text fontSize="lg" align="center">Linux</Text>
|
<Text fontSize="lg" textAlign="center">Linux</Text>
|
||||||
<SystemRequirementsTextareaInput label={'Linux Minimum'}
|
<SystemRequirementsTextareaInput label={'Linux Minimum'}
|
||||||
textField={'systemRequirements.linux.minimum'}
|
textField={'systemRequirements.linux.minimum'}
|
||||||
field={game.systemRequirements.linux.minimum} bg={bg}
|
field={game.systemRequirements.linux.minimum} bg={bg}
|
||||||
@@ -558,18 +556,18 @@ const GameForm = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
) : ''}
|
) : ''}
|
||||||
<Stack spacing={[1, 5]} direction={{base: 'column', sm: 'row'}} align={{base: 'start'}}>
|
<Stack gap={[1, 5]} direction={{base: 'column', sm: 'row'}} align={{base: 'start'}}>
|
||||||
<Button
|
<Button
|
||||||
isLoading={submitLoading}
|
loading={submitLoading}
|
||||||
colorScheme="blue"
|
colorPalette="blue"
|
||||||
type="submit"
|
type="submit"
|
||||||
data-name={"save"}
|
data-name={"save"}
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
isLoading={moveOn}
|
loading={moveOn}
|
||||||
colorScheme="green"
|
colorPalette="green"
|
||||||
type="submit"
|
type="submit"
|
||||||
data-name={"save-and-view"}
|
data-name={"save-and-view"}
|
||||||
>
|
>
|
||||||
@@ -579,7 +577,7 @@ const GameForm = () => {
|
|||||||
<>
|
<>
|
||||||
<Spacer/>
|
<Spacer/>
|
||||||
<Button
|
<Button
|
||||||
colorScheme="red"
|
colorPalette="red"
|
||||||
onMouseDown={() => {
|
onMouseDown={() => {
|
||||||
openState.set(true)
|
openState.set(true)
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Checkbox, Text } from '@chakra-ui/react'
|
import { Text } from '@chakra-ui/react'
|
||||||
|
import {Checkbox} from '../ui/checkbox'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
label: string
|
label: string
|
||||||
@@ -12,13 +13,14 @@ interface Props {
|
|||||||
const CheckboxInput = ({ label, textField, checked, defaultIsChecked, handleOSChange }: Props) => {
|
const CheckboxInput = ({ label, textField, checked, defaultIsChecked, handleOSChange }: Props) => {
|
||||||
return (
|
return (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
colorPalette="blue"
|
||||||
defaultChecked={defaultIsChecked}
|
defaultChecked={defaultIsChecked}
|
||||||
isChecked={checked}
|
checked={checked}
|
||||||
onChange={() => {
|
onCheckedChange={() => {
|
||||||
handleOSChange(textField)
|
handleOSChange(textField)
|
||||||
}}
|
}}
|
||||||
name={textField}
|
name={textField}
|
||||||
spacing="1rem"
|
gap="1rem"
|
||||||
><Text mt="1">{label}</Text></Checkbox>
|
><Text mt="1">{label}</Text></Checkbox>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Box, FormControl, FormLabel } from '@chakra-ui/react'
|
import { Box } from '@chakra-ui/react'
|
||||||
import { CreatableSelect } from 'chakra-react-select'
|
import { Field } from '../ui/field'
|
||||||
|
import { CreatableSelect } from '../FixedSelect'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
label: string
|
label: string
|
||||||
@@ -13,13 +14,12 @@ interface Props {
|
|||||||
|
|
||||||
const CreatableSelectInput = ({ label, textField, value, bg, handleInputChange, options}: Props) => {
|
const CreatableSelectInput = ({ label, textField, value, bg, handleInputChange, options}: Props) => {
|
||||||
return (
|
return (
|
||||||
<FormControl mb="3">
|
<Field mb="3" label={label}>
|
||||||
<FormLabel>{label}</FormLabel>
|
|
||||||
<Box
|
<Box
|
||||||
bg={bg}
|
bg={bg}
|
||||||
border="1px"
|
rounded="md"
|
||||||
borderColor="gray.500"
|
w="full"
|
||||||
rounded="md">
|
>
|
||||||
<CreatableSelect
|
<CreatableSelect
|
||||||
name={textField}
|
name={textField}
|
||||||
isClearable
|
isClearable
|
||||||
@@ -39,7 +39,7 @@ const CreatableSelectInput = ({ label, textField, value, bg, handleInputChange,
|
|||||||
options={options}
|
options={options}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</FormControl>
|
</Field>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Box, FormControl, FormLabel} from '@chakra-ui/react'
|
import {Box} from '@chakra-ui/react'
|
||||||
import {Select} from 'chakra-react-select'
|
import { Field } from '../ui/field'
|
||||||
|
import {Select} from '../FixedSelect'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
label: string
|
label: string
|
||||||
@@ -14,18 +15,15 @@ interface Props {
|
|||||||
|
|
||||||
const SelectInput = ({label, textField, field, bg, handleAccessedBy, options, width = ''}: Props) => {
|
const SelectInput = ({label, textField, field, bg, handleAccessedBy, options, width = ''}: Props) => {
|
||||||
return (
|
return (
|
||||||
<FormControl mb="3">
|
<Field mb="3" label={label}>
|
||||||
<FormLabel>{label}</FormLabel>
|
|
||||||
<Box
|
<Box
|
||||||
bg={bg}
|
bg={bg}
|
||||||
w={width}
|
w={width || 'full'}
|
||||||
border="1px"
|
|
||||||
borderColor="gray.500"
|
|
||||||
rounded="md">
|
rounded="md">
|
||||||
<Select
|
<Select
|
||||||
id={textField}
|
id={textField}
|
||||||
name={textField}
|
name={textField}
|
||||||
onChange={(values) => {
|
onChange={(values: any) => {
|
||||||
if (values) handleAccessedBy(textField, values.value)
|
if (values) handleAccessedBy(textField, values.value)
|
||||||
}}
|
}}
|
||||||
options={options}
|
options={options}
|
||||||
@@ -35,7 +33,7 @@ const SelectInput = ({label, textField, field, bg, handleAccessedBy, options, wi
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</FormControl>
|
</Field>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react'
|
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'
|
import { Data } from '../../models/game'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -14,15 +15,14 @@ interface Props {
|
|||||||
const SingleFieldInput = ({ label, textField, field, bg, handleInputChange, required }: Props) => {
|
const SingleFieldInput = ({ label, textField, field, bg, handleInputChange, required }: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl mb="3">
|
<Field mb="3" label={label}>
|
||||||
<FormLabel>{label}</FormLabel>
|
|
||||||
<Input
|
<Input
|
||||||
id={textField}
|
id={textField}
|
||||||
name={textField}
|
name={textField}
|
||||||
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"
|
||||||
value={field}
|
value={field}
|
||||||
required={required}
|
required={required}
|
||||||
@@ -30,7 +30,7 @@ const SingleFieldInput = ({ label, textField, field, bg, handleInputChange, requ
|
|||||||
handleInputChange(textField, e.target.value)
|
handleInputChange(textField, e.target.value)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</Field>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,63 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import { FormControl, FormLabel, Input } from '@chakra-ui/react'
|
import { Input } from "@chakra-ui/react";
|
||||||
import { Data } from '../../models/game'
|
import { Field } from "../ui/field";
|
||||||
|
import { Data } from "../../models/game";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
label: string
|
label: string;
|
||||||
textField: string
|
textField: string;
|
||||||
field: string
|
field: string;
|
||||||
bg: string
|
bg: string;
|
||||||
handleInputChange: <G extends keyof Data>(name: string, value: Data[G]) => void
|
handleInputChange: <G extends keyof Data>(
|
||||||
required: boolean
|
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 (
|
return (
|
||||||
<FormControl mb="3">
|
<Field
|
||||||
<FormLabel>{label}</FormLabel>
|
mb="3"
|
||||||
|
label={label}
|
||||||
|
errorText="Enter a Steam App Id or a Steam App URL"
|
||||||
|
>
|
||||||
<Input
|
<Input
|
||||||
id={textField}
|
id={textField}
|
||||||
name={textField}
|
name={textField}
|
||||||
bg={bg}
|
bg={bg}
|
||||||
type="text"
|
type="text"
|
||||||
inputMode="numeric"
|
inputMode="text"
|
||||||
pattern="[0-9]*"
|
aria-invalid={showError}
|
||||||
border="1px"
|
placeholder="Steam Id or Steam URL"
|
||||||
borderColor="gray.500"
|
borderWidth="1px"
|
||||||
|
borderStyle="solid"
|
||||||
|
borderColor={{ base: "gray.500", _invalid: "red.500" }}
|
||||||
rounded="md"
|
rounded="md"
|
||||||
value={field}
|
value={field}
|
||||||
required={required}
|
required={required}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
handleInputChange(textField, e.target.value)
|
handleInputChange(textField, e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</Field>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default SingleFieldNumericInput
|
export default SingleFieldNumericInput;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, {useMemo, useRef} from 'react'
|
import React, {useMemo, useRef} from 'react'
|
||||||
import {FormControl, FormLabel} from '@chakra-ui/react'
|
import {Field} from '../ui/field'
|
||||||
import JoditEditor from "jodit-react";
|
import JoditEditor from "jodit-react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -24,17 +24,16 @@ const SystemRequirementsTextareaInput = ({label, textField, field, handleSystemR
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl mb="3">
|
<Field mb="3" label={label}>
|
||||||
<FormLabel>{label}</FormLabel>
|
|
||||||
<JoditEditor
|
<JoditEditor
|
||||||
ref={editor}
|
ref={editor}
|
||||||
value={field}
|
value={field}
|
||||||
config={config}
|
config={config}
|
||||||
onChange={(value) => {
|
onChange={(value: string) => {
|
||||||
handleSystemRequirements(arrText[1], arrText[2], value)
|
handleSystemRequirements(arrText[1], arrText[2], value)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</Field>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, {useMemo, useRef} from 'react'
|
import React, {useMemo, useRef} from 'react'
|
||||||
import { FormControl, FormLabel } from '@chakra-ui/react'
|
import { Field } from '../ui/field'
|
||||||
import { Data } from '../../models/game'
|
import { Data } from '../../models/game'
|
||||||
import JoditEditor from "jodit-react";
|
import JoditEditor from "jodit-react";
|
||||||
|
|
||||||
@@ -25,17 +25,16 @@ const TextareaInput = ({ label, textField, field, handleInputChange }: Props) =>
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl mb="3">
|
<Field mb="3" label={label}>
|
||||||
<FormLabel>{label}</FormLabel>
|
|
||||||
<JoditEditor
|
<JoditEditor
|
||||||
ref={editor}
|
ref={editor}
|
||||||
value={field}
|
value={field}
|
||||||
config={config}
|
config={config}
|
||||||
onChange={(value) => {
|
onChange={(value: string) => {
|
||||||
handleInputChange(textField, value)
|
handleInputChange(textField, value)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</Field>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user