новый ответ на вопрос получает фокус

This commit is contained in:
Nastya 2025-09-03 20:25:14 +03:00
parent 751d9eb4f3
commit 862ed4f395
16 changed files with 144 additions and 67 deletions

@ -12,7 +12,7 @@ import {
} from "@mui/material"; } from "@mui/material";
import { addQuestionVariant, deleteQuestionVariant, setQuestionVariantField, updateQuestion } from "@root/questions/actions"; import { addQuestionVariant, deleteQuestionVariant, setQuestionVariantField, updateQuestion } from "@root/questions/actions";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
import { memo, type ChangeEvent, type FC, type KeyboardEvent, type ReactNode } from "react"; import { memo, useCallback, type ChangeEvent, type FC, type KeyboardEvent, type ReactNode } from "react";
import { Draggable } from "react-beautiful-dnd"; import { Draggable } from "react-beautiful-dnd";
import type { QuestionVariant, QuizQuestionVariant } from "@frontend/squzanswerer"; import type { QuestionVariant, QuizQuestionVariant } from "@frontend/squzanswerer";
@ -28,10 +28,13 @@ type AnswerItemProps = {
additionalMobile?: ReactNode; additionalMobile?: ReactNode;
isOwn: boolean; isOwn: boolean;
ownPlaceholder: string; ownPlaceholder: string;
shouldAutoFocus?: boolean;
onFocusHandled?: () => void;
onEnterKeyPress?: () => void;
}; };
const AnswerItem = memo<AnswerItemProps>( const AnswerItem = memo<AnswerItemProps>(
({ index, variant, questionId, largeCheck = false, additionalContent, additionalMobile, disableKeyDown, isOwn, ownPlaceholder }) => { ({ index, variant, questionId, largeCheck = false, additionalContent, additionalMobile, disableKeyDown, isOwn, ownPlaceholder, shouldAutoFocus, onFocusHandled, onEnterKeyPress }) => {
const theme = useTheme(); const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(790)); const isTablet = useMediaQuery(theme.breakpoints.down(790));
@ -41,6 +44,26 @@ const AnswerItem = memo<AnswerItemProps>(
}); });
}; };
const inputRefCallback = useCallback((element: HTMLInputElement | null) => {
if (element && shouldAutoFocus) {
element.focus();
onFocusHandled?.();
}
}, [shouldAutoFocus, onFocusHandled]);
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
if (disableKeyDown) {
enqueueSnackbar("100 максимальное количество");
} else if (event.code === "Enter" && !largeCheck) {
if (onEnterKeyPress) {
onEnterKeyPress();
} else {
// Fallback если onEnterKeyPress не передан
addQuestionVariant(questionId);
}
}
};
return ( return (
<Draggable <Draggable
@ -64,10 +87,11 @@ const AnswerItem = memo<AnswerItemProps>(
}} }}
> >
<TextField <TextField
inputRef={inputRefCallback}
value={isOwn ? ownPlaceholder : variant.answer} value={isOwn ? ownPlaceholder : variant.answer}
fullWidth fullWidth
focused={false} focused={false}
placeholder={isOwn ? "Добавьте текст-подсказку для ввода “своего ответа”" : "Добавьте ответ"} placeholder={isOwn ? "Добавьте текст-подсказку для ввода \"своего ответа\"" : "Добавьте ответ"}
multiline={largeCheck} multiline={largeCheck}
onChange={({ target }: ChangeEvent<HTMLInputElement>) => { onChange={({ target }: ChangeEvent<HTMLInputElement>) => {
if (target.value.length <= 1000) { if (target.value.length <= 1000) {
@ -79,13 +103,7 @@ const AnswerItem = memo<AnswerItemProps>(
enqueueSnackbar("Превышена длина вводимого текста") enqueueSnackbar("Превышена длина вводимого текста")
} }
}} }}
onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => { onKeyDown={handleKeyDown}
if (disableKeyDown) {
enqueueSnackbar("100 максимальное количество");
} else if (event.code === "Enter" && !largeCheck) {
addQuestionVariant(questionId);
}
}}
InputProps={{ InputProps={{
startAdornment: ( startAdornment: (
<> <>

@ -16,6 +16,9 @@ type Props = Omit<
openImageUploadModal: () => void; openImageUploadModal: () => void;
isOwn: boolean; isOwn: boolean;
ownPlaceholder: string; ownPlaceholder: string;
shouldAutoFocus?: boolean;
onFocusHandled?: () => void;
onEnterKeyPress?: () => void;
}; };
export default function ImageEditAnswerItem({ export default function ImageEditAnswerItem({
@ -31,6 +34,9 @@ export default function ImageEditAnswerItem({
openImageUploadModal, openImageUploadModal,
isOwn, isOwn,
ownPlaceholder, ownPlaceholder,
shouldAutoFocus,
onFocusHandled,
onEnterKeyPress,
}: Props) { }: Props) {
const addOrEditImageButton = useMemo(() => { const addOrEditImageButton = useMemo(() => {
return ( return (
@ -111,6 +117,9 @@ export default function ImageEditAnswerItem({
additionalMobile={addOrEditImageButtonMobile} additionalMobile={addOrEditImageButtonMobile}
isOwn={isOwn} isOwn={isOwn}
ownPlaceholder={ownPlaceholder} ownPlaceholder={ownPlaceholder}
shouldAutoFocus={shouldAutoFocus}
onFocusHandled={onFocusHandled}
onEnterKeyPress={onEnterKeyPress}
/> />
); );
} }

@ -1,7 +1,7 @@
import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material"; import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { EnterIcon } from "@/assets/icons/questionsPage/enterIcon"; import { EnterIcon } from "@/assets/icons/questionsPage/enterIcon";
import { useAddAnswer } from "../../../utils/hooks/useAddAnswer"; import { useQuestionVariantsWithFocus } from "../../../utils/questionVariants";
import { AnswerDraggableList } from "../AnswerDraggableList"; import { AnswerDraggableList } from "../AnswerDraggableList";
import ButtonsOptions from "../QuestionOptions/ButtonsLayout/ButtonsOptions"; import ButtonsOptions from "../QuestionOptions/ButtonsLayout/ButtonsOptions";
import SwitchDropDown from "./switchDropDown"; import SwitchDropDown from "./switchDropDown";
@ -16,7 +16,7 @@ interface Props {
} }
export default function DropDown({ question, openBranchingPage, setOpenBranchingPage }: Props) { export default function DropDown({ question, openBranchingPage, setOpenBranchingPage }: Props) {
const {onClickAddAnAnswer} = useAddAnswer(); const {addVariantWithFocus, addVariantOnEnter, focusedVariantId, clearFocusedVariant} = useQuestionVariantsWithFocus();
const [switchState, setSwitchState] = useState("setting"); const [switchState, setSwitchState] = useState("setting");
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
@ -50,6 +50,11 @@ export default function DropDown({ question, openBranchingPage, setOpenBranching
disableKeyDown={question.content.variants.length >= 100} disableKeyDown={question.content.variants.length >= 100}
questionId={question.id} questionId={question.id}
variant={variant} variant={variant}
isOwn={Boolean(variant?.isOwn)}
ownPlaceholder={question.content.ownPlaceholder || ""}
shouldAutoFocus={focusedVariantId === variant.id}
onFocusHandled={clearFocusedVariant}
onEnterKeyPress={() => addVariantOnEnter(question.id)}
/> />
))} ))}
/> />
@ -71,7 +76,7 @@ export default function DropDown({ question, openBranchingPage, setOpenBranching
mr: "4px", mr: "4px",
height: "19px", height: "19px",
}} }}
onClick={() => onClickAddAnAnswer(question)} onClick={() => addVariantWithFocus(question)}
> >
Добавьте ответ Добавьте ответ
</Link> </Link>

@ -4,7 +4,7 @@ import { EmojiPicker } from "@ui_kit/EmojiPicker";
import { useState } from "react"; import { useState } from "react";
import { EnterIcon } from "@/assets/icons/questionsPage/enterIcon"; import { EnterIcon } from "@/assets/icons/questionsPage/enterIcon";
import type { QuizQuestionEmoji } from "@frontend/squzanswerer"; import type { QuizQuestionEmoji } from "@frontend/squzanswerer";
import { useAddAnswer } from "../../../utils/hooks/useAddAnswer"; import { useQuestionVariantsWithFocus } from "../../../utils/questionVariants";
import { AnswerDraggableList } from "../AnswerDraggableList"; import { AnswerDraggableList } from "../AnswerDraggableList";
import ButtonsOptions from "../QuestionOptions/ButtonsLayout/ButtonsOptions"; import ButtonsOptions from "../QuestionOptions/ButtonsLayout/ButtonsOptions";
import EmojiAnswerItem from "./EmojiAnswerItem/EmojiAnswerItem"; import EmojiAnswerItem from "./EmojiAnswerItem/EmojiAnswerItem";
@ -19,7 +19,7 @@ interface Props {
export default function Emoji({ question, openBranchingPage, setOpenBranchingPage }: Props) { export default function Emoji({ question, openBranchingPage, setOpenBranchingPage }: Props) {
const [switchState, setSwitchState] = useState<string>("setting"); const [switchState, setSwitchState] = useState<string>("setting");
const {onClickAddAnAnswer} = useAddAnswer(); const {addVariantWithFocus, addVariantOnEnter, focusedVariantId, clearFocusedVariant} = useQuestionVariantsWithFocus();
const [open, setOpen] = useState<boolean>(false); const [open, setOpen] = useState<boolean>(false);
const [anchorElement, setAnchorElement] = useState<HTMLDivElement | null>(null); const [anchorElement, setAnchorElement] = useState<HTMLDivElement | null>(null);
const [selectedVariant, setSelectedVariant] = useState<string | null>(null); const [selectedVariant, setSelectedVariant] = useState<string | null>(null);
@ -48,6 +48,9 @@ export default function Emoji({ question, openBranchingPage, setOpenBranchingPag
setSelectedVariant={setSelectedVariant} setSelectedVariant={setSelectedVariant}
isOwn={Boolean(variant?.isOwn)} isOwn={Boolean(variant?.isOwn)}
ownPlaceholder={question.content.ownPlaceholder} ownPlaceholder={question.content.ownPlaceholder}
shouldAutoFocus={focusedVariantId === variant.id}
onFocusHandled={clearFocusedVariant}
onEnterKeyPress={() => addVariantOnEnter(question.id)}
/> />
))} ))}
/> />
@ -93,7 +96,7 @@ export default function Emoji({ question, openBranchingPage, setOpenBranchingPag
component="button" component="button"
variant="body2" variant="body2"
sx={{ color: theme.palette.brightPurple.main }} sx={{ color: theme.palette.brightPurple.main }}
onClick={() => onClickAddAnAnswer(question)} onClick={() => addVariantWithFocus(question)}
> >
Добавьте ответ Добавьте ответ
</Link> </Link>

@ -14,6 +14,9 @@ type Props = Omit<
setOpen: React.Dispatch<React.SetStateAction<boolean>>; setOpen: React.Dispatch<React.SetStateAction<boolean>>;
isOwn: boolean; isOwn: boolean;
ownPlaceholder: string; ownPlaceholder: string;
shouldAutoFocus?: boolean;
onFocusHandled?: () => void;
onEnterKeyPress?: () => void;
}; };
export default function EmojiAnswerItem({ export default function EmojiAnswerItem({
@ -28,6 +31,9 @@ export default function EmojiAnswerItem({
setOpen, setOpen,
isOwn, isOwn,
ownPlaceholder, ownPlaceholder,
shouldAutoFocus,
onFocusHandled,
onEnterKeyPress,
}: Props) { }: Props) {
@ -99,6 +105,9 @@ export default function EmojiAnswerItem({
additionalMobile={addOrEditImageButtonMobile} additionalMobile={addOrEditImageButtonMobile}
isOwn={isOwn} isOwn={isOwn}
ownPlaceholder={ownPlaceholder} ownPlaceholder={ownPlaceholder}
shouldAutoFocus={shouldAutoFocus}
onFocusHandled={onFocusHandled}
onEnterKeyPress={onEnterKeyPress}
/> />
); );
} }

@ -4,7 +4,7 @@ import { updateQuestion } from "@root/questions/actions";
import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomCheckbox from "@ui_kit/CustomCheckbox";
import { memo } from "react"; import { memo } from "react";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
import { useAddAnswer } from "@/utils/hooks/useAddAnswer"; import { useQuestionVariantsWithFocus } from "@/utils/questionVariants";
type SettingEmojiProps = { type SettingEmojiProps = {
question: QuizQuestionEmoji; question: QuizQuestionEmoji;
@ -17,7 +17,7 @@ type SettingEmojiProps = {
const SettingEmoji = memo<SettingEmojiProps>(function ({ question, questionId, isRequired, isLargeCheck, isMulti, isOwn }) { const SettingEmoji = memo<SettingEmojiProps>(function ({ question, questionId, isRequired, isLargeCheck, isMulti, isOwn }) {
const theme = useTheme(); const theme = useTheme();
const {switchOwn} = useAddAnswer(); const {switchOwnVariant} = useQuestionVariantsWithFocus();
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980)); const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
@ -92,7 +92,7 @@ const SettingEmoji = memo<SettingEmojiProps>(function ({ question, questionId, i
label={'Вариант "свой ответ"'} label={'Вариант "свой ответ"'}
checked={isOwn} checked={isOwn}
handleChange={({ target }) => { handleChange={({ target }) => {
switchOwn({question, checked:target.checked}) switchOwnVariant({question, checked:target.checked})
}} }}
/> />
{/* <Box sx={{ mt: isMobile ? "11px" : "6px", width: "100%" }}> {/* <Box sx={{ mt: isMobile ? "11px" : "6px", width: "100%" }}>

@ -1,9 +1,9 @@
import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material"; import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
import { import {
addQuestionVariant,
clearQuestionImages, clearQuestionImages,
uploadQuestionImage, uploadQuestionImage,
} from "@root/questions/actions"; } from "@root/questions/actions";
import { useQuestionVariantsWithFocus } from "@/utils/questionVariants";
import { useCurrentQuiz } from "@root/quizes/hooks"; import { useCurrentQuiz } from "@root/quizes/hooks";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { EnterIcon } from "@/assets/icons/questionsPage/enterIcon"; import { EnterIcon } from "@/assets/icons/questionsPage/enterIcon";
@ -31,6 +31,7 @@ export default function OptionsAndPicture({
const [switchState, setSwitchState] = useState("setting"); const [switchState, setSwitchState] = useState("setting");
const [pictureUploding, setPictureUploading] = useState<boolean>(false); const [pictureUploding, setPictureUploading] = useState<boolean>(false);
const [openCropModal, setOpenCropModal] = useState(false); const [openCropModal, setOpenCropModal] = useState(false);
const {addVariantWithFocus, addVariantOnEnter, focusedVariantId, clearFocusedVariant} = useQuestionVariantsWithFocus();
const [selectedVariantId, setSelectedVariantId] = useState<string | null>( const [selectedVariantId, setSelectedVariantId] = useState<string | null>(
null, null,
@ -111,6 +112,9 @@ export default function OptionsAndPicture({
setSelectedVariantId={setSelectedVariantId} setSelectedVariantId={setSelectedVariantId}
isOwn={Boolean(variant?.isOwn)} isOwn={Boolean(variant?.isOwn)}
ownPlaceholder={question.content.ownPlaceholder} ownPlaceholder={question.content.ownPlaceholder}
shouldAutoFocus={focusedVariantId === variant.id}
onFocusHandled={clearFocusedVariant}
onEnterKeyPress={() => addVariantOnEnter(question.id)}
/> />
))} ))}
/> />
@ -148,7 +152,7 @@ export default function OptionsAndPicture({
height: "19px", height: "19px",
}} }}
onClick={() => { onClick={() => {
addQuestionVariant(question.id); addVariantWithFocus(question);
}} }}
> >
Добавьте ответ Добавьте ответ

@ -1,4 +1,4 @@
import { useAddAnswer } from "@/utils/hooks/useAddAnswer"; import { useQuestionVariantsWithFocus } from "@/utils/questionVariants";
import type { QuizQuestionVarImg, QuizQuestionVariant } from "@frontend/squzanswerer"; import type { QuizQuestionVarImg, QuizQuestionVariant } from "@frontend/squzanswerer";
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import { updateQuestion } from "@root/questions/actions"; import { updateQuestion } from "@root/questions/actions";
@ -19,7 +19,7 @@ type SettingOptionsAndPictProps = {
const SettingOptionsAndPict = memo<SettingOptionsAndPictProps>(function ({ question, questionId, ownPlaceholder, isMulti, isLargeCheck, replText, isRequired, isOwn }) { const SettingOptionsAndPict = memo<SettingOptionsAndPictProps>(function ({ question, questionId, ownPlaceholder, isMulti, isLargeCheck, replText, isRequired, isOwn }) {
const theme = useTheme(); const theme = useTheme();
const { switchOwn } = useAddAnswer(); const { switchOwnVariant } = useQuestionVariantsWithFocus();
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980)); const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
@ -73,7 +73,7 @@ const SettingOptionsAndPict = memo<SettingOptionsAndPictProps>(function ({ quest
label={'Вариант "свой ответ"'} label={'Вариант "свой ответ"'}
checked={isOwn} checked={isOwn}
handleChange={({ target }) => { handleChange={({ target }) => {
switchOwn({ question, checked: target.checked }) switchOwnVariant({ question, checked: target.checked })
}} }}
/> />
<CustomCheckbox <CustomCheckbox

@ -9,7 +9,7 @@ import { EnterIcon } from "@/assets/icons/questionsPage/enterIcon";
import type { QuizQuestionVarImg } from "@frontend/squzanswerer/dist-package/model/questionTypes/varimg"; import type { QuizQuestionVarImg } from "@frontend/squzanswerer/dist-package/model/questionTypes/varimg";
//@/model/questionTypes/images"; //@/model/questionTypes/images";
import { useAddAnswer } from "@/utils/hooks/useAddAnswer"; import { useQuestionVariantsWithFocus } from "@/utils/questionVariants";
import { useDisclosure } from "@/utils/useDisclosure"; import { useDisclosure } from "@/utils/useDisclosure";
import { AnswerDraggableList } from "../../AnswerDraggableList"; import { AnswerDraggableList } from "../../AnswerDraggableList";
import ImageEditAnswerItem from "../../AnswerDraggableList/ImageEditAnswerItem"; import ImageEditAnswerItem from "../../AnswerDraggableList/ImageEditAnswerItem";
@ -31,7 +31,7 @@ export default function OptionsPicture({
setOpenBranchingPage, setOpenBranchingPage,
}: Props) { }: Props) {
const theme = useTheme(); const theme = useTheme();
const {onClickAddAnAnswer} = useAddAnswer(); const {addVariantWithFocus, addVariantOnEnter, focusedVariantId, clearFocusedVariant} = useQuestionVariantsWithFocus();
const quizQid = useCurrentQuiz()?.qid; const quizQid = useCurrentQuiz()?.qid;
const [pictureUploding, setPictureUploading] = useState<boolean>(false); const [pictureUploding, setPictureUploading] = useState<boolean>(false);
const [openCropModal, setOpenCropModal] = useState(false); const [openCropModal, setOpenCropModal] = useState(false);
@ -93,6 +93,9 @@ export default function OptionsPicture({
setSelectedVariantId={setSelectedVariantId} setSelectedVariantId={setSelectedVariantId}
isOwn={Boolean(variant?.isOwn)} isOwn={Boolean(variant?.isOwn)}
ownPlaceholder={question.content.ownPlaceholder} ownPlaceholder={question.content.ownPlaceholder}
shouldAutoFocus={focusedVariantId === variant.id}
onFocusHandled={clearFocusedVariant}
onEnterKeyPress={() => addVariantOnEnter(question.id)}
/> />
))} ))}
/> />
@ -117,7 +120,7 @@ export default function OptionsPicture({
component="button" component="button"
variant="body2" variant="body2"
sx={{ color: theme.palette.brightPurple.main }} sx={{ color: theme.palette.brightPurple.main }}
onClick={() => onClickAddAnAnswer(question)} onClick={() => addVariantWithFocus(question)}
> >
Добавьте ответ Добавьте ответ
</Link> </Link>

@ -9,7 +9,7 @@ import ProportionsIcon11 from "@/assets/icons/questionsPage/ProportionsIcon11";
import ProportionsIcon12 from "@/assets/icons/questionsPage/ProportionsIcon12"; import ProportionsIcon12 from "@/assets/icons/questionsPage/ProportionsIcon12";
import ProportionsIcon21 from "@/assets/icons/questionsPage/ProportionsIcon21"; import ProportionsIcon21 from "@/assets/icons/questionsPage/ProportionsIcon21";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
import { useAddAnswer } from "@/utils/hooks/useAddAnswer"; import { useQuestionVariantsWithFocus } from "@/utils/questionVariants";
type Proportion = "1:1" | "1:2" | "2:1"; type Proportion = "1:1" | "1:2" | "2:1";
@ -69,7 +69,7 @@ const SettingOptionsPict = memo<SettingOptionsPictProps>(function ({
question.content.ownPlaceholder = replText; question.content.ownPlaceholder = replText;
}); });
}; };
const {switchOwn} = useAddAnswer(); const {switchOwnVariant} = useQuestionVariantsWithFocus();
return ( return (
<Box <Box
@ -175,7 +175,7 @@ const SettingOptionsPict = memo<SettingOptionsPictProps>(function ({
label={'Вариант "свой ответ"'} label={'Вариант "свой ответ"'}
checked={isOwn} checked={isOwn}
handleChange={({ target }) => { handleChange={({ target }) => {
switchOwn({question, checked:target.checked}) switchOwnVariant({question, checked:target.checked})
}} }}
/> />
{/* <Box sx={{ mt: isMobile ? "11px" : "6px", width: "100%" }}> {/* <Box sx={{ mt: isMobile ? "11px" : "6px", width: "100%" }}>

@ -9,7 +9,7 @@ import ProportionsIcon11 from "@/assets/icons/questionsPage/ProportionsIcon11";
import ProportionsIcon12 from "@/assets/icons/questionsPage/ProportionsIcon12"; import ProportionsIcon12 from "@/assets/icons/questionsPage/ProportionsIcon12";
import ProportionsIcon21 from "@/assets/icons/questionsPage/ProportionsIcon21"; import ProportionsIcon21 from "@/assets/icons/questionsPage/ProportionsIcon21";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
import { useAddAnswer } from "@/utils/hooks/useAddAnswer"; import { useQuestionVariantsWithFocus } from "@/utils/questionVariants";
type Proportion = "1:1" | "1:2" | "2:1"; type Proportion = "1:1" | "1:2" | "2:1";
@ -69,7 +69,7 @@ const SettingOptionsPict = memo<SettingOptionsPictProps>(function ({
question.content.ownPlaceholder = replText; question.content.ownPlaceholder = replText;
}); });
}; };
const {switchOwn} = useAddAnswer(); const {switchOwnVariant} = useQuestionVariantsWithFocus();
return ( return (
<Box <Box
@ -175,7 +175,7 @@ const SettingOptionsPict = memo<SettingOptionsPictProps>(function ({
label={'Вариант "свой ответ"'} label={'Вариант "свой ответ"'}
checked={isOwn} checked={isOwn}
handleChange={({ target }) => { handleChange={({ target }) => {
switchOwn({question, checked:target.checked}) switchOwnVariant({question, checked:target.checked})
}} }}
/> />
{/* <Box sx={{ mt: isMobile ? "11px" : "6px", width: "100%" }}> {/* <Box sx={{ mt: isMobile ? "11px" : "6px", width: "100%" }}>

@ -2,7 +2,7 @@ import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { EnterIcon } from "@/assets/icons/questionsPage/enterIcon"; import { EnterIcon } from "@/assets/icons/questionsPage/enterIcon";
import type { QuizQuestionVariant } from "@frontend/squzanswerer"; import type { QuizQuestionVariant } from "@frontend/squzanswerer";
import { useAddAnswer } from "@/utils/hooks/useAddAnswer"; import { useQuestionVariantsWithFocus } from "@/utils/questionVariants";
import { AnswerDraggableList } from "../../AnswerDraggableList"; import { AnswerDraggableList } from "../../AnswerDraggableList";
import AnswerItem from "../../AnswerDraggableList/AnswerItem"; import AnswerItem from "../../AnswerDraggableList/AnswerItem";
import ButtonsOptions from "../ButtonsLayout/ButtonsOptions"; import ButtonsOptions from "../ButtonsLayout/ButtonsOptions";
@ -15,7 +15,7 @@ interface Props {
} }
export default function AnswerOptions({ question, openBranchingPage, setOpenBranchingPage }: Props) { export default function AnswerOptions({ question, openBranchingPage, setOpenBranchingPage }: Props) {
const {onClickAddAnAnswer} = useAddAnswer(); const {addVariantWithFocus, addVariantOnEnter, focusedVariantId, clearFocusedVariant} = useQuestionVariantsWithFocus();
const [switchState, setSwitchState] = useState("setting"); const [switchState, setSwitchState] = useState("setting");
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
@ -55,6 +55,9 @@ export default function AnswerOptions({ question, openBranchingPage, setOpenBran
variant={variant} variant={variant}
isOwn={Boolean(variant.isOwn)} isOwn={Boolean(variant.isOwn)}
ownPlaceholder={question.content.ownPlaceholder} ownPlaceholder={question.content.ownPlaceholder}
shouldAutoFocus={focusedVariantId === variant.id}
onFocusHandled={clearFocusedVariant}
onEnterKeyPress={() => addVariantOnEnter(question.id)}
/> />
))} ))}
/> />
@ -77,7 +80,7 @@ export default function AnswerOptions({ question, openBranchingPage, setOpenBran
mr: "4px", mr: "4px",
height: "19px", height: "19px",
}} }}
onClick={() => onClickAddAnAnswer(question)} onClick={() => addVariantWithFocus(question)}
> >
Добавьте ответ Добавьте ответ
</Link> </Link>

@ -4,7 +4,7 @@ import CustomCheckbox from "@ui_kit/CustomCheckbox";
import type { QuizQuestionVariant } from "@frontend/squzanswerer"; import type { QuizQuestionVariant } from "@frontend/squzanswerer";
import { memo } from "react"; import { memo } from "react";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
import { useAddAnswer } from "@/utils/hooks/useAddAnswer"; import { useQuestionVariantsWithFocus } from "@/utils/questionVariants";
interface Props { interface Props {
question: QuizQuestionVariant; question: QuizQuestionVariant;
@ -21,7 +21,7 @@ const ResponseSettings = memo<Props>(function ({question, questionId, ownPlaceho
const isTablet = useMediaQuery(theme.breakpoints.down(900)); const isTablet = useMediaQuery(theme.breakpoints.down(900));
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const {switchOwn} = useAddAnswer(); const {switchOwnVariant} = useQuestionVariantsWithFocus();
return ( return (
<Box <Box
@ -84,7 +84,7 @@ const ResponseSettings = memo<Props>(function ({question, questionId, ownPlaceho
label={'Вариант "свой ответ"'} label={'Вариант "свой ответ"'}
checked={isOwn} checked={isOwn}
handleChange={({ target }) => { handleChange={({ target }) => {
switchOwn({question, checked:target.checked}) switchOwnVariant({question, checked:target.checked})
}} }}
/> />
</Box> </Box>

@ -327,7 +327,8 @@ export const updateQuestion = async <T = AnyTypedQuizQuestion>(
requestQueue.enqueue(`updateQuestion-${questionId}`, request); requestQueue.enqueue(`updateQuestion-${questionId}`, request);
}; };
export const addQuestionVariant = (questionId: string) => { export const addQuestionVariant = (questionId: string): string => {
const newVariant = createQuestionVariant();
updateQuestion(questionId, (question) => { updateQuestion(questionId, (question) => {
switch (question.type) { switch (question.type) {
case "variant": case "variant":
@ -335,12 +336,13 @@ export const addQuestionVariant = (questionId: string) => {
case "select": case "select":
case "images": case "images":
case "varimg": case "varimg":
question.content.variants.push(createQuestionVariant()); question.content.variants.push(newVariant);
break; break;
default: default:
throw new Error(`Cannot add variant to question of type "${question.type}"`); throw new Error(`Cannot add variant to question of type "${question.type}"`);
} }
}); });
return newVariant.id;
}; };
export const addQuestionOwnVariant = (questionId: string) => { export const addQuestionOwnVariant = (questionId: string) => {
updateQuestion(questionId, (question) => { updateQuestion(questionId, (question) => {

@ -1,26 +0,0 @@
import { QuizQuestionsWithVariants } from "@frontend/squzanswerer";
import { addQuestionOwnVariant, addQuestionVariant, updateQuestion } from "@root/questions/actions";
export const useAddAnswer = () => {
const onClickAddAnAnswer = (question: QuizQuestionsWithVariants) => {
addQuestionVariant(question.id);
};
interface SwitchOwnProps {
question: QuizQuestionsWithVariants;
checked: boolean
}
const switchOwn = ({ question, checked }: SwitchOwnProps) => {
if (!question.content.variants.some(v => v.isOwn) && checked) {
addQuestionOwnVariant(question.id)
}
updateQuestion<QuizQuestionVariant>(question.id, (question) => {
question.content.own = checked;
});
}
return {
onClickAddAnAnswer,
switchOwn
};
};

@ -0,0 +1,47 @@
import { QuizQuestionsWithVariants, QuizQuestionVariant } from "@frontend/squzanswerer";
import { addQuestionOwnVariant, addQuestionVariant, updateQuestion } from "@root/questions/actions";
import { useState } from "react";
/**
* Утилита для управления вариантами ответов с автофокусом
*/
export const useQuestionVariantsWithFocus = () => {
const [focusedVariantId, setFocusedVariantId] = useState<string | null>(null);
const addVariantWithFocus = (question: QuizQuestionsWithVariants) => {
const newVariantId = addQuestionVariant(question.id);
setFocusedVariantId(newVariantId);
};
const addVariantOnEnter = (questionId: string) => {
const newVariantId = addQuestionVariant(questionId);
setFocusedVariantId(newVariantId);
};
const clearFocusedVariant = () => {
setFocusedVariantId(null);
};
interface SwitchOwnProps {
question: QuizQuestionsWithVariants;
checked: boolean
}
const switchOwnVariant = ({ question, checked }: SwitchOwnProps) => {
if (!question.content.variants.some(v => v.isOwn) && checked) {
addQuestionOwnVariant(question.id)
}
updateQuestion<QuizQuestionVariant>(question.id, (question) => {
question.content.own = checked;
});
}
return {
addVariantWithFocus,
addVariantOnEnter,
switchOwnVariant,
focusedVariantId,
clearFocusedVariant
};
};