npm package: @harshshahcg/survey-render
Description: Reusable Vite React + TypeScript survey rendering library (framework-independent UI)
Latest version: 1.4.0

# survey-render

A reusable **React + TypeScript** survey rendering library. Renders surveys from a JSON config with conditional logic, validation, file uploads (base64), and theming. Returns responses in a structured array format. No offline sync or built-in storage—you own submission and persistence.

## Features

- **JSON-driven** — Define surveys via `SurveyConfig`; no hard-coded forms
- **Multiple question types** — Single/multi select, dropdown, text, date, tabular inputs, image/video/voice/document/PDF uploads
- **Conditional logic** — Show/hide questions based on parent answers (`childrenQuestions` / `sourceQuestion`)
- **Validation** — Mandatory checks, character limits, numeric input, file type/size, tabular completeness
- **Media uploads** — Image, video, voice, document, PDF with configurable allowed types and max size (base64 in payload)
- **Step-based UI** — Carousel navigation, overview panel, previous/next/submit, optional exit with save
- **Themes** — 11 built-in color themes + dark mode

## Installation

```bash
npm install @harshshahcg/survey-render
```

**Peer dependencies:** React and React DOM (≥18).

```bash
npm install react react-dom
```

## Quick Start

```tsx
import { SurveyRenderer } from "@harshshahcg/survey-render";
import type { SurveyConfig, SurveySubmitPayload } from "@harshshahcg/survey-render";

const config: SurveyConfig = {
  surveyId: "my-survey",
  surveyName: "Quick Feedback",
  medium: "EN",
  questionList: [
    {
      surveyId: "my-survey",
      questionId: "Q1",
      description: "What is your name?",
      descriptionEN: "What is your name?",
      type: "text-response",
      isMandatory: true,
      outcomeDescription: "",
      questionDescriptionOptional: "",
      sourceQuestion: "",
    },
    {
      surveyId: "my-survey",
      questionId: "Q2",
      description: "How would you rate us?",
      descriptionEN: "How would you rate us?",
      type: "single-select",
      isMandatory: true,
      options: ["Poor", "Fair", "Good", "Excellent"],
      optionsEN: ["Poor", "Fair", "Good", "Excellent"],
      outcomeDescription: "",
      questionDescriptionOptional: "",
      sourceQuestion: "",
    },
  ],
};

function App() {
  const handleSubmit = (payload: SurveySubmitPayload) => {
    console.log("Submitted:", payload);
  };

  return (
    <SurveyRenderer
      data={config}
      onSubmit={handleSubmit}
      theme="emerald"
    />
  );
}
```

---

## Full Sample Example

This example covers all major features: question types, conditional logic, media config, progress bar, themes, save/exit, and validation.

