npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pamfilico/feedback

v2.1.9

Published

Feedback components for Material UI and other frameworks

Readme

@pamfilico/feedback

A pure React feedback component library for Material UI. Works with any React framework (Next.js, Vite, Create React App, Remix, etc.).

Installation

All React Projects (18 or 19)

npm install @pamfilico/feedback
npm install react-canvas-draw

Note: Starting with v2.1.0, react-canvas-draw is no longer bundled and must be installed separately in your project. This gives you more control over versioning and allows you to apply any necessary patches for React 19 compatibility.

For React 19 projects, you may need to install react-canvas-draw with the --legacy-peer-deps flag:

npm install react-canvas-draw --legacy-peer-deps

Migrating from v2.0.x to v2.1.0

Version 2.1.0 removes Next.js as a dependency, making this a pure React library:

What changed:

  • Removed Next.js dependency (no longer required)
  • Removed react-canvas-draw from package dependencies (install separately)
  • Replaced Next.js-specific code with standard React patterns

Action required:

  1. Install react-canvas-draw in your project: npm install react-canvas-draw
  2. If using React 19, add --legacy-peer-deps flag when installing react-canvas-draw
  3. No code changes needed in your application - the API remains exactly the same

Quick Start

Material UI Feedback Button

import { MaterialFeedbackButton } from "@pamfilico/feedback/material";

function App() {
  return (
    <MaterialFeedbackButton
      meta={{ user_email: "[email protected]", visitor_id: "abc123" }}
      apiBasePath="/api/v1/feedback"
      additionalHeaders={{ "Authorization": "Bearer token" }}
    />
  );
}

Examples

Basic Usage (Drawer Variant)

import { MaterialFeedbackButton } from "@pamfilico/feedback/material";

export default function MyApp() {
  return (
    <>
      {/* Your app content */}
      <MaterialFeedbackButton
        meta={{ user_email: "[email protected]" }}
        apiBasePath="/api/v1/feedback"
      />
    </>
  );
}

Dialog Variant (Centered Form)

If you prefer a centered dialog form instead of a right-side drawer:

<MaterialFeedbackButton
  meta={{ user_email: "[email protected]" }}
  apiBasePath="/api/v1/feedback"
  formAsDialog={true}  // Shows form as centered dialog
/>

With Custom Metadata

Pass any custom data you want to include with feedback submissions:

<MaterialFeedbackButton
  meta={{
    user_email: session?.user?.email,
    user_id: session?.user?.id,
    visitor_id: analytics.visitorId,
    company_id: user?.companyId,
    subscription_tier: "pro",
    custom_field: "any value"
  }}
  apiBasePath="/api/v1/feedback"
  additionalHeaders={{
    "Authorization": `Bearer ${token}`,
    "X-Custom-Header": "value"
  }}
/>

Conditional Display

<MaterialFeedbackButton
  meta={{ user_email: user?.email }}
  apiBasePath="/api/v1/feedback"
  hideIfNoMeta={true}  // Only show if meta is provided
/>

With App ID Tracking

The appId prop is automatically merged into the meta object:

<MaterialFeedbackButton
  meta={{ user_email: "[email protected]", visitor_id: "xyz789" }}
  apiBasePath="/api/v1/feedback"
  appId="my-app-production"  // Automatically added to meta as app_id
/>

Next.js App Router Example

// app/layout.tsx
import { MaterialFeedbackButton } from "@pamfilico/feedback/material";
import { auth } from "@/lib/auth";

export default async function RootLayout({ children }) {
  const session = await auth();

  return (
    <html>
      <body>
        {children}
        <MaterialFeedbackButton
          meta={{
            user_email: session?.user?.email,
            user_id: session?.user?.id,
            user_name: session?.user?.name
          }}
          apiBasePath="/api/v1/feedback"
          formAsDialog={true}
        />
      </body>
    </html>
  );
}

Button Placement

Position the feedback button anywhere on screen:

// Bottom positions
<MaterialFeedbackButton placement="bottom-right" />  // Default
<MaterialFeedbackButton placement="bottom-left" />
<MaterialFeedbackButton placement="bottom-center" />

