convex-feedback-ui 0.1.3 → 0.1.5

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 (104) hide show
  1. package/README.md +53 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/native/expo/FeedbackScreen.d.ts +1 -1
  4. package/dist/native/expo/FeedbackScreen.d.ts.map +1 -1
  5. package/dist/native/expo/FeedbackScreen.js +3 -2
  6. package/dist/native/expo/FeedbackScreen.js.map +1 -1
  7. package/dist/native/expo/index.d.ts +2 -0
  8. package/dist/native/expo/index.d.ts.map +1 -1
  9. package/dist/native/expo/index.js +2 -0
  10. package/dist/native/expo/index.js.map +1 -1
  11. package/dist/native/expo/metadata.d.ts +4 -0
  12. package/dist/native/expo/metadata.d.ts.map +1 -0
  13. package/dist/native/expo/metadata.js +44 -0
  14. package/dist/native/expo/metadata.js.map +1 -0
  15. package/dist/native/native/FeedbackScreen.d.ts +1 -1
  16. package/dist/native/native/FeedbackScreen.d.ts.map +1 -1
  17. package/dist/native/native/FeedbackScreen.js +3 -2
  18. package/dist/native/native/FeedbackScreen.js.map +1 -1
  19. package/dist/native/native/index.d.ts +2 -0
  20. package/dist/native/native/index.d.ts.map +1 -1
  21. package/dist/native/native/index.js +2 -0
  22. package/dist/native/native/index.js.map +1 -1
  23. package/dist/native/shared/metadata.d.ts +5 -0
  24. package/dist/native/shared/metadata.d.ts.map +1 -0
  25. package/dist/native/shared/metadata.js +31 -0
  26. package/dist/native/shared/metadata.js.map +1 -0
  27. package/dist/native/shared/ui/CommentBranch.d.ts +2 -0
  28. package/dist/native/shared/ui/CommentBranch.d.ts.map +1 -1
  29. package/dist/native/shared/ui/CommentBranch.js +12 -2
  30. package/dist/native/shared/ui/CommentBranch.js.map +1 -1
  31. package/dist/native/shared/ui/EntryDetail.d.ts.map +1 -1
  32. package/dist/native/shared/ui/EntryDetail.js +3 -1
  33. package/dist/native/shared/ui/EntryDetail.js.map +1 -1
  34. package/dist/native/shared/ui/FeedbackScreenHeader.d.ts.map +1 -1
  35. package/dist/native/shared/ui/FeedbackScreenHeader.js +10 -8
  36. package/dist/native/shared/ui/FeedbackScreenHeader.js.map +1 -1
  37. package/dist/native/shared/ui/FeedbackScreenList.js +1 -1
  38. package/dist/native/shared/ui/FeedbackScreenList.js.map +1 -1
  39. package/dist/native/shared/ui/MetadataModal.d.ts +6 -0
  40. package/dist/native/shared/ui/MetadataModal.d.ts.map +1 -0
  41. package/dist/native/shared/ui/MetadataModal.js +39 -0
  42. package/dist/native/shared/ui/MetadataModal.js.map +1 -0
  43. package/dist/native/shared/ui/NewEntry.d.ts.map +1 -1
  44. package/dist/native/shared/ui/NewEntry.js +10 -3
  45. package/dist/native/shared/ui/NewEntry.js.map +1 -1
  46. package/dist/react/index.d.ts +2 -0
  47. package/dist/react/index.d.ts.map +1 -1
  48. package/dist/react/index.js +2 -0
  49. package/dist/react/index.js.map +1 -1
  50. package/dist/react/metadata.d.ts +4 -0
  51. package/dist/react/metadata.d.ts.map +1 -0
  52. package/dist/react/metadata.js +40 -0
  53. package/dist/react/metadata.js.map +1 -0
  54. package/dist/react/screen.d.ts +1 -1
  55. package/dist/react/screen.d.ts.map +1 -1
  56. package/dist/react/screen.js +18 -4
  57. package/dist/react/screen.js.map +1 -1
  58. package/dist/react/styles.css +35 -0
  59. package/dist/shared/context/FeedbackBodyProvider.d.ts +1 -1
  60. package/dist/shared/context/FeedbackBodyProvider.d.ts.map +1 -1
  61. package/dist/shared/context/FeedbackBodyProvider.js +5 -1
  62. package/dist/shared/context/FeedbackBodyProvider.js.map +1 -1
  63. package/dist/shared/messages.d.ts.map +1 -1
  64. package/dist/shared/messages.js +9 -0
  65. package/dist/shared/messages.js.map +1 -1
  66. package/dist/shared/metadata.d.ts +6 -0
  67. package/dist/shared/metadata.d.ts.map +1 -0
  68. package/dist/shared/metadata.js +54 -0
  69. package/dist/shared/metadata.js.map +1 -0
  70. package/dist/shared/types/index.d.ts +1 -0
  71. package/dist/shared/types/index.d.ts.map +1 -1
  72. package/dist/shared/types/messages.d.ts +17 -0
  73. package/dist/shared/types/messages.d.ts.map +1 -1
  74. package/dist/shared/types/metadata.d.ts +22 -0
  75. package/dist/shared/types/metadata.d.ts.map +1 -0
  76. package/dist/shared/types/metadata.js +2 -0
  77. package/dist/shared/types/metadata.js.map +1 -0
  78. package/dist/shared/types/screen.d.ts +10 -1
  79. package/dist/shared/types/screen.d.ts.map +1 -1
  80. package/package.json +6 -2
  81. package/src/native/expo/FeedbackScreen.tsx +4 -0
  82. package/src/native/expo/index.ts +2 -0
  83. package/src/native/expo/metadata.ts +62 -0
  84. package/src/native/native/FeedbackScreen.tsx +4 -0
  85. package/src/native/native/index.ts +2 -0
  86. package/src/native/shared/metadata.ts +39 -0
  87. package/src/native/shared/ui/CommentBranch.tsx +36 -2
  88. package/src/native/shared/ui/EntryDetail.tsx +14 -0
  89. package/src/native/shared/ui/FeedbackScreenHeader.tsx +23 -21
  90. package/src/native/shared/ui/FeedbackScreenList.tsx +1 -1
  91. package/src/native/shared/ui/MetadataModal.tsx +112 -0
  92. package/src/native/shared/ui/NewEntry.tsx +17 -3
  93. package/src/react/index.ts +2 -0
  94. package/src/react/metadata.ts +51 -0
  95. package/src/react/screen.tsx +89 -2
  96. package/src/react/styles.css +35 -0
  97. package/src/shared/context/FeedbackBodyProvider.tsx +8 -0
  98. package/src/shared/messages.ts +9 -0
  99. package/src/shared/metadata.ts +84 -0
  100. package/src/shared/types/index.ts +1 -0
  101. package/src/shared/types/messages.ts +24 -0
  102. package/src/shared/types/metadata.ts +55 -0
  103. package/src/shared/types/screen.ts +13 -2
  104. package/src/native/shared/ui/ReplyList.tsx +0 -41
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import type { EntryKind } from "convex-feedback";
3
+ import type { EntryKind, FeedbackMetadata } from "convex-feedback";
4
4
  import { useMemo, useState, type SyntheticEvent } from "react";