```tsx
import React, { useState } from "react";
import { SurveyRenderer } from "@harshshahcg/survey-render";
import type {
  SurveyConfig,
  SurveySubmitPayload,
} from "@harshshahcg/survey-render";

const fullSampleConfig: SurveyConfig = {
  surveyId: "full-demo",
  surveyName: "Complete Survey Demo",
  medium: "EN",
  // Optional: media validation (allowed types, max size in MB)
  config: {
    "image-response": {
      suppotedType: ["image/jpeg", "image/png"],
      supportedSize: 10,
    },
    "video-response": { suppotedType: ["video/mp4"], supportedSize: 50 },
    "voice-response": { suppotedType: ["audio/mpeg"], supportedSize: 10 },
    "pdf-response": { suppotedType: ["application/pdf"], supportedSize: 10 },
  },
  questionList: [
    // Text response
    {
      surveyId: "full-demo",
      questionId: "Q1",
      description: "Your name?",
      descriptionEN: "Your name?",
      type: "text-response",
      isMandatory: true,
      textLimitCharacter: 100,
      outcomeDescription: "",
      questionDescriptionOptional: "",
      sourceQuestion: "",
    },
    // Single select
    {
      surveyId: "full-demo",
      questionId: "Q2",
      description: "Select your role",
      descriptionEN: "Select your role",
      type: "single-select",
      isMandatory: true,
      options: ["Developer", "Designer", "Manager", "Other"],
      optionsEN: ["Developer", "Designer", "Manager", "Other"],
      childrenQuestions: { Other: ["Q2.1"] },
      outcomeDescription: "",
      questionDescriptionOptional: "",
      sourceQuestion: "",
    },
    // Conditional text (shown only when "Other" selected in Q2)
    {
      surveyId: "full-demo",
      questionId: "Q2.1",
      description: "Please specify your role",
      descriptionEN: "Please specify your role",
      type: "text-response",
      isMandatory: false,
      sourceQuestion: "Q2",
      outcomeDescription: "",
      questionDescriptionOptional: "",
    },
    // Multi-select
    {
      surveyId: "full-demo",
      questionId: "Q3",
      description: "Select all that apply",
      descriptionEN: "Select all that apply",
      type: "multi-select",
      isMandatory: false,
      options: ["Option A", "Option B", "Option C"],
      optionsEN: ["Option A", "Option B", "Option C"],
      outcomeDescription: "",
      questionDescriptionOptional: "",
      sourceQuestion: "",
    },
    // Dropdown
    {
      surveyId: "full-demo",
      questionId: "Q4",
      description: "Choose one (dropdown)",
      descriptionEN: "Choose one (dropdown)",
      type: "drop-down",
      isMandatory: true,
      options: ["Choice 1", "Choice 2", "Choice 3"],
      optionsEN: ["Choice 1", "Choice 2", "Choice 3"],
      outcomeDescription: "",
      questionDescriptionOptional: "",
      sourceQuestion: "",
    },
    // Date picker
    {
      surveyId: "full-demo",
      questionId: "Q5",
      description: "Date of last visit",
      descriptionEN: "Date of last visit",
      type: "calendar",
      isMandatory: false,
      outcomeDescription: "",
      questionDescriptionOptional: "",
      sourceQuestion: "",
    },
    // Image upload
    {
      surveyId: "full-demo",
      questionId: "Q6",
      description: "Upload a photo",
      descriptionEN: "Upload a photo",
      type: "image-response",
      isMandatory: false,
      outcomeDescription: "",
      questionDescriptionOptional: "",
      sourceQuestion: "",
    },
    // Tabular input
    {
      surveyId: "full-demo",
      questionId: "Q7",
      description: "Fill the table",
      descriptionEN: "Fill the table",
      type: "tabular-text-input",
      isMandatory: false,
      tableHeaderValue: ["Item", "Value"],
      tableQuestionValue: [
        { rowId: "a", rowQuestion: "Field A" },
        { rowId: "b", rowQuestion: "Field B" },
      ],
      textInputType: "None",
      textLimitCharacter: 50,
      outcomeDescription: "",
      questionDescriptionOptional: "",
      sourceQuestion: "",
    },
  ],
};

export default function App() {
  const [submitting, setSubmitting] = useState(false);

  const handleSubmit = async (payload: SurveySubmitPayload) => {
    setSubmitting(true);
    try {
      console.log("Submit:", payload);
      await fetch("/api/survey", {
        method: "POST",
        body: JSON.stringify(payload),
        headers: { "Content-Type": "application/json" },
      });
      alert("Thank you! Survey submitted.");
    } finally {
      setSubmitting(false);
    }
  };

  const handleSave = (payload: SurveySubmitPayload) => {
    localStorage.setItem("survey-draft", JSON.stringify(payload));
    console.log("Draft saved");
  };

  const handleExit = () => {
    if (window.confirm("Leave without submitting?")) window.history.back();
  };

  return (
    <SurveyRenderer
      data={fullSampleConfig}
      onSubmit={handleSubmit}
      onSave={handleSave}
      onExit={handleExit}
      isSubmitting={submitting}
      showHeader={true}
      theme="emerald"
      className="min-h-screen max-w-2xl mx-auto p-4"
      progressBar={{
        showLabel: true,
        showPercentage: true,
        labelFormat: "Question {current} of {total}",
        variant: "default",
        progressMode: "completion",
      }}
    />
  );
}
```

---

## Themes

Apply a theme by passing the `theme` prop to `SurveyRenderer`. Wrap your app (or a parent) with `data-theme="..."` if you manage the DOM. The library sets it when `theme` is provided.

| Theme      | Sample                             | Primary Color   |
|-----------|-------------------------------------|-----------------|
| **emerald** | <span style="display:inline-block;width:14px;height:14px;background:#059669;border-radius:2px;"></span> | `#059669`       |
| **violet**  | <span style="display:inline-block;width:14px;height:14px;background:#7c3aed;border-radius:2px;"></span> | `#7c3aed`       |
| **amber**   | <span style="display:inline-block;width:14px;height:14px;background:#d97706;border-radius:2px;"></span> | `#d97706`       |
| **blue**    | <span style="display:inline-block;width:14px;height:14px;background:#2563eb;border-radius:2px;"></span> | `#2563eb`       |
| **rose**    | <span style="display:inline-block;width:14px;height:14px;background:#e11d48;border-radius:2px;"></span> | `#e11d48`       |
| **teal**    | <span style="display:inline-block;width:14px;height:14px;background:#0d9488;border-radius:2px;"></span> | `#0d9488`       |
| **indigo**  | <span style="display:inline-block;width:14px;height:14px;background:#4f46e5;border-radius:2px;"></span> | `#4f46e5`       |
| **cyan**    | <span style="display:inline-block;width:14px;height:14px;background:#0891b2;border-radius:2px;"></span> | `#0891b2`       |
| **orange**  | <span style="display:inline-block;width:14px;height:14px;background:#ea580c;border-radius:2px;"></span> | `#ea580c`       |
| **lime**    | <span style="display:inline-block;width:14px;height:14px;background:#65a30d;border-radius:2px;"></span> | `#65a30d`       |
| **fuchsia** | <span style="display:inline-block;width:14px;height:14px;background:#c026d3;border-radius:2px;"></span> | `#c026d3`       |
| **dark**    | <span style="display:inline-block;width:14px;height:14px;background:#60a5fa;border-radius:2px;"></span> | Dark mode variant |