// Top positions
<MaterialFeedbackButton placement="top-right" />
<MaterialFeedbackButton placement="top-left" />
<MaterialFeedbackButton placement="top-center" />

// Side positions (rotated vertically)
<MaterialFeedbackButton placement="right-middle" />
<MaterialFeedbackButton placement="left-middle" />

// Parent positioning - use as inline button
<div className="my-toolbar">
  <button>Save</button>
  <button>Cancel</button>
  <MaterialFeedbackButton placement="parent" userEmail="[email protected]" />
</div>

Features:

  • The parent placement makes the button follow normal document flow instead of being fixed, perfect for toolbars or inline usage
  • Side placements (right-middle, left-middle) are automatically rotated 90° for vertical display
  • On small screens (< 600px), only the icon is shown to save space

Button Color Customization

Customize the button color to match your app's theme:

// Red (default)
<MaterialFeedbackButton color="error" userEmail="[email protected]" />

// Blue
<MaterialFeedbackButton color="primary" userEmail="[email protected]" />

// Green
<MaterialFeedbackButton color="success" userEmail="[email protected]" />

// Orange
<MaterialFeedbackButton color="warning" userEmail="[email protected]" />

// Purple
<MaterialFeedbackButton color="secondary" userEmail="[email protected]" />

// Light Blue
<MaterialFeedbackButton color="info" userEmail="[email protected]" />

Internationalization (i18n)

The component supports multiple languages out of the box. All UI text, form labels, validation messages, and notifications are translatable:

// English (default)
<MaterialFeedbackButton
  userEmail="[email protected]"
  locale="en"
/>

// Greek (Ελληνικά)
<MaterialFeedbackButton
  userEmail="[email protected]"
  locale="el"
/>

Supported Languages:

  • en - English (default)
  • el - Greek (Ελληνικά)

What gets translated:

  • Button text
  • Form labels and placeholders
  • Validation error messages
  • Feedback type options (Bug, Feature Request, Other)
  • Drawing tool buttons (Reset, Undo, Close)
  • Success/error notifications
  • Screen size labels (Mobile, Tablet, Desktop)
  • All help text and instructions

Adding new languages:

To add support for additional languages:

  1. Create a new translation file in src/locales/[language-code].json
  2. Copy the structure from en.json and translate all values
  3. Update the Locale type in src/locales/index.ts to include your language code
  4. Import and add your translations to the translations object

Example for Spanish (es):

// src/locales/index.ts
import enTranslations from './en.json';
import elTranslations from './el.json';
import esTranslations from './es.json'; // Your new translation

export type Locale = 'en' | 'el' | 'es'; // Add 'es'

const translations: Record<Locale, Translations> = {
  en: enTranslations,
  el: elTranslations,
  es: esTranslations, // Add your translations
};

Components

MaterialFeedbackButton

A floating feedback button that opens a fullscreen dialog for creating feedback. When clicked, it automatically captures a screenshot of the current page and opens a dialog where users can draw annotations on the screenshot and submit their feedback.

Feedback Submission Example

Key Features:

  • 📸 Automatic screenshot capture - Captures the entire page when the button is clicked
  • ✏️ Interactive annotation canvas - Draw directly on the screenshot with customizable brush
  • 🎨 Drawing tools - Clear canvas, undo last stroke, brush color/size controls
  • 📝 Feedback form - Type selection (bug/feature/other), description field
  • 📱 Device detection - Automatically detects and stores device type (mobile/tablet/desktop)
  • 🔄 Manual upload fallback - If auto-capture fails, users can upload their own screenshot
  • 🌐 URL tracking - Automatically captures the current URL where feedback was submitted
  • 🎨 Customizable button color - Choose from error, primary, secondary, success, info, or warning colors
  • 🌍 Internationalization (i18n) - Built-in support for multiple languages (English and Greek included)
  • 📱 Mobile-optimized UI - Separate mobile and desktop components with optimized layouts

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | meta | Record<string, any> \| null | null | Custom metadata object to include with feedback (e.g., user info, analytics IDs, etc.) | | apiBasePath | string | "/api/feedback" | API endpoint for feedback submission | | additionalHeaders | Record<string, string> | {} | Additional headers for API requests | | hideIfNoMeta | boolean | false | Hide button if no meta provided | | appId | string | undefined | Application identifier - automatically merged into meta as app_id | | formAsDialog | boolean | false | Show form as centered dialog instead of drawer (desktop only) | | placement | 'bottom-right' \| 'bottom-left' \| 'bottom-center' \| 'top-right' \| 'top-left' \| 'top-center' \| 'right-middle' \| 'left-middle' \| 'parent' | 'bottom-right' | Button position on screen. Side positions are rotated vertically. Use 'parent' for inline positioning | | color | 'error' \| 'primary' \| 'secondary' \| 'success' \| 'info' \| 'warning' | 'error' | Button color theme (red, blue, purple, green, light blue, orange) | | locale | string | 'en' | Language locale for UI text. Supports 'en' (English) and 'el' (Greek). Falls back to 'en' for invalid values |

