UNPKG

react-roast

Version:
375 lines (308 loc) 16.1 kB
# 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) ![Roastnest Widget Demo](https://github.com/user-attachments/assets/41e555a5-e7b1-47c7-8aba-59fd5065f9eb) ## 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.