Compare commits

..

3 Commits

Author SHA1 Message Date
Dhruwang
2ebc493a7b refactor: update label variants for consistency across components
Changed label variant from "upper" to "card" in various components including NPS, Rating, and ElementHeader to unify styling. Updated associated CSS classes and adjusted the Label component's variant prop accordingly. This enhances the styling consistency across the survey UI elements.

Closes formbricks/internal#1516
2026-04-15 14:05:10 +05:30
Dhruwang
8618ab8b06 fix: connect rating/NPS scale labels to label styling settings
Scale labels on Rating and NPS elements used variant="default" which
maps to --fb-label-color, a CSS variable with no UI control. Switch
them to variant="upper" (label-upper class) so they respond to the
existing Label Color/Size/Weight controls in Survey Styling.

Also rename "Headline Label" to "Label" in the styling UI since the
setting now clearly applies to both input labels and scale labels.

Closes formbricks/internal#1516

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 12:40:23 +05:30
Dhruwang Jariwala
439dd0b44e fix: add loading skeleton for responses page (#7700)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Johannes <johannes@formbricks.com>
2026-04-13 16:56:20 +00:00
30 changed files with 216 additions and 386 deletions

View File

@@ -0,0 +1,22 @@
"use client";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { SkeletonLoader } from "@/modules/ui/components/skeleton-loader";
const Loading = () => {
return (
<PageContentWrapper>
<PageHeader pageTitle="" />
<div className="flex h-9 animate-pulse gap-2">
<div className="h-9 w-36 rounded-full bg-slate-200" />
<div className="h-9 w-36 rounded-full bg-slate-200" />
<div className="h-9 w-36 rounded-full bg-slate-200" />
<div className="h-9 w-36 rounded-full bg-slate-200" />
</div>
<SkeletonLoader type="summary" />
</PageContentWrapper>
);
};
export default Loading;

View File

@@ -0,0 +1,23 @@
"use client";
import { useTranslation } from "react-i18next";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { SkeletonLoader } from "@/modules/ui/components/skeleton-loader";
const Loading = () => {
const { t } = useTranslation();
return (
<PageContentWrapper>
<PageHeader pageTitle={t("common.responses")} />
<div className="flex h-9 animate-pulse gap-1.5">
<div className="h-9 w-36 rounded-full bg-slate-200" />
<div className="h-9 w-36 rounded-full bg-slate-200" />
</div>
<SkeletonLoader type="responseTable" />
</PageContentWrapper>
);
};
export default Loading;

View File

@@ -1289,7 +1289,7 @@ checksums:
environments/surveys/edit/audience: a4d9fab4214a641e2d358fbb28f010e0
environments/surveys/edit/auto_close_on_inactivity: 093db516799315ccd4242a3675693012
environments/surveys/edit/auto_progress_rating_and_nps: 76b98e95a5b850850baa0ccc3c7fbf7c
environments/surveys/edit/auto_progress_rating_and_nps_description: 2a992dd8a5b9532f178f9a21881feb9a
environments/surveys/edit/auto_progress_rating_and_nps_description: cbf676789b9f3f47e36bdf35fa58282b
environments/surveys/edit/auto_save_disabled: f7411fb0dcfb8f7b19b85f0be54f2231
environments/surveys/edit/auto_save_disabled_tooltip: 77322e1e866b7d29f7641a88bbd3b681
environments/surveys/edit/auto_save_on: 1524d466830b00c5d727c701db404963
@@ -2181,12 +2181,12 @@ checksums:
environments/workspace/look/advanced_styling_field_track_bg_description: 8a56258273dfe49e83fe752ea9e8daed
environments/workspace/look/advanced_styling_field_track_height: 9ce57cb4583039c224a37e013efb6b8f
environments/workspace/look/advanced_styling_field_track_height_description: 90243a4374e15d9118ad0fd93d5f3614
environments/workspace/look/advanced_styling_field_upper_label_color: 65d75c60dfdba88e5fed38bcb24a0a5d
environments/workspace/look/advanced_styling_field_upper_label_color_description: ae2276506807c7ceeb7a8b87723a8dd4
environments/workspace/look/advanced_styling_field_upper_label_size: ea0ca9a3ffa1650f97a31df453b0afc7
environments/workspace/look/advanced_styling_field_upper_label_size_description: 061668625be0f7a68ebc2e2ebe49e5a9
environments/workspace/look/advanced_styling_field_upper_label_weight: 946c5836d2cfaaee21e494424d550887
environments/workspace/look/advanced_styling_field_upper_label_weight_description: 916b03c719a8dead351679336aabcf53
environments/workspace/look/advanced_styling_field_upper_label_color: 896bc12d6c77f162abf189589e11287e
environments/workspace/look/advanced_styling_field_upper_label_color_description: 1bc4b92821ff694c18396a872ca0fb60
environments/workspace/look/advanced_styling_field_upper_label_size: 0d915cf63b845a0c4c6004efc731babf
environments/workspace/look/advanced_styling_field_upper_label_size_description: 8e9e348318b72837c9e04f81701b2fb6
environments/workspace/look/advanced_styling_field_upper_label_weight: 9128dcfea0e5b1e0fdf4f5902f6ea2d6
environments/workspace/look/advanced_styling_field_upper_label_weight_description: 3a9006a41ab84aea98d4291a6d5db9db
environments/workspace/look/advanced_styling_section_buttons: 3b44d6e2800e7bf3f133f1bce435f4c2
environments/workspace/look/advanced_styling_section_headlines: 6def704c0ac2ecb5951400c806856a41
environments/workspace/look/advanced_styling_section_inputs: 76bbeb561122a72fd3ec8c49eff7c563

View File

@@ -1360,7 +1360,7 @@
"audience": "Publikum",
"auto_close_on_inactivity": "Automatisches Schließen bei Inaktivität",
"auto_progress_rating_and_nps": "Bewertungs- und NPS-Fragen automatisch fortsetzen",
"auto_progress_rating_and_nps_description": "Automatisches Weitergehen bei Einzelfragen-Blöcken. Pflichtfragen blenden Weiter aus, außer wenn \"Sonstiges\" ausgewählt ist.",
"auto_progress_rating_and_nps_description": "Fahre automatisch fort, sobald Befragte eine Antwort bei Bewertungs- oder NPS-Fragen auswählen. Dies gilt nur für Blöcke mit einer einzelnen Frage. Bei Pflichtfragen wird die Weiter-Schaltfläche ausgeblendet; bei optionalen Fragen bleibt sie zum Überspringen sichtbar.",
"auto_save_disabled": "Automatisches Speichern deaktiviert",
"auto_save_disabled_tooltip": "Ihre Umfrage wird nur im Entwurfsmodus automatisch gespeichert. So wird sichergestellt, dass öffentliche Umfragen nicht unbeabsichtigt aktualisiert werden.",
"auto_save_on": "Automatisches Speichern an",
@@ -2298,11 +2298,11 @@
"advanced_styling_field_track_bg_description": "Färbt den nicht ausgefüllten Teil des Balkens.",
"advanced_styling_field_track_height": "Track-Höhe",
"advanced_styling_field_track_height_description": "Steuert die Dicke des Fortschrittsbalkens.",
"advanced_styling_field_upper_label_color": "Farbe des oberen Labels",
"advanced_styling_field_upper_label_color_description": "Färbt die kleine Beschriftung über Eingabefeldern.",
"advanced_styling_field_upper_label_size": "Schriftgröße des oberen Labels",
"advanced_styling_field_upper_label_size_description": "Skaliert die kleine Beschriftung über Eingabefeldern.",
"advanced_styling_field_upper_label_weight": "Schriftstärke des oberen Labels",
"advanced_styling_field_upper_label_color": "Labelfarbe",
"advanced_styling_field_upper_label_color_description": "Färbt die kleine Beschriftung über Eingabefeldern und Skalenbeschriftungen.",
"advanced_styling_field_upper_label_size": "Label-Schriftgröße",
"advanced_styling_field_upper_label_size_description": "Skaliert die kleine Beschriftung über Eingabefeldern und Skalenbeschriftungen.",
"advanced_styling_field_upper_label_weight": "Label-Schriftstärke",
"advanced_styling_field_upper_label_weight_description": "Macht die Beschriftung leichter oder fetter.",
"advanced_styling_section_buttons": "Buttons",
"advanced_styling_section_headlines": "Überschriften & Beschreibungen",

View File

@@ -1360,7 +1360,7 @@
"audience": "Audience",
"auto_close_on_inactivity": "Auto close on inactivity",
"auto_progress_rating_and_nps": "Auto-progress rating and NPS questions",
"auto_progress_rating_and_nps_description": "Auto-advance in single-question blocks. Required questions hide Next, except when \"Other\" is selected.",
"auto_progress_rating_and_nps_description": "Automatically advance when respondents select an answer on rating or NPS questions. This only applies to single-question blocks. Required questions hide the Next button; optional questions still show it for skipping.",
"auto_save_disabled": "Auto-save disabled",
"auto_save_disabled_tooltip": "Your survey is only auto-saved when in draft. This assures public surveys are not unintentionally updated.",
"auto_save_on": "Auto-save on",
@@ -2298,12 +2298,12 @@
"advanced_styling_field_track_bg_description": "Colors the unfilled portion of the bar.",
"advanced_styling_field_track_height": "Track Height",
"advanced_styling_field_track_height_description": "Controls the progress bar thickness.",
"advanced_styling_field_upper_label_color": "Headline Label Color",
"advanced_styling_field_upper_label_color_description": "Colors the small label above inputs.",
"advanced_styling_field_upper_label_size": "Headline Label Font Size",
"advanced_styling_field_upper_label_size_description": "Scales the small label above inputs.",
"advanced_styling_field_upper_label_weight": "Headline Label Font Weight",
"advanced_styling_field_upper_label_weight_description": "Makes the label lighter or bolder.",
"advanced_styling_field_upper_label_color": "Label Color",
"advanced_styling_field_upper_label_color_description": "Colors the small labels above inputs and scale labels.",
"advanced_styling_field_upper_label_size": "Label Font Size",
"advanced_styling_field_upper_label_size_description": "Scales the small labels above inputs and scale labels.",
"advanced_styling_field_upper_label_weight": "Label Font Weight",
"advanced_styling_field_upper_label_weight_description": "Makes the labels lighter or bolder.",
"advanced_styling_section_buttons": "Buttons",
"advanced_styling_section_headlines": "Headlines & Descriptions",
"advanced_styling_section_inputs": "Inputs",

View File

@@ -1360,7 +1360,7 @@
"audience": "Audiencia",
"auto_close_on_inactivity": "Cierre automático por inactividad",
"auto_progress_rating_and_nps": "Avanzar automáticamente en preguntas de valoración y NPS",
"auto_progress_rating_and_nps_description": "Avance automático en bloques de una sola pregunta. Las preguntas obligatorias ocultan Siguiente, excepto cuando se selecciona \"Otro\".",
"auto_progress_rating_and_nps_description": "Avanza automáticamente cuando los encuestados seleccionen una respuesta en preguntas de valoración o NPS. Esto solo se aplica a bloques de una sola pregunta. Las preguntas obligatorias ocultan el botón Siguiente; las preguntas opcionales aún lo muestran para omitirlas.",
"auto_save_disabled": "Guardado automático desactivado",
"auto_save_disabled_tooltip": "Su encuesta solo se guarda automáticamente cuando está en borrador. Esto asegura que las encuestas públicas no se actualicen involuntariamente.",
"auto_save_on": "Guardado automático activado",
@@ -2298,12 +2298,12 @@
"advanced_styling_field_track_bg_description": "Colorea la parte no rellenada de la barra.",
"advanced_styling_field_track_height": "Altura de la pista",
"advanced_styling_field_track_height_description": "Controla el grosor de la barra de progreso.",
"advanced_styling_field_upper_label_color": "Color de la etiqueta del titular",
"advanced_styling_field_upper_label_color_description": "Colorea la etiqueta pequeña sobre los campos de entrada.",
"advanced_styling_field_upper_label_size": "Tamaño de fuente de la etiqueta del titular",
"advanced_styling_field_upper_label_size_description": "Escala la etiqueta pequeña sobre los campos de entrada.",
"advanced_styling_field_upper_label_weight": "Grosor de fuente de la etiqueta del titular",
"advanced_styling_field_upper_label_weight_description": "Hace que la etiqueta sea más ligera o más gruesa.",
"advanced_styling_field_upper_label_color": "Color de la etiqueta",
"advanced_styling_field_upper_label_color_description": "Colorea las etiquetas pequeñas sobre los campos de entrada y las etiquetas de escala.",
"advanced_styling_field_upper_label_size": "Tamaño de fuente de la etiqueta",
"advanced_styling_field_upper_label_size_description": "Escala las etiquetas pequeñas sobre los campos de entrada y las etiquetas de escala.",
"advanced_styling_field_upper_label_weight": "Grosor de fuente de la etiqueta",
"advanced_styling_field_upper_label_weight_description": "Hace que las etiquetas sean más ligeras o más gruesas.",
"advanced_styling_section_buttons": "Botones",
"advanced_styling_section_headlines": "Títulos y descripciones",
"advanced_styling_section_inputs": "Campos de entrada",

View File

@@ -1360,7 +1360,7 @@
"audience": "Public",
"auto_close_on_inactivity": "Fermeture automatique en cas d'inactivité",
"auto_progress_rating_and_nps": "Progression automatique pour les questions d'évaluation et NPS",
"auto_progress_rating_and_nps_description": "Avancement automatique dans les blocs à question unique. Les questions obligatoires masquent le bouton Suivant, sauf lorsque « Autre » est sélection.",
"auto_progress_rating_and_nps_description": "Passe automatiquement à la question suivante lorsque les répondants sélectionnent une réponse aux questions d'évaluation ou NPS. Cela s'applique uniquement aux blocs à question unique. Les questions obligatoires masquent le bouton Suivant ; les questions facultatives l'affichent toujours pour permettre de passer la question.",
"auto_save_disabled": "Sauvegarde automatique désactivée",
"auto_save_disabled_tooltip": "Votre sondage n'est sauvegardé automatiquement que lorsqu'il est en brouillon. Cela garantit que les sondages publics ne sont pas mis à jour involontairement.",
"auto_save_on": "Sauvegarde automatique activée",
@@ -2298,12 +2298,12 @@
"advanced_styling_field_track_bg_description": "Colore la partie non remplie de la barre.",
"advanced_styling_field_track_height": "Hauteur de la piste",
"advanced_styling_field_track_height_description": "Contrôle l'épaisseur de la barre de progression.",
"advanced_styling_field_upper_label_color": "Couleur de l'étiquette du titre",
"advanced_styling_field_upper_label_color_description": "Colore le petit libellé au-dessus des champs de saisie.",
"advanced_styling_field_upper_label_size": "Taille de police de l'étiquette du titre",
"advanced_styling_field_upper_label_size_description": "Ajuste la taille du petit libellé au-dessus des champs de saisie.",
"advanced_styling_field_upper_label_weight": "Graisse de police de l'étiquette du titre",
"advanced_styling_field_upper_label_weight_description": "Rend le libellé plus léger ou plus gras.",
"advanced_styling_field_upper_label_color": "Couleur de l'étiquette",
"advanced_styling_field_upper_label_color_description": "Colore les petits libellés au-dessus des champs de saisie et les libellés d'échelle.",
"advanced_styling_field_upper_label_size": "Taille de police de l'étiquette",
"advanced_styling_field_upper_label_size_description": "Ajuste la taille des petits libellés au-dessus des champs de saisie et des libellés d'échelle.",
"advanced_styling_field_upper_label_weight": "Graisse de police de l'étiquette",
"advanced_styling_field_upper_label_weight_description": "Rend les libellés plus légers ou plus gras.",
"advanced_styling_section_buttons": "Boutons",
"advanced_styling_section_headlines": "Titres et descriptions",
"advanced_styling_section_inputs": "Champs de saisie",

View File

@@ -1360,7 +1360,7 @@
"audience": "Közönség",
"auto_close_on_inactivity": "Automatikus lezárás tétlenségnél",
"auto_progress_rating_and_nps": "Automatikus továbblépés értékelési és NPS kérdéseknél",
"auto_progress_rating_and_nps_description": "Automatikus továbblépés egy kérdést tartalmazó blokkokban. A kötelező kérdések elrejtik a Tovább gombot, kivéve amikor az „Egyéb“ opció van kiválasztva.",
"auto_progress_rating_and_nps_description": "Automatikus továbblépés, amikor a válaszadók kiválasztanak egy választ az értékelési vagy NPS kérdéseknél. Ez csak az egykérdéses blokkokra vonatkozik. A kötelező kérdések elrejtik a Tovább gombot; az opcionális kérdések továbbra is megjelenítik azt a kihagyás lehetősége érdekében.",
"auto_save_disabled": "Az automatikus mentés letiltva",
"auto_save_disabled_tooltip": "A kérdőív csak akkor kerül automatikusan mentésre, ha piszkozatban van. Ez biztosítja, hogy a nyilvános kérdőívek ne legyenek véletlenül frissítve.",
"auto_save_on": "Automatikus mentés bekapcsolva",
@@ -2298,12 +2298,12 @@
"advanced_styling_field_track_bg_description": "Kiszínezi a sáv kitöltetlen részét.",
"advanced_styling_field_track_height": "Követés magassága",
"advanced_styling_field_track_height_description": "A folyamatjelző vastagságát vezérli.",
"advanced_styling_field_upper_label_color": "Címsor címkéjének színe",
"advanced_styling_field_upper_label_color_description": "Kiszínezi a beviteli mezők fölötti kis címkéket.",
"advanced_styling_field_upper_label_size": "Címsor címkéjének betűmérete",
"advanced_styling_field_upper_label_size_description": "Átméretezi a beviteli mezők fölötti kis címkéket.",
"advanced_styling_field_upper_label_weight": "Címsor címkéjének betűvastagsága",
"advanced_styling_field_upper_label_weight_description": "Vékonyabbá vagy vastagabbá teszi a címkét.",
"advanced_styling_field_upper_label_color": "Címke színe",
"advanced_styling_field_upper_label_color_description": "Kiszínezi a beviteli mezők fölötti kis címkéket és a skálacímkéket.",
"advanced_styling_field_upper_label_size": "Címke betűmérete",
"advanced_styling_field_upper_label_size_description": "Átméretezi a beviteli mezők fölötti kis címkéket és a skálacímkéket.",
"advanced_styling_field_upper_label_weight": "Címke betűvastagsága",
"advanced_styling_field_upper_label_weight_description": "Vékonyabbá vagy vastagabbá teszi a címkéket.",
"advanced_styling_section_buttons": "Gombok",
"advanced_styling_section_headlines": "Címsorok és leírások",
"advanced_styling_section_inputs": "Beviteli mezők",

View File

@@ -1360,7 +1360,7 @@
"audience": "オーディエンス",
"auto_close_on_inactivity": "非アクティブ時に自動閉鎖",
"auto_progress_rating_and_nps": "評価とNPSの質問を自動進行",
"auto_progress_rating_and_nps_description": "単一質問ブロックで自動的に次へ進みます。必須質問では「次へ」ボタンが非表示になりますが、「その他」が選択された場合は表示されます。",
"auto_progress_rating_and_nps_description": "評価またはNPSの質問で回答者が選択肢を選んだ際に自動的に次へ進みます。これは単一質問ブロックにのみ適用されます。必須質問では「次へ」ボタンが非表示になり、任意の質問ではスキップ用に引き続き表示されます。",
"auto_save_disabled": "自動保存が無効",
"auto_save_disabled_tooltip": "アンケートは下書き状態の時のみ自動保存されます。これにより、公開中のアンケートが意図せず更新されることを防ぎます。",
"auto_save_on": "自動保存オン",
@@ -2298,11 +2298,11 @@
"advanced_styling_field_track_bg_description": "バーの未入力部分の色を設定します。",
"advanced_styling_field_track_height": "トラックの高さ",
"advanced_styling_field_track_height_description": "プログレスバーの太さを調整します。",
"advanced_styling_field_upper_label_color": "見出しラベルの色",
"advanced_styling_field_upper_label_color_description": "入力フィールド上部の小さなラベルの色を設定します。",
"advanced_styling_field_upper_label_size": "見出しラベルのフォントサイズ",
"advanced_styling_field_upper_label_size_description": "入力フィールド上部の小さなラベルのサイズを調整します。",
"advanced_styling_field_upper_label_weight": "見出しラベルのフォントの太さ",
"advanced_styling_field_upper_label_color": "ラベルの色",
"advanced_styling_field_upper_label_color_description": "入力フィールド上部の小さなラベルとスケールラベルの色を設定します。",
"advanced_styling_field_upper_label_size": "ラベルのフォントサイズ",
"advanced_styling_field_upper_label_size_description": "入力フィールド上部の小さなラベルとスケールラベルのサイズを調整します。",
"advanced_styling_field_upper_label_weight": "ラベルのフォントの太さ",
"advanced_styling_field_upper_label_weight_description": "ラベルを細くまたは太くします。",
"advanced_styling_section_buttons": "ボタン",
"advanced_styling_section_headlines": "見出しと説明",

View File

@@ -1360,7 +1360,7 @@
"audience": "Publiek",
"auto_close_on_inactivity": "Automatisch sluiten bij inactiviteit",
"auto_progress_rating_and_nps": "Automatisch doorgaan bij beoordelings- en NPS-vragen",
"auto_progress_rating_and_nps_description": "Automatisch doorgaan bij blokken met één vraag. Verplichte vragen verbergen Volgende, behalve wanneer \"Anders\" is geselecteerd.",
"auto_progress_rating_and_nps_description": "Ga automatisch verder wanneer respondenten een antwoord selecteren bij beoordelings- of NPS-vragen. Dit geldt alleen voor blokken met één vraag. Bij verplichte vragen wordt de Volgende-knop verborgen; bij optionele vragen blijft deze zichtbaar om de vraag over te slaan.",
"auto_save_disabled": "Automatisch opslaan uitgeschakeld",
"auto_save_disabled_tooltip": "Uw enquête wordt alleen automatisch opgeslagen wanneer deze een concept is. Dit zorgt ervoor dat openbare enquêtes niet onbedoeld worden bijgewerkt.",
"auto_save_on": "Automatisch opslaan aan",
@@ -2298,12 +2298,12 @@
"advanced_styling_field_track_bg_description": "Kleurt het ongevulde gedeelte van de balk.",
"advanced_styling_field_track_height": "Spoorhoogte",
"advanced_styling_field_track_height_description": "Regelt de dikte van de voortgangsbalk.",
"advanced_styling_field_upper_label_color": "Koplabelkleur",
"advanced_styling_field_upper_label_color_description": "Kleurt het kleine label boven invoervelden.",
"advanced_styling_field_upper_label_size": "Lettergrootte koplabel",
"advanced_styling_field_upper_label_size_description": "Schaalt het kleine label boven invoervelden.",
"advanced_styling_field_upper_label_weight": "Letterdikte koplabel",
"advanced_styling_field_upper_label_weight_description": "Maakt het label lichter of vetter.",
"advanced_styling_field_upper_label_color": "Labelkleur",
"advanced_styling_field_upper_label_color_description": "Kleurt de kleine labels boven invoervelden en schaallabels.",
"advanced_styling_field_upper_label_size": "Lettergrootte label",
"advanced_styling_field_upper_label_size_description": "Schaalt de kleine labels boven invoervelden en schaallabels.",
"advanced_styling_field_upper_label_weight": "Letterdikte label",
"advanced_styling_field_upper_label_weight_description": "Maakt de labels lichter of vetter.",
"advanced_styling_section_buttons": "Knoppen",
"advanced_styling_section_headlines": "Koppen & beschrijvingen",
"advanced_styling_section_inputs": "Invoervelden",

View File

@@ -1360,7 +1360,7 @@
"audience": "Público",
"auto_close_on_inactivity": "Fechar automaticamente por inatividade",
"auto_progress_rating_and_nps": "Avançar automaticamente em perguntas de avaliação e NPS",
"auto_progress_rating_and_nps_description": "Avança automaticamente em blocos de pergunta única. Perguntas obrigatórias ocultam o botão Próximo, exceto quando \"Outro\" está selecionado.",
"auto_progress_rating_and_nps_description": "Avança automaticamente quando os respondentes selecionam uma resposta em perguntas de avaliação ou NPS. Isso se aplica apenas a blocos com uma única pergunta. Perguntas obrigatórias ocultam o botão Próximo; perguntas opcionais ainda o exibem para permitir pular.",
"auto_save_disabled": "Salvamento automático desativado",
"auto_save_disabled_tooltip": "Sua pesquisa só é salva automaticamente quando está em rascunho. Isso garante que pesquisas públicas não sejam atualizadas involuntariamente.",
"auto_save_on": "Salvamento automático ativado",
@@ -2298,12 +2298,12 @@
"advanced_styling_field_track_bg_description": "Colore a porção não preenchida da barra.",
"advanced_styling_field_track_height": "Altura da trilha",
"advanced_styling_field_track_height_description": "Controla a espessura da barra de progresso.",
"advanced_styling_field_upper_label_color": "Cor do rótulo do título",
"advanced_styling_field_upper_label_color_description": "Colore o pequeno rótulo acima dos campos de entrada.",
"advanced_styling_field_upper_label_size": "Tamanho da fonte do rótulo do título",
"advanced_styling_field_upper_label_size_description": "Ajusta o tamanho do pequeno rótulo acima dos campos de entrada.",
"advanced_styling_field_upper_label_weight": "Peso da fonte do rótulo do título",
"advanced_styling_field_upper_label_weight_description": "Torna o rótulo mais leve ou mais negrito.",
"advanced_styling_field_upper_label_color": "Cor do rótulo",
"advanced_styling_field_upper_label_color_description": "Colore os pequenos rótulos acima dos campos de entrada e os rótulos de escala.",
"advanced_styling_field_upper_label_size": "Tamanho da fonte do rótulo",
"advanced_styling_field_upper_label_size_description": "Ajusta o tamanho dos pequenos rótulos acima dos campos de entrada e dos rótulos de escala.",
"advanced_styling_field_upper_label_weight": "Peso da fonte do rótulo",
"advanced_styling_field_upper_label_weight_description": "Torna os rótulos mais leves ou mais negritos.",
"advanced_styling_section_buttons": "Botões",
"advanced_styling_section_headlines": "Títulos e descrições",
"advanced_styling_section_inputs": "Campos de entrada",

View File

@@ -1360,7 +1360,7 @@
"audience": "Público",
"auto_close_on_inactivity": "Fechar automaticamente por inatividade",
"auto_progress_rating_and_nps": "Avançar automaticamente em perguntas de classificação e NPS",
"auto_progress_rating_and_nps_description": "Avança automaticamente em blocos de pergunta única. Perguntas obrigatórias ocultam o botão Seguinte, exceto quando \"Outro\" está selecionado.",
"auto_progress_rating_and_nps_description": "Avança automaticamente quando os inquiridos selecionam uma resposta em perguntas de classificação ou NPS. Isto aplica-se apenas a blocos com uma única pergunta. Perguntas obrigatórias ocultam o botão Seguinte; perguntas opcionais continuam a mostrá-lo para permitir saltar.",
"auto_save_disabled": "Guardar automático desativado",
"auto_save_disabled_tooltip": "O seu inquérito só é guardado automaticamente quando está em rascunho. Isto garante que os inquéritos públicos não sejam atualizados involuntariamente.",
"auto_save_on": "Guardar automático ativado",
@@ -2298,12 +2298,12 @@
"advanced_styling_field_track_bg_description": "Colore a porção não preenchida da barra.",
"advanced_styling_field_track_height": "Altura da faixa",
"advanced_styling_field_track_height_description": "Controla a espessura da barra de progresso.",
"advanced_styling_field_upper_label_color": "Cor da etiqueta do título",
"advanced_styling_field_upper_label_color_description": "Colore a pequena etiqueta acima dos campos de entrada.",
"advanced_styling_field_upper_label_size": "Tamanho da fonte da etiqueta do título",
"advanced_styling_field_upper_label_size_description": "Ajusta o tamanho da pequena etiqueta acima dos campos de entrada.",
"advanced_styling_field_upper_label_weight": "Peso da fonte da etiqueta do título",
"advanced_styling_field_upper_label_weight_description": "Torna a etiqueta mais leve ou mais negrito.",
"advanced_styling_field_upper_label_color": "Cor da etiqueta",
"advanced_styling_field_upper_label_color_description": "Colore as pequenas etiquetas acima dos campos de entrada e as etiquetas de escala.",
"advanced_styling_field_upper_label_size": "Tamanho da fonte da etiqueta",
"advanced_styling_field_upper_label_size_description": "Ajusta o tamanho das pequenas etiquetas acima dos campos de entrada e das etiquetas de escala.",
"advanced_styling_field_upper_label_weight": "Peso da fonte da etiqueta",
"advanced_styling_field_upper_label_weight_description": "Torna as etiquetas mais leves ou mais negritas.",
"advanced_styling_section_buttons": "Botões",
"advanced_styling_section_headlines": "Títulos e descrições",
"advanced_styling_section_inputs": "Campos de entrada",

View File

@@ -1360,7 +1360,7 @@
"audience": "Public",
"auto_close_on_inactivity": "Închidere automată la inactivitate",
"auto_progress_rating_and_nps": "Avansare automată pentru întrebări de rating și NPS",
"auto_progress_rating_and_nps_description": "Avansare automată în blocurile cu o singură întrebare. Întrebările obligatorii ascund butonul Următorul, cu excepția cazului în care este selectată opțiunea „Altele“.",
"auto_progress_rating_and_nps_description": "Avansează automat când respondenții selectează un răspuns la întrebările de rating sau NPS. Aceasta se aplică doar blocurilor cu o singură întrebare. Întrebările obligatorii ascund butonul Următorul; întrebările opționale îl afișează în continuare pentru a permite omiterea.",
"auto_save_disabled": "Salvare automată dezactivată",
"auto_save_disabled_tooltip": "Chestionarul dvs. este salvat automat doar când este în ciornă. Acest lucru asigură că sondajele publice nu sunt actualizate neintenționat.",
"auto_save_on": "Salvare automată activată",
@@ -2298,12 +2298,12 @@
"advanced_styling_field_track_bg_description": "Colorează partea necompletată a barei.",
"advanced_styling_field_track_height": "Înălțime track",
"advanced_styling_field_track_height_description": "Controlează grosimea barei de progres.",
"advanced_styling_field_upper_label_color": "Culoare etichetă titlu",
"advanced_styling_field_upper_label_color_description": "Colorează eticheta mică de deasupra câmpurilor.",
"advanced_styling_field_upper_label_size": "Mărime font etichetă titlu",
"advanced_styling_field_upper_label_size_description": "Redimensionează eticheta mică de deasupra câmpurilor.",
"advanced_styling_field_upper_label_weight": "Grosime font etichetă titlu",
"advanced_styling_field_upper_label_weight_description": "Face eticheta mai subțire sau mai îngroșată.",
"advanced_styling_field_upper_label_color": "Culoare etichetă",
"advanced_styling_field_upper_label_color_description": "Colorează etichetele mici de deasupra câmpurilor și etichetele de scală.",
"advanced_styling_field_upper_label_size": "Mărime font etichetă",
"advanced_styling_field_upper_label_size_description": "Redimensionează etichetele mici de deasupra câmpurilor și etichetele de scală.",
"advanced_styling_field_upper_label_weight": "Grosime font etichetă",
"advanced_styling_field_upper_label_weight_description": "Face etichetele mai subțiri sau mai îngroșate.",
"advanced_styling_section_buttons": "Butoane",
"advanced_styling_section_headlines": "Titluri și descrieri",
"advanced_styling_section_inputs": "Inputuri",

View File

@@ -1360,7 +1360,7 @@
"audience": "Аудитория",
"auto_close_on_inactivity": "Автоматически закрывать при бездействии",
"auto_progress_rating_and_nps": "Автоматический переход для вопросов с оценкой и NPS",
"auto_progress_rating_and_nps_description": "Автоматический переход в блоках с одним вопросом. Обязательные вопросы скрывают кнопку «Далее», за исключением случаев, когда выбран вариант «Другое».",
"auto_progress_rating_and_nps_description": "Автоматически переходить к следующему шагу, когда респонденты выбирают ответ в вопросах с оценкой или NPS. Это применяется только к блокам с одним вопросом. В обязательных вопросах кнопка «Далее» скрыта; в необязательных вопросах она остается видимой для пропуска.",
"auto_save_disabled": "Автосохранение отключено",
"auto_save_disabled_tooltip": "Ваш опрос автоматически сохраняется только в режиме черновика. Это гарантирует, что публичные опросы не будут случайно обновлены.",
"auto_save_on": "Автосохранение включено",
@@ -2298,12 +2298,12 @@
"advanced_styling_field_track_bg_description": "Задаёт цвет незаполненной части полосы.",
"advanced_styling_field_track_height": "Высота трека",
"advanced_styling_field_track_height_description": "Управляет толщиной индикатора прогресса.",
"advanced_styling_field_upper_label_color": "Цвет метки заголовка",
"advanced_styling_field_upper_label_color_description": "Задаёт цвет маленькой метки над полями ввода.",
"advanced_styling_field_upper_label_size": "Размер шрифта метки заголовка",
"advanced_styling_field_upper_label_size_description": "Изменяет размер маленькой метки над полями ввода.",
"advanced_styling_field_upper_label_weight": "Толщина шрифта метки заголовка",
"advanced_styling_field_upper_label_weight_description": "Делает метку тоньше или жирнее.",
"advanced_styling_field_upper_label_color": "Цвет метки",
"advanced_styling_field_upper_label_color_description": "Задаёт цвет маленьких меток над полями ввода и меток шкалы.",
"advanced_styling_field_upper_label_size": "Размер шрифта метки",
"advanced_styling_field_upper_label_size_description": "Изменяет размер маленьких меток над полями ввода и меток шкалы.",
"advanced_styling_field_upper_label_weight": "Толщина шрифта метки",
"advanced_styling_field_upper_label_weight_description": "Делает метки тоньше или жирнее.",
"advanced_styling_section_buttons": "Кнопки",
"advanced_styling_section_headlines": "Заголовки и описания",
"advanced_styling_section_inputs": "Поля ввода",

View File

@@ -1360,7 +1360,7 @@
"audience": "Målgrupp",
"auto_close_on_inactivity": "Stäng automatiskt vid inaktivitet",
"auto_progress_rating_and_nps": "Gå vidare automatiskt vid betygs- och NPS-frågor",
"auto_progress_rating_and_nps_description": "Gå automatiskt vidare i block med en enda fråga. Obligatoriska frågor döljer Nästa, utom när \"Annat\" är valt.",
"auto_progress_rating_and_nps_description": "Gå automatiskt vidare när respondenter väljer ett svar på betygs- eller NPS-frågor. Detta gäller endast block med en enda fråga. Obligatoriska frågor döljer Nästa-knappen; valfria frågor visar den fortfarande för att kunna hoppas över.",
"auto_save_disabled": "Automatisk sparning inaktiverad",
"auto_save_disabled_tooltip": "Din enkät sparas endast automatiskt när den är ett utkast. Detta säkerställer att publika enkäter inte uppdateras oavsiktligt.",
"auto_save_on": "Automatisk sparning på",
@@ -2298,12 +2298,12 @@
"advanced_styling_field_track_bg_description": "Färgar den ofyllda delen av stapeln.",
"advanced_styling_field_track_height": "Spårets höjd",
"advanced_styling_field_track_height_description": "Styr tjockleken på förloppsstapeln.",
"advanced_styling_field_upper_label_color": "Rubriketikettens färg",
"advanced_styling_field_upper_label_color_description": "Färgar den lilla etiketten ovanför fälten.",
"advanced_styling_field_upper_label_size": "Rubriketikettens teckenstorlek",
"advanced_styling_field_upper_label_size_description": "Skalar storleken på den lilla etiketten ovanför fälten.",
"advanced_styling_field_upper_label_weight": "Rubriketikettens teckentjocklek",
"advanced_styling_field_upper_label_weight_description": "Gör etiketten tunnare eller fetare.",
"advanced_styling_field_upper_label_color": "Etikettfärg",
"advanced_styling_field_upper_label_color_description": "Färgar de små etiketterna ovanför fälten och skaletiketter.",
"advanced_styling_field_upper_label_size": "Etikettens teckenstorlek",
"advanced_styling_field_upper_label_size_description": "Skalar storleken på de små etiketterna ovanför fälten och skaletiketter.",
"advanced_styling_field_upper_label_weight": "Etikettens teckentjocklek",
"advanced_styling_field_upper_label_weight_description": "Gör etiketterna tunnare eller fetare.",
"advanced_styling_section_buttons": "Knappar",
"advanced_styling_section_headlines": "Rubriker & beskrivningar",
"advanced_styling_section_inputs": "Inmatningar",

View File

@@ -1360,7 +1360,7 @@
"audience": "受众",
"auto_close_on_inactivity": "自动关闭 在 无活动时",
"auto_progress_rating_and_nps": "自动推进评分和 NPS 问题",
"auto_progress_rating_and_nps_description": "在单问题块中自动前进。必填问题会隐藏\"下一步\"按钮,除非选择了\"其他\"选项。",
"auto_progress_rating_and_nps_description": "当受访者在评分或 NPS 问题上选择答案时自动前进。这仅适用于单问题区块。必填问题会隐藏\"下一步\"按钮;可选问题仍会显示该按钮以便跳过。",
"auto_save_disabled": "自动保存已禁用",
"auto_save_disabled_tooltip": "您的调查仅在草稿状态时自动保存。这确保公开的调查不会被意外更新。",
"auto_save_on": "自动保存已启用",
@@ -2298,11 +2298,11 @@
"advanced_styling_field_track_bg_description": "设置进度条未填充部分的颜色。",
"advanced_styling_field_track_height": "轨道高度",
"advanced_styling_field_track_height_description": "控制进度条的粗细。",
"advanced_styling_field_upper_label_color": "标题标签颜色",
"advanced_styling_field_upper_label_color_description": "设置输入框上方小标签的颜色。",
"advanced_styling_field_upper_label_size": "标题标签字体大小",
"advanced_styling_field_upper_label_size_description": "调整输入框上方小标签的大小。",
"advanced_styling_field_upper_label_weight": "标题标签字体粗细",
"advanced_styling_field_upper_label_color": "标签颜色",
"advanced_styling_field_upper_label_color_description": "设置输入框上方小标签和刻度标签的颜色。",
"advanced_styling_field_upper_label_size": "标签字体大小",
"advanced_styling_field_upper_label_size_description": "调整输入框上方小标签和刻度标签的大小。",
"advanced_styling_field_upper_label_weight": "标签字体粗细",
"advanced_styling_field_upper_label_weight_description": "设置标签文字的粗细。",
"advanced_styling_section_buttons": "按钮",
"advanced_styling_section_headlines": "标题和描述",

View File

@@ -1360,7 +1360,7 @@
"audience": "受眾",
"auto_close_on_inactivity": "非活動時自動關閉",
"auto_progress_rating_and_nps": "自動前進評分與 NPS 問題",
"auto_progress_rating_and_nps_description": "在單一問題區塊中自動前進。必填問題會隱藏「下一步」按鈕,除非選擇了「其他」選項。",
"auto_progress_rating_and_nps_description": "當受訪者在評分或 NPS 問題中選擇答案時自動前進。此設定僅適用於單一問題區塊。必填問題會隱藏「下一步」按鈕;選填問題仍會顯示該按鈕以便跳過。",
"auto_save_disabled": "自動儲存已停用",
"auto_save_disabled_tooltip": "您的問卷僅在草稿狀態時自動儲存。這確保公開的問卷不會被意外更新。",
"auto_save_on": "自動儲存已啟用",
@@ -2298,11 +2298,11 @@
"advanced_styling_field_track_bg_description": "設定進度條未填滿部分的顏色。",
"advanced_styling_field_track_height": "軌道高度",
"advanced_styling_field_track_height_description": "調整進度條的厚度。",
"advanced_styling_field_upper_label_color": "標題標籤顏色",
"advanced_styling_field_upper_label_color_description": "設定輸入框上方小標籤的顏色。",
"advanced_styling_field_upper_label_size": "標題標籤字體大小",
"advanced_styling_field_upper_label_size_description": "調整輸入框上方小標籤的大小。",
"advanced_styling_field_upper_label_weight": "標題標籤字體粗細",
"advanced_styling_field_upper_label_color": "標籤顏色",
"advanced_styling_field_upper_label_color_description": "設定輸入框上方小標籤和刻度標籤的顏色。",
"advanced_styling_field_upper_label_size": "標籤字體大小",
"advanced_styling_field_upper_label_size_description": "調整輸入框上方小標籤和刻度標籤的大小。",
"advanced_styling_field_upper_label_weight": "標籤字體粗細",
"advanced_styling_field_upper_label_weight_description": "讓標籤字體變細或變粗。",
"advanced_styling_section_buttons": "按鈕",
"advanced_styling_section_headlines": "標題與說明",

View File

@@ -1,7 +1,7 @@
import { Skeleton } from "@/modules/ui/components/skeleton";
type SkeletonLoaderProps = {
type: "response" | "summary";
type: "response" | "responseTable" | "summary";
};
export const SkeletonLoader = ({ type }: SkeletonLoaderProps) => {
@@ -25,6 +25,43 @@ export const SkeletonLoader = ({ type }: SkeletonLoaderProps) => {
);
}
if (type === "responseTable") {
const renderTableCells = () => (
<>
<Skeleton className="h-4 w-4 rounded-xl bg-slate-400" />
<Skeleton className="h-4 w-24 rounded-xl bg-slate-200" />
<Skeleton className="h-4 w-32 rounded-xl bg-slate-200" />
<Skeleton className="h-4 w-40 rounded-xl bg-slate-200" />
<Skeleton className="h-4 w-40 rounded-xl bg-slate-200" />
<Skeleton className="h-4 w-32 rounded-xl bg-slate-200" />
</>
);
return (
<div className="animate-pulse space-y-4" data-testid="skeleton-loader-response-table">
<div className="flex items-center justify-between">
<Skeleton className="h-8 w-48 rounded-md bg-slate-300" />
<div className="flex gap-2">
<Skeleton className="h-8 w-8 rounded-md bg-slate-300" />
<Skeleton className="h-8 w-8 rounded-md bg-slate-300" />
</div>
</div>
<div className="overflow-hidden rounded-xl border border-slate-200">
<div className="flex h-12 items-center gap-4 border-b border-slate-200 bg-slate-100 px-4">
{renderTableCells()}
</div>
{Array.from({ length: 10 }, (_, i) => (
<div
key={i}
className="flex h-12 items-center gap-4 border-b border-slate-100 px-4 last:border-b-0">
{renderTableCells()}
</div>
))}
</div>
</div>
);
}
if (type === "response") {
return (
<div className="group space-y-4 rounded-lg bg-white p-6" data-testid="skeleton-loader-response">

View File

@@ -60,9 +60,9 @@ test.describe("Survey Styling", async () => {
await setDimension(page, "Headline Font Size", "24");
await setDimension(page, "Description Font Size", "18");
await setDimension(page, "Headline Font Weight", "700");
await setColor(page, "Headline Label Color", "0000aa"); // Blue-ish
await setDimension(page, "Headline Label Font Size", "14");
await setDimension(page, "Headline Label Font Weight", "600");
await setColor(page, "Label Color", "0000aa"); // Blue-ish
await setDimension(page, "Label Font Size", "14");
await setDimension(page, "Label Font Weight", "600");
// Verify Typography Variables
await page.waitForTimeout(1000);

View File

@@ -182,12 +182,12 @@ function NPS({
{(lowerLabel ?? upperLabel) ? (
<div className="mt-2 flex justify-between gap-8 px-1.5">
{lowerLabel ? (
<Label variant="default" className="max-w-[50%] text-xs leading-6" dir={dir}>
<Label variant="card" className="max-w-[50%] leading-6" dir={dir}>
{lowerLabel}
</Label>
) : null}
{upperLabel ? (
<Label variant="default" className="max-w-[50%] text-right text-xs leading-6" dir={dir}>
<Label variant="card" className="max-w-[50%] text-right leading-6" dir={dir}>
{upperLabel}
</Label>
) : null}

View File

@@ -426,12 +426,12 @@ function Rating({
{(lowerLabel ?? upperLabel) ? (
<div className="mt-4 flex justify-between gap-8 px-1.5">
{lowerLabel ? (
<Label variant="default" className="max-w-[50%] text-xs leading-6" dir={dir}>
<Label variant="card" className="max-w-[50%] leading-6" dir={dir}>
{lowerLabel}
</Label>
) : null}
{upperLabel ? (
<Label variant="default" className="max-w-[50%] text-right text-xs leading-6" dir={dir}>
<Label variant="card" className="max-w-[50%] text-right leading-6" dir={dir}>
{upperLabel}
</Label>
) : null}

View File

@@ -78,58 +78,6 @@ interface SingleSelectProps {
searchNoResultsText?: string;
}
const useDropdownCommitState = ({
variant,
selectedValue,
onChange,
handleDropdownOpen,
handleDropdownClose,
}: {
variant: "list" | "dropdown";
selectedValue: string | undefined;
onChange: (value: string) => void;
handleDropdownOpen: () => void;
handleDropdownClose: () => void;
}) => {
const [isDropdownOpen, setIsDropdownOpen] = React.useState(false);
const [pendingDropdownValue, setPendingDropdownValue] = React.useState<string | undefined>(selectedValue);
const [hasPendingDropdownChange, setHasPendingDropdownChange] = React.useState(false);
React.useEffect(() => {
if (!isDropdownOpen) {
setPendingDropdownValue(selectedValue);
setHasPendingDropdownChange(false);
}
}, [selectedValue, isDropdownOpen]);
const handleDropdownOpenChange = (open: boolean) => {
setIsDropdownOpen(open);
if (open) {
setPendingDropdownValue(selectedValue);
setHasPendingDropdownChange(false);
handleDropdownOpen();
return;
}
handleDropdownClose();
if (hasPendingDropdownChange && pendingDropdownValue && pendingDropdownValue !== selectedValue) {
onChange(pendingDropdownValue);
}
setHasPendingDropdownChange(false);
};
const effectiveSelectedValue =
variant === "dropdown" && isDropdownOpen ? pendingDropdownValue : selectedValue;
return {
effectiveSelectedValue,
handleDropdownOpenChange,
setPendingDropdownValue,
setHasPendingDropdownChange,
};
};
// NOSONAR - This component intentionally keeps list/dropdown rendering in one place for consistent a11y behavior.
function SingleSelect({
elementId,
headline,
@@ -180,19 +128,6 @@ function SingleSelect({
handleDropdownClose,
} = useDropdownSearch({ options, hasOtherOption, otherOptionLabel, isSearchEnabled: showSearch });
const {
effectiveSelectedValue,
handleDropdownOpenChange,
setPendingDropdownValue,
setHasPendingDropdownChange,
} = useDropdownCommitState({
variant,
selectedValue,
onChange,
handleDropdownOpen,
handleDropdownClose,
});
React.useEffect(() => {
if (!isOtherSelected || disabled) return;
@@ -227,7 +162,7 @@ function SingleSelect({
const optionLabelClassName = "font-option text-option font-option-weight text-option-label";
// Get selected option label for dropdown display
const selectedOption = options.find((opt) => opt.id === effectiveSelectedValue);
const selectedOption = options.find((opt) => opt.id === selectedValue);
const displayText = isOtherSelected
? otherValue || otherOptionLabel
: (selectedOption?.label ?? placeholder);
@@ -250,7 +185,11 @@ function SingleSelect({
{variant === "dropdown" ? (
<>
<ElementError errorMessage={errorMessage} dir={dir} />
<DropdownMenu onOpenChange={handleDropdownOpenChange}>
<DropdownMenu
onOpenChange={(open) => {
if (open) handleDropdownOpen();
else handleDropdownClose();
}}>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
@@ -278,12 +217,7 @@ function SingleSelect({
/>
) : null}
<div className="max-h-[260px] overflow-y-auto">
<DropdownMenuRadioGroup
value={effectiveSelectedValue}
onValueChange={(newValue) => {
setPendingDropdownValue(newValue);
setHasPendingDropdownChange(newValue !== selectedValue);
}}>
<DropdownMenuRadioGroup value={selectedValue} onValueChange={onChange}>
{filteredRegularOptions.map((option) => {
const optionId = `${inputId}-${option.id}`;

View File

@@ -75,7 +75,7 @@ function ElementHeader({
{/* Headline */}
<div>
<div>{required ? <span className="label-upper mb-[3px]">{requiredLabel}</span> : null}</div>
<div>{required ? <span className="label-card mb-[3px]">{requiredLabel}</span> : null}</div>
<div className="flex">
{isHeadlineHtml && safeHeadlineHtml ? (
<Label htmlFor={htmlFor} variant="headline">

View File

@@ -4,7 +4,7 @@ import { cn, stripInlineStyles } from "@/lib/utils";
interface LabelProps extends React.ComponentProps<"label"> {
/** Label variant for different styling contexts */
variant?: "default" | "headline" | "description";
variant?: "default" | "headline" | "description" | "card";
}
/**
@@ -51,6 +51,8 @@ function Label({
variantClass = "label-headline";
} else if (variant === "description") {
variantClass = "label-description";
} else if (variant === "card") {
variantClass = "label-card";
}
// Base classes - use flex-col for HTML content to allow line breaks, flex items-center for non-HTML

View File

@@ -197,8 +197,8 @@
opacity: var(--fb-label-opacity);
}
#fbjs .label-upper,
#fbjs .label-upper * {
#fbjs .label-card,
#fbjs .label-card * {
font-family: var(--fb-element-upper-label-font-family);
font-weight: var(--fb-element-upper-label-font-weight);
font-size: var(--fb-element-upper-label-font-size);

View File

@@ -20,8 +20,6 @@ import { getLocalizedValue } from "@/lib/i18n";
import { cn } from "@/lib/utils";
import { getFirstErrorMessage, validateBlockResponses } from "@/lib/validation/evaluator";
const AUTO_PROGRESS_SUBMIT_DELAY_MS = 350;
interface BlockConditionalProps {
block: TSurveyBlock;
value: TResponseData;
@@ -84,8 +82,7 @@ export function BlockConditional({
const autoProgressElement = getAutoProgressElement(block.elements, isAutoProgressingEnabled);
const shouldHideSubmitButton = shouldHideSubmitButtonForAutoProgress(
block.elements,
isAutoProgressingEnabled,
value
isAutoProgressingEnabled
);
// Handle change for an individual element
@@ -131,7 +128,7 @@ export function BlockConditional({
} finally {
autoProgressingInFlightRef.current = false;
}
}, AUTO_PROGRESS_SUBMIT_DELAY_MS);
}, 0);
}
};

View File

@@ -33,7 +33,7 @@ export function Headline({
<label htmlFor={elementId} className="text-heading mb-[3px] flex flex-col">
{hasRequiredRule && isQuestionCard && (
<span
className="label-upper mb-[3px] text-xs leading-6 font-normal opacity-60"
className="label-card mb-[3px] text-xs leading-6 font-normal opacity-60"
tabIndex={-1}
data-testid="fb__surveys__headline-optional-text-test">
{t("common.required")}

View File

@@ -2,7 +2,6 @@ import { describe, expect, test } from "vitest";
import { type TSurveyElement, TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements";
import {
getAutoProgressElement,
isSingleSelectOtherSelected,
shouldHideSubmitButtonForAutoProgress,
shouldTriggerAutoProgress,
} from "./auto-progress";
@@ -14,62 +13,26 @@ const createElement = (id: string, type: TSurveyElementTypeEnum, required: boole
required,
}) as unknown as TSurveyElement;
const createSingleSelectElement = (required: boolean): TSurveyElement =>
({
id: "single_select_1",
type: TSurveyElementTypeEnum.MultipleChoiceSingle,
required,
choices: [
{ id: "choice_1", label: { default: "Choice 1", de: "Auswahl 1" } },
{ id: "choice_2", label: { default: "Choice 2" } },
{ id: "other", label: { default: "Other" } },
],
}) as unknown as TSurveyElement;
const createPictureSelectionElement = (required: boolean, allowMulti: boolean): TSurveyElement =>
({
id: "picture_1",
type: TSurveyElementTypeEnum.PictureSelection,
required,
allowMulti,
choices: [
{ id: "pic_1", imageUrl: "https://example.com/1.png" },
{ id: "pic_2", imageUrl: "https://example.com/2.png" },
],
}) as unknown as TSurveyElement;
describe("auto-progress helpers", () => {
test("returns auto-progress element for all supported single-question types only", () => {
test("returns auto-progress element for single rating/nps blocks only", () => {
const ratingElement = createElement("rating_1", TSurveyElementTypeEnum.Rating, true);
const npsElement = createElement("nps_1", TSurveyElementTypeEnum.NPS, false);
const singleSelectElement = createSingleSelectElement(false);
const singlePictureElement = createPictureSelectionElement(false, false);
const multiPictureElement = createPictureSelectionElement(false, true);
const openTextElement = createElement("text_1", TSurveyElementTypeEnum.OpenText, false);
expect(getAutoProgressElement([ratingElement], true)).toEqual(ratingElement);
expect(getAutoProgressElement([npsElement], true)).toEqual(npsElement);
expect(getAutoProgressElement([singleSelectElement], true)).toEqual(singleSelectElement);
expect(getAutoProgressElement([singlePictureElement], true)).toEqual(singlePictureElement);
expect(getAutoProgressElement([multiPictureElement], true)).toBeNull();
expect(getAutoProgressElement([openTextElement], true)).toBeNull();
expect(getAutoProgressElement([ratingElement], false)).toBeNull();
expect(getAutoProgressElement([ratingElement, npsElement], true)).toBeNull();
});
test("hides submit button only for required auto-progress elements with no required+other exception", () => {
test("hides submit button only for required auto-progress elements", () => {
const requiredRating = createElement("rating_required", TSurveyElementTypeEnum.Rating, true);
const optionalRating = createElement("rating_optional", TSurveyElementTypeEnum.Rating, false);
const requiredSingleSelect = createSingleSelectElement(true);
expect(shouldHideSubmitButtonForAutoProgress([requiredRating], true)).toBe(true);
expect(shouldHideSubmitButtonForAutoProgress([optionalRating], true)).toBe(false);
expect(shouldHideSubmitButtonForAutoProgress([requiredRating], false)).toBe(false);
expect(
shouldHideSubmitButtonForAutoProgress([requiredSingleSelect], true, {
[requiredSingleSelect.id]: "",
})
).toBe(false);
});
test("triggers auto-progress only when an eligible response was changed", () => {
@@ -110,76 +73,5 @@ describe("auto-progress helpers", () => {
isAlreadyInFlight: true,
})
).toBe(false);
expect(
shouldTriggerAutoProgress({
changedElementId: "rating_1",
mergedValue: { rating_1: 5 },
autoProgressElement,
isAlreadyInFlight: false,
isCommittedSelection: false,
})
).toBe(false);
});
test("detects single-select other selection from sentinel and custom values", () => {
const autoProgressElement = createSingleSelectElement(true);
expect(
isSingleSelectOtherSelected({
autoProgressElement,
mergedValue: { [autoProgressElement.id]: "" },
})
).toBe(true);
expect(
isSingleSelectOtherSelected({
autoProgressElement,
mergedValue: { [autoProgressElement.id]: "Custom answer" },
})
).toBe(true);
expect(
isSingleSelectOtherSelected({
autoProgressElement,
mergedValue: { [autoProgressElement.id]: "Choice 1" },
})
).toBe(false);
expect(
isSingleSelectOtherSelected({
autoProgressElement,
mergedValue: { [autoProgressElement.id]: "Auswahl 1" },
})
).toBe(false);
expect(
isSingleSelectOtherSelected({
autoProgressElement,
mergedValue: { [autoProgressElement.id]: "choice_1" },
})
).toBe(false);
});
test("does not auto-progress when single-select other is selected", () => {
const autoProgressElement = createSingleSelectElement(true);
expect(
shouldTriggerAutoProgress({
changedElementId: autoProgressElement.id,
mergedValue: { [autoProgressElement.id]: "" },
autoProgressElement,
isAlreadyInFlight: false,
})
).toBe(false);
expect(
shouldTriggerAutoProgress({
changedElementId: autoProgressElement.id,
mergedValue: { [autoProgressElement.id]: "Custom answer" },
autoProgressElement,
isAlreadyInFlight: false,
})
).toBe(false);
});
});

View File

@@ -1,65 +1,8 @@
import { type TResponseData } from "@formbricks/types/responses";
import { type TSurveyElement, TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements";
const isAutoProgressElement = (element: TSurveyElement): boolean => {
if (element.type === TSurveyElementTypeEnum.Rating || element.type === TSurveyElementTypeEnum.NPS) {
return true;
}
if (element.type === TSurveyElementTypeEnum.MultipleChoiceSingle) {
return true;
}
if (element.type === TSurveyElementTypeEnum.PictureSelection) {
return !element.allowMulti;
}
return false;
};
const getAllChoiceLabels = (
element: Extract<TSurveyElement, { type: TSurveyElementTypeEnum.MultipleChoiceSingle }>
) =>
element.choices.filter((choice) => choice.id !== "other").flatMap((choice) => Object.values(choice.label));
export const isSingleSelectOtherSelected = ({
autoProgressElement,
mergedValue,
}: {
autoProgressElement: TSurveyElement | null;
mergedValue: TResponseData;
}): boolean => {
if (!autoProgressElement || autoProgressElement.type !== TSurveyElementTypeEnum.MultipleChoiceSingle) {
return false;
}
const hasOtherOption = autoProgressElement.choices.some((choice) => choice.id === "other");
if (!hasOtherOption) {
return false;
}
const currentValue = mergedValue[autoProgressElement.id];
if (currentValue === undefined) {
return false;
}
if (currentValue === "") {
return true;
}
if (typeof currentValue !== "string") {
return false;
}
const regularChoiceIds = autoProgressElement.choices
.filter((choice) => choice.id !== "other")
.map((choice) => choice.id);
if (regularChoiceIds.includes(currentValue)) {
return false;
}
return !getAllChoiceLabels(autoProgressElement).includes(currentValue);
};
const isAutoProgressElementType = (type: TSurveyElementTypeEnum): boolean =>
type === TSurveyElementTypeEnum.Rating || type === TSurveyElementTypeEnum.NPS;
export const getAutoProgressElement = (
elements: TSurveyElement[],
@@ -70,24 +13,15 @@ export const getAutoProgressElement = (
}
const [element] = elements;
return isAutoProgressElement(element) ? element : null;
return isAutoProgressElementType(element.type) ? element : null;
};
export const shouldHideSubmitButtonForAutoProgress = (
elements: TSurveyElement[],
isAutoProgressingEnabled: boolean,
mergedValue: TResponseData = {}
isAutoProgressingEnabled: boolean
): boolean => {
const autoProgressElement = getAutoProgressElement(elements, isAutoProgressingEnabled);
if (!autoProgressElement?.required) {
return false;
}
if (isSingleSelectOtherSelected({ autoProgressElement, mergedValue })) {
return false;
}
return true;
return Boolean(autoProgressElement?.required);
};
export const shouldTriggerAutoProgress = ({
@@ -95,24 +29,13 @@ export const shouldTriggerAutoProgress = ({
mergedValue,
autoProgressElement,
isAlreadyInFlight,
isCommittedSelection = true,
}: {
changedElementId: string;
mergedValue: TResponseData;
autoProgressElement: TSurveyElement | null;
isAlreadyInFlight: boolean;
isCommittedSelection?: boolean;
}): boolean => {
if (
!autoProgressElement ||
isAlreadyInFlight ||
changedElementId !== autoProgressElement.id ||
!isCommittedSelection
) {
return false;
}
if (isSingleSelectOtherSelected({ autoProgressElement, mergedValue })) {
if (!autoProgressElement || isAlreadyInFlight || changedElementId !== autoProgressElement.id) {
return false;
}

View File

@@ -348,7 +348,7 @@ export const addCustomThemeToDom = ({ styling }: { styling: TProjectStyling | TS
upperDecls += " color: var(--fb-element-upper-label-color) !important;\n";
upperDecls += " opacity: var(--fb-element-upper-label-opacity, 1) !important;\n";
}
addRule("#fbjs .label-upper,\n#fbjs .label-upper *", upperDecls);
addRule("#fbjs .label-card,\n#fbjs .label-card *", upperDecls);
// --- Buttons ---
let buttonDecls = "";