@eifi1/ui-kit 0.40.0 → 0.41.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 (42) hide show
  1. package/dist/components/nav-pills.d.ts +2 -2
  2. package/dist/components/nav-pills.js.map +1 -1
  3. package/dist/components/tabs.d.ts +4 -32
  4. package/dist/components/tabs.js +30 -66
  5. package/dist/components/tabs.js.map +1 -1
  6. package/dist/components/translation-review-parts.d.ts +2 -2
  7. package/dist/components/translation-review-parts.js.map +1 -1
  8. package/dist/eslint/index.js +1 -1
  9. package/dist/feedback/feedback-menu-labels.js +0 -2
  10. package/dist/feedback/feedback-menu-labels.js.map +1 -1
  11. package/dist/feedback/feedback-menu.d.ts +0 -6
  12. package/dist/feedback/feedback-menu.js.map +1 -1
  13. package/dist/feedback/feedback-public-list.d.ts +4 -1
  14. package/dist/feedback/feedback-public-list.js +66 -55
  15. package/dist/feedback/feedback-public-list.js.map +1 -1
  16. package/dist/i18n/german.js +0 -2
  17. package/dist/i18n/german.js.map +1 -1
  18. package/dist/i18n/locales/en.js +0 -2
  19. package/dist/i18n/locales/en.js.map +2 -2
  20. package/dist/i18n/locales/es.js +0 -2
  21. package/dist/i18n/locales/es.js.map +1 -1
  22. package/dist/i18n/locales/fr.js +0 -2
  23. package/dist/i18n/locales/fr.js.map +1 -1
  24. package/dist/i18n/locales/hu.js +0 -2
  25. package/dist/i18n/locales/hu.js.map +1 -1
  26. package/dist/i18n/locales/it.js +0 -2
  27. package/dist/i18n/locales/it.js.map +1 -1
  28. package/dist/i18n/locales/zh.js +0 -2
  29. package/dist/i18n/locales/zh.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/components/nav-pills.tsx +2 -2
  32. package/src/components/tabs.tsx +19 -110
  33. package/src/components/translation-review-parts.tsx +2 -2
  34. package/src/feedback/feedback-menu-labels.ts +0 -2
  35. package/src/feedback/feedback-menu.tsx +0 -6
  36. package/src/feedback/feedback-public-list.tsx +11 -1
  37. package/src/i18n/german.ts +0 -2
  38. package/src/i18n/locales/es.ts +0 -2
  39. package/src/i18n/locales/fr.ts +0 -2
  40. package/src/i18n/locales/hu.ts +0 -2
  41. package/src/i18n/locales/it.ts +0 -2
  42. package/src/i18n/locales/zh.ts +0 -2
@@ -22,12 +22,6 @@ export interface FeedbackMenuLabels {
22
22
  /** 0.40: the admin's link to the triage list, {@link FeedbackMenuProps.inboxHref}
23
23
  * ("View feedback" before). de-CH "Feedback-Eingang". */
24
24
  inbox: string;
25
- /** @deprecated 0.40 — use {@link inbox}; removed in 0.41. Unused: the inbox row reads
26
- * `inbox` (feedback §10.6). */
27
- viewFeedback?: string;
28
- /** @deprecated 0.40 — use {@link discussion}; removed in 0.41. Unused: the public row
29
- * reads `discussion` (feedback §10.6). */
30
- allFeedback?: string;
31
25
  }
32
26
  export { DEFAULT_FEEDBACK_MENU_LABELS };
