@geenius/feedback 0.1.0

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.
Files changed (85) hide show
  1. package/.changeset/config.json +11 -0
  2. package/.github/CODEOWNERS +1 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +11 -0
  5. package/.github/PULL_REQUEST_TEMPLATE.md +10 -0
  6. package/.github/dependabot.yml +11 -0
  7. package/.github/workflows/ci.yml +23 -0
  8. package/.github/workflows/release.yml +29 -0
  9. package/.nvmrc +1 -0
  10. package/.project/ACCOUNT.yaml +4 -0
  11. package/.project/IDEAS.yaml +7 -0
  12. package/.project/PROJECT.yaml +11 -0
  13. package/.project/ROADMAP.yaml +15 -0
  14. package/CHANGELOG.md +8 -0
  15. package/CODE_OF_CONDUCT.md +16 -0
  16. package/CONTRIBUTING.md +26 -0
  17. package/LICENSE +21 -0
  18. package/README.md +1 -0
  19. package/SECURITY.md +15 -0
  20. package/SUPPORT.md +8 -0
  21. package/package.json +75 -0
  22. package/packages/convex/package.json +42 -0
  23. package/packages/convex/src/index.ts +3 -0
  24. package/packages/convex/src/mutations.ts +88 -0
  25. package/packages/convex/src/queries.ts +78 -0
  26. package/packages/convex/src/schema.ts +47 -0
  27. package/packages/convex/tsconfig.json +18 -0
  28. package/packages/convex/tsup.config.ts +17 -0
  29. package/packages/react/README.md +1 -0
  30. package/packages/react/package.json +49 -0
  31. package/packages/react/src/components/FeedbackCard.tsx +51 -0
  32. package/packages/react/src/components/FeedbackForm.tsx +43 -0
  33. package/packages/react/src/components/FeedbackWidget.tsx +32 -0
  34. package/packages/react/src/components/NPSSurvey.tsx +62 -0
  35. package/packages/react/src/components/index.ts +4 -0
  36. package/packages/react/src/hooks/index.ts +5 -0
  37. package/packages/react/src/hooks/useFeedback.ts +23 -0
  38. package/packages/react/src/hooks/useFeedbackAdmin.ts +24 -0
  39. package/packages/react/src/hooks/useFeedbackForm.ts +35 -0
  40. package/packages/react/src/hooks/useNPS.ts +26 -0
  41. package/packages/react/src/index.tsx +13 -0
  42. package/packages/react/src/pages/FeedbackAdminPage.tsx +71 -0
  43. package/packages/react/src/pages/FeedbackPublicPage.tsx +42 -0
  44. package/packages/react/src/pages/FeedbackWidgetPage.tsx +25 -0
  45. package/packages/react/src/pages/index.ts +3 -0
  46. package/packages/react/tsconfig.json +19 -0
  47. package/packages/react/tsup.config.ts +12 -0
  48. package/packages/react-css/README.md +1 -0
  49. package/packages/react-css/package.json +36 -0
  50. package/packages/react-css/src/components/index.ts +5 -0
  51. package/packages/react-css/src/components/index.tsx +107 -0
  52. package/packages/react-css/src/hooks/index.ts +2 -0
  53. package/packages/react-css/src/index.tsx +5 -0
  54. package/packages/react-css/src/pages/FeedbackAdminPage.tsx +112 -0
  55. package/packages/react-css/src/pages/FeedbackPage.tsx +76 -0
  56. package/packages/react-css/src/styles.css +281 -0
  57. package/packages/react-css/tsconfig.json +19 -0
  58. package/packages/react-css/tsup.config.ts +10 -0
  59. package/packages/shared/README.md +1 -0
  60. package/packages/shared/package.json +44 -0
  61. package/packages/shared/src/__tests__/feedback.test.ts +72 -0
  62. package/packages/shared/src/config.ts +49 -0
  63. package/packages/shared/src/index.ts +111 -0
  64. package/packages/shared/src/types.ts +59 -0
  65. package/packages/shared/tsconfig.json +18 -0
  66. package/packages/shared/tsup.config.ts +11 -0
  67. package/packages/shared/vitest.config.ts +4 -0
  68. package/packages/solidjs/README.md +1 -0
  69. package/packages/solidjs/package.json +45 -0
  70. package/packages/solidjs/src/components.tsx +72 -0
  71. package/packages/solidjs/src/index.tsx +3 -0
  72. package/packages/solidjs/src/primitives.ts +49 -0
  73. package/packages/solidjs/tsconfig.json +20 -0
  74. package/packages/solidjs/tsup.config.ts +12 -0
  75. package/packages/solidjs-css/README.md +1 -0
  76. package/packages/solidjs-css/package.json +32 -0
  77. package/packages/solidjs-css/src/index.tsx +4 -0
  78. package/packages/solidjs-css/src/pages/FeedbackAdminPage.tsx +78 -0
  79. package/packages/solidjs-css/src/pages/FeedbackPage.tsx +65 -0
  80. package/packages/solidjs-css/src/primitives/index.ts +1 -0
  81. package/packages/solidjs-css/src/styles.css +281 -0
  82. package/packages/solidjs-css/tsconfig.json +20 -0
  83. package/packages/solidjs-css/tsup.config.ts +10 -0
  84. package/pnpm-workspace.yaml +2 -0
  85. package/tsconfig.json +23 -0
