# 💬 Universal Feedback Widget

A lightweight, framework-agnostic feedback widget that automatically creates **GitHub discussions** or **GitHub issues**. Works with **any** website - React, Vue, Svelte, plain HTML, or any other framework.

![Feedback Widget Demo](https://img.shields.io/badge/demo-live-brightgreen) ![NPM Version](https://img.shields.io/npm/v/@samletnorge/feedback-widget) ![Bundle Size](https://img.shields.io/bundlephobia/minzip/@samletnorge/feedback-widget)

## ✨ Features

- 🎯 **Zero Dependencies** - Works without any external libraries
- 🌐 **Universal Compatibility** - Works with any framework or vanilla HTML
- 💬 **GitHub Discussions** - Creates threaded discussions (recommended)
- 🐛 **GitHub Issues** - Creates trackable issues for bugs/features
- 🎨 **Flexible Theming** - Inherit page styles, Tailwind CSS, or custom colors
- 📱 **Responsive Design** - Mobile-friendly with elegant vertical tab
- ⚡ **Lightweight** - Under 20KB minified and gzipped
- 🔧 **Custom Triggers** - Use your own buttons or the default tab
- 📝 **Custom Forms** - Bring your own form design
- 🚀 **Programmatic API** - Trigger modals via JavaScript
- ⌨️ **Accessibility Ready** - Full keyboard navigation and ARIA support

## 🚀 Quick Start

### Option 1: CDN (Recommended)

Add these **two lines** to your HTML and you're done:

```html
<!-- Include the widget -->
<script src="https://cdn.jsdelivr.net/npm/@samletnorge/feedback-widget@latest/dist/feedback-widget.min.js"></script>

<!-- GitHub Discussions (Recommended) -->
<feedback-widget 
    data-repo="your-username/your-repo" 
    data-token="your-github-token"
    data-type="discussion"
    data-category="feedback"
>
</feedback-widget>
```

### Option 2: NPM Install

```bash
npm install @samletnorge/feedback-widget
```

```html
<script src="node_modules/@samletnorge/feedback-widget/dist/feedback-widget.min.js"></script>
<feedback-widget data-repo="user/repo" data-token="token"></feedback-widget>
```

## 💭 Discussions vs Issues

Choose the right GitHub feature for your feedback collection:

| Feature | GitHub Discussions | GitHub Issues |
|---------|-------------------|---------------|
| **Best for** | General feedback, questions, community input | Bug reports, feature requests, actionable items |
| **Structure** | Threaded conversations, organized by category | Linear comments, organized by labels |
| **Workflow** | Community-driven discussions | Project management, assignees, milestones |
| **Configuration** | `data-type="discussion"`<br>`data-category="feedback"` | `data-type="issue"`<br>`data-labels="feedback,bug"` |

## ⚙️ Configuration Reference

### Core Options
| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| `data-repo` | String | Required | GitHub repository (owner/repo) |
| `data-token` | String | Required | GitHub Personal Access Token |
| `data-type` | String | `"discussion"` | "discussion" or "issue" |
| `data-category` | String | `"feedback"` | Discussion category name |
| `data-labels` | String | `"feedback"` | Comma-separated issue labels |
| `data-title` | String | `"Feedback"` | Modal title |

### Appearance & Behavior
| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| `data-position` | String | `"right"` | "right", "left", "bottom-right", "bottom-left" |
| `data-inherit-styling` | Boolean | `false` | Use page CSS instead of inline styles |
| `data-custom-trigger` | String | `null` | CSS selector for custom trigger button |
| `data-custom-form` | String | `null` | CSS selector for custom form |
| `data-theme` | String | `"light"` | "light" or "dark" |

### Theming Options
| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| `data-primary-color` | String | `"#007bff"` | Primary color (hex/rgb) |
| `data-background-color` | String | `"#ffffff"` | Modal background color |
| `data-text-color` | String | `"#333333"` | Text color |
| `data-border-color` | String | `"#dddddd"` | Border color |
| `data-border-radius` | String | `"8px"` | Border radius |
| `data-font-family` | String | `"system-ui, -apple-system, sans-serif"` | Font family |

### CSS Class Support
| Attribute | Type | Description |
|-----------|------|-------------|
| `data-primary-color-class` | String | CSS class for primary color (e.g., "bg-blue-600") |
| `data-background-color-class` | String | CSS class for background color |
| `data-text-color-class` | String | CSS class for text color |
| `data-border-color-class` | String | CSS class for border color |

## 🎨 Theming & Customization

### 1. Inherit Page Styles (Recommended)

Perfect integration with your existing design:

```html
<!-- Enable inherit styling -->
<feedback-widget 
    data-repo="myuser/myrepo"
    data-token="token"
    data-inherit-styling="true"
>
</feedback-widget>
```

Then add CSS to style the widget components:

```css
/* Regular CSS */
.feedback-widget-modal {
  background: white;
  border: 1px solid #e5e7eb;
  border-radius: 0.5rem;
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
  padding: 1.5rem;
}

.feedback-widget-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1.5rem;
}

.feedback-widget-title {
  font-size: 1.25rem;
  font-weight: 600;
  color: #111827;
  margin: 0;
}

.feedback-widget-input,
.feedback-widget-textarea {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid #d1d5db;
  border-radius: 0.375rem;
  background: white;
}

.feedback-widget-btn-primary {
  background: #3b82f6;
  color: white;
  border: none;
  padding: 0.5rem 1rem;
  border-radius: 0.375rem;
  cursor: pointer;
}
```

### 2. Tailwind CSS Classes

```html
<feedback-widget 
    data-primary-color-class="bg-blue-600"
    data-background-color-class="bg-white dark:bg-gray-900"
    data-text-color-class="text-gray-900 dark:text-white"
    data-border-color-class="border-gray-200 dark:border-gray-700"
>
</feedback-widget>
```

For Svelte/Vue with Tailwind:

```css
<style>
/* Tailwind CSS classes */
:global(.feedback-widget-modal) {
  @apply bg-white border border-gray-200 rounded-lg shadow-lg p-6;
}

:global(.feedback-widget-header) {
  @apply flex justify-between items-center mb-6;
}

:global(.feedback-widget-title) {
  @apply text-xl font-semibold text-gray-900 m-0;
}

:global(.feedback-widget-input),
:global(.feedback-widget-textarea) {
  @apply w-full p-3 border border-gray-300 rounded-md bg-white text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500;
}

:global(.feedback-widget-btn-primary) {
  @apply bg-blue-600 text-white hover:bg-blue-700;
}

:global(.feedback-widget-btn-secondary) {
  @apply bg-gray-100 text-gray-700 hover:bg-gray-200;
}
</style>
```

### 3. Custom Colors & Positioning

```html
<feedback-widget 
    data-primary-color="#8B5CF6"
    data-background-color="#F8FAFC"
    data-text-color="#1E293B"
    data-border-radius="12px"
    data-position="left"
    data-theme="dark"
>
</feedback-widget>
```

### Available CSS Classes for Inherit Styling

| CSS Class | Element | Description |
|-----------|---------|-------------|
| `.feedback-widget-modal` | Modal container | Main modal wrapper |
| `.feedback-widget-header` | Header section | Contains title and close button |
| `.feedback-widget-title` | Modal title | H2 heading element |
| `.feedback-widget-close-btn` | Close button | X button in header |
| `.feedback-widget-form` | Form element | Main form container |
| `.feedback-widget-field` | Field wrapper | Contains label + input |
| `.feedback-widget-label` | Form labels | Label elements |
| `.feedback-widget-input` | Text inputs | Subject and email inputs |
| `.feedback-widget-textarea` | Textarea | Feedback message field |
| `.feedback-widget-actions` | Button container | Cancel and submit buttons |
| `.feedback-widget-btn` | Base button | Base class for all buttons |
| `.feedback-widget-btn-primary` | Submit button | Primary action button |
| `.feedback-widget-btn-secondary` | Cancel button | Secondary action button |

## 🎯 Custom Triggers & Forms

### Custom Trigger Button

Use your own styled button instead of the default vertical tab:

```html
<button id="my-feedback-btn">Give Feedback</button>

<feedback-widget 
    data-repo="myuser/myrepo"
    data-token="token"
    data-custom-trigger="#my-feedback-btn"
>
</feedback-widget>
```

### Programmatic API

```javascript
// Simple helper function
window.openFeedbackWidget();

// Or dispatch custom event
document.dispatchEvent(new Event('feedback-widget-open'));

// Access widget directly
const widget = document.querySelector('feedback-widget')._feedbackWidgetInstance;
widget.openModal();
```

### Custom Form

Provide your own completely custom form:

```html
<div id="custom-form" style="display: none;">
    <form>
        <input name="subject" placeholder="What's this about?" required>
        <textarea name="feedback" placeholder="Tell us more..." required></textarea>
        <input name="email" placeholder="Email (optional)">
        <button type="submit">Send</button>
    </form>
</div>

<feedback-widget 
    data-custom-form="#custom-form"
    data-repo="myuser/myrepo"
    data-token="token"
>
</feedback-widget>
```

## 🔑 GitHub Token Setup

### For GitHub Discussions

1. Go to [GitHub Settings > Developer settings > Personal access tokens](https://github.com/settings/tokens)
2. Click **"Generate new token (classic)"**
3. Give it a name like "Feedback Widget"
4. Select scopes:
   - **`write:discussion`** (required for discussions) !! NB: This is why this is recommended. It only allows writing discussions, not whole repo
5. Click **"Generate token"**
6. Copy the token (starts with `github_pat_` or `ghp_`)

### For GitHub Issues

Same steps as above, but you only need:
- **`public_repo`** (for public repositories) or **`repo`** (for private repos) 
for these i would recomend using a github finetuned token with only the `issues` scope. but you need to be admin for that.

**⚠️ Security Note:** Never expose your token in client-side code for production. Consider using a serverless function or GitHub App for production deployments. THIS IS ALSO WHY I RECOMMEND USING TOKEN WITH ONLY `write:discussion` SCOPE or `issues` SCOPE.

## 🌟 Framework Examples

### React/Next.js

```jsx
import { useEffect } from 'react';

function App() {
  useEffect(() => {
    const script = document.createElement('script');
    script.src = 'https://cdn.jsdelivr.net/npm/@samletnorge/feedback-widget@latest/dist/feedback-widget.min.js';
    document.head.appendChild(script);
  }, []);

  return (
    <div>
      <h1>My React App</h1>
      <feedback-widget 
        data-repo="myuser/myrepo"
        data-token={process.env.REACT_APP_GITHUB_TOKEN}
        data-type="discussion"
        data-inherit-styling="true"
      />
    </div>
  );
}
```

### Vue.js

```vue
<template>
  <div>
    <h1>My Vue App</h1>
    <feedback-widget 
      :data-repo="repo"
      :data-token="token"
      data-type="discussion"
      data-category="general"
      data-position="left"
    />
  </div>
</template>

<script>
export default {
  data() {
    return {
      repo: "myuser/myrepo",
      token: process.env.VUE_APP_GITHUB_TOKEN
    }
  },
  mounted() {
    const script = document.createElement('script');
    script.src = 'https://cdn.jsdelivr.net/npm/@samletnorge/feedback-widget@latest/dist/feedback-widget.min.js';
    document.head.appendChild(script);
  }
}
</script>
```

### Svelte/SvelteKit

```svelte
<script>
  import { onMount } from 'svelte';
  
  onMount(() => {
    const script = document.createElement('script');
    script.src = 'https://cdn.jsdelivr.net/npm/@samletnorge/feedback-widget@latest/dist/feedback-widget.min.js';
    document.head.appendChild(script);
  });
</script>

<!-- CSS for custom styling -->
<style>
  :global(.feedback-widget-modal) {
    @apply bg-background border border-border rounded-lg;
  }
</style>

<h1>My Svelte App</h1>
<feedback-widget 
  data-repo="myuser/myrepo"
  data-token="token"
  data-primary-color-class="bg-primary"
  data-background-color-class="bg-background"
  data-inherit-styling="true"
/>
```

### Angular

```typescript
// app.component.ts
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <feedback-widget 
      [attr.data-repo]="repo"
      [attr.data-token]="token"
      data-type="discussion"
      data-inherit-styling="true">
    </feedback-widget>
  `
})
export class AppComponent implements OnInit {
  repo = 'myuser/myrepo';
  token = environment.githubToken;