```tsx
<SurveyRenderer data={config} onSubmit={onSubmit} theme="violet" />
```

---

## Build

Build the library for distribution:

```bash
npm run build
```

This produces:

- `dist/surveyRender.js` — ESM bundle
- `dist/surveyRender.umd.cjs` — UMD bundle
- `dist/index.d.ts` — TypeScript declarations

---

## Publishing to npm

1. **Create an npm account** at [npmjs.com](https://www.npmjs.com/signup) if needed.

2. **Log in** from the project directory:

   ```bash
   npm login
   ```

3. **Check package name availability.** If `survey-render` is taken, use a scoped name in `package.json`:

   ```json
   "name": "@your-username/survey-render"
   ```

4. **Build and publish:**

   ```bash
   npm run build
   npm publish
   ```

   The `prepublishOnly` script runs `npm run build` automatically before `npm publish`.

5. **For scoped packages**, publish with public access:

   ```bash
   npm publish --access public
   ```

6. **Version updates:**

   ```bash
   npm version patch   # 1.0.0 → 1.0.1
   npm version minor   # 1.0.0 → 1.1.0
   npm publish
   ```

---

## How to Import

```tsx
// Main component + types
import { SurveyRenderer } from "@harshshahcg/survey-render";
import type {
  SurveyConfig,
  SurveySubmitPayload,
  SurveyQuestion,
} from "@harshshahcg/survey-render";

// Multiple components (custom layouts)
import {
  SurveyRenderer,
  SurveyContainer,
  QuestionRenderer,
  ValidationDialog,
  ExitConfirmationDialog,
} from "@harshshahcg/survey-render";

// Utils
import {
  validateResponses,
  canSubmit,
  formatResponses,
  calculateVisibility,
  getQuestionStatusMap,
  calculateWeightedProgress,
  isValueFileObject,
  getEffectiveMediaConfig,
  SUPPORTED_QUESTION_TYPES,
  isSupportedQuestionType,
} from "@harshshahcg/survey-render";
```

**Styling:** The library uses Tailwind CSS. Your app must have Tailwind configured so the survey UI renders correctly.

---

## Survey Config Shape

- **`SurveyConfig`** — `surveyId`, `medium`, `questionList` (required). Optional: `surveyCode`, `surveyName`, `config` (media validation).
- **`SurveyQuestion`** — `surveyId`, `questionId`, `description`, `descriptionEN`, `type`, `isMandatory`, `outcomeDescription`, `questionDescriptionOptional`, `sourceQuestion`. Optional: `options`, `optionsEN`, `textInputType`, `textLimitCharacter`, `minValue`, `maxValue`, `childrenQuestions`, `tableHeaderValue`, `tableQuestionValue`, `userAnswer`.
- **Media config** (`data.config`) — Keys: `"image-response"`, `"video-response"`, `"voice-response"`, `"document-response"`, `"pdf-response"`. Values: `supportedType` (or legacy `suppotedType`) and `supportedSize` (MB).

---

## Supported Question Types

| Type                | Description              |
|---------------------|--------------------------|
| `single-select`     | One option from list     |
| `multi-select`      | Multiple options         |
| `drop-down`         | Dropdown single select   |
| `text-response`     | Free text (optional limit) |
| `calendar`          | Date picker              |
| `image-response`    | Image upload (base64)    |
| `video-response`    | Video upload (base64)    |
| `voice-response`    | Audio upload (base64)    |
| `document-response` | Document upload (base64) |
| `pdf-response`      | PDF upload (base64)      |
| `tabular-text-input`| Rows with text fields    |
| `tabular-drop-down` | Rows with dropdowns      |
| `tabular-check-box` | Rows with checkboxes     |

---

## Conditional Logic

- **Root questions** — No `sourceQuestion`; always visible.
- **Child questions** — Listed in another question’s `childrenQuestions`. Shown when the parent’s answer matches the key (e.g. `"Other": ["Q5.1"]` → show Q5.1 when parent answer is `"Other"`).
- Hidden questions’ answers are cleared when they become hidden.

Helpers: `calculateVisibility`, `clearHiddenResponses`.

---

## Validation

Mandatory, text limit, numeric input type, file presence, tabular completeness. On submit, invalid answers produce a list of `{ questionId, message }` and the built-in validation dialog opens. Helpers: `validateResponses`, `canSubmit`.

---

## Response Format

**`SurveySubmitPayload`** — Passed to `onSubmit` and `onSave`: `surveyId`, optional `surveyCode`/`surveyName`/`type`, and `responses: SurveyResponseArray`.

**`SurveyResponseArray`** — Array of `{ questionId, userAnswer }`. `userAnswer` is string, number, boolean, string[], `RowAnswer[]` (tabular), or `FileObject` (fileName, fileType, binary base64). Dates are formatted as `YYYY-MM-DD`.

---

## License

MIT — see [LICENSE](LICENSE).