convex-feedback-ui 0.1.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.
Files changed (118) hide show
  1. package/README.md +187 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/native/context.d.ts +10 -0
  4. package/dist/native/context.d.ts.map +1 -0
  5. package/dist/native/context.js +33 -0
  6. package/dist/native/context.js.map +1 -0
  7. package/dist/native/index.d.ts +7 -0
  8. package/dist/native/index.d.ts.map +1 -0
  9. package/dist/native/index.js +6 -0
  10. package/dist/native/index.js.map +1 -0
  11. package/dist/native/primitives.d.ts +94 -0
  12. package/dist/native/primitives.d.ts.map +1 -0
  13. package/dist/native/primitives.js +303 -0
  14. package/dist/native/primitives.js.map +1 -0
  15. package/dist/native/screen.d.ts +3 -0
  16. package/dist/native/screen.d.ts.map +1 -0
  17. package/dist/native/screen.js +284 -0
  18. package/dist/native/screen.js.map +1 -0
  19. package/dist/react/context.d.ts +22 -0
  20. package/dist/react/context.d.ts.map +1 -0
  21. package/dist/react/context.js +41 -0
  22. package/dist/react/context.js.map +1 -0
  23. package/dist/react/index.d.ts +7 -0
  24. package/dist/react/index.d.ts.map +1 -0
  25. package/dist/react/index.js +6 -0
  26. package/dist/react/index.js.map +1 -0
  27. package/dist/react/primitives.d.ts +105 -0
  28. package/dist/react/primitives.d.ts.map +1 -0
  29. package/dist/react/primitives.js +217 -0
  30. package/dist/react/primitives.js.map +1 -0
  31. package/dist/react/screen.d.ts +6 -0
  32. package/dist/react/screen.d.ts.map +1 -0
  33. package/dist/react/screen.js +152 -0
  34. package/dist/react/screen.js.map +1 -0
  35. package/dist/react/styles.css +299 -0
  36. package/dist/shared/messages.d.ts +4 -0
  37. package/dist/shared/messages.d.ts.map +1 -0
  38. package/dist/shared/messages.js +85 -0
  39. package/dist/shared/messages.js.map +1 -0
  40. package/dist/shared/render.d.ts +4 -0
  41. package/dist/shared/render.d.ts.map +1 -0
  42. package/dist/shared/render.js +6 -0
  43. package/dist/shared/render.js.map +1 -0
  44. package/dist/shared/theme.d.ts +4 -0
  45. package/dist/shared/theme.d.ts.map +1 -0
  46. package/dist/shared/theme.js +23 -0
  47. package/dist/shared/theme.js.map +1 -0
  48. package/dist/shared/types/board.d.ts +43 -0
  49. package/dist/shared/types/board.d.ts.map +1 -0
  50. package/dist/shared/types/board.js +2 -0
  51. package/dist/shared/types/board.js.map +1 -0
  52. package/dist/shared/types/comment.d.ts +85 -0
  53. package/dist/shared/types/comment.d.ts.map +1 -0
  54. package/dist/shared/types/comment.js +2 -0
  55. package/dist/shared/types/comment.js.map +1 -0
  56. package/dist/shared/types/context.d.ts +68 -0
  57. package/dist/shared/types/context.d.ts.map +1 -0
  58. package/dist/shared/types/context.js +2 -0
  59. package/dist/shared/types/context.js.map +1 -0
  60. package/dist/shared/types/entry.d.ts +41 -0
  61. package/dist/shared/types/entry.d.ts.map +1 -0
  62. package/dist/shared/types/entry.js +2 -0
  63. package/dist/shared/types/entry.js.map +1 -0
  64. package/dist/shared/types/form.d.ts +25 -0
  65. package/dist/shared/types/form.d.ts.map +1 -0
  66. package/dist/shared/types/form.js +2 -0
  67. package/dist/shared/types/form.js.map +1 -0
  68. package/dist/shared/types/helpers.d.ts +2 -0
  69. package/dist/shared/types/helpers.d.ts.map +1 -0
  70. package/dist/shared/types/helpers.js +2 -0
  71. package/dist/shared/types/helpers.js.map +1 -0
  72. package/dist/shared/types/index.d.ts +10 -0
  73. package/dist/shared/types/index.d.ts.map +1 -0
  74. package/dist/shared/types/index.js +2 -0
  75. package/dist/shared/types/index.js.map +1 -0
  76. package/dist/shared/types/messages.d.ts +117 -0
  77. package/dist/shared/types/messages.d.ts.map +1 -0
  78. package/dist/shared/types/messages.js +2 -0
  79. package/dist/shared/types/messages.js.map +1 -0
  80. package/dist/shared/types/render.d.ts +12 -0
  81. package/dist/shared/types/render.d.ts.map +1 -0
  82. package/dist/shared/types/render.js +2 -0
  83. package/dist/shared/types/render.js.map +1 -0
  84. package/dist/shared/types/screen.d.ts +95 -0
  85. package/dist/shared/types/screen.d.ts.map +1 -0
  86. package/dist/shared/types/screen.js +2 -0
  87. package/dist/shared/types/screen.js.map +1 -0
  88. package/dist/shared/types/theme.d.ts +48 -0
  89. package/dist/shared/types/theme.d.ts.map +1 -0
  90. package/dist/shared/types/theme.js +2 -0
  91. package/dist/shared/types/theme.js.map +1 -0
  92. package/package.json +58 -0
  93. package/src/native/README.md +17 -0
  94. package/src/native/context.tsx +74 -0
  95. package/src/native/index.ts +39 -0
  96. package/src/native/primitives.tsx +730 -0
  97. package/src/native/screen.tsx +831 -0
  98. package/src/react/README.md +7 -0
  99. package/src/react/context.tsx +83 -0
  100. package/src/react/index.ts +42 -0
  101. package/src/react/primitives.tsx +589 -0
  102. package/src/react/screen.tsx +633 -0
  103. package/src/react/styles.css +299 -0
  104. package/src/shared/README.md +9 -0
  105. package/src/shared/messages.ts +99 -0
  106. package/src/shared/render.ts +12 -0
  107. package/src/shared/theme.ts +27 -0
  108. package/src/shared/types/board.ts +48 -0
  109. package/src/shared/types/comment.ts +101 -0
  110. package/src/shared/types/context.ts +81 -0
  111. package/src/shared/types/entry.ts +48 -0
  112. package/src/shared/types/form.ts +27 -0
  113. package/src/shared/types/helpers.ts +1 -0
  114. package/src/shared/types/index.ts +45 -0
  115. package/src/shared/types/messages.ts +160 -0
  116. package/src/shared/types/render.ts +12 -0
  117. package/src/shared/types/screen.ts +123 -0
  118. package/src/shared/types/theme.ts +61 -0
