- WebsiteTab Content for {props.surveyUrl} in {props.environmentId}
+vi.mock("./WebsiteEmbedTab", () => ({
+ WebsiteEmbedTab: (props: { surveyUrl: string }) => (
+
+ ),
+}));
+vi.mock("./DynamicPopupTab", () => ({
+ DynamicPopupTab: (props: { environmentId: string; surveyId: string }) => (
+
+ DynamicPopupTab Content for {props.surveyId} in {props.environmentId}
+
+ ),
+}));
+vi.mock("./TabContainer", () => ({
+ TabContainer: (props: { children: React.ReactNode; title: string; description: string }) => (
+
),
}));
vi.mock("./personal-links-tab", () => ({
- PersonalLinksTab: (props: { segments: any[]; surveyId: string; environmentId: string }) => (
+ PersonalLinksTab: (props: { surveyId: string; environmentId: string }) => (
PersonalLinksTab Content for {props.surveyId} in {props.environmentId}
@@ -39,7 +53,7 @@ vi.mock("./personal-links-tab", () => ({
}));
vi.mock("@/modules/ui/components/upgrade-prompt", () => ({
- UpgradePrompt: (props: { title: string; description: string; buttons: any[] }) => (
+ UpgradePrompt: (props: { title: string; description: string }) => (
AlertCircle
@@ -132,7 +147,8 @@ vi.mock("@/lib/cn", () => ({
const mockTabs = [
{ id: "email", label: "Email", icon: () =>
},
- { id: "webpage", label: "Web Page", icon: () =>
},
+ { id: "website-embed", label: "Website Embed", icon: () =>
},
+ { id: "dynamic-popup", label: "Dynamic Popup", icon: () =>
},
{ id: "link", label: "Link", icon: () =>
},
{ id: "app", label: "App", icon: () =>
},
];
@@ -268,9 +284,9 @@ describe("ShareView", () => {
test("calls setActiveId when a tab is clicked (desktop)", async () => {
render(
);
- const webpageTabButton = screen.getByLabelText("Web Page");
- await userEvent.click(webpageTabButton);
- expect(defaultProps.setActiveId).toHaveBeenCalledWith("webpage");
+ const websiteEmbedTabButton = screen.getByLabelText("Website Embed");
+ await userEvent.click(websiteEmbedTabButton);
+ expect(defaultProps.setActiveId).toHaveBeenCalledWith("website-embed");
});
test("renders EmailTab when activeId is 'email'", () => {
@@ -281,11 +297,21 @@ describe("ShareView", () => {
).toBeInTheDocument();
});
- test("renders WebsiteTab when activeId is 'webpage'", () => {
- render(
);
- expect(screen.getByTestId("website-tab")).toBeInTheDocument();
+ test("renders WebsiteEmbedTab when activeId is 'website-embed'", () => {
+ render(
);
+ expect(screen.getByTestId("tab-container")).toBeInTheDocument();
+ expect(screen.getByTestId("website-embed-tab")).toBeInTheDocument();
+ expect(screen.getByText(`WebsiteEmbedTab Content for ${defaultProps.surveyUrl}`)).toBeInTheDocument();
+ });
+
+ test("renders DynamicPopupTab when activeId is 'dynamic-popup'", () => {
+ render(
);
+ expect(screen.getByTestId("tab-container")).toBeInTheDocument();
+ expect(screen.getByTestId("dynamic-popup-tab")).toBeInTheDocument();
expect(
- screen.getByText(`WebsiteTab Content for ${defaultProps.surveyUrl} in ${defaultProps.environmentId}`)
+ screen.getByText(
+ `DynamicPopupTab Content for ${defaultProps.survey.id} in ${defaultProps.environmentId}`
+ )
).toBeInTheDocument();
});
@@ -316,7 +342,7 @@ describe("ShareView", () => {
render(
);
// Get responsive buttons - these are Button components containing icons
- const responsiveButtons = screen.getAllByTestId("webpage-tab-icon");
+ const responsiveButtons = screen.getAllByTestId("website-embed-tab-icon");
// The responsive button should be the one inside the md:hidden container
const responsiveButton = responsiveButtons
.find((icon) => {
@@ -327,7 +353,7 @@ describe("ShareView", () => {
if (responsiveButton) {
await userEvent.click(responsiveButton);
- expect(defaultProps.setActiveId).toHaveBeenCalledWith("webpage");
+ expect(defaultProps.setActiveId).toHaveBeenCalledWith("website-embed");
}
});
@@ -339,9 +365,9 @@ describe("ShareView", () => {
expect(emailTabButton).toHaveClass("font-medium");
expect(emailTabButton).toHaveClass("text-slate-900");
- const webpageTabButton = screen.getByLabelText("Web Page");
- expect(webpageTabButton).not.toHaveClass("bg-slate-100");
- expect(webpageTabButton).not.toHaveClass("font-medium");
+ const websiteEmbedTabButton = screen.getByLabelText("Website Embed");
+ expect(websiteEmbedTabButton).not.toHaveClass("bg-slate-100");
+ expect(websiteEmbedTabButton).not.toHaveClass("font-medium");
});
test("applies active styles to the active tab (responsive)", () => {
@@ -361,16 +387,18 @@ describe("ShareView", () => {
expect(responsiveEmailButton).toHaveClass("bg-white text-slate-900 shadow-sm hover:bg-white");
}
- const responsiveWebpageButtons = screen.getAllByTestId("webpage-tab-icon");
- const responsiveWebpageButton = responsiveWebpageButtons
+ const responsiveWebsiteEmbedButtons = screen.getAllByTestId("website-embed-tab-icon");
+ const responsiveWebsiteEmbedButton = responsiveWebsiteEmbedButtons
.find((icon) => {
const button = icon.closest("button");
return button && button.getAttribute("data-variant") === "ghost";
})
?.closest("button");
- if (responsiveWebpageButton) {
- expect(responsiveWebpageButton).toHaveClass("border-transparent text-slate-700 hover:text-slate-900");
+ if (responsiveWebsiteEmbedButton) {
+ expect(responsiveWebsiteEmbedButton).toHaveClass(
+ "border-transparent text-slate-700 hover:text-slate-900"
+ );
}
});
});
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/share-view.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/share-view.tsx
index 955e42c08b..8163c0ce23 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/share-view.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/share-view.tsx
@@ -1,5 +1,7 @@
"use client";
+import { DynamicPopupTab } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/DynamicPopupTab";
+import { TabContainer } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/shareEmbedModal/TabContainer";
import { cn } from "@/lib/cn";
import { Button } from "@/modules/ui/components/button";
import {
@@ -15,6 +17,7 @@ import {
} from "@/modules/ui/components/sidebar";
import { TooltipRenderer } from "@/modules/ui/components/tooltip";
import { Small } from "@/modules/ui/components/typography";
+import { useTranslate } from "@tolgee/react";
import { useEffect, useState } from "react";
import { TSegment } from "@formbricks/types/segment";
import { TSurvey } from "@formbricks/types/surveys/types";
@@ -22,7 +25,7 @@ import { TUserLocale } from "@formbricks/types/user";
import { AppTab } from "./AppTab";
import { EmailTab } from "./EmailTab";
import { LinkTab } from "./LinkTab";
-import { WebsiteTab } from "./WebsiteTab";
+import { WebsiteEmbedTab } from "./WebsiteEmbedTab";
import { PersonalLinksTab } from "./personal-links-tab";
interface ShareViewProps {
@@ -57,6 +60,7 @@ export const ShareView = ({
isFormbricksCloud,
}: ShareViewProps) => {
const [isLargeScreen, setIsLargeScreen] = useState(true);
+ const { t } = useTranslate();
useEffect(() => {
const checkScreenSize = () => {
@@ -74,8 +78,22 @@ export const ShareView = ({
switch (activeId) {
case "email":
return
;
- case "webpage":
- return
;
+ case "website-embed":
+ return (
+
+
+
+ );
+ case "dynamic-popup":
+ return (
+
+
+
+ );
case "link":
return (
{
const alertElement = screen.getByRole("alert");
expect(alertElement).toHaveClass("my-custom-class");
});
+
+ test("applies correct styles to anchor tags inside alert variants", () => {
+ render(
+
+ Info Alert with Link
+ This alert has a link
+
+ Test Link
+
+
+ );
+
+ const alertElement = screen.getByRole("alert");
+ expect(alertElement).toHaveClass("text-info-foreground");
+ expect(alertElement).toHaveClass("border-info/50");
+
+ const linkElement = screen.getByRole("link", { name: "Test Link" });
+ expect(linkElement).toBeInTheDocument();
+ });
+
+ test("applies correct styles to anchor tags in AlertButton with asChild", () => {
+ render(
+
+ Error Alert
+ This alert has a button link
+
+ Take Action
+
+
+ );
+
+ const alertElement = screen.getByRole("alert");
+ expect(alertElement).toHaveClass("text-error-foreground");
+ expect(alertElement).toHaveClass("border-error/50");
+
+ const linkElement = screen.getByRole("link", { name: "Take Action" });
+ expect(linkElement).toBeInTheDocument();
+ });
+
+ test("applies styles for all alert variants with anchor tags", () => {
+ const variants = ["error", "warning", "info", "success"] as const;
+
+ variants.forEach((variant) => {
+ const { unmount } = render(
+
+ {variant} Alert
+ Alert with anchor tag
+
+ Link
+
+
+ );
+
+ const alertElement = screen.getByRole("alert");
+ expect(alertElement).toHaveClass(`text-${variant}-foreground`);
+ expect(alertElement).toHaveClass(`border-${variant}/50`);
+
+ const linkElement = screen.getByTestId(`${variant}-link`);
+ expect(linkElement).toBeInTheDocument();
+
+ unmount();
+ });
+ });
});
diff --git a/apps/web/modules/ui/components/alert/index.tsx b/apps/web/modules/ui/components/alert/index.tsx
index 7f84e11968..b40ec6f95d 100644
--- a/apps/web/modules/ui/components/alert/index.tsx
+++ b/apps/web/modules/ui/components/alert/index.tsx
@@ -26,12 +26,12 @@ const alertVariants = cva("relative w-full rounded-lg border [&>svg]:size-4", {
variant: {
default: "text-foreground border-border",
error:
- "text-error-foreground [&>svg]:text-error border-error/50 [&_button]:bg-error-background [&_button]:text-error-foreground [&_button:hover]:bg-error-background-muted",
+ "text-error-foreground [&>svg]:text-error border-error/50 [&_button]:bg-error-background [&_button]:text-error-foreground [&_button:hover]:bg-error-background-muted [&_a]:bg-error-background [&_a]:text-error-foreground [&_a:hover]:bg-error-background-muted",
warning:
- "text-warning-foreground [&>svg]:text-warning border-warning/50 [&_button]:bg-warning-background [&_button]:text-warning-foreground [&_button:hover]:bg-warning-background-muted",
- info: "text-info-foreground [&>svg]:text-info border-info/50 [&_button]:bg-info-background [&_button]:text-info-foreground [&_button:hover]:bg-info-background-muted",
+ "text-warning-foreground [&>svg]:text-warning border-warning/50 [&_button]:bg-warning-background [&_button]:text-warning-foreground [&_button:hover]:bg-warning-background-muted [&_a]:bg-warning-background [&_a]:text-warning-foreground [&_a:hover]:bg-warning-background-muted",
+ info: "text-info-foreground [&>svg]:text-info border-info/50 [&_button]:bg-info-background [&_button]:text-info-foreground [&_button:hover]:bg-info-background-muted [&_a]:bg-info-background [&_a]:text-info-foreground [&_a:hover]:bg-info-background-muted",
success:
- "text-success-foreground [&>svg]:text-success border-success/50 [&_button]:bg-success-background [&_button]:text-success-foreground [&_button:hover]:bg-success-background-muted",
+ "text-success-foreground [&>svg]:text-success border-success/50 [&_button]:bg-success-background [&_button]:text-success-foreground [&_button:hover]:bg-success-background-muted [&_a]:bg-success-background [&_a]:text-success-foreground [&_a:hover]:bg-success-background-muted",
},
size: {
default:
diff --git a/apps/web/modules/ui/components/code-block/index.test.tsx b/apps/web/modules/ui/components/code-block/index.test.tsx
index 13a7cb76e9..3f60eb73ab 100644
--- a/apps/web/modules/ui/components/code-block/index.test.tsx
+++ b/apps/web/modules/ui/components/code-block/index.test.tsx
@@ -118,4 +118,39 @@ describe("CodeBlock", () => {
expect(codeElement).toHaveClass(`language-${language}`);
expect(codeElement).toHaveClass(customCodeClass);
});
+
+ test("applies no margin class when noMargin is true", () => {
+ const codeSnippet = "const test = 'no margin';";
+ const language = "javascript";
+ render(
+
+ {codeSnippet}
+
+ );
+
+ const containerElement = screen.getByText(codeSnippet).closest("div");
+ expect(containerElement).not.toHaveClass("mt-4");
+ });
+
+ test("applies default margin class when noMargin is false", () => {
+ const codeSnippet = "const test = 'with margin';";
+ const language = "javascript";
+ render(
+
+ {codeSnippet}
+
+ );
+
+ const containerElement = screen.getByText(codeSnippet).closest("div");
+ expect(containerElement).toHaveClass("mt-4");
+ });
+
+ test("applies default margin class when noMargin is undefined", () => {
+ const codeSnippet = "const test = 'default margin';";
+ const language = "javascript";
+ render({codeSnippet});
+
+ const containerElement = screen.getByText(codeSnippet).closest("div");
+ expect(containerElement).toHaveClass("mt-4");
+ });
});
diff --git a/apps/web/modules/ui/components/code-block/index.tsx b/apps/web/modules/ui/components/code-block/index.tsx
index be1cefccf7..2be2420510 100644
--- a/apps/web/modules/ui/components/code-block/index.tsx
+++ b/apps/web/modules/ui/components/code-block/index.tsx
@@ -15,6 +15,7 @@ interface CodeBlockProps {
customCodeClass?: string;
customEditorClass?: string;
showCopyToClipboard?: boolean;
+ noMargin?: boolean;
}
export const CodeBlock = ({
@@ -23,6 +24,7 @@ export const CodeBlock = ({
customEditorClass = "",
customCodeClass = "",
showCopyToClipboard = true,
+ noMargin = false,
}: CodeBlockProps) => {
const { t } = useTranslate();
useEffect(() => {
@@ -30,7 +32,7 @@ export const CodeBlock = ({
}, [children]);
return (
-
+
{showCopyToClipboard && (
{
expect(h3Element).toBeInTheDocument();
expect(h3Element).toHaveTextContent("Heading 3");
- expect(h3Element?.className).toContain("text-2xl");
- expect(h3Element?.className).toContain("font-semibold");
+ expect(h3Element?.className).toContain("text-lg");
+ expect(h3Element?.className).toContain("tracking-tight");
expect(h3Element?.className).toContain("text-slate-800");
});
@@ -49,8 +49,8 @@ describe("Typography Components", () => {
expect(h4Element).toBeInTheDocument();
expect(h4Element).toHaveTextContent("Heading 4");
- expect(h4Element?.className).toContain("text-xl");
- expect(h4Element?.className).toContain("font-semibold");
+ expect(h4Element?.className).toContain("text-base");
+ expect(h4Element?.className).toContain("tracking-tight");
expect(h4Element?.className).toContain("text-slate-800");
});
@@ -75,12 +75,11 @@ describe("Typography Components", () => {
test("renders Large correctly", () => {
const { container } = render(Large text);
- const divElement = container.querySelector("div");
+ const pElement = container.querySelector("p");
- expect(divElement).toBeInTheDocument();
- expect(divElement).toHaveTextContent("Large text");
- expect(divElement?.className).toContain("text-lg");
- expect(divElement?.className).toContain("font-semibold");
+ expect(pElement).toBeInTheDocument();
+ expect(pElement).toHaveTextContent("Large text");
+ expect(pElement?.className).toContain("text-lg");
});
test("renders Small correctly", () => {
@@ -90,6 +89,8 @@ describe("Typography Components", () => {
expect(pElement).toBeInTheDocument();
expect(pElement).toHaveTextContent("Small text");
expect(pElement?.className).toContain("text-sm");
+ expect(pElement?.className).toContain("leading-none");
+ expect(pElement?.className).toContain("text-slate-800");
expect(pElement?.className).toContain("font-medium");
});
diff --git a/apps/web/modules/ui/components/typography/index.tsx b/apps/web/modules/ui/components/typography/index.tsx
index e003fa89ac..f2170064d3 100644
--- a/apps/web/modules/ui/components/typography/index.tsx
+++ b/apps/web/modules/ui/components/typography/index.tsx
@@ -1,4 +1,5 @@
import { cn } from "@/modules/ui/lib/utils";
+import { cva } from "class-variance-authority";
import React, { forwardRef } from "react";
const H1 = forwardRef>((props, ref) => {
@@ -40,7 +41,7 @@ const H3 = forwardRef
+ className={cn("scroll-m-20 text-lg tracking-tight text-slate-800", props.className)}>
{props.children}
);
@@ -54,7 +55,7 @@ const H4 = forwardRef
+ className={cn("scroll-m-20 text-base tracking-tight text-slate-800", props.className)}>
{props.children}
);
@@ -87,18 +88,53 @@ export { P };
const Large = forwardRef>((props, ref) => {
return (
-
+
);
});
Large.displayName = "Large";
export { Large };
-const Small = forwardRef>((props, ref) => {
+const Base = forwardRef>((props, ref) => {
return (
-
+
+ {props.children}
+
+ );
+});
+
+Base.displayName = "Base";
+export { Base };
+
+const smallVariants = cva("text-sm leading-none", {
+ variants: {
+ color: {
+ default: "text-slate-800 font-medium",
+ muted: "text-slate-500",
+ },
+ margin: {
+ default: "mt-0",
+ headerDescription: "mt-1",
+ },
+ },
+});
+
+interface SmallProps extends React.HTMLAttributes {
+ color?: "default" | "muted";
+ margin?: "default" | "headerDescription";
+}
+
+const Small = forwardRef((props, ref) => {
+ return (
+
{props.children}
);
diff --git a/docs/images/xm-and-surveys/surveys/link-surveys/embed-surveys/embed-mode-toggle.webp b/docs/images/xm-and-surveys/surveys/link-surveys/embed-surveys/embed-mode-toggle.webp
index 661e76177b..2c43fe71ac 100644
Binary files a/docs/images/xm-and-surveys/surveys/link-surveys/embed-surveys/embed-mode-toggle.webp and b/docs/images/xm-and-surveys/surveys/link-surveys/embed-surveys/embed-mode-toggle.webp differ
diff --git a/docs/xm-and-surveys/surveys/link-surveys/embed-surveys.mdx b/docs/xm-and-surveys/surveys/link-surveys/embed-surveys.mdx
index 86dcdbba07..f731ba305f 100644
--- a/docs/xm-and-surveys/surveys/link-surveys/embed-surveys.mdx
+++ b/docs/xm-and-surveys/surveys/link-surveys/embed-surveys.mdx
@@ -79,7 +79,7 @@ Embed your survey with a minimalist design, disregarding padding and background.
It can be enabled by simply appending **?embed=true** to your survey link or from UI
-1. Open Embed survey tab in survey share modal
+1. Open Website embed tab in survey share modal
2. Toggle **Embed mode**