convex-feedback-ui 0.2.0-beta.2 → 0.2.0-beta.3
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 +38 -0
- package/dist/.tsbuildinfo +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 +44 -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 +36 -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 +56 -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 +4 -1
- package/dist/native/expo/index.d.ts.map +1 -1
- package/dist/native/expo/index.js +4 -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/shared/ui/RoadmapScreen.d.ts +21 -1
- package/dist/native/shared/ui/RoadmapScreen.d.ts.map +1 -1
- package/dist/native/shared/ui/RoadmapScreen.js +228 -50
- 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 +24 -12
- package/dist/react/roadmap.js.map +1 -1
- package/dist/react/styles.css +123 -0
- package/dist/shared/messages.d.ts.map +1 -1
- package/dist/shared/messages.js +23 -0
- package/dist/shared/messages.js.map +1 -1
- package/dist/shared/types/messages.d.ts +26 -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/package.json +2 -2
- package/src/native/expo/README.md +6 -2
- package/src/native/expo/RoadmapRoutes.tsx +133 -0
- package/src/native/expo/RoadmapScreen.tsx +101 -0
- package/src/native/expo/RoadmapStackLayout.tsx +111 -0
- package/src/native/expo/RoutedRoadmapContext.tsx +21 -0
- package/src/native/expo/index.ts +8 -1
- package/src/native/expo/routes.ts +69 -1
- package/src/native/expo/types.ts +43 -0
- package/src/native/native/README.md +4 -3
- package/src/native/shared/ui/RoadmapScreen.tsx +550 -228
- package/src/native/shared/ui/primitives.tsx +3 -0
- package/src/react/roadmap.tsx +145 -85
- package/src/react/styles.css +123 -0
- package/src/shared/messages.ts +24 -0
- package/src/shared/types/messages.ts +38 -1
- package/src/shared/types/roadmap.ts +18 -1
|
@@ -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,24 @@ 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 className="cf-roadmap-card__count">
|
|
219
|
+
{messages.roadmap.linkedEntries(item.feedbackCount)}
|
|
220
|
+
</span>
|
|
221
|
+
</span>
|
|
222
|
+
<strong className="cf-roadmap-card__title">{item.title}</strong>
|
|
223
|
+
{item.description && (
|
|
224
|
+
<span className="cf-roadmap-card__body">{item.description}</span>
|
|
225
|
+
)}
|
|
226
|
+
<span className="cf-roadmap-card__link">
|
|
227
|
+
{messages.entry.open} <span aria-hidden="true">›</span>
|
|
149
228
|
</span>
|
|
150
229
|
</button>
|
|
151
230
|
);
|
|
@@ -168,6 +247,7 @@ function RoadmapDetail({
|
|
|
168
247
|
}) {
|
|
169
248
|
const { messages } = useFeedbackUi();
|
|
170
249
|
const feedback = hooks.useRoadmapFeedback(item.id);
|
|
250
|
+
|
|
171
251
|
return (
|
|
172
252
|
<div className="cf-detail">
|
|
173
253
|
<button
|
|
@@ -177,46 +257,26 @@ function RoadmapDetail({
|
|
|
177
257
|
>
|
|
178
258
|
{messages.entry.back}
|
|
179
259
|
</button>
|
|
180
|
-
<header>
|
|
181
|
-
<div className="cf-
|
|
182
|
-
<span className="cf-
|
|
183
|
-
{item.status
|
|
260
|
+
<header className="cf-roadmap-detail__header">
|
|
261
|
+
<div className="cf-roadmap-card__meta">
|
|
262
|
+
<span className="cf-roadmap-status">
|
|
263
|
+
{messages.roadmap.statuses[item.status]}
|
|
264
|
+
</span>
|
|
265
|
+
<span className="cf-roadmap-card__count">
|
|
266
|
+
{messages.roadmap.linkedEntries(item.feedbackCount)}
|
|
184
267
|
</span>
|
|
185
|
-
<span className="cf-status">Position {item.position}</span>
|
|
186
268
|
</div>
|
|
187
269
|
<h2 className="cf-board__title">{item.title}</h2>
|
|
188
|
-
|
|
189
|
-
{item.description
|
|
190
|
-
|
|
270
|
+
{item.description && (
|
|
271
|
+
<p className="cf-entry__body">{item.description}</p>
|
|
272
|
+
)}
|
|
191
273
|
</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
274
|
<section className="cf-discussion">
|
|
215
|
-
<h3>
|
|
275
|
+
<h3>{messages.roadmap.attachedFeedback}</h3>
|
|
216
276
|
{feedback.status === "LoadingFirstPage" ? (
|
|
217
277
|
<p className="cf-state">{messages.board.loading}</p>
|
|
218
278
|
) : feedback.results.length === 0 ? (
|
|
219
|
-
<p className="cf-state">
|
|
279
|
+
<p className="cf-state">{messages.roadmap.noAttachedFeedback}</p>
|
|
220
280
|
) : (
|
|
221
281
|
<div className="cf-comments">
|
|
222
282
|
{feedback.results.map((entry) => (
|
|
@@ -238,7 +298,7 @@ function RoadmapDetail({
|
|
|
238
298
|
disabled={feedback.status === "LoadingMore"}
|
|
239
299
|
onClick={() => feedback.loadMore(entryPageSize)}
|
|
240
300
|
>
|
|
241
|
-
{messages.
|
|
301
|
+
{messages.board.loadMore}
|
|
242
302
|
</button>
|
|
243
303
|
)}
|
|
244
304
|
</section>
|
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;
|
|
@@ -348,6 +467,10 @@
|
|
|
348
467
|
width: 40px;
|
|
349
468
|
min-height: 48px;
|
|
350
469
|
}
|
|
470
|
+
:where(.cf-board) :where(.cf-roadmap-board) {
|
|
471
|
+
grid-template-columns: repeat(3, minmax(17rem, 1fr));
|
|
472
|
+
overflow-x: auto;
|
|
473
|
+
}
|
|
351
474
|
}
|
|
352
475
|
:where(.cf-board) :where(.cf-confirm-backdrop) {
|
|
353
476
|
position: fixed;
|
package/src/shared/messages.ts
CHANGED
|
@@ -12,6 +12,22 @@ 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} linked ${count === 1 ? "entry" : "entries"}`,
|
|
26
|
+
noItems: "No roadmap items yet.",
|
|
27
|
+
noSearchResults: "No matching roadmap items found.",
|
|
28
|
+
attachedFeedback: "Feedback behind this item",
|
|
29
|
+
noAttachedFeedback: "No feedback attached yet.",
|
|
30
|
+
},
|
|
15
31
|
kinds: {
|
|
16
32
|
feedback: "Feedback",
|
|
17
33
|
feature_request: "Feature request",
|
|
@@ -86,6 +102,14 @@ export function mergeFeedbackMessages(
|
|
|
86
102
|
): FeedbackMessages {
|
|
87
103
|
return {
|
|
88
104
|
board: { ...englishFeedbackMessages.board, ...overrides.board },
|
|
105
|
+
roadmap: {
|
|
106
|
+
...englishFeedbackMessages.roadmap,
|
|
107
|
+
...overrides.roadmap,
|
|
108
|
+
statuses: {
|
|
109
|
+
...englishFeedbackMessages.roadmap.statuses,
|
|
110
|
+
...overrides.roadmap?.statuses,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
89
113
|
kinds: {
|
|
90
114
|
...englishFeedbackMessages.kinds,
|
|
91
115
|
...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,36 @@ 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 entries attached to an item. */
|
|
58
|
+
linkedEntries: (count: number) => string;
|
|
59
|
+
|
|
60
|
+
/** Empty-state copy when the roadmap has no items. */
|
|
61
|
+
noItems: string;
|
|
62
|
+
|
|
63
|
+
/** Empty-state copy when a search produces no results. */
|
|
64
|
+
noSearchResults: string;
|
|
65
|
+
|
|
66
|
+
/** Heading for feedback attached to a roadmap item. */
|
|
67
|
+
attachedFeedback: string;
|
|
68
|
+
|
|
69
|
+
/** Empty-state copy when an item has no attached feedback. */
|
|
70
|
+
noAttachedFeedback: string;
|
|
71
|
+
};
|
|
72
|
+
|
|
43
73
|
/** Localized display labels for each fixed entry kind. */
|
|
44
74
|
kinds: Record<EntryKind, string>;
|
|
45
75
|
|
|
@@ -176,6 +206,13 @@ export interface FeedbackMessageOverrides {
|
|
|
176
206
|
/** Board copy overrides. */
|
|
177
207
|
board?: Partial<FeedbackMessages["board"]> | undefined;
|
|
178
208
|
|
|
209
|
+
/** Roadmap copy overrides. */
|
|
210
|
+
roadmap?:
|
|
211
|
+
| (Omit<Partial<FeedbackMessages["roadmap"]>, "statuses"> & {
|
|
212
|
+
statuses?: Partial<FeedbackMessages["roadmap"]["statuses"]>;
|
|
213
|
+
})
|
|
214
|
+
| undefined;
|
|
215
|
+
|
|
179
216
|
/** Entry-kind label overrides. */
|
|
180
217
|
kinds?: Partial<FeedbackMessages["kinds"]> | undefined;
|
|
181
218
|
|
|
@@ -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
|
|