33
27
  export interface FeedbackMenuProps {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/feedback/feedback-menu.tsx"],"sourcesContent":["import { Inbox, MessageSquare, User, Users } from \"lucide-react\";\nimport type { UserAvatarBadge } from \"../components/user-avatar\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { TopBarActionMenu, type TopBarMenuEntry } from \"../shell/topbar-action-menu\";\nimport type { HoverMenuProps } from \"../components/hover-menu\";\n// The vocabulary module, not the inbox: this menu is in every app's top bar, the inbox\n// is a page (MOD-1).\nimport { FEEDBACK_CATEGORY_META, type FeedbackCategory } from \"./feedback-vocabulary\";\nimport { useFeedbackCategoryLabels } from \"./feedback-labels\";\nimport { FEEDBACK_PICKABLE_CATEGORIES } from \"./feedback-record\";\nimport { DEFAULT_FEEDBACK_MENU_LABELS } from \"./feedback-menu-labels\";\n\n/**\n * `feedbackMenu` — the top-bar feedback menu's own words (0.27.0). The four category rows\n * are `feedbackCategory`'s, shared with the badges and the dialog's picker. de-CH canon\n * (keksdose `de-CH.json`, §10.6): `trigger` \"Feedback senden\", `discussion`\n * \"Feedback-Diskussion\", `myFeedback` \"Mein Feedback\", `inbox` \"Feedback-Eingang\".\n */\nexport interface FeedbackMenuLabels {\n /** The trigger's accessible name, and the menu's. keksdose `feedback.send`. */\n trigger: string;\n /** The link to the viewer's own reports — everyone's, an admin's too since 0.40\n * (§10.2 decision 3). keksdose `feedback.my_title`. */\n myFeedback: string;\n /** 0.40: the link to the public list, {@link FeedbackMenuProps.publicHref} (§10.2\n * decision 3; \"All feedback\" before). de-CH \"Feedback-Diskussion\". */\n discussion: string;\n /** 0.40: the admin's link to the triage list, {@link FeedbackMenuProps.inboxHref}\n * (\"View feedback\" before). de-CH \"Feedback-Eingang\". */\n inbox: string;\n /** @deprecated 0.40 — use {@link inbox}; removed in 0.41. Unused: the inbox row reads\n * `inbox` (feedback §10.6). */\n viewFeedback?: string;\n /** @deprecated 0.40 — use {@link discussion}; removed in 0.41. Unused: the public row\n * reads `discussion` (feedback §10.6). */\n allFeedback?: string;\n}\n\n// Defined in ./feedback-menu-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_FEEDBACK_MENU_LABELS };\n\nexport interface FeedbackMenuProps {\n /** A category row was chosen — open the submit dialog on it (`useFeedbackSubmit`'s\n * `open`). Never called with `CRASH`: there is no row for it. */\n onFile: (category: FeedbackCategory) => void;\n /** Adds the **Feedback inbox** row → {@link inboxHref} under \"My feedback\" (0.40, §10.2\n * decision 11; until 0.39 it replaced it). The admin check is the app's (kastlan's is\n * company-scoped). Default `false`. */\n isAdmin?: boolean;\n /**\n * Rows of the app's own, between the divider and the list link — keksdose's\n * `Sparkles` Help assistant → `/assistant` and `MessagesSquare` Support chat →\n * `/support` with its unread chip. Any {@link TopBarMenuEntry}; keep their `key`s clear\n * of the menu's own (`BUG`, `IDEA`, `QUESTION`, `OTHER`, `feedback-divider`,\n * `all-feedback`, `my-feedback`, `view-feedback`).\n */\n extraEntries?: TopBarMenuEntry[];\n /** The viewer's own reports, linked for everyone (an admin too since 0.40). Default\n * `\"/my-feedback\"` (§2.3). */\n myFeedbackHref?: string;\n /** The admin inbox, linked for an admin. Default `\"/feedback\"` (§2.3). */\n inboxHref?: string;\n /**\n * The public list (0.38, docs/feedback-harmonization.md §9.2 decision 21): when passed,\n * a **Feedback discussion** row (`Users`, key `all-feedback`; \"All feedback\" until 0.40)\n * first of the list links, for everyone — the admin too, who reads what the others read\n * there. keksdose: `\"/all-feedback\"`. Leave it out where the app has no\n * public parts (kastlan) or the viewer may not see them (a Kurvenschmiede customer, a\n * demo — §9.2 decisions 7, 8).\n */\n publicHref?: string;\n /** A dot on the trigger — keksdose's unread support replies (`tone: \"danger\"`). Its\n * `label` joins the trigger's name (\"Send feedback 2 unread\"). The words are the app's. */\n iconBadge?: UserAvatarBadge | null;\n /** Prop > `<UiKitProvider labels={{ feedbackMenu }}>` > English. */\n labels?: LabelOverride<FeedbackMenuLabels>;\n /** Which edge of the trigger the panel lines up with, `start` or `end` in the reading\n * direction (see `HoverMenu`, whose type this is). Default `end`. */\n align?: HoverMenuProps[\"align\"];\n}\n\n/**\n * The top-bar feedback menu, the same in every app (0.27.0, docs/feedback-harmonization.md\n * §4.1) — keksdose `frontend/src/app/top-bar.tsx:156` on the kit's `TopBarActionMenu`:\n *\n * 1. a `MessageSquare` trigger named \"Send feedback\", with an optional `iconBadge`;\n * 2. Bug · Idea · Question · Other (`Bug`, `Lightbulb`, `HelpCircle`, `MoreHorizontal` —\n * the category badges' own glyphs), each calling `onFile(category)`;\n * 3. a divider, always;\n * 4. the app's `extraEntries`;\n * 5. the list links (0.40, §10.2 decision 11), in this order: **Feedback discussion**\n * (`Users`, key `all-feedback`) → `publicHref`, when passed; **My feedback** (`User`,\n * key `my-feedback`) → `/my-feedback`, for everyone, admins included; **Feedback\n * inbox** (`Inbox`, key `view-feedback`) → `/feedback`, for an admin. An admin with\n * public parts sees three, a member two; without them an admin two and anyone else\n * one. Until 0.39 an admin had the inbox in place of their own list (feedback #331);\n * Marcel's kk #419 gave them both.\n *\n * WHY A PRESET. Three apps had drawn three menus on the same primitive and they disagreed:\n * kastlan headed it \"New submission\", Kurvenschmiede \"Send feedback\" with a single link\n * that changed its words by role, keksdose had no heading and one link by role. keksdose\n * is the source (§2.4, §7.11): no heading, the list links by role — and the kit draws\n * it, so the row order, the icons and the words cannot drift again. Where the menu sits in the bar stays each app's.\n *\n * `CRASH` has no row and never will: crash reports are filed by the error boundary with\n * the stack and a fingerprint (keksdose feedback #160), and a hand-filed \"crash\" would sort\n * to the top like one with none of what makes it actionable. The server refuses it with a\n * 422 anyway (§3.4).\n *\n * The menu forwards nothing to its trigger, as `TopBarActionMenu` does not: keksdose's\n * tour anchor (`data-tour=\"feedback-menu\"`) stays on a wrapping `<span>`. Needs a router,\n * as every `TopBarActionMenu` with link rows does.\n */\nexport function FeedbackMenu({\n onFile,\n isAdmin = false,\n extraEntries,\n myFeedbackHref = \"/my-feedback\",\n inboxHref = \"/feedback\",\n publicHref,\n iconBadge,\n labels: labelsProp,\n align,\n}: FeedbackMenuProps) {\n const labels = useKitLabels(\"feedbackMenu\", DEFAULT_FEEDBACK_MENU_LABELS, labelsProp);\n const categoryLabels = useFeedbackCategoryLabels();\n const entries: TopBarMenuEntry[] = [\n ...FEEDBACK_PICKABLE_CATEGORIES.map((category): TopBarMenuEntry => {\n const Icon = FEEDBACK_CATEGORY_META[category].icon;\n return {\n key: category,\n icon: <Icon className=\"size-4\" />,\n label: categoryLabels[category],\n onSelect: () => onFile(category),\n };\n }),\n { kind: \"divider\", key: \"feedback-divider\" },\n ...(extraEntries ?? []),\n ...(publicHref\n ? [\n {\n kind: \"link\",\n key: \"all-feedback\",\n to: publicHref,\n icon: <Users className=\"size-4\" />,\n label: labels.discussion,\n } satisfies TopBarMenuEntry,\n ]\n : []),\n {\n kind: \"link\",\n key: \"my-feedback\",\n to: myFeedbackHref,\n icon: <User className=\"size-4\" />,\n label: labels.myFeedback,\n },\n ...(isAdmin\n ? [\n {\n kind: \"link\",\n key: \"view-feedback\",\n to: inboxHref,\n icon: <Inbox className=\"size-4\" />,\n label: labels.inbox,\n } satisfies TopBarMenuEntry,\n ]\n : []),\n ];\n return (\n <TopBarActionMenu\n icon={<MessageSquare className=\"size-5\" />}\n iconBadge={iconBadge}\n aria-label={labels.trigger}\n entries={entries}\n align={align}\n />\n );\n}\n"],"mappings":";AAmIc;AAnId,SAAS,OAAO,eAAe,MAAM,aAAa;AAElD,SAAS,oBAAwC;AACjD,SAAS,wBAA8C;AAIvD,SAAS,8BAAqD;AAC9D,SAAS,iCAAiC;AAC1C,SAAS,oCAAoC;AAC7C,SAAS,oCAAoC;AAuGtC,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA,iBAAiB;AAAA,EACjB,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AACF,GAAsB;AACpB,QAAM,SAAS,aAAa,gBAAgB,8BAA8B,UAAU;AACpF,QAAM,iBAAiB,0BAA0B;AACjD,QAAM,UAA6B;AAAA,IACjC,GAAG,6BAA6B,IAAI,CAAC,aAA8B;AACjE,YAAM,OAAO,uBAAuB,QAAQ,EAAE;AAC9C,aAAO;AAAA,QACL,KAAK;AAAA,QACL,MAAM,oBAAC,QAAK,WAAU,UAAS;AAAA,QAC/B,OAAO,eAAe,QAAQ;AAAA,QAC9B,UAAU,MAAM,OAAO,QAAQ;AAAA,MACjC;AAAA,IACF,CAAC;AAAA,IACD,EAAE,MAAM,WAAW,KAAK,mBAAmB;AAAA,IAC3C,GAAI,gBAAgB,CAAC;AAAA,IACrB,GAAI,aACA;AAAA,MACE;AAAA,QACE,MAAM;AAAA,QACN,KAAK;AAAA,QACL,IAAI;AAAA,QACJ,MAAM,oBAAC,SAAM,WAAU,UAAS;AAAA,QAChC,OAAO,OAAO;AAAA,MAChB;AAAA,IACF,IACA,CAAC;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,KAAK;AAAA,MACL,IAAI;AAAA,MACJ,MAAM,oBAAC,QAAK,WAAU,UAAS;AAAA,MAC/B,OAAO,OAAO;AAAA,IAChB;AAAA,IACA,GAAI,UACA;AAAA,MACE;AAAA,QACE,MAAM;AAAA,QACN,KAAK;AAAA,QACL,IAAI;AAAA,QACJ,MAAM,oBAAC,SAAM,WAAU,UAAS;AAAA,QAChC,OAAO,OAAO;AAAA,MAChB;AAAA,IACF,IACA,CAAC;AAAA,EACP;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,oBAAC,iBAAc,WAAU,UAAS;AAAA,MACxC;AAAA,MACA,cAAY,OAAO;AAAA,MACnB;AAAA,MACA;AAAA;AAAA,EACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/feedback/feedback-menu.tsx"],"sourcesContent":["import { Inbox, MessageSquare, User, Users } from \"lucide-react\";\nimport type { UserAvatarBadge } from \"../components/user-avatar\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { TopBarActionMenu, type TopBarMenuEntry } from \"../shell/topbar-action-menu\";\nimport type { HoverMenuProps } from \"../components/hover-menu\";\n// The vocabulary module, not the inbox: this menu is in every app's top bar, the inbox\n// is a page (MOD-1).\nimport { FEEDBACK_CATEGORY_META, type FeedbackCategory } from \"./feedback-vocabulary\";\nimport { useFeedbackCategoryLabels } from \"./feedback-labels\";\nimport { FEEDBACK_PICKABLE_CATEGORIES } from \"./feedback-record\";\nimport { DEFAULT_FEEDBACK_MENU_LABELS } from \"./feedback-menu-labels\";\n\n/**\n * `feedbackMenu` — the top-bar feedback menu's own words (0.27.0). The four category rows\n * are `feedbackCategory`'s, shared with the badges and the dialog's picker. de-CH canon\n * (keksdose `de-CH.json`, §10.6): `trigger` \"Feedback senden\", `discussion`\n * \"Feedback-Diskussion\", `myFeedback` \"Mein Feedback\", `inbox` \"Feedback-Eingang\".\n */\nexport interface FeedbackMenuLabels {\n /** The trigger's accessible name, and the menu's. keksdose `feedback.send`. */\n trigger: string;\n /** The link to the viewer's own reports — everyone's, an admin's too since 0.40\n * (§10.2 decision 3). keksdose `feedback.my_title`. */\n myFeedback: string;\n /** 0.40: the link to the public list, {@link FeedbackMenuProps.publicHref} (§10.2\n * decision 3; \"All feedback\" before). de-CH \"Feedback-Diskussion\". */\n discussion: string;\n /** 0.40: the admin's link to the triage list, {@link FeedbackMenuProps.inboxHref}\n * (\"View feedback\" before). de-CH \"Feedback-Eingang\". */\n inbox: string;\n}\n\n// Defined in ./feedback-menu-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_FEEDBACK_MENU_LABELS };\n\nexport interface FeedbackMenuProps {\n /** A category row was chosen — open the submit dialog on it (`useFeedbackSubmit`'s\n * `open`). Never called with `CRASH`: there is no row for it. */\n onFile: (category: FeedbackCategory) => void;\n /** Adds the **Feedback inbox** row → {@link inboxHref} under \"My feedback\" (0.40, §10.2\n * decision 11; until 0.39 it replaced it). The admin check is the app's (kastlan's is\n * company-scoped). Default `false`. */\n isAdmin?: boolean;\n /**\n * Rows of the app's own, between the divider and the list link — keksdose's\n * `Sparkles` Help assistant → `/assistant` and `MessagesSquare` Support chat →\n * `/support` with its unread chip. Any {@link TopBarMenuEntry}; keep their `key`s clear\n * of the menu's own (`BUG`, `IDEA`, `QUESTION`, `OTHER`, `feedback-divider`,\n * `all-feedback`, `my-feedback`, `view-feedback`).\n */\n extraEntries?: TopBarMenuEntry[];\n /** The viewer's own reports, linked for everyone (an admin too since 0.40). Default\n * `\"/my-feedback\"` (§2.3). */\n myFeedbackHref?: string;\n /** The admin inbox, linked for an admin. Default `\"/feedback\"` (§2.3). */\n inboxHref?: string;\n /**\n * The public list (0.38, docs/feedback-harmonization.md §9.2 decision 21): when passed,\n * a **Feedback discussion** row (`Users`, key `all-feedback`; \"All feedback\" until 0.40)\n * first of the list links, for everyone — the admin too, who reads what the others read\n * there. keksdose: `\"/all-feedback\"`. Leave it out where the app has no\n * public parts (kastlan) or the viewer may not see them (a Kurvenschmiede customer, a\n * demo — §9.2 decisions 7, 8).\n */\n publicHref?: string;\n /** A dot on the trigger — keksdose's unread support replies (`tone: \"danger\"`). Its\n * `label` joins the trigger's name (\"Send feedback 2 unread\"). The words are the app's. */\n iconBadge?: UserAvatarBadge | null;\n /** Prop > `<UiKitProvider labels={{ feedbackMenu }}>` > English. */\n labels?: LabelOverride<FeedbackMenuLabels>;\n /** Which edge of the trigger the panel lines up with, `start` or `end` in the reading\n * direction (see `HoverMenu`, whose type this is). Default `end`. */\n align?: HoverMenuProps[\"align\"];\n}\n\n/**\n * The top-bar feedback menu, the same in every app (0.27.0, docs/feedback-harmonization.md\n * §4.1) — keksdose `frontend/src/app/top-bar.tsx:156` on the kit's `TopBarActionMenu`:\n *\n * 1. a `MessageSquare` trigger named \"Send feedback\", with an optional `iconBadge`;\n * 2. Bug · Idea · Question · Other (`Bug`, `Lightbulb`, `HelpCircle`, `MoreHorizontal` —\n * the category badges' own glyphs), each calling `onFile(category)`;\n * 3. a divider, always;\n * 4. the app's `extraEntries`;\n * 5. the list links (0.40, §10.2 decision 11), in this order: **Feedback discussion**\n * (`Users`, key `all-feedback`) → `publicHref`, when passed; **My feedback** (`User`,\n * key `my-feedback`) → `/my-feedback`, for everyone, admins included; **Feedback\n * inbox** (`Inbox`, key `view-feedback`) → `/feedback`, for an admin. An admin with\n * public parts sees three, a member two; without them an admin two and anyone else\n * one. Until 0.39 an admin had the inbox in place of their own list (feedback #331);\n * Marcel's kk #419 gave them both.\n *\n * WHY A PRESET. Three apps had drawn three menus on the same primitive and they disagreed:\n * kastlan headed it \"New submission\", Kurvenschmiede \"Send feedback\" with a single link\n * that changed its words by role, keksdose had no heading and one link by role. keksdose\n * is the source (§2.4, §7.11): no heading, the list links by role — and the kit draws\n * it, so the row order, the icons and the words cannot drift again. Where the menu sits in the bar stays each app's.\n *\n * `CRASH` has no row and never will: crash reports are filed by the error boundary with\n * the stack and a fingerprint (keksdose feedback #160), and a hand-filed \"crash\" would sort\n * to the top like one with none of what makes it actionable. The server refuses it with a\n * 422 anyway (§3.4).\n *\n * The menu forwards nothing to its trigger, as `TopBarActionMenu` does not: keksdose's\n * tour anchor (`data-tour=\"feedback-menu\"`) stays on a wrapping `<span>`. Needs a router,\n * as every `TopBarActionMenu` with link rows does.\n */\nexport function FeedbackMenu({\n onFile,\n isAdmin = false,\n extraEntries,\n myFeedbackHref = \"/my-feedback\",\n inboxHref = \"/feedback\",\n publicHref,\n iconBadge,\n labels: labelsProp,\n align,\n}: FeedbackMenuProps) {\n const labels = useKitLabels(\"feedbackMenu\", DEFAULT_FEEDBACK_MENU_LABELS, labelsProp);\n const categoryLabels = useFeedbackCategoryLabels();\n const entries: TopBarMenuEntry[] = [\n ...FEEDBACK_PICKABLE_CATEGORIES.map((category): TopBarMenuEntry => {\n const Icon = FEEDBACK_CATEGORY_META[category].icon;\n return {\n key: category,\n icon: <Icon className=\"size-4\" />,\n label: categoryLabels[category],\n onSelect: () => onFile(category),\n };\n }),\n { kind: \"divider\", key: \"feedback-divider\" },\n ...(extraEntries ?? []),\n ...(publicHref\n ? [\n {\n kind: \"link\",\n key: \"all-feedback\",\n to: publicHref,\n icon: <Users className=\"size-4\" />,\n label: labels.discussion,\n } satisfies TopBarMenuEntry,\n ]\n : []),\n {\n kind: \"link\",\n key: \"my-feedback\",\n to: myFeedbackHref,\n icon: <User className=\"size-4\" />,\n label: labels.myFeedback,\n },\n ...(isAdmin\n ? [\n {\n kind: \"link\",\n key: \"view-feedback\",\n to: inboxHref,\n icon: <Inbox className=\"size-4\" />,\n label: labels.inbox,\n } satisfies TopBarMenuEntry,\n ]\n : []),\n ];\n return (\n <TopBarActionMenu\n icon={<MessageSquare className=\"size-5\" />}\n iconBadge={iconBadge}\n aria-label={labels.trigger}\n entries={entries}\n align={align}\n />\n );\n}\n"],"mappings":";AA6Hc;AA7Hd,SAAS,OAAO,eAAe,MAAM,aAAa;AAElD,SAAS,oBAAwC;AACjD,SAAS,wBAA8C;AAIvD,SAAS,8BAAqD;AAC9D,SAAS,iCAAiC;AAC1C,SAAS,oCAAoC;AAC7C,SAAS,oCAAoC;AAiGtC,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA,iBAAiB;AAAA,EACjB,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AACF,GAAsB;AACpB,QAAM,SAAS,aAAa,gBAAgB,8BAA8B,UAAU;AACpF,QAAM,iBAAiB,0BAA0B;AACjD,QAAM,UAA6B;AAAA,IACjC,GAAG,6BAA6B,IAAI,CAAC,aAA8B;AACjE,YAAM,OAAO,uBAAuB,QAAQ,EAAE;AAC9C,aAAO;AAAA,QACL,KAAK;AAAA,QACL,MAAM,oBAAC,QAAK,WAAU,UAAS;AAAA,QAC/B,OAAO,eAAe,QAAQ;AAAA,QAC9B,UAAU,MAAM,OAAO,QAAQ;AAAA,MACjC;AAAA,IACF,CAAC;AAAA,IACD,EAAE,MAAM,WAAW,KAAK,mBAAmB;AAAA,IAC3C,GAAI,gBAAgB,CAAC;AAAA,IACrB,GAAI,aACA;AAAA,MACE;AAAA,QACE,MAAM;AAAA,QACN,KAAK;AAAA,QACL,IAAI;AAAA,QACJ,MAAM,oBAAC,SAAM,WAAU,UAAS;AAAA,QAChC,OAAO,OAAO;AAAA,MAChB;AAAA,IACF,IACA,CAAC;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,KAAK;AAAA,MACL,IAAI;AAAA,MACJ,MAAM,oBAAC,QAAK,WAAU,UAAS;AAAA,MAC/B,OAAO,OAAO;AAAA,IAChB;AAAA,IACA,GAAI,UACA;AAAA,MACE;AAAA,QACE,MAAM;AAAA,QACN,KAAK;AAAA,QACL,IAAI;AAAA,QACJ,MAAM,oBAAC,SAAM,WAAU,UAAS;AAAA,QAChC,OAAO,OAAO;AAAA,MAChB;AAAA,IACF,IACA,CAAC;AAAA,EACP;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,oBAAC,iBAAc,WAAU,UAAS;AAAA,MACxC;AAAA,MACA,cAAY,OAAO;AAAA,MACnB;AAAA,MACA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -65,6 +65,9 @@ export interface FeedbackPublicListProps<T extends FeedbackPublicListItem = Feed
