From 7bdf42ac077e01ac9c3d03fb5da0606d840a6a1e Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sun, 6 Sep 2026 10:29:41 -0400 Subject: [PATCH] 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 --- src/components/FixedSelect.tsx | 140 ++++++++++++++++++ src/components/GameForm.tsx | 88 ++++++----- src/components/formFields/CheckboxInput.tsx | 12 +- .../formFields/CreatableSelectInput.tsx | 18 +-- src/components/formFields/SelectInput.tsx | 18 +-- .../formFields/SingleFieldInput.tsx | 14 +- .../formFields/SingleFieldNumericInput.tsx | 66 ++++++--- .../SystemRequirementsTextareaInput.tsx | 11 +- src/components/formFields/TextareaInput.tsx | 11 +- 9 files changed, 269 insertions(+), 109 deletions(-) create mode 100644 src/components/FixedSelect.tsx 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/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}
- + - + {!id || admin ? ( + + ) : ''} {!id && ( - - Steam Scrape + + Steam Scrape { + colorPalette="blue" + checked={game.scrape} + onCheckedChange={() => { handleToggle('scrape') }} name="scrape" id="scrape" /> - + )} - + {(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 { ) : ''} - +