react-roast
v1.4.3
Published
A React widget to get feedback
Maintainers
Readme
React Roast
A React widget to get feedback
Table of Contents
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 | 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
useReactRoasthook - 🖼️ Flexible screenshot options: full page, selected element, or both (configurable)
- 📤 Easily send feedback to your backend or channels (Slack, Discord, etc.)
Installation
npm install react-roastor
yarn add react-roastUsage
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
- Install the
react-roastnpm package. - Import and wrap your app with the
WidgetProvidercomponent. - Set
mode="local"and implement theonFormSubmitcallback to process feedback data. - Store feedback data in your preferred backend or database, and return a boolean status.
- Optionally, use the
customizeprop to adjust the widget’s appearance and behavior.
Hosted Usage
- Sign in to RoastNest.
- Add your site and obtain a unique
siteId. - Install the
react-roastnpm package. - Import and wrap your app with the
WidgetProvidercomponent. - Set
mode="remote"and provide yoursiteIdto connect your site or app. - Optionally, use the
customizeprop to tailor the widget for your site.
Examples
Self-Host Example for React
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
// 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>
);
}// 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:
<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.
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:
// 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:
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:
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: "[email protected]" })}>Set User</button>
</div>
);
}Contribution
Contributions are welcome! If you would like to improve React Roast, please follow these steps:
- Fork the repository.
- Create a new branch for your feature or fix.
- Make changes and commit them.
- Submit a pull request.
Please ensure your contributions align with the project’s coding standards and best practices. If you want help, contact here
License
MIT License.
