convex-feedback-ui 0.1.8 → 0.2.0-beta.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 (137) hide show
  1. package/README.md +30 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/native/expo/ExpoFeedbackBody.d.ts +1 -1
  4. package/dist/native/expo/ExpoFeedbackBody.d.ts.map +1 -1
  5. package/dist/native/expo/ExpoFeedbackBody.js +2 -2
  6. package/dist/native/expo/ExpoFeedbackBody.js.map +1 -1
  7. package/dist/native/expo/FeedbackRoutes.d.ts.map +1 -1
  8. package/dist/native/expo/FeedbackRoutes.js +17 -9
  9. package/dist/native/expo/FeedbackRoutes.js.map +1 -1
  10. package/dist/native/expo/FeedbackScreen.d.ts +1 -1
  11. package/dist/native/expo/FeedbackScreen.d.ts.map +1 -1
  12. package/dist/native/expo/FeedbackScreen.js +2 -2
  13. package/dist/native/expo/FeedbackScreen.js.map +1 -1
  14. package/dist/native/expo/FeedbackStack.d.ts +1 -1
  15. package/dist/native/expo/FeedbackStack.d.ts.map +1 -1
  16. package/dist/native/expo/FeedbackStack.js +15 -8
  17. package/dist/native/expo/FeedbackStack.js.map +1 -1
  18. package/dist/native/expo/FeedbackStackLayout.d.ts +1 -1
  19. package/dist/native/expo/FeedbackStackLayout.d.ts.map +1 -1
  20. package/dist/native/expo/FeedbackStackLayout.js +7 -2
  21. package/dist/native/expo/FeedbackStackLayout.js.map +1 -1
  22. package/dist/native/expo/index.d.ts +1 -0
  23. package/dist/native/expo/index.d.ts.map +1 -1
  24. package/dist/native/expo/index.js +1 -0
  25. package/dist/native/expo/index.js.map +1 -1
  26. package/dist/native/expo/types.d.ts +13 -0
  27. package/dist/native/expo/types.d.ts.map +1 -1
  28. package/dist/native/native/FeedbackScreen.d.ts +1 -1
  29. package/dist/native/native/FeedbackScreen.d.ts.map +1 -1
  30. package/dist/native/native/FeedbackScreen.js +2 -2
  31. package/dist/native/native/FeedbackScreen.js.map +1 -1
  32. package/dist/native/native/index.d.ts +1 -0
  33. package/dist/native/native/index.d.ts.map +1 -1
  34. package/dist/native/native/index.js +1 -0
  35. package/dist/native/native/index.js.map +1 -1
  36. package/dist/native/shared/helpers.d.ts +6 -1
  37. package/dist/native/shared/helpers.d.ts.map +1 -1
  38. package/dist/native/shared/helpers.js +33 -0
  39. package/dist/native/shared/helpers.js.map +1 -1
  40. package/dist/native/shared/ui/BodyContent.d.ts.map +1 -1
  41. package/dist/native/shared/ui/BodyContent.js +8 -2
  42. package/dist/native/shared/ui/BodyContent.js.map +1 -1
  43. package/dist/native/shared/ui/Button.d.ts +2 -1
  44. package/dist/native/shared/ui/Button.d.ts.map +1 -1
  45. package/dist/native/shared/ui/Button.js +3 -2
  46. package/dist/native/shared/ui/Button.js.map +1 -1
  47. package/dist/native/shared/ui/CommentBranch.d.ts.map +1 -1
  48. package/dist/native/shared/ui/CommentBranch.js +33 -11
  49. package/dist/native/shared/ui/CommentBranch.js.map +1 -1
  50. package/dist/native/shared/ui/EntryCard.d.ts.map +1 -1
  51. package/dist/native/shared/ui/EntryCard.js +13 -1
  52. package/dist/native/shared/ui/EntryCard.js.map +1 -1
  53. package/dist/native/shared/ui/EntryDetail.d.ts.map +1 -1
  54. package/dist/native/shared/ui/EntryDetail.js +23 -5
  55. package/dist/native/shared/ui/EntryDetail.js.map +1 -1
  56. package/dist/native/shared/ui/FeedbackScreenHeader.d.ts.map +1 -1
  57. package/dist/native/shared/ui/FeedbackScreenHeader.js +7 -2
  58. package/dist/native/shared/ui/FeedbackScreenHeader.js.map +1 -1
  59. package/dist/native/shared/ui/FeedbackScreenList.d.ts.map +1 -1
  60. package/dist/native/shared/ui/FeedbackScreenList.js +16 -8
  61. package/dist/native/shared/ui/FeedbackScreenList.js.map +1 -1
  62. package/dist/native/shared/ui/NewEntry.d.ts.map +1 -1
  63. package/dist/native/shared/ui/NewEntry.js +13 -11
  64. package/dist/native/shared/ui/NewEntry.js.map +1 -1
  65. package/dist/native/shared/ui/RoadmapScreen.d.ts +3 -0
  66. package/dist/native/shared/ui/RoadmapScreen.d.ts.map +1 -0
  67. package/dist/native/shared/ui/RoadmapScreen.js +73 -0
  68. package/dist/native/shared/ui/RoadmapScreen.js.map +1 -0
  69. package/dist/react/action.d.ts +14 -0
  70. package/dist/react/action.d.ts.map +1 -0
  71. package/dist/react/action.js +38 -0
  72. package/dist/react/action.js.map +1 -0
  73. package/dist/react/index.d.ts +1 -0
  74. package/dist/react/index.d.ts.map +1 -1
  75. package/dist/react/index.js +1 -0
  76. package/dist/react/index.js.map +1 -1
  77. package/dist/react/roadmap.d.ts +3 -0
  78. package/dist/react/roadmap.d.ts.map +1 -0
  79. package/dist/react/roadmap.js +59 -0
  80. package/dist/react/roadmap.js.map +1 -0
  81. package/dist/react/screen.d.ts +1 -1
  82. package/dist/react/screen.d.ts.map +1 -1
  83. package/dist/react/screen.js +99 -42
  84. package/dist/react/screen.js.map +1 -1
  85. package/dist/react/styles.css +42 -0
  86. package/dist/shared/context/FeedbackBodyProvider.d.ts +1 -1
  87. package/dist/shared/context/FeedbackBodyProvider.d.ts.map +1 -1
  88. package/dist/shared/context/FeedbackBodyProvider.js +8 -2
  89. package/dist/shared/context/FeedbackBodyProvider.js.map +1 -1
  90. package/dist/shared/helpers.d.ts +6 -0
  91. package/dist/shared/helpers.d.ts.map +1 -1
  92. package/dist/shared/helpers.js +12 -0
  93. package/dist/shared/helpers.js.map +1 -1
  94. package/dist/shared/types/index.d.ts +1 -0
  95. package/dist/shared/types/index.d.ts.map +1 -1
  96. package/dist/shared/types/messages.d.ts +1 -1
  97. package/dist/shared/types/messages.d.ts.map +1 -1
  98. package/dist/shared/types/roadmap.d.ts +17 -0
  99. package/dist/shared/types/roadmap.d.ts.map +1 -0
  100. package/dist/shared/types/roadmap.js +2 -0
  101. package/dist/shared/types/roadmap.js.map +1 -0
  102. package/dist/shared/types/screen.d.ts +10 -1
  103. package/dist/shared/types/screen.d.ts.map +1 -1
  104. package/package.json +2 -2
  105. package/src/native/expo/ExpoFeedbackBody.tsx +2 -0
  106. package/src/native/expo/FeedbackRoutes.tsx +54 -30
  107. package/src/native/expo/FeedbackScreen.tsx +2 -0
  108. package/src/native/expo/FeedbackStack.tsx +46 -27
  109. package/src/native/expo/FeedbackStackLayout.tsx +11 -1
  110. package/src/native/expo/README.md +15 -0
  111. package/src/native/expo/index.ts +1 -0
  112. package/src/native/expo/types.ts +12 -0
  113. package/src/native/native/FeedbackScreen.tsx +2 -0
  114. package/src/native/native/README.md +4 -0
  115. package/src/native/native/index.ts +1 -0
  116. package/src/native/shared/helpers.ts +38 -1
  117. package/src/native/shared/ui/BodyContent.tsx +15 -2
  118. package/src/native/shared/ui/Button.tsx +4 -0
  119. package/src/native/shared/ui/CommentBranch.tsx +58 -15
  120. package/src/native/shared/ui/EntryCard.tsx +21 -3
  121. package/src/native/shared/ui/EntryDetail.tsx +42 -8
  122. package/src/native/shared/ui/FeedbackScreenHeader.tsx +9 -1
  123. package/src/native/shared/ui/FeedbackScreenList.tsx +28 -13
  124. package/src/native/shared/ui/NewEntry.tsx +25 -12
  125. package/src/native/shared/ui/RoadmapScreen.tsx +345 -0
  126. package/src/react/README.md +1 -1
  127. package/src/react/action.tsx +56 -0
  128. package/src/react/index.ts +1 -0
  129. package/src/react/roadmap.tsx +303 -0
  130. package/src/react/screen.tsx +199 -75
  131. package/src/react/styles.css +42 -0
  132. package/src/shared/context/FeedbackBodyProvider.tsx +9 -1
  133. package/src/shared/helpers.ts +16 -0
  134. package/src/shared/types/index.ts +1 -0
  135. package/src/shared/types/messages.ts +1 -1
  136. package/src/shared/types/roadmap.ts +25 -0
  137. package/src/shared/types/screen.ts +12 -1