5
5
 
6
6
  import {
@@ -26,6 +26,8 @@ import {
26
26
  FeedbackEntry,
27
27
  FeedbackForm,
28
28
  } from "./primitives.js";
29
+ import { collectWebMetadata } from "./metadata.js";
30
+ import { collectEntryMetadata, formatMetadataKey } from "../shared/metadata.js";
29
31
 
30
32
  const entryKinds: readonly EntryKind[] = [
31
33
  "feedback",
@@ -49,6 +51,7 @@ export function FeedbackScreen({
49
51
  transformComments,
50
52
  renderActor,
51
53
  debounceDuration = 300,
54
+ collectMetadata,
52
55
  ...props
53
56
  }: FeedbackScreenProps) {
54
57
  return (
@@ -60,6 +63,8 @@ export function FeedbackScreen({
60
63
  enabledKinds={enabledKinds}
61
64
  maxCommentDepth={maxCommentDepth}
62
65
  debounceDuration={debounceDuration}
66
+ collectMetadata={collectMetadata}
67
+ collectStandardMetadata={collectWebMetadata}
63
68
  transformComments={transformComments}
64
69
  renderActor={renderActor}
65
70
  >
@@ -207,7 +212,8 @@ function EntryCard({ entry, hooks, onOpen }: FeedbackScreenEntryCardProps) {
207
212
  }
208
213
 
209
214
  function CreateEntryForm({ onCreated }: FeedbackScreenEntryModalProps) {
210
- const { hooks, enabledKinds } = useFeedbackBody();
215
+ const { hooks, enabledKinds, collectMetadata, collectStandardMetadata } =
216
+ useFeedbackBody();
211
217
  const { messages } = useFeedbackUi();
212
218
  const [kind, setKind] = useState<EntryKind>(enabledKinds[0] ?? "feedback");
213
219
  const [title, setTitle] = useState("");
@@ -242,10 +248,16 @@ function CreateEntryForm({ onCreated }: FeedbackScreenEntryModalProps) {
242
248
  setSubmitting(true);
243
249
 
244
250
  try {
251
+ const metadata = await collectEntryMetadata(
252
+ collectMetadata,
253
+ kind,
254
+ collectStandardMetadata,
255
+ );
245
256
  const entryId = await createEntry({
246
257
  kind,
247
258
  title,
248
259
  body,
260
+ ...(metadata === undefined ? {} : { metadata }),
249
261
  });
250
262
 
251
263
  onCreated(entryId);
@@ -382,6 +394,7 @@ function EntryDetail({ entryId, onBack }: FeedbackScreenEntryDetailProps) {
382
394
  const comments = hooks.useComments({ entryId, sort: commentSort });
383
395
  const createComment = hooks.useCreateComment();
384
396
  const [body, setBody] = useState("");
397
+ const [showMetadata, setShowMetadata] = useState(false);
385
398
 
386
399
  const visibleComments = useMemo(
387
400
  () => transformComments?.(comments.results) ?? comments.results,
@@ -422,6 +435,25 @@ function EntryDetail({ entryId, onBack }: FeedbackScreenEntryDetailProps) {
422
435
  </FeedbackEntry.Content>
423
436
  </FeedbackEntry.Root>
424
437
 
438
+ {entry.metadata !== undefined && (
439
+ <div>
440
+ <button
441
+ type="button"
442
+ className="cf-button"
443
+ onClick={() => setShowMetadata(true)}
444
+ >
445
+ {messages.metadata.view}
446
+ </button>
447
+ </div>
448
+ )}
449
+
450
+ {showMetadata && entry.metadata !== undefined && (
451
+ <MetadataDialog
452
+ metadata={entry.metadata}
453
+ onClose={() => setShowMetadata(false)}
454
+ />
455
+ )}
456
+
425
457
  <section className="cf-discussion">
426
458
  <h3>{messages.comments.title}</h3>
427
459
  <FeedbackForm.Root
@@ -473,6 +505,61 @@ function EntryDetail({ entryId, onBack }: FeedbackScreenEntryDetailProps) {
473
505
  );
474
506
  }
475
507
 
508
+ function MetadataDialog({
509
+ metadata,
510
+ onClose,
511
+ }: {
512
+ metadata: FeedbackMetadata;
513
+ onClose: () => void;
514
+ }) {
515
+ const { messages } = useFeedbackUi();
516
+ const sections = [
517
+ [messages.metadata.standard, metadata.standard],
518
+ [messages.metadata.additional, metadata.additional],
519
+ ] as const;
520
+ const hasValues = sections.some(
521
+ ([, values]) => values !== undefined && Object.keys(values).length > 0,
522
+ );
523
+
524
+ return (
525
+ <div className="cf-confirm-backdrop" onMouseDown={onClose}>
526
+ <div
527
+ className="cf-confirm cf-metadata-dialog"
528
+ role="dialog"
529
+ aria-modal="true"
530
+ aria-labelledby="cf-metadata-title"
531
+ onMouseDown={(event) => event.stopPropagation()}
532
+ >
533
+ <h3 id="cf-metadata-title">{messages.metadata.title}</h3>
534
+ {hasValues ? (
535
+ sections.map(([label, values]) =>
536
+ values === undefined || Object.keys(values).length === 0 ? null : (
537
+ <section key={label} className="cf-metadata-section">
538
+ <h4>{label}</h4>
539
+ <dl>
540
+ {Object.entries(values).map(([key, value]) => (
541
+ <div key={key} className="cf-metadata-row">
542
+ <dt>{formatMetadataKey(key)}</dt>
543
+ <dd>{String(value)}</dd>
544
+ </div>
545
+ ))}
546
+ </dl>
547
+ </section>
548
+ ),
549
+ )
550
+ ) : (
551
+ <p>{messages.metadata.empty}</p>
552
+ )}
553
+ <div className="cf-inline-actions">
554
+ <button type="button" className="cf-button" onClick={onClose}>
555
+ {messages.metadata.close}
556
+ </button>
557
+ </div>
558
+ </div>
559
+ </div>
560
+ );
561
+ }
562
+
476
563
  function CommentBranch({ comment, entryId }: FeedbackScreenCommentBranchProps) {
477
564
  const { hooks, maxCommentDepth, renderActor } = useFeedbackBody();
478
565
  const { messages } = useFeedbackUi();
@@ -297,3 +297,38 @@
297
297
  color: var(--cf-muted);
298
298
  line-height: 1.5;
299
299
  }
300
+
301
+ :where(.cf-board) :where(.cf-metadata-dialog) {
302
+ width: min(100%, 560px);
303
+ max-height: min(80vh, 720px);
304
+ overflow: auto;
305
+ }
306
+
307
+ :where(.cf-board) :where(.cf-metadata-dialog h3),
308
+ :where(.cf-board) :where(.cf-metadata-dialog h4),
309
+ :where(.cf-board) :where(.cf-metadata-dialog dl) {
310
+ margin: 0;
311
+ }
312
+
313
+ :where(.cf-board) :where(.cf-metadata-section) {
314
+ display: grid;
315
+ gap: 8px;
316
+ }
317
+
318
+ :where(.cf-board) :where(.cf-metadata-row) {
319
+ display: grid;
320
+ grid-template-columns: minmax(120px, 0.8fr) minmax(0, 1.2fr);
321
+ gap: 12px;
322
+ padding: 8px 0;
323
+ border-bottom: 1px solid var(--cf-border);
324
+ }
325
+
326
+ :where(.cf-board) :where(.cf-metadata-row dt) {
327
+ color: var(--cf-muted);
328
+ font-weight: 600;
329
+ }
330
+
331
+ :where(.cf-board) :where(.cf-metadata-row dd) {
332
+ min-width: 0;
333
+ overflow-wrap: anywhere;
334
+ }
@@ -22,6 +22,10 @@ export function FeedbackBodyProvider({
22
22
  enabledKinds,
23
23
  maxCommentDepth,
24
24
  debounceDuration,
25
+ collectMetadata,
26
+ collectStandardMetadata,
27
+ transformComments,
28
+ renderActor,
25
29
  children,
26
30
  }: PropsWithChildren<FeedbackScreenProviderProps>) {
27
31
  const [selectedEntryId, setSelectedEntryId] = useState<string | null>(null);
@@ -60,6 +64,10 @@ export function FeedbackBodyProvider({
60
64
  enabledKinds,
61
65
  maxCommentDepth,
62
66
  debounceDuration,
67
+ collectMetadata,
68
+ collectStandardMetadata,
69
+ transformComments,
70
+ renderActor,
63
71
  } satisfies FeedbackScreenBodyContextValue;
64
72
 
65
73
  return (
@@ -41,6 +41,14 @@ export const englishFeedbackMessages: FeedbackMessages = {
41
41
  open: "Open",
42
42
  back: "Go back",
43
43
  },
44
+ metadata: {
45
+ view: "View metadata",
46
+ title: "Diagnostic metadata",
47
+ standard: "Standard",
48
+ additional: "Additional",
49
+ empty: "No metadata values were collected.",
50
+ close: "Close",
51
+ },
44
52
  form: {
45
53
  kind: "Type",
46
54
  title: "Title",
@@ -94,6 +102,7 @@ export function mergeFeedbackMessages(
94
102
  ...overrides.statuses,
95
103
  },
96
104
  entry: { ...englishFeedbackMessages.entry, ...overrides.entry },
105
+ metadata: { ...englishFeedbackMessages.metadata, ...overrides.metadata },
97
106
  form: { ...englishFeedbackMessages.form, ...overrides.form },
98
107
  comments: { ...englishFeedbackMessages.comments, ...overrides.comments },
99
108
  };
@@ -0,0 +1,84 @@
1
+ import type {
2
+ EntryKind,
3
+ FeedbackMetadata,
4
+ FeedbackMetadataValue,
5
+ } from "convex-feedback";
6
+
7
+ import type {
8
+ CollectMetadata,
9
+ EnabledStandardMetadataSelection,
10
+ MetadataCollectionContext,
11
+ MetadataSource,
12
+ StandardMetadataKey,
13
+ } from "./types/metadata.js";
14
+
15
+ export type StandardMetadataCollector = (
16
+ selection: EnabledStandardMetadataSelection,
17
+ ) => Partial<Record<StandardMetadataKey, FeedbackMetadataValue>>;
18
+
19
+ async function resolveAdditionalMetadata(
20
+ source: MetadataSource | undefined,
21
+ context: MetadataCollectionContext,
22
+ ): Promise<Record<string, FeedbackMetadataValue>> {
23
+ if (source === undefined) return {};
24
+
25
+ try {
26
+ return typeof source === "function" ? await source(context) : source;
27
+ } catch {
28
+ return {};
29
+ }
30
+ }
31
+
32
+ export async function collectEntryMetadata(
33
+ config: CollectMetadata | undefined,
34
+ kind: EntryKind,
35
+ collectStandard: StandardMetadataCollector,
36
+ ): Promise<FeedbackMetadata | undefined> {
37
+ if (config === undefined || config === false) return undefined;
38
+
39
+ const root = config === true ? { standard: true as const } : config;
40
+ const kindConfig = root.kinds?.[kind];
41
+ if (kindConfig === false) return undefined;
42
+
43
+ const kindOptions =
44
+ kindConfig === true ? { standard: true as const } : kindConfig;
45
+ const standardSelection = kindOptions?.standard ?? root.standard ?? false;
46
+ const context = { kind } satisfies MetadataCollectionContext;
47
+ const [globalAdditional, kindAdditional] = await Promise.all([
48
+ resolveAdditionalMetadata(root.additional, context),
49
+ resolveAdditionalMetadata(kindOptions?.additional, context),
50
+ ]);
51
+
52
+ let standard: FeedbackMetadata["standard"];
53
+ try {
54
+ standard =
55
+ standardSelection === false
56
+ ? undefined
57
+ : collectStandard(standardSelection);
58
+ } catch {
59
+ standard = undefined;
60
+ }
61
+
62
+ const additional = { ...globalAdditional, ...kindAdditional };
63
+ const hasStandard =
64
+ standard !== undefined && Object.keys(standard).length > 0;
65
+ const hasAdditional = Object.keys(additional).length > 0;
66
+
67
+ if (!hasStandard && !hasAdditional) return undefined;
68
+
69
+ return {
70
+ ...(hasStandard ? { standard } : {}),
71
+ ...(hasAdditional ? { additional } : {}),
72
+ };
73
+ }
74
+
75
+ export function formatMetadataKey(key: string): string {
76
+ const words = key
77
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
78
+ .replace(/[_-]+/g, " ")
79
+ .trim();
80
+
81
+ return words.length === 0
82
+ ? key
83
+ : words.charAt(0).toLocaleUpperCase("en-US") + words.slice(1);
84
+ }
@@ -4,6 +4,7 @@ export type * from "./context.js";
4
4
  export type * from "./entry.js";
5
5
  export type * from "./form.js";
6
6
  export type * from "./messages.js";
7
+ export type * from "./metadata.js";
7
8
  export type * from "./render.js";
8
9
  export type * from "./screen.js";
9
10
  export type * from "./theme.js";
@@ -67,6 +67,27 @@ export interface FeedbackMessages {
67
67
  back: string;
68
68
  };
69
69
 
70
+ /** Moderator-only diagnostic metadata copy. */
71
+ metadata: {
72
+ /** Action that opens the metadata viewer. */
73
+ view: string;
74
+
75
+ /** Metadata viewer heading. */
76
+ title: string;
77
+
78
+ /** Heading for automatically collected values. */
79
+ standard: string;
80
+
81
+ /** Heading for host-provided values. */
82
+ additional: string;
83
+
84
+ /** Empty state for a metadata object without values. */
85
+ empty: string;
86
+
87
+ /** Closes the metadata viewer. */
88
+ close: string;
89
+ };
90
+
70
91
  /** Create-entry form copy. */
71
92
  form: {
72
93
  /** Entry-kind field label. */
@@ -167,6 +188,9 @@ export interface FeedbackMessageOverrides {
167
188
  /** Entry action/counter copy overrides. */
168
189
  entry?: Partial<FeedbackMessages["entry"]> | undefined;
169
190
 
191
+ /** Diagnostic metadata copy overrides. */
192
+ metadata?: Partial<FeedbackMessages["metadata"]> | undefined;
193
+
170
194
  /** Create-entry form copy overrides. */
171
195
  form?: Partial<FeedbackMessages["form"]> | undefined;
172
196
 
@@ -0,0 +1,55 @@
1
+ import type { EntryKind, FeedbackMetadataValue } from "convex-feedback";
2
+
3
+ export type StandardMetadataKey =
4
+ | "platform"
5
+ | "userAgent"
6
+ | "language"
7
+ | "timezone"
8
+ | "screenWidth"
9
+ | "screenHeight"
10
+ | "viewportWidth"
11
+ | "viewportHeight"
12
+ | "devicePixelRatio"
13
+ | "osVersion"
14
+ | "deviceModel"
15
+ | "pixelRatio"
16
+ | "fontScale"
17
+ | "appVersion"
18
+ | "buildNumber"
19
+ | "applicationId"
20
+ | "expoRuntimeVersion"
21
+ | "executionEnvironment";
22
+
23
+ export type EnabledStandardMetadataSelection =
24
+ true | readonly StandardMetadataKey[];
25
+
26
+ export type StandardMetadataSelection =
27
+ false | EnabledStandardMetadataSelection;
28
+
29
+ export interface MetadataCollectionContext {
30
+ kind: EntryKind;
31
+ }
32
+
33
+ export type AdditionalMetadata = Record<string, FeedbackMetadataValue>;
34
+
35
+ export type MetadataSource =
36
+ | AdditionalMetadata
37
+ | ((
38
+ context: MetadataCollectionContext,
39
+ ) => AdditionalMetadata | Promise<AdditionalMetadata>);
40
+
41
+ export interface MetadataCollectionOptions {
42
+ /** Standard platform fields to collect. `true` selects platform defaults. */
43
+ standard?: StandardMetadataSelection;
44
+
45
+ /** Static or submission-time host metadata. */
46
+ additional?: MetadataSource;
47
+ }
48
+
49
+ export interface MetadataCollectionConfig extends MetadataCollectionOptions {
50
+ /** Per-entry-kind overrides. `false` disables all collection for that kind. */
51
+ kinds?: Partial<Record<EntryKind, boolean | MetadataCollectionOptions>>;
52
+ }
53
+
54
+ /** Metadata collection configuration accepted by prebuilt feedback screens. */
55
+ export type CollectMetadata = boolean | MetadataCollectionConfig;
@@ -9,6 +9,8 @@ import type { FeedbackHooks } from "convex-feedback/react";
9
9
  import type { ReactNode } from "react";
10
10
 
11
11
  import type { FeedbackColorProps, FeedbackProviderProps } from "./context.js";
12
+ import type { CollectMetadata } from "./metadata.js";
13
+ import type { StandardMetadataCollector } from "../metadata.js";
12
14
 
13
15
  /**
14
16
  * Presentation-only transform applied to one currently loaded comment page.
@@ -87,6 +89,12 @@ export interface FeedbackScreenBaseProps {
87
89
  * @default 300
88
90
  */
89
91
  debounceDuration?: number;
92
+
93
+ /**
94
+ * Enables creation-time diagnostic metadata collection globally and/or by
95
+ * entry kind. Disabled by default.
96
+ */
97
+ collectMetadata?: CollectMetadata;
90
98
  }
91
99
 
92
100
  export interface FeedbackScreenRootProps
@@ -98,8 +106,11 @@ export interface FeedbackScreenRootProps
98
106
 
99
107
  export interface FeedbackScreenProviderProps
100
108
  extends
101
- Required<FeedbackScreenBaseProps>,
102
- FeedbackScreenTransformationProps {}
109
+ Required<Omit<FeedbackScreenBaseProps, "collectMetadata">>,
110
+ FeedbackScreenTransformationProps {
111
+ collectMetadata?: CollectMetadata;
112
+ collectStandardMetadata: StandardMetadataCollector;
113
+ }
103
114
 
104
115
  export interface FeedbackScreenBodyContextValue
105
116
  extends FeedbackScreenTransformationProps, FeedbackScreenProviderProps {
@@ -1,41 +0,0 @@
1
- import { useMemo } from "react";
2
-
3
- import { useFeedbackBody } from "../../../shared/context/FeedbackBodyProvider";
4
- import { useFeedbackUi } from "../../../shared/context/FeedbackProvider";
5
- import type { FeedbackScreenReplyListProps } from "../../../shared/types";
6
- import { Button } from "./Button";
7
- import { CommentBranch } from "./CommentBranch";
8
- import { Comment } from "./primitives";
9
-
10
- export function ReplyList({
11
- entryId,
12
- parentCommentId,
13
- }: FeedbackScreenReplyListProps) {
14
- const { hooks, commentSort, transformComments } = useFeedbackBody();
15
- const { messages } = useFeedbackUi();
16
-
17
- const replies = hooks.useComments({
18
- entryId,
19
- parentCommentId,
20
- sort: commentSort,
21
- });
22
-
23
- const visible = useMemo(
24
- () => transformComments?.(replies.results) ?? replies.results,
25
- [replies.results, transformComments],
26
- );
27
-
28
- return (
29
- <Comment.Children>
30
- {visible.map((reply) => (
31
- <CommentBranch key={reply.id} comment={reply} entryId={entryId} />
32
- ))}
33
- {replies.status === "CanLoadMore" && (
34
- <Button
35
- label={messages.comments.loadMore}
36
- onPress={() => replies.loadMore(hooks.pageSizes.replies)}
37
- />
38
- )}
39
- </Comment.Children>
40
- );
41
- }