From b6d793e109101f5575fee45cc05c018ec7a7de9b Mon Sep 17 00:00:00 2001 From: Anshuman Pandey <54475686+pandeymangg@users.noreply.github.com> Date: Wed, 15 Apr 2026 12:20:20 +0530 Subject: [PATCH 001/180] fix: fixes unique constraint error with singleUseId and surveyId (#7737) --- .../responses/lib/response.test.ts | 30 +++++++++-- .../[environmentId]/responses/lib/response.ts | 9 +++- .../client/[environmentId]/responses/route.ts | 6 ++- apps/web/app/lib/api/response.test.ts | 50 +++++++++++++++++++ apps/web/app/lib/api/response.ts | 28 ++++++++++- 5 files changed, 117 insertions(+), 6 deletions(-) diff --git a/apps/web/app/api/v2/client/[environmentId]/responses/lib/response.test.ts b/apps/web/app/api/v2/client/[environmentId]/responses/lib/response.test.ts index ea132774da..bb0180be9d 100644 --- a/apps/web/app/api/v2/client/[environmentId]/responses/lib/response.test.ts +++ b/apps/web/app/api/v2/client/[environmentId]/responses/lib/response.test.ts @@ -2,7 +2,7 @@ import { Prisma } from "@prisma/client"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { prisma } from "@formbricks/database"; import { TContactAttributes } from "@formbricks/types/contact-attribute"; -import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors"; +import { DatabaseError, ResourceNotFoundError, UniqueConstraintError } from "@formbricks/types/errors"; import { TResponseWithQuotaFull, TSurveyQuota } from "@formbricks/types/quota"; import { TResponse } from "@formbricks/types/responses"; import { TTag } from "@formbricks/types/tags"; @@ -175,10 +175,34 @@ describe("createResponse V2", () => { ).rejects.toThrow(ResourceNotFoundError); }); - test("should throw DatabaseError on Prisma known request error", async () => { - const prismaError = new Prisma.PrismaClientKnownRequestError("Test Prisma Error", { + test("should throw UniqueConstraintError on P2002 with singleUseId target", async () => { + const prismaError = new Prisma.PrismaClientKnownRequestError("Unique constraint failed", { code: "P2002", clientVersion: "test", + meta: { target: ["surveyId", "singleUseId"] }, + }); + vi.mocked(mockTx.response.create).mockRejectedValue(prismaError); + await expect( + createResponse(mockResponseInput, mockTx as unknown as Prisma.TransactionClient) + ).rejects.toThrow(UniqueConstraintError); + }); + + test("should throw DatabaseError on P2002 without singleUseId target", async () => { + const prismaError = new Prisma.PrismaClientKnownRequestError("Unique constraint failed", { + code: "P2002", + clientVersion: "test", + meta: { target: ["displayId"] }, + }); + vi.mocked(mockTx.response.create).mockRejectedValue(prismaError); + await expect( + createResponse(mockResponseInput, mockTx as unknown as Prisma.TransactionClient) + ).rejects.toThrow(DatabaseError); + }); + + test("should throw DatabaseError on non-P2002 Prisma known request error", async () => { + const prismaError = new Prisma.PrismaClientKnownRequestError("Test Prisma Error", { + code: "P2025", + clientVersion: "test", }); vi.mocked(mockTx.response.create).mockRejectedValue(prismaError); await expect( diff --git a/apps/web/app/api/v2/client/[environmentId]/responses/lib/response.ts b/apps/web/app/api/v2/client/[environmentId]/responses/lib/response.ts index f6a828c396..71b64d6b11 100644 --- a/apps/web/app/api/v2/client/[environmentId]/responses/lib/response.ts +++ b/apps/web/app/api/v2/client/[environmentId]/responses/lib/response.ts @@ -2,7 +2,7 @@ import "server-only"; import { Prisma } from "@prisma/client"; import { prisma } from "@formbricks/database"; import { TContactAttributes } from "@formbricks/types/contact-attribute"; -import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors"; +import { DatabaseError, ResourceNotFoundError, UniqueConstraintError } from "@formbricks/types/errors"; import { TResponseWithQuotaFull } from "@formbricks/types/quota"; import { TResponse, ZResponseInput } from "@formbricks/types/responses"; import { TTag } from "@formbricks/types/tags"; @@ -129,6 +129,13 @@ export const createResponse = async ( return response; } catch (error) { if (error instanceof Prisma.PrismaClientKnownRequestError) { + if (error.code === "P2002") { + const target = (error.meta?.target as string[]) ?? []; + if (target?.includes("singleUseId")) { + throw new UniqueConstraintError("Response already submitted for this single-use link"); + } + } + throw new DatabaseError(error.message); } diff --git a/apps/web/app/api/v2/client/[environmentId]/responses/route.ts b/apps/web/app/api/v2/client/[environmentId]/responses/route.ts index 8fba1eee86..b5b49d61fc 100644 --- a/apps/web/app/api/v2/client/[environmentId]/responses/route.ts +++ b/apps/web/app/api/v2/client/[environmentId]/responses/route.ts @@ -1,6 +1,6 @@ import { UAParser } from "ua-parser-js"; import { ZEnvironmentId } from "@formbricks/types/environment"; -import { InvalidInputError } from "@formbricks/types/errors"; +import { InvalidInputError, UniqueConstraintError } from "@formbricks/types/errors"; import { TResponseWithQuotaFull } from "@formbricks/types/quota"; import { checkSurveyValidity } from "@/app/api/v2/client/[environmentId]/responses/lib/utils"; import { reportApiError } from "@/app/lib/api/api-error-reporter"; @@ -177,6 +177,10 @@ const createResponseForRequest = async ({ return responses.badRequestResponse(error.message, undefined, true); } + if (error instanceof UniqueConstraintError) { + return responses.conflictResponse(error.message, undefined, true); + } + const response = getUnexpectedPublicErrorResponse(); reportApiError({ request, diff --git a/apps/web/app/lib/api/response.test.ts b/apps/web/app/lib/api/response.test.ts index 48700313d9..dc24bb66c1 100644 --- a/apps/web/app/lib/api/response.test.ts +++ b/apps/web/app/lib/api/response.test.ts @@ -339,6 +339,56 @@ describe("API Response Utilities", () => { }); }); + describe("conflictResponse", () => { + test("should return a conflict response", () => { + const message = "Resource already exists"; + const details = { field: "singleUseId" }; + const response = responses.conflictResponse(message, details); + + expect(response.status).toBe(409); + + return response.json().then((body) => { + expect(body).toEqual({ + code: "conflict", + message, + details, + }); + }); + }); + + test("should handle undefined details", () => { + const message = "Resource already exists"; + const response = responses.conflictResponse(message); + + expect(response.status).toBe(409); + + return response.json().then((body) => { + expect(body).toEqual({ + code: "conflict", + message, + details: {}, + }); + }); + }); + + test("should include CORS headers when cors is true", () => { + const message = "Resource already exists"; + const response = responses.conflictResponse(message, undefined, true); + + expect(response.headers.get("Access-Control-Allow-Origin")).toBe("*"); + expect(response.headers.get("Access-Control-Allow-Methods")).toBe("GET, POST, PUT, DELETE, OPTIONS"); + expect(response.headers.get("Access-Control-Allow-Headers")).toBe("Content-Type, Authorization"); + }); + + test("should use custom cache control header when provided", () => { + const message = "Resource already exists"; + const customCache = "no-cache"; + const response = responses.conflictResponse(message, undefined, false, customCache); + + expect(response.headers.get("Cache-Control")).toBe(customCache); + }); + }); + describe("tooManyRequestsResponse", () => { test("should return a too many requests response", () => { const message = "Rate limit exceeded"; diff --git a/apps/web/app/lib/api/response.ts b/apps/web/app/lib/api/response.ts index 91714161a2..2d7785e8b2 100644 --- a/apps/web/app/lib/api/response.ts +++ b/apps/web/app/lib/api/response.ts @@ -16,7 +16,8 @@ interface ApiErrorResponse { | "method_not_allowed" | "not_authenticated" | "forbidden" - | "too_many_requests"; + | "too_many_requests" + | "conflict"; message: string; details: { [key: string]: string | string[] | number | number[] | boolean | boolean[]; @@ -236,6 +237,30 @@ const internalServerErrorResponse = ( ); }; +const conflictResponse = ( + message: string, + details?: { [key: string]: string }, + cors: boolean = false, + cache: string = "private, no-store" +) => { + const headers = { + ...(cors && corsHeaders), + "Cache-Control": cache, + }; + + return Response.json( + { + code: "conflict", + message, + details: details || {}, + } as ApiErrorResponse, + { + status: 409, + headers, + } + ); +}; + const tooManyRequestsResponse = ( message: string, cors: boolean = false, @@ -270,4 +295,5 @@ export const responses = { successResponse, tooManyRequestsResponse, forbiddenResponse, + conflictResponse, }; From 0653c6a59f66d6ca1ab03b95cf92f0222a824bde Mon Sep 17 00:00:00 2001 From: Marius Date: Wed, 15 Apr 2026 08:58:35 +0200 Subject: [PATCH 002/180] fix: strip @layer properties block to prevent host page CSS pollution (#7685) Co-authored-by: Dhruwang Co-authored-by: Claude Opus 4.6 --- .../users/lib/tests/users.test.ts | 13 +++---- packages/surveys/postcss.config.cjs | 39 +++++++++++++++++-- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/apps/web/modules/api/v2/organizations/[organizationId]/users/lib/tests/users.test.ts b/apps/web/modules/api/v2/organizations/[organizationId]/users/lib/tests/users.test.ts index 75e8a67b74..16d35f6b1e 100644 --- a/apps/web/modules/api/v2/organizations/[organizationId]/users/lib/tests/users.test.ts +++ b/apps/web/modules/api/v2/organizations/[organizationId]/users/lib/tests/users.test.ts @@ -98,14 +98,11 @@ describe("Users Lib", () => { test("returns conflict error if user with email already exists", async () => { (prisma.user.create as any).mockRejectedValueOnce( - new Prisma.PrismaClientKnownRequestError( - "Unique constraint failed on the fields: (`email`)", - { - code: PrismaErrorType.UniqueConstraintViolation, - clientVersion: "1.0.0", - meta: { target: ["email"] }, - } - ) + new Prisma.PrismaClientKnownRequestError("Unique constraint failed on the fields: (`email`)", { + code: PrismaErrorType.UniqueConstraintViolation, + clientVersion: "1.0.0", + meta: { target: ["email"] }, + }) ); const result = await createUser( { name: "Duplicate", email: "test@example.com", role: "member" }, diff --git a/packages/surveys/postcss.config.cjs b/packages/surveys/postcss.config.cjs index 69f51a51a7..2531b1660e 100644 --- a/packages/surveys/postcss.config.cjs +++ b/packages/surveys/postcss.config.cjs @@ -1,5 +1,6 @@ -// basic regex -- [whitespace](number)(rem)[whitespace or ;] -const REM_REGEX = /\b(\d+(\.\d+)?)(rem)\b/gi; +// Matches a CSS numeric value followed by "rem" — e.g. "1rem", "1.5rem", "16rem". +// Single character-class + single quantifier: no nested quantifiers, no backtracking risk. +const REM_REGEX = /([\d.]+)(rem)/gi; // NOSONAR -- single character-class quantifier on trusted CSS input; no backtracking risk const PROCESSED = Symbol("processed"); const remtoEm = (opts = {}) => { @@ -26,6 +27,36 @@ const remtoEm = (opts = {}) => { }; }; -module.exports = { - plugins: [require("@tailwindcss/postcss"), require("autoprefixer"), remtoEm()], +// Strips the `@layer properties { ... }` block that Tailwind v4 emits as a +// browser-compatibility fallback for `@property` declarations. +// +// Problem: CSS `@layer` at-rules are globally scoped by spec — they cannot be +// confined by a surrounding selector. Even though all other Formbricks survey +// styles are correctly scoped to `#fbjs`, the `@layer properties` block +// contains a bare `*, :before, :after, ::backdrop` selector that resets all +// `--tw-*` CSS custom properties on every element of the host page. This +// breaks shadows, rings, transforms, and other Tailwind utilities on any site +// that uses Tailwind v4 alongside the Formbricks SDK. +// +// The `@property` declarations already present in the same stylesheet cover +// the same browser-compatibility need for all supporting browsers, so removing +// `@layer properties` does not affect survey rendering. +// +// See: https://github.com/formbricks/js/issues/46 +const stripLayerProperties = () => { + return { + postcssPlugin: "postcss-strip-layer-properties", + AtRule: { + layer: (atRule) => { + if (atRule.params === "properties") { + atRule.remove(); + } + }, + }, + }; +}; +stripLayerProperties.postcss = true; + +module.exports = { + plugins: [require("@tailwindcss/postcss"), require("autoprefixer"), remtoEm(), stripLayerProperties()], }; From a1a11b2bb8c0e90ce4a4a19fda5f4be15e23d6f3 Mon Sep 17 00:00:00 2001 From: XHamzaX <90039624+HamzaSwitch@users.noreply.github.com> Date: Wed, 15 Apr 2026 10:42:20 +0100 Subject: [PATCH 003/180] fix: prevent OIDC button text overlap with 'last used' indicator (#7731) Co-authored-by: Dhruwang --- apps/web/modules/ee/sso/components/open-id-button.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/modules/ee/sso/components/open-id-button.tsx b/apps/web/modules/ee/sso/components/open-id-button.tsx index b34b13d53b..baf6dc418e 100644 --- a/apps/web/modules/ee/sso/components/open-id-button.tsx +++ b/apps/web/modules/ee/sso/components/open-id-button.tsx @@ -46,9 +46,9 @@ export const OpenIdButton = ({ type="button" onClick={handleLogin} variant="secondary" - className="relative w-full justify-center"> - {text ? text : t("auth.continue_with_openid")} - {lastUsed && {t("auth.last_used")}} + className="w-full items-center justify-center gap-2 px-2"> + {text || t("auth.continue_with_openid")} + {lastUsed && {t("auth.last_used")}} ); }; From 367bc23dd4c3f0fc085accb1f6134465898831f2 Mon Sep 17 00:00:00 2001 From: Dhruwang Jariwala <67850763+Dhruwang@users.noreply.github.com> Date: Thu, 16 Apr 2026 01:29:15 +0530 Subject: [PATCH 004/180] fix: prevent offline replay from dropping survey blocks after completion (#7743) --- packages/surveys/src/lib/response-queue.ts | 66 +++++++++++- .../surveys/src/lib/response.queue.test.ts | 100 ++++++++++++++++-- 2 files changed, 155 insertions(+), 11 deletions(-) diff --git a/packages/surveys/src/lib/response-queue.ts b/packages/surveys/src/lib/response-queue.ts index 90e0d7d730..d21064c3fc 100644 --- a/packages/surveys/src/lib/response-queue.ts +++ b/packages/surveys/src/lib/response-queue.ts @@ -32,16 +32,32 @@ export const delay = (ms: number): Promise => { }); }; +// Module-level locks keyed by surveyId. +// Survive ResponseQueue instance recreation (e.g. React useMemo recomputation) +// so that only one sync/send runs at a time per survey, even across instances. +const syncingBySurvey = new Map(); +const requestInProgressBySurvey = new Map(); + +/** @internal Exposed for tests only. */ +export const _syncLocks = { + clear: () => { + syncingBySurvey.clear(); + requestInProgressBySurvey.clear(); + }, + set: (surveyId: string, value: boolean) => syncingBySurvey.set(surveyId, value), + get: (surveyId: string) => syncingBySurvey.get(surveyId) ?? false, + setRequestInProgress: (surveyId: string, value: boolean) => requestInProgressBySurvey.set(surveyId, value), + getRequestInProgress: (surveyId: string) => requestInProgressBySurvey.get(surveyId) ?? false, +}; + export class ResponseQueue { readonly queue: TResponseUpdate[] = []; readonly config: QueueConfig; private surveyState: SurveyState; - private isRequestInProgress = false; readonly api: ApiClient; private responseRecaptchaToken?: string; // Maps in-memory queue index → IndexedDB id for cleanup after successful send private readonly pendingDbIds: Map = new Map(); - private isSyncing = false; constructor(config: QueueConfig, surveyState: SurveyState) { this.config = config; @@ -52,6 +68,26 @@ export class ResponseQueue { }); } + private get isSyncing(): boolean { + return this.config.surveyId ? (syncingBySurvey.get(this.config.surveyId) ?? false) : false; + } + + private set isSyncing(value: boolean) { + if (this.config.surveyId) { + syncingBySurvey.set(this.config.surveyId, value); + } + } + + private get isRequestInProgress(): boolean { + return this.config.surveyId ? (requestInProgressBySurvey.get(this.config.surveyId) ?? false) : false; + } + + private set isRequestInProgress(value: boolean) { + if (this.config.surveyId) { + requestInProgressBySurvey.set(this.config.surveyId, value); + } + } + setResponseRecaptchaToken(token?: string) { this.responseRecaptchaToken = token; } @@ -111,9 +147,26 @@ export class ResponseQueue { return { success: false }; } - this.isRequestInProgress = true; - const responseUpdate = this.queue[0]; + // When offline support is active and there are multiple pending entries in + // IndexedDB, defer to syncPersistedResponses which drains them in order. + // This prevents processQueue and syncPersistedResponses from racing to + // create the same response concurrently (duplicate POSTs). + if (this.config.persistOffline && this.config.surveyId) { + const pendingCount = await countPendingResponses(this.config.surveyId); + // Re-check after await — another processQueue/sync may have started during the yield + if (this.isSyncing || this.isRequestInProgress || this.queue.length === 0) { + return { success: false }; + } + + if (pendingCount > 1) { + void this.syncPersistedResponses(); + return { success: false }; + } + } + + const responseUpdate = this.queue[0]; + this.isRequestInProgress = true; const result = await this.sendResponseWithRetry(responseUpdate); if (result.success) { @@ -169,6 +222,11 @@ export class ResponseQueue { // Concurrency guard: prevent duplicate syncs from online/offline flicker if (this.isSyncing) return { success: false, syncedCount: 0 }; + + // If processQueue already has a request in flight, don't start syncing — + // let it finish first to avoid both paths creating the same response. + if (this.isRequestInProgress) return { success: false, syncedCount: 0 }; + this.isSyncing = true; try { diff --git a/packages/surveys/src/lib/response.queue.test.ts b/packages/surveys/src/lib/response.queue.test.ts index 0a99441d99..3267925d1c 100644 --- a/packages/surveys/src/lib/response.queue.test.ts +++ b/packages/surveys/src/lib/response.queue.test.ts @@ -3,7 +3,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, test, vi } from "vit import { err, ok } from "@formbricks/types/error-handlers"; import { TResponseUpdate } from "@formbricks/types/responses"; import { TResponseErrorCodesEnum } from "@/types/response-error-codes"; -import { ResponseQueue, delay } from "./response-queue"; +import { ResponseQueue, _syncLocks, delay } from "./response-queue"; import { SurveyState } from "./survey-state"; // Suppress noisy console output from retry logic during tests @@ -86,6 +86,7 @@ describe("ResponseQueue", () => { queue = new ResponseQueue(config, surveyState); apiMock = queue.api; vi.clearAllMocks(); + _syncLocks.clear(); }); test("constructor initializes properties", () => { @@ -309,12 +310,75 @@ describe("ResponseQueue", () => { }); test("processQueue returns false when isSyncing is true", async () => { - queue.queue.push(responseUpdate); - queue["isSyncing"] = true; - const result = await queue.processQueue(); + const offlineQueue = new ResponseQueue( + getConfig({ persistOffline: true, surveyId: "s1" }), + getSurveyState() + ); + offlineQueue.queue.push(responseUpdate); + _syncLocks.set("s1", true); + const result = await offlineQueue.processQueue(); expect(result.success).toBe(false); }); + test("processQueue defers to sync when multiple IDB entries exist", async () => { + const { countPendingResponses } = await import("./offline-storage"); + vi.mocked(countPendingResponses).mockResolvedValue(3); + + const offlineQueue = new ResponseQueue( + getConfig({ persistOffline: true, surveyId: "s1" }), + getSurveyState() + ); + offlineQueue.queue.push({ data: { q1: "answer" }, finished: false }); + + const syncSpy = vi.spyOn(offlineQueue, "syncPersistedResponses").mockResolvedValue({ + success: true, + syncedCount: 3, + }); + + const result = await offlineQueue.processQueue(); + expect(result.success).toBe(false); + expect(syncSpy).toHaveBeenCalled(); + expect(_syncLocks.getRequestInProgress("s1")).toBe(false); + }); + + test("processQueue bails out if syncPersistedResponses starts during countPendingResponses await", async () => { + const { countPendingResponses } = await import("./offline-storage"); + // Simulate syncPersistedResponses starting during the async gap + vi.mocked(countPendingResponses).mockImplementation(async () => { + // While countPendingResponses is resolving, isSyncing becomes true + _syncLocks.set("s1", true); + return 1; + }); + + const offlineQueue = new ResponseQueue( + getConfig({ persistOffline: true, surveyId: "s1" }), + getSurveyState() + ); + offlineQueue.queue.push({ data: { q1: "answer" }, finished: false }); + + const sendSpy = vi.spyOn(offlineQueue as any, "sendResponseWithRetry"); + + const result = await offlineQueue.processQueue(); + expect(result.success).toBe(false); + expect(sendSpy).not.toHaveBeenCalled(); + }); + + test("processQueue sends directly when it is the only IDB entry", async () => { + const { countPendingResponses } = await import("./offline-storage"); + vi.mocked(countPendingResponses).mockResolvedValue(1); + + const offlineQueue = new ResponseQueue( + getConfig({ persistOffline: true, surveyId: "s1" }), + getSurveyState() + ); + offlineQueue.queue.push({ data: { q1: "answer" }, finished: false }); + + vi.spyOn(offlineQueue as any, "sendResponseWithRetry").mockResolvedValue({ success: true }); + + const result = await offlineQueue.processQueue(); + expect(result.success).toBe(true); + }); + test("loadPersistedQueue returns 0 when persistOffline is disabled", async () => { const count = await queue.loadPersistedQueue(); expect(count).toBe(0); @@ -347,11 +411,33 @@ describe("ResponseQueue", () => { getConfig({ persistOffline: true, surveyId: "s1" }), getSurveyState() ); - offlineQueue["isSyncing"] = true; + _syncLocks.set("s1", true); const result = await offlineQueue.syncPersistedResponses(); expect(result).toEqual({ success: false, syncedCount: 0 }); }); + test("syncPersistedResponses returns early when a processQueue request is in flight", async () => { + _syncLocks.setRequestInProgress("s1", true); + const offlineQueue = new ResponseQueue( + getConfig({ persistOffline: true, surveyId: "s1" }), + getSurveyState() + ); + const result = await offlineQueue.syncPersistedResponses(); + expect(result).toEqual({ success: false, syncedCount: 0 }); + }); + + test("syncPersistedResponses on a new instance sees isRequestInProgress from an old instance", async () => { + // Simulate instance A having a request in flight (module-level lock) + _syncLocks.setRequestInProgress("s1", true); + // Instance B is newly created (e.g. React useMemo recomputation) + const instanceB = new ResponseQueue( + getConfig({ persistOffline: true, surveyId: "s1" }), + getSurveyState() + ); + const result = await instanceB.syncPersistedResponses(); + expect(result).toEqual({ success: false, syncedCount: 0 }); + }); + test("syncPersistedResponses sends entries and clears queue on success", async () => { const { getPendingResponses, removePendingResponse } = await import("./offline-storage"); vi.mocked(getPendingResponses).mockResolvedValue([ @@ -382,7 +468,7 @@ describe("ResponseQueue", () => { expect(result).toEqual({ success: true, syncedCount: 1 }); expect(removePendingResponse).toHaveBeenCalledWith(10); expect(offlineQueue.queue.length).toBe(0); - expect(offlineQueue["isSyncing"]).toBe(false); + expect(_syncLocks.get("s1")).toBe(false); }); test("syncPersistedResponses stops on server error", async () => { @@ -415,7 +501,7 @@ describe("ResponseQueue", () => { const result = await offlineQueue.syncPersistedResponses(); expect(result).toEqual({ success: false, syncedCount: 0 }); - expect(offlineQueue["isSyncing"]).toBe(false); + expect(_syncLocks.get("s1")).toBe(false); }); test("syncPersistedResponses retries 404 as createResponse by resetting responseId", async () => { From e6f347aa079b4cf2b897c0f04a5cac37e8d09a64 Mon Sep 17 00:00:00 2001 From: Dhruwang Jariwala <67850763+Dhruwang@users.noreply.github.com> Date: Thu, 16 Apr 2026 11:20:46 +0530 Subject: [PATCH 005/180] fix: remove dark: variant classes from survey-ui to prevent host page style leakage (#7747) --- packages/survey-ui/src/components/general/button.tsx | 9 ++++----- packages/survey-ui/src/components/general/calendar.tsx | 2 +- packages/survey-ui/src/components/general/checkbox.tsx | 2 +- .../survey-ui/src/components/general/dropdown-menu.tsx | 2 +- packages/survey-ui/src/components/general/input.tsx | 2 +- .../survey-ui/src/components/general/radio-group.tsx | 2 +- packages/survey-ui/src/components/general/textarea.tsx | 2 +- 7 files changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/survey-ui/src/components/general/button.tsx b/packages/survey-ui/src/components/general/button.tsx index b513191a6c..da14ed37f4 100644 --- a/packages/survey-ui/src/components/general/button.tsx +++ b/packages/survey-ui/src/components/general/button.tsx @@ -4,17 +4,16 @@ import * as React from "react"; import { cn } from "@/lib/utils"; const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-button text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-button text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: - "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", - outline: - "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20", + outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", custom: "button-custom", }, diff --git a/packages/survey-ui/src/components/general/calendar.tsx b/packages/survey-ui/src/components/general/calendar.tsx index 341f367195..747d0132a5 100644 --- a/packages/survey-ui/src/components/general/calendar.tsx +++ b/packages/survey-ui/src/components/general/calendar.tsx @@ -225,7 +225,7 @@ function CalendarDayButton({ data-range-end={modifiers.range_end} data-range-middle={modifiers.range_middle} className={cn( - "data-[selected-single=true]:bg-brand data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground hover:text-primary-foreground data-[selected-single=true]:hover:bg-brand data-[selected-single=true]:hover:text-primary-foreground data-[range-start=true]:hover:bg-primary data-[range-start=true]:hover:text-primary-foreground data-[range-end=true]:hover:bg-primary data-[range-end=true]:hover:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square h-auto w-full min-w-[--cell-size] flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] hover:bg-[color-mix(in_srgb,var(--fb-survey-brand-color)_70%,transparent)] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70", + "data-[selected-single=true]:bg-brand data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground hover:text-primary-foreground data-[selected-single=true]:hover:bg-brand data-[selected-single=true]:hover:text-primary-foreground data-[range-start=true]:hover:bg-primary data-[range-start=true]:hover:text-primary-foreground data-[range-end=true]:hover:bg-primary data-[range-end=true]:hover:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-[--cell-size] flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] hover:bg-[color-mix(in_srgb,var(--fb-survey-brand-color)_70%,transparent)] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70", defaultClassNames.day, className )} diff --git a/packages/survey-ui/src/components/general/checkbox.tsx b/packages/survey-ui/src/components/general/checkbox.tsx index ed19be7518..af75235f9b 100644 --- a/packages/survey-ui/src/components/general/checkbox.tsx +++ b/packages/survey-ui/src/components/general/checkbox.tsx @@ -11,7 +11,7 @@ function Checkbox({ diff --git a/packages/survey-ui/src/components/general/dropdown-menu.tsx b/packages/survey-ui/src/components/general/dropdown-menu.tsx index d2519df8fa..f9fcedfe30 100644 --- a/packages/survey-ui/src/components/general/dropdown-menu.tsx +++ b/packages/survey-ui/src/components/general/dropdown-menu.tsx @@ -58,7 +58,7 @@ function DropdownMenuItem({ data-inset={inset} data-variant={variant} className={cn( - "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none focus-visible:outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none focus-visible:outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className )} {...props} diff --git a/packages/survey-ui/src/components/general/input.tsx b/packages/survey-ui/src/components/general/input.tsx index 48e2045c7b..73f84771c4 100644 --- a/packages/survey-ui/src/components/general/input.tsx +++ b/packages/survey-ui/src/components/general/input.tsx @@ -41,7 +41,7 @@ const Input = React.forwardRef(function Input( // Focus ring "focus-visible:border-ring focus-visible:ring-ring focus-visible:ring-[3px]", // Error state ring - "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + "aria-invalid:ring-destructive/20 aria-invalid:border-destructive", // Disabled state "disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50", className diff --git a/packages/survey-ui/src/components/general/radio-group.tsx b/packages/survey-ui/src/components/general/radio-group.tsx index 4acf272f8f..2ce53cc3a8 100644 --- a/packages/survey-ui/src/components/general/radio-group.tsx +++ b/packages/survey-ui/src/components/general/radio-group.tsx @@ -31,7 +31,7 @@ function RadioGroupItem({ diff --git a/packages/survey-ui/src/components/general/textarea.tsx b/packages/survey-ui/src/components/general/textarea.tsx index b5323db388..777959d667 100644 --- a/packages/survey-ui/src/components/general/textarea.tsx +++ b/packages/survey-ui/src/components/general/textarea.tsx @@ -13,7 +13,7 @@ function Textarea({ className, dir = "auto", ...props }: TextareaProps): React.J style={{ fontSize: "var(--fb-input-font-size)" }} dir={dir} className={cn( - "w-input bg-input-bg border-input-border rounded-input font-input font-input-weight px-input-x py-input-y shadow-input placeholder:text-input-placeholder placeholder:opacity-input-placeholder focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 text-input text-input-text flex field-sizing-content min-h-16 border transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", + "w-input bg-input-bg border-input-border rounded-input font-input font-input-weight px-input-x py-input-y shadow-input placeholder:text-input-placeholder placeholder:opacity-input-placeholder focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 aria-invalid:border-destructive text-input text-input-text flex field-sizing-content min-h-16 border transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", className )} {...props} From 924132287ec81ff09b2a56152947110308a95cc2 Mon Sep 17 00:00:00 2001 From: Dhruwang Jariwala <67850763+Dhruwang@users.noreply.github.com> Date: Thu, 16 Apr 2026 16:29:59 +0530 Subject: [PATCH 006/180] fix: connect rating/NPS scale labels to label styling settings (#7738) Co-authored-by: Claude Opus 4.6 --- apps/web/i18n.lock | 12 ++++++------ apps/web/locales/de-DE.json | 10 +++++----- apps/web/locales/en-US.json | 12 ++++++------ apps/web/locales/es-ES.json | 12 ++++++------ apps/web/locales/fr-FR.json | 12 ++++++------ apps/web/locales/hu-HU.json | 12 ++++++------ apps/web/locales/ja-JP.json | 10 +++++----- apps/web/locales/nl-NL.json | 12 ++++++------ apps/web/locales/pt-BR.json | 12 ++++++------ apps/web/locales/pt-PT.json | 12 ++++++------ apps/web/locales/ro-RO.json | 12 ++++++------ apps/web/locales/ru-RU.json | 12 ++++++------ apps/web/locales/sv-SE.json | 12 ++++++------ apps/web/locales/zh-Hans-CN.json | 10 +++++----- apps/web/locales/zh-Hant-TW.json | 10 +++++----- apps/web/playwright/survey-styling.spec.ts | 6 +++--- packages/survey-ui/src/components/elements/nps.tsx | 4 ++-- .../survey-ui/src/components/elements/rating.tsx | 4 ++-- .../src/components/general/element-header.tsx | 2 +- packages/survey-ui/src/components/general/label.tsx | 4 +++- packages/survey-ui/src/styles/globals.css | 4 ++-- packages/surveys/src/components/general/headline.tsx | 2 +- packages/surveys/src/lib/styles.ts | 2 +- 23 files changed, 101 insertions(+), 99 deletions(-) diff --git a/apps/web/i18n.lock b/apps/web/i18n.lock index 4a190c7c78..81e78a871d 100644 --- a/apps/web/i18n.lock +++ b/apps/web/i18n.lock @@ -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 diff --git a/apps/web/locales/de-DE.json b/apps/web/locales/de-DE.json index 824afcd54b..f3619843f6 100644 --- a/apps/web/locales/de-DE.json +++ b/apps/web/locales/de-DE.json @@ -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", diff --git a/apps/web/locales/en-US.json b/apps/web/locales/en-US.json index 6aef9071a0..b5b0e5f2fb 100644 --- a/apps/web/locales/en-US.json +++ b/apps/web/locales/en-US.json @@ -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", diff --git a/apps/web/locales/es-ES.json b/apps/web/locales/es-ES.json index a77f9a2e87..0019470ad2 100644 --- a/apps/web/locales/es-ES.json +++ b/apps/web/locales/es-ES.json @@ -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", diff --git a/apps/web/locales/fr-FR.json b/apps/web/locales/fr-FR.json index 358ac53537..9cc73c697d 100644 --- a/apps/web/locales/fr-FR.json +++ b/apps/web/locales/fr-FR.json @@ -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", diff --git a/apps/web/locales/hu-HU.json b/apps/web/locales/hu-HU.json index 2cf5bd21fb..00eee4e80d 100644 --- a/apps/web/locales/hu-HU.json +++ b/apps/web/locales/hu-HU.json @@ -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", diff --git a/apps/web/locales/ja-JP.json b/apps/web/locales/ja-JP.json index 4b5b747a8b..96464c96ce 100644 --- a/apps/web/locales/ja-JP.json +++ b/apps/web/locales/ja-JP.json @@ -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": "見出しと説明", diff --git a/apps/web/locales/nl-NL.json b/apps/web/locales/nl-NL.json index d2f2185f0f..2ff5d6d496 100644 --- a/apps/web/locales/nl-NL.json +++ b/apps/web/locales/nl-NL.json @@ -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", diff --git a/apps/web/locales/pt-BR.json b/apps/web/locales/pt-BR.json index 35adbf61aa..638ea19d0a 100644 --- a/apps/web/locales/pt-BR.json +++ b/apps/web/locales/pt-BR.json @@ -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", diff --git a/apps/web/locales/pt-PT.json b/apps/web/locales/pt-PT.json index 4a28b38961..c5451f72e2 100644 --- a/apps/web/locales/pt-PT.json +++ b/apps/web/locales/pt-PT.json @@ -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", diff --git a/apps/web/locales/ro-RO.json b/apps/web/locales/ro-RO.json index af2858cbc4..a8f6870f9d 100644 --- a/apps/web/locales/ro-RO.json +++ b/apps/web/locales/ro-RO.json @@ -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", diff --git a/apps/web/locales/ru-RU.json b/apps/web/locales/ru-RU.json index 26ecac20e1..78915bb5d7 100644 --- a/apps/web/locales/ru-RU.json +++ b/apps/web/locales/ru-RU.json @@ -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": "Поля ввода", diff --git a/apps/web/locales/sv-SE.json b/apps/web/locales/sv-SE.json index 48dc4765e6..04a187c600 100644 --- a/apps/web/locales/sv-SE.json +++ b/apps/web/locales/sv-SE.json @@ -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", diff --git a/apps/web/locales/zh-Hans-CN.json b/apps/web/locales/zh-Hans-CN.json index 6290973caf..8fadc3149b 100644 --- a/apps/web/locales/zh-Hans-CN.json +++ b/apps/web/locales/zh-Hans-CN.json @@ -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": "标题和描述", diff --git a/apps/web/locales/zh-Hant-TW.json b/apps/web/locales/zh-Hant-TW.json index b80b73b9cc..84a5cc5a9a 100644 --- a/apps/web/locales/zh-Hant-TW.json +++ b/apps/web/locales/zh-Hant-TW.json @@ -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": "標題與說明", diff --git a/apps/web/playwright/survey-styling.spec.ts b/apps/web/playwright/survey-styling.spec.ts index 796d84b104..313c6c569c 100644 --- a/apps/web/playwright/survey-styling.spec.ts +++ b/apps/web/playwright/survey-styling.spec.ts @@ -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); diff --git a/packages/survey-ui/src/components/elements/nps.tsx b/packages/survey-ui/src/components/elements/nps.tsx index 4685f84469..a4e29ecb06 100644 --- a/packages/survey-ui/src/components/elements/nps.tsx +++ b/packages/survey-ui/src/components/elements/nps.tsx @@ -182,12 +182,12 @@ function NPS({ {(lowerLabel ?? upperLabel) ? (
{lowerLabel ? ( -