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:
parent
87a4dcefdd
commit
0b9b2f4dbc
@ -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",
|
||||
|
@ -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": "Хэтчбек",
|
||||
|
@ -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
|
162
src/components/order-form/form/car-color/car-color-select.tsx
Normal file
162
src/components/order-form/form/car-color/car-color-select.tsx
Normal 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>
|
||||
);
|
||||
},
|
||||
);
|
@ -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 }[];
|
@ -1 +1 @@
|
||||
export { CarColorInput } from './car-color-input';
|
||||
export { CarColorSelect } from './car-color-select';
|
@ -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}
|
||||
|
Loading…
x
Reference in New Issue
Block a user