mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-07 19:30:07 -05:00
clean up block & question toggles for clarity
This commit is contained in:
@@ -1373,6 +1373,7 @@ checksums:
|
||||
environments/surveys/edit/logic_error_warning: 542fbb918ffdb29e6f9a4a6196ffb558
|
||||
environments/surveys/edit/logic_error_warning_text: f2afad8852a95ed169a39959efbf592c
|
||||
environments/surveys/edit/long_answer: 3a97f8d2e90aba6e679917a0c5670c53
|
||||
environments/surveys/edit/long_answer_toggle_description: 86bcdfeb74d9825c2f2d5a215e92d111
|
||||
environments/surveys/edit/lower_label: 45985bca022d4370bd6e013af75d5160
|
||||
environments/surveys/edit/manage_languages: 9c56d5afee8a73dfc283a452470f3a10
|
||||
environments/surveys/edit/matrix_all_fields: 187240509163b2f52a400a565e57c67f
|
||||
|
||||
@@ -1458,6 +1458,7 @@
|
||||
"logic_error_warning": "Änderungen werden zu Logikfehlern führen",
|
||||
"logic_error_warning_text": "Das Ändern des Fragetypen entfernt die Logikbedingungen von dieser Frage",
|
||||
"long_answer": "Lange Antwort",
|
||||
"long_answer_toggle_description": "Ermöglichen Sie den Befragten, längere Antworten über mehrere Zeilen zu schreiben.",
|
||||
"lower_label": "Unteres Label",
|
||||
"manage_languages": "Sprachen verwalten",
|
||||
"matrix_all_fields": "Alle Felder",
|
||||
|
||||
@@ -1458,6 +1458,7 @@
|
||||
"logic_error_warning": "Changing will cause logic errors",
|
||||
"logic_error_warning_text": "Changing the question type will remove the logic conditions from this question",
|
||||
"long_answer": "Long answer",
|
||||
"long_answer_toggle_description": "Allow respondents to write longer, multi-line answers.",
|
||||
"lower_label": "Lower Label",
|
||||
"manage_languages": "Manage Languages",
|
||||
"matrix_all_fields": "All fields",
|
||||
|
||||
@@ -1458,6 +1458,7 @@
|
||||
"logic_error_warning": "Changer causera des erreurs logiques",
|
||||
"logic_error_warning_text": "Changer le type de question supprimera les conditions logiques de cette question.",
|
||||
"long_answer": "Longue réponse",
|
||||
"long_answer_toggle_description": "Permettre aux répondants d'écrire des réponses plus longues et sur plusieurs lignes.",
|
||||
"lower_label": "Étiquette inférieure",
|
||||
"manage_languages": "Gérer les langues",
|
||||
"matrix_all_fields": "Tous les champs",
|
||||
|
||||
@@ -1458,6 +1458,7 @@
|
||||
"logic_error_warning": "変更するとロジックエラーが発生します",
|
||||
"logic_error_warning_text": "質問の種類を変更すると、この質問のロジック条件が削除されます",
|
||||
"long_answer": "長文回答",
|
||||
"long_answer_toggle_description": "回答者が長文の複数行の回答を書けるようにします。",
|
||||
"lower_label": "下限ラベル",
|
||||
"manage_languages": "言語を管理",
|
||||
"matrix_all_fields": "すべてのフィールド",
|
||||
|
||||
@@ -1458,6 +1458,7 @@
|
||||
"logic_error_warning": "Mudar vai causar erros de lógica",
|
||||
"logic_error_warning_text": "Mudar o tipo de pergunta vai remover as condições lógicas dessa pergunta",
|
||||
"long_answer": "resposta longa",
|
||||
"long_answer_toggle_description": "Permitir que os respondentes escrevam respostas mais longas e com várias linhas.",
|
||||
"lower_label": "Etiqueta Inferior",
|
||||
"manage_languages": "Gerenciar Idiomas",
|
||||
"matrix_all_fields": "Todos os campos",
|
||||
|
||||
@@ -1458,6 +1458,7 @@
|
||||
"logic_error_warning": "A alteração causará erros de lógica",
|
||||
"logic_error_warning_text": "Alterar o tipo de pergunta irá remover as condições lógicas desta pergunta",
|
||||
"long_answer": "Resposta longa",
|
||||
"long_answer_toggle_description": "Permitir que os inquiridos escrevam respostas mais longas e com várias linhas.",
|
||||
"lower_label": "Etiqueta Inferior",
|
||||
"manage_languages": "Gerir Idiomas",
|
||||
"matrix_all_fields": "Todos os campos",
|
||||
|
||||
@@ -1458,6 +1458,7 @@
|
||||
"logic_error_warning": "Schimbarea va provoca erori de logică",
|
||||
"logic_error_warning_text": "Schimbarea tipului de întrebare va elimina condițiile de logică din această întrebare",
|
||||
"long_answer": "Răspuns lung",
|
||||
"long_answer_toggle_description": "Permite respondenților să scrie răspunsuri mai lungi, pe mai multe rânduri.",
|
||||
"lower_label": "Etichetă inferioară",
|
||||
"manage_languages": "Gestionați limbile",
|
||||
"matrix_all_fields": "Toate câmpurile",
|
||||
|
||||
@@ -1458,6 +1458,7 @@
|
||||
"logic_error_warning": "更改 将 导致 逻辑 错误",
|
||||
"logic_error_warning_text": "更改问题类型 会 移除 此问题 的 逻辑条件",
|
||||
"long_answer": "长答案",
|
||||
"long_answer_toggle_description": "允许受访者填写较长的多行答案。",
|
||||
"lower_label": "下限标签",
|
||||
"manage_languages": "管理 语言",
|
||||
"matrix_all_fields": "所有字段",
|
||||
|
||||
@@ -1458,6 +1458,7 @@
|
||||
"logic_error_warning": "變更將導致邏輯錯誤",
|
||||
"logic_error_warning_text": "變更問題類型將會從此問題中移除邏輯條件",
|
||||
"long_answer": "長回答",
|
||||
"long_answer_toggle_description": "允許受訪者撰寫較長的多行回答。",
|
||||
"lower_label": "下標籤",
|
||||
"manage_languages": "管理語言",
|
||||
"matrix_all_fields": "所有欄位",
|
||||
|
||||
@@ -25,6 +25,7 @@ import { Button } from "@/modules/ui/components/button";
|
||||
import { FileInput } from "@/modules/ui/components/file-input";
|
||||
import { Input } from "@/modules/ui/components/input";
|
||||
import { Label } from "@/modules/ui/components/label";
|
||||
import { Switch } from "@/modules/ui/components/switch";
|
||||
import { TooltipRenderer } from "@/modules/ui/components/tooltip";
|
||||
import {
|
||||
determineImageUploaderVisibility,
|
||||
@@ -310,6 +311,60 @@ export const QuestionFormInput = ({
|
||||
return false;
|
||||
};
|
||||
|
||||
const getIsRequiredToggleDisabled = (): boolean => {
|
||||
if (!question) return false;
|
||||
|
||||
if (question.type === TSurveyElementTypeEnum.Address) {
|
||||
const allFieldsAreOptional = [
|
||||
question.addressLine1,
|
||||
question.addressLine2,
|
||||
question.city,
|
||||
question.state,
|
||||
question.zip,
|
||||
question.country,
|
||||
]
|
||||
.filter((field) => field.show)
|
||||
.every((field) => !field.required);
|
||||
|
||||
if (allFieldsAreOptional) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return [
|
||||
question.addressLine1,
|
||||
question.addressLine2,
|
||||
question.city,
|
||||
question.state,
|
||||
question.zip,
|
||||
question.country,
|
||||
]
|
||||
.filter((field) => field.show)
|
||||
.some((condition) => condition.required === true);
|
||||
}
|
||||
|
||||
if (question.type === TSurveyElementTypeEnum.ContactInfo) {
|
||||
const allFieldsAreOptional = [
|
||||
question.firstName,
|
||||
question.lastName,
|
||||
question.email,
|
||||
question.phone,
|
||||
question.company,
|
||||
]
|
||||
.filter((field) => field.show)
|
||||
.every((field) => !field.required);
|
||||
|
||||
if (allFieldsAreOptional) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return [question.firstName, question.lastName, question.email, question.phone, question.company]
|
||||
.filter((field) => field.show)
|
||||
.some((condition) => condition.required === true);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const useRichTextEditor = id === "headline" || id === "subheader" || id === "html";
|
||||
|
||||
// For rich text editor fields, we need either updateQuestion or updateSurvey
|
||||
@@ -321,8 +376,23 @@ export const QuestionFormInput = ({
|
||||
return (
|
||||
<div className="w-full">
|
||||
{label && (
|
||||
<div className="mb-2 mt-3">
|
||||
<div className="mb-2 mt-3 flex items-center justify-between">
|
||||
<Label htmlFor={id}>{label}</Label>
|
||||
{id === "headline" && question && updateQuestion && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label htmlFor="required-toggle" className="text-sm">
|
||||
{t("environments.surveys.edit.required")}
|
||||
</Label>
|
||||
<Switch
|
||||
id="required-toggle"
|
||||
checked={question.required}
|
||||
disabled={getIsRequiredToggleDisabled()}
|
||||
onCheckedChange={(checked) => {
|
||||
updateQuestion(questionIdx, { required: checked });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-4" ref={animationParent}>
|
||||
@@ -376,7 +446,9 @@ export const QuestionFormInput = ({
|
||||
</div>
|
||||
|
||||
{id === "headline" && !isWelcomeCard && (
|
||||
<TooltipRenderer tooltipContent={t("environments.surveys.edit.add_photo_or_video")}>
|
||||
<TooltipRenderer
|
||||
tooltipContent={t("environments.surveys.edit.add_photo_or_video")}
|
||||
delayDuration={100}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
@@ -434,8 +506,23 @@ export const QuestionFormInput = ({
|
||||
return (
|
||||
<div className="w-full">
|
||||
{label && (
|
||||
<div className="mb-2 mt-3">
|
||||
<div className="mb-2 mt-3 flex items-center justify-between">
|
||||
<Label htmlFor={id}>{label}</Label>
|
||||
{id === "headline" && question && updateQuestion && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label htmlFor="required-toggle" className="text-sm">
|
||||
{t("environments.surveys.edit.required")}
|
||||
</Label>
|
||||
<Switch
|
||||
id="required-toggle"
|
||||
checked={question.required}
|
||||
disabled={getIsRequiredToggleDisabled()}
|
||||
onCheckedChange={(checked) => {
|
||||
updateQuestion(questionIdx, { required: checked });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<MultiLangWrapper
|
||||
|
||||
@@ -38,8 +38,6 @@ import { RatingQuestionForm } from "@/modules/survey/editor/components/rating-qu
|
||||
import { formatTextWithSlashes } from "@/modules/survey/editor/lib/utils";
|
||||
import { getQuestionIconMap, getTSurveyQuestionTypeEnumName } from "@/modules/survey/lib/questions";
|
||||
import { Alert, AlertButton, AlertTitle } from "@/modules/ui/components/alert";
|
||||
import { Label } from "@/modules/ui/components/label";
|
||||
import { Switch } from "@/modules/ui/components/switch";
|
||||
|
||||
interface BlockCardProps {
|
||||
localSurvey: TSurvey;
|
||||
@@ -214,62 +212,6 @@ export const BlockCard = ({
|
||||
const isInvalid = invalidQuestions ? invalidQuestions.includes(element.id) : false;
|
||||
const open = activeQuestionId === element.id;
|
||||
|
||||
const getIsRequiredToggleDisabled = (): boolean => {
|
||||
if (element.type === TSurveyElementTypeEnum.Address) {
|
||||
const allFieldsAreOptional = [
|
||||
element.addressLine1,
|
||||
element.addressLine2,
|
||||
element.city,
|
||||
element.state,
|
||||
element.zip,
|
||||
element.country,
|
||||
]
|
||||
.filter((field) => field.show)
|
||||
.every((field) => !field.required);
|
||||
|
||||
if (allFieldsAreOptional) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return [
|
||||
element.addressLine1,
|
||||
element.addressLine2,
|
||||
element.city,
|
||||
element.state,
|
||||
element.zip,
|
||||
element.country,
|
||||
]
|
||||
.filter((field) => field.show)
|
||||
.some((condition) => condition.required === true);
|
||||
}
|
||||
|
||||
if (element.type === TSurveyElementTypeEnum.ContactInfo) {
|
||||
const allFieldsAreOptional = [
|
||||
element.firstName,
|
||||
element.lastName,
|
||||
element.email,
|
||||
element.phone,
|
||||
element.company,
|
||||
]
|
||||
.filter((field) => field.show)
|
||||
.every((field) => !field.required);
|
||||
|
||||
if (allFieldsAreOptional) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return [element.firstName, element.lastName, element.email, element.phone, element.company]
|
||||
.filter((field) => field.show)
|
||||
.some((condition) => condition.required === true);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleRequiredToggle = () => {
|
||||
updateQuestion(questionIdx, { required: !element.required });
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={element.id} className={cn(elementIndex > 0 && "border-t border-slate-200")}>
|
||||
<Collapsible.Root
|
||||
@@ -608,46 +550,24 @@ export const BlockCard = ({
|
||||
</Collapsible.Root>
|
||||
</div>
|
||||
</Collapsible.CollapsibleContent>
|
||||
|
||||
{open && (
|
||||
<div className="mx-4 flex justify-end space-x-6 border-t border-slate-200">
|
||||
{element.type === "openText" && (
|
||||
<div className="my-4 flex items-center justify-end space-x-2">
|
||||
<Label htmlFor="longAnswer">{t("environments.surveys.edit.long_answer")}</Label>
|
||||
<Switch
|
||||
id="longAnswer"
|
||||
disabled={element.inputType !== "text"}
|
||||
checked={element.longAnswer !== false}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
updateQuestion(questionIdx, {
|
||||
longAnswer:
|
||||
typeof element.longAnswer === "undefined" ? false : !element.longAnswer,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
<div className="my-4 flex items-center justify-end space-x-2">
|
||||
<Label htmlFor="required-toggle">{t("environments.surveys.edit.required")}</Label>
|
||||
<Switch
|
||||
id="required-toggle"
|
||||
checked={element.required}
|
||||
disabled={getIsRequiredToggleDisabled()}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleRequiredToggle();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</Collapsible.Root>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<hr className="mb-4 border-dashed border-slate-200" />
|
||||
{/* Add Question to Block button */}
|
||||
|
||||
<div className="p-4 pt-0">
|
||||
<AddQuestionToBlockButton
|
||||
localSurvey={localSurvey}
|
||||
setLocalSurvey={setLocalSurvey}
|
||||
block={block}
|
||||
project={project}
|
||||
isCxMode={isCxMode}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<hr className="border-dashed border-slate-200" />
|
||||
|
||||
{/* Block Settings */}
|
||||
<div className="p-4">
|
||||
@@ -665,18 +585,6 @@ export const BlockCard = ({
|
||||
isLastBlock={blockIdx === totalBlocks - 1}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Add Question to Block button */}
|
||||
|
||||
<div className="p-4 pt-0">
|
||||
<AddQuestionToBlockButton
|
||||
localSurvey={localSurvey}
|
||||
setLocalSurvey={setLocalSurvey}
|
||||
block={block}
|
||||
project={project}
|
||||
isCxMode={isCxMode}
|
||||
/>
|
||||
</div>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.Root>
|
||||
</div>
|
||||
|
||||
@@ -189,7 +189,7 @@ export const FileUploadQuestionForm = ({
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-8 mt-6 space-y-6">
|
||||
<div className="mt-6 space-y-6">
|
||||
<AdvancedOptionToggle
|
||||
isChecked={question.allowMultipleFiles}
|
||||
onToggle={() => updateQuestion(questionIdx, { allowMultipleFiles: !question.allowMultipleFiles })}
|
||||
|
||||
@@ -167,7 +167,7 @@ export const OpenQuestionForm = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<div className="mt-6 space-y-6">
|
||||
{showCharLimits && (
|
||||
<AdvancedOptionToggle
|
||||
isChecked={isCharLimitEnabled}
|
||||
@@ -230,6 +230,21 @@ export const OpenQuestionForm = ({
|
||||
</div>
|
||||
</AdvancedOptionToggle>
|
||||
)}
|
||||
<div className="mt-4">
|
||||
<AdvancedOptionToggle
|
||||
isChecked={question.longAnswer !== false}
|
||||
onToggle={(checked: boolean) => {
|
||||
updateQuestion(questionIdx, {
|
||||
longAnswer: checked,
|
||||
});
|
||||
}}
|
||||
htmlId="longAnswer"
|
||||
title={t("environments.surveys.edit.long_answer")}
|
||||
description={t("environments.surveys.edit.long_answer_toggle_description")}
|
||||
disabled={question.inputType !== "text"}
|
||||
customContainerClass="p-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ const TooltipContent: React.ComponentType<TooltipPrimitive.TooltipContentProps>
|
||||
));
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
||||
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
|
||||
|
||||
interface TooltipRendererProps {
|
||||
tooltipContent: ReactNode;
|
||||
@@ -40,12 +40,13 @@ interface TooltipRendererProps {
|
||||
className?: string;
|
||||
triggerClass?: string;
|
||||
shouldRender?: boolean;
|
||||
delayDuration?: number;
|
||||
}
|
||||
export const TooltipRenderer = (props: TooltipRendererProps) => {
|
||||
const { children, shouldRender = true, tooltipContent, className, triggerClass } = props;
|
||||
const { children, shouldRender = true, tooltipContent, className, triggerClass, delayDuration = 0 } = props;
|
||||
if (shouldRender) {
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<TooltipProvider delayDuration={delayDuration}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className={triggerClass}>{children}</span>
|
||||
|
||||
Reference in New Issue
Block a user