@@ -0,0 +1,78 @@
1
+ import { Component } from 'solid-js';
2
+ import '../styles.css';
3
+
4
+ const FeedbackAdminPage: Component = () => {
5
+ return (
6
+ <div style={{ padding: '1.5rem' }}>
7
+ <div class="feedback__breadcrumb">
8
+ <span class="feedback__breadcrumb-item">Feedback</span>
9
+ <span class="feedback__breadcrumb-sep">/</span>
10
+ <span class="feedback__breadcrumb-item--active">Admin</span>
11
+ </div>
12
+
13
+ <div class="feedback__stats-grid">
14
+ <div class="feedback__stat-card">
15
+ <div class="feedback__stat-icon">📊</div>
16
+ <div class="feedback__stat-value">7.8</div>
17
+ <div class="feedback__stat-label">NPS Score</div>
18
+ </div>
19
+ <div class="feedback__stat-card">
20
+ <div class="feedback__stat-icon">⭐</div>
21
+ <div class="feedback__stat-value">4.2</div>
22
+ <div class="feedback__stat-label">Avg Rating</div>
23
+ </div>
24
+ <div class="feedback__stat-card">
25
+ <div class="feedback__stat-icon">⏱️</div>
26
+ <div class="feedback__stat-value">2.1h</div>
27
+ <div class="feedback__stat-label">Avg Response</div>
28
+ </div>
29
+ </div>
30
+
31
+ <div class="feedback__filter-panel">
32
+ <div class="feedback__filter-section">
33
+ <div class="feedback__filter-section-title">Status</div>
34
+ <div class="feedback__filter-options">
35
+ <label class="feedback__filter-option">
36
+ <input type="checkbox" checked />
37
+ <span class="feedback__filter-option-label">Open</span>
38
+ </label>
39
+ <label class="feedback__filter-option">
40
+ <input type="checkbox" checked />
41
+ <span class="feedback__filter-option-label">Under Review</span>
42
+ </label>
43
+ <label class="feedback__filter-option">
44
+ <input type="checkbox" checked />
45
+ <span class="feedback__filter-option-label">Planned</span>
46
+ </label>
47
+ </div>
48
+ </div>
49
+ </div>
50
+
51
+ <div style={{ 'margin-top': '2rem' }}>
52
+ <table class="feedback__admin-table">
53
+ <thead>
54
+ <tr>
55
+ <th>Item</th>
56
+ <th>Status</th>
57
+ <th>Votes</th>
58
+ </tr>
59
+ </thead>
60
+ <tbody>
61
+ <tr>
62
+ <td>Add Dark Mode</td>
63
+ <td>In Progress</td>
64
+ <td>42</td>
65
+ </tr>
66
+ <tr>
67
+ <td>Login Timeout Issue</td>
68
+ <td>Open</td>
69
+ <td>12</td>
70
+ </tr>
71
+ </tbody>
72
+ </table>
73
+ </div>
74
+ </div>
75
+ );
76
+ };
77
+
78
+ export default FeedbackAdminPage;
@@ -0,0 +1,65 @@
1
+ import { Component } from 'solid-js';
2
+ import '../styles.css';
3
+
4
+ const FeedbackPage: Component = () => {
5
+ const feedbackItems = [
6
+ { id: 1, title: 'Add Dark Mode', type: 'feature', status: 'in-progress', votes: 42 },
7
+ { id: 2, title: 'Bug: Login timeout issue', type: 'bug', status: 'open', votes: 12 },
8
+ { id: 3, title: 'Improve API documentation', type: 'suggestion', status: 'planned', votes: 28 },
9
+ ];
10
+
11
+ return (
12
+ <div style={{ padding: '1.5rem' }}>
13
+ <div class="feedback__breadcrumb">
14
+ <span class="feedback__breadcrumb-item">Feedback</span>
15
+ <span class="feedback__breadcrumb-sep">/</span>
16
+ <span class="feedback__breadcrumb-item--active">View All</span>
17
+ </div>
18
+
19
+ <div class="feedback__stats-grid">
20
+ <div class="feedback__stat-card">
21
+ <div class="feedback__stat-icon">💬</div>
22
+ <div class="feedback__stat-value">145</div>
23
+ <div class="feedback__stat-label">Total Feedback</div>
24
+ </div>
25
+ <div class="feedback__stat-card">
26
+ <div class="feedback__stat-icon">👍</div>
27
+ <div class="feedback__stat-value">89</div>
28
+ <div class="feedback__stat-label">In Progress</div>
29
+ </div>
30
+ <div class="feedback__stat-card">
31
+ <div class="feedback__stat-icon">✅</div>
32
+ <div class="feedback__stat-value">34</div>
33
+ <div class="feedback__stat-label">Implemented</div>
34
+ </div>
35
+ </div>
36
+
37
+ <div class="feedback__filter-tabs" style={{ 'margin-bottom': '1.5rem' }}>
38
+ <button class="feedback__filter-tab feedback__filter-tab--active">All</button>
39
+ <button class="feedback__filter-tab feedback__filter-tab--inactive">Features</button>
40
+ <button class="feedback__filter-tab feedback__filter-tab--inactive">Bugs</button>
41
+ <button class="feedback__filter-tab feedback__filter-tab--inactive">Suggestions</button>
42
+ </div>
43
+
44
+ <div style={{ display: 'flex', 'flex-direction': 'column', gap: '0.75rem' }}>
45
+ {feedbackItems.map(item => (
46
+ <div class={`feedback__card feedback__card--${item.type}`}>
47
+ <div class="feedback__vote-btn feedback__vote-btn--inactive">
48
+ <span class="feedback__vote-btn-arrow">▲</span>
49
+ <span>{item.votes}</span>
50
+ </div>
51
+ <div class="feedback__card-info">
52
+ <div class="feedback__card-badges">
53
+ <span class={`feedback__type-badge feedback__type-badge--${item.type}`}>{item.type}</span>
54
+ <span class={`feedback__status-badge feedback__status-badge--${item.status}`}>{item.status}</span>
55
+ </div>
56
+ <div class="feedback__card-title">{item.title}</div>
57
+ </div>
58
+ </div>
59
+ ))}
60
+ </div>
61
+ </div>
62
+ );
63
+ };
64
+
65
+ export default FeedbackPage;
@@ -0,0 +1 @@
1
+ export * from '@geenius-feedback/solidjs';
@@ -0,0 +1,281 @@
1
+ /* ─── Feedback Design Tokens (OKLCH) ──────────────────── */
2
+ :root {
3
+ --feedback-bg: oklch(0.12 0.01 250);
4
+ --feedback-surface: oklch(0.16 0.01 250);
5
+ --feedback-border: oklch(0.22 0.01 250);
6
+ --feedback-text: oklch(0.95 0.01 250);
7
+ --feedback-text-muted: oklch(0.58 0.02 250);
8
+ --feedback-accent: oklch(0.65 0.20 265);
9
+ --feedback-open: oklch(0.65 0.20 245);
10
+ --feedback-review: oklch(0.72 0.18 60);
11
+ --feedback-planned: oklch(0.70 0.22 280);
12
+ --feedback-progress: oklch(0.65 0.20 265);
13
+ --feedback-done: oklch(0.72 0.18 155);
14
+ --feedback-declined: oklch(0.50 0.10 250);
15
+ --feedback-bug: oklch(0.60 0.25 25);
16
+ --feedback-feature: oklch(0.70 0.22 280);
17
+ --feedback-suggestion: oklch(0.72 0.18 60);
18
+ --feedback-general: oklch(0.65 0.20 245);
19
+ --feedback-radius: 0.75rem;
20
+ }
21
+
22
+ /* ─── Feedback Card ──────────────────────────────────── */
23
+ .feedback__card {
24
+ display: flex;
25
+ gap: 0.75rem;
26
+ padding: 1rem;
27
+ border: 1px solid var(--feedback-border);
28
+ border-radius: var(--feedback-radius);
29
+ background: oklch(1 0 0 / 0.02);
30
+ transition: all 0.2s;
31
+ cursor: pointer;
32
+ }
33
+ .feedback__card:hover { border-color: oklch(0.65 0.20 265 / 0.2); background: oklch(1 0 0 / 0.04); }
34
+ .feedback__card-info { flex: 1; min-width: 0; }
35
+ .feedback__card-badges { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.25rem; }
36
+ .feedback__card-title { font-size: 0.875rem; font-weight: 600; color: oklch(1 0 0 / 0.9); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-bottom: 0.125rem; }
37
+ .feedback__card-desc { font-size: 0.6875rem; color: oklch(1 0 0 / 0.4); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
38
+ .feedback__card-meta { display: flex; align-items: center; gap: 0.75rem; margin-top: 0.5rem; font-size: 0.625rem; color: oklch(1 0 0 / 0.25); }
39
+ .feedback__card--bug { border-left: 3px solid var(--feedback-bug); }
40
+ .feedback__card--feature { border-left: 3px solid var(--feedback-feature); }
41
+ .feedback__card--suggestion { border-left: 3px solid var(--feedback-suggestion); }
42
+ .feedback__card--general { border-left: 3px solid var(--feedback-general); }
43
+
44
+ /* ─── Vote Button ────────────────────────────────────── */
45
+ .feedback__vote-btn {
46
+ display: flex;
47
+ flex-direction: column;
48
+ align-items: center;
49
+ gap: 0.125rem;
50
+ padding: 0.375rem 0.5rem;
51
+ border-radius: calc(var(--feedback-radius) * 0.8);
52
+ border: none;
53
+ font-size: 0.6875rem;
54
+ font-weight: 700;
55
+ cursor: pointer;
56
+ transition: all 0.15s;
57
+ font-variant-numeric: tabular-nums;
58
+ }
59
+ .feedback__vote-btn--inactive { background: oklch(1 0 0 / 0.05); color: oklch(1 0 0 / 0.3); }
60
+ .feedback__vote-btn--inactive:hover { background: oklch(1 0 0 / 0.1); color: oklch(1 0 0 / 0.5); }
61
+ .feedback__vote-btn--active { background: oklch(0.65 0.20 265 / 0.15); color: oklch(0.65 0.20 265); }
62
+ .feedback__vote-btn-arrow { font-size: 0.875rem; }
63
+
64
+ /* ─── Status Badge ───────────────────────────────────── */
65
+ .feedback__status-badge { display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.125rem 0.625rem; border-radius: 9999px; font-size: 0.625rem; font-weight: 500; }
66
+ .feedback__status-badge--open { background: oklch(0.65 0.20 245 / 0.15); color: oklch(0.65 0.20 245); }
67
+ .feedback__status-badge--under-review { background: oklch(0.72 0.18 60 / 0.15); color: oklch(0.72 0.18 60); }
68
+ .feedback__status-badge--planned { background: oklch(0.70 0.22 280 / 0.15); color: oklch(0.70 0.22 280); }
69
+ .feedback__status-badge--in-progress { background: oklch(0.65 0.20 265 / 0.15); color: oklch(0.65 0.20 265); }
70
+ .feedback__status-badge--done { background: oklch(0.72 0.18 155 / 0.15); color: oklch(0.72 0.18 155); }
71
+ .feedback__status-badge--declined { background: oklch(0.50 0.10 250 / 0.15); color: oklch(0.50 0.10 250); }
72
+
73
+ /* ─── Priority Badge ─────────────────────────────────── */
74
+ .feedback__priority-badge { padding: 0.125rem 0.5rem; border-radius: 9999px; font-size: 0.625rem; font-weight: 500; }
75
+ .feedback__priority-badge--low { background: oklch(0.58 0.10 250 / 0.15); color: oklch(0.58 0.10 250); }
76
+ .feedback__priority-badge--medium { background: oklch(0.72 0.18 60 / 0.15); color: oklch(0.72 0.18 60); }
77
+ .feedback__priority-badge--high { background: oklch(0.68 0.22 35 / 0.15); color: oklch(0.68 0.22 35); }
78
+ .feedback__priority-badge--critical { background: oklch(0.60 0.25 25 / 0.15); color: oklch(0.60 0.25 25); }
79
+
80
+ /* ─── Type Badge ─────────────────────────────────────── */
81
+ .feedback__type-badge { display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.125rem 0.5rem; border-radius: 9999px; font-size: 0.625rem; font-weight: 500; }
82
+ .feedback__type-badge--bug { background: oklch(0.60 0.25 25 / 0.15); color: oklch(0.60 0.25 25); }
83
+ .feedback__type-badge--feature { background: oklch(0.70 0.22 280 / 0.15); color: oklch(0.70 0.22 280); }
84
+ .feedback__type-badge--suggestion { background: oklch(0.72 0.18 60 / 0.15); color: oklch(0.72 0.18 60); }
85
+ .feedback__type-badge--general { background: oklch(0.65 0.20 245 / 0.15); color: oklch(0.65 0.20 245); }
86
+
87
+ /* ─── Widget ─────────────────────────────────────────── */
88
+ .feedback__widget-trigger {
89
+ position: fixed; top: 50%; right: 0; transform: translateY(-50%);
90
+ z-index: 40; padding: 0.5rem 1rem; border-radius: var(--feedback-radius) 0 0 var(--feedback-radius);
91
+ background: var(--feedback-accent); color: white; border: none; cursor: pointer;
92
+ writing-mode: vertical-rl; font-size: 0.6875rem; font-weight: 500; letter-spacing: 0.05em;
93
+ box-shadow: -2px 0 12px oklch(0.65 0.20 265 / 0.3);
94
+ }
95
+ .feedback__widget-trigger:hover { background: oklch(0.70 0.22 265); }
96
+ .feedback__widget-overlay { position: fixed; inset: 0; z-index: 50; display: flex; justify-content: flex-end; }
97
+ .feedback__widget-backdrop { position: absolute; inset: 0; background: oklch(0 0 0 / 0.5); backdrop-filter: blur(4px); }
98
+ .feedback__widget-panel { position: relative; width: 100%; max-width: 28rem; background: oklch(0.06 0.01 250); border-left: 1px solid var(--feedback-border); padding: 1.5rem; overflow-y: auto; }
99
+ .feedback__widget-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.5rem; }
100
+ .feedback__widget-title { font-size: 1.125rem; font-weight: 700; color: var(--feedback-text); }
101
+
102
+ /* ─── Form ───────────────────────────────────────────── */
103
+ .feedback__form { display: flex; flex-direction: column; gap: 1rem; }
104
+ .feedback__form-input { width: 100%; padding: 0.75rem 1rem; border: 1px solid var(--feedback-border); border-radius: var(--feedback-radius); background: oklch(1 0 0 / 0.03); font-size: 0.875rem; color: var(--feedback-text); outline: none; }
105
+ .feedback__form-input::placeholder { color: oklch(1 0 0 / 0.3); }
106
+ .feedback__form-input:focus { border-color: oklch(0.65 0.20 265 / 0.4); }
107
+ .feedback__form-textarea { resize: none; min-height: 6rem; }
108
+ .feedback__form-error { font-size: 0.6875rem; color: oklch(0.60 0.25 25); }
109
+ .feedback__form-actions { display: flex; justify-content: flex-end; gap: 0.5rem; }
110
+
111
+ /* ─── Type Selector ──────────────────────────────────── */
112
+ .feedback__type-selector { display: flex; gap: 0.375rem; }
113
+ .feedback__type-tab { display: flex; align-items: center; gap: 0.375rem; padding: 0.5rem 0.75rem; border-radius: calc(var(--feedback-radius) * 0.8); border: none; font-size: 0.6875rem; font-weight: 500; cursor: pointer; transition: all 0.15s; }
114
+ .feedback__type-tab--inactive { background: oklch(1 0 0 / 0.05); color: oklch(1 0 0 / 0.5); }
115
+ .feedback__type-tab--inactive:hover { background: oklch(1 0 0 / 0.1); }
116
+
117
+ /* ─── NPS ────────────────────────────────────────────── */
118
+ .feedback__nps { position: fixed; bottom: 1.5rem; right: 1.5rem; z-index: 50; width: 24rem; border: 1px solid oklch(1 0 0 / 0.1); border-radius: calc(var(--feedback-radius) * 1.5); background: oklch(0.08 0.01 250); padding: 1.5rem; box-shadow: 0 8px 32px oklch(0 0 0 / 0.5); }
119
+ .feedback__nps-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.25rem; }
120
+ .feedback__nps-title { font-size: 0.875rem; font-weight: 600; color: oklch(1 0 0 / 0.9); }
121
+ .feedback__nps-subtitle { font-size: 0.6875rem; color: oklch(1 0 0 / 0.4); margin-bottom: 1rem; }
122
+ .feedback__nps-scores { display: flex; gap: 0.25rem; }
123
+ .feedback__nps-score-btn { flex: 1; padding: 0.625rem 0; border: none; border-radius: calc(var(--feedback-radius) * 0.8); background: oklch(1 0 0 / 0.05); color: oklch(1 0 0 / 0.5); font-size: 0.6875rem; font-weight: 700; cursor: pointer; transition: all 0.15s; }
124
+ .feedback__nps-score-btn:hover { background: oklch(1 0 0 / 0.1); }
125
+ .feedback__nps-labels { display: flex; justify-content: space-between; margin-top: 0.5rem; font-size: 0.625rem; color: oklch(1 0 0 / 0.2); }
126
+ .feedback__nps-results { padding: 1.25rem; border: 1px solid var(--feedback-border); border-radius: var(--feedback-radius); background: oklch(1 0 0 / 0.02); }
127
+ .feedback__nps-results-score { font-size: 1.875rem; font-weight: 900; font-variant-numeric: tabular-nums; }
128
+ .feedback__nps-bar { display: flex; height: 1.25rem; overflow: hidden; border-radius: 9999px; margin: 1rem 0; }
129
+ .feedback__nps-bar-detractors { background: oklch(0.60 0.25 25 / 0.7); }
130
+ .feedback__nps-bar-passives { background: oklch(0.72 0.18 60 / 0.7); }
131
+ .feedback__nps-bar-promoters { background: oklch(0.72 0.18 155 / 0.7); }
132
+ .feedback__nps-breakdown { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.5rem; text-align: center; font-size: 0.6875rem; }
133
+
134
+ /* ─── Board ──────────────────────────────────────────── */
135
+ .feedback__board { display: flex; flex-direction: column; gap: 0.625rem; }
136
+
137
+ /* ─── Admin Table ────────────────────────────────────── */
138
+ .feedback__admin-table { width: 100%; font-size: 0.875rem; border-collapse: collapse; }
139
+ .feedback__admin-table th { padding: 0.75rem 1rem; text-align: left; font-size: 0.625rem; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; color: oklch(1 0 0 / 0.4); border-bottom: 1px solid oklch(1 0 0 / 0.08); }
140
+ .feedback__admin-table td { padding: 0.75rem 1rem; border-bottom: 1px solid oklch(1 0 0 / 0.05); }
141
+ .feedback__admin-table tr:hover { background: oklch(1 0 0 / 0.02); }
142
+ .feedback__admin-select { padding: 0.25rem 0.5rem; border: 1px solid var(--feedback-border); border-radius: calc(var(--feedback-radius) * 0.6); background: oklch(1 0 0 / 0.05); font-size: 0.6875rem; color: var(--feedback-text); outline: none; }
143
+
144
+ /* ─── Stats Grid ─────────────────────────────────────── */
145
+ .feedback__stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
146
+ .feedback__stat-card { padding: 1rem; border: 1px solid var(--feedback-border); border-radius: var(--feedback-radius); background: oklch(1 0 0 / 0.02); }
147
+ .feedback__stat-icon { font-size: 1.125rem; }
148
+ .feedback__stat-value { font-size: 1.5rem; font-weight: 700; font-variant-numeric: tabular-nums; color: oklch(1 0 0 / 0.9); }
149
+ .feedback__stat-label { font-size: 0.6875rem; color: oklch(1 0 0 / 0.4); }
150
+
151
+ /* ─── Search ─────────────────────────────────────────── */
152
+ .feedback__search { width: 100%; max-width: 16rem; padding: 0.625rem 1rem; border: 1px solid var(--feedback-border); border-radius: var(--feedback-radius); background: oklch(1 0 0 / 0.03); font-size: 0.875rem; color: var(--feedback-text); outline: none; }
153
+ .feedback__search::placeholder { color: oklch(1 0 0 / 0.3); }
154
+ .feedback__search:focus { border-color: oklch(0.65 0.20 265 / 0.4); }
155
+
156
+ /* ─── Filter Tabs ────────────────────────────────────── */
157
+ .feedback__filter-tabs { display: flex; flex-wrap: wrap; gap: 0.375rem; }
158
+ .feedback__filter-tab { padding: 0.375rem 0.75rem; border: none; border-radius: calc(var(--feedback-radius) * 0.8); font-size: 0.6875rem; font-weight: 500; cursor: pointer; }
159
+ .feedback__filter-tab--active { background: var(--feedback-accent); color: white; }
160
+ .feedback__filter-tab--inactive { background: oklch(1 0 0 / 0.05); color: oklch(1 0 0 / 0.5); }
161
+ .feedback__filter-tab--inactive:hover { background: oklch(1 0 0 / 0.1); }
162
+
163
+ /* ─── Btn ────────────────────────────────────────────── */
164
+ .feedback__btn { padding: 0.5rem 1rem; border-radius: calc(var(--feedback-radius) * 0.8); font-size: 0.6875rem; font-weight: 500; cursor: pointer; border: none; transition: all 0.15s; }
165
+ .feedback__btn--primary { background: var(--feedback-accent); color: white; }
166
+ .feedback__btn--primary:hover { background: oklch(0.70 0.22 265); }
167
+ .feedback__btn--outline { border: 1px solid var(--feedback-border); background: transparent; color: oklch(1 0 0 / 0.6); }
168
+ .feedback__btn--danger { background: oklch(0.60 0.25 25 / 0.2); color: oklch(0.60 0.25 25); }
169
+
170
+ /* ─── Skeleton ───────────────────────────────────────── */
171
+ .feedback__skeleton { background: oklch(1 0 0 / 0.05); border-radius: var(--feedback-radius); animation: feedback-pulse 1.5s ease-in-out infinite; }
172
+ @keyframes feedback-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
173
+
174
+ /* ─── Empty ──────────────────────────────────────────── */
175
+ .feedback__empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 4rem 1rem; text-align: center; }
176
+ .feedback__empty-icon { font-size: 3rem; opacity: 0.2; margin-bottom: 0.75rem; }
177
+ .feedback__empty-text { font-size: 0.875rem; color: oklch(1 0 0 / 0.4); }
178
+
179
+ /* ─── Success ────────────────────────────────────────── */
180
+ .feedback__success { display: flex; flex-direction: column; align-items: center; padding: 2rem; text-align: center; }
181
+ .feedback__success-icon { font-size: 2.5rem; margin-bottom: 0.75rem; }
182
+ .feedback__success-text { font-size: 1rem; font-weight: 600; color: oklch(1 0 0 / 0.9); margin-bottom: 0.25rem; }
183
+ .feedback__success-sub { font-size: 0.875rem; color: oklch(1 0 0 / 0.5); }
184
+
185
+ /* ─── Tag ────────────────────────────────────────────── */
186
+ .feedback__tag { padding: 0.125rem 0.375rem; border-radius: 0.25rem; background: oklch(1 0 0 / 0.05); font-size: 0.625rem; color: oklch(1 0 0 / 0.3); }
187
+
188
+ /* ─── Close Button ───────────────────────────────────── */
189
+ .feedback__close-btn { padding: 0.375rem; border: none; background: transparent; color: oklch(1 0 0 / 0.3); cursor: pointer; border-radius: calc(var(--feedback-radius) * 0.6); }
190
+ .feedback__close-btn:hover { color: oklch(1 0 0 / 0.5); background: oklch(1 0 0 / 0.05); }
191
+
192
+ /* ─── Comment Thread ─────────────────────────────────── */
193
+ .feedback__comment-thread { display: flex; flex-direction: column; gap: 1rem; padding: 1rem; border: 1px solid var(--feedback-border); border-radius: var(--feedback-radius); background: oklch(1 0 0 / 0.01); }
194
+ .feedback__comment { display: flex; gap: 0.75rem; }
195
+ .feedback__comment-avatar { width: 2rem; height: 2rem; border-radius: 50%; background: oklch(0.65 0.20 265 / 0.2); display: flex; align-items: center; justify-content: center; font-size: 0.875rem; flex-shrink: 0; }
196
+ .feedback__comment-body { flex: 1; }
197
+ .feedback__comment-header { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.25rem; }
198
+ .feedback__comment-author { font-size: 0.75rem; font-weight: 600; color: oklch(1 0 0 / 0.8); }
199
+ .feedback__comment-time { font-size: 0.625rem; color: oklch(1 0 0 / 0.3); }
200
+ .feedback__comment-text { font-size: 0.6875rem; color: oklch(1 0 0 / 0.7); line-height: 1.4; }
201
+ .feedback__comment-reply-btn { margin-top: 0.5rem; padding: 0.25rem 0.5rem; font-size: 0.625rem; border: none; background: transparent; color: oklch(0.65 0.20 265); cursor: pointer; transition: all 0.1s; }
202
+ .feedback__comment-reply-btn:hover { color: oklch(0.70 0.22 265); }
203
+
204
+ /* ─── Rating Component ───────────────────────────────── */
205
+ .feedback__rating { display: flex; gap: 0.25rem; }
206
+ .feedback__rating-star { font-size: 1.5rem; cursor: pointer; opacity: 0.3; transition: all 0.15s; }
207
+ .feedback__rating-star--filled { opacity: 1; color: oklch(0.72 0.18 60); }
208
+ .feedback__rating-star:hover { opacity: 1; transform: scale(1.1); }
209
+
210
+ /* ─── Sentiment Indicator ────────────────────────────── */
211
+ .feedback__sentiment-card { padding: 1rem; border: 1px solid var(--feedback-border); border-radius: var(--feedback-radius); background: oklch(1 0 0 / 0.02); text-align: center; }
212
+ .feedback__sentiment-icon { font-size: 2rem; margin-bottom: 0.5rem; }
213
+ .feedback__sentiment-label { font-size: 0.875rem; font-weight: 600; color: oklch(1 0 0 / 0.8); margin-bottom: 0.25rem; }
214
+ .feedback__sentiment-count { font-size: 0.6875rem; color: oklch(1 0 0 / 0.4); }
215
+
216
+ /* ─── Response Panel ─────────────────────────────────── */
217
+ .feedback__response-panel { border: 1px solid var(--feedback-border); border-radius: var(--feedback-radius); background: oklch(1 0 0 / 0.02); padding: 1rem; }
218
+ .feedback__response-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.75rem; }
219
+ .feedback__response-title { font-size: 0.875rem; font-weight: 600; color: oklch(1 0 0 / 0.8); }
220
+ .feedback__response-time { font-size: 0.625rem; color: oklch(1 0 0 / 0.3); }
221
+ .feedback__response-body { font-size: 0.6875rem; color: oklch(1 0 0 / 0.7); line-height: 1.5; }
222
+ .feedback__response-actions { display: flex; gap: 0.5rem; margin-top: 0.75rem; }
223
+ .feedback__response-btn { padding: 0.25rem 0.5rem; font-size: 0.625rem; border: none; background: oklch(1 0 0 / 0.05); color: oklch(1 0 0 / 0.5); cursor: pointer; border-radius: calc(var(--feedback-radius) * 0.6); transition: all 0.1s; }
224
+ .feedback__response-btn:hover { background: oklch(1 0 0 / 0.1); color: oklch(1 0 0 / 0.6); }
225
+
226
+ /* ─── Analytics Chart ────────────────────────────────── */
227
+ .feedback__analytics { border: 1px solid var(--feedback-border); border-radius: var(--feedback-radius); background: oklch(1 0 0 / 0.02); padding: 1rem; }
228
+ .feedback__analytics-title { font-size: 0.875rem; font-weight: 600; color: oklch(1 0 0 / 0.8); margin-bottom: 1rem; }
229
+ .feedback__analytics-chart { display: flex; align-items: flex-end; gap: 0.5rem; height: 10rem; }
230
+ .feedback__analytics-bar { flex: 1; border-radius: 0.25rem 0.25rem 0 0; background: linear-gradient(to top, oklch(0.65 0.20 265), oklch(0.65 0.20 265 / 0.4)); transition: all 0.2s; position: relative; }
231
+ .feedback__analytics-bar:hover { background: linear-gradient(to top, oklch(0.70 0.22 265), oklch(0.70 0.22 265)); }
232
+ .feedback__analytics-label { position: absolute; bottom: -1.5rem; left: 50%; transform: translateX(-50%); font-size: 0.625rem; color: oklch(1 0 0 / 0.4); white-space: nowrap; }
233
+
234
+ /* ─── Filter Control Panel ───────────────────────────── */
235
+ .feedback__filter-panel { border: 1px solid var(--feedback-border); border-radius: var(--feedback-radius); background: oklch(1 0 0 / 0.02); padding: 1rem; }
236
+ .feedback__filter-section { margin-bottom: 1rem; }
237
+ .feedback__filter-section:last-child { margin-bottom: 0; }
238
+ .feedback__filter-section-title { font-size: 0.6875rem; font-weight: 600; text-transform: uppercase; color: oklch(1 0 0 / 0.4); margin-bottom: 0.5rem; letter-spacing: 0.05em; }
239
+ .feedback__filter-options { display: flex; flex-direction: column; gap: 0.25rem; }
240
+ .feedback__filter-option { display: flex; align-items: center; gap: 0.5rem; padding: 0.25rem 0; font-size: 0.6875rem; }
241
+ .feedback__filter-option input { cursor: pointer; }
242
+ .feedback__filter-option-label { color: oklch(1 0 0 / 0.7); cursor: pointer; }
243
+
244
+ /* ─── Tag Selector ───────────────────────────────────── */
245
+ .feedback__tag-selector { padding: 1rem; border: 1px solid var(--feedback-border); border-radius: var(--feedback-radius); background: oklch(1 0 0 / 0.02); }
246
+ .feedback__tag-selector-label { display: block; font-size: 0.6875rem; font-weight: 500; color: oklch(1 0 0 / 0.6); margin-bottom: 0.5rem; text-transform: uppercase; }
247
+ .feedback__tag-list { display: flex; flex-wrap: wrap; gap: 0.5rem; }
248
+ .feedback__tag-item { display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.375rem 0.75rem; border-radius: 9999px; background: oklch(0.65 0.20 265 / 0.15); color: oklch(0.65 0.20 265); font-size: 0.6875rem; font-weight: 500; }
249
+ .feedback__tag-remove { margin-left: 0.25rem; cursor: pointer; font-weight: 700; opacity: 0.6; transition: opacity 0.1s; }
250
+ .feedback__tag-remove:hover { opacity: 1; }
251
+
252
+ /* ─── Feedback Form Actions ──────────────────────────── */
253
+ .feedback__form-footer { display: flex; justify-content: space-between; align-items: center; padding-top: 1rem; border-top: 1px solid var(--feedback-border); }
254
+ .feedback__form-counter { font-size: 0.625rem; color: oklch(1 0 0 / 0.3); }
255
+ .feedback__form-actions-group { display: flex; gap: 0.5rem; }
256
+
257
+ /* ─── Feedback Status Indicator ──────────────────────── */
258
+ .feedback__status-indicator { display: inline-flex; align-items: center; gap: 0.375rem; padding: 0.375rem 0.75rem; border-radius: 9999px; font-size: 0.625rem; font-weight: 500; }
259
+ .feedback__status-indicator--new { background: oklch(0.65 0.20 245 / 0.15); color: oklch(0.65 0.20 245); }
260
+ .feedback__status-indicator--in-review { background: oklch(0.72 0.18 60 / 0.15); color: oklch(0.72 0.18 60); }
261
+ .feedback__status-indicator--implemented { background: oklch(0.72 0.18 155 / 0.15); color: oklch(0.72 0.18 155); }
262
+
263
+ /* ─── Detailed Stats Row ─────────────────────────────── */
264
+ .feedback__detailed-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 0.5rem; }
265
+ .feedback__detailed-stat-item { padding: 0.75rem; border: 1px solid var(--feedback-border); border-radius: var(--feedback-radius); background: oklch(1 0 0 / 0.01); text-align: center; }
266
+ .feedback__detailed-stat-icon { font-size: 1.25rem; margin-bottom: 0.25rem; }
267
+ .feedback__detailed-stat-value { font-size: 1rem; font-weight: 700; color: oklch(1 0 0 / 0.9); }
268
+ .feedback__detailed-stat-label { font-size: 0.625rem; color: oklch(1 0 0 / 0.4); margin-top: 0.25rem; }
269
+
270
+ /* ─── Breadcrumb Navigation ──────────────────────────── */
271
+ .feedback__breadcrumb { display: flex; align-items: center; gap: 0.5rem; font-size: 0.875rem; margin-bottom: 1rem; }
272
+ .feedback__breadcrumb-item { color: oklch(1 0 0 / 0.5); }
273
+ .feedback__breadcrumb-item--active { color: oklch(1 0 0 / 0.8); font-weight: 500; }
274
+ .feedback__breadcrumb-sep { color: oklch(1 0 0 / 0.2); margin: 0 0.25rem; }
275
+
276
+ /* ─── Pagination Controls ────────────────────────────── */
277
+ .feedback__pagination { display: flex; align-items: center; gap: 0.5rem; justify-content: center; padding: 1rem; }
278
+ .feedback__pagination-btn { padding: 0.375rem 0.75rem; border: 1px solid var(--feedback-border); border-radius: calc(var(--feedback-radius) * 0.6); background: oklch(1 0 0 / 0.05); color: oklch(1 0 0 / 0.6); font-size: 0.6875rem; cursor: pointer; transition: all 0.1s; }
279
+ .feedback__pagination-btn:hover:not(:disabled) { background: oklch(1 0 0 / 0.1); color: oklch(1 0 0 / 0.8); }
280
+ .feedback__pagination-btn:disabled { opacity: 0.5; cursor: not-allowed; }
281
+ .feedback__pagination-info { font-size: 0.6875rem; color: oklch(1 0 0 / 0.5); }
@@ -0,0 +1,20 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "jsx": "preserve",
7
+ "jsxImportSource": "solid-js",
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "target": "ES2022",
14
+ "module": "ESNext",
15
+ "moduleResolution": "bundler"
16
+ },
17
+ "include": [
18
+ "src"
19
+ ]
20
+ }
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'tsup'
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.tsx'],
5
+ format: ['cjs', 'esm'],
6
+ dts: true,
7
+ clean: true,
8
+ sourcemap: true,
9
+ external: ['solid-js'],
10
+ })
@@ -0,0 +1,2 @@
1
+ packages:
2
+ - 'packages/*'
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "declaration": true,
10
+ "declarationMap": true,
11
+ "sourceMap": true,
12
+ "outDir": "dist",
13
+ "rootDir": "src",
14
+ "jsx": "react-jsx",
15
+ "forceConsistentCasingInFileNames": true,
16
+ "resolveJsonModule": true,
17
+ "isolatedModules": true
18
+ },
19
+ "exclude": [
20
+ "node_modules",
21
+ "dist"
22
+ ]
23
+ }