65
65
  * Subject, Status) are `feedbackPage`'s: pass `pageLabels` for those. */
66
66
  labels?: LabelOverride<FeedbackPublicLabels>;
67
67
  pageLabels?: LabelOverride<FeedbackPageLabels>;
68
+ /** 0.40.1: the list fills the window as DataTable's `fillHeight` (page layout §1) — the
69
+ * public feedback page passes §1.2's rule. Below md the page scrolls, as everywhere. */
70
+ fillHeight?: boolean;
68
71
  className?: string;
69
72
  }
70
73
  /**
@@ -91,7 +94,7 @@ export interface FeedbackPublicListProps<T extends FeedbackPublicListItem = Feed
91
94
  * next strip that needs no total (0.38.1); `openRow` shows a `?row=` report from
92
95
  * another page. Needs a router, as every `DataTable` does.
93
96
  */
94
- export declare function FeedbackPublicList<T extends FeedbackPublicListItem = FeedbackPublicListItem>({ rows, total, sort, onSortChange, search, onSearchChange, renderDetail, onVote, loading, openId: openIdProp, onOpenChange, openRow, page: pageIndex, hasMore, onPageChange, commit, renderDate: renderDateProp, labels: labelsProp, pageLabels: pageLabelsProp, className, }: FeedbackPublicListProps<T>): import("react").JSX.Element;
97
+ export declare function FeedbackPublicList<T extends FeedbackPublicListItem = FeedbackPublicListItem>({ rows, total, sort, onSortChange, search, onSearchChange, renderDetail, onVote, loading, openId: openIdProp, onOpenChange, openRow, page: pageIndex, hasMore, onPageChange, commit, renderDate: renderDateProp, labels: labelsProp, pageLabels: pageLabelsProp, fillHeight, className, }: FeedbackPublicListProps<T>): import("react").JSX.Element;
95
98
  export interface FeedbackPublicDetailProps {
96
99
  /** `GET /feedback/public/{id}`: the public body (attachment URLs already stripped by
97
100
  * the server, §9.4) and the replies. */
@@ -9,6 +9,7 @@ import { SearchField } from "../components/search-field.js";
9
9
  import { ToggleGroup } from "../components/toggle-group.js";
10
10
  import { useKitLabels } from "../i18n/kit-labels.js";
11
11
  import { cn } from "../lib/cn.js";
12
+ import { FILL_HEIGHT_CLASS } from "../shell/fill-height.js";
12
13
  import { EMPTY_FORMATTED_VALUE } from "../lib/format.js";
13
14
  import { FeedbackCategoryBadge, FeedbackDetail, FeedbackDetailSection, FeedbackProse, FeedbackStatusBadge } from "./feedback-inbox.js";
14
15
  import { useFeedbackCategoryLabels } from "./feedback-labels.js";
@@ -112,6 +113,7 @@ function FeedbackPublicList({
112
113
  renderDate: renderDateProp,
113
114
  labels: labelsProp,
114
115
  pageLabels: pageLabelsProp,
116
+ fillHeight,
115
117
  className
116
118
  }) {
117
119
  const labels = useFeedbackPublicLabels(labelsProp);
@@ -179,61 +181,70 @@ function FeedbackPublicList({
179
181
  ],
180
182
  [page, labels, categoryLabels, renderDate, onVote, commit]
181
183
  );
182
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-3", className), "data-feedback-public-list": "", "aria-busy": loading || void 0, children: [
183
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
184
- /* @__PURE__ */ jsx(
185
- ToggleGroup,
186
- {
187
- "aria-label": labels.sort,
188
- value: sort,
189
- onChange: (value) => onSortChange(value),
190
- options: SORTS.map((value) => ({
191
- value,
192
- label: value === "newest" ? labels.sortNewest : value === "votes" ? labels.sortVotes : labels.sortUpdated
193
- }))
194
- }
195
- ),
196
- onSearchChange && /* @__PURE__ */ jsx(
197
- SearchField,
198
- {
199
- "aria-label": labels.search,
200
- placeholder: labels.search,
201
- value: search,
202
- onChange: onSearchChange,
203
- className: "min-w-[12rem] flex-1 md:max-w-xs"
204
- }
205
- ),
206
- total !== void 0 && /* @__PURE__ */ jsx("span", { className: "ms-auto text-sm text-muted", "data-feedback-public-total": total, children: labels.total(total) })
207
- ] }),
208
- /* @__PURE__ */ jsx(
209
- DataTable,
210
- {
211
- rows: shownRows,
212
- columns,
213
- rowKey: (row) => row.id,
214
- rowName: (row) => row.title,
215
- onRowClick: (row) => setOpen(openId === row.id ? null : row.id),
216
- isExpanded: (row) => row.id === openId,
217
- expandedRow: (row) => renderDetail(row),
218
- mobileExpandAsDialog: true,
219
- paginated: false,
220
- empty: loading ? labels.loading : labels.empty,
221
- mobileCard: (row) => /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-1", children: [
222
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
223
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 font-medium [overflow-wrap:anywhere]", children: row.title }),
224
- /* @__PURE__ */ jsx(FeedbackStatusBadge, { status: row.status, className: "shrink-0" })
225
- ] }),
226
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted", children: [
227
- /* @__PURE__ */ jsx(FeedbackCategoryBadge, { category: row.category, compact: true, label: categoryLabels[row.category] }),
228
- /* @__PURE__ */ jsx(PublicDate, { value: row.created_at, renderDate }),
229
- /* @__PURE__ */ jsx(CommentCount, { count: row.comment_count, labels }),
230
- /* @__PURE__ */ jsx(VoteCount, { count: row.votes, voted: row.voted || row.mine, labels })
231
- ] })
232
- ] })
233
- }
234
- ),
235
- onPageChange && (pageIndex > 0 || hasMore) && /* @__PURE__ */ jsx(PublicPager, { page: pageIndex, hasMore, onPageChange, labels })
236
- ] });
184
+ return /* @__PURE__ */ jsxs(
185
+ "div",
186
+ {
187
+ className: cn("flex flex-col gap-3", fillHeight && FILL_HEIGHT_CLASS, className),
188
+ "data-feedback-public-list": "",
189
+ "aria-busy": loading || void 0,
190
+ children: [
191
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
192
+ /* @__PURE__ */ jsx(
193
+ ToggleGroup,
194
+ {
195
+ "aria-label": labels.sort,
196
+ value: sort,
197
+ onChange: (value) => onSortChange(value),
198
+ options: SORTS.map((value) => ({
199
+ value,
200
+ label: value === "newest" ? labels.sortNewest : value === "votes" ? labels.sortVotes : labels.sortUpdated
201
+ }))
202
+ }
203
+ ),
204
+ onSearchChange && /* @__PURE__ */ jsx(
205
+ SearchField,
206
+ {
207
+ "aria-label": labels.search,
208
+ placeholder: labels.search,
209
+ value: search,
210
+ onChange: onSearchChange,
211
+ className: "min-w-[12rem] flex-1 md:max-w-xs"
212
+ }
213
+ ),
214
+ total !== void 0 && /* @__PURE__ */ jsx("span", { className: "ms-auto text-sm text-muted", "data-feedback-public-total": total, children: labels.total(total) })
215
+ ] }),
216
+ /* @__PURE__ */ jsx(
217
+ DataTable,
218
+ {
219
+ rows: shownRows,
220
+ columns,
221
+ rowKey: (row) => row.id,
222
+ rowName: (row) => row.title,
223
+ onRowClick: (row) => setOpen(openId === row.id ? null : row.id),
224
+ isExpanded: (row) => row.id === openId,
225
+ expandedRow: (row) => renderDetail(row),
226
+ mobileExpandAsDialog: true,
227
+ paginated: false,
228
+ fillHeight,
229
+ empty: loading ? labels.loading : labels.empty,
230
+ mobileCard: (row) => /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-1", children: [
231
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
232
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 font-medium [overflow-wrap:anywhere]", children: row.title }),
233
+ /* @__PURE__ */ jsx(FeedbackStatusBadge, { status: row.status, className: "shrink-0" })
234
+ ] }),
235
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted", children: [
236
+ /* @__PURE__ */ jsx(FeedbackCategoryBadge, { category: row.category, compact: true, label: categoryLabels[row.category] }),
237
+ /* @__PURE__ */ jsx(PublicDate, { value: row.created_at, renderDate }),
238
+ /* @__PURE__ */ jsx(CommentCount, { count: row.comment_count, labels }),
239
+ /* @__PURE__ */ jsx(VoteCount, { count: row.votes, voted: row.voted || row.mine, labels })
240
+ ] })
241
+ ] })
242
+ }
243
+ ),
244
+ onPageChange && (pageIndex > 0 || hasMore) && /* @__PURE__ */ jsx(PublicPager, { page: pageIndex, hasMore, onPageChange, labels })
245
+ ]
246
+ }
247
+ );
237
248
  }