Submission Data Schema

When a user submits feedback, the component sends a POST request to your API endpoint with the following data structure:

{
  feedbackType: "bug" | "feature" | "other";  // Type of feedback (camelCase for compatibility)
  description: string;                 // User's feedback description
  image: string;                       // Base64 encoded screenshot image (data:image/png;base64,...)
  drawings: {                         // Canvas drawing data for annotations
    lines: Array<{
      points: Array<{x: number, y: number}>;
      brushColor: string;
      brushRadius: number;
    }>;
    width: number;                    // Canvas width
    height: number;                   // Canvas height
  } | null;
  current_url: string;                 // URL where feedback was submitted
  material_ui_screensize: "mobile" | "tablet" | "desktop";  // Device type
  meta: Record<string, any>;           // Custom metadata (includes app_id if provided)
}

Example Submission:

{
  "feedbackType": "bug",
  "description": "The submit button is not working on the checkout page",
  "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...",
  "drawings": {
    "lines": [
      {
        "points": [{"x": 100, "y": 150}, {"x": 102, "y": 152}],
        "brushColor": "#ff0000",
        "brushRadius": 2
      }
    ],
    "width": 1920,
    "height": 1080
  },
  "current_url": "https://example.com/checkout",
  "material_ui_screensize": "desktop",
  "meta": {
    "user_email": "[email protected]",
    "user_id": "123",
    "visitor_id": "abc-def-ghi",
    "app_id": "my-app-production"
  }
}

FeedbackPageComponent

A complete feedback list page with inline editing capabilities. Displays user feedback items with pagination and opens a fullscreen dialog for editing.

import { FeedbackPageComponent } from "@pamfilico/feedback/material";

function FeedbackPage() {
  return (
    <FeedbackPageComponent
      fetchFeedbacksUrl="https://api.example.com/api/v1/feedback"
      editingUrl="https://api.example.com/api/v1/feedback"  // Optional: for edit operations
      additionalHeaders={{ "EPICWORK-TOKEN": token }}
      onClickEditButtonFeedbackItem={(id) => console.log("Editing:", id)}
    />
  );
}

Key Points:

  • fetchFeedbacksUrl: Used for fetching the paginated list. Pagination params (page, limit) are automatically appended.
  • editingUrl: Used for GET/PUT operations on individual feedback items. The feedbackId is automatically appended (e.g., ${editingUrl}/${feedbackId}).

Props

| Prop | Type | Required | Description | |------|------|----------|-------------| | fetchFeedbacksUrl | string | Yes | Full URL for fetching paginated feedback list. Pagination params (page, limit) are automatically appended as query parameters | | editingUrl | string | No | Base URL for edit operations. feedbackId is automatically appended for GET/PUT requests (e.g., ${editingUrl}/${feedbackId}). Optional if you don't need inline editing | | additionalHeaders | Record<string, string> | No | Authentication headers for API requests | | onClickEditButtonFeedbackItem | (feedbackId: string) => void | No | Optional callback when edit button is clicked |