  ngOnInit() {
    const script = document.createElement('script');
    script.src = 'https://cdn.jsdelivr.net/npm/@samletnorge/feedback-widget@latest/dist/feedback-widget.min.js';
    document.head.appendChild(script);
  }
}
```

### Plain HTML

```html
<!DOCTYPE html>
<html>
<head>
    <title>My Website</title>
</head>
<body>
    <h1>Welcome to my website!</h1>
    
    <script src="https://cdn.jsdelivr.net/npm/@samletnorge/feedback-widget@latest/dist/feedback-widget.min.js"></script>
    <feedback-widget 
        data-repo="myuser/myrepo"
        data-token="github_pat_xxxxx"
        data-type="discussion"
        data-category="feedback"
    ></feedback-widget>
</body>
</html>
```

## 📊 Generated Content

### GitHub Discussions

Discussions are created with this format:

```markdown
**Subject:** Great new feature idea

Hi! I love the new design, but I think it could use more contrast on the buttons for better accessibility.

---
Contact: user@example.com

---
Submitted via feedback widget
Page: https://mywebsite.com/dashboard
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)...
```

### GitHub Issues

Issues are created with this format:

```markdown
**Subject:** Bug report - Login not working

The login form doesn't submit when I click the button. I've tried multiple browsers.

