mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 10:19:51 -06:00
introduced survey-embed and survey-core packages
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -40,6 +40,14 @@ yarn-error.log*
|
||||
.turbo
|
||||
**/*vite.config.*.timestamp-*
|
||||
|
||||
# TypeScript declaration files and source maps (generated build artifacts)
|
||||
**/*.d.ts.map
|
||||
# Ignore all .d.ts files except manually written ones
|
||||
**/*.d.ts
|
||||
!packages/types/next-auth.d.ts
|
||||
!packages/types/surveys.d.ts
|
||||
!packages/types/video.d.ts
|
||||
|
||||
# environment specific
|
||||
.direnv
|
||||
|
||||
|
||||
@@ -13,7 +13,11 @@ function getAbsolutePath(value: string): any {
|
||||
}
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../src/**/*.mdx", "../../web/modules/ui/**/stories.@(js|jsx|mjs|ts|tsx)"],
|
||||
stories: [
|
||||
"../src/**/*.mdx",
|
||||
"../../web/modules/ui/**/stories.@(js|jsx|mjs|ts|tsx)",
|
||||
"../../../packages/survey-core/src/**/stories.@(js|jsx|mjs|ts|tsx)",
|
||||
],
|
||||
addons: [
|
||||
getAbsolutePath("@storybook/addon-onboarding"),
|
||||
getAbsolutePath("@storybook/addon-links"),
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { Preview } from "@storybook/react-vite";
|
||||
import React from "react";
|
||||
import { I18nProvider } from "../../web/lingodotdev/client";
|
||||
import { I18nProvider as SurveyCoreI18nProvider } from "../../../packages/survey-core/src/components/i18n/provider";
|
||||
import { I18nProvider as WebI18nProvider } from "../../web/lingodotdev/client";
|
||||
import "../../web/modules/ui/globals.css";
|
||||
|
||||
// Create a Storybook-specific Lingodot Dev decorator
|
||||
// Create a Storybook-specific Lingodot Dev decorator for web components
|
||||
const withLingodotDev = (Story: any) => {
|
||||
return React.createElement(
|
||||
I18nProvider,
|
||||
WebI18nProvider,
|
||||
{
|
||||
language: "en-US",
|
||||
defaultLanguage: "en-US",
|
||||
@@ -15,6 +16,17 @@ const withLingodotDev = (Story: any) => {
|
||||
);
|
||||
};
|
||||
|
||||
// Create an i18n decorator for survey-core components
|
||||
const withSurveyCoreI18n = (Story: any) => {
|
||||
return React.createElement(
|
||||
SurveyCoreI18nProvider,
|
||||
{
|
||||
language: "en",
|
||||
} as any,
|
||||
React.createElement(Story)
|
||||
);
|
||||
};
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
controls: {
|
||||
@@ -24,7 +36,7 @@ const preview: Preview = {
|
||||
},
|
||||
},
|
||||
},
|
||||
decorators: [withLingodotDev],
|
||||
decorators: [withLingodotDev, withSurveyCoreI18n],
|
||||
};
|
||||
|
||||
export default preview;
|
||||
|
||||
@@ -11,6 +11,7 @@ export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "../web"),
|
||||
"@survey-core": path.resolve(__dirname, "../../packages/survey-core/src"),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -369,7 +369,7 @@ const nextConfig = {
|
||||
destination: "/js/formbricks.umd.cjs",
|
||||
},
|
||||
{
|
||||
source: "/api/packages/surveys",
|
||||
source: "/api/packages/survey-embed",
|
||||
destination: "/js/surveys.umd.cjs",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@formbricks/js-core": "workspace:*",
|
||||
"@formbricks/logger": "workspace:*",
|
||||
"@formbricks/storage": "workspace:*",
|
||||
"@formbricks/surveys": "workspace:*",
|
||||
"@formbricks/survey-embed": "workspace:*",
|
||||
"@formbricks/types": "workspace:*",
|
||||
"@hookform/resolvers": "5.0.1",
|
||||
"@intercom/messenger-js-sdk": "0.0.14",
|
||||
|
||||
@@ -98,13 +98,17 @@ The `packages/` directory contains shared libraries and utilities:
|
||||
|
||||
* Migration management
|
||||
|
||||
### packages/surveys/
|
||||
|
||||
* Survey-specific functionality
|
||||
### packages/survey-core/
|
||||
|
||||
* React-authored survey UI components
|
||||
* Survey rendering logic and UI components
|
||||
* Used in web app, Storybook, and compiled to Preact for embeds
|
||||
|
||||
* Survey state management
|
||||
### packages/survey-embed/
|
||||
|
||||
* Preact-compiled survey embed bundle
|
||||
* Lightweight widget for embedding surveys into customer websites
|
||||
* Compiled from survey-core using preact/compat
|
||||
|
||||
## Module Organization
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ This document shows how you can use Formbricks to manage survey definitions and
|
||||
## Core components
|
||||
|
||||
1. **Formbricks Backend:** Use the Formbricks app or Management API to create surveys (questions, flows, locales, validations).
|
||||
2. **Your UI Survey Package:** Renders your custom UI, collects the data and sends to Formbricks backend using Formbricks API. For inspiration, you can start looking [here](https://github.com/formbricks/formbricks/tree/main/packages/surveys). With an active Enterprise license you can even fork our surveys package, make changes and keep them private to your organization (freed from AGPL obligation to also release your changes under AGPL)
|
||||
2. **Your UI Survey Package:** Renders your custom UI, collects the data and sends to Formbricks backend using Formbricks API. For inspiration, you can start looking [here](https://github.com/formbricks/formbricks/tree/main/packages/survey-core). With an active Enterprise license you can even fork our survey packages, make changes and keep them private to your organization (freed from AGPL obligation to also release your changes under AGPL)
|
||||
3. **Webhook Integration:** Using in-built Webhook integration forward the data to your Analysis tool or Data warehouse.
|
||||
4. **Your Analysis Tool / Data Warehouse:** Receive all the data from Formbricks integration and process it for analysis.
|
||||
|
||||
@@ -136,7 +136,7 @@ Body:
|
||||
|
||||
Your frontend receives the survey JSON and renders it using your own UI components.
|
||||
|
||||
For inspiration, you can start looking [here](https://github.com/formbricks/formbricks/tree/main/packages/surveys). With an active Enterprise license you can even fork our surveys package, make changes and keep them private to your organization (freed from AGPL obligation to also release your changes under AGPL)
|
||||
For inspiration, you can start looking [here](https://github.com/formbricks/formbricks/tree/main/packages/survey-core). With an active Enterprise license you can even fork our survey packages, make changes and keep them private to your organization (freed from AGPL obligation to also release your changes under AGPL)
|
||||
|
||||
* Question rendering based on type (openText, multipleChoiceSingle, rating, etc.)
|
||||
* Skip logic and conditional branching
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"prepare": "husky install",
|
||||
"storybook": "turbo run storybook",
|
||||
"fb-migrate-dev": "pnpm --filter @formbricks/database create-migration && pnpm prisma generate",
|
||||
"i18n:generate": " pnpm --filter @formbricks/surveys i18n:generate",
|
||||
"i18n:generate": " pnpm --filter @formbricks/survey-embed i18n:generate",
|
||||
"generate-translations": "cd apps/web && npx lingo.dev@latest i18n",
|
||||
"scan-translations": "pnpm --filter @formbricks/i18n-utils scan-translations",
|
||||
"i18n": "pnpm generate-translations && pnpm scan-translations",
|
||||
|
||||
11
packages/survey-core/.gitignore
vendored
Normal file
11
packages/survey-core/.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
node_modules
|
||||
dist
|
||||
.turbo
|
||||
coverage
|
||||
*.log
|
||||
.DS_Store
|
||||
|
||||
# TypeScript declaration files (generated build artifacts)
|
||||
**/*.d.ts
|
||||
**/*.d.ts.map
|
||||
|
||||
83
packages/survey-core/README.md
Normal file
83
packages/survey-core/README.md
Normal file
@@ -0,0 +1,83 @@
|
||||
## Overview
|
||||
|
||||
The `@formbricks/survey-core` package provides React-authored survey UI components. These components are written using standard React APIs (hooks, JSX, etc.) for maximum familiarity and ecosystem compatibility.
|
||||
|
||||
## Purpose
|
||||
|
||||
This package serves as the source of truth for survey UI components that are used across:
|
||||
|
||||
- **Storybook** (React) - Component documentation and visual testing
|
||||
- **Next.js web app** (React) - Main application UI
|
||||
- **Embed bundle** (Preact) - Compiled via `@formbricks/survey-embed` using `preact/compat`
|
||||
|
||||
## Architecture
|
||||
|
||||
### React-First Development
|
||||
|
||||
All components are authored using standard React patterns:
|
||||
- React hooks (`useState`, `useEffect`, etc.)
|
||||
- JSX syntax
|
||||
- React Context API
|
||||
- Standard React component patterns
|
||||
|
||||
### Build Strategy
|
||||
|
||||
- **survey-core**: React components, treated as a normal React library
|
||||
- **survey-embed**: Build step aliases `react` → `preact/compat`, producing a small Preact-powered widget
|
||||
- **Web app + Storybook**: Continue using real React with no changes
|
||||
|
||||
## Features
|
||||
|
||||
- **React Developer Experience**: Familiar React patterns for all contributors
|
||||
- **Type Safety**: Full TypeScript support
|
||||
- **Testing**: Comprehensive test coverage with Vitest
|
||||
- **Single Component Codebase**: Same UI code works everywhere
|
||||
|
||||
## File Structure
|
||||
|
||||
```text
|
||||
packages/survey-core/
|
||||
├── src/
|
||||
│ ├── components/ # React survey components
|
||||
│ │ ├── buttons/ # Survey navigation buttons
|
||||
│ │ ├── general/ # Core survey components
|
||||
│ │ ├── i18n/ # i18n provider component
|
||||
│ │ ├── icons/ # Icon components
|
||||
│ │ ├── questions/ # Question type components
|
||||
│ │ └── wrappers/ # Layout wrappers
|
||||
│ ├── lib/ # Utilities and helpers
|
||||
│ ├── styles/ # CSS styles
|
||||
│ └── types/ # TypeScript types
|
||||
└── package.json
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Scripts
|
||||
|
||||
- `pnpm dev` - Start development build with watch mode
|
||||
- `pnpm build` - Build for production
|
||||
- `pnpm test` - Run tests
|
||||
- `pnpm test:coverage` - Run tests with coverage
|
||||
- `pnpm lint` - Lint and fix code
|
||||
|
||||
## Usage
|
||||
|
||||
### In React Applications
|
||||
|
||||
```tsx
|
||||
import { SurveyComponent } from "@formbricks/survey-core";
|
||||
|
||||
function App() {
|
||||
return <SurveyComponent {...props} />;
|
||||
}
|
||||
```
|
||||
|
||||
### In Storybook
|
||||
|
||||
Components from this package are automatically available in Storybook for visual testing and documentation.
|
||||
|
||||
### In Embed Bundle
|
||||
|
||||
The `@formbricks/survey-embed` package imports from this package and compiles it to Preact for lightweight embeds.
|
||||
|
||||
71
packages/survey-core/package.json
Normal file
71
packages/survey-core/package.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"name": "@formbricks/survey-core",
|
||||
"license": "MIT",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Formbricks survey core components - React-authored survey UI components for use in web app, Storybook, and embed bundle.",
|
||||
"homepage": "https://formbricks.com",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/formbricks/formbricks"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"source": "src/index.ts",
|
||||
"main": "dist/index.umd.cjs",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.umd.cjs"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite build --watch --mode dev",
|
||||
"build": "tsc && vite build",
|
||||
"build:dev": "tsc && vite build --mode dev",
|
||||
"go": "vite build --watch --mode dev",
|
||||
"lint": "eslint src --fix --ext .ts,.js,.tsx,.jsx",
|
||||
"preview": "vite preview",
|
||||
"clean": "rimraf .turbo node_modules dist",
|
||||
"test": "vitest run",
|
||||
"test:coverage": "vitest run --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@calcom/embed-snippet": "1.3.3",
|
||||
"@formkit/auto-animate": "0.8.2",
|
||||
"i18next": "25.5.2",
|
||||
"i18next-icu": "2.4.0",
|
||||
"isomorphic-dompurify": "2.24.0",
|
||||
"react": "19.1.0",
|
||||
"react-calendar": "5.1.0",
|
||||
"react-date-picker": "11.0.0",
|
||||
"react-i18next": "15.7.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/config-typescript": "workspace:*",
|
||||
"@formbricks/eslint-config": "workspace:*",
|
||||
"@formbricks/i18n-utils": "workspace:*",
|
||||
"@formbricks/types": "workspace:*",
|
||||
"@testing-library/react": "16.3.0",
|
||||
"@types/react": "19.1.4",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"autoprefixer": "10.4.21",
|
||||
"postcss": "8.5.3",
|
||||
"tailwindcss": "3.4.17",
|
||||
"terser": "5.39.1",
|
||||
"vite": "6.4.1",
|
||||
"vite-plugin-dts": "4.5.3",
|
||||
"vite-tsconfig-paths": "5.1.4",
|
||||
"vitest": "3.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
interface BackButtonProps {
|
||||
onClick: () => void;
|
||||
backButtonLabel?: string;
|
||||
tabIndex?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function BackButton({ onClick, backButtonLabel, tabIndex = 2 }: BackButtonProps) {
|
||||
@@ -15,7 +16,7 @@ export function BackButton({ onClick, backButtonLabel, tabIndex = 2 }: BackButto
|
||||
tabIndex={tabIndex}
|
||||
type="button"
|
||||
className={cn(
|
||||
"fb-mb-1 hover:fb-bg-input-bg fb-text-heading focus:fb-ring-focus fb-rounded-custom fb-flex fb-items-center fb-px-3 fb-py-3 fb-text-base fb-font-medium fb-leading-4 focus:fb-outline-none focus:fb-ring-2 focus:fb-ring-offset-2"
|
||||
"hover:bg-input-bg text-heading focus:ring-focus rounded-custom mb-1 flex items-center px-3 py-3 text-base font-medium leading-4 focus:outline-none focus:ring-2 focus:ring-offset-2"
|
||||
)}
|
||||
onClick={onClick}>
|
||||
{backButtonLabel || t("common.back")}
|
||||
100
packages/survey-core/src/components/common/stories.tsx
Normal file
100
packages/survey-core/src/components/common/stories.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { fn } from "storybook/test";
|
||||
import { BackButton } from "./back-button";
|
||||
|
||||
const meta: Meta<typeof BackButton> = {
|
||||
title: "Survey Core/Common/BackButton",
|
||||
component: BackButton,
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
controls: { sort: "alpha" },
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
"The **BackButton** component is used in surveys to allow users to navigate to the previous question. It supports internationalization and custom labels.",
|
||||
},
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
onClick: {
|
||||
action: "clicked",
|
||||
description: "Click handler function",
|
||||
table: {
|
||||
category: "Behavior",
|
||||
type: { summary: "() => void" },
|
||||
},
|
||||
},
|
||||
backButtonLabel: {
|
||||
control: "text",
|
||||
description:
|
||||
"Custom label for the back button. If not provided, uses the translated 'common.back' key.",
|
||||
table: {
|
||||
category: "Content",
|
||||
type: { summary: "string" },
|
||||
},
|
||||
},
|
||||
tabIndex: {
|
||||
control: "number",
|
||||
description: "Tab index for keyboard navigation",
|
||||
table: {
|
||||
category: "Accessibility",
|
||||
type: { summary: "number" },
|
||||
defaultValue: { summary: "2" },
|
||||
},
|
||||
},
|
||||
className: {
|
||||
control: "text",
|
||||
description: "Additional CSS classes",
|
||||
table: {
|
||||
category: "Appearance",
|
||||
type: { summary: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
args: { onClick: fn() },
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof BackButton>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
onClick: fn(),
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Default back button using the translated 'common.back' text.",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomLabel: Story = {
|
||||
args: {
|
||||
onClick: fn(),
|
||||
backButtonLabel: "Go Back",
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Back button with a custom label instead of the translated text.",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomStyling: Story = {
|
||||
args: {
|
||||
onClick: fn(),
|
||||
className: "bg-blue-500 hover:bg-blue-600 text-white",
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Back button with custom styling applied via className prop.",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
16
packages/survey-core/src/components/i18n/provider.tsx
Normal file
16
packages/survey-core/src/components/i18n/provider.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useEffect } from "react";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import i18n from "../../lib/i18n.config";
|
||||
|
||||
interface I18nProviderProps {
|
||||
language?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const I18nProvider = ({ language = "en", children }: I18nProviderProps) => {
|
||||
useEffect(() => {
|
||||
i18n.changeLanguage(language);
|
||||
}, [language]);
|
||||
|
||||
return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;
|
||||
};
|
||||
16
packages/survey-core/src/index.ts
Normal file
16
packages/survey-core/src/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
// @formbricks/survey-core
|
||||
// React-authored survey UI components
|
||||
//
|
||||
// This package exports React components that can be used in:
|
||||
// - Storybook (React)
|
||||
// - Next.js web app (React)
|
||||
// - Embed bundle (compiled to Preact via @formbricks/survey-embed)
|
||||
|
||||
// Common components
|
||||
export { BackButton } from "./components/common/back-button";
|
||||
|
||||
// i18n provider
|
||||
export { I18nProvider } from "./components/i18n/provider";
|
||||
|
||||
// Utilities
|
||||
export { cn } from "./lib/utils";
|
||||
47
packages/survey-core/src/lib/i18n.config.ts
Normal file
47
packages/survey-core/src/lib/i18n.config.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import i18n from "i18next";
|
||||
import ICU from "i18next-icu";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
// Import translations from survey-embed (shared translations)
|
||||
import arTranslations from "../../../survey-embed/locales/ar.json";
|
||||
import deTranslations from "../../../survey-embed/locales/de.json";
|
||||
import enTranslations from "../../../survey-embed/locales/en.json";
|
||||
import esTranslations from "../../../survey-embed/locales/es.json";
|
||||
import frTranslations from "../../../survey-embed/locales/fr.json";
|
||||
import hiTranslations from "../../../survey-embed/locales/hi.json";
|
||||
import itTranslations from "../../../survey-embed/locales/it.json";
|
||||
import jaTranslations from "../../../survey-embed/locales/ja.json";
|
||||
import nlTranslations from "../../../survey-embed/locales/nl.json";
|
||||
import ptTranslations from "../../../survey-embed/locales/pt.json";
|
||||
import roTranslations from "../../../survey-embed/locales/ro.json";
|
||||
import ruTranslations from "../../../survey-embed/locales/ru.json";
|
||||
import uzTranslations from "../../../survey-embed/locales/uz.json";
|
||||
import zhHansTranslations from "../../../survey-embed/locales/zh-Hans.json";
|
||||
|
||||
i18n
|
||||
.use(ICU)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
fallbackLng: "en",
|
||||
supportedLngs: ["en", "de", "it", "fr", "es", "ar", "pt", "ro", "ja", "ru", "uz", "zh-Hans", "hi", "nl"],
|
||||
|
||||
resources: {
|
||||
en: { translation: enTranslations },
|
||||
de: { translation: deTranslations },
|
||||
it: { translation: itTranslations },
|
||||
fr: { translation: frTranslations },
|
||||
es: { translation: esTranslations },
|
||||
ar: { translation: arTranslations },
|
||||
pt: { translation: ptTranslations },
|
||||
ro: { translation: roTranslations },
|
||||
ja: { translation: jaTranslations },
|
||||
nl: { translation: nlTranslations },
|
||||
ru: { translation: ruTranslations },
|
||||
uz: { translation: uzTranslations },
|
||||
"zh-Hans": { translation: zhHansTranslations },
|
||||
hi: { translation: hiTranslations },
|
||||
},
|
||||
|
||||
interpolation: { escapeValue: false },
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
7
packages/survey-core/src/lib/utils.ts
Normal file
7
packages/survey-core/src/lib/utils.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Utility function to combine class names
|
||||
* Filters out falsy values and joins them with spaces
|
||||
*/
|
||||
export const cn = (...classes: (string | undefined | null | false)[]): string => {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
};
|
||||
@@ -5,12 +5,11 @@
|
||||
"emitDeclarationOnly": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "preact",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"extends": "@formbricks/config-typescript/js-library.json",
|
||||
"extends": "@formbricks/config-typescript/react-library.json",
|
||||
"include": ["src", "../types/surveys.d.ts"]
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
import preact from "@preact/preset-vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { dirname, resolve } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { loadEnv } from "vite";
|
||||
import dts from "vite-plugin-dts";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
import { defineConfig } from "vitest/config";
|
||||
import { copyCompiledAssetsPlugin } from "../vite-plugins/copy-compiled-assets";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
@@ -15,7 +14,7 @@ const config = ({ mode }) => {
|
||||
|
||||
return defineConfig({
|
||||
test: {
|
||||
environment: "node",
|
||||
environment: "jsdom",
|
||||
environmentMatchGlobs: [
|
||||
["**/*.test.tsx", "jsdom"],
|
||||
["**/lib/**/*.test.ts", "jsdom"],
|
||||
@@ -47,18 +46,18 @@ const config = ({ mode }) => {
|
||||
},
|
||||
lib: {
|
||||
entry: resolve(__dirname, "src/index.ts"),
|
||||
name: "formbricksSurveys",
|
||||
name: "formbricksSurveyCore",
|
||||
formats: ["es", "umd"],
|
||||
fileName: "index",
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
preact(),
|
||||
react(),
|
||||
dts({ rollupTypes: true }),
|
||||
tsconfigPaths(),
|
||||
copyCompiledAssetsPlugin({ filename: "surveys", distDir: resolve(__dirname, "dist") }),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
11
packages/survey-core/vitestSetup.ts
Normal file
11
packages/survey-core/vitestSetup.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import * as matchers from "@testing-library/jest-dom/matchers";
|
||||
import { cleanup } from "@testing-library/react";
|
||||
import { afterEach, expect } from "vitest";
|
||||
|
||||
// Extend Vitest's expect with jest-dom matchers
|
||||
expect.extend(matchers);
|
||||
|
||||
// Cleanup after each test
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
## Overview
|
||||
|
||||
The `@formbricks/surveys` package provides a complete survey rendering system built with Preact/React. It features automated translation management through Lingo.dev.
|
||||
The `@formbricks/survey-embed` package provides a complete survey rendering system built with Preact. It features automated translation management through Lingo.dev and is compiled from React-style components in `@formbricks/survey-core`.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -16,7 +16,7 @@ The `@formbricks/surveys` package provides a complete survey rendering system bu
|
||||
### File Structure
|
||||
|
||||
```text
|
||||
packages/surveys/
|
||||
packages/survey-embed/
|
||||
├── locales/ # Translation files
|
||||
│ ├── en.json # Source translations (English)
|
||||
│ ├── de.json # Generated translations (German)
|
||||
@@ -65,7 +65,7 @@ packages/surveys/
|
||||
In the surveys package directory, create a `.env` file:
|
||||
|
||||
```bash
|
||||
# packages/surveys/.env
|
||||
# packages/survey-embed/.env
|
||||
LINGODOTDEV_API_KEY=<YOUR_API_KEY>
|
||||
```
|
||||
|
||||
@@ -84,14 +84,14 @@ This will execute the auto-translate script and update translation files if need
|
||||
|
||||
### Adding New Translation Keys
|
||||
|
||||
1. **Update Source File**: Add new keys to `packages/surveys/locales/en.json`
|
||||
1. **Update Source File**: Add new keys to `packages/survey-embed/locales/en.json`
|
||||
2. **Generate Translations**: Run `pnpm run i18n:generate`
|
||||
3. **Update Components**: Use the new translation keys in your components with `useTranslation` hook
|
||||
4. **Test**: Verify translations work across all supported languages
|
||||
|
||||
### Updating Existing Translations
|
||||
|
||||
1. **Update Target File**: Update the translation keys in the target language file (`packages/surveys/locales/<target-language>.json`)
|
||||
1. **Update Target File**: Update the translation keys in the target language file (`packages/survey-embed/locales/<target-language>.json`)
|
||||
2. **Test**: Verify translations work across all supported languages
|
||||
3. You don't need to run the `i18n:generate` command as it is only required when the source language is updated.
|
||||
|
||||
@@ -99,7 +99,7 @@ This will execute the auto-translate script and update translation files if need
|
||||
|
||||
#### 1. Update lingo.dev Configuration
|
||||
|
||||
Edit `packages/surveys/i18n.json` to include new target languages:
|
||||
Edit `packages/survey-embed/i18n.json` to include new target languages:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -112,7 +112,7 @@ Edit `packages/surveys/i18n.json` to include new target languages:
|
||||
|
||||
#### 2. Update i18n Configuration
|
||||
|
||||
Modify `packages/surveys/src/lib/i18n.config.ts`:
|
||||
Modify `packages/survey-embed/src/lib/i18n.config.ts`:
|
||||
|
||||
```tsx
|
||||
// Add new import
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "@formbricks/surveys",
|
||||
"name": "@formbricks/survey-embed",
|
||||
"license": "MIT",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Formbricks-surveys is a helper library to embed surveys into your application.",
|
||||
"description": "Formbricks survey embed bundle - Preact-compiled widget for embedding surveys into customer websites.",
|
||||
"homepage": "https://formbricks.com",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
@@ -39,6 +39,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@calcom/embed-snippet": "1.3.3",
|
||||
"@formbricks/survey-core": "workspace:*",
|
||||
"@formkit/auto-animate": "0.8.2",
|
||||
"i18next": "25.5.2",
|
||||
"i18next-icu": "2.4.0",
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useMemo, useRef, useState } from "preact/hooks";
|
||||
import { useCallback } from "react";
|
||||
import { BackButton } from "@formbricks/survey-core";
|
||||
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import type { TSurveyAddressQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
|
||||
import { BackButton } from "@/components/buttons/back-button";
|
||||
import { SubmitButton } from "@/components/buttons/submit-button";
|
||||
import { Headline } from "@/components/general/headline";
|
||||
import { Input } from "@/components/general/input";
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useCallback, useRef, useState } from "preact/hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BackButton } from "@formbricks/survey-core";
|
||||
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import { type TSurveyCalQuestion, type TSurveyQuestionId } from "@formbricks/types/surveys/types";
|
||||
import { BackButton } from "@/components/buttons/back-button";
|
||||
import { SubmitButton } from "@/components/buttons/submit-button";
|
||||
import { CalEmbed } from "@/components/general/cal-embed";
|
||||
import { Headline } from "@/components/general/headline";
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useState } from "preact/hooks";
|
||||
import { BackButton } from "@formbricks/survey-core";
|
||||
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import type { TSurveyConsentQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
|
||||
import { BackButton } from "@/components/buttons/back-button";
|
||||
import { SubmitButton } from "@/components/buttons/submit-button";
|
||||
import { Headline } from "@/components/general/headline";
|
||||
import { QuestionMedia } from "@/components/general/question-media";
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { BackButton } from "@formbricks/survey-core";
|
||||
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import type { TSurveyContactInfoQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
|
||||
import { BackButton } from "@/components/buttons/back-button";
|
||||
import { SubmitButton } from "@/components/buttons/submit-button";
|
||||
import { Headline } from "@/components/general/headline";
|
||||
import { Input } from "@/components/general/input";
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from "preact/hooks";
|
||||
import { BackButton } from "@formbricks/survey-core";
|
||||
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import type { TSurveyCTAQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
|
||||
import { BackButton } from "@/components/buttons/back-button";
|
||||
import { SubmitButton } from "@/components/buttons/submit-button";
|
||||
import { Headline } from "@/components/general/headline";
|
||||
import { QuestionMedia } from "@/components/general/question-media";
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useEffect, useMemo, useState } from "preact/hooks";
|
||||
import DatePicker, { DatePickerProps } from "react-date-picker";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BackButton } from "@formbricks/survey-core";
|
||||
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import type { TSurveyDateQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
|
||||
import { BackButton } from "@/components/buttons/back-button";
|
||||
import { SubmitButton } from "@/components/buttons/submit-button";
|
||||
import { Headline } from "@/components/general/headline";
|
||||
import { QuestionMedia } from "@/components/general/question-media";
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from "preact/hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BackButton } from "@formbricks/survey-core";
|
||||
import { type TJsFileUploadParams } from "@formbricks/types/js";
|
||||
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import { type TUploadFileConfig } from "@formbricks/types/storage";
|
||||
@@ -10,7 +11,6 @@ import { QuestionMedia } from "@/components/general/question-media";
|
||||
import { ScrollableContainer } from "@/components/wrappers/scrollable-container";
|
||||
import { getLocalizedValue } from "@/lib/i18n";
|
||||
import { getUpdatedTtc, useTtc } from "@/lib/ttc";
|
||||
import { BackButton } from "../buttons/back-button";
|
||||
import { FileInput } from "../general/file-input";
|
||||
import { Subheader } from "../general/subheader";
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { type JSX } from "preact";
|
||||
import { useCallback, useMemo, useState } from "preact/hooks";
|
||||
import { BackButton } from "@formbricks/survey-core";
|
||||
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import type {
|
||||
TSurveyMatrixQuestion,
|
||||
TSurveyMatrixQuestionChoice,
|
||||
TSurveyQuestionId,
|
||||
} from "@formbricks/types/surveys/types";
|
||||
import { BackButton } from "@/components/buttons/back-button";
|
||||
import { SubmitButton } from "@/components/buttons/submit-button";
|
||||
import { Headline } from "@/components/general/headline";
|
||||
import { QuestionMedia } from "@/components/general/question-media";
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { BackButton } from "@formbricks/survey-core";
|
||||
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import type { TSurveyMultipleChoiceQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
|
||||
import { BackButton } from "@/components/buttons/back-button";
|
||||
import { SubmitButton } from "@/components/buttons/submit-button";
|
||||
import { Headline } from "@/components/general/headline";
|
||||
import { QuestionMedia } from "@/components/general/question-media";
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { BackButton } from "@formbricks/survey-core";
|
||||
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import type { TSurveyMultipleChoiceQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
|
||||
import { BackButton } from "@/components/buttons/back-button";
|
||||
import { SubmitButton } from "@/components/buttons/submit-button";
|
||||
import { Headline } from "@/components/general/headline";
|
||||
import { QuestionMedia } from "@/components/general/question-media";
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from "preact/hooks";
|
||||
import { BackButton } from "@formbricks/survey-core";
|
||||
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import type { TSurveyNPSQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
|
||||
import { BackButton } from "@/components/buttons/back-button";
|
||||
import { SubmitButton } from "@/components/buttons/submit-button";
|
||||
import { Headline } from "@/components/general/headline";
|
||||
import { QuestionMedia } from "@/components/general/question-media";
|
||||
@@ -1,10 +1,10 @@
|
||||
import { type RefObject } from "preact";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BackButton } from "@formbricks/survey-core";
|
||||
import { ZEmail, ZUrl } from "@formbricks/types/common";
|
||||
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import type { TSurveyOpenTextQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
|
||||
import { BackButton } from "@/components/buttons/back-button";
|
||||
import { SubmitButton } from "@/components/buttons/submit-button";
|
||||
import { Headline } from "@/components/general/headline";
|
||||
import { QuestionMedia } from "@/components/general/question-media";
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BackButton } from "@formbricks/survey-core";
|
||||
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import type { TSurveyPictureSelectionQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
|
||||
import { BackButton } from "@/components/buttons/back-button";
|
||||
import { SubmitButton } from "@/components/buttons/submit-button";
|
||||
import { Headline } from "@/components/general/headline";
|
||||
import { QuestionMedia } from "@/components/general/question-media";
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { useCallback, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BackButton } from "@formbricks/survey-core";
|
||||
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import type {
|
||||
TSurveyQuestionChoice,
|
||||
TSurveyQuestionId,
|
||||
TSurveyRankingQuestion,
|
||||
} from "@formbricks/types/surveys/types";
|
||||
import { BackButton } from "@/components/buttons/back-button";
|
||||
import { SubmitButton } from "@/components/buttons/submit-button";
|
||||
import { Headline } from "@/components/general/headline";
|
||||
import { QuestionMedia } from "@/components/general/question-media";
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import type { JSX } from "react";
|
||||
import { BackButton } from "@formbricks/survey-core";
|
||||
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
|
||||
import type { TSurveyQuestionId, TSurveyRatingQuestion } from "@formbricks/types/surveys/types";
|
||||
import { BackButton } from "@/components/buttons/back-button";
|
||||
import { SubmitButton } from "@/components/buttons/submit-button";
|
||||
import { Headline } from "@/components/general/headline";
|
||||
import { QuestionMedia } from "@/components/general/question-media";
|
||||
33
packages/survey-embed/src/lib/add-fb-prefix.ts
Normal file
33
packages/survey-embed/src/lib/add-fb-prefix.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Adds the 'fb-' prefix to Tailwind CSS classes for survey-embed
|
||||
* Handles pseudo-classes, responsive prefixes, and negative values
|
||||
*/
|
||||
export function addFbPrefix(className: string): string {
|
||||
if (!className) return className;
|
||||
|
||||
return className
|
||||
.split(/\s+/)
|
||||
.map((cls) => {
|
||||
if (!cls || cls.startsWith("fb-")) return cls;
|
||||
|
||||
// Handle pseudo-classes and responsive prefixes (hover:, focus:, sm:, etc.)
|
||||
const pseudoMatch = cls.match(/^([a-z-]+:)(.+)$/);
|
||||
if (pseudoMatch) {
|
||||
const [, prefix, rest] = pseudoMatch;
|
||||
// Add fb- prefix to the class part, handling negative values
|
||||
if (rest.startsWith("-")) {
|
||||
return `${prefix}-fb-${rest.slice(1)}`;
|
||||
}
|
||||
return `${prefix}fb-${rest}`;
|
||||
}
|
||||
|
||||
// Handle negative values (e.g., -translate-x-1/2)
|
||||
if (cls.startsWith("-")) {
|
||||
return `-fb-${cls.slice(1)}`;
|
||||
}
|
||||
|
||||
// Regular class
|
||||
return `fb-${cls}`;
|
||||
})
|
||||
.join(" ");
|
||||
}
|
||||
15
packages/survey-embed/src/lib/cn-with-prefix.ts
Normal file
15
packages/survey-embed/src/lib/cn-with-prefix.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { addFbPrefix } from "./add-fb-prefix";
|
||||
|
||||
/**
|
||||
* Enhanced cn utility that automatically adds fb- prefix to classes
|
||||
* This replaces survey-core's cn utility via Vite alias
|
||||
* All survey-core components will automatically get fb- prefixed classes
|
||||
*/
|
||||
export function cn(...classes: (string | undefined | null | false)[]): string {
|
||||
// Filter out falsy values and join
|
||||
const combined = classes.filter((cls): cls is string => Boolean(cls)).join(" ");
|
||||
// Add fb- prefix to all Tailwind classes
|
||||
// Handle empty string case (when all classes are falsy)
|
||||
if (!combined) return "";
|
||||
return addFbPrefix(combined);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user