@@ -0,0 +1,303 @@
1
+ "use client";
2
+
3
+ import type { RoadmapItem } from "convex-feedback";
4
+ import { useState } from "react";
5
+
6
+ import {
7
+ FeedbackProvider,
8
+ useFeedbackUi,
9
+ } from "../shared/context/FeedbackProvider.js";
10
+ import { allowAuthenticatedAction } from "../shared/helpers.js";
11
+ import type { RoadmapScreenProps } from "../shared/types/index.js";
12
+ import { FeedbackActionError, useFeedbackAction } from "./action.js";
13
+ import { FeedbackBoard, FeedbackEntry } from "./primitives.js";
14
+
15
+ export function RoadmapScreen({
16
+ hooks,
17
+ messages,
18
+ theme,
19
+ unstyled,
20
+ onEntryOpen,
21
+ onUnauthenticated,
22
+ pageSize,
23
+ entryPageSize,
24
+ ...colors
25
+ }: RoadmapScreenProps) {
26
+ const resolvedPageSize = pageSize ?? hooks.pageSizes.roadmap;
27
+ const resolvedEntryPageSize = entryPageSize ?? hooks.pageSizes.entries;
28
+ return (
29
+ <FeedbackProvider messages={messages} theme={theme} unstyled={unstyled}>
30
+ <RoadmapScreenInner
31
+ hooks={hooks}
32
+ onEntryOpen={onEntryOpen}
33
+ onUnauthenticated={onUnauthenticated}
34
+ pageSize={resolvedPageSize}
35
+ entryPageSize={resolvedEntryPageSize}
36
+ {...colors}
37
+ />
38
+ </FeedbackProvider>
39
+ );
40
+ }
41
+
42
+ function RoadmapScreenInner({
43
+ hooks,
44
+ onEntryOpen,
45
+ onUnauthenticated,
46
+ pageSize,
47
+ entryPageSize,
48
+ ...colors
49
+ }: Omit<RoadmapScreenProps, "pageSize" | "entryPageSize"> & {
50
+ pageSize: number;
51
+ entryPageSize: number;
52
+ }) {
53
+ const { messages } = useFeedbackUi();
54
+ const [query, setQuery] = useState("");
55
+ const [selectedId, setSelectedId] = useState<string | null>(null);
56
+ const roadmap = hooks.useRoadmap();
57
+ const search = hooks.useSearchRoadmap(query);
58
+ const searching = query.trim().length > 0;
59
+ const items = searching ? (search ?? []) : roadmap.results;
60
+ const selected = items.find((item) => item.id === selectedId) ?? null;
61
+
62
+ if (selected !== null) {
63
+ return (
64
+ <FeedbackBoard.Root {...colors}>
65
+ <RoadmapDetail
66
+ item={selected}
67
+ hooks={hooks}
68
+ entryPageSize={entryPageSize}
69
+ onEntryOpen={onEntryOpen}
70
+ onUnauthenticated={onUnauthenticated}
71
+ onBack={() => setSelectedId(null)}
72
+ />
73
+ </FeedbackBoard.Root>
74
+ );
75
+ }
76
+
77
+ const loading = searching
78
+ ? search === undefined
79
+ : roadmap.status === "LoadingFirstPage";
80
+
81
+ return (
82
+ <FeedbackBoard.Root {...colors}>
83
+ <FeedbackBoard.Header>
84
+ <div>
85
+ <FeedbackBoard.Title>Roadmap</FeedbackBoard.Title>
86
+ <p className="cf-board__subtitle">
87
+ See what is planned, in progress, and shipped.
88
+ </p>
89
+ </div>
90
+ </FeedbackBoard.Header>
91
+ <FeedbackBoard.Search
92
+ value={query}
93
+ onValueChange={setQuery}
94
+ placeholder="Search roadmap…"
95
+ />
96
+ {loading ? (
97
+ <FeedbackBoard.State>{messages.board.loading}</FeedbackBoard.State>
98
+ ) : items.length === 0 ? (
99
+ <FeedbackBoard.State>
100
+ {searching ? "No roadmap items found." : "No roadmap items yet."}
101
+ </FeedbackBoard.State>
102
+ ) : (
103
+ <FeedbackBoard.List>
104
+ {items.map((item) => (
105
+ <RoadmapCard
106
+ key={item.id}
107
+ item={item}
108
+ onOpen={() => setSelectedId(item.id)}
109
+ />
110
+ ))}
111
+ </FeedbackBoard.List>
112
+ )}
113
+ {!searching &&
114
+ (roadmap.status === "CanLoadMore" ||
115
+ roadmap.status === "LoadingMore") && (
116
+ <button
117
+ type="button"
118
+ className="cf-button"
119
+ disabled={roadmap.status === "LoadingMore"}
120
+ onClick={() => roadmap.loadMore(pageSize)}
121
+ >
122
+ {messages.board.loadMore}
123
+ </button>
124
+ )}
125
+ </FeedbackBoard.Root>
126
+ );
127
+ }
128
+
129
+ function RoadmapCard({
130
+ item,
131
+ onOpen,
132
+ }: {
133
+ item: RoadmapItem;
134
+ onOpen: () => void;
135
+ }) {
136
+ return (
137
+ <button type="button" className="cf-entry cf-roadmap-card" onClick={onOpen}>
138
+ <span className="cf-entry__content">
139
+ <span className="cf-entry__meta">
140
+ <span className="cf-entry__kind">
141
+ {item.status.replaceAll("_", " ")}
142
+ </span>
143
+ <span className="cf-status">{item.feedbackCount} linked</span>
144
+ </span>
145
+ <strong className="cf-entry__title">{item.title}</strong>
146
+ {item.description && (
147
+ <span className="cf-entry__body">{item.description}</span>
148
+ )}
149
+ </span>
150
+ </button>
151
+ );
152
+ }
153
+
154
+ function RoadmapDetail({
155
+ item,
156
+ hooks,
157
+ entryPageSize,
158
+ onEntryOpen,
159
+ onUnauthenticated,
160
+ onBack,
161
+ }: {
162
+ item: RoadmapItem;
163
+ hooks: RoadmapScreenProps["hooks"];
164
+ entryPageSize: number;
165
+ onEntryOpen: RoadmapScreenProps["onEntryOpen"];
166
+ onUnauthenticated: RoadmapScreenProps["onUnauthenticated"];
167
+ onBack: () => void;
168
+ }) {
169
+ const { messages } = useFeedbackUi();
170
+ const feedback = hooks.useRoadmapFeedback(item.id);
171
+ return (
172
+ <div className="cf-detail">
173
+ <button
174
+ type="button"
175
+ className="cf-button cf-button--link"
176
+ onClick={onBack}
177
+ >
178
+ {messages.entry.back}
179
+ </button>
180
+ <header>
181
+ <div className="cf-entry__meta">
182
+ <span className="cf-entry__kind">
183
+ {item.status.replaceAll("_", " ")}
184
+ </span>
185
+ <span className="cf-status">Position {item.position}</span>
186
+ </div>
187
+ <h2 className="cf-board__title">{item.title}</h2>
188
+ <p className="cf-entry__body">
189
+ {item.description ?? "No description."}
190
+ </p>
191
+ </header>
192
+ <dl className="cf-roadmap-meta">
193
+ <div>
194
+ <dt>ID</dt>
195
+ <dd>{item.id}</dd>
196
+ </div>
197
+ <div>
198
+ <dt>Linked feedback</dt>
199
+ <dd>{item.feedbackCount}</dd>
200
+ </div>
201
+ <div>
202
+ <dt>Created</dt>
203
+ <dd>{new Date(item.createdAt).toLocaleString()}</dd>
204
+ </div>
205
+ <div>
206
+ <dt>Updated</dt>
207
+ <dd>{new Date(item.updatedAt).toLocaleString()}</dd>
208
+ </div>
209
+ <div>
210
+ <dt>Document created</dt>
211
+ <dd>{new Date(item.creationTime).toLocaleString()}</dd>
212
+ </div>
213
+ </dl>
214
+ <section className="cf-discussion">
215
+ <h3>Attached feedback</h3>
216
+ {feedback.status === "LoadingFirstPage" ? (
217
+ <p className="cf-state">{messages.board.loading}</p>
218
+ ) : feedback.results.length === 0 ? (
219
+ <p className="cf-state">No feedback attached.</p>
220
+ ) : (
221
+ <div className="cf-comments">
222
+ {feedback.results.map((entry) => (
223
+ <RoadmapEntry
224
+ key={entry.id}
225
+ entry={entry}
226
+ hooks={hooks}
227
+ onOpen={onEntryOpen}
228
+ onUnauthenticated={onUnauthenticated}
229
+ />
230
+ ))}
231
+ </div>
232
+ )}
233
+ {(feedback.status === "CanLoadMore" ||
234
+ feedback.status === "LoadingMore") && (
235
+ <button
236
+ type="button"
237
+ className="cf-button"
238
+ disabled={feedback.status === "LoadingMore"}
239
+ onClick={() => feedback.loadMore(entryPageSize)}
240
+ >
241
+ {messages.comments.loadMore}
242
+ </button>
243
+ )}
244
+ </section>
245
+ </div>
246
+ );
247
+ }
248
+
249
+ function RoadmapEntry({
250
+ entry,
251
+ hooks,
252
+ onOpen,
253
+ onUnauthenticated,
254
+ }: {
255
+ entry: Parameters<typeof FeedbackEntry.Root>[0]["entry"];
256
+ hooks: RoadmapScreenProps["hooks"];
257
+ onOpen: RoadmapScreenProps["onEntryOpen"];
258
+ onUnauthenticated: RoadmapScreenProps["onUnauthenticated"];
259
+ }) {
260
+ const isAuthenticated = hooks.useIsAuthenticated();
261
+ const setUpvote = hooks.useSetEntryUpvote();
262
+ const action = useFeedbackAction();
263
+ const toggle = (desiredState: boolean) => {
264
+ if (
265
+ !allowAuthenticatedAction(isAuthenticated, onUnauthenticated) ||
266
+ action.pending
267
+ ) {
268
+ return;
269
+ }
270
+ void action.run(() => setUpvote({ entryId: entry.id, desiredState }));
271
+ };
272
+
273
+ return (
274
+ <>
275
+ <FeedbackEntry.Root entry={entry}>
276
+ <FeedbackEntry.Upvote
277
+ disabled={isAuthenticated === undefined || action.pending}
278
+ onToggle={toggle}
279
+ />
280
+ <FeedbackEntry.Content
281
+ onClick={() => onOpen?.(entry.id)}
282
+ onKeyDown={(event) => {
283
+ if (event.key === "Enter" || event.key === " ") {
284
+ event.preventDefault();
285
+ onOpen?.(entry.id);
286
+ }
287
+ }}
288
+ role={onOpen === undefined ? undefined : "button"}
289
+ tabIndex={onOpen === undefined ? undefined : 0}
290
+ >
291
+ <div className="cf-entry__meta">
292
+ <FeedbackEntry.Kind />
293
+ <FeedbackEntry.Status />
294
+ <FeedbackEntry.CommentCount />
295
+ </div>
296
+ <FeedbackEntry.Title />
297
+ <FeedbackEntry.Body />
298
+ </FeedbackEntry.Content>
299
+ </FeedbackEntry.Root>
300
+ <FeedbackActionError failure={action.failure} />
301
+ </>
302
+ );
303
+ }