react-roast
Version:
A React widget to get feedback
375 lines (308 loc) • 16.1 kB
Markdown
# React Roast
A React widget to get feedback
---
## Table of Contents
- [Purpose](#purpose)
- [Demo](#demo)
- [Features](#features)
- [Installation](#installation)
- [Usage](#usage)
- [Self-host Usage](#self-host-usage)
- [Hosted Usage](#hosted-usage)
- [Examples](#examples)
- [Self-Host Example for React](#self-host-example-for-react)
- [Self-Host Example for Nextjs](#self-host-example-for-nextjs)
- [Props](#props)
- [Widget Provider Props](#widget-provider-props)
- [Widget Customize Props](#widget-customize-props)
- [Default Customization](#default-customization)
- [Form Data Props](#form-data-props)
- [Hooks](#hooks)
- [useReactRoast](#usereactroast)
- [Contribution](#contribution)
- [License](#license)
---
## Purpose
React Roast is an open-source app inspector that allows users to select elements on a webpage, capture their state (including screenshots), and send the details to a desired channel. This tool is useful for UI/UX testing, feedback collection, and debugging user interfaces.
## Demo
**Live Demo:** [RoastNest.com](https://roastnest.com) | [Growati.com](https://growati.com)

## Features
- 🖱️ Select any element on a webpage
- 📸 Capture element position, size, and a screenshot
- 📝 Collect feedback with customizable forms
- 🔔 Supports notifications and user rewards
- ⚛️ Supports React-based frameworks like Next.js
- 🏠 Self-host and customize widget appearance and behavior
- ⚡ Lightweight and easy to integrate
- 🟦 Written in Typescript and built using rollup
- 🌐 Works in both local and remote modes
- 🛠️ Imperative control via `useReactRoast` hook
- 🖼️ Flexible screenshot options: full page, selected element, or both (configurable)
- 📤 Easily send feedback to your backend or channels (Slack, Discord, etc.)
## Installation
```sh
npm install react-roast
```
or
```sh
yarn add react-roast
```
## Usage
To use React Roast, wrap your application with the `WidgetProvider` component from `react-roast`. Make sure to use the provider on the client side, set the `mode` prop to `local`, and implement the `onFormSubmit` callback to handle form submissions.
### Self-host Usage
1. Install the `react-roast` npm package.
2. Import and wrap your app with the `WidgetProvider` component.
3. Set `mode="local"` and implement the `onFormSubmit` callback to process feedback data.
4. Store feedback data in your preferred backend or database, and return a boolean status.
5. Optionally, use the `customize` prop to adjust the widget’s appearance and behavior.
### Hosted Usage
1. Sign in to [RoastNest](https://roastnest.com).
2. Add your site and obtain a unique `siteId`.
3. Install the `react-roast` npm package.
4. Import and wrap your app with the `WidgetProvider` component.
5. Set `mode="remote"` and provide your `siteId` to connect your site or app.
6. Optionally, use the `customize` prop to tailor the widget for your site.
## Examples
### Self-Host Example for React
```tsx
import WidgetProvider, { FormDataProps } from "react-roast";
export default function App() {
const handleSubmit = async ({ message, email, screenshotBlobs }: FormDataProps): Promise<boolean> => {
// Must return boolean value.
try {
// Send feedback data to your backend
// Or send to you channel (e.g., Slack, Discord)
return true;
} catch (e) {
return false;
}
};
return (
<WidgetProvider mode="local" onFormSubmit={handleSubmit}>
<Main />
</WidgetProvider>
);
}
```
### Self-Host Example for Next.js
```tsx
// app/RoastProvider.tsx
"use client";
import WidgetProvider, { FormDataProps } from "react-roast";
import { ReactNode } from "react";
export default function RoastProvider({ children }: { children: ReactNode }) {
const handleSubmit = async ({ message, email, screenshotBlobs }: FormDataProps): Promise<boolean> => {
try {
// Send feedback data to your backend
// Or send to you channel (e.g., Slack, Discord)
return true;
} catch (e) {
return false;
}
};
return (
<WidgetProvider mode="local" onFormSubmit={handleSubmit}>
{children}
</WidgetProvider>
);
}
```
```tsx
// app/layout.tsx
import RoastProvider from "./RoastProvider";
import { ReactNode } from "react";
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html>
<body>
<RoastProvider>{children}</RoastProvider>
</body>
</html>
);
}
```
## Props
### Widget Provider Props
| Property | Type | Description |
| -------------- | ------------------- | -------------------------------------------------------------------------------- |
| `mode` | `local` or `remote` | Defines if the widget operates locally or remotely |
| `children` | `ReactNode` | Nested components inside the provider |
| `onFormSubmit` | `function` | Callback for form submission. Returns a boolean for success/failure. |
| `customize` | `object` | Customization options for widget appearance, behavior, screenshots, and notices. |
| `siteId` | `string` | Optional site identifier, useful for remote mode or multi-site setups. |
| `hideIsland` | `boolean` | Hide the floating trigger island while keeping the widget available via hooks. |
### Widget Customize Props
Customize the widget by passing the `customize` prop with these options:
| Property | Type | Description |
| --------------------------------------------- | --------- | ------------------------------------------------------------------------------------------- |
| `form.className` | `string` | Custom CSS class for the form container |
| `form.errorMessage` | `string` | Error message shown when submission fails |
| `form.successMessage` | `string` | Success message shown when submission succeeds |
| `form.messageInput.className` | `string` | Custom CSS class for the message input field |
| `form.messageInput.placeholder` | `string` | Placeholder text for the message input field |
| `form.submitButton.label` | `string` | Label text for the submit button |
| `form.submitButton.className` | `string` | Custom CSS class for the submit button |
| `form.cancelButton.label` | `string` | Label text for the cancel button |
| `form.cancelButton.className` | `string` | Custom CSS class for the cancel button |
| `form.output.excludeFullPageScreenshot` | `boolean` | If `true`, skip capturing a full-page screenshot |
| `form.output.excludeSelectedElementScreenshot`| `boolean` | If `true`, skip capturing the selected-element screenshot |
| `island.mode` | `string` | Display mode for the island button (`default` = label + switch, `icon` = pointer icon only) |
| `island.placement` | `string` | Position of the island button (`left-center`, `right-bottom`, etc.) |
| `island.className` | `string` | Custom CSS class for the island button |
| `island.label` | `string` | Label text for the island button |
| `island.switchButton.className` | `string` | Custom CSS class for the switch button inside the island |
| `island.switchButton.thumb.className` | `string` | Custom CSS class for the thumb of the switch button |
| `notifications.enable` | `boolean` | Enable or disable notifications |
| `notifications.repeatDelay` | `number` | Seconds to wait between showing notification messages |
| `notifications.displayDuration` | `number` | Seconds each notification stays visible |
| `notifications.allowDismissal` | `boolean` | Allow the user to dismiss notifications for the current session |
| `notifications.allowParmanentDismissal` | `boolean` | Allow the user to permanently dismiss notifications across sessions |
| `notifications.paramanentDismissalExpiryDays` | `number` | Number of days after which a permanent dismissal expires and notifications start again |
| `notifications.messages` | `array` | Array of notification message objects |
| `notifications.messages.type` | `string` | Type of notification message (`info`, `hint`, `offer`, etc.) |
| `notifications.messages.message` | `string` | Text content of the notification message |
**Example usage:**
```tsx
<WidgetProvider
mode="local"
onFormSubmit={handleSubmit}
customize={{
form: {
className: "custom-form",
errorMessage: "Submission failed!",
successMessage: "Feedback sent!",
messageInput: {
className: "custom-input",
placeholder: "Type your feedback...",
},
submitButton: {
label: "Send",
className: "custom-submit",
},
cancelButton: {
label: "Cancel",
className: "custom-cancel",
},
},
island: {
placement: "right-center",
className: "custom-island",
label: "Roast",
switchButton: {
className: "custom-switch",
thumb: {
className: "custom-thumb",
},
},
},
notifications: {
enable: true,
messages: [
{ type: "info", message: "Feedback sent!" },
{ type: "hint", message: "Something went wrong." },
],
},
}}
>
<Main />
</WidgetProvider>
```
### Default Customization
The widget comes with sensible defaults. You can override any part using the `customize` prop.
```typescript
const defaultCustomize = {
form: {
messageInput: {
placeholder: "Don't be nice, Just Roast!",
},
submitButton: { label: "Roast it" },
cancelButton: { label: "Cancel" },
errorMessage: "Failed to submit message",
successMessage: "Message Submitted",
output: {
excludeFullPageScreenshot: false,
excludeSelectedElementScreenshot: false,
},
},
island: {
mode: "default",
label: "Roast Mode",
placement: "left-center",
},
notifications: {
enable: true,
repeatDelay: 15,
displayDuration: 5,
allowDismissal: true,
allowParmanentDismissal: false,
paramanentDismissalExpiryDays: 7,
messages: [
{ message: "Feedback help us improve! Share your thoughts.", type: "info" },
{ message: "Click here to share feedback with us.", type: "hint" },
{ message: "Give feedback and get discounts!", type: "offer" },
{ message: "You’ve earned discount! Redeem them now.", type: "reward" },
{ message: "20+ Users love our product! Join them now.", type: "social" },
{ message: "Last chance! discount ends in 2 days. Hurry up!", type: "urgent" },
],
},
};
```
### Form Data Props
| Property | Type | Description |
| ----------------- | ------------------- | -------------------------------------- |
| `email` | `string` (optional) | The user's email address, if provided. |
| `message` | `string` | The message input by the user. |
| `screenshotBlobs` | `ScreenshotBlobs` | Array of screenshot blobs (see below). |
**ScreenshotBlobs structure:**
```typescript
// ScreenshotBlobs type
Array<{
blob: Blob;
type: "full-screenshot" | "selected-screenshot";
}>;
```
- `blob`: The captured screenshot as a Blob object.
- `type`: Indicates if the screenshot is of the full page or a selected element.
**Example FormDataProps usage:**
```typescript
interface FormDataProps {
email?: string;
message: string;
screenshotBlobs: ScreenshotBlobs;
}
```
## Hooks
### useReactRoast
The `useReactRoast` hook provides imperative control and utility functions for the widget. Use it inside your components to interact with the widget programmatically.
**Returned values:**
| Property | Type | Description |
| ----------------------- | ---------- | ------------------------------------------------- |
| `isWidgetActive` | `boolean` | Whether the widget is currently active |
| `toggleWidget` | `function` | Toggle the widget's active state |
| `avoidElementClassName` | `string` | CSS class name to exclude elements from selection |
| `setIslandVisiblity` | `function` | Show or hide the widget island button |
| `setUser` | `function` | Set or update the user data |
**Usage Example:**
```tsx
import { useReactRoast } from "react-roast";
function WidgetControls() {
const { isWidgetActive, toggleWidget, setIslandVisiblity, setUser } = useReactRoast();
return (
<div>
<button onClick={toggleWidget}>{isWidgetActive ? "Deactivate" : "Activate"} Widget</button>
<button onClick={() => setIslandVisiblity(false)}>Hide Island</button>
<button onClick={() => setUser({ email: "user@example.com" })}>Set User</button>
</div>
);
}
```
## Contribution
Contributions are welcome! If you would like to improve React Roast, please follow these steps:
1. Fork the repository.
2. Create a new branch for your feature or fix.
3. Make changes and commit them.
4. Submit a pull request.
Please ensure your contributions align with the project’s coding standards and best practices. If you want help, [contact here](https://x.com/satyamskillz)
## License
MIT License.