@eifi1/ui-kit 0.51.7 → 0.53.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.
- package/README.md +178 -67
- package/dist/account/account-parts.d.ts +10 -8
- package/dist/account/account-parts.js +1 -18
- package/dist/account/account-parts.js.map +1 -1
- package/dist/account/data-export-setting-labels.js +1 -1
- package/dist/account/data-export-setting-labels.js.map +1 -1
- package/dist/account/delete-account-setting-labels.js +1 -1
- package/dist/account/delete-account-setting-labels.js.map +1 -1
- package/dist/account/email-change-setting-labels.js +1 -1
- package/dist/account/email-change-setting-labels.js.map +1 -1
- package/dist/account/passkeys-setting.d.ts +9 -2
- package/dist/account/passkeys-setting.js +41 -29
- package/dist/account/passkeys-setting.js.map +1 -1
- package/dist/account/sessions-setting.d.ts +3 -2
- package/dist/account/sessions-setting.js +10 -8
- package/dist/account/sessions-setting.js.map +1 -1
- package/dist/admin/admin-action-confirm.js +1 -3
- package/dist/admin/admin-action-confirm.js.map +1 -1
- package/dist/admin/admin-parts.d.ts +6 -3
- package/dist/admin/admin-parts.js +6 -21
- package/dist/admin/admin-parts.js.map +1 -1
- package/dist/admin/mail-test-card-labels.js +1 -1
- package/dist/admin/mail-test-card-labels.js.map +1 -1
- package/dist/admin/plan-change-labels.d.ts +2 -2
- package/dist/admin/plan-change-labels.js +1 -17
- package/dist/admin/plan-change-labels.js.map +1 -1
- package/dist/admin/plan-change.d.ts +3 -42
- package/dist/admin/plan-change.js +1 -1
- package/dist/admin/plan-change.js.map +1 -1
- package/dist/admin/reviewer-scope-editor.js +3 -3
- package/dist/admin/reviewer-scope-editor.js.map +1 -1
- package/dist/auth/auth-errors-labels.d.ts +11 -0
- package/dist/auth/auth-errors-labels.js +13 -0
- package/dist/auth/auth-errors-labels.js.map +1 -0
- package/dist/auth/auth-errors.d.ts +7 -23
- package/dist/auth/auth-errors.js +2 -15
- package/dist/auth/auth-errors.js.map +1 -1
- package/dist/auth/complete-name-dialog.js +2 -1
- package/dist/auth/complete-name-dialog.js.map +1 -1
- package/dist/auth/forgot-password-form-labels.js +1 -1
- package/dist/auth/forgot-password-form-labels.js.map +1 -1
- package/dist/auth/forgot-password-form.js +7 -2
- package/dist/auth/forgot-password-form.js.map +1 -1
- package/dist/auth/register-form-labels.js +1 -1
- package/dist/auth/register-form-labels.js.map +1 -1
- package/dist/auth/reset-password-form-labels.js +1 -1
- package/dist/auth/reset-password-form-labels.js.map +1 -1
- package/dist/auth/reset-password-form.js +5 -1
- package/dist/auth/reset-password-form.js.map +1 -1
- package/dist/auth/sign-in-form-labels.js +1 -1
- package/dist/auth/sign-in-form-labels.js.map +1 -1
- package/dist/auth/status-parts.d.ts +0 -3
- package/dist/auth/status-parts.js +0 -4
- package/dist/auth/status-parts.js.map +1 -1
- package/dist/auth/verify-email-labels.js +1 -1
- package/dist/auth/verify-email-labels.js.map +1 -1
- package/dist/auth/verify-email.js +7 -2
- package/dist/auth/verify-email.js.map +1 -1
- package/dist/billing/cancel-subscription.d.ts +10 -7
- package/dist/billing/cancel-subscription.js +6 -6
- package/dist/billing/cancel-subscription.js.map +1 -1
- package/dist/billing/plan-card.d.ts +5 -3
- package/dist/billing/plan-card.js +7 -8
- package/dist/billing/plan-card.js.map +1 -1
- package/dist/billing/plan-change-labels.d.ts +42 -0
- package/dist/billing/plan-change-labels.js +22 -0
- package/dist/billing/plan-change-labels.js.map +1 -0
- package/dist/billing/plan-switch.d.ts +9 -6
- package/dist/billing/plan-switch.js +7 -5
- package/dist/billing/plan-switch.js.map +1 -1
- package/dist/chart.d.ts +4 -3
- package/dist/chart.js +72 -4
- package/dist/chart.js.map +1 -1
- package/dist/components/account-chips.d.ts +5 -5
- package/dist/components/account-chips.js +11 -13
- package/dist/components/account-chips.js.map +1 -1
- package/dist/components/alert-banner.d.ts +16 -5
- package/dist/components/alert-banner.js +46 -1
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +13 -0
- package/dist/components/amount-input.js +1 -0
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.js +57 -65
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/bulk-action-bar.d.ts +12 -2
- package/dist/components/bulk-action-bar.js +65 -21
- package/dist/components/bulk-action-bar.js.map +1 -1
- package/dist/components/button-look.d.ts +5 -5
- package/dist/components/button-look.js.map +1 -1
- package/dist/components/button.d.ts +2 -2
- package/dist/components/button.js.map +1 -1
- package/dist/components/chart-edge.d.ts +16 -0
- package/dist/components/chart-edge.js +14 -1
- package/dist/components/chart-edge.js.map +1 -1
- package/dist/components/chart-figure.d.ts +13 -0
- package/dist/components/chart-figure.js +15 -0
- package/dist/components/chart-figure.js.map +1 -0
- package/dist/components/chart-zoom-core.js +1 -4
- package/dist/components/chart-zoom-core.js.map +1 -1
- package/dist/components/chart.d.ts +8 -1
- package/dist/components/chart.js +19 -10
- package/dist/components/chart.js.map +1 -1
- package/dist/components/combobox-core.d.ts +111 -2
- package/dist/components/combobox-core.js +148 -42
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +1 -1
- package/dist/components/combobox.js +141 -130
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/copy-button.d.ts +6 -5
- package/dist/components/copy-button.js +5 -3
- package/dist/components/copy-button.js.map +1 -1
- package/dist/components/country-select.js +2 -2
- package/dist/components/country-select.js.map +1 -1
- package/dist/components/data-table-column-settings.d.ts +1 -1
- package/dist/components/data-table-column-settings.js +33 -7
- package/dist/components/data-table-column-settings.js.map +1 -1
- package/dist/components/data-table-filters.d.ts +4 -2
- package/dist/components/data-table-filters.js +2 -2
- package/dist/components/data-table-filters.js.map +1 -1
- package/dist/components/data-table-fit.d.ts +6 -4
- package/dist/components/data-table-fit.js +4 -1
- package/dist/components/data-table-fit.js.map +1 -1
- package/dist/components/data-table-groups.d.ts +1 -1
- package/dist/components/data-table-groups.js.map +1 -1
- package/dist/components/data-table-head.d.ts +1 -1
- package/dist/components/data-table-head.js +15 -12
- package/dist/components/data-table-head.js.map +1 -1
- package/dist/components/data-table-mobile.d.ts +12 -7
- package/dist/components/data-table-mobile.js +43 -30
- package/dist/components/data-table-mobile.js.map +1 -1
- package/dist/components/data-table-pagination.js +11 -6
- package/dist/components/data-table-pagination.js.map +1 -1
- package/dist/components/data-table-resize.d.ts +1 -1
- package/dist/components/data-table-resize.js.map +1 -1
- package/dist/components/data-table-row-actions.d.ts +2 -2
- package/dist/components/data-table-row-actions.js.map +1 -1
- package/dist/components/data-table-row.d.ts +6 -2
- package/dist/components/data-table-row.js +17 -10
- package/dist/components/data-table-row.js.map +1 -1
- package/dist/components/data-table-selection.d.ts +1 -1
- package/dist/components/data-table-selection.js.map +1 -1
- package/dist/components/data-table-shared.d.ts +10 -2
- package/dist/components/data-table-shared.js +18 -0
- package/dist/components/data-table-shared.js.map +1 -1
- package/dist/components/data-table-sort.d.ts +21 -2
- package/dist/components/data-table-sort.js +24 -0
- package/dist/components/data-table-sort.js.map +1 -1
- package/dist/components/data-table-virtual.d.ts +1 -1
- package/dist/components/data-table-virtual.js +18 -6
- package/dist/components/data-table-virtual.js.map +1 -1
- package/dist/components/data-table.d.ts +13 -4
- package/dist/components/data-table.js +139 -36
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-field.d.ts +25 -3
- package/dist/components/date-field.js +33 -8
- package/dist/components/date-field.js.map +1 -1
- package/dist/components/date-picker.d.ts +16 -3
- package/dist/components/date-picker.js +55 -26
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/date-range-picker.d.ts +38 -24
- package/dist/components/date-range-picker.js +23 -7
- package/dist/components/date-range-picker.js.map +1 -1
- package/dist/components/error-boundary.js +37 -30
- package/dist/components/error-boundary.js.map +1 -1
- package/dist/components/{ui-labels.d.ts → field-base-labels.d.ts} +0 -2
- package/dist/components/{ui-labels.js → field-base-labels.js} +2 -9
- package/dist/components/field-base-labels.js.map +1 -0
- package/dist/components/field-base.d.ts +4 -3
- package/dist/components/field-base.js +3 -3
- package/dist/components/field-base.js.map +1 -1
- package/dist/components/form-actions.js +11 -5
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/hover-menu.d.ts +3 -0
- package/dist/components/hover-menu.js +5 -3
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/icon-button.js +14 -9
- package/dist/components/icon-button.js.map +1 -1
- package/dist/components/inline-confirm.d.ts +43 -0
- package/dist/components/inline-confirm.js +67 -0
- package/dist/components/inline-confirm.js.map +1 -0
- package/dist/components/inline-entity-combobox.d.ts +4 -3
- package/dist/components/inline-entity-combobox.js +148 -140
- package/dist/components/inline-entity-combobox.js.map +1 -1
- package/dist/components/input.js +1 -1
- package/dist/components/input.js.map +1 -1
- package/dist/components/jump-calendar.js +28 -112
- package/dist/components/jump-calendar.js.map +1 -1
- package/dist/components/legal-page.d.ts +3 -1
- package/dist/components/legal-page.js +3 -2
- package/dist/components/legal-page.js.map +1 -1
- package/dist/components/legal.d.ts +4 -2
- package/dist/components/legal.js.map +1 -1
- package/dist/components/lightbox.d.ts +2 -2
- package/dist/components/lightbox.js +17 -6
- package/dist/components/lightbox.js.map +1 -1
- package/dist/components/list.d.ts +6 -2
- package/dist/components/list.js +3 -2
- package/dist/components/list.js.map +1 -1
- package/dist/components/measured-grid.d.ts +1 -1
- package/dist/components/measured-grid.js +2 -1
- package/dist/components/measured-grid.js.map +1 -1
- package/dist/components/mini-calendar.js +3 -5
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.js +3 -1
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-grid.d.ts +55 -0
- package/dist/components/month-grid.js +148 -0
- package/dist/components/month-grid.js.map +1 -0
- package/dist/components/month-picker.d.ts +36 -4
- package/dist/components/month-picker.js +123 -146
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-select.d.ts +2 -2
- package/dist/components/multi-select.js +11 -6
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/nav-pills.d.ts +1 -1
- package/dist/components/nav-pills.js.map +1 -1
- package/dist/components/picker-sheet.js +2 -1
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/pie-chart-labels.d.ts +7 -0
- package/dist/components/pie-chart-labels.js +1 -0
- package/dist/components/pie-chart-labels.js.map +1 -1
- package/dist/components/pie-chart.d.ts +5 -3
- package/dist/components/pie-chart.js +7 -22
- package/dist/components/pie-chart.js.map +1 -1
- package/dist/components/popover.d.ts +3 -0
- package/dist/components/popover.js +7 -1
- package/dist/components/popover.js.map +1 -1
- package/dist/components/row-actions.js +2 -1
- package/dist/components/row-actions.js.map +1 -1
- package/dist/components/series-chart-axes.d.ts +4 -4
- package/dist/components/series-chart-axes.js.map +1 -1
- package/dist/components/series-chart-labels.d.ts +7 -4
- package/dist/components/series-chart-labels.js.map +1 -1
- package/dist/components/series-chart.d.ts +92 -23
- package/dist/components/series-chart.js +74 -30
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/share-card.js +3 -3
- package/dist/components/share-card.js.map +1 -1
- package/dist/components/tabs-labels.d.ts +2 -0
- package/dist/components/tabs-labels.js +11 -0
- package/dist/components/tabs-labels.js.map +1 -0
- package/dist/components/tabs.d.ts +1 -1
- package/dist/components/tabs.js +1 -1
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/toggle-legend.d.ts +2 -2
- package/dist/components/toggle-legend.js +6 -6
- package/dist/components/toggle-legend.js.map +1 -1
- package/dist/components/translation-review-editor.d.ts +4 -2
- package/dist/components/translation-review-editor.js +2 -3
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review.d.ts +2 -1
- package/dist/components/translation-review.js +2 -2
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/treemap-labels.d.ts +31 -0
- package/dist/components/treemap-labels.js +10 -0
- package/dist/components/treemap-labels.js.map +1 -0
- package/dist/components/treemap-layout.d.ts +62 -0
- package/dist/components/treemap-layout.js +126 -0
- package/dist/components/treemap-layout.js.map +1 -0
- package/dist/components/treemap.d.ts +167 -19
- package/dist/components/treemap.js +340 -100
- package/dist/components/treemap.js.map +1 -1
- package/dist/components/use-table-state.d.ts +5 -5
- package/dist/components/use-table-state.js +4 -1
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/data-table.d.ts +1 -2
- package/dist/data-table.js +1 -4
- package/dist/data-table.js.map +1 -1
- package/dist/demo/demo-countdown.d.ts +0 -5
- package/dist/demo/demo-countdown.js +0 -4
- package/dist/demo/demo-countdown.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/eslint/role-utilities.d.ts +36 -7
- package/dist/eslint/role-utilities.js +42 -9
- package/dist/eslint/role-utilities.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +25 -16
- package/dist/feedback/feedback-dialog.js +6 -2
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-mutation.d.ts +18 -0
- package/dist/feedback/feedback-mutation.js +8 -0
- package/dist/feedback/feedback-mutation.js.map +1 -0
- package/dist/feedback/feedback-page-link.d.ts +53 -0
- package/dist/feedback/feedback-page-link.js +37 -0
- package/dist/feedback/feedback-page-link.js.map +1 -0
- package/dist/feedback/feedback-public-list.d.ts +8 -4
- package/dist/feedback/feedback-public-list.js +2 -2
- package/dist/feedback/feedback-public-list.js.map +1 -1
- package/dist/feedback/feedback-replies.d.ts +3 -1
- package/dist/feedback/feedback-replies.js +5 -4
- package/dist/feedback/feedback-replies.js.map +1 -1
- package/dist/feedback/feedback-row-detail-labels.js +0 -3
- package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +33 -44
- package/dist/feedback/feedback-row-detail.js +28 -34
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-status-undo.d.ts +17 -14
- package/dist/feedback/feedback-status-undo.js +10 -15
- package/dist/feedback/feedback-status-undo.js.map +1 -1
- package/dist/feedback/feedback-submit-dialog.d.ts +1 -1
- package/dist/feedback/feedback-submit-dialog.js +1 -1
- package/dist/feedback/feedback-submit-dialog.js.map +1 -1
- package/dist/feedback/feedback-submit.js +9 -3
- package/dist/feedback/feedback-submit.js.map +1 -1
- package/dist/feedback/feedback-swipe.d.ts +1 -1
- package/dist/feedback/feedback-swipe.js.map +1 -1
- package/dist/feedback/feedback-table.d.ts +21 -9
- package/dist/feedback/feedback-table.js +45 -25
- package/dist/feedback/feedback-table.js.map +1 -1
- package/dist/feedback/feedback-thread-labels.d.ts +2 -2
- package/dist/feedback/feedback-thread-labels.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +15 -23
- package/dist/feedback/feedback-thread.js +2 -1
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/feedback.d.ts +6 -6
- package/dist/feedback.js +90 -6
- package/dist/feedback.js.map +1 -1
- package/dist/hooks/use-focus-trap.d.ts +35 -0
- package/dist/hooks/use-focus-trap.js +6 -4
- package/dist/hooks/use-focus-trap.js.map +1 -1
- package/dist/hooks/use-hotkey.d.ts +5 -2
- package/dist/hooks/use-hotkey.js +5 -4
- package/dist/hooks/use-hotkey.js.map +1 -1
- package/dist/hooks/use-long-press.d.ts +7 -4
- package/dist/hooks/use-long-press.js +3 -0
- package/dist/hooks/use-long-press.js.map +1 -1
- package/dist/hooks/use-media-query.js +12 -4
- package/dist/hooks/use-media-query.js.map +1 -1
- package/dist/hooks/use-mounted.d.ts +22 -0
- package/dist/hooks/use-mounted.js +24 -0
- package/dist/hooks/use-mounted.js.map +1 -0
- package/dist/hooks/use-overlay-history.js +19 -3
- package/dist/hooks/use-overlay-history.js.map +1 -1
- package/dist/i18n/defaults.js +4 -9
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +7 -18
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +9 -11
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +15 -64
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +7 -18
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +20 -27
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +9 -18
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +7 -19
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +8 -18
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +12 -2
- package/dist/i18n/review.js.map +1 -1
- package/dist/i18n/shared-labels.js +0 -1
- package/dist/i18n/shared-labels.js.map +1 -1
- package/dist/index.d.ts +21 -23
- package/dist/index.js +50 -65
- package/dist/index.js.map +1 -1
- package/dist/landing/routing.d.ts +3 -1
- package/dist/landing/routing.js +4 -7
- package/dist/landing/routing.js.map +1 -1
- package/dist/lib/broadcast.d.ts +17 -0
- package/dist/lib/broadcast.js +30 -0
- package/dist/lib/broadcast.js.map +1 -0
- package/dist/lib/date-format.d.ts +37 -2
- package/dist/lib/date-format.js +34 -8
- package/dist/lib/date-format.js.map +1 -1
- package/dist/lib/day-format.d.ts +46 -0
- package/dist/lib/day-format.js +46 -0
- package/dist/lib/day-format.js.map +1 -0
- package/dist/lib/describe-failure.d.ts +14 -0
- package/dist/lib/describe-failure.js +9 -0
- package/dist/lib/describe-failure.js.map +1 -0
- package/dist/lib/direction.d.ts +16 -0
- package/dist/lib/direction.js +9 -1
- package/dist/lib/direction.js.map +1 -1
- package/dist/lib/focus-left.d.ts +63 -0
- package/dist/lib/focus-left.js +67 -0
- package/dist/lib/focus-left.js.map +1 -0
- package/dist/lib/format.js +5 -14
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/http-status.d.ts +24 -0
- package/dist/lib/http-status.js +14 -0
- package/dist/lib/http-status.js.map +1 -0
- package/dist/lib/intl-cache.d.ts +14 -0
- package/dist/lib/intl-cache.js +15 -0
- package/dist/lib/intl-cache.js.map +1 -0
- package/dist/lib/iso-day.d.ts +11 -0
- package/dist/lib/iso-day.js +8 -0
- package/dist/lib/iso-day.js.map +1 -0
- package/dist/lib/lazy-with-fallback.d.ts +31 -0
- package/dist/lib/lazy-with-fallback.js +40 -0
- package/dist/lib/lazy-with-fallback.js.map +1 -0
- package/dist/lib/path-pattern.d.ts +34 -0
- package/dist/lib/path-pattern.js +24 -0
- package/dist/lib/path-pattern.js.map +1 -0
- package/dist/lib/reduced-motion.d.ts +2 -1
- package/dist/lib/reduced-motion.js.map +1 -1
- package/dist/lib/round.d.ts +2 -1
- package/dist/lib/round.js +2 -1
- package/dist/lib/round.js.map +1 -1
- package/dist/lib/safe-storage.d.ts +24 -5
- package/dist/lib/safe-storage.js +27 -2
- package/dist/lib/safe-storage.js.map +1 -1
- package/dist/lib/session-refresh.js +1 -1
- package/dist/lib/session-refresh.js.map +1 -1
- package/dist/lib/share-format.d.ts +13 -0
- package/dist/lib/share-format.js +10 -0
- package/dist/lib/share-format.js.map +1 -0
- package/dist/lib/text-case.d.ts +17 -0
- package/dist/lib/text-case.js +8 -0
- package/dist/lib/text-case.js.map +1 -0
- package/dist/lib/text-match.d.ts +29 -0
- package/dist/lib/text-match.js +20 -2
- package/dist/lib/text-match.js.map +1 -1
- package/dist/lib/thenable.d.ts +13 -0
- package/dist/lib/thenable.js +8 -0
- package/dist/lib/thenable.js.map +1 -0
- package/dist/lib/translation-review.d.ts +7 -2
- package/dist/lib/translation-review.js +4 -4
- package/dist/lib/translation-review.js.map +1 -1
- package/dist/notifications/device-zone-report.js +1 -1
- package/dist/notifications/device-zone-report.js.map +1 -1
- package/dist/notifications/notification-bell.d.ts +2 -1
- package/dist/notifications/notification-bell.js +3 -3
- package/dist/notifications/notification-bell.js.map +1 -1
- package/dist/notifications/notification-inbox.d.ts +10 -6
- package/dist/notifications/notification-inbox.js +26 -48
- package/dist/notifications/notification-inbox.js.map +1 -1
- package/dist/notifications/notification-parts.d.ts +3 -2
- package/dist/notifications/notification-parts.js +1 -3
- package/dist/notifications/notification-parts.js.map +1 -1
- package/dist/notifications/notification-types-setting.js +1 -1
- package/dist/notifications/notification-types-setting.js.map +1 -1
- package/dist/notifications/notifications-labels.d.ts +6 -0
- package/dist/notifications/notifications-labels.js +1 -0
- package/dist/notifications/notifications-labels.js.map +1 -1
- package/dist/notifications/push-device-setting.js +1 -1
- package/dist/notifications/push-device-setting.js.map +1 -1
- package/dist/notifications/push-devices-setting.d.ts +2 -1
- package/dist/notifications/push-devices-setting.js +27 -49
- package/dist/notifications/push-devices-setting.js.map +1 -1
- package/dist/notifications/quiet-hours-setting.d.ts +10 -3
- package/dist/notifications/quiet-hours-setting.js +65 -26
- package/dist/notifications/quiet-hours-setting.js.map +1 -1
- package/dist/notifications/unsubscribe-panel.js +4 -7
- package/dist/notifications/unsubscribe-panel.js.map +1 -1
- package/dist/notifications/use-push-device.d.ts +39 -1
- package/dist/notifications/use-push-device.js +28 -7
- package/dist/notifications/use-push-device.js.map +1 -1
- package/dist/offline/query-persistence.d.ts +3 -1
- package/dist/offline/query-persistence.js +19 -2
- package/dist/offline/query-persistence.js.map +1 -1
- package/dist/rhf/fields.d.ts +11 -4
- package/dist/rhf/fields.js.map +1 -1
- package/dist/search/command-palette.js +1 -1
- package/dist/search/command-palette.js.map +1 -1
- package/dist/search/global-search.js +19 -9
- package/dist/search/global-search.js.map +1 -1
- package/dist/search/search-index.d.ts +0 -6
- package/dist/search/search-index.js +0 -4
- package/dist/search/search-index.js.map +1 -1
- package/dist/search/use-command-key.d.ts +12 -1
- package/dist/search/use-command-key.js +3 -12
- package/dist/search/use-command-key.js.map +1 -1
- package/dist/search.d.ts +1 -1
- package/dist/search.js +6 -1
- package/dist/search.js.map +1 -1
- package/dist/settings/use-settings-focus.js +1 -0
- package/dist/settings/use-settings-focus.js.map +1 -1
- package/dist/shell/account-menu-language.d.ts +5 -3
- package/dist/shell/account-menu-language.js +2 -2
- package/dist/shell/account-menu-language.js.map +1 -1
- package/dist/shell/account-menu-text-size.d.ts +9 -6
- package/dist/shell/account-menu-text-size.js +2 -2
- package/dist/shell/account-menu-text-size.js.map +1 -1
- package/dist/shell/app-shell.d.ts +0 -10
- package/dist/shell/app-shell.js +1 -7
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/company-switcher.d.ts +1 -1
- package/dist/shell/company-switcher.js.map +1 -1
- package/dist/shell/dev-menu.d.ts +1 -1
- package/dist/shell/dev-menu.js +5 -18
- package/dist/shell/dev-menu.js.map +1 -1
- package/dist/shell/top-bar-actions.d.ts +1 -1
- package/dist/shell/top-bar-actions.js.map +1 -1
- package/dist/shell/top-bar-parts-labels.d.ts +1 -3
- package/dist/shell/top-bar-parts-labels.js +0 -1
- package/dist/shell/top-bar-parts-labels.js.map +1 -1
- package/dist/shell.d.ts +7 -10
- package/dist/shell.js +14 -23
- package/dist/shell.js.map +1 -1
- package/dist/sync/engine.d.ts +30 -4
- package/dist/sync/engine.js +172 -52
- package/dist/sync/engine.js.map +1 -1
- package/dist/sync.d.ts +2 -2
- package/dist/sync.js +2 -10
- package/dist/sync.js.map +1 -1
- package/dist/testing/css-variables.js +3 -8
- package/dist/testing/css-variables.js.map +1 -1
- package/dist/theme/chart-palette.d.ts +0 -5
- package/dist/theme/chart-palette.js +0 -2
- package/dist/theme/chart-palette.js.map +1 -1
- package/dist/theme/contrast.d.ts +2 -1
- package/dist/theme/contrast.js +3 -8
- package/dist/theme/contrast.js.map +1 -1
- package/dist/theme/palette-store.js +6 -6
- package/dist/theme/palette-store.js.map +1 -1
- package/dist/theme/text-size.js +3 -8
- package/dist/theme/text-size.js.map +1 -1
- package/dist/theme/theme-store.js +3 -6
- package/dist/theme/theme-store.js.map +1 -1
- package/dist/tour/tour-catalogue.d.ts +7 -4
- package/dist/tour/tour-catalogue.js +12 -10
- package/dist/tour/tour-catalogue.js.map +1 -1
- package/dist/tour/tour-dock.d.ts +0 -2
- package/dist/tour/tour-dock.js +0 -2
- package/dist/tour/tour-dock.js.map +1 -1
- package/dist/tour/tour-gates.d.ts +4 -0
- package/dist/tour/tour-gates.js +14 -0
- package/dist/tour/tour-gates.js.map +1 -1
- package/dist/tour/tour-overlay.d.ts +27 -0
- package/dist/tour/tour-overlay.js +741 -0
- package/dist/tour/tour-overlay.js.map +1 -0
- package/dist/tour/tour.d.ts +13 -3
- package/dist/tour/tour.js +59 -781
- package/dist/tour/tour.js.map +1 -1
- package/dist/tour/use-tour-progress.js +1 -1
- package/dist/tour/use-tour-progress.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +5 -1
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +6 -2
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.d.ts +3 -1
- package/dist/wizard/use-wizard.js.map +1 -1
- package/dist/wizard/validation.d.ts +3 -1
- package/dist/wizard/validation.js.map +1 -1
- package/package.json +10 -6
- package/src/account/account-parts.ts +5 -25
- package/src/account/data-export-setting-labels.ts +1 -1
- package/src/account/delete-account-setting-labels.ts +1 -1
- package/src/account/email-change-setting-labels.ts +1 -1
- package/src/account/passkeys-setting.tsx +60 -38
- package/src/account/sessions-setting.tsx +18 -12
- package/src/admin/admin-action-confirm.tsx +1 -5
- package/src/admin/admin-parts.ts +11 -25
- package/src/admin/mail-test-card-labels.ts +1 -1
- package/src/admin/plan-change-labels.ts +5 -23
- package/src/admin/plan-change.tsx +7 -44
- package/src/admin/reviewer-scope-editor.tsx +3 -3
- package/src/auth/auth-errors-labels.ts +23 -0
- package/src/auth/auth-errors.ts +11 -38
- package/src/auth/complete-name-dialog.tsx +4 -1
- package/src/auth/forgot-password-form-labels.ts +1 -1
- package/src/auth/forgot-password-form.tsx +11 -6
- package/src/auth/register-form-labels.ts +1 -1
- package/src/auth/reset-password-form-labels.ts +1 -1
- package/src/auth/reset-password-form.tsx +8 -4
- package/src/auth/sign-in-form-labels.ts +1 -1
- package/src/auth/status-parts.tsx +0 -6
- package/src/auth/verify-email-labels.ts +1 -1
- package/src/auth/verify-email.tsx +11 -6
- package/src/billing/cancel-subscription.tsx +15 -12
- package/src/billing/plan-card.tsx +18 -10
- package/src/billing/plan-change-labels.ts +66 -0
- package/src/billing/plan-switch.tsx +15 -12
- package/src/chart.ts +73 -6
- package/src/components/account-chips.tsx +20 -23
- package/src/components/alert-banner.tsx +82 -5
- package/src/components/amount-input.tsx +18 -7
- package/src/components/autocomplete.tsx +64 -70
- package/src/components/bulk-action-bar.tsx +70 -2
- package/src/components/button-look.ts +5 -5
- package/src/components/button.tsx +2 -2
- package/src/components/chart-edge.ts +34 -1
- package/src/components/chart-figure.tsx +35 -0
- package/src/components/chart-zoom-core.tsx +6 -5
- package/src/components/chart.tsx +26 -5
- package/src/components/combobox-core.tsx +293 -62
- package/src/components/combobox.tsx +183 -153
- package/src/components/copy-button.tsx +34 -23
- package/src/components/country-select.tsx +16 -11
- package/src/components/data-table-column-settings.tsx +52 -10
- package/src/components/data-table-filters.ts +6 -4
- package/src/components/data-table-fit.ts +19 -5
- package/src/components/data-table-groups.tsx +1 -1
- package/src/components/data-table-head.tsx +32 -22
- package/src/components/data-table-mobile.tsx +61 -26
- package/src/components/data-table-pagination.tsx +21 -6
- package/src/components/data-table-resize.ts +1 -1
- package/src/components/data-table-row-actions.tsx +3 -3
- package/src/components/data-table-row.tsx +25 -14
- package/src/components/data-table-selection.ts +1 -1
- package/src/components/data-table-shared.tsx +33 -1
- package/src/components/data-table-sort.ts +57 -2
- package/src/components/data-table-virtual.ts +32 -8
- package/src/components/data-table.tsx +239 -68
- package/src/components/date-field.tsx +66 -9
- package/src/components/date-picker.tsx +125 -49
- package/src/components/date-range-picker.tsx +71 -36
- package/src/components/error-boundary.tsx +53 -39
- package/src/components/field-base-labels.ts +20 -0
- package/src/components/field-base.tsx +13 -7
- package/src/components/form-actions.tsx +20 -5
- package/src/components/hover-menu.tsx +18 -8
- package/src/components/icon-button.tsx +14 -9
- package/src/components/inline-confirm.tsx +116 -0
- package/src/components/inline-entity-combobox.tsx +197 -153
- package/src/components/input.tsx +1 -1
- package/src/components/jump-calendar.tsx +30 -146
- package/src/components/legal-page.tsx +10 -7
- package/src/components/legal.tsx +4 -2
- package/src/components/lightbox.tsx +21 -8
- package/src/components/list.tsx +10 -4
- package/src/components/measured-grid.tsx +8 -4
- package/src/components/mini-calendar.tsx +6 -5
- package/src/components/modal.tsx +6 -1
- package/src/components/month-grid.tsx +229 -0
- package/src/components/month-picker.tsx +192 -200
- package/src/components/multi-select.tsx +21 -9
- package/src/components/nav-pills.tsx +1 -1
- package/src/components/picker-sheet.tsx +12 -9
- package/src/components/pie-chart-labels.ts +8 -0
- package/src/components/pie-chart.tsx +22 -28
- package/src/components/popover.tsx +18 -1
- package/src/components/row-actions.tsx +2 -1
- package/src/components/series-chart-axes.ts +9 -4
- package/src/components/series-chart-labels.ts +7 -4
- package/src/components/series-chart.tsx +253 -87
- package/src/components/share-card.tsx +6 -6
- package/src/components/tabs-labels.ts +13 -0
- package/src/components/tabs.tsx +2 -2
- package/src/components/toggle-legend.tsx +16 -11
- package/src/components/translation-review-editor.tsx +6 -5
- package/src/components/translation-review.tsx +4 -3
- package/src/components/treemap-labels.ts +47 -0
- package/src/components/treemap-layout.ts +210 -0
- package/src/components/treemap.tsx +780 -138
- package/src/components/use-table-state.ts +12 -9
- package/src/data-table.ts +1 -4
- package/src/demo/demo-countdown.ts +3 -8
- package/src/eslint/role-utilities.ts +97 -19
- package/src/feedback/feedback-dialog.tsx +30 -19
- package/src/feedback/feedback-mutation.ts +20 -0
- package/src/feedback/feedback-page-link.ts +90 -0
- package/src/feedback/feedback-public-list.tsx +13 -7
- package/src/feedback/feedback-replies.tsx +13 -6
- package/src/feedback/feedback-row-detail-labels.ts +0 -3
- package/src/feedback/feedback-row-detail.tsx +70 -84
- package/src/feedback/feedback-status-undo.ts +28 -28
- package/src/feedback/feedback-submit-dialog.tsx +2 -2
- package/src/feedback/feedback-submit.tsx +21 -7
- package/src/feedback/feedback-swipe.tsx +1 -1
- package/src/feedback/feedback-table.tsx +86 -38
- package/src/feedback/feedback-thread-labels.ts +2 -2
- package/src/feedback/feedback-thread.tsx +18 -26
- package/src/feedback.ts +89 -6
- package/src/hooks/use-focus-trap.ts +8 -24
- package/src/hooks/use-hotkey.ts +24 -7
- package/src/hooks/use-long-press.ts +16 -2
- package/src/hooks/use-media-query.ts +20 -4
- package/src/hooks/use-mounted.ts +39 -0
- package/src/hooks/use-overlay-history.ts +32 -3
- package/src/i18n/defaults.ts +4 -9
- package/src/i18n/german.ts +8 -19
- package/src/i18n/kit-labels.tsx +9 -11
- package/src/i18n/locales/es.ts +8 -19
- package/src/i18n/locales/fr.ts +21 -30
- package/src/i18n/locales/hu.ts +10 -19
- package/src/i18n/locales/it.ts +8 -20
- package/src/i18n/locales/zh.ts +9 -19
- package/src/i18n/review.ts +12 -2
- package/src/i18n/shared-labels.ts +0 -1
- package/src/index.ts +57 -49
- package/src/landing/routing.tsx +14 -10
- package/src/lib/broadcast.ts +52 -0
- package/src/lib/date-format.ts +89 -13
- package/src/lib/day-format.ts +96 -0
- package/src/lib/describe-failure.ts +18 -0
- package/src/lib/direction.ts +39 -0
- package/src/lib/focus-left.ts +156 -0
- package/src/lib/format.ts +5 -17
- package/src/lib/http-status.ts +38 -0
- package/src/lib/intl-cache.ts +25 -0
- package/src/lib/iso-day.ts +13 -0
- package/src/lib/lazy-with-fallback.ts +82 -0
- package/src/lib/path-pattern.ts +55 -0
- package/src/lib/reduced-motion.ts +2 -1
- package/src/lib/round.ts +7 -2
- package/src/lib/safe-storage.ts +48 -6
- package/src/lib/session-refresh.ts +1 -1
- package/src/lib/share-format.ts +18 -0
- package/src/lib/text-case.ts +19 -0
- package/src/lib/text-match.ts +57 -2
- package/src/lib/thenable.ts +19 -0
- package/src/lib/translation-review.ts +13 -5
- package/src/notifications/device-zone-report.ts +1 -1
- package/src/notifications/notification-bell.tsx +7 -4
- package/src/notifications/notification-inbox.tsx +40 -58
- package/src/notifications/notification-parts.tsx +3 -4
- package/src/notifications/notification-types-setting.tsx +1 -1
- package/src/notifications/notifications-labels.ts +7 -0
- package/src/notifications/push-device-setting.tsx +1 -1
- package/src/notifications/push-devices-setting.tsx +30 -55
- package/src/notifications/quiet-hours-setting.tsx +93 -31
- package/src/notifications/unsubscribe-panel.tsx +4 -7
- package/src/notifications/use-push-device.ts +76 -10
- package/src/offline/query-persistence.ts +35 -3
- package/src/rhf/fields.tsx +11 -4
- package/src/search/command-palette.tsx +2 -4
- package/src/search/global-search.tsx +36 -15
- package/src/search/search-index.ts +0 -15
- package/src/search/use-command-key.ts +13 -12
- package/src/search.ts +10 -1
- package/src/settings/use-settings-focus.ts +3 -0
- package/src/shell/account-menu-language.tsx +6 -4
- package/src/shell/account-menu-text-size.tsx +10 -7
- package/src/shell/app-shell.tsx +0 -13
- package/src/shell/company-switcher.tsx +1 -1
- package/src/shell/dev-menu.tsx +8 -22
- package/src/shell/top-bar-actions.tsx +1 -1
- package/src/shell/top-bar-parts-labels.ts +1 -4
- package/src/shell.ts +21 -17
- package/src/sync/engine.ts +339 -61
- package/src/sync.ts +2 -8
- package/src/testing/css-variables.ts +8 -9
- package/src/theme/chart-palette.ts +0 -2
- package/src/theme/contrast.ts +8 -9
- package/src/theme/palette-store.ts +9 -9
- package/src/theme/text-size.ts +6 -8
- package/src/theme/theme-store.ts +7 -9
- package/src/tour/tour-catalogue.tsx +26 -15
- package/src/tour/tour-dock.ts +0 -2
- package/src/tour/tour-gates.ts +23 -0
- package/src/tour/tour-overlay.tsx +1170 -0
- package/src/tour/tour.tsx +108 -1202
- package/src/tour/use-tour-progress.ts +1 -1
- package/src/wizard/stepper-nav.tsx +3 -1
- package/src/wizard/types.ts +11 -2
- package/src/wizard/use-wizard.ts +2 -1
- package/src/wizard/validation.ts +8 -2
- package/tokens.css +6 -5
- package/dist/components/field-sync-labels.d.ts +0 -2
- package/dist/components/field-sync-labels.js +0 -12
- package/dist/components/field-sync-labels.js.map +0 -1
- package/dist/components/field-sync.d.ts +0 -164
- package/dist/components/field-sync.js +0 -269
- package/dist/components/field-sync.js.map +0 -1
- package/dist/components/section-nav.d.ts +0 -37
- package/dist/components/section-nav.js +0 -33
- package/dist/components/section-nav.js.map +0 -1
- package/dist/components/sign-chip-labels.d.ts +0 -2
- package/dist/components/sign-chip-labels.js +0 -11
- package/dist/components/sign-chip-labels.js.map +0 -1
- package/dist/components/sign-chip.d.ts +0 -68
- package/dist/components/sign-chip.js +0 -47
- package/dist/components/sign-chip.js.map +0 -1
- package/dist/components/ui-labels.js.map +0 -1
- package/dist/lib/isin.d.ts +0 -28
- package/dist/lib/isin.js +0 -36
- package/dist/lib/isin.js.map +0 -1
- package/dist/shell/dev-switchers.d.ts +0 -6
- package/dist/shell/dev-switchers.js +0 -12
- package/dist/shell/dev-switchers.js.map +0 -1
- package/dist/shell/role-switcher.d.ts +0 -21
- package/dist/shell/role-switcher.js +0 -31
- package/dist/shell/role-switcher.js.map +0 -1
- package/src/components/field-sync-labels.ts +0 -13
- package/src/components/field-sync.tsx +0 -482
- package/src/components/section-nav.tsx +0 -65
- package/src/components/sign-chip-labels.ts +0 -12
- package/src/components/sign-chip.tsx +0 -124
- package/src/components/ui-labels.ts +0 -24
- package/src/lib/isin.ts +0 -62
- package/src/shell/dev-switchers.tsx +0 -18
- package/src/shell/role-switcher.tsx +0 -46
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { matchesPathPattern, routerPathname } from "../lib/path-pattern.js";
|
|
3
|
+
const FEEDBACK_ONE_SHOT_PAGES = Object.freeze([
|
|
4
|
+
"/join",
|
|
5
|
+
"/register",
|
|
6
|
+
"/verify-email",
|
|
7
|
+
"/reset-password"
|
|
8
|
+
]);
|
|
9
|
+
function isOneShotPage(href, exclude = []) {
|
|
10
|
+
const pathname = routerPathname(href);
|
|
11
|
+
const named = (pattern) => matchesPathPattern(pathname, pattern);
|
|
12
|
+
return FEEDBACK_ONE_SHOT_PAGES.some(named) || exclude.some(named);
|
|
13
|
+
}
|
|
14
|
+
const localPath = (pathname) => pathname.replace(/^\/{2,}/, "/");
|
|
15
|
+
function feedbackPage(context) {
|
|
16
|
+
const raw = (context?.url || context?.route || "").trim();
|
|
17
|
+
if (!raw) return null;
|
|
18
|
+
let url;
|
|
19
|
+
try {
|
|
20
|
+
url = new URL(raw, "http://localhost");
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
25
|
+
const path = localPath(url.pathname);
|
|
26
|
+
return { path, href: `${path}${url.search}${url.hash}`, url: raw };
|
|
27
|
+
}
|
|
28
|
+
function feedbackPageLinkTarget(page, exclude = []) {
|
|
29
|
+
return isOneShotPage(page.path, exclude) ? null : page.path;
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
FEEDBACK_ONE_SHOT_PAGES,
|
|
33
|
+
feedbackPage,
|
|
34
|
+
feedbackPageLinkTarget,
|
|
35
|
+
isOneShotPage
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=feedback-page-link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/feedback/feedback-page-link.ts"],"sourcesContent":["import type { PathPattern } from \"../landing/routing\";\nimport { matchesPathPattern, routerPathname } from \"../lib/path-pattern\";\nimport type { FeedbackContext } from \"./feedback-record\";\n\n/**\n * The page a report was filed on, and which of it the feedback parts link (refactor audit\n * 2026-10-30, FLW-7).\n *\n * The page comes from the REPORTER's client context: its origin is always this app's\n * (only the path is ever linked), but its path and query are whatever the row says. A\n * signed-in user could file a report whose link takes the admin to a route that acts on\n * load — keksdose's `/join?token=` accepts an invitation through `AcceptInvitation`,\n * kastlan's and Kurvenschmiede's `/register?invite=` likewise.\n *\n * **Only the PATH is linked** (0.52 code review, this file). The tokens and the forward\n * targets travel in the query: a guard on the path alone let `/login?next=%2Fjoin%3Ftoken%3D…`\n * through as a link, and `RedirectIfAuthed` sent the signed-in admin on to the invitation —\n * as `/forgot-password`, `/demo` and `/welcome` would, and whatever forwarder an app adds\n * next (`?returnTo=`, a share target). No list of forwarders can know them all; a link\n * without a query reaches none. The query and hash are shown as text beside it, and \"Copy\n * URL\" copies the whole address, for whoever wants that exact state and opens it knowingly.\n *\n * **A one-shot PATH is not linked at all** — the kit's `/join`, `/register`,\n * `/verify-email`, `/reset-password` (`/reset-password/<token>` carries it in the path),\n * and the app's `pageLinkExclude` on top: shown as text with Copy.\n *\n * @internal Not part of `/feedback` or the barrel.\n */\n\n/** The kit's one-shot routes: the invitation accepts and the two token pages. */\nexport const FEEDBACK_ONE_SHOT_PAGES: readonly PathPattern[] = Object.freeze([\n \"/join\",\n \"/register\",\n \"/verify-email\",\n \"/reset-password\",\n]);\n\n/** Whether `href` (a path, with or without its query) names a one-shot route — the kit's,\n * or one of the app's `exclude` on top. Read as the ROUTER reads it, decoded and\n * case-insensitive: `/Join?token=` and `/j%6Fin?token=` render `/join` too (0.52 code\n * review, this file). */\nexport function isOneShotPage(href: string, exclude: readonly PathPattern[] = []): boolean {\n const pathname = routerPathname(href);\n const named = (pattern: PathPattern) => matchesPathPattern(pathname, pattern);\n return FEEDBACK_ONE_SHOT_PAGES.some(named) || exclude.some(named);\n}\n\n/** The page a report was filed on, read once — the URL cell and the detail each parsed it\n * three times, once per part (0.52 code review, feedback-table.tsx: 810 `URL`s per render\n * of a 270-row inbox, for one column). */\nexport interface FeedbackPage {\n /** The path: what the URL column shows, and the only part ever linked. */\n path: string;\n /** Path + query + hash: the page as the reporter had it (keksdose `localHref`: the query\n * and hash carry the state, `?tab=import`, a drill-down) — shown, never followed. */\n href: string;\n /** What \"Copy URL\" copies: the FULL `context.url` as the reporter's browser had it —\n * origin included, the one part that says which copy of the app it was (feedback\n * #278) — else `context.route`. */\n url: string;\n}\n\n/** Collapses a leading `//` run: `/\\/\\/evil.example/` as a path would be a\n * protocol-relative link off the app. */\nconst localPath = (pathname: string) => pathname.replace(/^\\/{2,}/, \"/\");\n\n/** `context.url`, else `context.route`, parsed — or null when neither is set or the value\n * is no http(s) address or path (a `javascript:` value is not a page). */\nexport function feedbackPage(context: FeedbackContext | null | undefined): FeedbackPage | null {\n const raw = (context?.url || context?.route || \"\").trim();\n if (!raw) return null;\n let url: URL;\n try {\n // A base, so a PATH-ONLY value parses too: Kurvenschmiede backfills its old rows\n // with `url = page_path + search` (§3.2), and keksdose's `new URL(raw)` threw on\n // those and dropped the section.\n url = new URL(raw, \"http://localhost\");\n } catch {\n return null;\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return null;\n const path = localPath(url.pathname);\n return { path, href: `${path}${url.search}${url.hash}`, url: raw };\n}\n\n/** Where a click on the page goes: its path, or `null` — show it as text — when that path\n * is one-shot. */\nexport function feedbackPageLinkTarget(page: FeedbackPage, exclude: readonly PathPattern[] = []): string | null {\n return isOneShotPage(page.path, exclude) ? null : page.path;\n}\n"],"mappings":";AACA,SAAS,oBAAoB,sBAAsB;AA6B5C,MAAM,0BAAkD,OAAO,OAAO;AAAA,EAC3E;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAMM,SAAS,cAAc,MAAc,UAAkC,CAAC,GAAY;AACzF,QAAM,WAAW,eAAe,IAAI;AACpC,QAAM,QAAQ,CAAC,YAAyB,mBAAmB,UAAU,OAAO;AAC5E,SAAO,wBAAwB,KAAK,KAAK,KAAK,QAAQ,KAAK,KAAK;AAClE;AAmBA,MAAM,YAAY,CAAC,aAAqB,SAAS,QAAQ,WAAW,GAAG;AAIhE,SAAS,aAAa,SAAkE;AAC7F,QAAM,OAAO,SAAS,OAAO,SAAS,SAAS,IAAI,KAAK;AACxD,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACJ,MAAI;AAIF,UAAM,IAAI,IAAI,KAAK,kBAAkB;AAAA,EACvC,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,IAAI,aAAa,WAAW,IAAI,aAAa,SAAU,QAAO;AAClE,QAAM,OAAO,UAAU,IAAI,QAAQ;AACnC,SAAO,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,IAAI,MAAM,GAAG,IAAI,IAAI,IAAI,KAAK,IAAI;AACnE;AAIO,SAAS,uBAAuB,MAAoB,UAAkC,CAAC,GAAkB;AAC9G,SAAO,cAAc,KAAK,MAAM,OAAO,IAAI,OAAO,KAAK;AACzD;","names":[]}
|
|
@@ -12,8 +12,9 @@ export type { FeedbackPublicLabels };
|
|
|
12
12
|
/** `feedbackPublic` resolved: English, then `<UiKitProvider labels>`, then `labels`. */
|
|
13
13
|
export declare function useFeedbackPublicLabels(labels?: LabelOverride<FeedbackPublicLabels>): FeedbackPublicLabels;
|
|
14
14
|
export interface FeedbackPublicListProps<T extends FeedbackPublicListItem = FeedbackPublicListItem> {
|
|
15
|
-
/** `GET /feedback/public?sort=&q=` — the server's page, in the server's order.
|
|
16
|
-
|
|
15
|
+
/** `GET /feedback/public?sort=&q=` — the server's page, in the server's order.
|
|
16
|
+
* `readonly` since 0.52 (refactor audit 2026-10-30, API-7): the list never changes it. */
|
|
17
|
+
rows: readonly T[];
|
|
17
18
|
/** How many the query has in all — the line beside the sort. Left out, no line. */
|
|
18
19
|
total?: number;
|
|
19
20
|
/** The server sorts (§9.6): the toggle only says which way. */
|
|
@@ -38,7 +39,10 @@ export interface FeedbackPublicListProps<T extends FeedbackPublicListItem = Feed
|
|
|
38
39
|
/** The opened row's id, when the app keeps it (a `?row=` param). Left out, the list
|
|
39
40
|
* keeps it itself. */
|
|
40
41
|
openId?: number | null;
|
|
41
|
-
|
|
42
|
+
/** 0.52: a row was opened (its id) or closed (`null`) — `openId`'s pair. Named for
|
|
43
|
+
* what it carries: every other `onOpenChange` in the kit is a boolean (refactor audit
|
|
44
|
+
* 2026-10-30, API-5). */
|
|
45
|
+
onOpenIdChange?: (id: number | null) => void;
|
|
42
46
|
/**
|
|
43
47
|
* 0.38.1: the opened report when it is NOT on this page — a `?row=` link to an older
|
|
44
48
|
* one (Kurvenschmiede). The app fetches it (`GET /feedback/public/{id}`, a list item's
|
|
@@ -96,7 +100,7 @@ export interface FeedbackPublicListProps<T extends FeedbackPublicListItem = Feed
|
|
|
96
100
|
* next strip that needs no total (0.38.1); `openRow` shows a `?row=` report from
|
|
97
101
|
* another page. Needs a router, as every `DataTable` does.
|
|
98
102
|
*/
|
|
99
|
-
export declare function FeedbackPublicList<T extends FeedbackPublicListItem = FeedbackPublicListItem>({ rows, total, sort, onSortChange, search, onSearchChange, renderDetail, onVote, loading, openId: openIdProp,
|
|
103
|
+
export declare function FeedbackPublicList<T extends FeedbackPublicListItem = FeedbackPublicListItem>({ rows, total, sort, onSortChange, search, onSearchChange, renderDetail, onVote, loading, openId: openIdProp, onOpenIdChange, openRow, page: pageIndex, hasMore, onPageChange, commit, renderDate: renderDateProp, labels: labelsProp, pageLabels: pageLabelsProp, fillHeight, className, }: FeedbackPublicListProps<T>): import("react").JSX.Element;
|
|
100
104
|
export interface FeedbackPublicDetailProps {
|
|
101
105
|
/** `GET /feedback/public/{id}`: the public body (attachment URLs already stripped by
|
|
102
106
|
* the server, §9.4) and the replies. */
|
|
@@ -110,7 +110,7 @@ function FeedbackPublicList({
|
|
|
110
110
|
onVote,
|
|
111
111
|
loading = false,
|
|
112
112
|
openId: openIdProp,
|
|
113
|
-
|
|
113
|
+
onOpenIdChange,
|
|
114
114
|
openRow,
|
|
115
115
|
page: pageIndex = 0,
|
|
116
116
|
hasMore = false,
|
|
@@ -130,7 +130,7 @@ function FeedbackPublicList({
|
|
|
130
130
|
const openId = openIdProp !== void 0 ? openIdProp : ownOpenId;
|
|
131
131
|
const setOpen = (id) => {
|
|
132
132
|
if (openIdProp === void 0) setOwnOpenId(id);
|
|
133
|
-
|
|
133
|
+
onOpenIdChange?.(id);
|
|
134
134
|
};
|
|
135
135
|
const shownRows = openRow && openId === openRow.id && !rows.some((row) => row.id === openRow.id) ? [openRow, ...rows] : rows;
|
|
136
136
|
const columns = useMemo(
|
|
@@ -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 type { AuthedFetcher } from \"../hooks/use-authed-src\";\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 {\n FeedbackCategoryBadge,\n FeedbackDetail,\n FeedbackDetailSection,\n FeedbackProse,\n FeedbackStatusBadge,\n} from \"./feedback-inbox\";\nimport { useFeedbackCategoryLabels } from \"./feedback-labels\";\nimport { FeedbackCommentThread, type FeedbackCommentsLabels } from \"./feedback-comment-thread\";\nimport type { FeedbackEntryUploadOptions } from \"./feedback-entry-file\";\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. 0.47 (§12): `attachmentUrl` is the one file uploaded with it (with\n * `onUpload`). */\n onComment?: (body: string, attachmentUrl?: string) => void | Promise<unknown>;\n /** 0.47 (§12): `POST /feedback/attachments` — the comment box then takes one file,\n * pasted, captured or picked, as the rework note does. Left out, text only. */\n onUpload?: FeedbackEntryUploadOptions[\"onUpload\"];\n /** 0.47: the comment box's \"Capture screenshot\". */\n onCaptureScreenshot?: FeedbackEntryUploadOptions[\"onCaptureScreenshot\"];\n /** 0.47: the comment box's file field, over the contract's defaults. */\n attachment?: FeedbackEntryUploadOptions[\"attachment\"];\n /** 0.47: the app's authenticated GET for the comments' pictures, drawn inline. */\n fetcher?: AuthedFetcher | null;\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, the report's screenshot or files, the page the report was filed on. The\n * body is the server's `public_body` — the client strips nothing and adds nothing. The\n * one file it shows (0.47, §12) is a comment's own, posted with it to be seen.\n */\nexport function FeedbackPublicDetail({\n row,\n comments,\n onVote,\n isAdmin = false,\n onComment,\n onHide,\n onDelete,\n onUpload,\n onCaptureScreenshot,\n attachment,\n fetcher,\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 onUpload={onUpload}\n onCaptureScreenshot={onCaptureScreenshot}\n attachment={attachment}\n fetcher={fetcher}\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 type { AuthedFetcher } from \"../hooks/use-authed-src\";\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 {\n FeedbackCategoryBadge,\n FeedbackDetail,\n FeedbackDetailSection,\n FeedbackProse,\n FeedbackStatusBadge,\n} from \"./feedback-inbox\";\nimport { useFeedbackCategoryLabels } from \"./feedback-labels\";\nimport { FeedbackCommentThread, type FeedbackCommentsLabels } from \"./feedback-comment-thread\";\nimport type { FeedbackEntryUploadOptions } from \"./feedback-entry-file\";\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 * `readonly` since 0.52 (refactor audit 2026-10-30, API-7): the list never changes it. */\n rows: readonly 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 /** 0.52: a row was opened (its id) or closed (`null`) — `openId`'s pair. Named for\n * what it carries: every other `onOpenChange` in the kit is a boolean (refactor audit\n * 2026-10-30, API-5). */\n onOpenIdChange?: (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 onOpenIdChange,\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 onOpenIdChange?.(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 // `as T[]`: DataTable's `rows` is still mutable in its type (API-7), and never mutated.\n const shownRows = (\n openRow && openId === openRow.id && !rows.some((row) => row.id === openRow.id) ? [openRow, ...rows] : rows\n ) as T[];\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. 0.47 (§12): `attachmentUrl` is the one file uploaded with it (with\n * `onUpload`). */\n onComment?: (body: string, attachmentUrl?: string) => void | Promise<unknown>;\n /** 0.47 (§12): `POST /feedback/attachments` — the comment box then takes one file,\n * pasted, captured or picked, as the rework note does. Left out, text only. */\n onUpload?: FeedbackEntryUploadOptions[\"onUpload\"];\n /** 0.47: the comment box's \"Capture screenshot\". */\n onCaptureScreenshot?: FeedbackEntryUploadOptions[\"onCaptureScreenshot\"];\n /** 0.47: the comment box's file field, over the contract's defaults. */\n attachment?: FeedbackEntryUploadOptions[\"attachment\"];\n /** 0.47: the app's authenticated GET for the comments' pictures, drawn inline. */\n fetcher?: AuthedFetcher | null;\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, the report's screenshot or files, the page the report was filed on. The\n * body is the server's `public_body` — the client strips nothing and adds nothing. The\n * one file it shows (0.47, §12) is a comment's own, posted with it to be seen.\n */\nexport function FeedbackPublicDetail({\n row,\n comments,\n onVote,\n isAdmin = false,\n onComment,\n onHide,\n onDelete,\n onUpload,\n onCaptureScreenshot,\n attachment,\n fetcher,\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 onUpload={onUpload}\n onCaptureScreenshot={onCaptureScreenshot}\n attachment={attachment}\n fetcher={fetcher}\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":[]}
|
|
@@ -55,7 +55,9 @@ export declare function feedbackReplyNudge(row: {
|
|
|
55
55
|
export interface FeedbackReplyTimelineProps {
|
|
56
56
|
/** Oldest first, as the server sends them. */
|
|
57
57
|
replies: readonly FeedbackReply[];
|
|
58
|
-
/** How a reply's time reads. Default: the
|
|
58
|
+
/** How a reply's time reads. Default (0.52): the `<UiKitProvider formatDate>`'s day
|
|
59
|
+
* with the language's clock when the app set one, else the date and time in the
|
|
60
|
+
* provider's locale and time zone. */
|
|
59
61
|
renderDate?: (iso: string) => string;
|
|
60
62
|
/** Tag bodies and names `data-private` (a host's screen-share blur). Default true. */
|
|
61
63
|
redact?: boolean;
|
|
@@ -3,9 +3,9 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { BellOff } from "lucide-react";
|
|
5
5
|
import { Select } from "../components/select.js";
|
|
6
|
-
import { useKitLabels
|
|
6
|
+
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
7
7
|
import { cn } from "../lib/cn.js";
|
|
8
|
-
import {
|
|
8
|
+
import { useDayFormatter } from "../lib/day-format.js";
|
|
9
9
|
import { FeedbackStatusBadge, selectableFeedbackStatuses } from "./feedback-inbox.js";
|
|
10
10
|
import { useFeedbackStatusLabels } from "./feedback-labels.js";
|
|
11
11
|
import { FEEDBACK_DECIDED_STATUSES, FEEDBACK_REPLY_MAX } from "./feedback-record.js";
|
|
@@ -21,6 +21,7 @@ function feedbackReplyNudge(row) {
|
|
|
21
21
|
if (!row.replies || !FEEDBACK_DECIDED_STATUSES.includes(row.status)) return void 0;
|
|
22
22
|
return row.replies.some((reply) => reply.status_to === row.status) ? void 0 : row.status;
|
|
23
23
|
}
|
|
24
|
+
const REPLY_TIME_STYLE = { dateStyle: "medium", timeStyle: "short" };
|
|
24
25
|
function EntryStatus({ reply, statusChanged }) {
|
|
25
26
|
if (!reply.status_to) return null;
|
|
26
27
|
return /* @__PURE__ */ jsxs(
|
|
@@ -51,12 +52,12 @@ function FeedbackReplyTimeline({
|
|
|
51
52
|
}) {
|
|
52
53
|
const labels = useFeedbackRepliesLabels(labelsProp);
|
|
53
54
|
const anonymous = useKitLabels("feedbackPublic", DEFAULT_FEEDBACK_PUBLIC_LABELS).anonymous;
|
|
54
|
-
const
|
|
55
|
+
const day = useDayFormatter(renderDate);
|
|
55
56
|
const priv = redact ? "" : void 0;
|
|
56
57
|
if (replies.length === 0) {
|
|
57
58
|
return /* @__PURE__ */ jsx("p", { className: cn("text-sm text-placeholder", className), children: labels.empty });
|
|
58
59
|
}
|
|
59
|
-
const dateText = (iso) =>
|
|
60
|
+
const dateText = (iso) => day(iso, { time: true, style: REPLY_TIME_STYLE });
|
|
60
61
|
return /* @__PURE__ */ jsx("ol", { "aria-label": labels.replies, className: cn("space-y-3 border-s-2 border-subtle ps-3", className), children: replies.map((reply) => {
|
|
61
62
|
const rework = reply.kind === "rework";
|
|
62
63
|
return /* @__PURE__ */ jsxs(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/feedback/feedback-replies.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { BellOff } from \"lucide-react\";\nimport { Select } from \"../components/select\";\nimport type { CommitScope } from \"../components/write-lock\";\nimport type { AuthedFetcher } from \"../hooks/use-authed-src\";\nimport { useKitLabels, useKitLocale, type LabelOverride } from \"../i18n/kit-labels\";\nimport { cn } from \"../lib/cn\";\nimport { formatDate } from \"../lib/format\";\nimport { FeedbackStatusBadge, selectableFeedbackStatuses, type FeedbackStatus } from \"./feedback-inbox\";\nimport { useFeedbackStatusLabels } from \"./feedback-labels\";\nimport type { FeedbackReply } from \"./feedback-public-types\";\nimport { FEEDBACK_DECIDED_STATUSES, FEEDBACK_REPLY_MAX } from \"./feedback-record\";\nimport { FeedbackEntryFile, useFeedbackEntryUpload, type FeedbackEntryUploadOptions } from \"./feedback-entry-file\";\nimport { ChatComposer } from \"./feedback-thread\";\nimport { DEFAULT_FEEDBACK_REPLIES_LABELS } from \"./feedback-thread-parts-labels\";\nimport { FeedbackReworkEntryChip } from \"./feedback-rework-entry-chip\";\nimport { DEFAULT_FEEDBACK_PUBLIC_LABELS } from \"./feedback-public-labels\";\n\n/**\n * The admin's answers as a list (docs/feedback-harmonization.md §9.7, decision 5): each\n * reply its own row — who, when, what, and the status it moved the report to — where\n * 0.37 had one `outcome` field that every answer overwrote. {@link FeedbackReplyTimeline}\n * draws them; {@link FeedbackReplyComposer} writes the next one, with the status picker\n * beside it (decision 13: a reply notifies the author, a status alone does not, so the\n * status belongs with the words).\n */\n\n/* ── Labels ───────────────────────────────────────────────────────────────── */\n\n/** The `feedbackReplies` namespace of `<UiKitProvider labels>`. */\nexport interface FeedbackRepliesLabels {\n /** The timeline's accessible name. de-CH: \"Antworten\" */\n replies: string;\n /** No reply yet. de-CH: \"Noch keine Antwort\" */\n empty: string;\n /** A reply without an author — a loop's, a migrated outcome, an erased admin (§9.7).\n * The app's word for its admin; never \"the team\". de-CH: \"Admin\" */\n admin: string;\n /** Read before a reply's status chip (\"→ Done\"). de-CH: \"Status geändert auf\" */\n statusChanged: string;\n /** The reply box's accessible name. de-CH: \"Antwort schreiben\" */\n field: string;\n /** de-CH: \"Antwort schreiben…\" */\n placeholder: string;\n /** The send button. de-CH: \"Antworten\" */\n reply: string;\n /** The status picker's accessible name. de-CH: \"Status mit der Antwort\" */\n status: string;\n /** The picker's first option: the reply changes no status. de-CH: \"Keine Änderung\" */\n noChange: string;\n /** Above the composer when a decided status was set without a reply (§9.7, B2).\n * de-CH: \"Die meldende Person wurde nicht benachrichtigt. Schreiben Sie eine Antwort.\" */\n nudge: string;\n}\n\nexport { DEFAULT_FEEDBACK_REPLIES_LABELS };\n\n/** `feedbackReplies` resolved: English, then `<UiKitProvider labels>`, then `labels`. */\nexport function useFeedbackRepliesLabels(labels?: LabelOverride<FeedbackRepliesLabels>): FeedbackRepliesLabels {\n return useKitLabels(\"feedbackReplies\", DEFAULT_FEEDBACK_REPLIES_LABELS, labels);\n}\n\n/**\n * The status a row detail should nudge toward (§9.7, B2), or `undefined`: the row's\n * status when it is decided ({@link FEEDBACK_DECIDED_STATUSES}) and no reply carries it\n * as its `status_to` — a status-only move from the table, a swipe or the pills told the\n * author nothing. `undefined` too without `replies` (a server before 0.38).\n */\nexport function feedbackReplyNudge(row: {\n status: FeedbackStatus;\n replies?: readonly Pick<FeedbackReply, \"status_to\">[] | null;\n}): FeedbackStatus | undefined {\n if (!row.replies || !FEEDBACK_DECIDED_STATUSES.includes(row.status)) return undefined;\n return row.replies.some((reply) => reply.status_to === row.status) ? undefined : row.status;\n}\n\n/* ── The timeline ─────────────────────────────────────────────────────────── */\n\nexport interface FeedbackReplyTimelineProps {\n /** Oldest first, as the server sends them. */\n replies: readonly FeedbackReply[];\n /** How a reply's time reads. Default: the date and time in the provider's locale. */\n renderDate?: (iso: string) => string;\n /** Tag bodies and names `data-private` (a host's screen-share blur). Default true. */\n redact?: boolean;\n /** How an entry's file (`attachment_url` — a rework's since 0.40, an admin reply's\n * since 0.47) is drawn under its text. Default (0.47, §12): {@link FeedbackEntryFile} —\n * a picture inline as a thumbnail that opens full size, any other file a download —\n * through `fetcher`. */\n renderAttachment?: (url: string) => ReactNode;\n /** 0.47: the app's authenticated GET (`AuthedImage`'s) for the entries' files. Left\n * out, the browser's own fetch with same-origin cookies. */\n fetcher?: AuthedFetcher | null;\n labels?: LabelOverride<FeedbackRepliesLabels>;\n className?: string;\n}\n\n/** The status an entry moved the report to — \"→ Done\", with where from in the admin view.\n * Nothing on an entry with no `status_to`: a status-less reply, or a rework migrated out\n * of the body, whose move was never recorded (0.40, §10.3). */\nfunction EntryStatus({ reply, statusChanged }: { reply: FeedbackReply; statusChanged: string }) {\n if (!reply.status_to) return null;\n return (\n <span\n className=\"inline-flex min-w-0 max-w-full flex-wrap items-center gap-1\"\n data-feedback-reply-status={reply.status_to}\n >\n <span className=\"sr-only\">{statusChanged}</span>\n {reply.status_from && reply.status_from !== reply.status_to && (\n <>\n <FeedbackStatusBadge status={reply.status_from} />\n <span aria-hidden>→</span>\n </>\n )}\n {!reply.status_from && <span aria-hidden>→</span>}\n <FeedbackStatusBadge status={reply.status_to} />\n </span>\n );\n}\n\n/**\n * The replies, oldest first, down a rule: each with its author (`null` → \"Admin\"), its\n * time, the status it set (\"→ Done\"; the admin view adds where from) and its text. No\n * edit and no delete (§9.7): a correction is a new reply.\n *\n * 0.40 (§10.3): an author's **rework** entry (`kind: \"rework\"`) stands among them as the\n * author's — their name (`null` → \"A user\", never \"Admin\"), the **Rework** chip, the\n * status it moved the report to (none on a migrated one) and its file.\n *\n * 0.47 (§12): any entry's file — a rework's, an admin reply's — is drawn under its text,\n * a picture INLINE ({@link FeedbackEntryFile}).\n */\nexport function FeedbackReplyTimeline({\n replies,\n renderDate,\n redact = true,\n renderAttachment,\n fetcher,\n labels: labelsProp,\n className,\n}: FeedbackReplyTimelineProps) {\n const labels = useFeedbackRepliesLabels(labelsProp);\n const anonymous = useKitLabels(\"feedbackPublic\", DEFAULT_FEEDBACK_PUBLIC_LABELS).anonymous;\n const locale = useKitLocale();\n const priv = redact ? \"\" : undefined;\n if (replies.length === 0) {\n return <p className={cn(\"text-sm text-placeholder\", className)}>{labels.empty}</p>;\n }\n const dateText = (iso: string) =>\n renderDate ? renderDate(iso) : formatDate(iso, { dateStyle: \"medium\", timeStyle: \"short\" }, { locale });\n return (\n <ol aria-label={labels.replies} className={cn(\"space-y-3 border-s-2 border-subtle ps-3\", className)}>\n {replies.map((reply) => {\n const rework = reply.kind === \"rework\";\n return (\n <li\n key={reply.id}\n data-feedback-reply={reply.id}\n data-feedback-entry-kind={rework ? \"rework\" : undefined}\n className=\"min-w-0 text-sm\"\n >\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1 text-caption text-muted\">\n <span data-private={priv} className=\"font-medium text-secondary\">\n {reply.author_name ?? (rework ? anonymous : labels.admin)}\n </span>\n {rework && <FeedbackReworkEntryChip />}\n <time dateTime={reply.created_at} className=\"tabular-nums\">\n {dateText(reply.created_at)}\n </time>\n <EntryStatus reply={reply} statusChanged={labels.statusChanged} />\n </div>\n <p\n data-private={priv}\n dir=\"auto\"\n className=\"mt-0.5 whitespace-pre-wrap break-words text-start text-primary\"\n >\n {reply.body}\n </p>\n {reply.attachment_url && (\n <div className=\"mt-1 min-w-0\" data-private={priv} data-feedback-entry-attachment=\"\">\n {renderAttachment ? (\n renderAttachment(reply.attachment_url)\n ) : (\n <FeedbackEntryFile url={reply.attachment_url} fetcher={fetcher} />\n )}\n </div>\n )}\n </li>\n );\n })}\n </ol>\n );\n}\n\n/* ── The composer ─────────────────────────────────────────────────────────── */\n\n/**\n * The status Select and Reply on one line, one height (keksdose live #417, Android, dark).\n *\n * A field and a button of the same padding are not the same height everywhere: on a touch\n * screen the field's type grows to 16 px (FIELD_TOUCH_TEXT, so iOS does not zoom) and its\n * box with it, while the button keeps its 14 px line — and at Large the button takes its\n * 48 px touch floor, which the field has not. So the composer's action row stretches its\n * two halves to one height and Reply fills it (Button's `stretch`, which is the kit's\n * answer for a button beside a taller field). Scoped to this composer through the marks\n * ChatComposer puts on its row: a host slot with an attachment preview must not drag the\n * send button to the preview's 80 px.\n *\n * On a touch screen the \"Ctrl+Enter to send\" hint has no keyboard to speak of, and beside\n * a field-height select it pushed Reply onto a line of its own on a phone — it is\n * visually hidden there (still read in order). At Large the button's 48 px floor is\n * taller than any field, here as in every form row of the kit.\n */\nconst REPLY_ROW_ALIGN = cn(\n \"[&_[data-chat-composer-actions]]:items-stretch\",\n \"[&_[data-chat-composer-send]]:self-stretch\",\n \"pointer-coarse:[&_[data-chat-composer-hint]]:sr-only\",\n);\n\nexport interface FeedbackReplyComposerProps extends FeedbackEntryUploadOptions {\n /** The report's status now — what \"No change\" keeps. */\n status: FeedbackStatus;\n /**\n * `POST /feedback/{id}/replies` — the body (1–{@link FEEDBACK_REPLY_MAX} after\n * trimming), the status picked, `undefined` for \"No change\", and (0.47, §12) the URL\n * of the one file uploaded with it, `undefined` for none. Return the promise: the box\n * clears when it resolves, and keeps everything on a rejection.\n */\n onReply: (body: string, status?: FeedbackStatus, attachmentUrl?: string) => void | Promise<unknown>;\n /** The reply is in flight. */\n pending?: boolean;\n /** Preselect this status and say the author has not been told (§9.7, the nudge) —\n * {@link feedbackReplyNudge}. */\n suggestedStatus?: FeedbackStatus;\n /** Reply COMMITS — see ChatComposer's `commit`. */\n commit?: CommitScope;\n labels?: LabelOverride<FeedbackRepliesLabels>;\n className?: string;\n}\n\n/**\n * The admin's reply box (§9.7): the text, the **status** to set with it (\"No change\"\n * first, then every status — {@link selectableFeedbackStatuses}, `REWORK` among them\n * since 0.40: how an admin sends any report back, their own included, §10.2 decision 6),\n * and **Reply**. A\n * {@link ChatComposer} underneath: Ctrl/⌘+Enter sends, the count runs to 8 000, a draft\n * typed while a reply is in flight stays.\n *\n * With `suggestedStatus` the line \"The author has not been told. Add a reply.\" stands\n * above it and that status is picked, so the reply that answers it records the status\n * and the nudge goes away.\n *\n * 0.47 (§12, keksdose live #447): with `onUpload`, one file as the rework note takes it —\n * a screenshot pasted into the box, a capture (`onCaptureScreenshot`), or a pick; the\n * contract's types and 10 MB. It is uploaded FIRST; a failed upload toasts and sends\n * nothing, and the draft and the file stay. `onUpload` here is the composer's own opt-in\n * (new in 0.47, so no page passed it before); `FeedbackRowDetail` hands it on only with\n * `replyAttachments`, since its `onUpload` is the rework note's too.\n */\nexport function FeedbackReplyComposer({\n status,\n onReply,\n pending = false,\n suggestedStatus,\n commit,\n onUpload,\n onCaptureScreenshot,\n attachment,\n labels: labelsProp,\n className,\n}: FeedbackReplyComposerProps) {\n const labels = useFeedbackRepliesLabels(labelsProp);\n const { composerAttachment, upload, uploading } = useFeedbackEntryUpload({\n onUpload,\n onCaptureScreenshot,\n attachment,\n });\n const statusNames = useFeedbackStatusLabels();\n // The pick, valid for the suggestion it was made under: a new suggestion (another\n // status set from the table) picks itself again.\n const [pick, setPick] = useState<{ under: FeedbackStatus | undefined; value: FeedbackStatus | \"\" }>({\n under: suggestedStatus,\n value: suggestedStatus ?? \"\",\n });\n const picked = pick.under === suggestedStatus ? pick.value : (suggestedStatus ?? \"\");\n\n const send = async (body: string, file: File | null) => {\n const sent = picked;\n const url = await upload(file);\n // The third argument only with a file, so a text reply calls as before 0.47.\n const replied = url === undefined ? onReply(body, sent || undefined) : onReply(body, sent || undefined, url);\n return Promise.resolve(replied).then(() => {\n setPick((current) => (current.value === sent ? { under: suggestedStatus, value: \"\" } : current));\n });\n };\n\n return (\n <div className={cn(\"space-y-2\", REPLY_ROW_ALIGN, className)} data-feedback-reply-composer=\"\">\n {suggestedStatus && (\n <p className=\"flex items-start gap-1.5 text-xs text-secondary\" data-feedback-nudge={suggestedStatus}>\n <BellOff className=\"mt-0.5 size-3.5 shrink-0 text-warning\" aria-hidden />\n <span>{labels.nudge}</span>\n </p>\n )}\n <ChatComposer\n onSend={send}\n pending={pending || uploading}\n attachment={composerAttachment}\n commit={commit}\n maxLength={FEEDBACK_REPLY_MAX}\n showCount\n labels={{ field: labels.field, placeholder: labels.placeholder, send: labels.reply }}\n attachmentSlot={\n // The kit's standard Select at field height (#417: \"not invent new things where\n // there are usable ones already\") — `size=\"sm\"` was a 28 px box beside the\n // 38 px Reply. See REPLY_ROW_ALIGN for how the two keep one height.\n <Select\n aria-label={labels.status}\n value={picked}\n disabled={pending || uploading}\n onChange={(event) => setPick({ under: suggestedStatus, value: event.target.value as FeedbackStatus | \"\" })}\n data-feedback-reply-status-pick=\"\"\n >\n <option value=\"\">{labels.noChange}</option>\n {selectableFeedbackStatuses(status).map((value) => (\n <option key={value} value={value}>\n {statusNames[value]}\n </option>\n ))}\n </Select>\n }\n />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/feedback/feedback-replies.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { BellOff } from \"lucide-react\";\nimport { Select } from \"../components/select\";\nimport type { CommitScope } from \"../components/write-lock\";\nimport type { AuthedFetcher } from \"../hooks/use-authed-src\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { cn } from \"../lib/cn\";\nimport { useDayFormatter } from \"../lib/day-format\";\nimport { FeedbackStatusBadge, selectableFeedbackStatuses, type FeedbackStatus } from \"./feedback-inbox\";\nimport { useFeedbackStatusLabels } from \"./feedback-labels\";\nimport type { FeedbackReply } from \"./feedback-public-types\";\nimport { FEEDBACK_DECIDED_STATUSES, FEEDBACK_REPLY_MAX } from \"./feedback-record\";\nimport { FeedbackEntryFile, useFeedbackEntryUpload, type FeedbackEntryUploadOptions } from \"./feedback-entry-file\";\nimport { ChatComposer } from \"./feedback-thread\";\nimport { DEFAULT_FEEDBACK_REPLIES_LABELS } from \"./feedback-thread-parts-labels\";\nimport { FeedbackReworkEntryChip } from \"./feedback-rework-entry-chip\";\nimport { DEFAULT_FEEDBACK_PUBLIC_LABELS } from \"./feedback-public-labels\";\n\n/**\n * The admin's answers as a list (docs/feedback-harmonization.md §9.7, decision 5): each\n * reply its own row — who, when, what, and the status it moved the report to — where\n * 0.37 had one `outcome` field that every answer overwrote. {@link FeedbackReplyTimeline}\n * draws them; {@link FeedbackReplyComposer} writes the next one, with the status picker\n * beside it (decision 13: a reply notifies the author, a status alone does not, so the\n * status belongs with the words).\n */\n\n/* ── Labels ───────────────────────────────────────────────────────────────── */\n\n/** The `feedbackReplies` namespace of `<UiKitProvider labels>`. */\nexport interface FeedbackRepliesLabels {\n /** The timeline's accessible name. de-CH: \"Antworten\" */\n replies: string;\n /** No reply yet. de-CH: \"Noch keine Antwort\" */\n empty: string;\n /** A reply without an author — a loop's, a migrated outcome, an erased admin (§9.7).\n * The app's word for its admin; never \"the team\". de-CH: \"Admin\" */\n admin: string;\n /** Read before a reply's status chip (\"→ Done\"). de-CH: \"Status geändert auf\" */\n statusChanged: string;\n /** The reply box's accessible name. de-CH: \"Antwort schreiben\" */\n field: string;\n /** de-CH: \"Antwort schreiben…\" */\n placeholder: string;\n /** The send button. de-CH: \"Antworten\" */\n reply: string;\n /** The status picker's accessible name. de-CH: \"Status mit der Antwort\" */\n status: string;\n /** The picker's first option: the reply changes no status. de-CH: \"Keine Änderung\" */\n noChange: string;\n /** Above the composer when a decided status was set without a reply (§9.7, B2).\n * de-CH: \"Die meldende Person wurde nicht benachrichtigt. Schreiben Sie eine Antwort.\" */\n nudge: string;\n}\n\nexport { DEFAULT_FEEDBACK_REPLIES_LABELS };\n\n/** `feedbackReplies` resolved: English, then `<UiKitProvider labels>`, then `labels`. */\nexport function useFeedbackRepliesLabels(labels?: LabelOverride<FeedbackRepliesLabels>): FeedbackRepliesLabels {\n return useKitLabels(\"feedbackReplies\", DEFAULT_FEEDBACK_REPLIES_LABELS, labels);\n}\n\n/**\n * The status a row detail should nudge toward (§9.7, B2), or `undefined`: the row's\n * status when it is decided ({@link FEEDBACK_DECIDED_STATUSES}) and no reply carries it\n * as its `status_to` — a status-only move from the table, a swipe or the pills told the\n * author nothing. `undefined` too without `replies` (a server before 0.38).\n */\nexport function feedbackReplyNudge(row: {\n status: FeedbackStatus;\n replies?: readonly Pick<FeedbackReply, \"status_to\">[] | null;\n}): FeedbackStatus | undefined {\n if (!row.replies || !FEEDBACK_DECIDED_STATUSES.includes(row.status)) return undefined;\n return row.replies.some((reply) => reply.status_to === row.status) ? undefined : row.status;\n}\n\n/* ── The timeline ─────────────────────────────────────────────────────────── */\n\n/** A reply's time, the kit's own: the medium date and the short clock. */\nconst REPLY_TIME_STYLE: Intl.DateTimeFormatOptions = { dateStyle: \"medium\", timeStyle: \"short\" };\n\nexport interface FeedbackReplyTimelineProps {\n /** Oldest first, as the server sends them. */\n replies: readonly FeedbackReply[];\n /** How a reply's time reads. Default (0.52): the `<UiKitProvider formatDate>`'s day\n * with the language's clock when the app set one, else the date and time in the\n * provider's locale and time zone. */\n renderDate?: (iso: string) => string;\n /** Tag bodies and names `data-private` (a host's screen-share blur). Default true. */\n redact?: boolean;\n /** How an entry's file (`attachment_url` — a rework's since 0.40, an admin reply's\n * since 0.47) is drawn under its text. Default (0.47, §12): {@link FeedbackEntryFile} —\n * a picture inline as a thumbnail that opens full size, any other file a download —\n * through `fetcher`. */\n renderAttachment?: (url: string) => ReactNode;\n /** 0.47: the app's authenticated GET (`AuthedImage`'s) for the entries' files. Left\n * out, the browser's own fetch with same-origin cookies. */\n fetcher?: AuthedFetcher | null;\n labels?: LabelOverride<FeedbackRepliesLabels>;\n className?: string;\n}\n\n/** The status an entry moved the report to — \"→ Done\", with where from in the admin view.\n * Nothing on an entry with no `status_to`: a status-less reply, or a rework migrated out\n * of the body, whose move was never recorded (0.40, §10.3). */\nfunction EntryStatus({ reply, statusChanged }: { reply: FeedbackReply; statusChanged: string }) {\n if (!reply.status_to) return null;\n return (\n <span\n className=\"inline-flex min-w-0 max-w-full flex-wrap items-center gap-1\"\n data-feedback-reply-status={reply.status_to}\n >\n <span className=\"sr-only\">{statusChanged}</span>\n {reply.status_from && reply.status_from !== reply.status_to && (\n <>\n <FeedbackStatusBadge status={reply.status_from} />\n <span aria-hidden>→</span>\n </>\n )}\n {!reply.status_from && <span aria-hidden>→</span>}\n <FeedbackStatusBadge status={reply.status_to} />\n </span>\n );\n}\n\n/**\n * The replies, oldest first, down a rule: each with its author (`null` → \"Admin\"), its\n * time, the status it set (\"→ Done\"; the admin view adds where from) and its text. No\n * edit and no delete (§9.7): a correction is a new reply.\n *\n * 0.40 (§10.3): an author's **rework** entry (`kind: \"rework\"`) stands among them as the\n * author's — their name (`null` → \"A user\", never \"Admin\"), the **Rework** chip, the\n * status it moved the report to (none on a migrated one) and its file.\n *\n * 0.47 (§12): any entry's file — a rework's, an admin reply's — is drawn under its text,\n * a picture INLINE ({@link FeedbackEntryFile}).\n */\nexport function FeedbackReplyTimeline({\n replies,\n renderDate,\n redact = true,\n renderAttachment,\n fetcher,\n labels: labelsProp,\n className,\n}: FeedbackReplyTimelineProps) {\n const labels = useFeedbackRepliesLabels(labelsProp);\n const anonymous = useKitLabels(\"feedbackPublic\", DEFAULT_FEEDBACK_PUBLIC_LABELS).anonymous;\n // `renderDate` when it answers, else the provider's `formatDate` for the day with the\n // language's clock, else the kit's date and time in the provider's locale and time zone\n // (0.52, refactor audit 2026-10-30, DUP-2: the provider never reached this one).\n const day = useDayFormatter<string>(renderDate);\n const priv = redact ? \"\" : undefined;\n if (replies.length === 0) {\n return <p className={cn(\"text-sm text-placeholder\", className)}>{labels.empty}</p>;\n }\n const dateText = (iso: string) => day(iso, { time: true, style: REPLY_TIME_STYLE });\n return (\n <ol aria-label={labels.replies} className={cn(\"space-y-3 border-s-2 border-subtle ps-3\", className)}>\n {replies.map((reply) => {\n const rework = reply.kind === \"rework\";\n return (\n <li\n key={reply.id}\n data-feedback-reply={reply.id}\n data-feedback-entry-kind={rework ? \"rework\" : undefined}\n className=\"min-w-0 text-sm\"\n >\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1 text-caption text-muted\">\n <span data-private={priv} className=\"font-medium text-secondary\">\n {reply.author_name ?? (rework ? anonymous : labels.admin)}\n </span>\n {rework && <FeedbackReworkEntryChip />}\n <time dateTime={reply.created_at} className=\"tabular-nums\">\n {dateText(reply.created_at)}\n </time>\n <EntryStatus reply={reply} statusChanged={labels.statusChanged} />\n </div>\n <p\n data-private={priv}\n dir=\"auto\"\n className=\"mt-0.5 whitespace-pre-wrap break-words text-start text-primary\"\n >\n {reply.body}\n </p>\n {reply.attachment_url && (\n <div className=\"mt-1 min-w-0\" data-private={priv} data-feedback-entry-attachment=\"\">\n {renderAttachment ? (\n renderAttachment(reply.attachment_url)\n ) : (\n <FeedbackEntryFile url={reply.attachment_url} fetcher={fetcher} />\n )}\n </div>\n )}\n </li>\n );\n })}\n </ol>\n );\n}\n\n/* ── The composer ─────────────────────────────────────────────────────────── */\n\n/**\n * The status Select and Reply on one line, one height (keksdose live #417, Android, dark).\n *\n * A field and a button of the same padding are not the same height everywhere: on a touch\n * screen the field's type grows to 16 px (FIELD_TOUCH_TEXT, so iOS does not zoom) and its\n * box with it, while the button keeps its 14 px line — and at Large the button takes its\n * 48 px touch floor, which the field has not. So the composer's action row stretches its\n * two halves to one height and Reply fills it (Button's `stretch`, which is the kit's\n * answer for a button beside a taller field). Scoped to this composer through the marks\n * ChatComposer puts on its row: a host slot with an attachment preview must not drag the\n * send button to the preview's 80 px.\n *\n * On a touch screen the \"Ctrl+Enter to send\" hint has no keyboard to speak of, and beside\n * a field-height select it pushed Reply onto a line of its own on a phone — it is\n * visually hidden there (still read in order). At Large the button's 48 px floor is\n * taller than any field, here as in every form row of the kit.\n */\nconst REPLY_ROW_ALIGN = cn(\n \"[&_[data-chat-composer-actions]]:items-stretch\",\n \"[&_[data-chat-composer-send]]:self-stretch\",\n \"pointer-coarse:[&_[data-chat-composer-hint]]:sr-only\",\n);\n\nexport interface FeedbackReplyComposerProps extends FeedbackEntryUploadOptions {\n /** The report's status now — what \"No change\" keeps. */\n status: FeedbackStatus;\n /**\n * `POST /feedback/{id}/replies` — the body (1–{@link FEEDBACK_REPLY_MAX} after\n * trimming), the status picked, `undefined` for \"No change\", and (0.47, §12) the URL\n * of the one file uploaded with it, `undefined` for none. Return the promise: the box\n * clears when it resolves, and keeps everything on a rejection.\n */\n onReply: (body: string, status?: FeedbackStatus, attachmentUrl?: string) => void | Promise<unknown>;\n /** The reply is in flight. */\n pending?: boolean;\n /** Preselect this status and say the author has not been told (§9.7, the nudge) —\n * {@link feedbackReplyNudge}. */\n suggestedStatus?: FeedbackStatus;\n /** Reply COMMITS — see ChatComposer's `commit`. */\n commit?: CommitScope;\n labels?: LabelOverride<FeedbackRepliesLabels>;\n className?: string;\n}\n\n/**\n * The admin's reply box (§9.7): the text, the **status** to set with it (\"No change\"\n * first, then every status — {@link selectableFeedbackStatuses}, `REWORK` among them\n * since 0.40: how an admin sends any report back, their own included, §10.2 decision 6),\n * and **Reply**. A\n * {@link ChatComposer} underneath: Ctrl/⌘+Enter sends, the count runs to 8 000, a draft\n * typed while a reply is in flight stays.\n *\n * With `suggestedStatus` the line \"The author has not been told. Add a reply.\" stands\n * above it and that status is picked, so the reply that answers it records the status\n * and the nudge goes away.\n *\n * 0.47 (§12, keksdose live #447): with `onUpload`, one file as the rework note takes it —\n * a screenshot pasted into the box, a capture (`onCaptureScreenshot`), or a pick; the\n * contract's types and 10 MB. It is uploaded FIRST; a failed upload toasts and sends\n * nothing, and the draft and the file stay. `onUpload` here is the composer's own opt-in\n * (new in 0.47, so no page passed it before); `FeedbackRowDetail` hands it on only with\n * `replyAttachments`, since its `onUpload` is the rework note's too.\n */\nexport function FeedbackReplyComposer({\n status,\n onReply,\n pending = false,\n suggestedStatus,\n commit,\n onUpload,\n onCaptureScreenshot,\n attachment,\n labels: labelsProp,\n className,\n}: FeedbackReplyComposerProps) {\n const labels = useFeedbackRepliesLabels(labelsProp);\n const { composerAttachment, upload, uploading } = useFeedbackEntryUpload({\n onUpload,\n onCaptureScreenshot,\n attachment,\n });\n const statusNames = useFeedbackStatusLabels();\n // The pick, valid for the suggestion it was made under: a new suggestion (another\n // status set from the table) picks itself again.\n const [pick, setPick] = useState<{ under: FeedbackStatus | undefined; value: FeedbackStatus | \"\" }>({\n under: suggestedStatus,\n value: suggestedStatus ?? \"\",\n });\n const picked = pick.under === suggestedStatus ? pick.value : (suggestedStatus ?? \"\");\n\n const send = async (body: string, file: File | null) => {\n const sent = picked;\n const url = await upload(file);\n // The third argument only with a file, so a text reply calls as before 0.47.\n const replied = url === undefined ? onReply(body, sent || undefined) : onReply(body, sent || undefined, url);\n return Promise.resolve(replied).then(() => {\n setPick((current) => (current.value === sent ? { under: suggestedStatus, value: \"\" } : current));\n });\n };\n\n return (\n <div className={cn(\"space-y-2\", REPLY_ROW_ALIGN, className)} data-feedback-reply-composer=\"\">\n {suggestedStatus && (\n <p className=\"flex items-start gap-1.5 text-xs text-secondary\" data-feedback-nudge={suggestedStatus}>\n <BellOff className=\"mt-0.5 size-3.5 shrink-0 text-warning\" aria-hidden />\n <span>{labels.nudge}</span>\n </p>\n )}\n <ChatComposer\n onSend={send}\n pending={pending || uploading}\n attachment={composerAttachment}\n commit={commit}\n maxLength={FEEDBACK_REPLY_MAX}\n showCount\n labels={{ field: labels.field, placeholder: labels.placeholder, send: labels.reply }}\n attachmentSlot={\n // The kit's standard Select at field height (#417: \"not invent new things where\n // there are usable ones already\") — `size=\"sm\"` was a 28 px box beside the\n // 38 px Reply. See REPLY_ROW_ALIGN for how the two keep one height.\n <Select\n aria-label={labels.status}\n value={picked}\n disabled={pending || uploading}\n onChange={(event) => setPick({ under: suggestedStatus, value: event.target.value as FeedbackStatus | \"\" })}\n data-feedback-reply-status-pick=\"\"\n >\n <option value=\"\">{labels.noChange}</option>\n {selectableFeedbackStatuses(status).map((value) => (\n <option key={value} value={value}>\n {statusNames[value]}\n </option>\n ))}\n </Select>\n }\n />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -14,9 +14,6 @@ const DEFAULT_FEEDBACK_DETAIL_LABELS = {
|
|
|
14
14
|
outcome: "Outcome",
|
|
15
15
|
replies: "Replies",
|
|
16
16
|
resolvedAt: (date) => `Resolved: ${date}`,
|
|
17
|
-
outcomeAdd: "Add outcome",
|
|
18
|
-
outcomeUpdate: "Update",
|
|
19
|
-
outcomePlaceholder: "What was done, decided, or why this won't be addressed.",
|
|
20
17
|
rework: "Rework",
|
|
21
18
|
reworkTitle: "Send for rework",
|
|
22
19
|
reworkSend: "Send rework",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/feedback/feedback-row-detail-labels.ts"],"sourcesContent":["// The English words of ./feedback-row-detail, in a module with no component code: the\n// English catalogue (src/i18n/defaults.ts) imports them without the component, so `en`\n// loads as one chunk like the other languages (0.34, KS U8). ./feedback-row-detail\n// re-exports them.\n\nimport type { FeedbackDetailLabels } from \"./feedback-row-detail\";\n\nexport const DEFAULT_FEEDBACK_DETAIL_LABELS: FeedbackDetailLabels = {\n subject: \"Subject\",\n body: \"Description\",\n edit: \"Edit\",\n editDescription: \"Edit description\",\n save: \"Save\",\n cancel: \"Cancel\",\n url: \"URL\",\n copyUrl: \"Copy URL\",\n attachment: \"Attachment\",\n download: (name) => `Download ${name}`,\n downloadFailed: \"The attachment could not be downloaded.\",\n outcome: \"Outcome\",\n replies: \"Replies\",\n resolvedAt: (date) => `Resolved: ${date}`,\n
|
|
1
|
+
{"version":3,"sources":["../../src/feedback/feedback-row-detail-labels.ts"],"sourcesContent":["// The English words of ./feedback-row-detail, in a module with no component code: the\n// English catalogue (src/i18n/defaults.ts) imports them without the component, so `en`\n// loads as one chunk like the other languages (0.34, KS U8). ./feedback-row-detail\n// re-exports them.\n\nimport type { FeedbackDetailLabels } from \"./feedback-row-detail\";\n\nexport const DEFAULT_FEEDBACK_DETAIL_LABELS: FeedbackDetailLabels = {\n subject: \"Subject\",\n body: \"Description\",\n edit: \"Edit\",\n editDescription: \"Edit description\",\n save: \"Save\",\n cancel: \"Cancel\",\n url: \"URL\",\n copyUrl: \"Copy URL\",\n attachment: \"Attachment\",\n download: (name) => `Download ${name}`,\n downloadFailed: \"The attachment could not be downloaded.\",\n outcome: \"Outcome\",\n replies: \"Replies\",\n resolvedAt: (date) => `Resolved: ${date}`,\n rework: \"Rework\",\n reworkTitle: \"Send for rework\",\n reworkSend: \"Send rework\",\n reworkPlaceholder: \"What still needs refinement? Any new constraints or change of direction.\",\n reworkUploadFailed: \"The file could not be uploaded. The rework was not sent.\",\n status: \"Status\",\n note: \"Note\",\n};\n"],"mappings":";AAOO,MAAM,iCAAuD;AAAA,EAClE,SAAS;AAAA,EACT,MAAM;AAAA,EACN,MAAM;AAAA,EACN,iBAAiB;AAAA,EACjB,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,UAAU,CAAC,SAAS,YAAY,IAAI;AAAA,EACpC,gBAAgB;AAAA,EAChB,SAAS;AAAA,EACT,SAAS;AAAA,EACT,YAAY,CAAC,SAAS,aAAa,IAAI;AAAA,EACvC,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB,QAAQ;AAAA,EACR,MAAM;AACR;","names":[]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type { AuthedFetcher } from "../hooks/use-authed-src.js";
|
|
3
|
+
import type { PathPattern } from "../landing/routing.js";
|
|
3
4
|
import { type LabelOverride } from "../i18n/kit-labels.js";
|
|
4
5
|
import type { FeedbackNoteAttachment, FeedbackStatus } from "./feedback-inbox.js";
|
|
5
6
|
import type { FeedbackContext, FeedbackRecord } from "./feedback-record.js";
|
|
@@ -89,16 +90,6 @@ export interface FeedbackDetailLabels {
|
|
|
89
90
|
/** On section 4's heading line once the row is settled, given the formatted
|
|
90
91
|
* `resolved_at` date. de-CH: "Erledigt am: {{date}}" */
|
|
91
92
|
resolvedAt: (date: string) => string;
|
|
92
|
-
/** The admin's button while there is no outcome. **Unused since 0.38** (the outcome is
|
|
93
|
-
* frozen, §9.7; a reply is written instead); kept for one release.
|
|
94
|
-
* de-CH: "Ergebnis hinzufügen" */
|
|
95
|
-
outcomeAdd: string;
|
|
96
|
-
/** The same button once there is one. Unused since 0.38, as `outcomeAdd`.
|
|
97
|
-
* de-CH: "Aktualisieren" */
|
|
98
|
-
outcomeUpdate: string;
|
|
99
|
-
/** The line above the outcome editor. Unused since 0.38, as `outcomeAdd`. de-CH: "Was wurde umgesetzt, entschieden oder
|
|
100
|
-
* warum nicht." */
|
|
101
|
-
outcomePlaceholder: string;
|
|
102
93
|
/** The button that opens section 5 on an answered row. de-CH: "Nacharbeit" */
|
|
103
94
|
rework: string;
|
|
104
95
|
/** Section 5's heading. de-CH: "Zur Nacharbeit senden" */
|
|
@@ -123,22 +114,9 @@ export interface FeedbackDetailLabels {
|
|
|
123
114
|
export { DEFAULT_FEEDBACK_DETAIL_LABELS };
|
|
124
115
|
/** `feedbackDetail` resolved: English, then `<UiKitProvider labels>`, then `labels`. */
|
|
125
116
|
export declare function useFeedbackDetailLabels(labels?: LabelOverride<FeedbackDetailLabels>): FeedbackDetailLabels;
|
|
126
|
-
/**
|
|
127
|
-
*
|
|
128
|
-
* `localHref`: the query and hash carry the state worth restoring, `?tab=import`, a
|
|
129
|
-
* drill-down). Read from `context.url`, else `context.route`; a path-only `url` works.
|
|
130
|
-
* Always a path on THIS app, never another origin's address, so it is safe as an
|
|
131
|
-
* `href` whatever the row says. `null` when there is nothing to link.
|
|
132
|
-
*/
|
|
133
|
-
export declare function feedbackPageHref(context: FeedbackContext | null | undefined): string | null;
|
|
134
|
-
/** The page's path alone — the compact text a URL column shows (§4.3). */
|
|
117
|
+
/** The page's path alone — the compact text a URL column shows (§4.3), and the only part
|
|
118
|
+
* of the page the kit links. */
|
|
135
119
|
export declare function feedbackPagePath(context: FeedbackContext | null | undefined): string | null;
|
|
136
|
-
/**
|
|
137
|
-
* What "Copy URL" copies: the FULL `context.url` as the reporter's browser had it —
|
|
138
|
-
* origin included, which is the one part that says which copy of the app it was
|
|
139
|
-
* (feedback #278) — else `context.route`. `null` when there is no page.
|
|
140
|
-
*/
|
|
141
|
-
export declare function feedbackPageUrl(context: FeedbackContext | null | undefined): string | null;
|
|
142
120
|
/**
|
|
143
121
|
* Every file a report carries, in the order the detail shows them (§4.4.3): the
|
|
144
122
|
* screenshot it was filed with, then its other files, then whatever the rework rounds
|
|
@@ -197,16 +175,15 @@ export interface FeedbackDetailPatch {
|
|
|
197
175
|
public?: boolean;
|
|
198
176
|
}
|
|
199
177
|
/**
|
|
200
|
-
* The page's update mutation. **TanStack Query's `
|
|
201
|
-
* `update.
|
|
202
|
-
* `useFeedbackStatusUndo` takes.
|
|
203
|
-
*
|
|
204
|
-
*
|
|
178
|
+
* The page's update mutation (0.52): **TanStack Query's `mutateAsync`** — pass
|
|
179
|
+
* `update.mutateAsync` (and `pending={update.isPending}`), the same function
|
|
180
|
+
* `useFeedbackStatusUndo` takes. An editor closes when the promise resolves, which it does
|
|
181
|
+
* for every call — also when a status pill was pressed while the save was in flight
|
|
182
|
+
* (refactor audit 2026-10-30, FLW-3). A hand-written one resolves once the PATCH has
|
|
183
|
+
* landed. A failure is the mutation's own to toast — the server's `detail`, else
|
|
205
184
|
* `feedbackToast.updateFailed` (§4.4) — and leaves the editor open with its draft.
|
|
206
185
|
*/
|
|
207
|
-
export type
|
|
208
|
-
onSuccess: () => void;
|
|
209
|
-
}) => void;
|
|
186
|
+
export type FeedbackDetailUpdateAsync = (patch: FeedbackDetailPatch) => Promise<unknown>;
|
|
210
187
|
/** `POST /feedback/attachments` (§3.3): one file up, its URL
|
|
211
188
|
* (`/api/v1/feedback/attachments/<key>`) back. Reject on failure. */
|
|
212
189
|
export type FeedbackUpload = (file: File) => Promise<string>;
|
|
@@ -221,10 +198,10 @@ export interface FeedbackReworkSectionProps {
|
|
|
221
198
|
/** 0.40: the rework as an entry of its own — see {@link FeedbackReworkSend}. With it,
|
|
222
199
|
* `onUpdate` is not called. */
|
|
223
200
|
onRework?: FeedbackReworkSend;
|
|
224
|
-
/** The PATCH — see {@link
|
|
225
|
-
* only without `onRework`: the legacy append (§3.4), which server-kit 0.13 still
|
|
201
|
+
/** The PATCH — see {@link FeedbackDetailUpdateAsync}. Called with `{ id, body }` only,
|
|
202
|
+
* and only without `onRework`: the legacy append (§3.4), which server-kit 0.13 still
|
|
226
203
|
* turns into an entry for one release. Optional since 0.40; pass one of the two. */
|
|
227
|
-
onUpdate?:
|
|
204
|
+
onUpdate?: FeedbackDetailUpdateAsync;
|
|
228
205
|
/** The PATCH is in flight (`update.isPending`). The upload adds its own. */
|
|
229
206
|
pending?: boolean;
|
|
230
207
|
/** The upload. Left out, the note goes without a file field. */
|
|
@@ -276,8 +253,9 @@ export interface FeedbackRowDetailProps {
|
|
|
276
253
|
canEdit: boolean;
|
|
277
254
|
/** The signed-in user's id — who the AUTHOR is: the description editor and rework. */
|
|
278
255
|
viewerId?: number | null;
|
|
279
|
-
/** The page's PATCH — `update.
|
|
280
|
-
|
|
256
|
+
/** The page's PATCH — `update.mutateAsync` (see {@link FeedbackDetailUpdateAsync}). The
|
|
257
|
+
* callback form, `update.mutate`, went in 0.53 (deprecated in 0.52). */
|
|
258
|
+
onUpdate: FeedbackDetailUpdateAsync;
|
|
281
259
|
/** `update.isPending`: the editors' save buttons wait for it. */
|
|
282
260
|
pending?: boolean;
|
|
283
261
|
/** `POST /feedback/attachments` for the rework's one file and, since 0.47 (§12) with
|
|
@@ -288,7 +266,7 @@ export interface FeedbackRowDetailProps {
|
|
|
288
266
|
* the browser's own fetch with same-origin cookies. */
|
|
289
267
|
fetcher?: AuthedFetcher | null;
|
|
290
268
|
/**
|
|
291
|
-
* The undoable status change — what `useFeedbackStatusUndo(update.
|
|
269
|
+
* The undoable status change — what `useFeedbackStatusUndo(update.mutateAsync)` returns,
|
|
292
270
|
* the SAME one the table's status cell and the swipes use (§4.5). Left out, the
|
|
293
271
|
* detail builds one from `onUpdate`, which is equivalent as long as `onUpdate` is the
|
|
294
272
|
* page's mutation (its observer outlives the row a status filter removes).
|
|
@@ -300,9 +278,20 @@ export interface FeedbackRowDetailProps {
|
|
|
300
278
|
/** The rework's and (0.47, with `replyAttachments`) the reply composer's file field,
|
|
301
279
|
* over the contract's defaults. */
|
|
302
280
|
reworkAttachment?: Partial<FeedbackNoteAttachment>;
|
|
303
|
-
/** How the `resolved_at` date reads — the app's date cell, as a string. Default
|
|
304
|
-
*
|
|
281
|
+
/** How the `resolved_at` date reads — the app's date cell, as a string. Default
|
|
282
|
+
* (0.52): the `<UiKitProvider formatDate>` when the app set one, else the kit's
|
|
283
|
+
* `formatDate` in the provider's locale and time zone, `medium` style. */
|
|
305
284
|
renderDate?: (iso: string) => string;
|
|
285
|
+
/**
|
|
286
|
+
* 0.52 (FLW-7): the app's one-shot routes, ON TOP of the kit's — `/join`,
|
|
287
|
+
* `/register`, `/verify-email`, `/reset-password` (a string is that path and everything
|
|
288
|
+
* under it, a RegExp is tested on the path, as `useLastVisitedPage`'s `exclude`). The
|
|
289
|
+
* URL section shows such a page as text with Copy, never as a link: it acts on load,
|
|
290
|
+
* and a report's page is the reporter's to write. Any other page links its PATH alone;
|
|
291
|
+
* the query, where tokens and `?next=` targets travel, is text. keksdose:
|
|
292
|
+
* `["/banks/callback", "/share-target"]`. Keep it stable (module scope).
|
|
293
|
+
*/
|
|
294
|
+
pageLinkExclude?: readonly PathPattern[];
|
|
306
295
|
/**
|
|
307
296
|
* The detail's saves COMMIT (description, outcome, rework, status): under a locked
|
|
308
297
|
* `WriteLockProvider` each is `aria-disabled` with the lock's reason, and nothing
|
|
@@ -372,12 +361,12 @@ export interface FeedbackRowDetailProps {
|
|
|
372
361
|
* ```tsx
|
|
373
362
|
* const update = useMutation({ mutationFn: ({ id, ...patch }) => api.updateFeedback(id, patch),
|
|
374
363
|
* onSuccess: invalidate, onError: (e) => toast.error(detailOf(e) ?? toastLabels.updateFailed) });
|
|
375
|
-
* const changeStatus = useFeedbackStatusUndo(update.
|
|
364
|
+
* const changeStatus = useFeedbackStatusUndo(update.mutateAsync);
|
|
376
365
|
* <DataTable
|
|
377
366
|
* isExpanded={(row) => expandedId === row.id}
|
|
378
367
|
* expandedRow={(row) => (
|
|
379
368
|
* <FeedbackRowDetail row={row} canEdit={isAdmin && !mine} viewerId={user?.id}
|
|
380
|
-
* onUpdate={update.
|
|
369
|
+
* onUpdate={update.mutateAsync} pending={update.isPending} statusChange={changeStatus}
|
|
381
370
|
* onUpload={api.uploadFeedbackAttachment} fetcher={authedImageFetcher}
|
|
382
371
|
* onCaptureScreenshot={captureAppScreenshot} />
|
|
383
372
|
* )} … />
|
|
@@ -387,4 +376,4 @@ export interface FeedbackRowDetailProps {
|
|
|
387
376
|
* collapsing the row closes it. Each editor is keyed by the row's id, so a draft is
|
|
388
377
|
* never thrown away by a list refresh under it.
|
|
389
378
|
*/
|
|
390
|
-
export declare function FeedbackRowDetail({ row, canEdit, viewerId, onUpdate, pending, onUpload, fetcher, statusChange, onCaptureScreenshot, reworkAttachment, renderDate, commit, showSubject, onReply, replyAttachments, replying, onRework, comments, onPublicChange, labels: labelsProp, className, }: FeedbackRowDetailProps): import("react").JSX.Element;
|
|
379
|
+
export declare function FeedbackRowDetail({ row, canEdit, viewerId, onUpdate, pending, onUpload, fetcher, statusChange, onCaptureScreenshot, reworkAttachment, renderDate, pageLinkExclude, commit, showSubject, onReply, replyAttachments, replying, onRework, comments, onPublicChange, labels: labelsProp, className, }: FeedbackRowDetailProps): import("react").JSX.Element;
|