---
Contact: user@example.com

---
Submitted via feedback widget
Page: https://mywebsite.com/login
User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)...
```

## 🎯 Event Handling

Listen for feedback submissions:

```javascript
document.addEventListener('feedback-submitted', (event) => {
    console.log('Feedback received:', event.detail);
    // { subject: "Bug report", feedback: "Login broken", email: "user@example.com" }
    
    // Send to your analytics
    gtag('event', 'feedback_submitted', {
        'feedback_type': event.detail.subject ? 'detailed' : 'simple',
        'has_email': !!event.detail.email
    });
});

document.addEventListener('feedback-error', (event) => {
    console.error('Feedback submission failed:', event.detail.error);
});

document.addEventListener('feedback-success', (event) => {
    console.log('Feedback submitted successfully:', event.detail);
});
```

## 🛠️ Development

### Local Development

```bash
git clone https://github.com/samletnorge/feedback-widget.git
cd feedback-widget
pnpm install
pnpm run dev
```

Visit `http://localhost:8080` to see the demo.

### Build

```bash
pnpm run build  # Creates dist/feedback-widget.min.js
```

## 🤝 Contributing

1. Fork the repository
2. Create a feature branch: `git checkout -b feature/amazing-feature`
3. Add a changelog when you do something significant (using changesets): `npx changeset`
   - Follow the prompts to describe your changes
   - This will create a new file in the `.changeset` directory
   - You can add multiple changesets before committing
   - Include the `.changeset/` files in your commit
4. Commit changes: `git commit -m 'Add amazing feature'`
5. Push to branch: `git push origin feature/amazing-feature`
6. Open a Pull Request
7. Wait for review and address any feedback
8. Once approved, your changes will be merged and included in the next release!

## 📝 License

MIT License - see [LICENSE](LICENSE) file for details.

## 🆘 Support

- **Demo**: [Live Demo](https://feedback-widget.samletnorge.com)
- **Documentation**: [GitHub Wiki](https://github.com/samletnorge/feedback-widget/wiki)
- **Issues**: [GitHub Issues](https://github.com/samletnorge/feedback-widget/issues)
- **Discussions**: [GitHub Discussions](https://github.com/samletnorge/feedback-widget/discussions)

## 🌟 Show Your Support

If this widget helped your project, please give it a ⭐ on GitHub!

---

**Made with ❤️ for the web community**