Compare commits

...

4 Commits

Author SHA1 Message Date
Javi Aguilar cb82ca7ff4 fix preact not supporting important in styles 2026-05-21 17:29:16 +02:00
Javi Aguilar 6ef5b48590 declare cva config outside 2026-05-21 17:09:13 +02:00
Javi Aguilar fb4bf7ca6d fix: back button not readable in dark backgrounds - fixes #7922 2026-05-21 16:28:31 +02:00
Javi Aguilar 11b4d34b79 refactor: allow arrays and falsy values in cn util 2026-05-21 16:27:51 +02:00
5 changed files with 68 additions and 33 deletions
@@ -1,5 +1,5 @@
import { useTranslation } from "react-i18next";
import { cn } from "@/lib/utils";
import { Button } from "./button";
interface BackButtonProps {
onClick: () => void;
@@ -7,18 +7,11 @@ interface BackButtonProps {
tabIndex?: number;
}
export function BackButton({ onClick, backButtonLabel, tabIndex = 2 }: BackButtonProps) {
export function BackButton({ onClick, backButtonLabel, tabIndex = 2 }: Readonly<BackButtonProps>) {
const { t } = useTranslation();
return (
<button
dir="auto"
tabIndex={tabIndex}
type="button"
className={cn(
"hover:bg-input-bg text-heading focus:ring-focus rounded-custom mb-1 flex items-center px-3 py-3 text-base leading-4 font-medium focus:ring-2 focus:ring-offset-2 focus:outline-hidden"
)}
onClick={onClick}>
<Button dir="auto" type="button" variant="secondary" tabIndex={tabIndex} onClick={onClick}>
{backButtonLabel || t("common.back")}
</button>
</Button>
);
}
@@ -0,0 +1,41 @@
import { ButtonHTMLAttributes } from "preact";
import { cn } from "@/lib/utils";
export type ButtonProps = {
variant: "primary" | "secondary";
} & ButtonHTMLAttributes<HTMLButtonElement>;
export function Button({ variant, className, children, ...rest }: Readonly<ButtonProps>) {
return (
<button
dir="auto"
type="button"
className={cn([
"border-border mb-1 flex items-center justify-center border leading-4",
"focus:ring-focus shadow-xs focus:ring-2 focus:ring-offset-2 focus:outline-hidden",
"enabled:hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60",
[
// secondary uses primary's bg color as text color and vice-versa
'data-[variant="secondary"]:bg-(--fb-button-text-color)!',
'data-[variant="secondary"]:text-(--fb-button-bg-color)!',
],
String(className || ""),
])}
data-variant={variant}
style={{
backgroundColor: "var(--fb-button-bg-color)",
color: "var(--fb-button-text-color)",
borderRadius: "var(--fb-button-border-radius)",
height: "var(--fb-button-height)",
fontSize: "var(--fb-button-font-size)",
fontWeight: "var(--fb-button-font-weight)",
paddingLeft: "var(--fb-button-padding-x)",
paddingRight: "var(--fb-button-padding-x)",
paddingTop: "var(--fb-button-padding-y)",
paddingBottom: "var(--fb-button-padding-y)",
}}
{...rest}>
{children}
</button>
);
}
@@ -1,7 +1,8 @@
import { ButtonHTMLAttributes, useRef } from "preact/compat";
import { ButtonHTMLAttributes } from "preact";
import { useRef } from "preact/compat";
import { useCallback, useEffect, useState } from "preact/hooks";
import { useTranslation } from "react-i18next";
import { cn } from "@/lib/utils";
import { Button } from "./button";
interface SubmitButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
buttonLabel?: string;
@@ -66,32 +67,18 @@ export function SubmitButton({
}, [focus]);
return (
<button
<Button
{...props}
dir="auto"
variant="primary"
className="button-custom border-submit-button-border"
ref={buttonRef}
type={type}
tabIndex={tabIndex}
autoFocus={focus}
className={cn(
"border-submit-button-border focus:ring-focus mb-1 flex items-center justify-center border leading-4 shadow-xs focus:ring-2 focus:ring-offset-2 focus:outline-hidden enabled:hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60",
"button-custom"
)}
style={{
borderRadius: "var(--fb-button-border-radius)",
backgroundColor: "var(--fb-button-bg-color)",
color: "var(--fb-button-text-color)",
height: "var(--fb-button-height)",
fontSize: "var(--fb-button-font-size)",
fontWeight: "var(--fb-button-font-weight)",
paddingLeft: "var(--fb-button-padding-x)",
paddingRight: "var(--fb-button-padding-x)",
paddingTop: "var(--fb-button-padding-y)",
paddingBottom: "var(--fb-button-padding-y)",
}}
onClick={onClick}
disabled={disabled}>
{buttonLabel || (isLastQuestion ? t("common.finish") : t("common.next"))}
</button>
</Button>
);
}
+8
View File
@@ -552,4 +552,12 @@ describe("cn", () => {
test("handles all undefined", () => {
expect(cn(undefined, undefined)).toBe("");
});
test("handles nested arrays of classes", () => {
expect(cn(["foo", ["foo", ["foo", "bar"]]])).toBe("foo foo foo bar");
});
test("handles nulls, booleans and undefined values", () => {
expect(cn(null, true, false, undefined, [null, true, false, undefined])).toBe("");
});
});
+8 -2
View File
@@ -12,8 +12,14 @@ import { type TSurveyElement, type TSurveyElementChoice } from "@formbricks/type
import { type TShuffleOption } from "@formbricks/types/surveys/types";
import { ApiResponse, ApiSuccessResponse } from "@/types/api";
export const cn = (...classes: (string | undefined)[]) => {
return twMerge(classes.filter(Boolean).join(" "));
type ClassValue = string | boolean | null | undefined | ClassValue[];
export const cn = (...classes: ClassValue[]): string => {
return twMerge(
classes
.map((c) => (Array.isArray(c) ? cn(...c) : c))
.filter((c): c is string => typeof c === "string" && c.length > 0)
.join(" ")
);
};
export const getSecureRandom = (): number => {