Merge pull request 'fix: option select (#55)' (#70) from feature/graphic-car-body-select into main
All checks were successful
it-academy/dry-wash-pl/pipeline/head This commit looks good

Reviewed-on: #70
Reviewed-by: Primakov Alexandr Alexandrovich <primakovpro@gmail.com>
This commit is contained in:
Rustam 2025-01-26 15:26:03 +03:00
commit 1471bf94dd
9 changed files with 120 additions and 50 deletions

View File

@ -3,6 +3,7 @@ export { default as CrossoverImg } from './crossover.webp';
export { default as HatchbackImg } from './hatchback.webp'; export { default as HatchbackImg } from './hatchback.webp';
export { default as LiftbackImg } from './liftback.webp'; export { default as LiftbackImg } from './liftback.webp';
export { default as MinivanImg } from './minivan.webp'; export { default as MinivanImg } from './minivan.webp';
export { default as OtherImg } from './other.webp';
export { default as PickupImg } from './pickup.webp'; export { default as PickupImg } from './pickup.webp';
export { default as SedanImg } from './sedan.webp'; export { default as SedanImg } from './sedan.webp';
export { default as SportsCarImg } from './sports-car.webp'; export { default as SportsCarImg } from './sports-car.webp';

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,34 +1,38 @@
import React, { forwardRef, useState } from 'react'; import React, { forwardRef } from 'react';
import { import {
Input, Input,
Image,
InputProps,
Box, Box,
Popover, Popover,
PopoverAnchor, PopoverAnchor,
PopoverContent, PopoverContent,
PopoverBody, PopoverBody,
List, useRadioGroup,
ListItem, Grid,
GridItem,
UseRadioGroupProps,
useDisclosure,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { carBodySelectOptions } from './helper'; import { carBodySelectOptions, getInputValue } from './helper';
import { CarBodySelectOption } from './types'; import { CarBodyOption } from './option';
import { CarBodySelectProps } from './types';
export const CarBodySelect = forwardRef<HTMLInputElement, InputProps>( export const CarBodySelect = forwardRef<HTMLInputElement, CarBodySelectProps>(
function CarBodySelect(props, ref) { function CarBodySelect(props, ref) {
const initialOption = carBodySelectOptions.find(({ value }) => value === Number(props.value)); const handleOptionClick: UseRadioGroupProps['onChange'] = (value) => {
const [selected, setSelected] = useState<Partial<CarBodySelectOption>>(initialOption);
const handleOptionClick = (option: CarBodySelectOption) => {
setSelected(option);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
props.onChange(option.value); props.onChange(value);
}; };
const [isDropdownOpen, setIsDropdownOpen] = useState<boolean>(false); const { value, getRadioProps, getRootProps } = useRadioGroup({
defaultValue: props.value,
value: props.value,
onChange: handleOptionClick,
});
const { isOpen, onOpen, onClose } = useDisclosure();
const { t } = useTranslation('~', { const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.order-create.car-body-select', keyPrefix: 'dry-wash.order-create.car-body-select',
@ -37,7 +41,7 @@ export const CarBodySelect = forwardRef<HTMLInputElement, InputProps>(
return ( return (
<Box width='100%'> <Box width='100%'>
<Popover <Popover
isOpen={isDropdownOpen} isOpen={isOpen}
autoFocus={false} autoFocus={false}
placement='bottom-start' placement='bottom-start'
matchWidth matchWidth
@ -46,45 +50,29 @@ export const CarBodySelect = forwardRef<HTMLInputElement, InputProps>(
<Input <Input
{...props} {...props}
ref={ref} ref={ref}
value={ value={getInputValue(Number(value), t) ?? props.value}
selected?.labelTKey
? t(`options.${selected.labelTKey}`)
: undefined
}
readOnly readOnly
onClick={() => setIsDropdownOpen(true)} onClick={onOpen}
onBlur={() => setIsDropdownOpen(false)} onBlur={onClose}
placeholder={t('placeholder')} placeholder={t('placeholder')}
/> />
</PopoverAnchor> </PopoverAnchor>
<PopoverContent width='100%' maxWidth='100%'> <PopoverContent width='100%' maxWidth='100%'>
<PopoverBody border='1px' borderColor='gray.300' p={0}> <PopoverBody border='1px' borderColor='gray.300' p={0}>
<List <Grid
display='grid' templateColumns='repeat(auto-fit, minmax(150px, 1fr))'
gridTemplateColumns='repeat(auto-fit, minmax(150px, 1fr))' {...getRootProps()}
> >
{carBodySelectOptions.map((option) => ( {carBodySelectOptions.map(({ value, img, labelTKey }) => (
<ListItem <GridItem key={value}>
key={option.value} <CarBodyOption
display='flex' image={img}
flexDirection='column' label={t(`options.${labelTKey}`)}
justifyContent='flex-end' {...getRadioProps({ value: String(value) })}
alignItems='center' />
p={2} </GridItem>
cursor='pointer'
_hover={{
bgColor: 'primary.50',
}}
_active={{
bgColor: 'primary.100',
}}
onClick={() => handleOptionClick(option)}
>
<Image src={option.img} />
{t(`options.${option.labelTKey}`)}
</ListItem>
))} ))}
</List> </Grid>
</PopoverBody> </PopoverBody>
</PopoverContent> </PopoverContent>
</Popover> </Popover>

View File

@ -1,3 +1,6 @@
import { TFunction } from "i18next";
import { InputProps } from "@chakra-ui/react";
import { import {
CoupeImg, CoupeImg,
CrossoverImg, CrossoverImg,
@ -8,7 +11,8 @@ import {
SedanImg, SedanImg,
SportsCarImg, SportsCarImg,
StationWagonImg, StationWagonImg,
SuvImg SuvImg,
OtherImg
} from "../../../../assets/images"; } from "../../../../assets/images";
import { Car } from "../../../../models/landing"; import { Car } from "../../../../models/landing";
@ -67,6 +71,17 @@ export const carBodySelectOptions: CarBodySelectOption[] = [
}, },
{ {
value: Car.BodyStyle.OTHER, value: Car.BodyStyle.OTHER,
labelTKey: 'other' labelTKey: 'other',
img: OtherImg
}, },
]; ];
export const getInputValue = (value: Car.BodyStyle, t: TFunction<"~", "dry-wash.order-create.car-body-select">): InputProps['value'] => {
const { labelTKey } = carBodySelectOptions.find((option) => value === option.value) ?? {};
if (labelTKey) {
return t(`options.${labelTKey}`);
}
return;
};

View File

@ -0,0 +1,39 @@
import React from 'react';
import {
ImageProps,
StackProps,
Image,
useRadio,
chakra,
Box,
UseRadioProps,
Flex,
} from '@chakra-ui/react';
import { getPropsByState } from './helper';
type CarBodyOptionProps = {
image: ImageProps['src'];
label: StackProps['children'];
} & UseRadioProps;
export const CarBodyOption = ({
image,
label,
...radioProps
}: CarBodyOptionProps) => {
const { state, getInputProps, getRadioProps, htmlProps, getLabelProps } =
useRadio(radioProps);
return (
<chakra.label {...htmlProps} cursor='pointer'>
<input {...getInputProps({})} hidden />
<Box {...getRadioProps()} p={2} {...getPropsByState(state)}>
<Flex direction='column' alignItems='center' {...getLabelProps()}>
<Image src={image} rounded={4} />
{label}
</Flex>
</Box>
</chakra.label>
);
};

View File

@ -0,0 +1,19 @@
import {
BoxProps,
} from '@chakra-ui/react';
import { RadioState } from '@chakra-ui/react/dist/types/radio/use-radio';
export const getPropsByState = ({ isChecked }: RadioState): BoxProps => {
if (isChecked) {
return {
bgColor: 'primary.200',
_hover: { bgColor: 'primary.100' },
_active: { bgColor: 'primary.300' },
};
}
return {
_hover: { bgColor: 'primary.50' },
_active: { bgColor: 'primary.100' },
};
};

View File

@ -0,0 +1 @@
export { CarBodyOption } from './car-body-option';

View File

@ -1,3 +1,5 @@
import { InputProps } from "@chakra-ui/react";
import { Car } from "../../../../models/landing"; import { Car } from "../../../../models/landing";
export type CarBodySelectOption = { export type CarBodySelectOption = {
@ -16,3 +18,7 @@ export type CarBodySelectOption = {
'other'; 'other';
img?: string; img?: string;
}; };
export type CarBodySelectProps = {
value?: string;
} & Pick<InputProps, 'onChange'>;

View File

@ -9,6 +9,7 @@ export const defaultValues: Partial<OrderFormValues> = {
phone: '', phone: '',
carNumber: '', carNumber: '',
carColor: '', carColor: '',
carBody: '',
availableDatetimeBegin: '', availableDatetimeBegin: '',
availableDatetimeEnd: '', availableDatetimeEnd: '',
}; };