convex-feedback-ui 0.2.0-beta.2 → 0.2.0-beta.4
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 +40 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/native/expo/FeedbackRoutes.d.ts.map +1 -1
- package/dist/native/expo/FeedbackRoutes.js +15 -2
- package/dist/native/expo/FeedbackRoutes.js.map +1 -1
- package/dist/native/expo/RoadmapRoutes.d.ts +3 -0
- package/dist/native/expo/RoadmapRoutes.d.ts.map +1 -0
- package/dist/native/expo/RoadmapRoutes.js +61 -0
- package/dist/native/expo/RoadmapRoutes.js.map +1 -0
- package/dist/native/expo/RoadmapScreen.d.ts +24 -0
- package/dist/native/expo/RoadmapScreen.d.ts.map +1 -0
- package/dist/native/expo/RoadmapScreen.js +43 -0
- package/dist/native/expo/RoadmapScreen.js.map +1 -0
- package/dist/native/expo/RoadmapStackLayout.d.ts +8 -0
- package/dist/native/expo/RoadmapStackLayout.d.ts.map +1 -0
- package/dist/native/expo/RoadmapStackLayout.js +55 -0
- package/dist/native/expo/RoadmapStackLayout.js.map +1 -0
- package/dist/native/expo/RoutedRoadmapContext.d.ts +4 -0
- package/dist/native/expo/RoutedRoadmapContext.d.ts.map +1 -0
- package/dist/native/expo/RoutedRoadmapContext.js +11 -0
- package/dist/native/expo/RoutedRoadmapContext.js.map +1 -0
- package/dist/native/expo/index.d.ts +5 -1
- package/dist/native/expo/index.d.ts.map +1 -1
- package/dist/native/expo/index.js +5 -1
- package/dist/native/expo/index.js.map +1 -1
- package/dist/native/expo/routes.d.ts +17 -1
- package/dist/native/expo/routes.d.ts.map +1 -1
- package/dist/native/expo/routes.js +46 -0
- package/dist/native/expo/routes.js.map +1 -1
- package/dist/native/expo/types.d.ts +35 -1
- package/dist/native/expo/types.d.ts.map +1 -1
- package/dist/native/native/index.d.ts +1 -0
- package/dist/native/native/index.d.ts.map +1 -1
- package/dist/native/native/index.js +1 -0
- package/dist/native/native/index.js.map +1 -1
- package/dist/native/shared/ui/EntryCard.d.ts +1 -1
- package/dist/native/shared/ui/EntryCard.d.ts.map +1 -1
- package/dist/native/shared/ui/EntryCard.js +2 -2
- package/dist/native/shared/ui/EntryCard.js.map +1 -1
- package/dist/native/shared/ui/RoadmapBoard.d.ts +55 -0
- package/dist/native/shared/ui/RoadmapBoard.d.ts.map +1 -0
- package/dist/native/shared/ui/RoadmapBoard.js +89 -0
- package/dist/native/shared/ui/RoadmapBoard.js.map +1 -0
- package/dist/native/shared/ui/RoadmapScreen.d.ts +36 -1
- package/dist/native/shared/ui/RoadmapScreen.d.ts.map +1 -1
- package/dist/native/shared/ui/RoadmapScreen.js +143 -51
- package/dist/native/shared/ui/RoadmapScreen.js.map +1 -1
- package/dist/native/shared/ui/primitives.d.ts +1 -1
- package/dist/native/shared/ui/primitives.d.ts.map +1 -1
- package/dist/native/shared/ui/primitives.js +3 -1
- package/dist/native/shared/ui/primitives.js.map +1 -1
- package/dist/react/roadmap.d.ts.map +1 -1
- package/dist/react/roadmap.js +25 -13
- package/dist/react/roadmap.js.map +1 -1
- package/dist/react/styles.css +129 -0
- package/dist/shared/messages.d.ts.map +1 -1
- package/dist/shared/messages.js +24 -0
- package/dist/shared/messages.js.map +1 -1
- package/dist/shared/types/messages.d.ts +28 -1
- package/dist/shared/types/messages.d.ts.map +1 -1
- package/dist/shared/types/roadmap.d.ts +10 -1
- package/dist/shared/types/roadmap.d.ts.map +1 -1
- package/dist/shared/types/screen.d.ts +2 -0
- package/dist/shared/types/screen.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/native/expo/FeedbackRoutes.tsx +30 -7
- package/src/native/expo/README.md +10 -2
- package/src/native/expo/RoadmapRoutes.tsx +171 -0
- package/src/native/expo/RoadmapScreen.tsx +177 -0
- package/src/native/expo/RoadmapStackLayout.tsx +110 -0
- package/src/native/expo/RoutedRoadmapContext.tsx +21 -0
- package/src/native/expo/index.ts +10 -1
- package/src/native/expo/routes.ts +69 -1
- package/src/native/expo/types.ts +43 -0
- package/src/native/native/README.md +9 -3
- package/src/native/native/index.ts +1 -0
- package/src/native/shared/ui/EntryCard.tsx +2 -1
- package/src/native/shared/ui/RoadmapBoard.tsx +225 -0
- package/src/native/shared/ui/RoadmapScreen.tsx +415 -242
- package/src/native/shared/ui/primitives.tsx +3 -0
- package/src/react/roadmap.tsx +140 -85
- package/src/react/styles.css +129 -0
- package/src/shared/messages.ts +25 -0
- package/src/shared/types/messages.ts +41 -1
- package/src/shared/types/roadmap.ts +18 -1
- package/src/shared/types/screen.ts +2 -0
|
@@ -54,6 +54,7 @@ export type BoardRootProps = PropsWithChildren<FeedbackColorProps>;
|
|
|
54
54
|
|
|
55
55
|
function BoardRoot({
|
|
56
56
|
primaryColor,
|
|
57
|
+
primaryForeground,
|
|
57
58
|
backgroundColor,
|
|
58
59
|
surfaceColor,
|
|
59
60
|
textColor,
|
|
@@ -65,6 +66,7 @@ function BoardRoot({
|
|
|
65
66
|
const colorOverrides = useMemo(
|
|
66
67
|
() => ({
|
|
67
68
|
...(primaryColor === undefined ? {} : { primary: primaryColor }),
|
|
69
|
+
...(primaryForeground === undefined ? {} : { primaryForeground }),
|
|
68
70
|
...(backgroundColor === undefined ? {} : { background: backgroundColor }),
|
|
69
71
|
...(surfaceColor === undefined ? {} : { surface: surfaceColor }),
|
|
70
72
|
...(textColor === undefined ? {} : { text: textColor }),
|
|
@@ -78,6 +80,7 @@ function BoardRoot({
|
|
|
78
80
|
dangerColor,
|
|
79
81
|
mutedColor,
|
|
80
82
|
primaryColor,
|
|
83
|
+
primaryForeground,
|
|
81
84
|
surfaceColor,
|
|
82
85
|
textColor,
|
|
83
86
|
],
|
package/src/react/roadmap.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import type { RoadmapItem } from "convex-feedback";
|
|
3
|
+
import type { RoadmapItem, RoadmapStatus } from "convex-feedback";
|
|
4
4
|
import { useState } from "react";
|
|
5
5
|
|
|
6
6
|
import {
|
|
@@ -12,6 +12,12 @@ import type { RoadmapScreenProps } from "../shared/types/index.js";
|
|
|
12
12
|
import { FeedbackActionError, useFeedbackAction } from "./action.js";
|
|
13
13
|
import { FeedbackBoard, FeedbackEntry } from "./primitives.js";
|
|
14
14
|
|
|
15
|
+
const stages: readonly { value: RoadmapStatus }[] = [
|
|
16
|
+
{ value: "planned" },
|
|
17
|
+
{ value: "in_progress" },
|
|
18
|
+
{ value: "shipped" },
|
|
19
|
+
];
|
|
20
|
+
|
|
15
21
|
export function RoadmapScreen({
|
|
16
22
|
hooks,
|
|
17
23
|
messages,
|
|
@@ -25,6 +31,7 @@ export function RoadmapScreen({
|
|
|
25
31
|
}: RoadmapScreenProps) {
|
|
26
32
|
const resolvedPageSize = pageSize ?? hooks.pageSizes.roadmap;
|
|
27
33
|
const resolvedEntryPageSize = entryPageSize ?? hooks.pageSizes.entries;
|
|
34
|
+
|
|
28
35
|
return (
|
|
29
36
|
<FeedbackProvider messages={messages} theme={theme} unstyled={unstyled}>
|
|
30
37
|
<RoadmapScreenInner
|
|
@@ -50,7 +57,6 @@ function RoadmapScreenInner({
|
|
|
50
57
|
pageSize: number;
|
|
51
58
|
entryPageSize: number;
|
|
52
59
|
}) {
|
|
53
|
-
const { messages } = useFeedbackUi();
|
|
54
60
|
const [query, setQuery] = useState("");
|
|
55
61
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
56
62
|
const roadmap = hooks.useRoadmap();
|
|
@@ -59,9 +65,29 @@ function RoadmapScreenInner({
|
|
|
59
65
|
const items = searching ? (search ?? []) : roadmap.results;
|
|
60
66
|
const selected = items.find((item) => item.id === selectedId) ?? null;
|
|
61
67
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
return (
|
|
69
|
+
<FeedbackBoard.Root {...colors}>
|
|
70
|
+
{selected === null ? (
|
|
71
|
+
<RoadmapBoard
|
|
72
|
+
items={items}
|
|
73
|
+
loading={
|
|
74
|
+
searching
|
|
75
|
+
? search === undefined
|
|
76
|
+
: roadmap.status === "LoadingFirstPage"
|
|
77
|
+
}
|
|
78
|
+
searching={searching}
|
|
79
|
+
query={query}
|
|
80
|
+
onQueryChange={setQuery}
|
|
81
|
+
onItemOpen={(item) => setSelectedId(item.id)}
|
|
82
|
+
canLoadMore={
|
|
83
|
+
!searching &&
|
|
84
|
+
(roadmap.status === "CanLoadMore" ||
|
|
85
|
+
roadmap.status === "LoadingMore")
|
|
86
|
+
}
|
|
87
|
+
loadingMore={roadmap.status === "LoadingMore"}
|
|
88
|
+
onLoadMore={() => roadmap.loadMore(pageSize)}
|
|
89
|
+
/>
|
|
90
|
+
) : (
|
|
65
91
|
<RoadmapDetail
|
|
66
92
|
item={selected}
|
|
67
93
|
hooks={hooks}
|
|
@@ -70,59 +96,107 @@ function RoadmapScreenInner({
|
|
|
70
96
|
onUnauthenticated={onUnauthenticated}
|
|
71
97
|
onBack={() => setSelectedId(null)}
|
|
72
98
|
/>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
99
|
+
)}
|
|
100
|
+
</FeedbackBoard.Root>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
76
103
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
104
|
+
function RoadmapBoard({
|
|
105
|
+
items,
|
|
106
|
+
loading,
|
|
107
|
+
searching,
|
|
108
|
+
query,
|
|
109
|
+
onQueryChange,
|
|
110
|
+
onItemOpen,
|
|
111
|
+
canLoadMore,
|
|
112
|
+
loadingMore,
|
|
113
|
+
onLoadMore,
|
|
114
|
+
}: {
|
|
115
|
+
items: readonly RoadmapItem[];
|
|
116
|
+
loading: boolean;
|
|
117
|
+
searching: boolean;
|
|
118
|
+
query: string;
|
|
119
|
+
onQueryChange: (value: string) => void;
|
|
120
|
+
onItemOpen: (item: RoadmapItem) => void;
|
|
121
|
+
canLoadMore: boolean;
|
|
122
|
+
loadingMore: boolean;
|
|
123
|
+
onLoadMore: () => void;
|
|
124
|
+
}) {
|
|
125
|
+
const { messages } = useFeedbackUi();
|
|
80
126
|
|
|
81
127
|
return (
|
|
82
|
-
|
|
128
|
+
<>
|
|
83
129
|
<FeedbackBoard.Header>
|
|
84
130
|
<div>
|
|
85
|
-
<FeedbackBoard.Title>
|
|
86
|
-
<p className="cf-board__subtitle">
|
|
87
|
-
See what is planned, in progress, and shipped.
|
|
88
|
-
</p>
|
|
131
|
+
<FeedbackBoard.Title>{messages.roadmap.title}</FeedbackBoard.Title>
|
|
132
|
+
<p className="cf-board__subtitle">{messages.roadmap.subtitle}</p>
|
|
89
133
|
</div>
|
|
90
134
|
</FeedbackBoard.Header>
|
|
91
135
|
<FeedbackBoard.Search
|
|
92
136
|
value={query}
|
|
93
|
-
onValueChange={
|
|
94
|
-
placeholder=
|
|
137
|
+
onValueChange={onQueryChange}
|
|
138
|
+
placeholder={messages.roadmap.searchPlaceholder}
|
|
95
139
|
/>
|
|
96
140
|
{loading ? (
|
|
97
141
|
<FeedbackBoard.State>{messages.board.loading}</FeedbackBoard.State>
|
|
98
142
|
) : items.length === 0 ? (
|
|
99
143
|
<FeedbackBoard.State>
|
|
100
|
-
{searching
|
|
144
|
+
{searching
|
|
145
|
+
? messages.roadmap.noSearchResults
|
|
146
|
+
: messages.roadmap.noItems}
|
|
101
147
|
</FeedbackBoard.State>
|
|
102
148
|
) : (
|
|
103
|
-
<
|
|
104
|
-
{
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
149
|
+
<div className="cf-roadmap-board">
|
|
150
|
+
{stages.map((stage) => {
|
|
151
|
+
const stageItems = items
|
|
152
|
+
.filter((item) => item.status === stage.value)
|
|
153
|
+
.sort((a, b) => a.position - b.position);
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<section className="cf-roadmap-column" key={stage.value}>
|
|
157
|
+
<header className="cf-roadmap-column__header">
|
|
158
|
+
<div className="cf-roadmap-column__heading">
|
|
159
|
+
<span
|
|
160
|
+
className="cf-roadmap-status__dot"
|
|
161
|
+
aria-hidden="true"
|
|
162
|
+
/>
|
|
163
|
+
<h3>{messages.roadmap.statuses[stage.value]}</h3>
|
|
164
|
+
</div>
|
|
165
|
+
<span className="cf-roadmap-column__count">
|
|
166
|
+
{stageItems.length}
|
|
167
|
+
</span>
|
|
168
|
+
</header>
|
|
169
|
+
<div className="cf-roadmap-column__list">
|
|
170
|
+
{stageItems.length === 0 ? (
|
|
171
|
+
<p className="cf-roadmap-column__empty">
|
|
172
|
+
{messages.roadmap.noItems}
|
|
173
|
+
</p>
|
|
174
|
+
) : (
|
|
175
|
+
stageItems.map((item) => (
|
|
176
|
+
<RoadmapCard
|
|
177
|
+
key={item.id}
|
|
178
|
+
item={item}
|
|
179
|
+
onOpen={() => onItemOpen(item)}
|
|
180
|
+
/>
|
|
181
|
+
))
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
</section>
|
|
185
|
+
);
|
|
186
|
+
})}
|
|
187
|
+
</div>
|
|
112
188
|
)}
|
|
113
|
-
{
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
)}
|
|
125
|
-
</FeedbackBoard.Root>
|
|
189
|
+
{canLoadMore && (
|
|
190
|
+
<button
|
|
191
|
+
type="button"
|
|
192
|
+
className="cf-button"
|
|
193
|
+
disabled={loadingMore}
|
|
194
|
+
onClick={onLoadMore}
|
|
195
|
+
>
|
|
196
|
+
{messages.board.loadMore}
|
|
197
|
+
</button>
|
|
198
|
+
)}
|
|
199
|
+
</>
|
|
126
200
|
);
|
|
127
201
|
}
|
|
128
202
|
|
|
@@ -133,19 +207,21 @@ function RoadmapCard({
|
|
|
133
207
|
item: RoadmapItem;
|
|
134
208
|
onOpen: () => void;
|
|
135
209
|
}) {
|
|
210
|
+
const { messages } = useFeedbackUi();
|
|
211
|
+
|
|
136
212
|
return (
|
|
137
|
-
<button type="button" className="cf-
|
|
138
|
-
<span className="cf-
|
|
139
|
-
<span className="cf-
|
|
140
|
-
|
|
141
|
-
{item.status.replaceAll("_", " ")}
|
|
142
|
-
</span>
|
|
143
|
-
<span className="cf-status">{item.feedbackCount} linked</span>
|
|
213
|
+
<button type="button" className="cf-roadmap-card" onClick={onOpen}>
|
|
214
|
+
<span className="cf-roadmap-card__meta">
|
|
215
|
+
<span className="cf-roadmap-status">
|
|
216
|
+
{messages.roadmap.statuses[item.status]}
|
|
144
217
|
</span>
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
218
|
+
</span>
|
|
219
|
+
<strong className="cf-roadmap-card__title">{item.title}</strong>
|
|
220
|
+
{item.description && (
|
|
221
|
+
<span className="cf-roadmap-card__body">{item.description}</span>
|
|
222
|
+
)}
|
|
223
|
+
<span className="cf-roadmap-card__link">
|
|
224
|
+
{messages.entry.open} <span aria-hidden="true">›</span>
|
|
149
225
|
</span>
|
|
150
226
|
</button>
|
|
151
227
|
);
|
|
@@ -168,6 +244,7 @@ function RoadmapDetail({
|
|
|
168
244
|
}) {
|
|
169
245
|
const { messages } = useFeedbackUi();
|
|
170
246
|
const feedback = hooks.useRoadmapFeedback(item.id);
|
|
247
|
+
|
|
171
248
|
return (
|
|
172
249
|
<div className="cf-detail">
|
|
173
250
|
<button
|
|
@@ -177,46 +254,23 @@ function RoadmapDetail({
|
|
|
177
254
|
>
|
|
178
255
|
{messages.entry.back}
|
|
179
256
|
</button>
|
|
180
|
-
<header>
|
|
181
|
-
<div className="cf-
|
|
182
|
-
<span className="cf-
|
|
183
|
-
{item.status
|
|
257
|
+
<header className="cf-roadmap-detail__header">
|
|
258
|
+
<div className="cf-roadmap-card__meta">
|
|
259
|
+
<span className="cf-roadmap-status">
|
|
260
|
+
{messages.roadmap.statuses[item.status]}
|
|
184
261
|
</span>
|
|
185
|
-
<span className="cf-status">Position {item.position}</span>
|
|
186
262
|
</div>
|
|
187
263
|
<h2 className="cf-board__title">{item.title}</h2>
|
|
188
|
-
|
|
189
|
-
{item.description
|
|
190
|
-
|
|
264
|
+
{item.description && (
|
|
265
|
+
<p className="cf-entry__body">{item.description}</p>
|
|
266
|
+
)}
|
|
191
267
|
</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
268
|
<section className="cf-discussion">
|
|
215
|
-
<h3>
|
|
269
|
+
<h3>{messages.roadmap.attachedFeedback}</h3>
|
|
216
270
|
{feedback.status === "LoadingFirstPage" ? (
|
|
217
271
|
<p className="cf-state">{messages.board.loading}</p>
|
|
218
272
|
) : feedback.results.length === 0 ? (
|
|
219
|
-
<p className="cf-state">
|
|
273
|
+
<p className="cf-state">{messages.roadmap.noAttachedFeedback}</p>
|
|
220
274
|
) : (
|
|
221
275
|
<div className="cf-comments">
|
|
222
276
|
{feedback.results.map((entry) => (
|
|
@@ -238,7 +292,7 @@ function RoadmapDetail({
|
|
|
238
292
|
disabled={feedback.status === "LoadingMore"}
|
|
239
293
|
onClick={() => feedback.loadMore(entryPageSize)}
|
|
240
294
|
>
|
|
241
|
-
{messages.
|
|
295
|
+
{messages.board.loadMore}
|
|
242
296
|
</button>
|
|
243
297
|
)}
|
|
244
298
|
</section>
|
|
@@ -278,6 +332,7 @@ function RoadmapEntry({
|
|
|
278
332
|
onToggle={toggle}
|
|
279
333
|
/>
|
|
280
334
|
<FeedbackEntry.Content
|
|
335
|
+
className="cf-roadmap-entry__content"
|
|
281
336
|
onClick={() => onOpen?.(entry.id)}
|
|
282
337
|
onKeyDown={(event) => {
|
|
283
338
|
if (event.key === "Enter" || event.key === " ") {
|
package/src/react/styles.css
CHANGED
|
@@ -95,11 +95,130 @@
|
|
|
95
95
|
background: var(--cf-surface);
|
|
96
96
|
}
|
|
97
97
|
:where(.cf-board) :where(.cf-roadmap-card) {
|
|
98
|
+
display: grid;
|
|
99
|
+
gap: 10px;
|
|
98
100
|
width: 100%;
|
|
101
|
+
border: 1px solid var(--cf-border);
|
|
102
|
+
border-radius: calc(var(--cf-radius) * 0.9);
|
|
103
|
+
padding: 14px;
|
|
104
|
+
background: var(--cf-surface);
|
|
99
105
|
color: inherit;
|
|
100
106
|
font: inherit;
|
|
101
107
|
text-align: left;
|
|
102
108
|
cursor: pointer;
|
|
109
|
+
transition:
|
|
110
|
+
transform 140ms ease,
|
|
111
|
+
border-color 140ms ease,
|
|
112
|
+
box-shadow 140ms ease;
|
|
113
|
+
}
|
|
114
|
+
:where(.cf-board) :where(.cf-roadmap-card:hover) {
|
|
115
|
+
transform: translateY(-1px);
|
|
116
|
+
border-color: color-mix(in srgb, var(--cf-primary) 35%, var(--cf-border));
|
|
117
|
+
box-shadow: 0 8px 20px rgb(20 20 35 / 0.06);
|
|
118
|
+
}
|
|
119
|
+
:where(.cf-board) :where(.cf-roadmap-card:focus-visible) {
|
|
120
|
+
outline: 3px solid color-mix(in srgb, var(--cf-primary) 25%, transparent);
|
|
121
|
+
outline-offset: 2px;
|
|
122
|
+
}
|
|
123
|
+
:where(.cf-board) :where(.cf-roadmap-board) {
|
|
124
|
+
display: grid;
|
|
125
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
126
|
+
gap: 1px;
|
|
127
|
+
margin-bottom: var(--cf-space);
|
|
128
|
+
overflow: hidden;
|
|
129
|
+
border: 1px solid var(--cf-border);
|
|
130
|
+
border-radius: var(--cf-radius);
|
|
131
|
+
background: var(--cf-border);
|
|
132
|
+
}
|
|
133
|
+
:where(.cf-board) :where(.cf-roadmap-column) {
|
|
134
|
+
min-width: 0;
|
|
135
|
+
background: var(--cf-background);
|
|
136
|
+
}
|
|
137
|
+
:where(.cf-board) :where(.cf-roadmap-column__header) {
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
justify-content: space-between;
|
|
141
|
+
gap: 10px;
|
|
142
|
+
min-height: 58px;
|
|
143
|
+
padding: 12px 14px;
|
|
144
|
+
border-bottom: 1px solid var(--cf-border);
|
|
145
|
+
background: color-mix(in srgb, var(--cf-background) 94%, var(--cf-surface));
|
|
146
|
+
}
|
|
147
|
+
:where(.cf-board) :where(.cf-roadmap-column__heading) {
|
|
148
|
+
display: flex;
|
|
149
|
+
align-items: center;
|
|
150
|
+
gap: 8px;
|
|
151
|
+
}
|
|
152
|
+
:where(.cf-board) :where(.cf-roadmap-column__heading h3) {
|
|
153
|
+
margin: 0;
|
|
154
|
+
font-size: 0.9rem;
|
|
155
|
+
}
|
|
156
|
+
:where(.cf-board) :where(.cf-roadmap-column__count),
|
|
157
|
+
:where(.cf-board) :where(.cf-roadmap-card__count) {
|
|
158
|
+
color: var(--cf-muted);
|
|
159
|
+
font-size: 0.75rem;
|
|
160
|
+
font-weight: 600;
|
|
161
|
+
}
|
|
162
|
+
:where(.cf-board) :where(.cf-roadmap-column__list) {
|
|
163
|
+
display: grid;
|
|
164
|
+
align-content: start;
|
|
165
|
+
gap: 10px;
|
|
166
|
+
min-height: 180px;
|
|
167
|
+
padding: 10px;
|
|
168
|
+
}
|
|
169
|
+
:where(.cf-board) :where(.cf-roadmap-column__empty) {
|
|
170
|
+
margin: 0;
|
|
171
|
+
padding: 28px 10px;
|
|
172
|
+
color: var(--cf-muted);
|
|
173
|
+
font-size: 0.85rem;
|
|
174
|
+
line-height: 1.45;
|
|
175
|
+
text-align: center;
|
|
176
|
+
}
|
|
177
|
+
:where(.cf-board) :where(.cf-roadmap-status__dot) {
|
|
178
|
+
width: 7px;
|
|
179
|
+
height: 7px;
|
|
180
|
+
border-radius: 999px;
|
|
181
|
+
background: var(--cf-primary);
|
|
182
|
+
}
|
|
183
|
+
:where(.cf-board) :where(.cf-roadmap-status) {
|
|
184
|
+
display: inline-flex;
|
|
185
|
+
width: fit-content;
|
|
186
|
+
border: 1px solid var(--cf-border);
|
|
187
|
+
border-radius: 999px;
|
|
188
|
+
padding: 4px 8px;
|
|
189
|
+
background: var(--cf-surface-muted);
|
|
190
|
+
color: var(--cf-muted);
|
|
191
|
+
font-size: 0.74rem;
|
|
192
|
+
font-weight: 700;
|
|
193
|
+
}
|
|
194
|
+
:where(.cf-board) :where(.cf-roadmap-card__meta) {
|
|
195
|
+
display: flex;
|
|
196
|
+
align-items: center;
|
|
197
|
+
justify-content: space-between;
|
|
198
|
+
gap: 8px;
|
|
199
|
+
}
|
|
200
|
+
:where(.cf-board) :where(.cf-roadmap-card__title) {
|
|
201
|
+
font-size: 1rem;
|
|
202
|
+
line-height: 1.35;
|
|
203
|
+
}
|
|
204
|
+
:where(.cf-board) :where(.cf-roadmap-card__body) {
|
|
205
|
+
overflow: hidden;
|
|
206
|
+
color: var(--cf-muted);
|
|
207
|
+
font-size: 0.88rem;
|
|
208
|
+
line-height: 1.5;
|
|
209
|
+
white-space: pre-wrap;
|
|
210
|
+
display: -webkit-box;
|
|
211
|
+
line-clamp: 3;
|
|
212
|
+
-webkit-box-orient: vertical;
|
|
213
|
+
}
|
|
214
|
+
:where(.cf-board) :where(.cf-roadmap-card__link) {
|
|
215
|
+
color: var(--cf-primary);
|
|
216
|
+
font-size: 0.82rem;
|
|
217
|
+
font-weight: 700;
|
|
218
|
+
}
|
|
219
|
+
:where(.cf-board) :where(.cf-roadmap-detail__header) {
|
|
220
|
+
display: grid;
|
|
221
|
+
gap: 10px;
|
|
103
222
|
}
|
|
104
223
|
:where(.cf-board) :where(.cf-entry__content) {
|
|
105
224
|
min-width: 0;
|
|
@@ -109,6 +228,12 @@
|
|
|
109
228
|
margin: 5px 0 3px;
|
|
110
229
|
font-size: 1rem;
|
|
111
230
|
}
|
|
231
|
+
:where(.cf-board) :where(.cf-roadmap-entry__content .cf-entry__title) {
|
|
232
|
+
overflow: hidden;
|
|
233
|
+
display: -webkit-box;
|
|
234
|
+
line-clamp: 1;
|
|
235
|
+
-webkit-box-orient: vertical;
|
|
236
|
+
}
|
|
112
237
|
:where(.cf-board) :where(.cf-entry__body) {
|
|
113
238
|
margin: 0;
|
|
114
239
|
color: var(--cf-muted);
|
|
@@ -348,6 +473,10 @@
|
|
|
348
473
|
width: 40px;
|
|
349
474
|
min-height: 48px;
|
|
350
475
|
}
|
|
476
|
+
:where(.cf-board) :where(.cf-roadmap-board) {
|
|
477
|
+
grid-template-columns: repeat(3, minmax(17rem, 1fr));
|
|
478
|
+
overflow-x: auto;
|
|
479
|
+
}
|
|
351
480
|
}
|
|
352
481
|
:where(.cf-board) :where(.cf-confirm-backdrop) {
|
|
353
482
|
position: fixed;
|
package/src/shared/messages.ts
CHANGED
|
@@ -12,6 +12,23 @@ export const englishFeedbackMessages: FeedbackMessages = {
|
|
|
12
12
|
loading: "Loading…",
|
|
13
13
|
loadMore: "Load more",
|
|
14
14
|
},
|
|
15
|
+
roadmap: {
|
|
16
|
+
title: "Roadmap",
|
|
17
|
+
subtitle: "See what is planned, in progress, and shipped.",
|
|
18
|
+
searchPlaceholder: "Search roadmap…",
|
|
19
|
+
statuses: {
|
|
20
|
+
planned: "Planned",
|
|
21
|
+
in_progress: "In progress",
|
|
22
|
+
shipped: "Shipped",
|
|
23
|
+
},
|
|
24
|
+
linkedEntries: (count) =>
|
|
25
|
+
`${count} ${count === 1 ? "feedback item" : "feedback items"}`,
|
|
26
|
+
emptyStage: "Nothing here yet.",
|
|
27
|
+
noItems: "No roadmap items yet.",
|
|
28
|
+
noSearchResults: "No matching roadmap items found.",
|
|
29
|
+
attachedFeedback: "Feedback",
|
|
30
|
+
noAttachedFeedback: "No feedback yet.",
|
|
31
|
+
},
|
|
15
32
|
kinds: {
|
|
16
33
|
feedback: "Feedback",
|
|
17
34
|
feature_request: "Feature request",
|
|
@@ -86,6 +103,14 @@ export function mergeFeedbackMessages(
|
|
|
86
103
|
): FeedbackMessages {
|
|
87
104
|
return {
|
|
88
105
|
board: { ...englishFeedbackMessages.board, ...overrides.board },
|
|
106
|
+
roadmap: {
|
|
107
|
+
...englishFeedbackMessages.roadmap,
|
|
108
|
+
...overrides.roadmap,
|
|
109
|
+
statuses: {
|
|
110
|
+
...englishFeedbackMessages.roadmap.statuses,
|
|
111
|
+
...overrides.roadmap?.statuses,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
89
114
|
kinds: {
|
|
90
115
|
...englishFeedbackMessages.kinds,
|
|
91
116
|
...overrides.kinds,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EntryKind, EntryStatus } from "convex-feedback";
|
|
1
|
+
import type { EntryKind, EntryStatus, RoadmapStatus } from "convex-feedback";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Localizable package-owned UI copy used by both React DOM and React Native.
|
|
@@ -40,6 +40,39 @@ export interface FeedbackMessages {
|
|
|
40
40
|
loadMore: string;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
/** Public roadmap copy. */
|
|
44
|
+
roadmap: {
|
|
45
|
+
/** Main roadmap heading. */
|
|
46
|
+
title: string;
|
|
47
|
+
|
|
48
|
+
/** Supporting copy shown near the roadmap heading. */
|
|
49
|
+
subtitle: string;
|
|
50
|
+
|
|
51
|
+
/** Search input placeholder. */
|
|
52
|
+
searchPlaceholder: string;
|
|
53
|
+
|
|
54
|
+
/** Display labels for each roadmap stage. */
|
|
55
|
+
statuses: Record<RoadmapStatus, string>;
|
|
56
|
+
|
|
57
|
+
/** Formats the number of feedback items attached to an item. */
|
|
58
|
+
linkedEntries: (count: number) => string;
|
|
59
|
+
|
|
60
|
+
/** Empty-state copy for a roadmap stage column. */
|
|
61
|
+
emptyStage: string;
|
|
62
|
+
|
|
63
|
+
/** Empty-state copy when the roadmap has no items. */
|
|
64
|
+
noItems: string;
|
|
65
|
+
|
|
66
|
+
/** Empty-state copy when a search produces no results. */
|
|
67
|
+
noSearchResults: string;
|
|
68
|
+
|
|
69
|
+
/** Heading for feedback shown on a roadmap item. */
|
|
70
|
+
attachedFeedback: string;
|
|
71
|
+
|
|
72
|
+
/** Empty-state copy when an item has no attached feedback. */
|
|
73
|
+
noAttachedFeedback: string;
|
|
74
|
+
};
|
|
75
|
+
|
|
43
76
|
/** Localized display labels for each fixed entry kind. */
|
|
44
77
|
kinds: Record<EntryKind, string>;
|
|
45
78
|
|
|
@@ -176,6 +209,13 @@ export interface FeedbackMessageOverrides {
|
|
|
176
209
|
/** Board copy overrides. */
|
|
177
210
|
board?: Partial<FeedbackMessages["board"]> | undefined;
|
|
178
211
|
|
|
212
|
+
/** Roadmap copy overrides. */
|
|
213
|
+
roadmap?:
|
|
214
|
+
| (Omit<Partial<FeedbackMessages["roadmap"]>, "statuses"> & {
|
|
215
|
+
statuses?: Partial<FeedbackMessages["roadmap"]["statuses"]>;
|
|
216
|
+
})
|
|
217
|
+
| undefined;
|
|
218
|
+
|
|
179
219
|
/** Entry-kind label overrides. */
|
|
180
220
|
kinds?: Partial<FeedbackMessages["kinds"]> | undefined;
|
|
181
221
|
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import type { CommentSort } from "convex-feedback";
|
|
1
2
|
import type { FeedbackHooks } from "convex-feedback/react";
|
|
2
3
|
|
|
3
4
|
import type { FeedbackColorProps, FeedbackProviderProps } from "./context.js";
|
|
4
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
FeedbackCommentTransform,
|
|
7
|
+
FeedbackActorRenderer,
|
|
8
|
+
FeedbackUnauthenticatedHandler,
|
|
9
|
+
} from "./screen.js";
|
|
5
10
|
|
|
6
11
|
/** Shared props for the public roadmap screen implementations. */
|
|
7
12
|
export interface RoadmapScreenProps
|
|
@@ -17,6 +22,18 @@ export interface RoadmapScreenProps
|
|
|
17
22
|
/** Called when a visitor tries an authenticated-only action anonymously. */
|
|
18
23
|
onUnauthenticated?: FeedbackUnauthenticatedHandler;
|
|
19
24
|
|
|
25
|
+
/** Server-side ordering used when displaying comments on an attached entry. */
|
|
26
|
+
commentSort?: CommentSort;
|
|
27
|
+
|
|
28
|
+
/** Maximum depth exposed when an attached entry's discussion is opened. */
|
|
29
|
+
maxCommentDepth?: number;
|
|
30
|
+
|
|
31
|
+
/** Presentation-only transform applied to loaded comments. */
|
|
32
|
+
transformComments?: FeedbackCommentTransform;
|
|
33
|
+
|
|
34
|
+
/** Optional host renderer for actor information in comments. */
|
|
35
|
+
renderActor?: FeedbackActorRenderer;
|
|
36
|
+
|
|
20
37
|
/** Number of roadmap items initially requested. */
|
|
21
38
|
pageSize?: number;
|
|
22
39
|
|
|
@@ -183,6 +183,8 @@ export interface FeedbackScreenEntryCardProps {
|
|
|
183
183
|
hooks: FeedbackHooks;
|
|
184
184
|
/** Callback when the EntryCard is opened. */
|
|
185
185
|
onOpen: () => void;
|
|
186
|
+
/** Optional line limit for the entry title in compact lists. */
|
|
187
|
+
titleNumberOfLines?: number;
|
|
186
188
|
}
|
|
187
189
|
|
|
188
190
|
export interface FeedbackScreenEntryModalProps {
|