@@ -0,0 +1,299 @@
1
+ :where(.cf-board) {
2
+ box-sizing: border-box;
3
+ width: 100%;
4
+ max-width: 920px;
5
+ margin: 0 auto;
6
+ padding: calc(var(--cf-space) * 1.5);
7
+ color: var(--cf-text);
8
+ background: var(--cf-background);
9
+ font: inherit;
10
+ }
11
+
12
+ :where(.cf-board *),
13
+ :where(.cf-board *::before),
14
+ :where(.cf-board *::after) {
15
+ box-sizing: border-box;
16
+ }
17
+ :where(.cf-board) :where(.cf-board__header) {
18
+ display: flex;
19
+ align-items: flex-start;
20
+ justify-content: space-between;
21
+ gap: var(--cf-space);
22
+ margin-bottom: calc(var(--cf-space) * 1.5);
23
+ }
24
+ :where(.cf-board) :where(.cf-board__title) {
25
+ margin: 0;
26
+ font-size: 1.65rem;
27
+ line-height: 1.15;
28
+ }
29
+ :where(.cf-board) :where(.cf-board__subtitle) {
30
+ margin: 0.4rem 0 0;
31
+ color: var(--cf-muted);
32
+ }
33
+ :where(.cf-board) :where(.cf-board__list),
34
+ :where(.cf-board) :where(.cf-comments) {
35
+ display: grid;
36
+ gap: 10px;
37
+ }
38
+ :where(.cf-board) :where(.cf-search),
39
+ :where(.cf-board) :where(.cf-input),
40
+ :where(.cf-board) :where(.cf-textarea),
41
+ :where(.cf-board) :where(.cf-select) {
42
+ width: 100%;
43
+ border: 1px solid var(--cf-border);
44
+ border-radius: calc(var(--cf-radius) * 0.75);
45
+ background: var(--cf-surface);
46
+ color: var(--cf-text);
47
+ padding: 10px 12px;
48
+ font: inherit;
49
+ outline: none;
50
+ }
51
+ :where(.cf-board) :where(.cf-search) {
52
+ margin-bottom: var(--cf-space);
53
+ }
54
+ :where(.cf-board) :where(.cf-search:focus),
55
+ :where(.cf-board) :where(.cf-input:focus),
56
+ :where(.cf-board) :where(.cf-textarea:focus),
57
+ :where(.cf-board) :where(.cf-select:focus) {
58
+ border-color: var(--cf-primary);
59
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--cf-primary) 14%, transparent);
60
+ }
61
+ :where(.cf-board) :where(.cf-entry) {
62
+ display: grid;
63
+ grid-template-columns: 52px minmax(0, 1fr);
64
+ gap: 12px;
65
+ padding: 14px;
66
+ border: 1px solid var(--cf-border);
67
+ border-radius: var(--cf-radius);
68
+ background: var(--cf-surface);
69
+ }
70
+ :where(.cf-board) :where(.cf-entry__content) {
71
+ min-width: 0;
72
+ cursor: pointer;
73
+ }
74
+ :where(.cf-board) :where(.cf-entry__title) {
75
+ margin: 5px 0 3px;
76
+ font-size: 1rem;
77
+ }
78
+ :where(.cf-board) :where(.cf-entry__body) {
79
+ margin: 0;
80
+ color: var(--cf-muted);
81
+ line-height: 1.5;
82
+ white-space: pre-wrap;
83
+ }
84
+ :where(.cf-board) :where(.cf-entry__meta) {
85
+ display: flex;
86
+ gap: 8px;
87
+ align-items: center;
88
+ flex-wrap: wrap;
89
+ color: var(--cf-muted);
90
+ font-size: 0.8rem;
91
+ }
92
+ :where(.cf-board) :where(.cf-entry__kind) {
93
+ color: var(--cf-muted);
94
+ font-size: 0.78rem;
95
+ font-weight: 600;
96
+ }
97
+ :where(.cf-board) :where(.cf-status) {
98
+ display: inline-flex;
99
+ border-radius: 999px;
100
+ padding: 3px 8px;
101
+ background: var(--cf-surface-muted);
102
+ color: var(--cf-muted);
103
+ font-size: 0.78rem;
104
+ font-weight: 600;
105
+ }
106
+ :where(.cf-board) :where(.cf-upvote) {
107
+ width: 46px;
108
+ min-height: 54px;
109
+ display: inline-flex;
110
+ flex-direction: column;
111
+ align-items: center;
112
+ justify-content: center;
113
+ gap: 2px;
114
+ border: 1px solid var(--cf-border);
115
+ border-radius: 10px;
116
+ background: var(--cf-surface-muted);
117
+ color: var(--cf-muted);
118
+ cursor: pointer;
119
+ }
120
+ :where(.cf-board) :where(.cf-upvote[data-active="true"]) {
121
+ border-color: var(--cf-primary);
122
+ color: var(--cf-primary);
123
+ background: color-mix(in srgb, var(--cf-primary) 8%, var(--cf-surface));
124
+ }
125
+ :where(.cf-board) :where(.cf-button) {
126
+ display: inline-flex;
127
+ align-items: center;
128
+ justify-content: center;
129
+ gap: 6px;
130
+ min-height: 38px;
131
+ border: 1px solid var(--cf-border);
132
+ border-radius: 9px;
133
+ padding: 8px 12px;
134
+ background: var(--cf-surface);
135
+ color: var(--cf-text);
136
+ font: inherit;
137
+ font-weight: 600;
138
+ cursor: pointer;
139
+ }
140
+ :where(.cf-board) :where(.cf-button--primary) {
141
+ border-color: var(--cf-primary);
142
+ background: var(--cf-primary);
143
+ color: white;
144
+ }
145
+ :where(.cf-board) :where(.cf-button--link) {
146
+ border-color: transparent;
147
+ padding-inline: 0;
148
+ background: transparent;
149
+ color: var(--cf-primary);
150
+ }
151
+ :where(.cf-board) :where(.cf-form) {
152
+ display: grid;
153
+ gap: 10px;
154
+ padding: 14px;
155
+ border: 1px solid var(--cf-border);
156
+ border-radius: var(--cf-radius);
157
+ background: var(--cf-surface-muted);
158
+ margin-bottom: 14px;
159
+ }
160
+ :where(.cf-board) :where(.cf-field) {
161
+ display: grid;
162
+ gap: 5px;
163
+ font-size: 0.88rem;
164
+ font-weight: 600;
165
+ }
166
+ :where(.cf-board) :where(.cf-duplicates) {
167
+ display: grid;
168
+ gap: 5px;
169
+ padding: 10px;
170
+ border-radius: 9px;
171
+ background: color-mix(in srgb, var(--cf-primary) 7%, var(--cf-surface));
172
+ font-size: 0.86rem;
173
+ }
174
+ :where(.cf-board) :where(.cf-duplicates__list) {
175
+ display: grid;
176
+ gap: 8px;
177
+ }
178
+
179
+ :where(.cf-board) :where(.cf-duplicate) {
180
+ display: grid;
181
+ gap: 5px;
182
+ padding: 10px;
183
+ border: 1px solid var(--cf-border);
184
+ border-radius: calc(var(--cf-radius) * 0.75);
185
+ background: var(--cf-surface);
186
+ }
187
+
188
+ :where(.cf-board) :where(.cf-duplicate__body),
189
+ :where(.cf-board) :where(.cf-duplicate__meta) {
190
+ color: var(--cf-muted);
191
+ font-size: 0.82rem;
192
+ }
193
+
194
+ :where(.cf-board) :where(.cf-duplicate__body) {
195
+ display: -webkit-box;
196
+ overflow: hidden;
197
+ line-clamp: 2;
198
+ -webkit-box-orient: vertical;
199
+ }
200
+ :where(.cf-board) :where(.cf-state) {
201
+ color: var(--cf-muted);
202
+ padding: 12px 2px;
203
+ }
204
+ :where(.cf-board) :where(.cf-detail),
205
+ :where(.cf-board) :where(.cf-discussion) {
206
+ display: grid;
207
+ gap: 14px;
208
+ }
209
+ :where(.cf-board) :where(.cf-discussion) {
210
+ margin-top: 8px;
211
+ }
212
+ :where(.cf-board) :where(.cf-comment) {
213
+ padding: 12px;
214
+ border: 1px solid var(--cf-border);
215
+ border-radius: calc(var(--cf-radius) * 0.85);
216
+ background: var(--cf-surface);
217
+ }
218
+ :where(.cf-board) :where(.cf-comment__body) {
219
+ margin: 0 0 8px;
220
+ line-height: 1.45;
221
+ white-space: pre-wrap;
222
+ }
223
+ :where(.cf-board) :where(.cf-comment__toolbar),
224
+ :where(.cf-board) :where(.cf-inline-actions) {
225
+ display: flex;
226
+ align-items: center;
227
+ gap: 8px;
228
+ flex-wrap: wrap;
229
+ }
230
+ :where(.cf-board) :where(.cf-comment__like),
231
+ :where(.cf-board) :where(.cf-comment__action),
232
+ :where(.cf-board) :where(.cf-comment__replies) {
233
+ border: 0;
234
+ padding: 4px 6px;
235
+ background: transparent;
236
+ color: var(--cf-muted);
237
+ font: inherit;
238
+ font-size: 0.82rem;
239
+ cursor: pointer;
240
+ }
241
+ :where(.cf-board) :where(.cf-comment__like[data-active="true"]) {
242
+ color: var(--cf-primary);
243
+ }
244
+ :where(.cf-board) :where(.cf-comment__children) {
245
+ display: grid;
246
+ gap: 8px;
247
+ margin: 10px 0 0 16px;
248
+ padding-left: 10px;
249
+ border-left: 2px solid var(--cf-border);
250
+ }
251
+ :where(.cf-board) :where(.cf-comment__author) {
252
+ margin-bottom: 5px;
253
+ color: var(--cf-muted);
254
+ font-size: 0.8rem;
255
+ }
256
+
257
+ @media (max-width: 600px) {
258
+ :where(.cf-board) {
259
+ padding: var(--cf-space);
260
+ }
261
+ :where(.cf-board) :where(.cf-board__header) {
262
+ flex-direction: column;
263
+ }
264
+ :where(.cf-board) :where(.cf-entry) {
265
+ grid-template-columns: 44px minmax(0, 1fr);
266
+ padding: 10px;
267
+ }
268
+ :where(.cf-board) :where(.cf-upvote) {
269
+ width: 40px;
270
+ min-height: 48px;
271
+ }
272
+ }
273
+ :where(.cf-board) :where(.cf-confirm-backdrop) {
274
+ position: fixed;
275
+ z-index: 1000;
276
+ inset: 0;
277
+ display: grid;
278
+ place-items: center;
279
+ padding: 20px;
280
+ background: rgb(0 0 0 / 0.35);
281
+ }
282
+
283
+ :where(.cf-board) :where(.cf-confirm) {
284
+ width: min(100%, 420px);
285
+ display: grid;
286
+ gap: 12px;
287
+ padding: 18px;
288
+ border: 1px solid var(--cf-border);
289
+ border-radius: var(--cf-radius);
290
+ background: var(--cf-surface);
291
+ color: var(--cf-text);
292
+ box-shadow: 0 20px 60px rgb(0 0 0 / 0.18);
293
+ }
294
+
295
+ :where(.cf-board) :where(.cf-confirm p) {
296
+ margin: 0;
297
+ color: var(--cf-muted);
298
+ line-height: 1.5;
299
+ }
@@ -0,0 +1,9 @@
1
+ # Shared UI contracts
2
+
3
+ This directory contains platform-neutral contracts used by both web and native UI layers.
4
+
5
+ - `messages.ts`: typed, overrideable package-owned UI copy. User-generated content is never translated or rewritten.
6
+ - `theme.ts`: fallback theme tokens and override contracts.
7
+ - `render.ts`: shared render-child types for replaceable primitive implementations.
8
+
9
+ Do not introduce an i18n runtime dependency here. Localization is provided by typed message injection so hosts can use any localization library.
@@ -0,0 +1,99 @@
1
+ import type { FeedbackMessageOverrides, FeedbackMessages } from "./types";
2
+
3
+ export const englishFeedbackMessages: FeedbackMessages = {
4
+ board: {
5
+ title: "Feedback",
6
+ subtitle: "Share ideas, report problems, and help shape what comes next.",
7
+ searchPlaceholder: "Search feedback…",
8
+ createEntry: "Create feedback",
9
+ noEntries: "No feedback yet.",
10
+ noSearchResults: "No matching feedback found.",
11
+ loading: "Loading…",
12
+ loadMore: "Load more",
13
+ },
14
+ kinds: {
15
+ feedback: "Feedback",
16
+ feature_request: "Feature request",
17
+ bug_report: "Bug report",
18
+ },
19
+ statuses: {
20
+ open: "Open",
21
+ under_review: "Under review",
22
+ planned: "Planned",
23
+ in_progress: "In progress",
24
+ completed: "Completed",
25
+ closed: "Closed",
26
+ },
27
+ entry: {
28
+ upvote: "Upvote",
29
+ removeUpvote: "Remove upvote",
30
+ comments: (count) => `${count} ${count === 1 ? "comment" : "comments"}`,
31
+ open: "Open",
32
+ back: "Back to feedback",
33
+ },
34
+ form: {
35
+ kind: "Type",
36
+ title: "Title",
37
+ titlePlaceholder: "Summarize your feedback",
38
+ body: "Details",
39
+ bodyPlaceholder: "Tell us more…",
40
+ submit: "Submit feedback",
41
+ cancel: "Cancel",
42
+ possibleDuplicates: "Possible existing feedback",
43
+ exactDuplicate: "This may already exist",
44
+ duplicateWarning:
45
+ "Similar feedback already exists. Are you sure you want to submit another entry?",
46
+ submitAnyway: "Submit anyways",
47
+ },
48
+ comments: {
49
+ title: "Discussion",
50
+ placeholder: "Add a comment…",
51
+ submit: "Comment",
52
+ reply: "Reply",
53
+ cancelReply: "Cancel reply",
54
+ like: "Like",
55
+ unlike: "Unlike",
56
+ viewReplies: (count) =>
57
+ `View ${count} ${count === 1 ? "reply" : "replies"}`,
58
+ hideReplies: "Hide replies",
59
+ deleted: "Comment deleted",
60
+ noComments: "No comments yet.",
61
+ loadMore: "Load more comments",
62
+ },
63
+ };
64
+
65
+ export function mergeFeedbackMessages(
66
+ overrides: FeedbackMessageOverrides = {},
67
+ ): FeedbackMessages {
68
+ return {
69
+ board: { ...englishFeedbackMessages.board, ...overrides.board },
70
+ kinds: {
71
+ feedback:
72
+ overrides.kinds?.feedback ?? englishFeedbackMessages.kinds.feedback,
73
+ feature_request:
74
+ overrides.kinds?.feature_request ??
75
+ englishFeedbackMessages.kinds.feature_request,
76
+ bug_report:
77
+ overrides.kinds?.bug_report ?? englishFeedbackMessages.kinds.bug_report,
78
+ },
79
+ statuses: {
80
+ open: overrides.statuses?.open ?? englishFeedbackMessages.statuses.open,
81
+ under_review:
82
+ overrides.statuses?.under_review ??
83
+ englishFeedbackMessages.statuses.under_review,
84
+ planned:
85
+ overrides.statuses?.planned ?? englishFeedbackMessages.statuses.planned,
86
+ in_progress:
87
+ overrides.statuses?.in_progress ??
88
+ englishFeedbackMessages.statuses.in_progress,
89
+ completed:
90
+ overrides.statuses?.completed ??
91
+ englishFeedbackMessages.statuses.completed,
92
+ closed:
93
+ overrides.statuses?.closed ?? englishFeedbackMessages.statuses.closed,
94
+ },
95
+ entry: { ...englishFeedbackMessages.entry, ...overrides.entry },
96
+ form: { ...englishFeedbackMessages.form, ...overrides.form },
97
+ comments: { ...englishFeedbackMessages.comments, ...overrides.comments },
98
+ };
99
+ }
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from "react";
2
+ import type { RenderChildren } from "./types";
3
+
4
+ export function renderChildren<State>(
5
+ children: RenderChildren<State> | undefined,
6
+ state: State,
7
+ fallback: ReactNode,
8
+ ): ReactNode {
9
+ return typeof children === "function"
10
+ ? children(state)
11
+ : (children ?? fallback);
12
+ }
@@ -0,0 +1,27 @@
1
+ import type { FeedbackTheme, FeedbackThemeOverride } from "./types";
2
+
3
+ export const defaultFeedbackTheme: FeedbackTheme = {
4
+ colors: {
5
+ primary: "#5b5bd6",
6
+ background: "#ffffff",
7
+ surface: "#ffffff",
8
+ surfaceMuted: "#f6f6f8",
9
+ text: "#17171a",
10
+ mutedText: "#68686f",
11
+ border: "#e2e2e8",
12
+ danger: "#c73535",
13
+ success: "#27834a",
14
+ },
15
+ radius: 12,
16
+ spacing: 12,
17
+ };
18
+
19
+ export function mergeFeedbackTheme(
20
+ override: FeedbackThemeOverride = {},
21
+ ): FeedbackTheme {
22
+ return {
23
+ colors: { ...defaultFeedbackTheme.colors, ...override.colors },
24
+ radius: override.radius ?? defaultFeedbackTheme.radius,
25
+ spacing: override.spacing ?? defaultFeedbackTheme.spacing,
26
+ };
27
+ }
@@ -0,0 +1,48 @@
1
+ import type { FeedbackColorProps } from "./context.js";
2
+ import type { RenderChildren } from "./render.js";
3
+
4
+ /**
5
+ * Platform-neutral props shared by `FeedbackBoard.Root` implementations.
6
+ *
7
+ * Platform-specific root props extend this interface with their native host
8
+ * element props (`HTMLAttributes` on web and `ViewProps` on React Native).
9
+ */
10
+ export interface BoardRootBaseProps extends FeedbackColorProps {
11
+ /**
12
+ * Overrides provider-level `unstyled` for this board.
13
+ */
14
+ unstyled?: boolean;
15
+ }
16
+
17
+ /**
18
+ * State supplied to a custom `FeedbackBoard.Search` render-function child.
19
+ */
20
+ export interface BoardSearchState {
21
+ /** Current controlled search value. */
22
+ value: string;
23
+
24
+ /** Updates the controlled search value. */
25
+ setValue: (value: string) => void;
26
+ }
27
+
28
+ /**
29
+ * Platform-neutral props shared by `FeedbackBoard.Search` implementations.
30
+ *
31
+ * Web and native search props should extend this interface and add only their
32
+ * host input props.
33
+ */
34
+ export interface BoardSearchBaseProps {
35
+ /** Controlled search value. */
36
+ value: string;
37
+
38
+ /** Called whenever the user changes the search value. */
39
+ onValueChange: (value: string) => void;
40
+
41
+ /**
42
+ * Normal children replace the default content. A render-function child
43
+ * replaces the complete visual implementation while retaining controlled
44
+ * search state.
45
+ *
46
+ */
47
+ children?: RenderChildren<BoardSearchState> | undefined;
48
+ }
@@ -0,0 +1,101 @@
1
+ import type { FeedbackComment } from "convex-feedback";
2
+
3
+ import type { RenderChildren } from "./render.js";
4
+
5
+ /**
6
+ * Platform-neutral props shared by `Comment.Root` implementations.
7
+ */
8
+ export interface CommentRootBaseProps {
9
+ /** Comment exposed to every nested `Comment.*` primitive. */
10
+ comment: FeedbackComment;
11
+ }
12
+
13
+ /**
14
+ * State and actions exposed to a custom `Comment.Like` renderer.
15
+ */
16
+ export interface CommentLikeState {
17
+ /** Current comment. */
18
+ comment: FeedbackComment;
19
+
20
+ /** Whether the current viewer currently likes the comment. */
21
+ active: boolean;
22
+
23
+ /** Current total like count. */
24
+ count: number;
25
+
26
+ /** Requests the opposite of the current like state. */
27
+ toggle: () => void | Promise<void>;
28
+ }
29
+
30
+ /**
31
+ * Platform-neutral props shared by `Comment.Like` implementations.
32
+ */
33
+ export interface CommentLikeBaseProps {
34
+ /**
35
+ * Called with the desired final like state.
36
+ *
37
+ * `true` ensures the viewer likes the comment. `false` ensures the viewer
38
+ * does not like it. The server mutation is idempotent rather than toggle-
39
+ * based.
40
+ */
41
+ onToggle: (desiredState: boolean) => void | Promise<void>;
42
+
43
+ /**
44
+ * Normal children replace the default content. A render-function child
45
+ * replaces the complete control and receives `CommentLikeState`.
46
+ */
47
+ children?: RenderChildren<CommentLikeState> | undefined;
48
+ }
49
+
50
+ /**
51
+ * State exposed to simple comment action renderers such as `Comment.Reply`.
52
+ */
53
+ export interface CommentActionState {
54
+ /** Current comment. */
55
+ comment: FeedbackComment;
56
+
57
+ /** Activates the action. */
58
+ activate: () => void;
59
+ }
60
+
61
+ /**
62
+ * Platform-neutral props shared by simple comment action primitives.
63
+ */
64
+ export interface CommentActionBaseProps {
65
+ /** Called when the action is activated. */
66
+ onActivate: () => void;
67
+
68
+ /**
69
+ * Normal children replace the default content. A render-function child
70
+ * replaces the complete control and receives `CommentActionState`.
71
+ */
72
+ children?: RenderChildren<CommentActionState> | undefined;
73
+ }
74
+
75
+ /**
76
+ * State exposed to a custom lazy-replies control.
77
+ */
78
+ export interface RepliesButtonState extends CommentActionState {
79
+ /** Whether this direct-reply branch is currently expanded. */
80
+ expanded: boolean;
81
+
82
+ /** Number of direct replies available for the comment. */
83
+ count: number;
84
+ }
85
+
86
+ /**
87
+ * Platform-neutral props shared by `Comment.RepliesButton` implementations.
88
+ */
89
+ export interface RepliesButtonBaseProps {
90
+ /** Whether direct replies are currently displayed. */
91
+ expanded: boolean;
92
+
93
+ /** Requests a new expanded/collapsed state. */
94
+ onExpandedChange: (expanded: boolean) => void;
95
+
96
+ /**
97
+ * Normal children replace the default content. A render-function child
98
+ * replaces the complete control and receives `RepliesButtonState`.
99
+ */
100
+ children?: RenderChildren<RepliesButtonState> | undefined;
101
+ }
@@ -0,0 +1,81 @@
1
+ import type { ReactNode } from "react";
2
+ import type { FeedbackMessageOverrides, FeedbackMessages } from "./messages";
3
+ import type { FeedbackTheme, FeedbackThemeOverride } from "./theme";
4
+
5
+ /**
6
+ * Resolved feedback UI configuration available through `useFeedbackUi`.
7
+ *
8
+ * This contract is shared by the React DOM and React Native implementations.
9
+ */
10
+ export interface FeedbackUiContextValue {
11
+ /** Fully resolved and localized UI messages. */
12
+ messages: FeedbackMessages;
13
+
14
+ /** Fully resolved visual theme. */
15
+ theme: FeedbackTheme;
16
+
17
+ /**
18
+ * Whether package-provided default styling is disabled.
19
+ *
20
+ * Behavioral and structural functionality remains available when enabled.
21
+ */
22
+ unstyled: boolean;
23
+ }
24
+
25
+ /**
26
+ * Props shared by the React DOM and React Native feedback providers.
27
+ */
28
+ export interface FeedbackProviderProps {
29
+ /** Feedback UI rendered within the provider. */
30
+ children: ReactNode;
31
+
32
+ /**
33
+ * Partial localization or copy overrides.
34
+ *
35
+ * Unspecified values fall back to the built-in English messages.
36
+ */
37
+ messages?: FeedbackMessageOverrides;
38
+
39
+ /**
40
+ * Partial visual-theme overrides.
41
+ *
42
+ * Unspecified values fall back to the default feedback theme.
43
+ */
44
+ theme?: FeedbackThemeOverride;
45
+
46
+ /**
47
+ * Disables package-provided default styling.
48
+ *
49
+ * @default false
50
+ */
51
+ unstyled?: boolean;
52
+ }
53
+
54
+ /**
55
+ * Convenient semantic color overrides accepted by feedback UI containers.
56
+ *
57
+ * These override equivalent values from the provider theme for the
58
+ * corresponding subtree.
59
+ */
60
+ export interface FeedbackColorProps {
61
+ /** Primary interaction and accent color. */
62
+ primaryColor?: string;
63
+
64
+ /** Main background color. */
65
+ backgroundColor?: string;
66
+
67
+ /** Card and control surface color. */
68
+ surfaceColor?: string;
69
+
70
+ /** Primary text color. */
71
+ textColor?: string;
72
+
73
+ /** Secondary and helper text color. */
74
+ mutedColor?: string;
75
+
76
+ /** Border and separator color. */
77
+ borderColor?: string;
78
+
79
+ /** Destructive and error-state color. */
80
+ dangerColor?: string;
81
+ }