mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-20 19:30:41 -05:00
feat: add searchable dropdown for Select field with long option lists (#7407)
When a Select field is rendered in dropdown format with more than 5 options, a search input appears at the top of the dropdown. Users can type to filter options in real time with case-insensitive matching. - Search input with magnifying glass icon, threshold-gated (>5 options) - useMemo-wrapped filtering for performance - "None" option always visible regardless of search - "Other" option filtered independently - "No results found" empty state with i18n support - Keyboard handling: Escape clears then closes, arrow keys for navigation - Auto-focus on search input using double-defer pattern - role="search" landmark for accessibility - New searchPlaceholder/searchNoResultsText props with translations in 17 locales - 3 new Storybook stories for searchable dropdown scenarios Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -340,6 +340,75 @@ export const DropdownWithOtherOption: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const DropdownWithSearch: Story = {
|
||||
args: {
|
||||
headline: "Select your country",
|
||||
description: "Dropdown with search enabled (6+ options)",
|
||||
options: [
|
||||
{ id: "us", label: "United States" },
|
||||
{ id: "uk", label: "United Kingdom" },
|
||||
{ id: "de", label: "Germany" },
|
||||
{ id: "fr", label: "France" },
|
||||
{ id: "jp", label: "Japan" },
|
||||
{ id: "br", label: "Brazil" },
|
||||
{ id: "in", label: "India" },
|
||||
],
|
||||
variant: "dropdown",
|
||||
placeholder: "Choose a country...",
|
||||
},
|
||||
};
|
||||
|
||||
export const DropdownWithSearchAndOther: Story = {
|
||||
render: () => {
|
||||
const [value, setValue] = React.useState<string | undefined>(undefined);
|
||||
const [otherValue, setOtherValue] = React.useState<string>("");
|
||||
|
||||
return (
|
||||
<div className="w-[600px]">
|
||||
<SingleSelect
|
||||
elementId="search-other"
|
||||
inputId="search-other-input"
|
||||
headline="Select your country"
|
||||
description="Dropdown with search and other option"
|
||||
options={[
|
||||
{ id: "us", label: "United States" },
|
||||
{ id: "uk", label: "United Kingdom" },
|
||||
{ id: "de", label: "Germany" },
|
||||
{ id: "fr", label: "France" },
|
||||
{ id: "jp", label: "Japan" },
|
||||
{ id: "br", label: "Brazil" },
|
||||
]}
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
variant="dropdown"
|
||||
placeholder="Choose a country..."
|
||||
otherOptionId="other"
|
||||
otherOptionLabel="Other"
|
||||
otherOptionPlaceholder="Enter your country"
|
||||
otherValue={otherValue}
|
||||
onOtherValueChange={setOtherValue}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const DropdownAtThreshold: Story = {
|
||||
args: {
|
||||
headline: "Pick a color",
|
||||
description: "Exactly 5 options — search should NOT appear",
|
||||
options: [
|
||||
{ id: "red", label: "Red" },
|
||||
{ id: "green", label: "Green" },
|
||||
{ id: "blue", label: "Blue" },
|
||||
{ id: "yellow", label: "Yellow" },
|
||||
{ id: "purple", label: "Purple" },
|
||||
],
|
||||
variant: "dropdown",
|
||||
placeholder: "Choose a color...",
|
||||
},
|
||||
};
|
||||
|
||||
export const WithContainerStyling: Story = {
|
||||
args: {
|
||||
headline: "Select your preferred option",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { ChevronDown, Search } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { Button } from "@/components/general/button";
|
||||
import {
|
||||
@@ -14,6 +14,9 @@ import { Input } from "@/components/general/input";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/general/radio-group";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/** Number of options above which the search input is shown inside the dropdown */
|
||||
const SEARCH_THRESHOLD = 5;
|
||||
|
||||
/**
|
||||
* Option for single-select element
|
||||
*/
|
||||
@@ -45,7 +48,7 @@ interface SingleSelectProps {
|
||||
requiredLabel?: string;
|
||||
/** Error message to display below the options */
|
||||
errorMessage?: string;
|
||||
/** Text direction: 'ltr' (left-to-right), 'rtl' (right-to-left), or 'auto' (auto-detect from content) */
|
||||
/** Text direction: 'ltr' (left-to-right), 'rtl' (right-to-right), or 'auto' (auto-detect from content) */
|
||||
dir?: "ltr" | "rtl" | "auto";
|
||||
/** Whether the options are disabled */
|
||||
disabled?: boolean;
|
||||
@@ -67,6 +70,10 @@ interface SingleSelectProps {
|
||||
imageUrl?: string;
|
||||
/** Video URL to display above the headline */
|
||||
videoUrl?: string;
|
||||
/** Placeholder text for the search input in dropdown mode */
|
||||
searchPlaceholder?: string;
|
||||
/** Message shown when search yields no results */
|
||||
searchNoResultsText?: string;
|
||||
}
|
||||
|
||||
function SingleSelect({
|
||||
@@ -91,6 +98,8 @@ function SingleSelect({
|
||||
onOtherValueChange,
|
||||
imageUrl,
|
||||
videoUrl,
|
||||
searchPlaceholder = "Search...",
|
||||
searchNoResultsText = "No results found",
|
||||
}: Readonly<SingleSelectProps>): React.JSX.Element {
|
||||
// Ensure value is always a string or undefined
|
||||
const selectedValue = value ?? undefined;
|
||||
@@ -98,6 +107,46 @@ function SingleSelect({
|
||||
const isOtherSelected = hasOtherOption && selectedValue === otherOptionId;
|
||||
const otherInputRef = React.useRef<HTMLInputElement>(null);
|
||||
|
||||
// Search state for the dropdown variant
|
||||
const [searchQuery, setSearchQuery] = React.useState("");
|
||||
const searchInputRef = React.useRef<HTMLInputElement>(null);
|
||||
|
||||
// Total option count including "other" to determine whether to show search
|
||||
const allDropdownOptionCount = options.length + (hasOtherOption ? 1 : 0);
|
||||
const showSearch = variant === "dropdown" && allDropdownOptionCount > SEARCH_THRESHOLD;
|
||||
|
||||
// Separate "none" option from regular options — "none" is always visible regardless of search
|
||||
const noneOption = React.useMemo(() => options.find((opt) => opt.id === "none"), [options]);
|
||||
const regularOptions = React.useMemo(() => options.filter((opt) => opt.id !== "none"), [options]);
|
||||
|
||||
// Filtered regular options based on the search query (only active when search is shown)
|
||||
const filteredRegularOptions = React.useMemo(() => {
|
||||
if (!showSearch || !searchQuery) return regularOptions;
|
||||
const lowerQuery = searchQuery.toLowerCase();
|
||||
return regularOptions.filter((opt) => opt.label.toLowerCase().includes(lowerQuery));
|
||||
}, [showSearch, searchQuery, regularOptions]);
|
||||
|
||||
// Whether the "other" option matches the search
|
||||
const otherMatchesSearch = React.useMemo(() => {
|
||||
if (!hasOtherOption) return false;
|
||||
if (!showSearch || !searchQuery) return true;
|
||||
return otherOptionLabel.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
}, [showSearch, searchQuery, hasOtherOption, otherOptionLabel]);
|
||||
|
||||
const handleDropdownOpenChange = (open: boolean): void => {
|
||||
if (!open) {
|
||||
setSearchQuery("");
|
||||
} else if (showSearch) {
|
||||
// Focus the search input when dropdown opens, using the same double-defer pattern
|
||||
// as the "other" input focus to win against Radix focus management.
|
||||
globalThis.setTimeout(() => {
|
||||
globalThis.requestAnimationFrame(() => {
|
||||
searchInputRef.current?.focus();
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isOtherSelected || disabled) return;
|
||||
|
||||
@@ -155,7 +204,7 @@ function SingleSelect({
|
||||
{variant === "dropdown" ? (
|
||||
<>
|
||||
<ElementError errorMessage={errorMessage} dir={dir} />
|
||||
<DropdownMenu>
|
||||
<DropdownMenu onOpenChange={handleDropdownOpenChange}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -168,12 +217,41 @@ function SingleSelect({
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="bg-option-bg max-h-[300px] w-[var(--radix-dropdown-menu-trigger-width)] overflow-y-auto"
|
||||
className="bg-option-bg w-[var(--radix-dropdown-menu-trigger-width)]"
|
||||
align="start">
|
||||
<DropdownMenuRadioGroup value={selectedValue} onValueChange={onChange}>
|
||||
{options
|
||||
.filter((option) => option.id !== "none")
|
||||
.map((option) => {
|
||||
{showSearch ? (
|
||||
<div className="border-option-border border-b px-2 py-2" role="search">
|
||||
<div className="relative flex items-center">
|
||||
<Search className="text-input-placeholder pointer-events-none absolute left-2 h-4 w-4 shrink-0" />
|
||||
<input
|
||||
ref={searchInputRef}
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder={searchPlaceholder}
|
||||
dir={dir}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") {
|
||||
if (searchQuery) {
|
||||
e.stopPropagation();
|
||||
setSearchQuery("");
|
||||
}
|
||||
} else if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
||||
// Let arrow keys propagate so Radix can move focus to options
|
||||
} else {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}}
|
||||
className="bg-input-bg text-input-text placeholder:text-input-placeholder font-input font-input-weight w-full rounded-sm py-1 pr-2 pl-7 text-sm outline-none"
|
||||
aria-label={searchPlaceholder}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="max-h-[260px] overflow-y-auto">
|
||||
<DropdownMenuRadioGroup value={selectedValue} onValueChange={onChange}>
|
||||
{filteredRegularOptions.map((option) => {
|
||||
const optionId = `${inputId}-${option.id}`;
|
||||
|
||||
return (
|
||||
@@ -187,34 +265,36 @@ function SingleSelect({
|
||||
</DropdownMenuRadioItem>
|
||||
);
|
||||
})}
|
||||
{hasOtherOption && otherOptionId ? (
|
||||
<DropdownMenuRadioItem
|
||||
value={otherOptionId}
|
||||
id={`${inputId}-${otherOptionId}`}
|
||||
dir={dir}
|
||||
disabled={disabled}>
|
||||
<span className="font-input font-input-weight text-input-text">
|
||||
{otherValue || otherOptionLabel}
|
||||
</span>
|
||||
</DropdownMenuRadioItem>
|
||||
) : null}
|
||||
{options
|
||||
.filter((option) => option.id === "none")
|
||||
.map((option) => {
|
||||
const optionId = `${inputId}-${option.id}`;
|
||||
|
||||
return (
|
||||
<DropdownMenuRadioItem
|
||||
key={option.id}
|
||||
value={option.id}
|
||||
id={optionId}
|
||||
dir={dir}
|
||||
disabled={disabled}>
|
||||
<span className="font-input font-input-weight text-input-text">{option.label}</span>
|
||||
</DropdownMenuRadioItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuRadioGroup>
|
||||
{otherMatchesSearch && otherOptionId ? (
|
||||
<DropdownMenuRadioItem
|
||||
value={otherOptionId}
|
||||
id={`${inputId}-${otherOptionId}`}
|
||||
dir={dir}
|
||||
disabled={disabled}>
|
||||
<span className="font-input font-input-weight text-input-text">
|
||||
{otherValue || otherOptionLabel}
|
||||
</span>
|
||||
</DropdownMenuRadioItem>
|
||||
) : null}
|
||||
{noneOption ? (
|
||||
<DropdownMenuRadioItem
|
||||
key={noneOption.id}
|
||||
value={noneOption.id}
|
||||
id={`${inputId}-${noneOption.id}`}
|
||||
dir={dir}
|
||||
disabled={disabled}>
|
||||
<span className="font-input font-input-weight text-input-text">
|
||||
{noneOption.label}
|
||||
</span>
|
||||
</DropdownMenuRadioItem>
|
||||
) : null}
|
||||
{showSearch && filteredRegularOptions.length === 0 && !otherMatchesSearch ? (
|
||||
<div className="text-input-placeholder px-2 py-4 text-center text-sm">
|
||||
{searchNoResultsText}
|
||||
</div>
|
||||
) : null}
|
||||
</DropdownMenuRadioGroup>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
{isOtherSelected ? (
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"finish": "إنهاء",
|
||||
"language_switch": "تبديل اللغة",
|
||||
"next": "التالي",
|
||||
"no_results_found": "لم يتم العثور على نتائج",
|
||||
"open_in_new_tab": "فتح في علامة تبويب جديدة",
|
||||
"people_responded": "{count, plural, one {شخص واحد استجاب} two {شخصان استجابا} few {{count} أشخاص استجابوا} many {{count} شخصًا استجابوا} other {{count} شخص استجابوا}}",
|
||||
"please_retry_now_or_try_again_later": "يرجى إعادة المحاولة الآن أو المحاولة مرة أخرى لاحقًا.",
|
||||
@@ -21,6 +22,7 @@
|
||||
"respondents_will_not_see_this_card": "لن يرى المستجيبون هذه البطاقة",
|
||||
"retry": "إعادة المحاولة",
|
||||
"retrying": "إعادة المحاولة...",
|
||||
"search": "بحث...",
|
||||
"select_option": "اختر خيارًا",
|
||||
"select_options": "اختر الخيارات",
|
||||
"sending_responses": "جارٍ إرسال الردود...",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"finish": "Afslut",
|
||||
"language_switch": "Sprogskift",
|
||||
"next": "Næste",
|
||||
"no_results_found": "Ingen resultater fundet",
|
||||
"open_in_new_tab": "Åbn i ny fane",
|
||||
"people_responded": "{count, plural, one {1 person har svaret} other {{count} personer har svaret}}",
|
||||
"please_retry_now_or_try_again_later": "Prøv igen nu eller prøv senere.",
|
||||
@@ -21,6 +22,7 @@
|
||||
"respondents_will_not_see_this_card": "Respondenter vil ikke se dette kort",
|
||||
"retry": "Prøv igen",
|
||||
"retrying": "Prøver igen…",
|
||||
"search": "Søg...",
|
||||
"select_option": "Vælg en mulighed",
|
||||
"select_options": "Vælg muligheder",
|
||||
"sending_responses": "Sender svar…",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"finish": "Fertig",
|
||||
"language_switch": "Sprachwechsel",
|
||||
"next": "Weiter",
|
||||
"no_results_found": "Keine Ergebnisse gefunden",
|
||||
"open_in_new_tab": "In neuem Tab öffnen",
|
||||
"people_responded": "{count, plural, one {1 Person hat geantwortet} other {{count} Personen haben geantwortet}}",
|
||||
"please_retry_now_or_try_again_later": "Bitte versuchen Sie es jetzt erneut oder später noch einmal.",
|
||||
@@ -21,6 +22,7 @@
|
||||
"respondents_will_not_see_this_card": "Befragte werden diese Karte nicht sehen",
|
||||
"retry": "Wiederholen",
|
||||
"retrying": "Erneuter Versuch...",
|
||||
"search": "Suchen...",
|
||||
"select_option": "Wähle eine Option",
|
||||
"select_options": "Wähle Optionen",
|
||||
"sending_responses": "Antworten werden gesendet...",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"finish": "Finish",
|
||||
"language_switch": "Language switch",
|
||||
"next": "Next",
|
||||
"no_results_found": "No results found",
|
||||
"open_in_new_tab": "Open in new tab",
|
||||
"people_responded": "{count, plural, one {1 person responded} other {{count} people responded}}",
|
||||
"please_retry_now_or_try_again_later": "Please retry now or try again later.",
|
||||
@@ -21,6 +22,7 @@
|
||||
"respondents_will_not_see_this_card": "Respondents will not see this card",
|
||||
"retry": "Retry",
|
||||
"retrying": "Retrying…",
|
||||
"search": "Search...",
|
||||
"select_option": "Select an option",
|
||||
"select_options": "Select options",
|
||||
"sending_responses": "Sending responses…",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"finish": "Finalizar",
|
||||
"language_switch": "Cambio de idioma",
|
||||
"next": "Siguiente",
|
||||
"no_results_found": "No se encontraron resultados",
|
||||
"open_in_new_tab": "Abrir en nueva pestaña",
|
||||
"people_responded": "{count, plural, one {1 persona respondió} other {{count} personas respondieron}}",
|
||||
"please_retry_now_or_try_again_later": "Por favor, inténtalo ahora o prueba más tarde.",
|
||||
@@ -21,6 +22,7 @@
|
||||
"respondents_will_not_see_this_card": "Los encuestados no verán esta tarjeta",
|
||||
"retry": "Reintentar",
|
||||
"retrying": "Reintentando...",
|
||||
"search": "Buscar...",
|
||||
"select_option": "Selecciona una opción",
|
||||
"select_options": "Selecciona opciones",
|
||||
"sending_responses": "Enviando respuestas...",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"finish": "Terminer",
|
||||
"language_switch": "Changement de langue",
|
||||
"next": "Suivant",
|
||||
"no_results_found": "Aucun résultat trouvé",
|
||||
"open_in_new_tab": "Ouvrir dans un nouvel onglet",
|
||||
"people_responded": "{count, plural, one {1 personne a répondu} other {{count} personnes ont répondu}}",
|
||||
"please_retry_now_or_try_again_later": "Veuillez réessayer maintenant ou réessayer plus tard.",
|
||||
@@ -21,6 +22,7 @@
|
||||
"respondents_will_not_see_this_card": "Les répondants ne verront pas cette carte",
|
||||
"retry": "Réessayer",
|
||||
"retrying": "Nouvelle tentative...",
|
||||
"search": "Rechercher...",
|
||||
"select_option": "Sélectionner une option",
|
||||
"select_options": "Sélectionner des options",
|
||||
"sending_responses": "Envoi des réponses...",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"finish": "समाप्त करें",
|
||||
"language_switch": "भाषा बदलें",
|
||||
"next": "अगला",
|
||||
"no_results_found": "कोई परिणाम नहीं मिला",
|
||||
"open_in_new_tab": "नए टैब में खोलें",
|
||||
"people_responded": "{count, plural, one {1 व्यक्ति ने जवाब दिया} other {{count} लोगों ने जवाब दिया}}",
|
||||
"please_retry_now_or_try_again_later": "कृपया अभी पुनः प्रयास करें या बाद में फिर से प्रयास करें।",
|
||||
@@ -21,6 +22,7 @@
|
||||
"respondents_will_not_see_this_card": "उत्तरदाता इस कार्ड को नहीं देखेंगे",
|
||||
"retry": "पुनः प्रयास करें",
|
||||
"retrying": "पुनः प्रयास कर रहे हैं...",
|
||||
"search": "खोजें...",
|
||||
"select_option": "एक विकल्प चुनें",
|
||||
"select_options": "विकल्प चुनें",
|
||||
"sending_responses": "प्रतिक्रियाएँ भेज रहे हैं...",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"finish": "Befejezés",
|
||||
"language_switch": "Nyelvválasztó",
|
||||
"next": "Következő",
|
||||
"no_results_found": "Nincs találat",
|
||||
"open_in_new_tab": "Megnyitás új lapon",
|
||||
"people_responded": "{count, plural, one {1 személy válaszolt} other {{count} személy válaszolt}}",
|
||||
"please_retry_now_or_try_again_later": "Próbálkozzon újra most, vagy próbálja meg később újra.",
|
||||
@@ -21,6 +22,7 @@
|
||||
"respondents_will_not_see_this_card": "A válaszadók nem fogják látni ezt a kártyát",
|
||||
"retry": "Újrapróbálkozás",
|
||||
"retrying": "Újrapróbálkozás…",
|
||||
"search": "Keresés...",
|
||||
"select_option": "Lehetőség kiválasztása",
|
||||
"select_options": "Lehetőségek kiválasztása",
|
||||
"sending_responses": "Válaszok küldése…",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"finish": "Fine",
|
||||
"language_switch": "Cambio lingua",
|
||||
"next": "Avanti",
|
||||
"no_results_found": "Nessun risultato trovato",
|
||||
"open_in_new_tab": "Apri in una nuova scheda",
|
||||
"people_responded": "{count, plural, one {1 persona ha risposto} other {{count} persone hanno risposto}}",
|
||||
"please_retry_now_or_try_again_later": "Riprova ora o più tardi.",
|
||||
@@ -21,6 +22,7 @@
|
||||
"respondents_will_not_see_this_card": "I rispondenti non vedranno questa scheda",
|
||||
"retry": "Riprova",
|
||||
"retrying": "Riprovando...",
|
||||
"search": "Cerca...",
|
||||
"select_option": "Seleziona un'opzione",
|
||||
"select_options": "Seleziona opzioni",
|
||||
"sending_responses": "Invio risposte in corso...",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"finish": "完了",
|
||||
"language_switch": "言語切替",
|
||||
"next": "次へ",
|
||||
"no_results_found": "結果が見つかりません",
|
||||
"open_in_new_tab": "新しいタブで開く",
|
||||
"people_responded": "{count, plural, other {{count}人が回答しました}}",
|
||||
"please_retry_now_or_try_again_later": "今すぐ再試行するか、後でもう一度お試しください。",
|
||||
@@ -21,6 +22,7 @@
|
||||
"respondents_will_not_see_this_card": "回答者はこのカードを見ることができません",
|
||||
"retry": "再試行",
|
||||
"retrying": "再試行中...",
|
||||
"search": "検索...",
|
||||
"select_option": "オプションを選択",
|
||||
"select_options": "オプションを選択",
|
||||
"sending_responses": "回答を送信中...",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"finish": "Voltooien",
|
||||
"language_switch": "Taalschakelaar",
|
||||
"next": "Volgende",
|
||||
"no_results_found": "Geen resultaten gevonden",
|
||||
"open_in_new_tab": "Openen in nieuw tabblad",
|
||||
"people_responded": "{count, plural, one {1 persoon heeft gereageerd} other {{count} mensen hebben gereageerd}}",
|
||||
"please_retry_now_or_try_again_later": "Probeer het nu opnieuw of probeer het later opnieuw.",
|
||||
@@ -21,6 +22,7 @@
|
||||
"respondents_will_not_see_this_card": "Respondenten zien deze kaart niet",
|
||||
"retry": "Opnieuw proberen",
|
||||
"retrying": "Opnieuw proberen...",
|
||||
"search": "Zoeken...",
|
||||
"select_option": "Selecteer een optie",
|
||||
"select_options": "Selecteer opties",
|
||||
"sending_responses": "Reacties verzenden...",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"finish": "Finalizar",
|
||||
"language_switch": "Alternar idioma",
|
||||
"next": "Próximo",
|
||||
"no_results_found": "Nenhum resultado encontrado",
|
||||
"open_in_new_tab": "Abrir em nova aba",
|
||||
"people_responded": "{count, plural, one {1 pessoa respondeu} other {{count} pessoas responderam}}",
|
||||
"please_retry_now_or_try_again_later": "Por favor, tente novamente agora ou mais tarde.",
|
||||
@@ -21,6 +22,7 @@
|
||||
"respondents_will_not_see_this_card": "Os respondentes não verão este cartão",
|
||||
"retry": "Tentar novamente",
|
||||
"retrying": "Tentando novamente...",
|
||||
"search": "Pesquisar...",
|
||||
"select_option": "Selecione uma opção",
|
||||
"select_options": "Selecione opções",
|
||||
"sending_responses": "Enviando respostas...",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"finish": "Finalizează",
|
||||
"language_switch": "Schimbare limbă",
|
||||
"next": "Următorul",
|
||||
"no_results_found": "Nu s-au găsit rezultate",
|
||||
"open_in_new_tab": "Deschide într-o filă nouă",
|
||||
"people_responded": "{count, plural, one {1 persoană a răspuns} other {{count} persoane au răspuns}}",
|
||||
"please_retry_now_or_try_again_later": "Te rugăm să încerci din nou acum sau mai târziu.",
|
||||
@@ -21,6 +22,7 @@
|
||||
"respondents_will_not_see_this_card": "Respondenții nu vor vedea acest card",
|
||||
"retry": "Reîncearcă",
|
||||
"retrying": "Se reîncearcă...",
|
||||
"search": "Căutare...",
|
||||
"select_option": "Selectează o opțiune",
|
||||
"select_options": "Selectează opțiuni",
|
||||
"sending_responses": "Trimiterea răspunsurilor...",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"finish": "Завершить",
|
||||
"language_switch": "Переключение языка",
|
||||
"next": "Далее",
|
||||
"no_results_found": "Результатов не найдено",
|
||||
"open_in_new_tab": "Открыть в новой вкладке",
|
||||
"people_responded": "{count, plural, one {1 человек ответил} other {{count} человека ответили}}",
|
||||
"please_retry_now_or_try_again_later": "Пожалуйста, повторите попытку сейчас или попробуйте позже.",
|
||||
@@ -21,6 +22,7 @@
|
||||
"respondents_will_not_see_this_card": "Респонденты не увидят эту карточку",
|
||||
"retry": "Повторить",
|
||||
"retrying": "Повторная попытка...",
|
||||
"search": "Поиск...",
|
||||
"select_option": "Выбери вариант",
|
||||
"select_options": "Выбери варианты",
|
||||
"sending_responses": "Отправка ответов...",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"finish": "Slutför",
|
||||
"language_switch": "Språkväxlare",
|
||||
"next": "Nästa",
|
||||
"no_results_found": "Inga resultat hittades",
|
||||
"open_in_new_tab": "Öppna i ny flik",
|
||||
"people_responded": "{count, plural, one {1 person har svarat} other {{count} personer har svarat}}",
|
||||
"please_retry_now_or_try_again_later": "Försök igen nu eller försök igen senare.",
|
||||
@@ -21,6 +22,7 @@
|
||||
"respondents_will_not_see_this_card": "Respondenter kommer inte att se detta kort",
|
||||
"retry": "Försök igen",
|
||||
"retrying": "Försöker igen...",
|
||||
"search": "Sök...",
|
||||
"select_option": "Välj ett alternativ",
|
||||
"select_options": "Välj alternativ",
|
||||
"sending_responses": "Skickar svar...",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"finish": "Tugatish",
|
||||
"language_switch": "Tilni almashtirish",
|
||||
"next": "Keyingisi",
|
||||
"no_results_found": "Natijalar topilmadi",
|
||||
"open_in_new_tab": "Yangi oynada ochish",
|
||||
"people_responded": "{count, plural, one {1 kishi javob berdi} other {{count} kishi javob berdi}}",
|
||||
"please_retry_now_or_try_again_later": "Iltimos, hozir qayta urinib ko‘ring yoki keyinroq urinib ko‘ring.",
|
||||
@@ -21,6 +22,7 @@
|
||||
"respondents_will_not_see_this_card": "Javob beruvchilar ushbu kartani ko'rmaydi",
|
||||
"retry": "Qayta urinib ko'ring",
|
||||
"retrying": "Qayta urinilmoqda...",
|
||||
"search": "Qidirish...",
|
||||
"select_option": "Variantni tanla",
|
||||
"select_options": "Variantlarni tanla",
|
||||
"sending_responses": "Javoblar yuborilmoqda...",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"finish": "完成",
|
||||
"language_switch": "语言切换",
|
||||
"next": "下一步",
|
||||
"no_results_found": "未找到结果",
|
||||
"open_in_new_tab": "在新标签页中打开",
|
||||
"people_responded": "{count, plural, one {1 人已回应} other {{count} 人已回应}}",
|
||||
"please_retry_now_or_try_again_later": "请立即重试或稍后再试。",
|
||||
@@ -21,6 +22,7 @@
|
||||
"respondents_will_not_see_this_card": "受访者将不会看到此卡片",
|
||||
"retry": "重试",
|
||||
"retrying": "重试中...",
|
||||
"search": "搜索...",
|
||||
"select_option": "请选择一个选项",
|
||||
"select_options": "请选择多个选项",
|
||||
"sending_responses": "正在发送响应...",
|
||||
|
||||
@@ -187,6 +187,8 @@ export function MultipleChoiceSingleElement({
|
||||
onOtherValueChange={handleOtherValueChange}
|
||||
imageUrl={element.imageUrl}
|
||||
videoUrl={element.videoUrl}
|
||||
searchPlaceholder={t("common.search")}
|
||||
searchNoResultsText={t("common.no_results_found")}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user