feat: Implement enhanced car color selection component

- Replaced CarColorInput with a new CarColorSelect component
- Added color selection with visual color indicators
- Implemented custom color input option
- Updated locales with color selection translations
- Improved type safety in helper file
This commit is contained in:
RustamRu 2025-03-08 20:35:20 +03:00
parent 87a4dcefdd
commit 0b9b2f4dbc
7 changed files with 189 additions and 28 deletions

View File

@ -26,6 +26,17 @@
"dry-wash.order-create.form.washing-location-field.label": "Where is the car located?",
"dry-wash.order-create.form.washing-location-field.placeholder": "Enter the address or select on the map",
"dry-wash.order-create.form.washing-location-field.help": "For example, 55.754364, 48.743295 Universitetskaya Street, 1, Innopolis, Verkhneuslonsky district, Republic of Tatarstan (Tatarstan), 420500",
"dry-wash.order-create.car-color-select.placeholder": "Input color",
"dry-wash.order-create.car-color-select.custom": "Custom",
"dry-wash.order-create.car-color-select.custom-label": "Custom:",
"dry-wash.order-create.car-color-select.colors.white": "White",
"dry-wash.order-create.car-color-select.colors.black": "Black",
"dry-wash.order-create.car-color-select.colors.silver": "Silver",
"dry-wash.order-create.car-color-select.colors.gray": "Gray",
"dry-wash.order-create.car-color-select.colors.beige-brown": "Beige Brown",
"dry-wash.order-create.car-color-select.colors.red": "Red",
"dry-wash.order-create.car-color-select.colors.blue": "Blue",
"dry-wash.order-create.car-color-select.colors.green": "Green",
"dry-wash.order-create.car-body-select.placeholder": "Not specified",
"dry-wash.order-create.car-body-select.options.sedan": "Sedan",
"dry-wash.order-create.car-body-select.options.hatchback" : "Hatchback",

View File

@ -81,6 +81,17 @@
"dry-wash.order-create.form.washing-location-field.label": "Где находится автомобиль?",
"dry-wash.order-create.form.washing-location-field.placeholder": "Введите адрес или выберите на карте",
"dry-wash.order-create.form.washing-location-field.help": "Например, 55.754364, 48.743295 Университетская улица, 1, Иннополис, Верхнеуслонский район, Республика Татарстан (Татарстан), 420500",
"dry-wash.order-create.car-color-select.placeholder": "Введите цвет",
"dry-wash.order-create.car-color-select.custom": "Другой",
"dry-wash.order-create.car-color-select.custom-label": "Другой:",
"dry-wash.order-create.car-color-select.colors.white": "Белый",
"dry-wash.order-create.car-color-select.colors.black": "Черный",
"dry-wash.order-create.car-color-select.colors.silver": "Серебристый",
"dry-wash.order-create.car-color-select.colors.gray": "Серый",
"dry-wash.order-create.car-color-select.colors.beige-brown": "Бежево-коричневый",
"dry-wash.order-create.car-color-select.colors.red": "Красный",
"dry-wash.order-create.car-color-select.colors.blue": "Синий",
"dry-wash.order-create.car-color-select.colors.green": "Зеленый",
"dry-wash.order-create.car-body-select.placeholder": "Не указан",
"dry-wash.order-create.car-body-select.options.sedan": "Седан",
"dry-wash.order-create.car-body-select.options.hatchback": "Хэтчбек",

View File

@ -1,23 +0,0 @@
import React, { forwardRef, useId } from 'react';
import { Input, InputProps } from '@chakra-ui/react';
import { CAR_COLORS } from './helper';
export const CarColorInput = forwardRef<HTMLInputElement, InputProps>(
function CarColorInput(props, ref) {
const listId = useId();
return (
<>
<Input ref={ref} list={listId} {...props} />
<datalist id={listId}>
{CAR_COLORS.map(({ code, name }) => (
<option key={code} label={name} value={code}>{name}</option>
))}
</datalist>
</>
);
},
);
// todo: add option color visual indication

View File