Features

  • 📋 Paginated feedback list
  • ✏️ Inline editing with fullscreen dialog
  • 🔄 Auto-refresh after updates
  • 📱 Device type indicators (mobile/tablet/desktop)
  • 🎨 Type-based color coding (bug/feature/other)
  • 🖼️ Screenshot previews with annotation support

FeedbackEditPageComponent

A wrapper component that handles fetching feedback data and rendering the appropriate edit component (Desktop or Mobile) based on the original submission device.

import {
  FeedbackEditPageComponent,
  DesktopEditFeedbackComponent,
  MobileEditFeedbackComponent
} from "@pamfilico/feedback/material";

function EditFeedback({ feedbackId }) {
  return (
    <FeedbackEditPageComponent
      editingUrl="https://api.example.com/api/v1/feedback"
      feedbackId={feedbackId}
      additionalHeaders={{ "EPICWORK-TOKEN": token }}
      onUpdate={(id) => router.push("/feedback")}
      onCancel={() => router.push("/feedback")}
      desktopComponent={DesktopEditFeedbackComponent}
      mobileComponent={MobileEditFeedbackComponent}
    />
  );
}

Note: The feedbackId is automatically appended to editingUrl for GET/PUT requests.

Props

| Prop | Type | Required | Description | |------|------|----------|-------------| | editingUrl | string | Yes | Base URL for edit operations. feedbackId is automatically appended (e.g., ${editingUrl}/${feedbackId}) | | feedbackId | string | Yes | ID of feedback to edit | | additionalHeaders | Record<string, string> | No | Authentication headers | | onUpdate | (feedbackId: string) => void | No | Callback when feedback is updated | | onCancel | () => void | No | Callback when editing is cancelled | | desktopComponent | React.ComponentType | Yes | Desktop edit component | | mobileComponent | React.ComponentType | Yes | Mobile edit component |


DesktopEditFeedbackComponent

Edit component optimized for desktop/tablet devices with side-by-side screenshot annotation and form.

Props

| Prop | Type | Required | Description | |------|------|----------|-------------| | feedback | any | Yes | Feedback object to edit | | editingUrl | string | Yes | Base URL for edit operations. feedbackId is automatically appended from feedback.id | | additionalHeaders | Record<string, string> | No | Authentication headers | | onUpdate | (feedbackId: string) => void | No | Callback after successful update | | onCancel | () => void | No | Callback when cancelled |


MobileEditFeedbackComponent

Edit component optimized for mobile devices with stacked screenshot and form layout.

Props

Same as DesktopEditFeedbackComponent.


API Requirements

All feedback components expect your backend API to implement these endpoints:

POST /api/v1/feedback

Used by: MaterialFeedbackButton

Creates a new feedback submission.

Request Body:

{
  user_email: string | null;          // Optional - user's email
  feedbackType: "bug" | "feature" | "other";  // Optional - type of feedback
  description: string;                // Optional - feedback description
  image: string;                      // Optional - base64 data URL or S3 URL
  current_url: string;                // Optional - URL where feedback submitted
  drawings: {                         // Optional - drawing annotation data
    lines: Array<{
      points: Array<{x: number, y: number}>;
      brushColor: string;
      brushRadius: number;
    }>;
    width: number;
    height: number;
  } | null;
  material_ui_screensize: "mobile" | "tablet" | "desktop";  // Optional - device type
  softwarefast_task_id: string;      // Optional - external task tracker ID
}

Expected Response:

{
  "success": true,
  "message": "Feedback received!",
  "data": {
    "id": "uuid-string",
    "user_id": "uuid-string",
    "type_of": "bug",
    "message": "Description text",
    "created_at": "2025-01-01T12:00:00"
  }
}

GET {fetchFeedbacksUrl}?page=1&limit=20

Used by: FeedbackPageComponent

Returns paginated list of feedback items. The component automatically appends page and limit query parameters to the fetchFeedbacksUrl prop. Additional query parameters (like user_id) can be included directly in the fetchFeedbacksUrl string.

Response:

