react-visual-feedback 1.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +292 -181
- package/dist/index.css +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +3 -3
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# React Visual Feedback
|
|
2
2
|
|
|
3
|
-
A powerful, visual feedback collection tool for React applications. Users can select any element on your page, and the widget automatically captures a screenshot and context information.
|
|
3
|
+
A powerful, visual feedback collection tool for React applications with an integrated dashboard for managing user feedback. Users can select any element on your page, and the widget automatically captures a screenshot and context information.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
+
### Feedback Collection
|
|
7
8
|
- 🎯 Visual element selection with hover highlighting
|
|
8
9
|
- 📸 Automatic screenshot capture of selected elements with perfect CSS rendering
|
|
9
10
|
- 📝 Feedback form with rich context
|
|
@@ -12,6 +13,16 @@ A powerful, visual feedback collection tool for React applications. Users can se
|
|
|
12
13
|
- ⌨️ Keyboard shortcuts (Ctrl+Q to activate, Esc to cancel, Ctrl+Enter to submit)
|
|
13
14
|
- 🌓 Dark mode support
|
|
14
15
|
|
|
16
|
+
### Feedback Dashboard (New!)
|
|
17
|
+
- 📊 **Professional dashboard** with localStorage or custom data source
|
|
18
|
+
- 👨💻 **Developer mode** with full technical details
|
|
19
|
+
- 👤 **User mode** for simplified feedback view
|
|
20
|
+
- 🏷️ **Status management** with 7 professional status options
|
|
21
|
+
- ✅ **Custom dropdown** with smooth animations
|
|
22
|
+
- 🔒 **Permission system** - Users can only view, developers can manage
|
|
23
|
+
- 🔄 **Status change callbacks** for database synchronization
|
|
24
|
+
- ⌨️ Dashboard keyboard shortcut (Ctrl+Shift+Q)
|
|
25
|
+
|
|
15
26
|
## Installation
|
|
16
27
|
|
|
17
28
|
```bash
|
|
@@ -26,7 +37,7 @@ import 'react-visual-feedback/dist/index.css';
|
|
|
26
37
|
|
|
27
38
|
## Quick Start
|
|
28
39
|
|
|
29
|
-
###
|
|
40
|
+
### Basic Usage (Feedback Only)
|
|
30
41
|
|
|
31
42
|
```jsx
|
|
32
43
|
import React from 'react';
|
|
@@ -54,293 +65,393 @@ function App() {
|
|
|
54
65
|
export default App;
|
|
55
66
|
```
|
|
56
67
|
|
|
57
|
-
###
|
|
68
|
+
### With Dashboard (Full Feature Set)
|
|
58
69
|
|
|
59
70
|
```jsx
|
|
60
|
-
import
|
|
71
|
+
import React from 'react';
|
|
72
|
+
import { FeedbackProvider, useFeedback } from 'react-visual-feedback';
|
|
73
|
+
import 'react-visual-feedback/dist/index.css';
|
|
61
74
|
|
|
62
|
-
function
|
|
63
|
-
const { setIsActive } = useFeedback();
|
|
75
|
+
function FeedbackButtons() {
|
|
76
|
+
const { isActive, setIsActive, setIsDashboardOpen } = useFeedback();
|
|
64
77
|
|
|
65
78
|
return (
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
79
|
+
<div style={{ position: 'fixed', bottom: 20, right: 20, display: 'flex', gap: 10 }}>
|
|
80
|
+
<button onClick={() => setIsDashboardOpen(true)}>
|
|
81
|
+
📊 Dashboard
|
|
82
|
+
</button>
|
|
83
|
+
<button onClick={() => setIsActive(!isActive)}>
|
|
84
|
+
{isActive ? '✕ Cancel' : '💬 Report Issue'}
|
|
85
|
+
</button>
|
|
86
|
+
</div>
|
|
69
87
|
);
|
|
70
88
|
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Usage
|
|
74
|
-
|
|
75
|
-
### Keyboard Shortcuts
|
|
76
|
-
- **Ctrl+Q** - Activate feedback mode
|
|
77
|
-
- **Esc** - Cancel/deactivate
|
|
78
|
-
- **Ctrl+Enter** - Submit feedback (when form is open)
|
|
79
89
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
90
|
+
function App() {
|
|
91
|
+
const handleFeedbackSubmit = async (feedbackData) => {
|
|
92
|
+
console.log('Feedback received:', feedbackData);
|
|
93
|
+
await fetch('/api/feedback', {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: { 'Content-Type': 'application/json' },
|
|
96
|
+
body: JSON.stringify(feedbackData)
|
|
97
|
+
});
|
|
98
|
+
};
|
|
85
99
|
|
|
86
|
-
|
|
87
|
-
|
|
100
|
+
const handleStatusChange = async ({ id, status }) => {
|
|
101
|
+
console.log('Status changed:', { id, status });
|
|
102
|
+
// Update your database
|
|
103
|
+
await fetch(`/api/feedback/${id}/status`, {
|
|
104
|
+
method: 'PATCH',
|
|
105
|
+
headers: { 'Content-Type': 'application/json' },
|
|
106
|
+
body: JSON.stringify({ status })
|
|
107
|
+
});
|
|
108
|
+
};
|
|
88
109
|
|
|
89
110
|
return (
|
|
90
|
-
<
|
|
91
|
-
{
|
|
92
|
-
|
|
111
|
+
<FeedbackProvider
|
|
112
|
+
onSubmit={handleFeedbackSubmit}
|
|
113
|
+
onStatusChange={handleStatusChange}
|
|
114
|
+
dashboard={true}
|
|
115
|
+
isDeveloper={true} // Set to false for user mode
|
|
116
|
+
userName="John Doe"
|
|
117
|
+
userEmail="john@example.com"
|
|
118
|
+
>
|
|
119
|
+
<YourApp />
|
|
120
|
+
<FeedbackButtons />
|
|
121
|
+
</FeedbackProvider>
|
|
93
122
|
);
|
|
94
123
|
}
|
|
124
|
+
|
|
125
|
+
export default App;
|
|
95
126
|
```
|
|
96
127
|
|
|
97
|
-
|
|
98
|
-
You can control the widget's active state from the parent component:
|
|
128
|
+
## Dashboard Features
|
|
99
129
|
|
|
100
|
-
|
|
101
|
-
import React, { useState } from 'react';
|
|
102
|
-
import { FeedbackProvider } from 'react-visual-feedback';
|
|
130
|
+
### Status Options
|
|
103
131
|
|
|
104
|
-
|
|
105
|
-
const [isFeedbackActive, setIsFeedbackActive] = useState(false);
|
|
132
|
+
The dashboard includes 7 professional status options:
|
|
106
133
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
134
|
+
| Status | Color | Description |
|
|
135
|
+
|--------|-------|-------------|
|
|
136
|
+
| **Reported** 🔴 | Red | Initial feedback submission |
|
|
137
|
+
| **Opened** 🟠 | Amber | Acknowledged and under review |
|
|
138
|
+
| **Doing it** 🔵 | Blue | Actively being worked on |
|
|
139
|
+
| **Resolved** 🟢 | Green | Fixed and ready |
|
|
140
|
+
| **Released** 🟣 | Purple | Deployed to production |
|
|
141
|
+
| **Blocked** 🔴 | Red | Waiting on dependencies |
|
|
142
|
+
| **Won't do** ⚪ | Gray | Not planned for implementation |
|
|
111
143
|
|
|
112
|
-
|
|
113
|
-
<div>
|
|
114
|
-
<button onClick={() => setIsFeedbackActive(!isFeedbackActive)}>
|
|
115
|
-
{isFeedbackActive ? 'Cancel' : 'Report Bug'}
|
|
116
|
-
</button>
|
|
144
|
+
### Developer vs User Mode
|
|
117
145
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
146
|
+
#### Developer Mode (`isDeveloper={true}`)
|
|
147
|
+
- ✅ View all technical details (element info, CSS, viewport, user agent)
|
|
148
|
+
- ✅ Change feedback status
|
|
149
|
+
- ✅ Delete feedback items
|
|
150
|
+
- ✅ Full control over feedback management
|
|
151
|
+
|
|
152
|
+
#### User Mode (`isDeveloper={false}`)
|
|
153
|
+
- ✅ View feedback submissions
|
|
154
|
+
- ✅ See current status (read-only)
|
|
155
|
+
- ❌ Cannot change status
|
|
156
|
+
- ❌ Cannot delete items
|
|
157
|
+
- Perfect for product managers and stakeholders
|
|
158
|
+
|
|
159
|
+
### Data Persistence
|
|
160
|
+
|
|
161
|
+
The dashboard supports two modes:
|
|
162
|
+
|
|
163
|
+
1. **localStorage** (default) - Automatic persistence in browser
|
|
164
|
+
2. **Custom data source** - Pass your own data via `dashboardData` prop
|
|
165
|
+
|
|
166
|
+
```jsx
|
|
167
|
+
// Using localStorage (automatic)
|
|
168
|
+
<FeedbackProvider dashboard={true}>
|
|
169
|
+
|
|
170
|
+
// Using custom data source
|
|
171
|
+
<FeedbackProvider
|
|
172
|
+
dashboard={true}
|
|
173
|
+
dashboardData={yourFeedbackArray}
|
|
174
|
+
>
|
|
128
175
|
```
|
|
129
176
|
|
|
130
177
|
## API Reference
|
|
131
178
|
|
|
132
|
-
### FeedbackProvider
|
|
179
|
+
### FeedbackProvider Props
|
|
180
|
+
|
|
181
|
+
| Prop | Type | Required | Default | Description |
|
|
182
|
+
|------|------|----------|---------|-------------|
|
|
183
|
+
| `onSubmit` | `(feedbackData) => Promise<void>` | Yes | - | Callback when feedback is submitted |
|
|
184
|
+
| `onStatusChange` | `({ id, status }) => void` | No | - | Callback when status changes |
|
|
185
|
+
| `children` | `ReactNode` | Yes | - | Your app components |
|
|
186
|
+
| `dashboard` | `boolean` | No | `false` | Enable dashboard feature |
|
|
187
|
+
| `dashboardData` | `Array` | No | `undefined` | Custom feedback data (uses localStorage if undefined) |
|
|
188
|
+
| `isDeveloper` | `boolean` | No | `false` | Enable developer mode with full permissions |
|
|
189
|
+
| `isUser` | `boolean` | No | `true` | Enable user mode (read-only) |
|
|
190
|
+
| `userName` | `string` | No | `'Anonymous'` | User name for feedback submissions |
|
|
191
|
+
| `userEmail` | `string` | No | `null` | User email for feedback submissions |
|
|
192
|
+
| `isActive` | `boolean` | No | `undefined` | Control widget active state (controlled mode) |
|
|
193
|
+
| `onActiveChange` | `(active: boolean) => void` | No | - | Callback when active state changes |
|
|
194
|
+
|
|
195
|
+
### useFeedback Hook
|
|
196
|
+
|
|
197
|
+
```jsx
|
|
198
|
+
const { isActive, setIsActive, setIsDashboardOpen } = useFeedback();
|
|
199
|
+
```
|
|
133
200
|
|
|
134
|
-
|
|
201
|
+
**Returns:**
|
|
202
|
+
- `isActive`: `boolean` - Whether feedback mode is active
|
|
203
|
+
- `setIsActive`: `(active: boolean) => void` - Activate/deactivate feedback mode
|
|
204
|
+
- `setIsDashboardOpen`: `(open: boolean) => void` - Open/close dashboard
|
|
135
205
|
|
|
136
|
-
|
|
137
|
-
- `onSubmit` (required): `(feedbackData) => Promise<void>` - Callback function when feedback is submitted
|
|
138
|
-
- `children`: React nodes
|
|
139
|
-
- `isActive` (optional): `boolean` - Control the widget active state from parent (controlled mode)
|
|
140
|
-
- `onActiveChange` (optional): `(active: boolean) => void` - Callback when active state changes (used with controlled mode)
|
|
206
|
+
### Feedback Data Structure
|
|
141
207
|
|
|
142
|
-
|
|
143
|
-
```javascript
|
|
208
|
+
```typescript
|
|
144
209
|
{
|
|
145
|
-
|
|
210
|
+
id: string,
|
|
211
|
+
feedback: string,
|
|
212
|
+
userName: string,
|
|
213
|
+
userEmail: string | null,
|
|
214
|
+
status: 'reported' | 'opened' | 'doingIt' | 'resolved' | 'released' | 'blocked' | 'wontDo',
|
|
215
|
+
timestamp: string, // ISO 8601 format
|
|
216
|
+
url: string,
|
|
146
217
|
elementInfo: {
|
|
147
|
-
tagName:
|
|
148
|
-
id:
|
|
149
|
-
className:
|
|
150
|
-
selector:
|
|
151
|
-
|
|
218
|
+
tagName: string,
|
|
219
|
+
id: string,
|
|
220
|
+
className: string,
|
|
221
|
+
selector: string,
|
|
222
|
+
text: string,
|
|
152
223
|
position: {
|
|
153
|
-
x:
|
|
154
|
-
y:
|
|
155
|
-
width:
|
|
156
|
-
height:
|
|
224
|
+
x: number,
|
|
225
|
+
y: number,
|
|
226
|
+
width: number,
|
|
227
|
+
height: number
|
|
157
228
|
},
|
|
158
229
|
styles: {
|
|
159
|
-
backgroundColor:
|
|
160
|
-
color:
|
|
161
|
-
fontSize:
|
|
162
|
-
fontFamily:
|
|
230
|
+
backgroundColor: string,
|
|
231
|
+
color: string,
|
|
232
|
+
fontSize: string,
|
|
233
|
+
fontFamily: string
|
|
163
234
|
}
|
|
164
235
|
},
|
|
165
|
-
screenshot:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
236
|
+
screenshot: string, // Base64 encoded PNG
|
|
237
|
+
viewport: {
|
|
238
|
+
width: number,
|
|
239
|
+
height: number
|
|
240
|
+
},
|
|
241
|
+
userAgent: string
|
|
169
242
|
}
|
|
170
243
|
```
|
|
171
244
|
|
|
172
|
-
|
|
245
|
+
## Keyboard Shortcuts
|
|
173
246
|
|
|
174
|
-
|
|
247
|
+
| Shortcut | Action |
|
|
248
|
+
|----------|--------|
|
|
249
|
+
| `Ctrl+Q` | Activate feedback mode |
|
|
250
|
+
| `Ctrl+Shift+Q` | Open dashboard (when dashboard is enabled) |
|
|
251
|
+
| `Esc` | Cancel/close feedback mode or dashboard |
|
|
252
|
+
| `Ctrl+Enter` | Submit feedback (when form is open) |
|
|
175
253
|
|
|
176
|
-
|
|
177
|
-
- `isActive`: boolean - Whether the widget is currently active
|
|
178
|
-
- `setIsActive`: (active: boolean) => void - Function to activate/deactivate the widget
|
|
254
|
+
## Usage Examples
|
|
179
255
|
|
|
180
|
-
|
|
256
|
+
### Example 1: Basic Feedback Collection
|
|
257
|
+
|
|
258
|
+
```jsx
|
|
259
|
+
import { FeedbackProvider, useFeedback } from 'react-visual-feedback';
|
|
260
|
+
import 'react-visual-feedback/dist/index.css';
|
|
181
261
|
|
|
182
|
-
|
|
262
|
+
function FeedbackButton() {
|
|
263
|
+
const { setIsActive } = useFeedback();
|
|
264
|
+
return <button onClick={() => setIsActive(true)}>Report Issue</button>;
|
|
265
|
+
}
|
|
183
266
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
.
|
|
190
|
-
|
|
191
|
-
|
|
267
|
+
function App() {
|
|
268
|
+
return (
|
|
269
|
+
<FeedbackProvider onSubmit={async (data) => {
|
|
270
|
+
await fetch('/api/feedback', {
|
|
271
|
+
method: 'POST',
|
|
272
|
+
body: JSON.stringify(data)
|
|
273
|
+
});
|
|
274
|
+
}}>
|
|
275
|
+
<YourApp />
|
|
276
|
+
<FeedbackButton />
|
|
277
|
+
</FeedbackProvider>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
192
280
|
```
|
|
193
281
|
|
|
194
|
-
|
|
282
|
+
### Example 2: Full Dashboard with Status Management
|
|
195
283
|
|
|
196
284
|
```jsx
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
import 'react-visual-feedback/dist/index.css';
|
|
285
|
+
function App() {
|
|
286
|
+
const [isDeveloper, setIsDeveloper] = useState(true);
|
|
200
287
|
|
|
201
|
-
|
|
202
|
-
|
|
288
|
+
const handleStatusChange = async ({ id, status }) => {
|
|
289
|
+
// Update database
|
|
290
|
+
await fetch(`/api/feedback/${id}`, {
|
|
291
|
+
method: 'PATCH',
|
|
292
|
+
body: JSON.stringify({ status })
|
|
293
|
+
});
|
|
294
|
+
};
|
|
203
295
|
|
|
204
296
|
return (
|
|
205
|
-
<
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
background: isActive ? '#ef4444' : '#3b82f6',
|
|
213
|
-
color: 'white',
|
|
214
|
-
border: 'none',
|
|
215
|
-
borderRadius: '8px',
|
|
216
|
-
cursor: 'pointer',
|
|
217
|
-
fontWeight: 'bold',
|
|
218
|
-
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)'
|
|
219
|
-
}}
|
|
297
|
+
<FeedbackProvider
|
|
298
|
+
dashboard={true}
|
|
299
|
+
isDeveloper={isDeveloper}
|
|
300
|
+
onSubmit={handleFeedbackSubmit}
|
|
301
|
+
onStatusChange={handleStatusChange}
|
|
302
|
+
userName="Jane Smith"
|
|
303
|
+
userEmail="jane@company.com"
|
|
220
304
|
>
|
|
221
|
-
|
|
222
|
-
</
|
|
305
|
+
<YourApp />
|
|
306
|
+
</FeedbackProvider>
|
|
223
307
|
);
|
|
224
308
|
}
|
|
309
|
+
```
|
|
225
310
|
|
|
311
|
+
### Example 3: Custom Data Source
|
|
312
|
+
|
|
313
|
+
```jsx
|
|
226
314
|
function App() {
|
|
227
|
-
const
|
|
228
|
-
try {
|
|
229
|
-
const response = await fetch('https://your-api.com/feedback', {
|
|
230
|
-
method: 'POST',
|
|
231
|
-
headers: { 'Content-Type': 'application/json' },
|
|
232
|
-
body: JSON.stringify(feedbackData)
|
|
233
|
-
});
|
|
315
|
+
const [feedbackData, setFeedbackData] = useState([]);
|
|
234
316
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
};
|
|
317
|
+
useEffect(() => {
|
|
318
|
+
// Load feedback from your API
|
|
319
|
+
fetch('/api/feedback')
|
|
320
|
+
.then(res => res.json())
|
|
321
|
+
.then(setFeedbackData);
|
|
322
|
+
}, []);
|
|
242
323
|
|
|
243
324
|
return (
|
|
244
|
-
<FeedbackProvider
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
325
|
+
<FeedbackProvider
|
|
326
|
+
dashboard={true}
|
|
327
|
+
dashboardData={feedbackData}
|
|
328
|
+
isDeveloper={true}
|
|
329
|
+
onStatusChange={async ({ id, status }) => {
|
|
330
|
+
// Update API
|
|
331
|
+
await fetch(`/api/feedback/${id}`, {
|
|
332
|
+
method: 'PATCH',
|
|
333
|
+
body: JSON.stringify({ status })
|
|
334
|
+
});
|
|
335
|
+
// Reload data
|
|
336
|
+
const updated = await fetch('/api/feedback').then(r => r.json());
|
|
337
|
+
setFeedbackData(updated);
|
|
338
|
+
}}
|
|
339
|
+
>
|
|
340
|
+
<YourApp />
|
|
250
341
|
</FeedbackProvider>
|
|
251
342
|
);
|
|
252
343
|
}
|
|
344
|
+
```
|
|
253
345
|
|
|
254
|
-
|
|
346
|
+
## Styling
|
|
347
|
+
|
|
348
|
+
The widget comes with default styles, but you can customize them:
|
|
349
|
+
|
|
350
|
+
```css
|
|
351
|
+
/* Feedback Collection */
|
|
352
|
+
.feedback-overlay { /* Background overlay */ }
|
|
353
|
+
.feedback-highlight { /* Element highlight */ }
|
|
354
|
+
.feedback-tooltip { /* Element info tooltip */ }
|
|
355
|
+
.feedback-modal { /* Feedback form modal */ }
|
|
356
|
+
.feedback-backdrop { /* Modal backdrop */ }
|
|
357
|
+
|
|
358
|
+
/* Dashboard */
|
|
359
|
+
.feedback-dashboard { /* Dashboard container */ }
|
|
360
|
+
.feedback-dashboard-backdrop { /* Dashboard backdrop */ }
|
|
255
361
|
```
|
|
256
362
|
|
|
257
363
|
## How It Works
|
|
258
364
|
|
|
365
|
+
### Feedback Collection Flow
|
|
366
|
+
|
|
259
367
|
1. User activates the widget (Ctrl+Q or button click)
|
|
260
368
|
2. User hovers over elements to see them highlighted
|
|
261
369
|
3. User clicks on the problematic element
|
|
262
|
-
4. Widget captures a pixel-perfect screenshot
|
|
263
|
-
5. Feedback form appears with
|
|
264
|
-
6. User enters
|
|
265
|
-
7. Your `onSubmit` handler receives all
|
|
370
|
+
4. Widget captures a pixel-perfect screenshot
|
|
371
|
+
5. Feedback form appears with context pre-filled
|
|
372
|
+
6. User enters feedback and submits
|
|
373
|
+
7. Your `onSubmit` handler receives all data
|
|
374
|
+
|
|
375
|
+
### Dashboard Flow
|
|
376
|
+
|
|
377
|
+
1. User opens dashboard (Ctrl+Shift+Q or programmatically)
|
|
378
|
+
2. Dashboard displays all feedback submissions
|
|
379
|
+
3. Developer can change status via dropdown
|
|
380
|
+
4. Status change triggers `onStatusChange` callback
|
|
381
|
+
5. Your backend updates the database
|
|
382
|
+
6. Dashboard reflects the new status
|
|
266
383
|
|
|
267
384
|
## Browser Support
|
|
268
385
|
|
|
269
|
-
- Chrome/Edge
|
|
270
|
-
- Firefox
|
|
271
|
-
- Safari
|
|
272
|
-
- Opera
|
|
386
|
+
- ✅ Chrome/Edge
|
|
387
|
+
- ✅ Firefox
|
|
388
|
+
- ✅ Safari
|
|
389
|
+
- ✅ Opera
|
|
273
390
|
|
|
274
391
|
## Screenshot Capture
|
|
275
392
|
|
|
276
|
-
|
|
277
|
-
- Perfect CSS rendering
|
|
278
|
-
-
|
|
279
|
-
-
|
|
280
|
-
- High-resolution
|
|
393
|
+
Uses `html-to-image` with `html2canvas` fallback for:
|
|
394
|
+
- ✅ Perfect CSS rendering
|
|
395
|
+
- ✅ Tailwind, Bootstrap, Material-UI support
|
|
396
|
+
- ✅ Gradients, shadows, modern CSS
|
|
397
|
+
- ✅ High-resolution (2x pixel ratio)
|
|
281
398
|
|
|
282
399
|
## Dependencies
|
|
283
400
|
|
|
284
401
|
- React ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
285
402
|
- react-dom ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
286
403
|
- html-to-image ^1.11.13
|
|
287
|
-
- html2canvas ^1.4.1
|
|
404
|
+
- html2canvas ^1.4.1
|
|
288
405
|
- lucide-react ^0.263.1
|
|
289
406
|
|
|
290
|
-
## Local Development
|
|
291
|
-
|
|
292
|
-
Want to test the widget locally? We've included a complete example app!
|
|
293
|
-
|
|
294
|
-
### Quick Start
|
|
407
|
+
## Local Development
|
|
295
408
|
|
|
296
409
|
```bash
|
|
297
|
-
#
|
|
410
|
+
# Clone repository
|
|
298
411
|
git clone https://github.com/Murali1889/react-feedback-widget.git
|
|
299
412
|
cd react-feedback-widget
|
|
300
413
|
|
|
301
|
-
#
|
|
414
|
+
# Install dependencies
|
|
302
415
|
npm install
|
|
303
416
|
|
|
304
|
-
#
|
|
417
|
+
# Build the widget
|
|
305
418
|
npm run build
|
|
306
419
|
|
|
307
|
-
#
|
|
420
|
+
# Run example app
|
|
308
421
|
cd example
|
|
309
422
|
npm install
|
|
310
423
|
npm run dev
|
|
311
424
|
```
|
|
312
425
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
### What's Included
|
|
316
|
-
|
|
317
|
-
- ✅ Complete working example with Tailwind CSS
|
|
318
|
-
- ✅ Both light and dark mode support
|
|
319
|
-
- ✅ Controlled and uncontrolled mode examples
|
|
320
|
-
- ✅ Interactive test elements (buttons, forms, cards, gradients)
|
|
321
|
-
- ✅ Console logging to see feedback data
|
|
322
|
-
- ✅ Hot reload for fast development
|
|
426
|
+
Visit `http://localhost:8080` to see the demo!
|
|
323
427
|
|
|
324
|
-
|
|
428
|
+
## What's New in v1.2.0
|
|
325
429
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
430
|
+
### Dashboard Feature
|
|
431
|
+
- 📊 Complete feedback management dashboard
|
|
432
|
+
- 👨💻 Developer/User mode with permission system
|
|
433
|
+
- 🏷️ 7 professional status options with custom dropdown
|
|
434
|
+
- 🔄 Status change callbacks for database sync
|
|
435
|
+
- 💾 localStorage or custom data source support
|
|
436
|
+
- 📅 Improved timestamp formatting (e.g., "21 Oct 2025 at 9:35 PM")
|
|
329
437
|
|
|
330
|
-
|
|
438
|
+
### UI Improvements
|
|
439
|
+
- ✨ Smooth animations for status dropdown
|
|
440
|
+
- 🎨 Professional minimal design
|
|
441
|
+
- 🔒 Read-only status for users
|
|
442
|
+
- 🗑️ Conditional delete permissions
|
|
331
443
|
|
|
332
444
|
## License
|
|
333
445
|
|
|
334
|
-
MIT © 2025 Murali
|
|
446
|
+
MIT © 2025 Murali Vvrsn Gurajapu
|
|
335
447
|
|
|
336
448
|
## Contributing
|
|
337
449
|
|
|
338
|
-
Contributions
|
|
450
|
+
Contributions welcome! Please submit a Pull Request.
|
|
339
451
|
|
|
340
452
|
## Issues
|
|
341
453
|
|
|
342
|
-
|
|
343
|
-
https://github.com/Murali1889/react-feedback-widget/issues
|
|
454
|
+
Report issues at: https://github.com/Murali1889/react-feedback-widget/issues
|
|
344
455
|
|
|
345
456
|
## Author
|
|
346
457
|
|
package/dist/index.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.feedback-overlay{
|
|
1
|
+
.feedback-light{--feedback-overlay-bg:rgba(0,0,0,.03);--feedback-backdrop-bg:rgba(0,0,0,.6);--feedback-modal-bg:#fff;--feedback-modal-border:#e5e7eb;--feedback-text-primary:#111827;--feedback-text-secondary:#6b7280;--feedback-text-tertiary:#9ca3af;--feedback-border:#d1d5db;--feedback-border-focus:#3b82f6;--feedback-input-bg:#fff;--feedback-input-disabled-bg:#f9fafb;--feedback-btn-cancel-bg:#f3f4f6;--feedback-btn-cancel-hover:#e5e7eb;--feedback-btn-cancel-text:#374151;--feedback-btn-primary-bg:#3b82f6;--feedback-btn-primary-hover:#2563eb;--feedback-btn-primary-text:#fff;--feedback-btn-disabled-bg:#9ca3af;--feedback-highlight-border:#3b82f6;--feedback-highlight-bg:rgba(59,130,246,.1);--feedback-highlight-shadow:rgba(59,130,246,.25);--feedback-tooltip-bg:#1f2937;--feedback-tooltip-text:#fff;--feedback-error-bg:#fef2f2;--feedback-error-border:#fca5a5;--feedback-error-text:#dc2626;--feedback-screenshot-border:#e5e7eb;--feedback-screenshot-bg:#f9fafb;--feedback-shadow:rgba(0,0,0,.1);--feedback-close-hover-bg:#f3f4f6}.feedback-dark{--feedback-overlay-bg:rgba(0,0,0,.2);--feedback-backdrop-bg:rgba(0,0,0,.8);--feedback-modal-bg:#1f2937;--feedback-modal-border:#374151;--feedback-text-primary:#f9fafb;--feedback-text-secondary:#d1d5db;--feedback-text-tertiary:#9ca3af;--feedback-border:#4b5563;--feedback-border-focus:#60a5fa;--feedback-input-bg:#111827;--feedback-input-disabled-bg:#374151;--feedback-btn-cancel-bg:#374151;--feedback-btn-cancel-hover:#4b5563;--feedback-btn-cancel-text:#d1d5db;--feedback-btn-primary-bg:#3b82f6;--feedback-btn-primary-hover:#2563eb;--feedback-btn-primary-text:#fff;--feedback-btn-disabled-bg:#4b5563;--feedback-highlight-border:#60a5fa;--feedback-highlight-bg:rgba(96,165,250,.15);--feedback-highlight-shadow:rgba(96,165,250,.3);--feedback-tooltip-bg:#374151;--feedback-tooltip-text:#f9fafb;--feedback-error-bg:#7f1d1d;--feedback-error-border:#dc2626;--feedback-error-text:#fca5a5;--feedback-screenshot-border:#374151;--feedback-screenshot-bg:#111827;--feedback-shadow:rgba(0,0,0,.4);--feedback-close-hover-bg:#374151}.feedback-overlay{background:var(--feedback-overlay-bg);bottom:0;cursor:crosshair;left:0;pointer-events:none;position:fixed;right:0;top:0;transition:background .2s ease;z-index:999998}.feedback-highlight{background:var(--feedback-highlight-bg);border:2px solid var(--feedback-highlight-border);border-radius:4px;box-shadow:0 0 0 4px var(--feedback-highlight-shadow),0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);pointer-events:none;position:absolute;transition:all .15s cubic-bezier(.4,0,.2,1);z-index:999999}.feedback-tooltip{backdrop-filter:blur(10px);background:var(--feedback-tooltip-bg);border-radius:8px;box-shadow:0 10px 15px -3px rgba(0,0,0,.3),0 4px 6px -2px rgba(0,0,0,.2);color:var(--feedback-tooltip-text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:13px;font-weight:500;padding:8px 14px;pointer-events:none;position:fixed;transition:all .15s ease;white-space:nowrap;z-index:1000000}.feedback-backdrop{animation:fadeIn .2s ease;backdrop-filter:blur(4px);background:var(--feedback-backdrop-bg);bottom:0;left:0;position:fixed;right:0;top:0;z-index:1000001}.feedback-modal{animation:slideUp .3s cubic-bezier(.16,1,.3,1);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;left:50%;max-height:85vh;max-width:1000px;position:fixed;top:50%;transform:translate(-50%,-50%);width:90%;z-index:1000002}.feedback-modal-content{background:var(--feedback-modal-bg);border-radius:16px;box-shadow:0 20px 25px -5px var(--feedback-shadow),0 10px 10px -5px var(--feedback-shadow),0 0 0 1px var(--feedback-modal-border);display:flex;flex-direction:column;max-height:85vh;overflow:hidden;padding:28px}.feedback-header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between;margin-bottom:24px}.feedback-title-wrapper{flex:1}.feedback-title{color:var(--feedback-text-primary);font-size:22px;font-weight:700;letter-spacing:-.02em;line-height:1.3;margin:0 0 4px}.feedback-subtitle{font-size:14px;font-weight:400;margin:0}.feedback-close,.feedback-subtitle{color:var(--feedback-text-secondary)}.feedback-close{align-items:center;background:none;border:none;border-radius:8px;cursor:pointer;display:flex;flex-shrink:0;justify-content:center;padding:8px;transition:all .2s}.feedback-close:hover{background:var(--feedback-close-hover-bg);color:var(--feedback-text-primary);transform:scale(1.05)}.feedback-close:active{transform:scale(.95)}.feedback-body{display:flex;flex:1;gap:24px;min-height:0;overflow:auto}.feedback-screenshot-container{display:flex;flex:1;flex-direction:column;gap:12px;min-width:0}.feedback-screenshot-label{color:var(--feedback-text-secondary);font-size:13px;font-weight:600;letter-spacing:.05em;text-transform:uppercase}.feedback-screenshot{align-items:center;background:var(--feedback-screenshot-bg);border:2px solid var(--feedback-screenshot-border);border-radius:12px;box-sizing:border-box;display:flex;justify-content:center;max-height:500px;overflow:auto;padding:16px;width:100%}.feedback-screenshot-img{border-radius:8px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1);display:block;height:auto;width:100%}.feedback-form-container{gap:16px;min-width:320px}.feedback-form,.feedback-form-container{display:flex;flex:1;flex-direction:column}.feedback-form{gap:10px;min-height:0}.feedback-label{color:var(--feedback-text-primary);display:block;font-size:14px;font-weight:600;margin:0}.feedback-required{color:#ef4444;margin-left:4px}.feedback-textarea{background:var(--feedback-input-bg);border:2px solid var(--feedback-border);border-radius:10px;box-sizing:border-box;color:var(--feedback-text-primary);flex:1;font-family:inherit;font-size:14px;line-height:1.6;min-height:180px;padding:14px;resize:vertical;transition:all .2s ease;width:100%}.feedback-textarea::placeholder{color:var(--feedback-text-tertiary)}.feedback-textarea:focus{border-color:var(--feedback-border-focus);box-shadow:0 0 0 4px var(--feedback-highlight-bg);outline:none;transform:scale(1.005)}.feedback-textarea:disabled{background:var(--feedback-input-disabled-bg);color:var(--feedback-text-secondary);cursor:not-allowed;opacity:.7}.feedback-error{align-items:center;animation:slideDown .3s ease;background:var(--feedback-error-bg);border:1px solid var(--feedback-error-border);border-radius:8px;color:var(--feedback-error-text);display:flex;font-size:13px;font-weight:500;gap:8px;padding:12px 14px}.feedback-error svg{flex-shrink:0}.feedback-actions{display:flex;flex-shrink:0;gap:12px;justify-content:flex-end}.feedback-btn{align-items:center;border:none;border-radius:10px;cursor:pointer;display:flex;font-family:inherit;font-size:14px;font-weight:600;gap:8px;justify-content:center;min-width:120px;padding:12px 24px;transition:all .2s cubic-bezier(.4,0,.2,1);white-space:nowrap}.feedback-cancel{background:var(--feedback-btn-cancel-bg);border:1px solid var(--feedback-border);color:var(--feedback-btn-cancel-text)}.feedback-cancel:hover:not(:disabled){background:var(--feedback-btn-cancel-hover);box-shadow:0 4px 6px -1px var(--feedback-shadow);transform:translateY(-1px)}.feedback-cancel:active:not(:disabled){transform:translateY(0)}.feedback-submit{background:var(--feedback-btn-primary-bg);color:var(--feedback-btn-primary-text)}.feedback-submit:hover:not(:disabled){background:var(--feedback-btn-primary-hover);box-shadow:0 10px 15px -3px rgba(59,130,246,.3),0 4px 6px -2px rgba(59,130,246,.2);transform:translateY(-2px)}.feedback-submit:active:not(:disabled){transform:translateY(0)}.feedback-submit:disabled{background:var(--feedback-btn-disabled-bg);cursor:not-allowed;opacity:.6;transform:none}.feedback-loading{animation:spin 1s linear infinite}@media (max-width:768px){.feedback-modal{max-width:95%;width:95%}.feedback-modal-content{padding:20px}.feedback-body{flex-direction:column}.feedback-screenshot-container{flex:none;max-height:250px}.feedback-screenshot{max-height:250px;padding:12px}.feedback-form-container{min-width:0}.feedback-title{font-size:18px}.feedback-subtitle{font-size:13px}.feedback-actions{flex-direction:column-reverse}.feedback-btn{min-width:0;width:100%}}@media (max-width:480px){.feedback-modal-content{padding:16px}.feedback-header{margin-bottom:16px}.feedback-body{gap:16px}.feedback-textarea{min-height:150px}}@keyframes spin{to{transform:rotate(1turn)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes slideUp{0%{opacity:0;transform:translate(-50%,-45%) scale(.96)}to{opacity:1;transform:translate(-50%,-50%) scale(1)}}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}.feedback-btn:focus-visible,.feedback-close:focus-visible,.feedback-textarea:focus-visible{outline:3px solid var(--feedback-border-focus);outline-offset:2px}@media (prefers-reduced-motion:reduce){*,:after,:before{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@media print{.feedback-backdrop,.feedback-highlight,.feedback-modal,.feedback-overlay,.feedback-tooltip{display:none!important}}
|