@ -0,0 +1,162 @@
import React, { forwardRef, useState } from 'react';
import {
Input,
Box,
Stack,
Text,
Flex,
} from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { CAR_COLORS } from './helper';
interface CarColorSelectProps {
value?: string;
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
name?: string;
isInvalid?: boolean;
}
export const CarColorSelect = forwardRef<HTMLInputElement, CarColorSelectProps>(
function CarColorSelect(props) {
const [customColor, setCustomColor] = useState('');
const [isCustom, setIsCustom] = useState(false);
const handleColorChange = (value: string) => {
if (value === 'custom') {
setIsCustom(true);
return;
}
setIsCustom(false);
props.onChange?.({
target: { value },
} as React.ChangeEvent<HTMLInputElement>);
};
const handleCustomColorChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setCustomColor(value);
props.onChange?.({
target: { value },
} as React.ChangeEvent<HTMLInputElement>);
};
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.order-create.car-color-select',
});
const currentValue = isCustom ? 'custom' : props.value;
return (
<Stack spacing={4} width="100%">
<Flex gap={3} wrap="nowrap" overflowX="auto" pb={2}>
{CAR_COLORS.map(({ name, code }) => (
<Box
key={name}
flexShrink={0}
as="button"
type="button"
onClick={() => handleColorChange(name)}
>
<Flex
align="center"
gap={2}
p={2}
borderRadius="full"
borderWidth="2px"
borderColor={currentValue === name ? 'primary.500' : 'gray.200'}
bg={currentValue === name ? 'primary.50' : 'white'}
_hover={{
borderColor: 'primary.500',
bg: currentValue === name ? 'primary.50' : 'gray.50'
}}
minW={currentValue === name ? '120px' : 'auto'}
h="48px"
justify="center"
transition="all 0.2s"
>
<Flex align="center" gap={2}>
<Box
w="32px"
h="32px"
borderRadius="full"
bg={code}
border="1px"
borderColor={currentValue === name ? 'primary.500' : 'gray.200'}
transition="all 0.2s"
boxShadow={currentValue === name ? 'sm' : 'none'}
/>
{currentValue === name && (
<Text fontSize="xs" color="primary.700" fontWeight="medium">
{t(`colors.${name}`)}
</Text>
)}
</Flex>
</Flex>
</Box>
))}
<Box
flexShrink={0}
as="button"
type="button"
onClick={() => handleColorChange('custom')}
>
<Flex
align="center"
gap={2}
p={2}
borderRadius="full"
borderWidth="2px"
borderColor={isCustom ? 'primary.500' : 'gray.200'}
bg={isCustom ? 'primary.50' : 'white'}
_hover={{
borderColor: 'primary.500',
bg: isCustom ? 'primary.50' : 'gray.50'
}}
minW={isCustom ? '200px' : 'auto'}
h="48px"
justify="center"
transition="all 0.2s"
>
{isCustom ? (
<Flex gap={2} align="center">
<Text fontSize="xs" color="primary.700" fontWeight="medium">
{t('custom-label')}
</Text>
<Input
size="sm"
width="120px"
value={customColor}
onChange={handleCustomColorChange}
placeholder={t('placeholder')}
onClick={(e) => e.stopPropagation()}
borderColor="primary.200"
_focus={{
borderColor: 'primary.500',
boxShadow: '0 0 0 1px var(--chakra-colors-primary-500)'
}}
/>
</Flex>
) : (
<Flex align="center" gap={2}>
<Box
w="32px"
h="32px"
borderRadius="full"
bg="gray.100"
border="1px"
borderColor="gray.200"
transition="all 0.2s"
/>
<Text fontSize="xs" color="gray.500">
{t('custom')}
</Text>
</Flex>
)}
</Flex>
</Box>
</Flex>
</Stack>
);
},
);

View File

@ -1,4 +1,4 @@
export const CAR_COLORS: Record<'name' | 'code', string>[] = [
export const CAR_COLORS = [
{
name: 'white',
code: '#ffffff'
@ -31,4 +31,4 @@ export const CAR_COLORS: Record<'name' | 'code', string>[] = [
name: 'green',
code: '#078d51'
},
];
] as const satisfies { name: string; code: string }[];

View File

@ -1 +1 @@
export { CarColorInput } from './car-color-input';
export { CarColorSelect } from './car-color-select';

View File

@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next';
import { Box, Flex, FormControl, FormLabel, VStack } from '@chakra-ui/react';
import { CarBodySelect } from './car-body';
import { CarColorInput } from './car-color';
import { CarNumberInput } from './car-number';
import { FormInputField, FormControllerField } from './field';
import { OrderFormProps, OrderFormValues } from './types';
@ -18,6 +17,7 @@ import {
StringLocation,
YMapsProvider,
} from './location';
import { CarColorSelect } from './car-color';
export const OrderForm = ({ onSubmit, loading, ...props }: OrderFormProps) => {
const {
@ -72,7 +72,7 @@ export const OrderForm = ({ onSubmit, loading, ...props }: OrderFormProps) => {
name='carColor'
label={t('car-color-field.label')}
errors={errors}
Input={CarColorInput}
Input={CarColorSelect}
/>
<FormInputField
control={control}