{
  "success": true,
  "data": {
    "items": [
      {
        "id": "uuid-1",
        "user_id": "user-uuid",
        "type_of": "bug",
        "message": "The button doesn't work",
        "image": "https://storage.example.com/feedback/screenshot.png",
        "drawings": {
          "lines": [...],
          "width": 1920,
          "height": 1080
        },
        "current_url": "https://example.com/page",
        "material_ui_screensize": "desktop",
        "softwarefast_task_id": "TASK-123",  // Optional: external task tracking ID
        "created_at": "2025-10-07T10:00:00Z",
        "last_updated": "2025-10-07T10:30:00Z"
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 20,
      "total_count": 50,
      "total_pages": 3,
      "has_next": true,
      "has_prev": false
    }
  }
}

Feedback Item Schema:

{
  id: string;                        // Unique feedback identifier
  user_id: string | null;            // User who submitted (can be null)
  type_of: "bug" | "feature" | "other";
  message: string;                   // Feedback description
  image: string | null;              // URL to stored screenshot (not base64)
  drawings: {                        // Drawing annotation data
    lines: Array<{
      points: Array<{x: number, y: number}>;
      brushColor: string;
      brushRadius: number;
    }>;
    width: number;
    height: number;
  } | null;
  current_url: string | null;       // URL where feedback was submitted
  material_ui_screensize: "mobile" | "tablet" | "desktop" | null;
  softwarefast_task_id: string | null;  // Optional external tracking
  created_at: string;                // ISO 8601 timestamp
  last_updated: string;              // ISO 8601 timestamp
}

GET {editingUrl}/{feedbackId}

Used by: FeedbackEditPageComponent

Returns a single feedback item for editing. The component automatically constructs the URL by appending the feedbackId to the editingUrl prop.

Example Request: GET https://api.example.com/api/v1/feedback/uuid-1

Response:

{
  "success": true,
  "data": {
    "id": "uuid-1",
    "user_id": "user-uuid",
    "type_of": "bug",
    "message": "The button doesn't work",
    "image": "https://storage.example.com/feedback/screenshot.png",
    "drawings": {
      "lines": [...],
      "width": 1920,
      "height": 1080
    },
    "current_url": "https://example.com/page",
    "material_ui_screensize": "desktop",
    "softwarefast_task_id": null,
    "created_at": "2025-10-07T10:00:00Z",
    "last_updated": "2025-10-07T10:00:00Z"
  }
}

PUT {editingUrl}/{feedbackId}

Used by: DesktopEditFeedbackComponent, MobileEditFeedbackComponent

Updates an existing feedback item. The component automatically constructs the URL by appending feedback.id to the editingUrl prop.

Example Request: PUT https://api.example.com/api/v1/feedback/uuid-1

Request Body:

{
  "feedbackType": "bug",
  "description": "Updated description with more details",
  "image": "data:image/png;base64,iVBORw0KGg...",  // Can be base64 data URL or existing URL
  "drawings": {
    "lines": [
      {
        "points": [{"x": 100, "y": 150}],
        "brushColor": "#ff0000",
        "brushRadius": 2
      }
    ],
    "width": 1920,
    "height": 1080
  }
}

Expected Response:

{
  "success": true,
  "data": {
    "id": "uuid-1",
    "message": "Feedback updated successfully"
  }
}

Storage Recommendations

Screenshot Storage:

  • When receiving image field from MaterialFeedbackButton (base64 data URL), decode and store the image in your cloud storage (S3, Cloud Storage, etc.)
  • Store the public URL in the image field of your database
  • Return the URL (not base64) in GET responses to reduce payload size
  • When updating feedback, accept either base64 data URLs or existing URLs in the image field

Drawings Storage:

  • Store the drawings object as JSON in your database
  • This allows the edit components to reload and continue editing annotations
  • The drawings data is relatively small (~1-5KB per feedback item)

Features

  • 📸 Automatic screenshot capture
  • ✏️ Drawing annotations on screenshots
  • 📱 Responsive design with device detection
  • 🎨 Material UI themed
  • 🔄 Manual upload fallback
  • 🌐 Customizable API endpoint
  • 📋 Complete feedback management UI
  • 🔐 Flexible authentication header support
  • 📄 Pagination support
  • 🖼️ Screenshot preview and editing

License

MIT