238
249
  function FeedbackPublicDetail({
239
250
  row,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/feedback/feedback-public-list.tsx"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { ChevronLeft, ChevronRight, MessageSquare, ThumbsUp } from \"lucide-react\";\nimport { DataTable, type DataTableColumn } from \"../components/data-table\";\nimport { DEFAULT_DATA_TABLE_LABELS } from \"../components/data-table-labels\";\nimport { IconButton } from \"../components/icon-button\";\nimport { SearchField } from \"../components/search-field\";\nimport { ToggleGroup } from \"../components/toggle-group\";\nimport type { CommitScope } from \"../components/write-lock\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { cn } from \"../lib/cn\";\nimport { EMPTY_FORMATTED_VALUE } from \"../lib/format\";\nimport { FeedbackCategoryBadge, FeedbackDetail, FeedbackDetailSection, FeedbackProse, FeedbackStatusBadge } from \"./feedback-inbox\";\nimport { useFeedbackCategoryLabels } from \"./feedback-labels\";\nimport { FeedbackCommentThread, type FeedbackCommentsLabels } from \"./feedback-comment-thread\";\nimport { DEFAULT_FEEDBACK_PUBLIC_LABELS, type FeedbackPublicLabels } from \"./feedback-public-labels\";\nimport type {\n FeedbackComment,\n FeedbackPublicListItem,\n FeedbackPublicRecord,\n FeedbackPublicSort,\n FeedbackVoteState,\n} from \"./feedback-public-types\";\nimport {\n useFeedbackPageLabels,\n useFeedbackRenderDate,\n type FeedbackPageLabels,\n type FeedbackRenderDate,\n} from \"./feedback-table\";\nimport { FeedbackVoteButton } from \"./feedback-vote-button\";\n\nexport { DEFAULT_FEEDBACK_PUBLIC_LABELS };\nexport type { FeedbackPublicLabels };\n\n/** `feedbackPublic` resolved: English, then `<UiKitProvider labels>`, then `labels`. */\nexport function useFeedbackPublicLabels(labels?: LabelOverride<FeedbackPublicLabels>): FeedbackPublicLabels {\n return useKitLabels(\"feedbackPublic\", DEFAULT_FEEDBACK_PUBLIC_LABELS, labels);\n}\n\n/** The sort toggle's order: the server's default first (§9.6). */\nconst SORTS: readonly FeedbackPublicSort[] = [\"newest\", \"votes\", \"updated\"];\n\n/** A date in a cell, as the inbox writes it: `<time>`, nowrap, tabular figures. */\nfunction PublicDate({ value, renderDate }: { value: string | null | undefined; renderDate: FeedbackRenderDate }) {\n if (!value) return <span className=\"text-placeholder\">{EMPTY_FORMATTED_VALUE}</span>;\n return (\n <time dateTime={value} className=\"whitespace-nowrap tabular-nums text-muted\">\n {renderDate(value)}\n </time>\n );\n}\n\n/** The visible comments as a small chip beside the title — the count in figures, the\n * words for a screen reader. Nothing at 0. */\nfunction CommentCount({ count, labels }: { count: number; labels: FeedbackPublicLabels }) {\n if (!count) return null;\n return (\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-muted\" data-feedback-comment-count={count}>\n <MessageSquare className=\"size-3.5\" aria-hidden />\n <span aria-hidden className=\"tabular-nums\">\n {count}\n </span>\n <span className=\"sr-only\">{labels.comments(count)}</span>\n </span>\n );\n}\n\n/** The votes on a phone card: the thumb and the count, in the brand colour when the\n * viewer's own. */\nfunction VoteCount({ count, voted, labels }: { count: number; voted: boolean; labels: FeedbackPublicLabels }) {\n return (\n <span\n className={cn(\"ms-auto inline-flex shrink-0 items-center gap-1 text-xs\", voted ? \"text-brand\" : \"text-muted\")}\n data-feedback-vote-count={count}\n >\n <ThumbsUp className=\"size-3.5\" aria-hidden />\n <span aria-hidden className=\"tabular-nums\">\n {count}\n </span>\n <span className=\"sr-only\">{labels.votes(count)}</span>\n </span>\n );\n}\n\n/** 0.38.1: previous / next over the server's pages when it answers no total\n * (`GET /feedback/public` — Kurvenschmiede): \"Page 3\" between two arrows. The arrows'\n * names are `dataTable`'s, the strip's; the page's is `feedbackPublic.page`. */\nfunction PublicPager({\n page,\n hasMore,\n onPageChange,\n labels,\n}: {\n page: number;\n hasMore: boolean;\n onPageChange: (page: number) => void;\n labels: FeedbackPublicLabels;\n}) {\n const tableLabels = useKitLabels(\"dataTable\", DEFAULT_DATA_TABLE_LABELS);\n const name = labels.page(page + 1);\n return (\n <div\n role=\"group\"\n aria-label={name}\n className=\"flex items-center justify-end gap-2 text-sm text-secondary\"\n data-feedback-public-pager={page}\n >\n <IconButton\n size=\"sm\"\n variant=\"secondary\"\n label={tableLabels.prevPage}\n disabled={page <= 0}\n onClick={() => onPageChange(page - 1)}\n >\n {/* \"Previous\" points back along the line — left in LTR, right in RTL. */}\n <ChevronLeft className=\"rtl:-scale-x-100\" />\n </IconButton>\n <span className=\"tabular-nums\" aria-hidden>\n {name}\n </span>\n <IconButton\n size=\"sm\"\n variant=\"secondary\"\n label={tableLabels.nextPage}\n disabled={!hasMore}\n onClick={() => onPageChange(page + 1)}\n >\n <ChevronRight className=\"rtl:-scale-x-100\" />\n </IconButton>\n </div>\n );\n}\n\n/* ── The list ─────────────────────────────────────────────────────────────── */\n\nexport interface FeedbackPublicListProps<T extends FeedbackPublicListItem = FeedbackPublicListItem> {\n /** `GET /feedback/public?sort=&q=` — the server's page, in the server's order. */\n rows: T[];\n /** How many the query has in all — the line beside the sort. Left out, no line. */\n total?: number;\n /** The server sorts (§9.6): the toggle only says which way. */\n sort: FeedbackPublicSort;\n onSortChange: (sort: FeedbackPublicSort) => void;\n /** The search box's text (`q`, titles only). */\n search?: string;\n /**\n * The search box is drawn ONLY with this handler: where the app's titles are sealed\n * (Kurvenschmiede, `FeedbackDeps.public_search=False`, §9.9) there is nothing to search\n * on and the server answers `q` with a 422 — so it passes none.\n */\n onSearchChange?: (search: string) => void;\n /** The opened row: the app's {@link FeedbackPublicDetail} with the row's\n * `GET /feedback/public/{id}` and its comments. */\n renderDetail: (row: T) => ReactNode;\n /** `PUT` / `DELETE /feedback/{id}/vote` for a row's button (`next`: the new state);\n * resolve with the answer, reject to roll back. */\n onVote: (row: T, next: boolean) => Promise<FeedbackVoteState>;\n /** The page is loading: the table says so where its rows would be. */\n loading?: boolean;\n /** The opened row's id, when the app keeps it (a `?row=` param). Left out, the list\n * keeps it itself. */\n openId?: number | null;\n onOpenChange?: (id: number | null) => void;\n /**\n * 0.38.1: the opened report when it is NOT on this page — a `?row=` link to an older\n * one (Kurvenschmiede). The app fetches it (`GET /feedback/public/{id}`, a list item's\n * fields and more) and passes it with `openId`; while `openId` is its id it is drawn\n * first, above the page's rows, and opened with `renderDetail` (on the phone, in the\n * full-screen dialog). Closed, it is gone. A row the page has already is never drawn\n * twice: the page's own is opened instead.\n */\n openRow?: T | null;\n /**\n * 0.38.1: the server's page, from 0 — for a pager where `/feedback/public` answers no\n * total (Kurvenschmiede). With {@link onPageChange} a **Previous / Page n / Next**\n * strip stands under the table; Next is off without {@link hasMore}. Left out, no\n * pager: the server's `limit` is all there is.\n */\n page?: number;\n /** 0.38.1: there is a page after this one (e.g. the page came back full). */\n hasMore?: boolean;\n /** 0.38.1: go to another page (`page ± 1`); the app fetches it. Draws the pager. */\n onPageChange?: (page: number) => void;\n /** A vote COMMITS — see Button's `commit`. */\n commit?: CommitScope;\n /** See {@link FeedbackRenderDate}. Default: the kit's, with the weekday. */\n renderDate?: FeedbackRenderDate;\n /** Over the `feedbackPublic` namespace. The shared column names (#, Date, Category,\n * Subject, Status) are `feedbackPage`'s: pass `pageLabels` for those. */\n labels?: LabelOverride<FeedbackPublicLabels>;\n pageLabels?: LabelOverride<FeedbackPageLabels>;\n className?: string;\n}\n\n/**\n * **All feedback** — the reports every user the app lets in may read (round 0.38,\n * docs/feedback-harmonization.md §9.3, §9.9; keksdose's `/all-feedback`).\n *\n * - **Titles only** (decision 23): #, Date, Category, Subject (with the visible\n * comments), Votes, Status. No body, no submitter, no file — the list item carries\n * none of them (§9.10).\n * - **The server sorts**: the toggle **Newest / Most wanted / Recently updated** sets\n * `sort`, and the columns themselves do not sort (a page of 50 sorted in the browser\n * would not be the 50 the sort asks for).\n * - **The search box only with `onSearchChange`**: an app whose titles are sealed\n * passes none (§9.9).\n * - **Votes** are `FeedbackVoteButton`s, optimistic, the viewer's own report pressed\n * and locked (\"Your report\", §9.6).\n * - A row opens in place with `renderDetail` — on the phone, in a full-screen dialog.\n * The phone cards carry the title, the status, the category, the date and the two\n * counts; the vote itself is in the opened report (a button inside the card, which is\n * the row's own button, would be two controls in one).\n *\n * No client paging: the server's `limit` is the page, and `total` says how many there\n * are. For more than one page, `page` / `hasMore` / `onPageChange` draw a previous /\n * next strip that needs no total (0.38.1); `openRow` shows a `?row=` report from\n * another page. Needs a router, as every `DataTable` does.\n */\nexport function FeedbackPublicList<T extends FeedbackPublicListItem = FeedbackPublicListItem>({\n rows,\n total,\n sort,\n onSortChange,\n search = \"\",\n onSearchChange,\n renderDetail,\n onVote,\n loading = false,\n openId: openIdProp,\n onOpenChange,\n openRow,\n page: pageIndex = 0,\n hasMore = false,\n onPageChange,\n commit,\n renderDate: renderDateProp,\n labels: labelsProp,\n pageLabels: pageLabelsProp,\n className,\n}: FeedbackPublicListProps<T>) {\n const labels = useFeedbackPublicLabels(labelsProp);\n const page = useFeedbackPageLabels(pageLabelsProp);\n const categoryLabels = useFeedbackCategoryLabels();\n const renderDate = useFeedbackRenderDate(renderDateProp);\n const [ownOpenId, setOwnOpenId] = useState<number | null>(null);\n const openId = openIdProp !== undefined ? openIdProp : ownOpenId;\n const setOpen = (id: number | null) => {\n if (openIdProp === undefined) setOwnOpenId(id);\n onOpenChange?.(id);\n };\n // A `?row=` report from another page: first, while it is the open one, and only when\n // the page does not have it itself.\n const shownRows =\n openRow && openId === openRow.id && !rows.some((row) => row.id === openRow.id) ? [openRow, ...rows] : rows;\n\n const columns = useMemo<DataTableColumn<T>[]>(\n () => [\n {\n key: \"id\",\n header: page.columnId,\n cell: (row) => <span className=\"font-mono text-xs text-muted\">{row.id}</span>,\n mobileHidden: true,\n },\n {\n key: \"date\",\n header: page.columnDate,\n cell: (row) => <PublicDate value={row.created_at} renderDate={renderDate} />,\n },\n {\n key: \"category\",\n header: page.columnCategory,\n cell: (row) => <FeedbackCategoryBadge category={row.category} label={categoryLabels[row.category]} />,\n },\n {\n key: \"title\",\n header: page.columnSubject,\n cell: (row) => (\n <span className=\"inline-flex min-w-0 max-w-full items-center gap-2 large:items-start\">\n <span className=\"min-w-0 truncate-until-large font-medium\">{row.title}</span>\n <CommentCount count={row.comment_count} labels={labels} />\n </span>\n ),\n mobilePrimary: true,\n },\n {\n key: \"votes\",\n header: labels.columnVotes,\n className: \"whitespace-nowrap\",\n // The cell is a button: the row's own link must never wrap it.\n noRowLink: true,\n cell: (row) => (\n <FeedbackVoteButton\n votes={row.votes}\n voted={row.voted}\n own={row.mine}\n onVote={(next) => onVote(row, next)}\n commit={commit}\n size=\"sm\"\n />\n ),\n },\n {\n key: \"status\",\n header: page.columnStatus,\n className: \"whitespace-nowrap\",\n cell: (row) => <FeedbackStatusBadge status={row.status} />,\n },\n ],\n [page, labels, categoryLabels, renderDate, onVote, commit],\n );\n\n return (\n <div className={cn(\"flex flex-col gap-3\", className)} data-feedback-public-list=\"\" aria-busy={loading || undefined}>\n <div className=\"flex flex-wrap items-center gap-2\">\n <ToggleGroup\n aria-label={labels.sort}\n value={sort}\n onChange={(value) => onSortChange(value)}\n options={SORTS.map((value) => ({\n value,\n label: value === \"newest\" ? labels.sortNewest : value === \"votes\" ? labels.sortVotes : labels.sortUpdated,\n }))}\n />\n {onSearchChange && (\n <SearchField\n aria-label={labels.search}\n placeholder={labels.search}\n value={search}\n onChange={onSearchChange}\n className=\"min-w-[12rem] flex-1 md:max-w-xs\"\n />\n )}\n {total !== undefined && (\n <span className=\"ms-auto text-sm text-muted\" data-feedback-public-total={total}>\n {labels.total(total)}\n </span>\n )}\n </div>\n <DataTable\n rows={shownRows}\n columns={columns}\n rowKey={(row) => row.id}\n rowName={(row) => row.title}\n onRowClick={(row) => setOpen(openId === row.id ? null : row.id)}\n isExpanded={(row) => row.id === openId}\n expandedRow={(row) => renderDetail(row)}\n mobileExpandAsDialog\n paginated={false}\n empty={loading ? labels.loading : labels.empty}\n mobileCard={(row) => (\n <div className=\"min-w-0 space-y-1\">\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"min-w-0 flex-1 font-medium [overflow-wrap:anywhere]\">{row.title}</span>\n <FeedbackStatusBadge status={row.status} className=\"shrink-0\" />\n </div>\n <div className=\"flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted\">\n <FeedbackCategoryBadge category={row.category} compact label={categoryLabels[row.category]} />\n <PublicDate value={row.created_at} renderDate={renderDate} />\n <CommentCount count={row.comment_count} labels={labels} />\n {/* A count, not the button: the whole card is the row's button, and a button\n inside it is two controls in one. The vote is in the opened report. */}\n <VoteCount count={row.votes} voted={row.voted || row.mine} labels={labels} />\n </div>\n </div>\n )}\n />\n {onPageChange && (pageIndex > 0 || hasMore) && (\n <PublicPager page={pageIndex} hasMore={hasMore} onPageChange={onPageChange} labels={labels} />\n )}\n </div>\n );\n}\n\n/* ── The detail ───────────────────────────────────────────────────────────── */\n\nexport interface FeedbackPublicDetailProps {\n /** `GET /feedback/public/{id}`: the public body (attachment URLs already stripped by\n * the server, §9.4) and the replies. */\n row: FeedbackPublicRecord;\n /** `GET /feedback/{id}/comments`, oldest first; `undefined` while it loads. */\n comments: readonly FeedbackComment[] | undefined;\n /** `PUT` / `DELETE /feedback/{id}/vote`; resolve with the answer, reject to roll back. */\n onVote: (next: boolean) => Promise<FeedbackVoteState>;\n /** The viewer is the app's admin: Hide / Unhide on the comments. */\n isAdmin?: boolean;\n /** `POST /feedback/{id}/comments`. Left out — a demo, a reader who may not write — no\n * composer. */\n onComment?: (body: string) => void | Promise<unknown>;\n /** The admin's hide (`PATCH …/comments/{id} {hidden}`). */\n onHide?: (comment: FeedbackComment, hidden: boolean) => void | Promise<unknown>;\n /** The writer's delete (`DELETE …/comments/{id}`), after a confirm. */\n onDelete?: (comment: FeedbackComment) => void | Promise<unknown>;\n /** A comment is being sent. */\n pending?: boolean;\n /** The vote and the thread's buttons COMMIT — see Button's `commit`. */\n commit?: CommitScope;\n /** Voting is off for this viewer (a demo): `true`, or the reason. */\n voteDisabled?: boolean | string;\n /** See {@link FeedbackRenderDate}. */\n renderDate?: FeedbackRenderDate;\n /** \"Now\" for the thread's relative times — pin it in tests and stories. */\n now?: Date | number;\n /** Over the `feedbackPublic` namespace. */\n labels?: LabelOverride<FeedbackPublicLabels>;\n /** Over the `feedbackComments` namespace, for the thread. */\n commentLabels?: LabelOverride<FeedbackCommentsLabels>;\n className?: string;\n}\n\n/**\n * One public report opened (round 0.38, docs/feedback-harmonization.md §9.3, §9.10): who\n * filed it — the first name the server sent, else **\"A user\"** (§9.5) — and when; the\n * **vote**; the **public body**; and the **conversation**, the admin's replies and other\n * people's comments merged oldest first (§9.8, `FeedbackCommentThread`), with the box to\n * add one.\n *\n * What it never draws, because the public record never carries it (§9.10): an address,\n * a last name, a screenshot, a file, the page the report was filed on. The body is the\n * server's `public_body` — the client strips nothing and adds nothing.\n */\nexport function FeedbackPublicDetail({\n row,\n comments,\n onVote,\n isAdmin = false,\n onComment,\n onHide,\n onDelete,\n pending,\n commit,\n voteDisabled,\n renderDate: renderDateProp,\n now,\n labels: labelsProp,\n commentLabels,\n className,\n}: FeedbackPublicDetailProps) {\n const labels = useFeedbackPublicLabels(labelsProp);\n const renderDate = useFeedbackRenderDate(renderDateProp);\n return (\n <FeedbackDetail className={className}>\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-2\" data-feedback-public-detail={row.id}>\n <span className=\"text-sm text-secondary\" data-feedback-author=\"\">\n {labels.filedBy(row.author_name ?? labels.anonymous)}\n </span>\n <PublicDate value={row.created_at} renderDate={renderDate} />\n <FeedbackVoteButton\n votes={row.votes}\n voted={row.voted}\n own={row.mine}\n onVote={onVote}\n disabled={voteDisabled}\n commit={commit}\n className=\"ms-auto\"\n />\n </div>\n <FeedbackDetailSection title={labels.body}>\n {/* Rework rounds stay in it — the author's own words (§9.4) — on a server before\n server-kit 0.13; since then each is an entry in the conversation (§10.3). */}\n <FeedbackProse>{row.body}</FeedbackProse>\n </FeedbackDetailSection>\n <FeedbackDetailSection title={labels.conversation}>\n <FeedbackCommentThread\n replies={row.replies}\n comments={comments ?? []}\n loading={comments === undefined}\n isAdmin={isAdmin}\n // 0.40 (§10.3): the author's rework entries are theirs — at the reading end.\n isAuthor={row.mine}\n onComment={onComment}\n onHide={onHide}\n onDelete={onDelete}\n pending={pending}\n commit={commit}\n now={now}\n labels={commentLabels}\n />\n </FeedbackDetailSection>\n </FeedbackDetail>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/feedback/feedback-public-list.tsx"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { ChevronLeft, ChevronRight, MessageSquare, ThumbsUp } from \"lucide-react\";\nimport { DataTable, type DataTableColumn } from \"../components/data-table\";\nimport { DEFAULT_DATA_TABLE_LABELS } from \"../components/data-table-labels\";\nimport { IconButton } from \"../components/icon-button\";\nimport { SearchField } from \"../components/search-field\";\nimport { ToggleGroup } from \"../components/toggle-group\";\nimport type { CommitScope } from \"../components/write-lock\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { cn } from \"../lib/cn\";\nimport { FILL_HEIGHT_CLASS } from \"../shell/fill-height\";\nimport { EMPTY_FORMATTED_VALUE } from \"../lib/format\";\nimport { FeedbackCategoryBadge, FeedbackDetail, FeedbackDetailSection, FeedbackProse, FeedbackStatusBadge } from \"./feedback-inbox\";\nimport { useFeedbackCategoryLabels } from \"./feedback-labels\";\nimport { FeedbackCommentThread, type FeedbackCommentsLabels } from \"./feedback-comment-thread\";\nimport { DEFAULT_FEEDBACK_PUBLIC_LABELS, type FeedbackPublicLabels } from \"./feedback-public-labels\";\nimport type {\n FeedbackComment,\n FeedbackPublicListItem,\n FeedbackPublicRecord,\n FeedbackPublicSort,\n FeedbackVoteState,\n} from \"./feedback-public-types\";\nimport {\n useFeedbackPageLabels,\n useFeedbackRenderDate,\n type FeedbackPageLabels,\n type FeedbackRenderDate,\n} from \"./feedback-table\";\nimport { FeedbackVoteButton } from \"./feedback-vote-button\";\n\nexport { DEFAULT_FEEDBACK_PUBLIC_LABELS };\nexport type { FeedbackPublicLabels };\n\n/** `feedbackPublic` resolved: English, then `<UiKitProvider labels>`, then `labels`. */\nexport function useFeedbackPublicLabels(labels?: LabelOverride<FeedbackPublicLabels>): FeedbackPublicLabels {\n return useKitLabels(\"feedbackPublic\", DEFAULT_FEEDBACK_PUBLIC_LABELS, labels);\n}\n\n/** The sort toggle's order: the server's default first (§9.6). */\nconst SORTS: readonly FeedbackPublicSort[] = [\"newest\", \"votes\", \"updated\"];\n\n/** A date in a cell, as the inbox writes it: `<time>`, nowrap, tabular figures. */\nfunction PublicDate({ value, renderDate }: { value: string | null | undefined; renderDate: FeedbackRenderDate }) {\n if (!value) return <span className=\"text-placeholder\">{EMPTY_FORMATTED_VALUE}</span>;\n return (\n <time dateTime={value} className=\"whitespace-nowrap tabular-nums text-muted\">\n {renderDate(value)}\n </time>\n );\n}\n\n/** The visible comments as a small chip beside the title — the count in figures, the\n * words for a screen reader. Nothing at 0. */\nfunction CommentCount({ count, labels }: { count: number; labels: FeedbackPublicLabels }) {\n if (!count) return null;\n return (\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-muted\" data-feedback-comment-count={count}>\n <MessageSquare className=\"size-3.5\" aria-hidden />\n <span aria-hidden className=\"tabular-nums\">\n {count}\n </span>\n <span className=\"sr-only\">{labels.comments(count)}</span>\n </span>\n );\n}\n\n/** The votes on a phone card: the thumb and the count, in the brand colour when the\n * viewer's own. */\nfunction VoteCount({ count, voted, labels }: { count: number; voted: boolean; labels: FeedbackPublicLabels }) {\n return (\n <span\n className={cn(\"ms-auto inline-flex shrink-0 items-center gap-1 text-xs\", voted ? \"text-brand\" : \"text-muted\")}\n data-feedback-vote-count={count}\n >\n <ThumbsUp className=\"size-3.5\" aria-hidden />\n <span aria-hidden className=\"tabular-nums\">\n {count}\n </span>\n <span className=\"sr-only\">{labels.votes(count)}</span>\n </span>\n );\n}\n\n/** 0.38.1: previous / next over the server's pages when it answers no total\n * (`GET /feedback/public` — Kurvenschmiede): \"Page 3\" between two arrows. The arrows'\n * names are `dataTable`'s, the strip's; the page's is `feedbackPublic.page`. */\nfunction PublicPager({\n page,\n hasMore,\n onPageChange,\n labels,\n}: {\n page: number;\n hasMore: boolean;\n onPageChange: (page: number) => void;\n labels: FeedbackPublicLabels;\n}) {\n const tableLabels = useKitLabels(\"dataTable\", DEFAULT_DATA_TABLE_LABELS);\n const name = labels.page(page + 1);\n return (\n <div\n role=\"group\"\n aria-label={name}\n className=\"flex items-center justify-end gap-2 text-sm text-secondary\"\n data-feedback-public-pager={page}\n >\n <IconButton\n size=\"sm\"\n variant=\"secondary\"\n label={tableLabels.prevPage}\n disabled={page <= 0}\n onClick={() => onPageChange(page - 1)}\n >\n {/* \"Previous\" points back along the line — left in LTR, right in RTL. */}\n <ChevronLeft className=\"rtl:-scale-x-100\" />\n </IconButton>\n <span className=\"tabular-nums\" aria-hidden>\n {name}\n </span>\n <IconButton\n size=\"sm\"\n variant=\"secondary\"\n label={tableLabels.nextPage}\n disabled={!hasMore}\n onClick={() => onPageChange(page + 1)}\n >\n <ChevronRight className=\"rtl:-scale-x-100\" />\n </IconButton>\n </div>\n );\n}\n\n/* ── The list ─────────────────────────────────────────────────────────────── */\n\nexport interface FeedbackPublicListProps<T extends FeedbackPublicListItem = FeedbackPublicListItem> {\n /** `GET /feedback/public?sort=&q=` — the server's page, in the server's order. */\n rows: T[];\n /** How many the query has in all — the line beside the sort. Left out, no line. */\n total?: number;\n /** The server sorts (§9.6): the toggle only says which way. */\n sort: FeedbackPublicSort;\n onSortChange: (sort: FeedbackPublicSort) => void;\n /** The search box's text (`q`, titles only). */\n search?: string;\n /**\n * The search box is drawn ONLY with this handler: where the app's titles are sealed\n * (Kurvenschmiede, `FeedbackDeps.public_search=False`, §9.9) there is nothing to search\n * on and the server answers `q` with a 422 — so it passes none.\n */\n onSearchChange?: (search: string) => void;\n /** The opened row: the app's {@link FeedbackPublicDetail} with the row's\n * `GET /feedback/public/{id}` and its comments. */\n renderDetail: (row: T) => ReactNode;\n /** `PUT` / `DELETE /feedback/{id}/vote` for a row's button (`next`: the new state);\n * resolve with the answer, reject to roll back. */\n onVote: (row: T, next: boolean) => Promise<FeedbackVoteState>;\n /** The page is loading: the table says so where its rows would be. */\n loading?: boolean;\n /** The opened row's id, when the app keeps it (a `?row=` param). Left out, the list\n * keeps it itself. */\n openId?: number | null;\n onOpenChange?: (id: number | null) => void;\n /**\n * 0.38.1: the opened report when it is NOT on this page — a `?row=` link to an older\n * one (Kurvenschmiede). The app fetches it (`GET /feedback/public/{id}`, a list item's\n * fields and more) and passes it with `openId`; while `openId` is its id it is drawn\n * first, above the page's rows, and opened with `renderDetail` (on the phone, in the\n * full-screen dialog). Closed, it is gone. A row the page has already is never drawn\n * twice: the page's own is opened instead.\n */\n openRow?: T | null;\n /**\n * 0.38.1: the server's page, from 0 — for a pager where `/feedback/public` answers no\n * total (Kurvenschmiede). With {@link onPageChange} a **Previous / Page n / Next**\n * strip stands under the table; Next is off without {@link hasMore}. Left out, no\n * pager: the server's `limit` is all there is.\n */\n page?: number;\n /** 0.38.1: there is a page after this one (e.g. the page came back full). */\n hasMore?: boolean;\n /** 0.38.1: go to another page (`page ± 1`); the app fetches it. Draws the pager. */\n onPageChange?: (page: number) => void;\n /** A vote COMMITS — see Button's `commit`. */\n commit?: CommitScope;\n /** See {@link FeedbackRenderDate}. Default: the kit's, with the weekday. */\n renderDate?: FeedbackRenderDate;\n /** Over the `feedbackPublic` namespace. The shared column names (#, Date, Category,\n * Subject, Status) are `feedbackPage`'s: pass `pageLabels` for those. */\n labels?: LabelOverride<FeedbackPublicLabels>;\n pageLabels?: LabelOverride<FeedbackPageLabels>;\n /** 0.40.1: the list fills the window as DataTable's `fillHeight` (page layout §1) — the\n * public feedback page passes §1.2's rule. Below md the page scrolls, as everywhere. */\n fillHeight?: boolean;\n className?: string;\n}\n\n/**\n * **All feedback** — the reports every user the app lets in may read (round 0.38,\n * docs/feedback-harmonization.md §9.3, §9.9; keksdose's `/all-feedback`).\n *\n * - **Titles only** (decision 23): #, Date, Category, Subject (with the visible\n * comments), Votes, Status. No body, no submitter, no file — the list item carries\n * none of them (§9.10).\n * - **The server sorts**: the toggle **Newest / Most wanted / Recently updated** sets\n * `sort`, and the columns themselves do not sort (a page of 50 sorted in the browser\n * would not be the 50 the sort asks for).\n * - **The search box only with `onSearchChange`**: an app whose titles are sealed\n * passes none (§9.9).\n * - **Votes** are `FeedbackVoteButton`s, optimistic, the viewer's own report pressed\n * and locked (\"Your report\", §9.6).\n * - A row opens in place with `renderDetail` — on the phone, in a full-screen dialog.\n * The phone cards carry the title, the status, the category, the date and the two\n * counts; the vote itself is in the opened report (a button inside the card, which is\n * the row's own button, would be two controls in one).\n *\n * No client paging: the server's `limit` is the page, and `total` says how many there\n * are. For more than one page, `page` / `hasMore` / `onPageChange` draw a previous /\n * next strip that needs no total (0.38.1); `openRow` shows a `?row=` report from\n * another page. Needs a router, as every `DataTable` does.\n */\nexport function FeedbackPublicList<T extends FeedbackPublicListItem = FeedbackPublicListItem>({\n rows,\n total,\n sort,\n onSortChange,\n search = \"\",\n onSearchChange,\n renderDetail,\n onVote,\n loading = false,\n openId: openIdProp,\n onOpenChange,\n openRow,\n page: pageIndex = 0,\n hasMore = false,\n onPageChange,\n commit,\n renderDate: renderDateProp,\n labels: labelsProp,\n pageLabels: pageLabelsProp,\n fillHeight,\n className,\n}: FeedbackPublicListProps<T>) {\n const labels = useFeedbackPublicLabels(labelsProp);\n const page = useFeedbackPageLabels(pageLabelsProp);\n const categoryLabels = useFeedbackCategoryLabels();\n const renderDate = useFeedbackRenderDate(renderDateProp);\n const [ownOpenId, setOwnOpenId] = useState<number | null>(null);\n const openId = openIdProp !== undefined ? openIdProp : ownOpenId;\n const setOpen = (id: number | null) => {\n if (openIdProp === undefined) setOwnOpenId(id);\n onOpenChange?.(id);\n };\n // A `?row=` report from another page: first, while it is the open one, and only when\n // the page does not have it itself.\n const shownRows =\n openRow && openId === openRow.id && !rows.some((row) => row.id === openRow.id) ? [openRow, ...rows] : rows;\n\n const columns = useMemo<DataTableColumn<T>[]>(\n () => [\n {\n key: \"id\",\n header: page.columnId,\n cell: (row) => <span className=\"font-mono text-xs text-muted\">{row.id}</span>,\n mobileHidden: true,\n },\n {\n key: \"date\",\n header: page.columnDate,\n cell: (row) => <PublicDate value={row.created_at} renderDate={renderDate} />,\n },\n {\n key: \"category\",\n header: page.columnCategory,\n cell: (row) => <FeedbackCategoryBadge category={row.category} label={categoryLabels[row.category]} />,\n },\n {\n key: \"title\",\n header: page.columnSubject,\n cell: (row) => (\n <span className=\"inline-flex min-w-0 max-w-full items-center gap-2 large:items-start\">\n <span className=\"min-w-0 truncate-until-large font-medium\">{row.title}</span>\n <CommentCount count={row.comment_count} labels={labels} />\n </span>\n ),\n mobilePrimary: true,\n },\n {\n key: \"votes\",\n header: labels.columnVotes,\n className: \"whitespace-nowrap\",\n // The cell is a button: the row's own link must never wrap it.\n noRowLink: true,\n cell: (row) => (\n <FeedbackVoteButton\n votes={row.votes}\n voted={row.voted}\n own={row.mine}\n onVote={(next) => onVote(row, next)}\n commit={commit}\n size=\"sm\"\n />\n ),\n },\n {\n key: \"status\",\n header: page.columnStatus,\n className: \"whitespace-nowrap\",\n cell: (row) => <FeedbackStatusBadge status={row.status} />,\n },\n ],\n [page, labels, categoryLabels, renderDate, onVote, commit],\n );\n\n return (\n <div\n className={cn(\"flex flex-col gap-3\", fillHeight && FILL_HEIGHT_CLASS, className)}\n data-feedback-public-list=\"\"\n aria-busy={loading || undefined}\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <ToggleGroup\n aria-label={labels.sort}\n value={sort}\n onChange={(value) => onSortChange(value)}\n options={SORTS.map((value) => ({\n value,\n label: value === \"newest\" ? labels.sortNewest : value === \"votes\" ? labels.sortVotes : labels.sortUpdated,\n }))}\n />\n {onSearchChange && (\n <SearchField\n aria-label={labels.search}\n placeholder={labels.search}\n value={search}\n onChange={onSearchChange}\n className=\"min-w-[12rem] flex-1 md:max-w-xs\"\n />\n )}\n {total !== undefined && (\n <span className=\"ms-auto text-sm text-muted\" data-feedback-public-total={total}>\n {labels.total(total)}\n </span>\n )}\n </div>\n <DataTable\n rows={shownRows}\n columns={columns}\n rowKey={(row) => row.id}\n rowName={(row) => row.title}\n onRowClick={(row) => setOpen(openId === row.id ? null : row.id)}\n isExpanded={(row) => row.id === openId}\n expandedRow={(row) => renderDetail(row)}\n mobileExpandAsDialog\n paginated={false}\n fillHeight={fillHeight}\n empty={loading ? labels.loading : labels.empty}\n mobileCard={(row) => (\n <div className=\"min-w-0 space-y-1\">\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"min-w-0 flex-1 font-medium [overflow-wrap:anywhere]\">{row.title}</span>\n <FeedbackStatusBadge status={row.status} className=\"shrink-0\" />\n </div>\n <div className=\"flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted\">\n <FeedbackCategoryBadge category={row.category} compact label={categoryLabels[row.category]} />\n <PublicDate value={row.created_at} renderDate={renderDate} />\n <CommentCount count={row.comment_count} labels={labels} />\n {/* A count, not the button: the whole card is the row's button, and a button\n inside it is two controls in one. The vote is in the opened report. */}\n <VoteCount count={row.votes} voted={row.voted || row.mine} labels={labels} />\n </div>\n </div>\n )}\n />\n {onPageChange && (pageIndex > 0 || hasMore) && (\n <PublicPager page={pageIndex} hasMore={hasMore} onPageChange={onPageChange} labels={labels} />\n )}\n </div>\n );\n}\n\n/* ── The detail ───────────────────────────────────────────────────────────── */\n\nexport interface FeedbackPublicDetailProps {\n /** `GET /feedback/public/{id}`: the public body (attachment URLs already stripped by\n * the server, §9.4) and the replies. */\n row: FeedbackPublicRecord;\n /** `GET /feedback/{id}/comments`, oldest first; `undefined` while it loads. */\n comments: readonly FeedbackComment[] | undefined;\n /** `PUT` / `DELETE /feedback/{id}/vote`; resolve with the answer, reject to roll back. */\n onVote: (next: boolean) => Promise<FeedbackVoteState>;\n /** The viewer is the app's admin: Hide / Unhide on the comments. */\n isAdmin?: boolean;\n /** `POST /feedback/{id}/comments`. Left out — a demo, a reader who may not write — no\n * composer. */\n onComment?: (body: string) => void | Promise<unknown>;\n /** The admin's hide (`PATCH …/comments/{id} {hidden}`). */\n onHide?: (comment: FeedbackComment, hidden: boolean) => void | Promise<unknown>;\n /** The writer's delete (`DELETE …/comments/{id}`), after a confirm. */\n onDelete?: (comment: FeedbackComment) => void | Promise<unknown>;\n /** A comment is being sent. */\n pending?: boolean;\n /** The vote and the thread's buttons COMMIT — see Button's `commit`. */\n commit?: CommitScope;\n /** Voting is off for this viewer (a demo): `true`, or the reason. */\n voteDisabled?: boolean | string;\n /** See {@link FeedbackRenderDate}. */\n renderDate?: FeedbackRenderDate;\n /** \"Now\" for the thread's relative times — pin it in tests and stories. */\n now?: Date | number;\n /** Over the `feedbackPublic` namespace. */\n labels?: LabelOverride<FeedbackPublicLabels>;\n /** Over the `feedbackComments` namespace, for the thread. */\n commentLabels?: LabelOverride<FeedbackCommentsLabels>;\n className?: string;\n}\n\n/**\n * One public report opened (round 0.38, docs/feedback-harmonization.md §9.3, §9.10): who\n * filed it — the first name the server sent, else **\"A user\"** (§9.5) — and when; the\n * **vote**; the **public body**; and the **conversation**, the admin's replies and other\n * people's comments merged oldest first (§9.8, `FeedbackCommentThread`), with the box to\n * add one.\n *\n * What it never draws, because the public record never carries it (§9.10): an address,\n * a last name, a screenshot, a file, the page the report was filed on. The body is the\n * server's `public_body` — the client strips nothing and adds nothing.\n */\nexport function FeedbackPublicDetail({\n row,\n comments,\n onVote,\n isAdmin = false,\n onComment,\n onHide,\n onDelete,\n pending,\n commit,\n voteDisabled,\n renderDate: renderDateProp,\n now,\n labels: labelsProp,\n commentLabels,\n className,\n}: FeedbackPublicDetailProps) {\n const labels = useFeedbackPublicLabels(labelsProp);\n const renderDate = useFeedbackRenderDate(renderDateProp);\n return (\n <FeedbackDetail className={className}>\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-2\" data-feedback-public-detail={row.id}>\n <span className=\"text-sm text-secondary\" data-feedback-author=\"\">\n {labels.filedBy(row.author_name ?? labels.anonymous)}\n </span>\n <PublicDate value={row.created_at} renderDate={renderDate} />\n <FeedbackVoteButton\n votes={row.votes}\n voted={row.voted}\n own={row.mine}\n onVote={onVote}\n disabled={voteDisabled}\n commit={commit}\n className=\"ms-auto\"\n />\n </div>\n <FeedbackDetailSection title={labels.body}>\n {/* Rework rounds stay in it — the author's own words (§9.4) — on a server before\n server-kit 0.13; since then each is an entry in the conversation (§10.3). */}\n <FeedbackProse>{row.body}</FeedbackProse>\n </FeedbackDetailSection>\n <FeedbackDetailSection title={labels.conversation}>\n <FeedbackCommentThread\n replies={row.replies}\n comments={comments ?? []}\n loading={comments === undefined}\n isAdmin={isAdmin}\n // 0.40 (§10.3): the author's rework entries are theirs — at the reading end.\n isAuthor={row.mine}\n onComment={onComment}\n onHide={onHide}\n onDelete={onDelete}\n pending={pending}\n commit={commit}\n now={now}\n labels={commentLabels}\n />\n </FeedbackDetailSection>\n </FeedbackDetail>\n );\n}\n"],"mappings":"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","names":[]}
@@ -89,8 +89,6 @@ function germanLabels(numberLocale) {
89
89
  feedbackMenu: {
90
90
  trigger: "Feedback senden",
91
91
  myFeedback: "Mein Feedback",
92
- viewFeedback: "Feedback ansehen",
93
- allFeedback: "Alle R\xFCckmeldungen",
94
92
  discussion: "Feedback-Diskussion",
95
93
  inbox: "Feedback-Eingang"
96
94
  },